blob: 40a9cb97aae11563a95c5970a6d414f66ff602d0 [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"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020024#include "rtc_base/checks.h"
25#include "rtc_base/cryptstring.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020026#include "rtc_base/httpcommon.h"
27#include "rtc_base/messagedigest.h"
28#include "rtc_base/socketaddress.h"
Jonas Olsson366a50c2018-09-06 13:41:30 +020029#include "rtc_base/strings/string_builder.h"
Artem Titova76af0c2018-07-23 17:38:12 +020030#include "rtc_base/third_party/base64/base64.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);
Yves Gerey665174f2018-06-19 15:03:05 +020050struct ConstantToLabel {
51 int value;
52 const char* label;
53};
Tommie51a0a82018-02-27 15:30:29 +010054
55const char* LookupLabel(int value, const ConstantToLabel entries[]) {
56 for (int i = 0; entries[i].label; ++i) {
57 if (value == entries[i].value) {
58 return entries[i].label;
59 }
60 }
61 return 0;
62}
63
64std::string GetErrorName(int err, const ConstantToLabel* err_table) {
65 if (err == 0)
66 return "No error";
67
68 if (err_table != 0) {
69 if (const char* value = LookupLabel(err, err_table))
70 return value;
71 }
72
73 char buffer[16];
74 snprintf(buffer, sizeof(buffer), "0x%08x", err);
75 return buffer;
76}
77
Yves Gerey665174f2018-06-19 15:03:05 +020078#define KLABEL(x) \
79 { x, #x }
80#define LASTLABEL \
81 { 0, 0 }
Tommie51a0a82018-02-27 15:30:29 +010082
83const ConstantToLabel SECURITY_ERRORS[] = {
Yves Gerey665174f2018-06-19 15:03:05 +020084 KLABEL(SEC_I_COMPLETE_AND_CONTINUE),
85 KLABEL(SEC_I_COMPLETE_NEEDED),
86 KLABEL(SEC_I_CONTEXT_EXPIRED),
87 KLABEL(SEC_I_CONTINUE_NEEDED),
88 KLABEL(SEC_I_INCOMPLETE_CREDENTIALS),
89 KLABEL(SEC_I_RENEGOTIATE),
90 KLABEL(SEC_E_CERT_EXPIRED),
91 KLABEL(SEC_E_INCOMPLETE_MESSAGE),
92 KLABEL(SEC_E_INSUFFICIENT_MEMORY),
93 KLABEL(SEC_E_INTERNAL_ERROR),
94 KLABEL(SEC_E_INVALID_HANDLE),
95 KLABEL(SEC_E_INVALID_TOKEN),
96 KLABEL(SEC_E_LOGON_DENIED),
97 KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY),
98 KLABEL(SEC_E_NO_CREDENTIALS),
99 KLABEL(SEC_E_NOT_OWNER),
100 KLABEL(SEC_E_OK),
101 KLABEL(SEC_E_SECPKG_NOT_FOUND),
102 KLABEL(SEC_E_TARGET_UNKNOWN),
103 KLABEL(SEC_E_UNKNOWN_CREDENTIALS),
104 KLABEL(SEC_E_UNSUPPORTED_FUNCTION),
105 KLABEL(SEC_E_UNTRUSTED_ROOT),
106 KLABEL(SEC_E_WRONG_PRINCIPAL),
107 LASTLABEL};
Tommie51a0a82018-02-27 15:30:29 +0100108#undef KLABEL
109#undef LASTLABEL
110#endif // defined(WEBRTC_WIN)
111} // namespace
deadbeeff137e972017-03-23 15:45:49 -0700112
113//////////////////////////////////////////////////////////////////////
114// Enum - TODO: expose globally later?
115//////////////////////////////////////////////////////////////////////
116
Yves Gerey665174f2018-06-19 15:03:05 +0200117bool find_string(size_t& index,
118 const std::string& needle,
119 const char* const haystack[],
120 size_t max_index) {
121 for (index = 0; index < max_index; ++index) {
deadbeeff137e972017-03-23 15:45:49 -0700122 if (_stricmp(needle.c_str(), haystack[index]) == 0) {
123 return true;
124 }
125 }
126 return false;
127}
128
Yves Gerey665174f2018-06-19 15:03:05 +0200129template <class E>
deadbeeff137e972017-03-23 15:45:49 -0700130struct Enum {
131 static const char** Names;
132 static size_t Size;
133
134 static inline const char* Name(E val) { return Names[val]; }
135 static inline bool Parse(E& val, const std::string& name) {
136 size_t index;
137 if (!find_string(index, name, Names, Size))
138 return false;
139 val = static_cast<E>(index);
140 return true;
141 }
142
143 E val;
144
145 inline operator E&() { return val; }
Yves Gerey665174f2018-06-19 15:03:05 +0200146 inline Enum& operator=(E rhs) {
147 val = rhs;
148 return *this;
149 }
deadbeeff137e972017-03-23 15:45:49 -0700150
151 inline const char* name() const { return Name(val); }
152 inline bool assign(const std::string& name) { return Parse(val, name); }
Yves Gerey665174f2018-06-19 15:03:05 +0200153 inline Enum& operator=(const std::string& rhs) {
154 assign(rhs);
155 return *this;
156 }
deadbeeff137e972017-03-23 15:45:49 -0700157};
158
Yves Gerey665174f2018-06-19 15:03:05 +0200159#define ENUM(e, n) \
160 template <> \
161 const char** Enum<e>::Names = n; \
162 template <> \
163 size_t Enum<e>::Size = sizeof(n) / sizeof(n[0])
deadbeeff137e972017-03-23 15:45:49 -0700164
165//////////////////////////////////////////////////////////////////////
166// HttpCommon
167//////////////////////////////////////////////////////////////////////
168
Yves Gerey665174f2018-06-19 15:03:05 +0200169static const char* kHttpVersions[HVER_LAST + 1] = {"1.0", "1.1", "Unknown"};
deadbeeff137e972017-03-23 15:45:49 -0700170ENUM(HttpVersion, kHttpVersions);
171
Yves Gerey665174f2018-06-19 15:03:05 +0200172static const char* kHttpHeaders[HH_LAST + 1] = {
173 "Age",
174 "Cache-Control",
175 "Connection",
176 "Content-Disposition",
177 "Content-Length",
178 "Content-Range",
179 "Content-Type",
180 "Cookie",
181 "Date",
182 "ETag",
183 "Expires",
184 "Host",
185 "If-Modified-Since",
186 "If-None-Match",
187 "Keep-Alive",
188 "Last-Modified",
189 "Location",
190 "Proxy-Authenticate",
191 "Proxy-Authorization",
192 "Proxy-Connection",
193 "Range",
194 "Set-Cookie",
195 "TE",
196 "Trailers",
197 "Transfer-Encoding",
198 "Upgrade",
199 "User-Agent",
200 "WWW-Authenticate",
deadbeeff137e972017-03-23 15:45:49 -0700201};
202ENUM(HttpHeader, kHttpHeaders);
203
204const char* ToString(HttpVersion version) {
205 return Enum<HttpVersion>::Name(version);
206}
207
208bool FromString(HttpVersion& version, const std::string& str) {
209 return Enum<HttpVersion>::Parse(version, str);
210}
211
deadbeeff137e972017-03-23 15:45:49 -0700212const 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
deadbeeff137e972017-03-23 15:45:49 -0700220bool HttpHeaderIsEndToEnd(HttpHeader header) {
221 switch (header) {
Yves Gerey665174f2018-06-19 15:03:05 +0200222 case HH_CONNECTION:
223 case HH_KEEP_ALIVE:
224 case HH_PROXY_AUTHENTICATE:
225 case HH_PROXY_AUTHORIZATION:
226 case HH_PROXY_CONNECTION: // Note part of RFC... this is non-standard
227 // header
228 case HH_TE:
229 case HH_TRAILERS:
230 case HH_TRANSFER_ENCODING:
231 case HH_UPGRADE:
232 return false;
233 default:
234 return true;
deadbeeff137e972017-03-23 15:45:49 -0700235 }
236}
237
238bool HttpHeaderIsCollapsible(HttpHeader header) {
239 switch (header) {
Yves Gerey665174f2018-06-19 15:03:05 +0200240 case HH_SET_COOKIE:
241 case HH_PROXY_AUTHENTICATE:
242 case HH_WWW_AUTHENTICATE:
243 return false;
244 default:
245 return true;
deadbeeff137e972017-03-23 15:45:49 -0700246 }
247}
248
249bool HttpShouldKeepAlive(const HttpData& data) {
250 std::string connection;
Yves Gerey665174f2018-06-19 15:03:05 +0200251 if ((data.hasHeader(HH_PROXY_CONNECTION, &connection) ||
252 data.hasHeader(HH_CONNECTION, &connection))) {
deadbeeff137e972017-03-23 15:45:49 -0700253 return (_stricmp(connection.c_str(), "Keep-Alive") == 0);
254 }
255 return (data.version >= HVER_1_1);
256}
257
258namespace {
259
Yves Gerey665174f2018-06-19 15:03:05 +0200260inline bool IsEndOfAttributeName(size_t pos, size_t len, const char* data) {
deadbeeff137e972017-03-23 15:45:49 -0700261 if (pos >= len)
262 return true;
263 if (isspace(static_cast<unsigned char>(data[pos])))
264 return true;
265 // The reason for this complexity is that some attributes may contain trailing
266 // equal signs (like base64 tokens in Negotiate auth headers)
Yves Gerey665174f2018-06-19 15:03:05 +0200267 if ((pos + 1 < len) && (data[pos] == '=') &&
268 !isspace(static_cast<unsigned char>(data[pos + 1])) &&
269 (data[pos + 1] != '=')) {
deadbeeff137e972017-03-23 15:45:49 -0700270 return true;
271 }
272 return false;
273}
274
deadbeeff137e972017-03-23 15:45:49 -0700275} // anonymous namespace
276
Yves Gerey665174f2018-06-19 15:03:05 +0200277void HttpParseAttributes(const char* data,
278 size_t len,
deadbeeff137e972017-03-23 15:45:49 -0700279 HttpAttributeList& attributes) {
280 size_t pos = 0;
281 while (true) {
282 // Skip leading whitespace
283 while ((pos < len) && isspace(static_cast<unsigned char>(data[pos]))) {
284 ++pos;
285 }
286
287 // End of attributes?
288 if (pos >= len)
289 return;
290
291 // Find end of attribute name
292 size_t start = pos;
293 while (!IsEndOfAttributeName(pos, len, data)) {
294 ++pos;
295 }
296
297 HttpAttribute attribute;
298 attribute.first.assign(data + start, data + pos);
299
300 // Attribute has value?
301 if ((pos < len) && (data[pos] == '=')) {
Yves Gerey665174f2018-06-19 15:03:05 +0200302 ++pos; // Skip '='
deadbeeff137e972017-03-23 15:45:49 -0700303 // Check if quoted value
304 if ((pos < len) && (data[pos] == '"')) {
305 while (++pos < len) {
306 if (data[pos] == '"') {
307 ++pos;
308 break;
309 }
310 if ((data[pos] == '\\') && (pos + 1 < len))
311 ++pos;
312 attribute.second.append(1, data[pos]);
313 }
314 } else {
Yves Gerey665174f2018-06-19 15:03:05 +0200315 while ((pos < len) && !isspace(static_cast<unsigned char>(data[pos])) &&
316 (data[pos] != ',')) {
deadbeeff137e972017-03-23 15:45:49 -0700317 attribute.second.append(1, data[pos++]);
318 }
319 }
320 }
321
322 attributes.push_back(attribute);
Yves Gerey665174f2018-06-19 15:03:05 +0200323 if ((pos < len) && (data[pos] == ','))
324 ++pos; // Skip ','
deadbeeff137e972017-03-23 15:45:49 -0700325 }
326}
327
328bool HttpHasAttribute(const HttpAttributeList& attributes,
329 const std::string& name,
330 std::string* value) {
331 for (HttpAttributeList::const_iterator it = attributes.begin();
332 it != attributes.end(); ++it) {
333 if (it->first == name) {
334 if (value) {
335 *value = it->second;
336 }
337 return true;
338 }
339 }
340 return false;
341}
342
343bool HttpHasNthAttribute(HttpAttributeList& attributes,
344 size_t index,
345 std::string* name,
346 std::string* value) {
347 if (index >= attributes.size())
348 return false;
349
350 if (name)
351 *name = attributes[index].first;
352 if (value)
353 *value = attributes[index].second;
354 return true;
355}
356
357bool HttpDateToSeconds(const std::string& date, time_t* seconds) {
358 const char* const kTimeZones[] = {
Yves Gerey665174f2018-06-19 15:03:05 +0200359 "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST",
360 "PDT", "A", "B", "C", "D", "E", "F", "G", "H",
361 "I", "K", "L", "M", "N", "O", "P", "Q", "R",
362 "S", "T", "U", "V", "W", "X", "Y"};
deadbeeff137e972017-03-23 15:45:49 -0700363 const int kTimeZoneOffsets[] = {
Yves Gerey665174f2018-06-19 15:03:05 +0200364 0, 0, -5, -4, -6, -5, -7, -6, -8, -7, -1, -2, -3, -4, -5, -6, -7,
365 -8, -9, -10, -11, -12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
deadbeeff137e972017-03-23 15:45:49 -0700366
367 RTC_DCHECK(nullptr != seconds);
368 struct tm tval;
369 memset(&tval, 0, sizeof(tval));
370 char month[4], zone[6];
371 memset(month, 0, sizeof(month));
372 memset(zone, 0, sizeof(zone));
373
Yves Gerey665174f2018-06-19 15:03:05 +0200374 if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c", &tval.tm_mday,
375 month, &tval.tm_year, &tval.tm_hour, &tval.tm_min,
376 &tval.tm_sec, zone)) {
deadbeeff137e972017-03-23 15:45:49 -0700377 return false;
378 }
379 switch (toupper(month[2])) {
Yves Gerey665174f2018-06-19 15:03:05 +0200380 case 'N':
381 tval.tm_mon = (month[1] == 'A') ? 0 : 5;
382 break;
383 case 'B':
384 tval.tm_mon = 1;
385 break;
386 case 'R':
387 tval.tm_mon = (month[0] == 'M') ? 2 : 3;
388 break;
389 case 'Y':
390 tval.tm_mon = 4;
391 break;
392 case 'L':
393 tval.tm_mon = 6;
394 break;
395 case 'G':
396 tval.tm_mon = 7;
397 break;
398 case 'P':
399 tval.tm_mon = 8;
400 break;
401 case 'T':
402 tval.tm_mon = 9;
403 break;
404 case 'V':
405 tval.tm_mon = 10;
406 break;
407 case 'C':
408 tval.tm_mon = 11;
409 break;
deadbeeff137e972017-03-23 15:45:49 -0700410 }
411 tval.tm_year -= 1900;
412 time_t gmt, non_gmt = mktime(&tval);
413 if ((zone[0] == '+') || (zone[0] == '-')) {
Yves Gerey665174f2018-06-19 15:03:05 +0200414 if (!isdigit(zone[1]) || !isdigit(zone[2]) || !isdigit(zone[3]) ||
415 !isdigit(zone[4])) {
deadbeeff137e972017-03-23 15:45:49 -0700416 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 }
Yves Gerey665174f2018-06-19 15:03:05 +0200429// TODO: Android should support timezone, see b/2441195
430#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(WEBRTC_ANDROID) || \
431 defined(BSD)
432 tm* tm_for_timezone = localtime(&gmt);
deadbeeff137e972017-03-23 15:45:49 -0700433 *seconds = gmt + tm_for_timezone->tm_gmtoff;
434#else
435#if defined(_MSC_VER) && _MSC_VER >= 1900
436 long timezone = 0;
437 _get_timezone(&timezone);
438#endif
439 *seconds = gmt - timezone;
440#endif
441 return true;
442}
443
444std::string HttpAddress(const SocketAddress& address, bool secure) {
Yves Gerey665174f2018-06-19 15:03:05 +0200445 return (address.port() == HttpDefaultPort(secure)) ? address.hostname()
446 : address.ToString();
deadbeeff137e972017-03-23 15:45:49 -0700447}
448
449//////////////////////////////////////////////////////////////////////
450// HttpData
451//////////////////////////////////////////////////////////////////////
452
Yves Gerey665174f2018-06-19 15:03:05 +0200453HttpData::HttpData() : version(HVER_1_1) {}
deadbeeff137e972017-03-23 15:45:49 -0700454
455HttpData::~HttpData() = default;
456
Yves Gerey665174f2018-06-19 15:03:05 +0200457void HttpData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700458 // 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
Yves Gerey665174f2018-06-19 15:03:05 +0200466void HttpData::changeHeader(const std::string& name,
467 const std::string& value,
468 HeaderCombine combine) {
deadbeeff137e972017-03-23 15:45:49 -0700469 if (combine == HC_AUTO) {
470 HttpHeader header;
471 // Unrecognized headers are collapsible
472 combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
Yves Gerey665174f2018-06-19 15:03:05 +0200473 ? HC_YES
474 : HC_NO;
deadbeeff137e972017-03-23 15:45:49 -0700475 } else if (combine == HC_REPLACE) {
476 headers_.erase(name);
477 combine = HC_NO;
478 }
479 // At this point, combine is one of (YES, NO, NEW)
480 if (combine != HC_NO) {
481 HeaderMap::iterator it = headers_.find(name);
482 if (it != headers_.end()) {
483 if (combine == HC_YES) {
484 it->second.append(",");
485 it->second.append(value);
486 }
487 return;
488 }
489 }
490 headers_.insert(HeaderMap::value_type(name, value));
491}
492
493size_t HttpData::clearHeader(const std::string& name) {
494 return headers_.erase(name);
495}
496
497HttpData::iterator HttpData::clearHeader(iterator header) {
498 iterator deprecated = header++;
499 headers_.erase(deprecated);
500 return header;
501}
502
Yves Gerey665174f2018-06-19 15:03:05 +0200503bool HttpData::hasHeader(const std::string& name, std::string* value) const {
deadbeeff137e972017-03-23 15:45:49 -0700504 HeaderMap::const_iterator it = headers_.find(name);
505 if (it == headers_.end()) {
506 return false;
507 } else if (value) {
508 *value = it->second;
509 }
510 return true;
511}
512
deadbeeff137e972017-03-23 15:45:49 -0700513//
514// HttpRequestData
515//
516
Yves Gerey665174f2018-06-19 15:03:05 +0200517void HttpRequestData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700518 path.clear();
519 HttpData::clear(release_document);
520}
521
Yves Gerey665174f2018-06-19 15:03:05 +0200522size_t HttpRequestData::formatLeader(char* buffer, size_t size) const {
deadbeeff137e972017-03-23 15:45:49 -0700523 RTC_DCHECK(path.find(' ') == std::string::npos);
Niels Möllerb4731ff2018-06-25 10:41:46 +0200524 return sprintfn(buffer, size, "GET %.*s HTTP/%s", path.size(), path.data(),
525 ToString(version));
deadbeeff137e972017-03-23 15:45:49 -0700526}
527
Yves Gerey665174f2018-06-19 15:03:05 +0200528HttpError HttpRequestData::parseLeader(const char* line, size_t len) {
deadbeeff137e972017-03-23 15:45:49 -0700529 unsigned int vmajor, vminor;
530 int vend, dstart, dend;
531 // sscanf isn't safe with strings that aren't null-terminated, and there is
532 // no guarantee that |line| is. Create a local copy that is null-terminated.
533 std::string line_str(line, len);
534 line = line_str.c_str();
Yves Gerey665174f2018-06-19 15:03:05 +0200535 if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", &vend, &dstart, &dend, &vmajor,
536 &vminor) != 2) ||
537 (vmajor != 1)) {
deadbeeff137e972017-03-23 15:45:49 -0700538 return HE_PROTOCOL;
539 }
540 if (vminor == 0) {
541 version = HVER_1_0;
542 } else if (vminor == 1) {
543 version = HVER_1_1;
544 } else {
545 return HE_PROTOCOL;
546 }
Niels Möllerb4731ff2018-06-25 10:41:46 +0200547 if (vend != 3 || memcmp(line, "GET", 3)) {
Yves Gerey665174f2018-06-19 15:03:05 +0200548 return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
deadbeeff137e972017-03-23 15:45:49 -0700549 }
550 path.assign(line + dstart, line + dend);
551 return HE_NONE;
552}
553
deadbeeff137e972017-03-23 15:45:49 -0700554//
555// HttpResponseData
556//
557
Yves Gerey665174f2018-06-19 15:03:05 +0200558void HttpResponseData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700559 scode = HC_INTERNAL_SERVER_ERROR;
560 message.clear();
561 HttpData::clear(release_document);
562}
563
deadbeeff137e972017-03-23 15:45:49 -0700564void HttpResponseData::set_success(uint32_t scode) {
565 this->scode = scode;
566 message.clear();
567 setHeader(HH_CONTENT_LENGTH, "0", false);
568}
569
deadbeeff137e972017-03-23 15:45:49 -0700570void HttpResponseData::set_error(uint32_t scode) {
571 this->scode = scode;
572 message.clear();
573 setHeader(HH_CONTENT_LENGTH, "0", false);
574}
575
Yves Gerey665174f2018-06-19 15:03:05 +0200576size_t HttpResponseData::formatLeader(char* buffer, size_t size) const {
deadbeeff137e972017-03-23 15:45:49 -0700577 size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
578 if (!message.empty()) {
Yves Gerey665174f2018-06-19 15:03:05 +0200579 len += sprintfn(buffer + len, size - len, " %.*s", message.size(),
580 message.data());
deadbeeff137e972017-03-23 15:45:49 -0700581 }
582 return len;
583}
584
Yves Gerey665174f2018-06-19 15:03:05 +0200585HttpError HttpResponseData::parseLeader(const char* line, size_t len) {
deadbeeff137e972017-03-23 15:45:49 -0700586 size_t pos = 0;
587 unsigned int vmajor, vminor, temp_scode;
588 int temp_pos;
589 // sscanf isn't safe with strings that aren't null-terminated, and there is
590 // no guarantee that |line| is. Create a local copy that is null-terminated.
591 std::string line_str(line, len);
592 line = line_str.c_str();
Yves Gerey665174f2018-06-19 15:03:05 +0200593 if (sscanf(line, "HTTP %u%n", &temp_scode, &temp_pos) == 1) {
deadbeeff137e972017-03-23 15:45:49 -0700594 // This server's response has no version. :( NOTE: This happens for every
595 // response to requests made from Chrome plugins, regardless of the server's
596 // behaviour.
Mirko Bonadei675513b2017-11-09 11:09:25 +0100597 RTC_LOG(LS_VERBOSE) << "HTTP version missing from response";
deadbeeff137e972017-03-23 15:45:49 -0700598 version = HVER_UNKNOWN;
Yves Gerey665174f2018-06-19 15:03:05 +0200599 } else if ((sscanf(line, "HTTP/%u.%u %u%n", &vmajor, &vminor, &temp_scode,
600 &temp_pos) == 3) &&
601 (vmajor == 1)) {
deadbeeff137e972017-03-23 15:45:49 -0700602 // This server's response does have a version.
603 if (vminor == 0) {
604 version = HVER_1_0;
605 } else if (vminor == 1) {
606 version = HVER_1_1;
607 } else {
608 return HE_PROTOCOL;
609 }
610 } else {
611 return HE_PROTOCOL;
612 }
613 scode = temp_scode;
614 pos = static_cast<size_t>(temp_pos);
Yves Gerey665174f2018-06-19 15:03:05 +0200615 while ((pos < len) && isspace(static_cast<unsigned char>(line[pos])))
616 ++pos;
deadbeeff137e972017-03-23 15:45:49 -0700617 message.assign(line + pos, len - pos);
618 return HE_NONE;
619}
620
621//////////////////////////////////////////////////////////////////////
622// Http Authentication
623//////////////////////////////////////////////////////////////////////
624
625std::string quote(const std::string& str) {
626 std::string result;
627 result.push_back('"');
Yves Gerey665174f2018-06-19 15:03:05 +0200628 for (size_t i = 0; i < str.size(); ++i) {
deadbeeff137e972017-03-23 15:45:49 -0700629 if ((str[i] == '"') || (str[i] == '\\'))
630 result.push_back('\\');
631 result.push_back(str[i]);
632 }
633 result.push_back('"');
634 return result;
635}
636
637#if defined(WEBRTC_WIN)
638struct NegotiateAuthContext : public HttpAuthContext {
639 CredHandle cred;
640 CtxtHandle ctx;
641 size_t steps;
642 bool specified_credentials;
643
644 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
Yves Gerey665174f2018-06-19 15:03:05 +0200645 : HttpAuthContext(auth),
646 cred(c1),
647 ctx(c2),
648 steps(0),
649 specified_credentials(false) {}
deadbeeff137e972017-03-23 15:45:49 -0700650
Steve Anton9de3aac2017-10-24 10:08:26 -0700651 ~NegotiateAuthContext() override {
deadbeeff137e972017-03-23 15:45:49 -0700652 DeleteSecurityContext(&ctx);
653 FreeCredentialsHandle(&cred);
654 }
655};
Yves Gerey665174f2018-06-19 15:03:05 +0200656#endif // WEBRTC_WIN
deadbeeff137e972017-03-23 15:45:49 -0700657
Yves Gerey665174f2018-06-19 15:03:05 +0200658HttpAuthResult HttpAuthenticate(const char* challenge,
659 size_t len,
660 const SocketAddress& server,
661 const std::string& method,
662 const std::string& uri,
663 const std::string& username,
664 const CryptString& password,
665 HttpAuthContext*& context,
666 std::string& response,
667 std::string& auth_method) {
deadbeeff137e972017-03-23 15:45:49 -0700668 HttpAttributeList args;
669 HttpParseAttributes(challenge, len, args);
670 HttpHasNthAttribute(args, 0, &auth_method, nullptr);
671
672 if (context && (context->auth_method != auth_method))
673 return HAR_IGNORE;
674
675 // BASIC
676 if (_stricmp(auth_method.c_str(), "basic") == 0) {
677 if (context)
Yves Gerey665174f2018-06-19 15:03:05 +0200678 return HAR_CREDENTIALS; // Bad credentials
deadbeeff137e972017-03-23 15:45:49 -0700679 if (username.empty())
Yves Gerey665174f2018-06-19 15:03:05 +0200680 return HAR_CREDENTIALS; // Missing credentials
deadbeeff137e972017-03-23 15:45:49 -0700681
682 context = new HttpAuthContext(auth_method);
683
Joachim Bauch5b32f232018-03-07 20:02:26 +0100684 // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also
685 // return response as CryptString so contents get securely deleted
686 // automatically.
687 // std::string decoded = username + ":" + password;
deadbeeff137e972017-03-23 15:45:49 -0700688 size_t len = username.size() + password.GetLength() + 2;
Yves Gerey665174f2018-06-19 15:03:05 +0200689 char* sensitive = new char[len];
deadbeeff137e972017-03-23 15:45:49 -0700690 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
691 pos += strcpyn(sensitive + pos, len - pos, ":");
692 password.CopyTo(sensitive + pos, true);
693
694 response = auth_method;
695 response.append(" ");
696 // TODO: create a sensitive-source version of Base64::encode
697 response.append(Base64::Encode(sensitive));
Joachim Bauch5b32f232018-03-07 20:02:26 +0100698 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200699 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700700 return HAR_RESPONSE;
701 }
702
703 // DIGEST
704 if (_stricmp(auth_method.c_str(), "digest") == 0) {
705 if (context)
Yves Gerey665174f2018-06-19 15:03:05 +0200706 return HAR_CREDENTIALS; // Bad credentials
deadbeeff137e972017-03-23 15:45:49 -0700707 if (username.empty())
Yves Gerey665174f2018-06-19 15:03:05 +0200708 return HAR_CREDENTIALS; // Missing credentials
deadbeeff137e972017-03-23 15:45:49 -0700709
710 context = new HttpAuthContext(auth_method);
711
712 std::string cnonce, ncount;
713 char buffer[256];
714 sprintf(buffer, "%d", static_cast<int>(time(0)));
715 cnonce = MD5(buffer);
716 ncount = "00000001";
717
718 std::string realm, nonce, qop, opaque;
719 HttpHasAttribute(args, "realm", &realm);
720 HttpHasAttribute(args, "nonce", &nonce);
721 bool has_qop = HttpHasAttribute(args, "qop", &qop);
722 bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
723
Joachim Bauch5b32f232018-03-07 20:02:26 +0100724 // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also
725 // return response as CryptString so contents get securely deleted
726 // automatically.
727 // std::string A1 = username + ":" + realm + ":" + password;
deadbeeff137e972017-03-23 15:45:49 -0700728 size_t len = username.size() + realm.size() + password.GetLength() + 3;
Yves Gerey665174f2018-06-19 15:03:05 +0200729 char* sensitive = new char[len]; // A1
deadbeeff137e972017-03-23 15:45:49 -0700730 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
731 pos += strcpyn(sensitive + pos, len - pos, ":");
732 pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
733 pos += strcpyn(sensitive + pos, len - pos, ":");
734 password.CopyTo(sensitive + pos, true);
735
736 std::string A2 = method + ":" + uri;
737 std::string middle;
738 if (has_qop) {
739 qop = "auth";
740 middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
741 } else {
742 middle = nonce;
743 }
744 std::string HA1 = MD5(sensitive);
Joachim Bauch5b32f232018-03-07 20:02:26 +0100745 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200746 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700747 std::string HA2 = MD5(A2);
748 std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
749
Jonas Olsson366a50c2018-09-06 13:41:30 +0200750 rtc::StringBuilder ss;
deadbeeff137e972017-03-23 15:45:49 -0700751 ss << auth_method;
752 ss << " username=" << quote(username);
753 ss << ", realm=" << quote(realm);
754 ss << ", nonce=" << quote(nonce);
755 ss << ", uri=" << quote(uri);
756 if (has_qop) {
757 ss << ", qop=" << qop;
Yves Gerey665174f2018-06-19 15:03:05 +0200758 ss << ", nc=" << ncount;
deadbeeff137e972017-03-23 15:45:49 -0700759 ss << ", cnonce=" << quote(cnonce);
760 }
761 ss << ", response=\"" << dig_response << "\"";
762 if (has_opaque) {
763 ss << ", opaque=" << quote(opaque);
764 }
765 response = ss.str();
766 return HAR_RESPONSE;
767 }
768
769#if defined(WEBRTC_WIN)
770#if 1
771 bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
772 bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
773 // SPNEGO & NTLM
774 if (want_negotiate || want_ntlm) {
775 const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
776 char out_buf[MAX_MESSAGE], spn[MAX_SPN];
777
Yves Gerey665174f2018-06-19 15:03:05 +0200778#if 0 // Requires funky windows versions
deadbeeff137e972017-03-23 15:45:49 -0700779 DWORD len = MAX_SPN;
780 if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), nullptr,
781 server.port(),
782 0, &len, spn) != ERROR_SUCCESS) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100783 RTC_LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
deadbeeff137e972017-03-23 15:45:49 -0700784 return HAR_IGNORE;
785 }
786#else
787 sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
788#endif
789
790 SecBuffer out_sec;
Yves Gerey665174f2018-06-19 15:03:05 +0200791 out_sec.pvBuffer = out_buf;
792 out_sec.cbBuffer = sizeof(out_buf);
deadbeeff137e972017-03-23 15:45:49 -0700793 out_sec.BufferType = SECBUFFER_TOKEN;
794
795 SecBufferDesc out_buf_desc;
796 out_buf_desc.ulVersion = 0;
Yves Gerey665174f2018-06-19 15:03:05 +0200797 out_buf_desc.cBuffers = 1;
798 out_buf_desc.pBuffers = &out_sec;
deadbeeff137e972017-03-23 15:45:49 -0700799
800 const ULONG NEG_FLAGS_DEFAULT =
Yves Gerey665174f2018-06-19 15:03:05 +0200801 // ISC_REQ_ALLOCATE_MEMORY
802 ISC_REQ_CONFIDENTIALITY
803 //| ISC_REQ_EXTENDED_ERROR
804 //| ISC_REQ_INTEGRITY
805 | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT
806 //| ISC_REQ_STREAM
807 //| ISC_REQ_USE_SUPPLIED_CREDS
808 ;
deadbeeff137e972017-03-23 15:45:49 -0700809
810 ::TimeStamp lifetime;
811 SECURITY_STATUS ret = S_OK;
812 ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
813
814 bool specify_credentials = !username.empty();
815 size_t steps = 0;
816
817 // uint32_t now = Time();
818
Yves Gerey665174f2018-06-19 15:03:05 +0200819 NegotiateAuthContext* neg = static_cast<NegotiateAuthContext*>(context);
deadbeeff137e972017-03-23 15:45:49 -0700820 if (neg) {
821 const size_t max_steps = 10;
822 if (++neg->steps >= max_steps) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100823 RTC_LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) "
824 "too many retries";
deadbeeff137e972017-03-23 15:45:49 -0700825 return HAR_ERROR;
826 }
827 steps = neg->steps;
828
829 std::string challenge, decoded_challenge;
830 if (HttpHasNthAttribute(args, 1, &challenge, nullptr) &&
831 Base64::Decode(challenge, Base64::DO_STRICT, &decoded_challenge,
832 nullptr)) {
833 SecBuffer in_sec;
Yves Gerey665174f2018-06-19 15:03:05 +0200834 in_sec.pvBuffer = const_cast<char*>(decoded_challenge.data());
835 in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
deadbeeff137e972017-03-23 15:45:49 -0700836 in_sec.BufferType = SECBUFFER_TOKEN;
837
838 SecBufferDesc in_buf_desc;
839 in_buf_desc.ulVersion = 0;
Yves Gerey665174f2018-06-19 15:03:05 +0200840 in_buf_desc.cBuffers = 1;
841 in_buf_desc.pBuffers = &in_sec;
deadbeeff137e972017-03-23 15:45:49 -0700842
Yves Gerey665174f2018-06-19 15:03:05 +0200843 ret = InitializeSecurityContextA(
844 &neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP,
845 &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700846 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100847 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +0100848 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700849 return HAR_ERROR;
850 }
851 } else if (neg->specified_credentials) {
852 // Try again with default credentials
853 specify_credentials = false;
854 delete context;
855 context = neg = 0;
856 } else {
857 return HAR_CREDENTIALS;
858 }
859 }
860
861 if (!neg) {
862 unsigned char userbuf[256], passbuf[256], domainbuf[16];
Yves Gerey665174f2018-06-19 15:03:05 +0200863 SEC_WINNT_AUTH_IDENTITY_A auth_id, *pauth_id = 0;
deadbeeff137e972017-03-23 15:45:49 -0700864 if (specify_credentials) {
865 memset(&auth_id, 0, sizeof(auth_id));
Yves Gerey665174f2018-06-19 15:03:05 +0200866 size_t len = password.GetLength() + 1;
867 char* sensitive = new char[len];
deadbeeff137e972017-03-23 15:45:49 -0700868 password.CopyTo(sensitive, true);
869 std::string::size_type pos = username.find('\\');
870 if (pos == std::string::npos) {
871 auth_id.UserLength = static_cast<unsigned long>(
872 std::min(sizeof(userbuf) - 1, username.size()));
873 memcpy(userbuf, username.c_str(), auth_id.UserLength);
874 userbuf[auth_id.UserLength] = 0;
875 auth_id.DomainLength = 0;
876 domainbuf[auth_id.DomainLength] = 0;
877 auth_id.PasswordLength = static_cast<unsigned long>(
878 std::min(sizeof(passbuf) - 1, password.GetLength()));
879 memcpy(passbuf, sensitive, auth_id.PasswordLength);
880 passbuf[auth_id.PasswordLength] = 0;
881 } else {
882 auth_id.UserLength = static_cast<unsigned long>(
883 std::min(sizeof(userbuf) - 1, username.size() - pos - 1));
884 memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
885 userbuf[auth_id.UserLength] = 0;
886 auth_id.DomainLength =
887 static_cast<unsigned long>(std::min(sizeof(domainbuf) - 1, pos));
888 memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
889 domainbuf[auth_id.DomainLength] = 0;
890 auth_id.PasswordLength = static_cast<unsigned long>(
891 std::min(sizeof(passbuf) - 1, password.GetLength()));
892 memcpy(passbuf, sensitive, auth_id.PasswordLength);
893 passbuf[auth_id.PasswordLength] = 0;
894 }
Joachim Bauch5b32f232018-03-07 20:02:26 +0100895 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200896 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700897 auth_id.User = userbuf;
898 auth_id.Domain = domainbuf;
899 auth_id.Password = passbuf;
900 auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
901 pauth_id = &auth_id;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100902 RTC_LOG(LS_VERBOSE)
903 << "Negotiate protocol: Using specified credentials";
deadbeeff137e972017-03-23 15:45:49 -0700904 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100905 RTC_LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
deadbeeff137e972017-03-23 15:45:49 -0700906 }
907
908 CredHandle cred;
909 ret = AcquireCredentialsHandleA(
910 0, const_cast<char*>(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A),
911 SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700912 if (ret != SEC_E_OK) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100913 RTC_LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
Tommie51a0a82018-02-27 15:30:29 +0100914 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700915 return HAR_IGNORE;
916 }
917
Yves Gerey665174f2018-06-19 15:03:05 +0200918 // CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
deadbeeff137e972017-03-23 15:45:49 -0700919
920 CtxtHandle ctx;
Yves Gerey665174f2018-06-19 15:03:05 +0200921 ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0,
922 SECURITY_NATIVE_DREP, 0, 0, &ctx,
923 &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700924 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100925 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +0100926 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700927 FreeCredentialsHandle(&cred);
928 return HAR_IGNORE;
929 }
930
931 RTC_DCHECK(!context);
932 context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
933 neg->specified_credentials = specify_credentials;
934 neg->steps = steps;
935 }
936
Yves Gerey665174f2018-06-19 15:03:05 +0200937 if ((ret == SEC_I_COMPLETE_NEEDED) ||
938 (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
deadbeeff137e972017-03-23 15:45:49 -0700939 ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100940 RTC_LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
Tommie51a0a82018-02-27 15:30:29 +0100941 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700942 if (FAILED(ret)) {
943 return HAR_ERROR;
944 }
945 }
946
deadbeeff137e972017-03-23 15:45:49 -0700947 std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
948 response = auth_method;
949 response.append(" ");
950 response.append(Base64::Encode(decoded));
951 return HAR_RESPONSE;
952 }
953#endif
Yves Gerey665174f2018-06-19 15:03:05 +0200954#endif // WEBRTC_WIN
deadbeeff137e972017-03-23 15:45:49 -0700955
956 return HAR_IGNORE;
957}
958
959//////////////////////////////////////////////////////////////////////
960
Yves Gerey665174f2018-06-19 15:03:05 +0200961} // namespace rtc