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