blob: e60ad477a82ad060da2ba5db8ca60ad687654bf9 [file] [log] [blame]
deadbeeff137e972017-03-23 15:45:49 -07001/*
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include <time.h>
12
13#if defined(WEBRTC_WIN)
deadbeeff137e972017-03-23 15:45:49 -070014#include <windows.h>
15#include <winsock2.h>
16#include <ws2tcpip.h>
17#define SECURITY_WIN32
18#include <security.h>
19#endif
20
21#include <algorithm>
22
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020023#include "rtc_base/arraysize.h"
24#include "rtc_base/base64.h"
25#include "rtc_base/checks.h"
26#include "rtc_base/cryptstring.h"
27#include "rtc_base/httpcommon-inl.h"
28#include "rtc_base/httpcommon.h"
29#include "rtc_base/messagedigest.h"
30#include "rtc_base/socketaddress.h"
deadbeeff137e972017-03-23 15:45:49 -070031
32namespace rtc {
Tommie51a0a82018-02-27 15:30:29 +010033namespace {
deadbeeff137e972017-03-23 15:45:49 -070034#if defined(WEBRTC_WIN)
Tommie51a0a82018-02-27 15:30:29 +010035///////////////////////////////////////////////////////////////////////////////
36// ConstantToLabel can be used to easily generate string names from constant
37// values. This can be useful for logging descriptive names of error messages.
38// Usage:
39// const ConstantToLabel LIBRARY_ERRORS[] = {
40// KLABEL(SOME_ERROR),
41// KLABEL(SOME_OTHER_ERROR),
42// ...
43// LASTLABEL
44// }
45//
46// int err = LibraryFunc();
47// LOG(LS_ERROR) << "LibraryFunc returned: "
48// << GetErrorName(err, LIBRARY_ERRORS);
49struct ConstantToLabel { int value; const char * label; };
50
51const char* LookupLabel(int value, const ConstantToLabel entries[]) {
52 for (int i = 0; entries[i].label; ++i) {
53 if (value == entries[i].value) {
54 return entries[i].label;
55 }
56 }
57 return 0;
58}
59
60std::string GetErrorName(int err, const ConstantToLabel* err_table) {
61 if (err == 0)
62 return "No error";
63
64 if (err_table != 0) {
65 if (const char* value = LookupLabel(err, err_table))
66 return value;
67 }
68
69 char buffer[16];
70 snprintf(buffer, sizeof(buffer), "0x%08x", err);
71 return buffer;
72}
73
74#define KLABEL(x) { x, #x }
75#define LASTLABEL { 0, 0 }
76
77const ConstantToLabel SECURITY_ERRORS[] = {
78 KLABEL(SEC_I_COMPLETE_AND_CONTINUE),
79 KLABEL(SEC_I_COMPLETE_NEEDED),
80 KLABEL(SEC_I_CONTEXT_EXPIRED),
81 KLABEL(SEC_I_CONTINUE_NEEDED),
82 KLABEL(SEC_I_INCOMPLETE_CREDENTIALS),
83 KLABEL(SEC_I_RENEGOTIATE),
84 KLABEL(SEC_E_CERT_EXPIRED),
85 KLABEL(SEC_E_INCOMPLETE_MESSAGE),
86 KLABEL(SEC_E_INSUFFICIENT_MEMORY),
87 KLABEL(SEC_E_INTERNAL_ERROR),
88 KLABEL(SEC_E_INVALID_HANDLE),
89 KLABEL(SEC_E_INVALID_TOKEN),
90 KLABEL(SEC_E_LOGON_DENIED),
91 KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY),
92 KLABEL(SEC_E_NO_CREDENTIALS),
93 KLABEL(SEC_E_NOT_OWNER),
94 KLABEL(SEC_E_OK),
95 KLABEL(SEC_E_SECPKG_NOT_FOUND),
96 KLABEL(SEC_E_TARGET_UNKNOWN),
97 KLABEL(SEC_E_UNKNOWN_CREDENTIALS),
98 KLABEL(SEC_E_UNSUPPORTED_FUNCTION),
99 KLABEL(SEC_E_UNTRUSTED_ROOT),
100 KLABEL(SEC_E_WRONG_PRINCIPAL),
101 LASTLABEL
102};
103#undef KLABEL
104#undef LASTLABEL
105#endif // defined(WEBRTC_WIN)
106} // namespace
deadbeeff137e972017-03-23 15:45:49 -0700107
108//////////////////////////////////////////////////////////////////////
109// Enum - TODO: expose globally later?
110//////////////////////////////////////////////////////////////////////
111
112bool find_string(size_t& index, const std::string& needle,
113 const char* const haystack[], size_t max_index) {
114 for (index=0; index<max_index; ++index) {
115 if (_stricmp(needle.c_str(), haystack[index]) == 0) {
116 return true;
117 }
118 }
119 return false;
120}
121
122template<class E>
123struct Enum {
124 static const char** Names;
125 static size_t Size;
126
127 static inline const char* Name(E val) { return Names[val]; }
128 static inline bool Parse(E& val, const std::string& name) {
129 size_t index;
130 if (!find_string(index, name, Names, Size))
131 return false;
132 val = static_cast<E>(index);
133 return true;
134 }
135
136 E val;
137
138 inline operator E&() { return val; }
139 inline Enum& operator=(E rhs) { val = rhs; return *this; }
140
141 inline const char* name() const { return Name(val); }
142 inline bool assign(const std::string& name) { return Parse(val, name); }
143 inline Enum& operator=(const std::string& rhs) { assign(rhs); return *this; }
144};
145
146#define ENUM(e,n) \
147 template<> const char** Enum<e>::Names = n; \
148 template<> size_t Enum<e>::Size = sizeof(n)/sizeof(n[0])
149
150//////////////////////////////////////////////////////////////////////
151// HttpCommon
152//////////////////////////////////////////////////////////////////////
153
154static const char* kHttpVersions[HVER_LAST+1] = {
155 "1.0", "1.1", "Unknown"
156};
157ENUM(HttpVersion, kHttpVersions);
158
159static const char* kHttpVerbs[HV_LAST+1] = {
160 "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD"
161};
162ENUM(HttpVerb, kHttpVerbs);
163
164static const char* kHttpHeaders[HH_LAST+1] = {
165 "Age",
166 "Cache-Control",
167 "Connection",
168 "Content-Disposition",
169 "Content-Length",
170 "Content-Range",
171 "Content-Type",
172 "Cookie",
173 "Date",
174 "ETag",
175 "Expires",
176 "Host",
177 "If-Modified-Since",
178 "If-None-Match",
179 "Keep-Alive",
180 "Last-Modified",
181 "Location",
182 "Proxy-Authenticate",
183 "Proxy-Authorization",
184 "Proxy-Connection",
185 "Range",
186 "Set-Cookie",
187 "TE",
188 "Trailers",
189 "Transfer-Encoding",
190 "Upgrade",
191 "User-Agent",
192 "WWW-Authenticate",
193};
194ENUM(HttpHeader, kHttpHeaders);
195
196const char* ToString(HttpVersion version) {
197 return Enum<HttpVersion>::Name(version);
198}
199
200bool FromString(HttpVersion& version, const std::string& str) {
201 return Enum<HttpVersion>::Parse(version, str);
202}
203
204const char* ToString(HttpVerb verb) {
205 return Enum<HttpVerb>::Name(verb);
206}
207
208bool FromString(HttpVerb& verb, const std::string& str) {
209 return Enum<HttpVerb>::Parse(verb, str);
210}
211
212const char* ToString(HttpHeader header) {
213 return Enum<HttpHeader>::Name(header);
214}
215
216bool FromString(HttpHeader& header, const std::string& str) {
217 return Enum<HttpHeader>::Parse(header, str);
218}
219
220bool HttpCodeHasBody(uint32_t code) {
221 return !HttpCodeIsInformational(code)
222 && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
223}
224
225bool HttpCodeIsCacheable(uint32_t code) {
226 switch (code) {
227 case HC_OK:
228 case HC_NON_AUTHORITATIVE:
229 case HC_PARTIAL_CONTENT:
230 case HC_MULTIPLE_CHOICES:
231 case HC_MOVED_PERMANENTLY:
232 case HC_GONE:
233 return true;
234 default:
235 return false;
236 }
237}
238
239bool HttpHeaderIsEndToEnd(HttpHeader header) {
240 switch (header) {
241 case HH_CONNECTION:
242 case HH_KEEP_ALIVE:
243 case HH_PROXY_AUTHENTICATE:
244 case HH_PROXY_AUTHORIZATION:
245 case HH_PROXY_CONNECTION: // Note part of RFC... this is non-standard header
246 case HH_TE:
247 case HH_TRAILERS:
248 case HH_TRANSFER_ENCODING:
249 case HH_UPGRADE:
250 return false;
251 default:
252 return true;
253 }
254}
255
256bool HttpHeaderIsCollapsible(HttpHeader header) {
257 switch (header) {
258 case HH_SET_COOKIE:
259 case HH_PROXY_AUTHENTICATE:
260 case HH_WWW_AUTHENTICATE:
261 return false;
262 default:
263 return true;
264 }
265}
266
267bool HttpShouldKeepAlive(const HttpData& data) {
268 std::string connection;
269 if ((data.hasHeader(HH_PROXY_CONNECTION, &connection)
270 || data.hasHeader(HH_CONNECTION, &connection))) {
271 return (_stricmp(connection.c_str(), "Keep-Alive") == 0);
272 }
273 return (data.version >= HVER_1_1);
274}
275
276namespace {
277
278inline bool IsEndOfAttributeName(size_t pos, size_t len, const char * data) {
279 if (pos >= len)
280 return true;
281 if (isspace(static_cast<unsigned char>(data[pos])))
282 return true;
283 // The reason for this complexity is that some attributes may contain trailing
284 // equal signs (like base64 tokens in Negotiate auth headers)
285 if ((pos+1 < len) && (data[pos] == '=') &&
286 !isspace(static_cast<unsigned char>(data[pos+1])) &&
287 (data[pos+1] != '=')) {
288 return true;
289 }
290 return false;
291}
292
deadbeeff137e972017-03-23 15:45:49 -0700293} // anonymous namespace
294
deadbeeff137e972017-03-23 15:45:49 -0700295void HttpParseAttributes(const char * data, size_t len,
296 HttpAttributeList& attributes) {
297 size_t pos = 0;
298 while (true) {
299 // Skip leading whitespace
300 while ((pos < len) && isspace(static_cast<unsigned char>(data[pos]))) {
301 ++pos;
302 }
303
304 // End of attributes?
305 if (pos >= len)
306 return;
307
308 // Find end of attribute name
309 size_t start = pos;
310 while (!IsEndOfAttributeName(pos, len, data)) {
311 ++pos;
312 }
313
314 HttpAttribute attribute;
315 attribute.first.assign(data + start, data + pos);
316
317 // Attribute has value?
318 if ((pos < len) && (data[pos] == '=')) {
319 ++pos; // Skip '='
320 // Check if quoted value
321 if ((pos < len) && (data[pos] == '"')) {
322 while (++pos < len) {
323 if (data[pos] == '"') {
324 ++pos;
325 break;
326 }
327 if ((data[pos] == '\\') && (pos + 1 < len))
328 ++pos;
329 attribute.second.append(1, data[pos]);
330 }
331 } else {
332 while ((pos < len) &&
333 !isspace(static_cast<unsigned char>(data[pos])) &&
334 (data[pos] != ',')) {
335 attribute.second.append(1, data[pos++]);
336 }
337 }
338 }
339
340 attributes.push_back(attribute);
341 if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ','
342 }
343}
344
345bool HttpHasAttribute(const HttpAttributeList& attributes,
346 const std::string& name,
347 std::string* value) {
348 for (HttpAttributeList::const_iterator it = attributes.begin();
349 it != attributes.end(); ++it) {
350 if (it->first == name) {
351 if (value) {
352 *value = it->second;
353 }
354 return true;
355 }
356 }
357 return false;
358}
359
360bool HttpHasNthAttribute(HttpAttributeList& attributes,
361 size_t index,
362 std::string* name,
363 std::string* value) {
364 if (index >= attributes.size())
365 return false;
366
367 if (name)
368 *name = attributes[index].first;
369 if (value)
370 *value = attributes[index].second;
371 return true;
372}
373
374bool HttpDateToSeconds(const std::string& date, time_t* seconds) {
375 const char* const kTimeZones[] = {
376 "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT",
377 "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M",
378 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y"
379 };
380 const int kTimeZoneOffsets[] = {
381 0, 0, -5, -4, -6, -5, -7, -6, -8, -7,
382 -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12,
383 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
384 };
385
386 RTC_DCHECK(nullptr != seconds);
387 struct tm tval;
388 memset(&tval, 0, sizeof(tval));
389 char month[4], zone[6];
390 memset(month, 0, sizeof(month));
391 memset(zone, 0, sizeof(zone));
392
393 if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c",
394 &tval.tm_mday, month, &tval.tm_year,
395 &tval.tm_hour, &tval.tm_min, &tval.tm_sec, zone)) {
396 return false;
397 }
398 switch (toupper(month[2])) {
399 case 'N': tval.tm_mon = (month[1] == 'A') ? 0 : 5; break;
400 case 'B': tval.tm_mon = 1; break;
401 case 'R': tval.tm_mon = (month[0] == 'M') ? 2 : 3; break;
402 case 'Y': tval.tm_mon = 4; break;
403 case 'L': tval.tm_mon = 6; break;
404 case 'G': tval.tm_mon = 7; break;
405 case 'P': tval.tm_mon = 8; break;
406 case 'T': tval.tm_mon = 9; break;
407 case 'V': tval.tm_mon = 10; break;
408 case 'C': tval.tm_mon = 11; break;
409 }
410 tval.tm_year -= 1900;
411 time_t gmt, non_gmt = mktime(&tval);
412 if ((zone[0] == '+') || (zone[0] == '-')) {
413 if (!isdigit(zone[1]) || !isdigit(zone[2])
414 || !isdigit(zone[3]) || !isdigit(zone[4])) {
415 return false;
416 }
417 int hours = (zone[1] - '0') * 10 + (zone[2] - '0');
418 int minutes = (zone[3] - '0') * 10 + (zone[4] - '0');
419 int offset = (hours * 60 + minutes) * 60;
420 gmt = non_gmt + ((zone[0] == '+') ? offset : -offset);
421 } else {
422 size_t zindex;
423 if (!find_string(zindex, zone, kTimeZones, arraysize(kTimeZones))) {
424 return false;
425 }
426 gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60;
427 }
428 // TODO: Android should support timezone, see b/2441195
429#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(WEBRTC_ANDROID) || defined(BSD)
430 tm *tm_for_timezone = localtime(&gmt);
431 *seconds = gmt + tm_for_timezone->tm_gmtoff;
432#else
433#if defined(_MSC_VER) && _MSC_VER >= 1900
434 long timezone = 0;
435 _get_timezone(&timezone);
436#endif
437 *seconds = gmt - timezone;
438#endif
439 return true;
440}
441
442std::string HttpAddress(const SocketAddress& address, bool secure) {
443 return (address.port() == HttpDefaultPort(secure))
444 ? address.hostname() : address.ToString();
445}
446
447//////////////////////////////////////////////////////////////////////
448// HttpData
449//////////////////////////////////////////////////////////////////////
450
451HttpData::HttpData() : version(HVER_1_1) {
452}
453
454HttpData::~HttpData() = default;
455
456void
457HttpData::clear(bool release_document) {
458 // Clear headers first, since releasing a document may have far-reaching
459 // effects.
460 headers_.clear();
461 if (release_document) {
462 document.reset();
463 }
464}
465
466void
467HttpData::copy(const HttpData& src) {
468 headers_ = src.headers_;
469}
470
471void
472HttpData::changeHeader(const std::string& name, const std::string& value,
473 HeaderCombine combine) {
474 if (combine == HC_AUTO) {
475 HttpHeader header;
476 // Unrecognized headers are collapsible
477 combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
478 ? HC_YES : HC_NO;
479 } else if (combine == HC_REPLACE) {
480 headers_.erase(name);
481 combine = HC_NO;
482 }
483 // At this point, combine is one of (YES, NO, NEW)
484 if (combine != HC_NO) {
485 HeaderMap::iterator it = headers_.find(name);
486 if (it != headers_.end()) {
487 if (combine == HC_YES) {
488 it->second.append(",");
489 it->second.append(value);
490 }
491 return;
492 }
493 }
494 headers_.insert(HeaderMap::value_type(name, value));
495}
496
497size_t HttpData::clearHeader(const std::string& name) {
498 return headers_.erase(name);
499}
500
501HttpData::iterator HttpData::clearHeader(iterator header) {
502 iterator deprecated = header++;
503 headers_.erase(deprecated);
504 return header;
505}
506
507bool
508HttpData::hasHeader(const std::string& name, std::string* value) const {
509 HeaderMap::const_iterator it = headers_.find(name);
510 if (it == headers_.end()) {
511 return false;
512 } else if (value) {
513 *value = it->second;
514 }
515 return true;
516}
517
518void HttpData::setContent(const std::string& content_type,
519 StreamInterface* document) {
520 setHeader(HH_CONTENT_TYPE, content_type);
521 setDocumentAndLength(document);
522}
523
524void HttpData::setDocumentAndLength(StreamInterface* document) {
525 // TODO: Consider calling Rewind() here?
526 RTC_DCHECK(!hasHeader(HH_CONTENT_LENGTH, nullptr));
527 RTC_DCHECK(!hasHeader(HH_TRANSFER_ENCODING, nullptr));
528 RTC_DCHECK(document != nullptr);
529 this->document.reset(document);
530 size_t content_length = 0;
531 if (this->document->GetAvailable(&content_length)) {
532 char buffer[32];
533 sprintfn(buffer, sizeof(buffer), "%d", content_length);
534 setHeader(HH_CONTENT_LENGTH, buffer);
535 } else {
536 setHeader(HH_TRANSFER_ENCODING, "chunked");
537 }
538}
539
540//
541// HttpRequestData
542//
543
544void
545HttpRequestData::clear(bool release_document) {
546 verb = HV_GET;
547 path.clear();
548 HttpData::clear(release_document);
549}
550
551void
552HttpRequestData::copy(const HttpRequestData& src) {
553 verb = src.verb;
554 path = src.path;
555 HttpData::copy(src);
556}
557
558size_t
559HttpRequestData::formatLeader(char* buffer, size_t size) const {
560 RTC_DCHECK(path.find(' ') == std::string::npos);
561 return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(),
562 path.data(), ToString(version));
563}
564
565HttpError
566HttpRequestData::parseLeader(const char* line, size_t len) {
567 unsigned int vmajor, vminor;
568 int vend, dstart, dend;
569 // sscanf isn't safe with strings that aren't null-terminated, and there is
570 // no guarantee that |line| is. Create a local copy that is null-terminated.
571 std::string line_str(line, len);
572 line = line_str.c_str();
573 if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u",
574 &vend, &dstart, &dend, &vmajor, &vminor) != 2)
575 || (vmajor != 1)) {
576 return HE_PROTOCOL;
577 }
578 if (vminor == 0) {
579 version = HVER_1_0;
580 } else if (vminor == 1) {
581 version = HVER_1_1;
582 } else {
583 return HE_PROTOCOL;
584 }
585 std::string sverb(line, vend);
586 if (!FromString(verb, sverb.c_str())) {
587 return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
588 }
589 path.assign(line + dstart, line + dend);
590 return HE_NONE;
591}
592
593bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
594 if (HV_CONNECT == verb)
595 return false;
596 Url<char> url(path);
597 if (url.valid()) {
598 uri->assign(path);
599 return true;
600 }
601 std::string host;
602 if (!hasHeader(HH_HOST, &host))
603 return false;
604 url.set_address(host);
605 url.set_full_path(path);
606 uri->assign(url.url());
607 return url.valid();
608}
609
610bool HttpRequestData::getRelativeUri(std::string* host,
611 std::string* path) const
612{
613 if (HV_CONNECT == verb)
614 return false;
615 Url<char> url(this->path);
616 if (url.valid()) {
617 host->assign(url.address());
618 path->assign(url.full_path());
619 return true;
620 }
621 if (!hasHeader(HH_HOST, host))
622 return false;
623 path->assign(this->path);
624 return true;
625}
626
627//
628// HttpResponseData
629//
630
631void
632HttpResponseData::clear(bool release_document) {
633 scode = HC_INTERNAL_SERVER_ERROR;
634 message.clear();
635 HttpData::clear(release_document);
636}
637
638void
639HttpResponseData::copy(const HttpResponseData& src) {
640 scode = src.scode;
641 message = src.message;
642 HttpData::copy(src);
643}
644
645void HttpResponseData::set_success(uint32_t scode) {
646 this->scode = scode;
647 message.clear();
648 setHeader(HH_CONTENT_LENGTH, "0", false);
649}
650
651void HttpResponseData::set_success(const std::string& content_type,
652 StreamInterface* document,
653 uint32_t scode) {
654 this->scode = scode;
655 message.erase(message.begin(), message.end());
656 setContent(content_type, document);
657}
658
659void HttpResponseData::set_redirect(const std::string& location,
660 uint32_t scode) {
661 this->scode = scode;
662 message.clear();
663 setHeader(HH_LOCATION, location);
664 setHeader(HH_CONTENT_LENGTH, "0", false);
665}
666
667void HttpResponseData::set_error(uint32_t scode) {
668 this->scode = scode;
669 message.clear();
670 setHeader(HH_CONTENT_LENGTH, "0", false);
671}
672
673size_t
674HttpResponseData::formatLeader(char* buffer, size_t size) const {
675 size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
676 if (!message.empty()) {
677 len += sprintfn(buffer + len, size - len, " %.*s",
678 message.size(), message.data());
679 }
680 return len;
681}
682
683HttpError
684HttpResponseData::parseLeader(const char* line, size_t len) {
685 size_t pos = 0;
686 unsigned int vmajor, vminor, temp_scode;
687 int temp_pos;
688 // sscanf isn't safe with strings that aren't null-terminated, and there is
689 // no guarantee that |line| is. Create a local copy that is null-terminated.
690 std::string line_str(line, len);
691 line = line_str.c_str();
692 if (sscanf(line, "HTTP %u%n",
693 &temp_scode, &temp_pos) == 1) {
694 // This server's response has no version. :( NOTE: This happens for every
695 // response to requests made from Chrome plugins, regardless of the server's
696 // behaviour.
Mirko Bonadei675513b2017-11-09 11:09:25 +0100697 RTC_LOG(LS_VERBOSE) << "HTTP version missing from response";
deadbeeff137e972017-03-23 15:45:49 -0700698 version = HVER_UNKNOWN;
699 } else if ((sscanf(line, "HTTP/%u.%u %u%n",
700 &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
701 && (vmajor == 1)) {
702 // This server's response does have a version.
703 if (vminor == 0) {
704 version = HVER_1_0;
705 } else if (vminor == 1) {
706 version = HVER_1_1;
707 } else {
708 return HE_PROTOCOL;
709 }
710 } else {
711 return HE_PROTOCOL;
712 }
713 scode = temp_scode;
714 pos = static_cast<size_t>(temp_pos);
715 while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
716 message.assign(line + pos, len - pos);
717 return HE_NONE;
718}
719
720//////////////////////////////////////////////////////////////////////
721// Http Authentication
722//////////////////////////////////////////////////////////////////////
723
724std::string quote(const std::string& str) {
725 std::string result;
726 result.push_back('"');
727 for (size_t i=0; i<str.size(); ++i) {
728 if ((str[i] == '"') || (str[i] == '\\'))
729 result.push_back('\\');
730 result.push_back(str[i]);
731 }
732 result.push_back('"');
733 return result;
734}
735
736#if defined(WEBRTC_WIN)
737struct NegotiateAuthContext : public HttpAuthContext {
738 CredHandle cred;
739 CtxtHandle ctx;
740 size_t steps;
741 bool specified_credentials;
742
743 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
744 : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
745 specified_credentials(false)
746 { }
747
Steve Anton9de3aac2017-10-24 10:08:26 -0700748 ~NegotiateAuthContext() override {
deadbeeff137e972017-03-23 15:45:49 -0700749 DeleteSecurityContext(&ctx);
750 FreeCredentialsHandle(&cred);
751 }
752};
753#endif // WEBRTC_WIN
754
755HttpAuthResult HttpAuthenticate(
756 const char * challenge, size_t len,
757 const SocketAddress& server,
758 const std::string& method, const std::string& uri,
759 const std::string& username, const CryptString& password,
760 HttpAuthContext *& context, std::string& response, std::string& auth_method)
761{
762 HttpAttributeList args;
763 HttpParseAttributes(challenge, len, args);
764 HttpHasNthAttribute(args, 0, &auth_method, nullptr);
765
766 if (context && (context->auth_method != auth_method))
767 return HAR_IGNORE;
768
769 // BASIC
770 if (_stricmp(auth_method.c_str(), "basic") == 0) {
771 if (context)
772 return HAR_CREDENTIALS; // Bad credentials
773 if (username.empty())
774 return HAR_CREDENTIALS; // Missing credentials
775
776 context = new HttpAuthContext(auth_method);
777
778 // TODO: convert sensitive to a secure buffer that gets securely deleted
779 //std::string decoded = username + ":" + password;
780 size_t len = username.size() + password.GetLength() + 2;
781 char * sensitive = new char[len];
782 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
783 pos += strcpyn(sensitive + pos, len - pos, ":");
784 password.CopyTo(sensitive + pos, true);
785
786 response = auth_method;
787 response.append(" ");
788 // TODO: create a sensitive-source version of Base64::encode
789 response.append(Base64::Encode(sensitive));
790 memset(sensitive, 0, len);
791 delete [] sensitive;
792 return HAR_RESPONSE;
793 }
794
795 // DIGEST
796 if (_stricmp(auth_method.c_str(), "digest") == 0) {
797 if (context)
798 return HAR_CREDENTIALS; // Bad credentials
799 if (username.empty())
800 return HAR_CREDENTIALS; // Missing credentials
801
802 context = new HttpAuthContext(auth_method);
803
804 std::string cnonce, ncount;
805 char buffer[256];
806 sprintf(buffer, "%d", static_cast<int>(time(0)));
807 cnonce = MD5(buffer);
808 ncount = "00000001";
809
810 std::string realm, nonce, qop, opaque;
811 HttpHasAttribute(args, "realm", &realm);
812 HttpHasAttribute(args, "nonce", &nonce);
813 bool has_qop = HttpHasAttribute(args, "qop", &qop);
814 bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
815
816 // TODO: convert sensitive to be secure buffer
817 //std::string A1 = username + ":" + realm + ":" + password;
818 size_t len = username.size() + realm.size() + password.GetLength() + 3;
819 char * sensitive = new char[len]; // A1
820 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
821 pos += strcpyn(sensitive + pos, len - pos, ":");
822 pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
823 pos += strcpyn(sensitive + pos, len - pos, ":");
824 password.CopyTo(sensitive + pos, true);
825
826 std::string A2 = method + ":" + uri;
827 std::string middle;
828 if (has_qop) {
829 qop = "auth";
830 middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
831 } else {
832 middle = nonce;
833 }
834 std::string HA1 = MD5(sensitive);
835 memset(sensitive, 0, len);
836 delete [] sensitive;
837 std::string HA2 = MD5(A2);
838 std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
839
840 std::stringstream ss;
841 ss << auth_method;
842 ss << " username=" << quote(username);
843 ss << ", realm=" << quote(realm);
844 ss << ", nonce=" << quote(nonce);
845 ss << ", uri=" << quote(uri);
846 if (has_qop) {
847 ss << ", qop=" << qop;
848 ss << ", nc=" << ncount;
849 ss << ", cnonce=" << quote(cnonce);
850 }
851 ss << ", response=\"" << dig_response << "\"";
852 if (has_opaque) {
853 ss << ", opaque=" << quote(opaque);
854 }
855 response = ss.str();
856 return HAR_RESPONSE;
857 }
858
859#if defined(WEBRTC_WIN)
860#if 1
861 bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
862 bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
863 // SPNEGO & NTLM
864 if (want_negotiate || want_ntlm) {
865 const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
866 char out_buf[MAX_MESSAGE], spn[MAX_SPN];
867
868#if 0 // Requires funky windows versions
869 DWORD len = MAX_SPN;
870 if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), nullptr,
871 server.port(),
872 0, &len, spn) != ERROR_SUCCESS) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100873 RTC_LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
deadbeeff137e972017-03-23 15:45:49 -0700874 return HAR_IGNORE;
875 }
876#else
877 sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
878#endif
879
880 SecBuffer out_sec;
881 out_sec.pvBuffer = out_buf;
882 out_sec.cbBuffer = sizeof(out_buf);
883 out_sec.BufferType = SECBUFFER_TOKEN;
884
885 SecBufferDesc out_buf_desc;
886 out_buf_desc.ulVersion = 0;
887 out_buf_desc.cBuffers = 1;
888 out_buf_desc.pBuffers = &out_sec;
889
890 const ULONG NEG_FLAGS_DEFAULT =
891 //ISC_REQ_ALLOCATE_MEMORY
892 ISC_REQ_CONFIDENTIALITY
893 //| ISC_REQ_EXTENDED_ERROR
894 //| ISC_REQ_INTEGRITY
895 | ISC_REQ_REPLAY_DETECT
896 | ISC_REQ_SEQUENCE_DETECT
897 //| ISC_REQ_STREAM
898 //| ISC_REQ_USE_SUPPLIED_CREDS
899 ;
900
901 ::TimeStamp lifetime;
902 SECURITY_STATUS ret = S_OK;
903 ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
904
905 bool specify_credentials = !username.empty();
906 size_t steps = 0;
907
908 // uint32_t now = Time();
909
910 NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
911 if (neg) {
912 const size_t max_steps = 10;
913 if (++neg->steps >= max_steps) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100914 RTC_LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) "
915 "too many retries";
deadbeeff137e972017-03-23 15:45:49 -0700916 return HAR_ERROR;
917 }
918 steps = neg->steps;
919
920 std::string challenge, decoded_challenge;
921 if (HttpHasNthAttribute(args, 1, &challenge, nullptr) &&
922 Base64::Decode(challenge, Base64::DO_STRICT, &decoded_challenge,
923 nullptr)) {
924 SecBuffer in_sec;
925 in_sec.pvBuffer = const_cast<char *>(decoded_challenge.data());
926 in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
927 in_sec.BufferType = SECBUFFER_TOKEN;
928
929 SecBufferDesc in_buf_desc;
930 in_buf_desc.ulVersion = 0;
931 in_buf_desc.cBuffers = 1;
932 in_buf_desc.pBuffers = &in_sec;
933
934 ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700935 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100936 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +0100937 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700938 return HAR_ERROR;
939 }
940 } else if (neg->specified_credentials) {
941 // Try again with default credentials
942 specify_credentials = false;
943 delete context;
944 context = neg = 0;
945 } else {
946 return HAR_CREDENTIALS;
947 }
948 }
949
950 if (!neg) {
951 unsigned char userbuf[256], passbuf[256], domainbuf[16];
952 SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
953 if (specify_credentials) {
954 memset(&auth_id, 0, sizeof(auth_id));
955 size_t len = password.GetLength()+1;
956 char * sensitive = new char[len];
957 password.CopyTo(sensitive, true);
958 std::string::size_type pos = username.find('\\');
959 if (pos == std::string::npos) {
960 auth_id.UserLength = static_cast<unsigned long>(
961 std::min(sizeof(userbuf) - 1, username.size()));
962 memcpy(userbuf, username.c_str(), auth_id.UserLength);
963 userbuf[auth_id.UserLength] = 0;
964 auth_id.DomainLength = 0;
965 domainbuf[auth_id.DomainLength] = 0;
966 auth_id.PasswordLength = static_cast<unsigned long>(
967 std::min(sizeof(passbuf) - 1, password.GetLength()));
968 memcpy(passbuf, sensitive, auth_id.PasswordLength);
969 passbuf[auth_id.PasswordLength] = 0;
970 } else {
971 auth_id.UserLength = static_cast<unsigned long>(
972 std::min(sizeof(userbuf) - 1, username.size() - pos - 1));
973 memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
974 userbuf[auth_id.UserLength] = 0;
975 auth_id.DomainLength =
976 static_cast<unsigned long>(std::min(sizeof(domainbuf) - 1, pos));
977 memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
978 domainbuf[auth_id.DomainLength] = 0;
979 auth_id.PasswordLength = static_cast<unsigned long>(
980 std::min(sizeof(passbuf) - 1, password.GetLength()));
981 memcpy(passbuf, sensitive, auth_id.PasswordLength);
982 passbuf[auth_id.PasswordLength] = 0;
983 }
984 memset(sensitive, 0, len);
985 delete [] sensitive;
986 auth_id.User = userbuf;
987 auth_id.Domain = domainbuf;
988 auth_id.Password = passbuf;
989 auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
990 pauth_id = &auth_id;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100991 RTC_LOG(LS_VERBOSE)
992 << "Negotiate protocol: Using specified credentials";
deadbeeff137e972017-03-23 15:45:49 -0700993 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100994 RTC_LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
deadbeeff137e972017-03-23 15:45:49 -0700995 }
996
997 CredHandle cred;
998 ret = AcquireCredentialsHandleA(
999 0, const_cast<char*>(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A),
1000 SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -07001001 if (ret != SEC_E_OK) {
Mirko Bonadei675513b2017-11-09 11:09:25 +01001002 RTC_LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
Tommie51a0a82018-02-27 15:30:29 +01001003 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -07001004 return HAR_IGNORE;
1005 }
1006
1007 //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
1008
1009 CtxtHandle ctx;
1010 ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -07001011 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +01001012 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +01001013 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -07001014 FreeCredentialsHandle(&cred);
1015 return HAR_IGNORE;
1016 }
1017
1018 RTC_DCHECK(!context);
1019 context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
1020 neg->specified_credentials = specify_credentials;
1021 neg->steps = steps;
1022 }
1023
1024 if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
1025 ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
Mirko Bonadei675513b2017-11-09 11:09:25 +01001026 RTC_LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
Tommie51a0a82018-02-27 15:30:29 +01001027 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -07001028 if (FAILED(ret)) {
1029 return HAR_ERROR;
1030 }
1031 }
1032
deadbeeff137e972017-03-23 15:45:49 -07001033 std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
1034 response = auth_method;
1035 response.append(" ");
1036 response.append(Base64::Encode(decoded));
1037 return HAR_RESPONSE;
1038 }
1039#endif
1040#endif // WEBRTC_WIN
1041
1042 return HAR_IGNORE;
1043}
1044
1045//////////////////////////////////////////////////////////////////////
1046
1047} // namespace rtc