blob: 4ecb393545b8e9db7c3a8071fcae8bccfe1ca4b5 [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"
26#include "rtc_base/httpcommon-inl.h"
27#include "rtc_base/httpcommon.h"
28#include "rtc_base/messagedigest.h"
29#include "rtc_base/socketaddress.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
513void HttpData::setContent(const std::string& content_type,
514 StreamInterface* document) {
515 setHeader(HH_CONTENT_TYPE, content_type);
516 setDocumentAndLength(document);
517}
518
519void HttpData::setDocumentAndLength(StreamInterface* document) {
520 // TODO: Consider calling Rewind() here?
521 RTC_DCHECK(!hasHeader(HH_CONTENT_LENGTH, nullptr));
522 RTC_DCHECK(!hasHeader(HH_TRANSFER_ENCODING, nullptr));
523 RTC_DCHECK(document != nullptr);
524 this->document.reset(document);
525 size_t content_length = 0;
526 if (this->document->GetAvailable(&content_length)) {
527 char buffer[32];
528 sprintfn(buffer, sizeof(buffer), "%d", content_length);
529 setHeader(HH_CONTENT_LENGTH, buffer);
530 } else {
531 setHeader(HH_TRANSFER_ENCODING, "chunked");
532 }
533}
534
535//
536// HttpRequestData
537//
538
Yves Gerey665174f2018-06-19 15:03:05 +0200539void HttpRequestData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700540 path.clear();
541 HttpData::clear(release_document);
542}
543
Yves Gerey665174f2018-06-19 15:03:05 +0200544size_t HttpRequestData::formatLeader(char* buffer, size_t size) const {
deadbeeff137e972017-03-23 15:45:49 -0700545 RTC_DCHECK(path.find(' ') == std::string::npos);
Niels Möllerb4731ff2018-06-25 10:41:46 +0200546 return sprintfn(buffer, size, "GET %.*s HTTP/%s", path.size(), path.data(),
547 ToString(version));
deadbeeff137e972017-03-23 15:45:49 -0700548}
549
Yves Gerey665174f2018-06-19 15:03:05 +0200550HttpError HttpRequestData::parseLeader(const char* line, size_t len) {
deadbeeff137e972017-03-23 15:45:49 -0700551 unsigned int vmajor, vminor;
552 int vend, dstart, dend;
553 // sscanf isn't safe with strings that aren't null-terminated, and there is
554 // no guarantee that |line| is. Create a local copy that is null-terminated.
555 std::string line_str(line, len);
556 line = line_str.c_str();
Yves Gerey665174f2018-06-19 15:03:05 +0200557 if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", &vend, &dstart, &dend, &vmajor,
558 &vminor) != 2) ||
559 (vmajor != 1)) {
deadbeeff137e972017-03-23 15:45:49 -0700560 return HE_PROTOCOL;
561 }
562 if (vminor == 0) {
563 version = HVER_1_0;
564 } else if (vminor == 1) {
565 version = HVER_1_1;
566 } else {
567 return HE_PROTOCOL;
568 }
Niels Möllerb4731ff2018-06-25 10:41:46 +0200569 if (vend != 3 || memcmp(line, "GET", 3)) {
Yves Gerey665174f2018-06-19 15:03:05 +0200570 return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
deadbeeff137e972017-03-23 15:45:49 -0700571 }
572 path.assign(line + dstart, line + dend);
573 return HE_NONE;
574}
575
576bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
deadbeeff137e972017-03-23 15:45:49 -0700577 Url<char> url(path);
578 if (url.valid()) {
579 uri->assign(path);
580 return true;
581 }
582 std::string host;
583 if (!hasHeader(HH_HOST, &host))
584 return false;
585 url.set_address(host);
586 url.set_full_path(path);
587 uri->assign(url.url());
588 return url.valid();
589}
590
591bool HttpRequestData::getRelativeUri(std::string* host,
Yves Gerey665174f2018-06-19 15:03:05 +0200592 std::string* path) const {
deadbeeff137e972017-03-23 15:45:49 -0700593 Url<char> url(this->path);
594 if (url.valid()) {
595 host->assign(url.address());
596 path->assign(url.full_path());
597 return true;
598 }
599 if (!hasHeader(HH_HOST, host))
600 return false;
601 path->assign(this->path);
602 return true;
603}
604
605//
606// HttpResponseData
607//
608
Yves Gerey665174f2018-06-19 15:03:05 +0200609void HttpResponseData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700610 scode = HC_INTERNAL_SERVER_ERROR;
611 message.clear();
612 HttpData::clear(release_document);
613}
614
deadbeeff137e972017-03-23 15:45:49 -0700615void HttpResponseData::set_success(uint32_t scode) {
616 this->scode = scode;
617 message.clear();
618 setHeader(HH_CONTENT_LENGTH, "0", false);
619}
620
deadbeeff137e972017-03-23 15:45:49 -0700621void HttpResponseData::set_error(uint32_t scode) {
622 this->scode = scode;
623 message.clear();
624 setHeader(HH_CONTENT_LENGTH, "0", false);
625}
626
Yves Gerey665174f2018-06-19 15:03:05 +0200627size_t HttpResponseData::formatLeader(char* buffer, size_t size) const {
deadbeeff137e972017-03-23 15:45:49 -0700628 size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
629 if (!message.empty()) {
Yves Gerey665174f2018-06-19 15:03:05 +0200630 len += sprintfn(buffer + len, size - len, " %.*s", message.size(),
631 message.data());
deadbeeff137e972017-03-23 15:45:49 -0700632 }
633 return len;
634}
635
Yves Gerey665174f2018-06-19 15:03:05 +0200636HttpError HttpResponseData::parseLeader(const char* line, size_t len) {
deadbeeff137e972017-03-23 15:45:49 -0700637 size_t pos = 0;
638 unsigned int vmajor, vminor, temp_scode;
639 int temp_pos;
640 // sscanf isn't safe with strings that aren't null-terminated, and there is
641 // no guarantee that |line| is. Create a local copy that is null-terminated.
642 std::string line_str(line, len);
643 line = line_str.c_str();
Yves Gerey665174f2018-06-19 15:03:05 +0200644 if (sscanf(line, "HTTP %u%n", &temp_scode, &temp_pos) == 1) {
deadbeeff137e972017-03-23 15:45:49 -0700645 // This server's response has no version. :( NOTE: This happens for every
646 // response to requests made from Chrome plugins, regardless of the server's
647 // behaviour.
Mirko Bonadei675513b2017-11-09 11:09:25 +0100648 RTC_LOG(LS_VERBOSE) << "HTTP version missing from response";
deadbeeff137e972017-03-23 15:45:49 -0700649 version = HVER_UNKNOWN;
Yves Gerey665174f2018-06-19 15:03:05 +0200650 } else if ((sscanf(line, "HTTP/%u.%u %u%n", &vmajor, &vminor, &temp_scode,
651 &temp_pos) == 3) &&
652 (vmajor == 1)) {
deadbeeff137e972017-03-23 15:45:49 -0700653 // This server's response does have a version.
654 if (vminor == 0) {
655 version = HVER_1_0;
656 } else if (vminor == 1) {
657 version = HVER_1_1;
658 } else {
659 return HE_PROTOCOL;
660 }
661 } else {
662 return HE_PROTOCOL;
663 }
664 scode = temp_scode;
665 pos = static_cast<size_t>(temp_pos);
Yves Gerey665174f2018-06-19 15:03:05 +0200666 while ((pos < len) && isspace(static_cast<unsigned char>(line[pos])))
667 ++pos;
deadbeeff137e972017-03-23 15:45:49 -0700668 message.assign(line + pos, len - pos);
669 return HE_NONE;
670}
671
672//////////////////////////////////////////////////////////////////////
673// Http Authentication
674//////////////////////////////////////////////////////////////////////
675
676std::string quote(const std::string& str) {
677 std::string result;
678 result.push_back('"');
Yves Gerey665174f2018-06-19 15:03:05 +0200679 for (size_t i = 0; i < str.size(); ++i) {
deadbeeff137e972017-03-23 15:45:49 -0700680 if ((str[i] == '"') || (str[i] == '\\'))
681 result.push_back('\\');
682 result.push_back(str[i]);
683 }
684 result.push_back('"');
685 return result;
686}
687
688#if defined(WEBRTC_WIN)
689struct NegotiateAuthContext : public HttpAuthContext {
690 CredHandle cred;
691 CtxtHandle ctx;
692 size_t steps;
693 bool specified_credentials;
694
695 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
Yves Gerey665174f2018-06-19 15:03:05 +0200696 : HttpAuthContext(auth),
697 cred(c1),
698 ctx(c2),
699 steps(0),
700 specified_credentials(false) {}
deadbeeff137e972017-03-23 15:45:49 -0700701
Steve Anton9de3aac2017-10-24 10:08:26 -0700702 ~NegotiateAuthContext() override {
deadbeeff137e972017-03-23 15:45:49 -0700703 DeleteSecurityContext(&ctx);
704 FreeCredentialsHandle(&cred);
705 }
706};
Yves Gerey665174f2018-06-19 15:03:05 +0200707#endif // WEBRTC_WIN
deadbeeff137e972017-03-23 15:45:49 -0700708
Yves Gerey665174f2018-06-19 15:03:05 +0200709HttpAuthResult HttpAuthenticate(const char* challenge,
710 size_t len,
711 const SocketAddress& server,
712 const std::string& method,
713 const std::string& uri,
714 const std::string& username,
715 const CryptString& password,
716 HttpAuthContext*& context,
717 std::string& response,
718 std::string& auth_method) {
deadbeeff137e972017-03-23 15:45:49 -0700719 HttpAttributeList args;
720 HttpParseAttributes(challenge, len, args);
721 HttpHasNthAttribute(args, 0, &auth_method, nullptr);
722
723 if (context && (context->auth_method != auth_method))
724 return HAR_IGNORE;
725
726 // BASIC
727 if (_stricmp(auth_method.c_str(), "basic") == 0) {
728 if (context)
Yves Gerey665174f2018-06-19 15:03:05 +0200729 return HAR_CREDENTIALS; // Bad credentials
deadbeeff137e972017-03-23 15:45:49 -0700730 if (username.empty())
Yves Gerey665174f2018-06-19 15:03:05 +0200731 return HAR_CREDENTIALS; // Missing credentials
deadbeeff137e972017-03-23 15:45:49 -0700732
733 context = new HttpAuthContext(auth_method);
734
Joachim Bauch5b32f232018-03-07 20:02:26 +0100735 // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also
736 // return response as CryptString so contents get securely deleted
737 // automatically.
738 // std::string decoded = username + ":" + password;
deadbeeff137e972017-03-23 15:45:49 -0700739 size_t len = username.size() + password.GetLength() + 2;
Yves Gerey665174f2018-06-19 15:03:05 +0200740 char* sensitive = new char[len];
deadbeeff137e972017-03-23 15:45:49 -0700741 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
742 pos += strcpyn(sensitive + pos, len - pos, ":");
743 password.CopyTo(sensitive + pos, true);
744
745 response = auth_method;
746 response.append(" ");
747 // TODO: create a sensitive-source version of Base64::encode
748 response.append(Base64::Encode(sensitive));
Joachim Bauch5b32f232018-03-07 20:02:26 +0100749 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200750 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700751 return HAR_RESPONSE;
752 }
753
754 // DIGEST
755 if (_stricmp(auth_method.c_str(), "digest") == 0) {
756 if (context)
Yves Gerey665174f2018-06-19 15:03:05 +0200757 return HAR_CREDENTIALS; // Bad credentials
deadbeeff137e972017-03-23 15:45:49 -0700758 if (username.empty())
Yves Gerey665174f2018-06-19 15:03:05 +0200759 return HAR_CREDENTIALS; // Missing credentials
deadbeeff137e972017-03-23 15:45:49 -0700760
761 context = new HttpAuthContext(auth_method);
762
763 std::string cnonce, ncount;
764 char buffer[256];
765 sprintf(buffer, "%d", static_cast<int>(time(0)));
766 cnonce = MD5(buffer);
767 ncount = "00000001";
768
769 std::string realm, nonce, qop, opaque;
770 HttpHasAttribute(args, "realm", &realm);
771 HttpHasAttribute(args, "nonce", &nonce);
772 bool has_qop = HttpHasAttribute(args, "qop", &qop);
773 bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
774
Joachim Bauch5b32f232018-03-07 20:02:26 +0100775 // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also
776 // return response as CryptString so contents get securely deleted
777 // automatically.
778 // std::string A1 = username + ":" + realm + ":" + password;
deadbeeff137e972017-03-23 15:45:49 -0700779 size_t len = username.size() + realm.size() + password.GetLength() + 3;
Yves Gerey665174f2018-06-19 15:03:05 +0200780 char* sensitive = new char[len]; // A1
deadbeeff137e972017-03-23 15:45:49 -0700781 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
782 pos += strcpyn(sensitive + pos, len - pos, ":");
783 pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
784 pos += strcpyn(sensitive + pos, len - pos, ":");
785 password.CopyTo(sensitive + pos, true);
786
787 std::string A2 = method + ":" + uri;
788 std::string middle;
789 if (has_qop) {
790 qop = "auth";
791 middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
792 } else {
793 middle = nonce;
794 }
795 std::string HA1 = MD5(sensitive);
Joachim Bauch5b32f232018-03-07 20:02:26 +0100796 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200797 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700798 std::string HA2 = MD5(A2);
799 std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
800
801 std::stringstream ss;
802 ss << auth_method;
803 ss << " username=" << quote(username);
804 ss << ", realm=" << quote(realm);
805 ss << ", nonce=" << quote(nonce);
806 ss << ", uri=" << quote(uri);
807 if (has_qop) {
808 ss << ", qop=" << qop;
Yves Gerey665174f2018-06-19 15:03:05 +0200809 ss << ", nc=" << ncount;
deadbeeff137e972017-03-23 15:45:49 -0700810 ss << ", cnonce=" << quote(cnonce);
811 }
812 ss << ", response=\"" << dig_response << "\"";
813 if (has_opaque) {
814 ss << ", opaque=" << quote(opaque);
815 }
816 response = ss.str();
817 return HAR_RESPONSE;
818 }
819
820#if defined(WEBRTC_WIN)
821#if 1
822 bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
823 bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
824 // SPNEGO & NTLM
825 if (want_negotiate || want_ntlm) {
826 const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
827 char out_buf[MAX_MESSAGE], spn[MAX_SPN];
828
Yves Gerey665174f2018-06-19 15:03:05 +0200829#if 0 // Requires funky windows versions
deadbeeff137e972017-03-23 15:45:49 -0700830 DWORD len = MAX_SPN;
831 if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), nullptr,
832 server.port(),
833 0, &len, spn) != ERROR_SUCCESS) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100834 RTC_LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
deadbeeff137e972017-03-23 15:45:49 -0700835 return HAR_IGNORE;
836 }
837#else
838 sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
839#endif
840
841 SecBuffer out_sec;
Yves Gerey665174f2018-06-19 15:03:05 +0200842 out_sec.pvBuffer = out_buf;
843 out_sec.cbBuffer = sizeof(out_buf);
deadbeeff137e972017-03-23 15:45:49 -0700844 out_sec.BufferType = SECBUFFER_TOKEN;
845
846 SecBufferDesc out_buf_desc;
847 out_buf_desc.ulVersion = 0;
Yves Gerey665174f2018-06-19 15:03:05 +0200848 out_buf_desc.cBuffers = 1;
849 out_buf_desc.pBuffers = &out_sec;
deadbeeff137e972017-03-23 15:45:49 -0700850
851 const ULONG NEG_FLAGS_DEFAULT =
Yves Gerey665174f2018-06-19 15:03:05 +0200852 // ISC_REQ_ALLOCATE_MEMORY
853 ISC_REQ_CONFIDENTIALITY
854 //| ISC_REQ_EXTENDED_ERROR
855 //| ISC_REQ_INTEGRITY
856 | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT
857 //| ISC_REQ_STREAM
858 //| ISC_REQ_USE_SUPPLIED_CREDS
859 ;
deadbeeff137e972017-03-23 15:45:49 -0700860
861 ::TimeStamp lifetime;
862 SECURITY_STATUS ret = S_OK;
863 ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
864
865 bool specify_credentials = !username.empty();
866 size_t steps = 0;
867
868 // uint32_t now = Time();
869
Yves Gerey665174f2018-06-19 15:03:05 +0200870 NegotiateAuthContext* neg = static_cast<NegotiateAuthContext*>(context);
deadbeeff137e972017-03-23 15:45:49 -0700871 if (neg) {
872 const size_t max_steps = 10;
873 if (++neg->steps >= max_steps) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100874 RTC_LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) "
875 "too many retries";
deadbeeff137e972017-03-23 15:45:49 -0700876 return HAR_ERROR;
877 }
878 steps = neg->steps;
879
880 std::string challenge, decoded_challenge;
881 if (HttpHasNthAttribute(args, 1, &challenge, nullptr) &&
882 Base64::Decode(challenge, Base64::DO_STRICT, &decoded_challenge,
883 nullptr)) {
884 SecBuffer in_sec;
Yves Gerey665174f2018-06-19 15:03:05 +0200885 in_sec.pvBuffer = const_cast<char*>(decoded_challenge.data());
886 in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
deadbeeff137e972017-03-23 15:45:49 -0700887 in_sec.BufferType = SECBUFFER_TOKEN;
888
889 SecBufferDesc in_buf_desc;
890 in_buf_desc.ulVersion = 0;
Yves Gerey665174f2018-06-19 15:03:05 +0200891 in_buf_desc.cBuffers = 1;
892 in_buf_desc.pBuffers = &in_sec;
deadbeeff137e972017-03-23 15:45:49 -0700893
Yves Gerey665174f2018-06-19 15:03:05 +0200894 ret = InitializeSecurityContextA(
895 &neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP,
896 &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700897 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100898 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +0100899 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700900 return HAR_ERROR;
901 }
902 } else if (neg->specified_credentials) {
903 // Try again with default credentials
904 specify_credentials = false;
905 delete context;
906 context = neg = 0;
907 } else {
908 return HAR_CREDENTIALS;
909 }
910 }
911
912 if (!neg) {
913 unsigned char userbuf[256], passbuf[256], domainbuf[16];
Yves Gerey665174f2018-06-19 15:03:05 +0200914 SEC_WINNT_AUTH_IDENTITY_A auth_id, *pauth_id = 0;
deadbeeff137e972017-03-23 15:45:49 -0700915 if (specify_credentials) {
916 memset(&auth_id, 0, sizeof(auth_id));
Yves Gerey665174f2018-06-19 15:03:05 +0200917 size_t len = password.GetLength() + 1;
918 char* sensitive = new char[len];
deadbeeff137e972017-03-23 15:45:49 -0700919 password.CopyTo(sensitive, true);
920 std::string::size_type pos = username.find('\\');
921 if (pos == std::string::npos) {
922 auth_id.UserLength = static_cast<unsigned long>(
923 std::min(sizeof(userbuf) - 1, username.size()));
924 memcpy(userbuf, username.c_str(), auth_id.UserLength);
925 userbuf[auth_id.UserLength] = 0;
926 auth_id.DomainLength = 0;
927 domainbuf[auth_id.DomainLength] = 0;
928 auth_id.PasswordLength = static_cast<unsigned long>(
929 std::min(sizeof(passbuf) - 1, password.GetLength()));
930 memcpy(passbuf, sensitive, auth_id.PasswordLength);
931 passbuf[auth_id.PasswordLength] = 0;
932 } else {
933 auth_id.UserLength = static_cast<unsigned long>(
934 std::min(sizeof(userbuf) - 1, username.size() - pos - 1));
935 memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
936 userbuf[auth_id.UserLength] = 0;
937 auth_id.DomainLength =
938 static_cast<unsigned long>(std::min(sizeof(domainbuf) - 1, pos));
939 memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
940 domainbuf[auth_id.DomainLength] = 0;
941 auth_id.PasswordLength = static_cast<unsigned long>(
942 std::min(sizeof(passbuf) - 1, password.GetLength()));
943 memcpy(passbuf, sensitive, auth_id.PasswordLength);
944 passbuf[auth_id.PasswordLength] = 0;
945 }
Joachim Bauch5b32f232018-03-07 20:02:26 +0100946 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200947 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700948 auth_id.User = userbuf;
949 auth_id.Domain = domainbuf;
950 auth_id.Password = passbuf;
951 auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
952 pauth_id = &auth_id;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100953 RTC_LOG(LS_VERBOSE)
954 << "Negotiate protocol: Using specified credentials";
deadbeeff137e972017-03-23 15:45:49 -0700955 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100956 RTC_LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
deadbeeff137e972017-03-23 15:45:49 -0700957 }
958
959 CredHandle cred;
960 ret = AcquireCredentialsHandleA(
961 0, const_cast<char*>(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A),
962 SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700963 if (ret != SEC_E_OK) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100964 RTC_LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
Tommie51a0a82018-02-27 15:30:29 +0100965 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700966 return HAR_IGNORE;
967 }
968
Yves Gerey665174f2018-06-19 15:03:05 +0200969 // CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
deadbeeff137e972017-03-23 15:45:49 -0700970
971 CtxtHandle ctx;
Yves Gerey665174f2018-06-19 15:03:05 +0200972 ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0,
973 SECURITY_NATIVE_DREP, 0, 0, &ctx,
974 &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700975 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100976 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +0100977 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700978 FreeCredentialsHandle(&cred);
979 return HAR_IGNORE;
980 }
981
982 RTC_DCHECK(!context);
983 context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
984 neg->specified_credentials = specify_credentials;
985 neg->steps = steps;
986 }
987
Yves Gerey665174f2018-06-19 15:03:05 +0200988 if ((ret == SEC_I_COMPLETE_NEEDED) ||
989 (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
deadbeeff137e972017-03-23 15:45:49 -0700990 ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100991 RTC_LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
Tommie51a0a82018-02-27 15:30:29 +0100992 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700993 if (FAILED(ret)) {
994 return HAR_ERROR;
995 }
996 }
997
deadbeeff137e972017-03-23 15:45:49 -0700998 std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
999 response = auth_method;
1000 response.append(" ");
1001 response.append(Base64::Encode(decoded));
1002 return HAR_RESPONSE;
1003 }
1004#endif
Yves Gerey665174f2018-06-19 15:03:05 +02001005#endif // WEBRTC_WIN
deadbeeff137e972017-03-23 15:45:49 -07001006
1007 return HAR_IGNORE;
1008}
1009
1010//////////////////////////////////////////////////////////////////////
1011
Yves Gerey665174f2018-06-19 15:03:05 +02001012} // namespace rtc