blob: 86bfe974611ce2fef72c06b7186dc8508806634f [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"
Jonas Olsson366a50c2018-09-06 13:41:30 +020030#include "rtc_base/strings/string_builder.h"
Artem Titova76af0c2018-07-23 17:38:12 +020031#include "rtc_base/third_party/base64/base64.h"
Joachim Bauch5b32f232018-03-07 20:02:26 +010032#include "rtc_base/zero_memory.h"
deadbeeff137e972017-03-23 15:45:49 -070033
34namespace rtc {
Tommie51a0a82018-02-27 15:30:29 +010035namespace {
deadbeeff137e972017-03-23 15:45:49 -070036#if defined(WEBRTC_WIN)
Tommie51a0a82018-02-27 15:30:29 +010037///////////////////////////////////////////////////////////////////////////////
38// ConstantToLabel can be used to easily generate string names from constant
39// values. This can be useful for logging descriptive names of error messages.
40// Usage:
41// const ConstantToLabel LIBRARY_ERRORS[] = {
42// KLABEL(SOME_ERROR),
43// KLABEL(SOME_OTHER_ERROR),
44// ...
45// LASTLABEL
46// }
47//
48// int err = LibraryFunc();
49// LOG(LS_ERROR) << "LibraryFunc returned: "
50// << GetErrorName(err, LIBRARY_ERRORS);
Yves Gerey665174f2018-06-19 15:03:05 +020051struct ConstantToLabel {
52 int value;
53 const char* label;
54};
Tommie51a0a82018-02-27 15:30:29 +010055
56const char* LookupLabel(int value, const ConstantToLabel entries[]) {
57 for (int i = 0; entries[i].label; ++i) {
58 if (value == entries[i].value) {
59 return entries[i].label;
60 }
61 }
62 return 0;
63}
64
65std::string GetErrorName(int err, const ConstantToLabel* err_table) {
66 if (err == 0)
67 return "No error";
68
69 if (err_table != 0) {
70 if (const char* value = LookupLabel(err, err_table))
71 return value;
72 }
73
74 char buffer[16];
75 snprintf(buffer, sizeof(buffer), "0x%08x", err);
76 return buffer;
77}
78
Yves Gerey665174f2018-06-19 15:03:05 +020079#define KLABEL(x) \
80 { x, #x }
81#define LASTLABEL \
82 { 0, 0 }
Tommie51a0a82018-02-27 15:30:29 +010083
84const ConstantToLabel SECURITY_ERRORS[] = {
Yves Gerey665174f2018-06-19 15:03:05 +020085 KLABEL(SEC_I_COMPLETE_AND_CONTINUE),
86 KLABEL(SEC_I_COMPLETE_NEEDED),
87 KLABEL(SEC_I_CONTEXT_EXPIRED),
88 KLABEL(SEC_I_CONTINUE_NEEDED),
89 KLABEL(SEC_I_INCOMPLETE_CREDENTIALS),
90 KLABEL(SEC_I_RENEGOTIATE),
91 KLABEL(SEC_E_CERT_EXPIRED),
92 KLABEL(SEC_E_INCOMPLETE_MESSAGE),
93 KLABEL(SEC_E_INSUFFICIENT_MEMORY),
94 KLABEL(SEC_E_INTERNAL_ERROR),
95 KLABEL(SEC_E_INVALID_HANDLE),
96 KLABEL(SEC_E_INVALID_TOKEN),
97 KLABEL(SEC_E_LOGON_DENIED),
98 KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY),
99 KLABEL(SEC_E_NO_CREDENTIALS),
100 KLABEL(SEC_E_NOT_OWNER),
101 KLABEL(SEC_E_OK),
102 KLABEL(SEC_E_SECPKG_NOT_FOUND),
103 KLABEL(SEC_E_TARGET_UNKNOWN),
104 KLABEL(SEC_E_UNKNOWN_CREDENTIALS),
105 KLABEL(SEC_E_UNSUPPORTED_FUNCTION),
106 KLABEL(SEC_E_UNTRUSTED_ROOT),
107 KLABEL(SEC_E_WRONG_PRINCIPAL),
108 LASTLABEL};
Tommie51a0a82018-02-27 15:30:29 +0100109#undef KLABEL
110#undef LASTLABEL
111#endif // defined(WEBRTC_WIN)
112} // namespace
deadbeeff137e972017-03-23 15:45:49 -0700113
114//////////////////////////////////////////////////////////////////////
115// Enum - TODO: expose globally later?
116//////////////////////////////////////////////////////////////////////
117
Yves Gerey665174f2018-06-19 15:03:05 +0200118bool find_string(size_t& index,
119 const std::string& needle,
120 const char* const haystack[],
121 size_t max_index) {
122 for (index = 0; index < max_index; ++index) {
deadbeeff137e972017-03-23 15:45:49 -0700123 if (_stricmp(needle.c_str(), haystack[index]) == 0) {
124 return true;
125 }
126 }
127 return false;
128}
129
Yves Gerey665174f2018-06-19 15:03:05 +0200130template <class E>
deadbeeff137e972017-03-23 15:45:49 -0700131struct Enum {
132 static const char** Names;
133 static size_t Size;
134
135 static inline const char* Name(E val) { return Names[val]; }
136 static inline bool Parse(E& val, const std::string& name) {
137 size_t index;
138 if (!find_string(index, name, Names, Size))
139 return false;
140 val = static_cast<E>(index);
141 return true;
142 }
143
144 E val;
145
146 inline operator E&() { return val; }
Yves Gerey665174f2018-06-19 15:03:05 +0200147 inline Enum& operator=(E rhs) {
148 val = rhs;
149 return *this;
150 }
deadbeeff137e972017-03-23 15:45:49 -0700151
152 inline const char* name() const { return Name(val); }
153 inline bool assign(const std::string& name) { return Parse(val, name); }
Yves Gerey665174f2018-06-19 15:03:05 +0200154 inline Enum& operator=(const std::string& rhs) {
155 assign(rhs);
156 return *this;
157 }
deadbeeff137e972017-03-23 15:45:49 -0700158};
159
Yves Gerey665174f2018-06-19 15:03:05 +0200160#define ENUM(e, n) \
161 template <> \
162 const char** Enum<e>::Names = n; \
163 template <> \
164 size_t Enum<e>::Size = sizeof(n) / sizeof(n[0])
deadbeeff137e972017-03-23 15:45:49 -0700165
166//////////////////////////////////////////////////////////////////////
167// HttpCommon
168//////////////////////////////////////////////////////////////////////
169
Yves Gerey665174f2018-06-19 15:03:05 +0200170static const char* kHttpVersions[HVER_LAST + 1] = {"1.0", "1.1", "Unknown"};
deadbeeff137e972017-03-23 15:45:49 -0700171ENUM(HttpVersion, kHttpVersions);
172
Yves Gerey665174f2018-06-19 15:03:05 +0200173static const char* kHttpHeaders[HH_LAST + 1] = {
174 "Age",
175 "Cache-Control",
176 "Connection",
177 "Content-Disposition",
178 "Content-Length",
179 "Content-Range",
180 "Content-Type",
181 "Cookie",
182 "Date",
183 "ETag",
184 "Expires",
185 "Host",
186 "If-Modified-Since",
187 "If-None-Match",
188 "Keep-Alive",
189 "Last-Modified",
190 "Location",
191 "Proxy-Authenticate",
192 "Proxy-Authorization",
193 "Proxy-Connection",
194 "Range",
195 "Set-Cookie",
196 "TE",
197 "Trailers",
198 "Transfer-Encoding",
199 "Upgrade",
200 "User-Agent",
201 "WWW-Authenticate",
deadbeeff137e972017-03-23 15:45:49 -0700202};
203ENUM(HttpHeader, kHttpHeaders);
204
205const char* ToString(HttpVersion version) {
206 return Enum<HttpVersion>::Name(version);
207}
208
209bool FromString(HttpVersion& version, const std::string& str) {
210 return Enum<HttpVersion>::Parse(version, str);
211}
212
deadbeeff137e972017-03-23 15:45:49 -0700213const 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
deadbeeff137e972017-03-23 15:45:49 -0700221bool HttpHeaderIsEndToEnd(HttpHeader header) {
222 switch (header) {
Yves Gerey665174f2018-06-19 15:03:05 +0200223 case HH_CONNECTION:
224 case HH_KEEP_ALIVE:
225 case HH_PROXY_AUTHENTICATE:
226 case HH_PROXY_AUTHORIZATION:
227 case HH_PROXY_CONNECTION: // Note part of RFC... this is non-standard
228 // header
229 case HH_TE:
230 case HH_TRAILERS:
231 case HH_TRANSFER_ENCODING:
232 case HH_UPGRADE:
233 return false;
234 default:
235 return true;
deadbeeff137e972017-03-23 15:45:49 -0700236 }
237}
238
239bool HttpHeaderIsCollapsible(HttpHeader header) {
240 switch (header) {
Yves Gerey665174f2018-06-19 15:03:05 +0200241 case HH_SET_COOKIE:
242 case HH_PROXY_AUTHENTICATE:
243 case HH_WWW_AUTHENTICATE:
244 return false;
245 default:
246 return true;
deadbeeff137e972017-03-23 15:45:49 -0700247 }
248}
249
250bool HttpShouldKeepAlive(const HttpData& data) {
251 std::string connection;
Yves Gerey665174f2018-06-19 15:03:05 +0200252 if ((data.hasHeader(HH_PROXY_CONNECTION, &connection) ||
253 data.hasHeader(HH_CONNECTION, &connection))) {
deadbeeff137e972017-03-23 15:45:49 -0700254 return (_stricmp(connection.c_str(), "Keep-Alive") == 0);
255 }
256 return (data.version >= HVER_1_1);
257}
258
259namespace {
260
Yves Gerey665174f2018-06-19 15:03:05 +0200261inline bool IsEndOfAttributeName(size_t pos, size_t len, const char* data) {
deadbeeff137e972017-03-23 15:45:49 -0700262 if (pos >= len)
263 return true;
264 if (isspace(static_cast<unsigned char>(data[pos])))
265 return true;
266 // The reason for this complexity is that some attributes may contain trailing
267 // equal signs (like base64 tokens in Negotiate auth headers)
Yves Gerey665174f2018-06-19 15:03:05 +0200268 if ((pos + 1 < len) && (data[pos] == '=') &&
269 !isspace(static_cast<unsigned char>(data[pos + 1])) &&
270 (data[pos + 1] != '=')) {
deadbeeff137e972017-03-23 15:45:49 -0700271 return true;
272 }
273 return false;
274}
275
deadbeeff137e972017-03-23 15:45:49 -0700276} // anonymous namespace
277
Yves Gerey665174f2018-06-19 15:03:05 +0200278void HttpParseAttributes(const char* data,
279 size_t len,
deadbeeff137e972017-03-23 15:45:49 -0700280 HttpAttributeList& attributes) {
281 size_t pos = 0;
282 while (true) {
283 // Skip leading whitespace
284 while ((pos < len) && isspace(static_cast<unsigned char>(data[pos]))) {
285 ++pos;
286 }
287
288 // End of attributes?
289 if (pos >= len)
290 return;
291
292 // Find end of attribute name
293 size_t start = pos;
294 while (!IsEndOfAttributeName(pos, len, data)) {
295 ++pos;
296 }
297
298 HttpAttribute attribute;
299 attribute.first.assign(data + start, data + pos);
300
301 // Attribute has value?
302 if ((pos < len) && (data[pos] == '=')) {
Yves Gerey665174f2018-06-19 15:03:05 +0200303 ++pos; // Skip '='
deadbeeff137e972017-03-23 15:45:49 -0700304 // Check if quoted value
305 if ((pos < len) && (data[pos] == '"')) {
306 while (++pos < len) {
307 if (data[pos] == '"') {
308 ++pos;
309 break;
310 }
311 if ((data[pos] == '\\') && (pos + 1 < len))
312 ++pos;
313 attribute.second.append(1, data[pos]);
314 }
315 } else {
Yves Gerey665174f2018-06-19 15:03:05 +0200316 while ((pos < len) && !isspace(static_cast<unsigned char>(data[pos])) &&
317 (data[pos] != ',')) {
deadbeeff137e972017-03-23 15:45:49 -0700318 attribute.second.append(1, data[pos++]);
319 }
320 }
321 }
322
323 attributes.push_back(attribute);
Yves Gerey665174f2018-06-19 15:03:05 +0200324 if ((pos < len) && (data[pos] == ','))
325 ++pos; // Skip ','
deadbeeff137e972017-03-23 15:45:49 -0700326 }
327}
328
329bool HttpHasAttribute(const HttpAttributeList& attributes,
330 const std::string& name,
331 std::string* value) {
332 for (HttpAttributeList::const_iterator it = attributes.begin();
333 it != attributes.end(); ++it) {
334 if (it->first == name) {
335 if (value) {
336 *value = it->second;
337 }
338 return true;
339 }
340 }
341 return false;
342}
343
344bool HttpHasNthAttribute(HttpAttributeList& attributes,
345 size_t index,
346 std::string* name,
347 std::string* value) {
348 if (index >= attributes.size())
349 return false;
350
351 if (name)
352 *name = attributes[index].first;
353 if (value)
354 *value = attributes[index].second;
355 return true;
356}
357
358bool HttpDateToSeconds(const std::string& date, time_t* seconds) {
359 const char* const kTimeZones[] = {
Yves Gerey665174f2018-06-19 15:03:05 +0200360 "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST",
361 "PDT", "A", "B", "C", "D", "E", "F", "G", "H",
362 "I", "K", "L", "M", "N", "O", "P", "Q", "R",
363 "S", "T", "U", "V", "W", "X", "Y"};
deadbeeff137e972017-03-23 15:45:49 -0700364 const int kTimeZoneOffsets[] = {
Yves Gerey665174f2018-06-19 15:03:05 +0200365 0, 0, -5, -4, -6, -5, -7, -6, -8, -7, -1, -2, -3, -4, -5, -6, -7,
366 -8, -9, -10, -11, -12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
deadbeeff137e972017-03-23 15:45:49 -0700367
368 RTC_DCHECK(nullptr != seconds);
369 struct tm tval;
370 memset(&tval, 0, sizeof(tval));
371 char month[4], zone[6];
372 memset(month, 0, sizeof(month));
373 memset(zone, 0, sizeof(zone));
374
Yves Gerey665174f2018-06-19 15:03:05 +0200375 if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c", &tval.tm_mday,
376 month, &tval.tm_year, &tval.tm_hour, &tval.tm_min,
377 &tval.tm_sec, zone)) {
deadbeeff137e972017-03-23 15:45:49 -0700378 return false;
379 }
380 switch (toupper(month[2])) {
Yves Gerey665174f2018-06-19 15:03:05 +0200381 case 'N':
382 tval.tm_mon = (month[1] == 'A') ? 0 : 5;
383 break;
384 case 'B':
385 tval.tm_mon = 1;
386 break;
387 case 'R':
388 tval.tm_mon = (month[0] == 'M') ? 2 : 3;
389 break;
390 case 'Y':
391 tval.tm_mon = 4;
392 break;
393 case 'L':
394 tval.tm_mon = 6;
395 break;
396 case 'G':
397 tval.tm_mon = 7;
398 break;
399 case 'P':
400 tval.tm_mon = 8;
401 break;
402 case 'T':
403 tval.tm_mon = 9;
404 break;
405 case 'V':
406 tval.tm_mon = 10;
407 break;
408 case 'C':
409 tval.tm_mon = 11;
410 break;
deadbeeff137e972017-03-23 15:45:49 -0700411 }
412 tval.tm_year -= 1900;
413 time_t gmt, non_gmt = mktime(&tval);
414 if ((zone[0] == '+') || (zone[0] == '-')) {
Yves Gerey665174f2018-06-19 15:03:05 +0200415 if (!isdigit(zone[1]) || !isdigit(zone[2]) || !isdigit(zone[3]) ||
416 !isdigit(zone[4])) {
deadbeeff137e972017-03-23 15:45:49 -0700417 return false;
418 }
419 int hours = (zone[1] - '0') * 10 + (zone[2] - '0');
420 int minutes = (zone[3] - '0') * 10 + (zone[4] - '0');
421 int offset = (hours * 60 + minutes) * 60;
422 gmt = non_gmt + ((zone[0] == '+') ? offset : -offset);
423 } else {
424 size_t zindex;
425 if (!find_string(zindex, zone, kTimeZones, arraysize(kTimeZones))) {
426 return false;
427 }
428 gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60;
429 }
Yves Gerey665174f2018-06-19 15:03:05 +0200430// TODO: Android should support timezone, see b/2441195
431#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) || defined(WEBRTC_ANDROID) || \
432 defined(BSD)
433 tm* tm_for_timezone = localtime(&gmt);
deadbeeff137e972017-03-23 15:45:49 -0700434 *seconds = gmt + tm_for_timezone->tm_gmtoff;
435#else
436#if defined(_MSC_VER) && _MSC_VER >= 1900
437 long timezone = 0;
438 _get_timezone(&timezone);
439#endif
440 *seconds = gmt - timezone;
441#endif
442 return true;
443}
444
445std::string HttpAddress(const SocketAddress& address, bool secure) {
Yves Gerey665174f2018-06-19 15:03:05 +0200446 return (address.port() == HttpDefaultPort(secure)) ? address.hostname()
447 : address.ToString();
deadbeeff137e972017-03-23 15:45:49 -0700448}
449
450//////////////////////////////////////////////////////////////////////
451// HttpData
452//////////////////////////////////////////////////////////////////////
453
Yves Gerey665174f2018-06-19 15:03:05 +0200454HttpData::HttpData() : version(HVER_1_1) {}
deadbeeff137e972017-03-23 15:45:49 -0700455
456HttpData::~HttpData() = default;
457
Yves Gerey665174f2018-06-19 15:03:05 +0200458void HttpData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700459 // 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
Yves Gerey665174f2018-06-19 15:03:05 +0200467void HttpData::changeHeader(const std::string& name,
468 const std::string& value,
469 HeaderCombine combine) {
deadbeeff137e972017-03-23 15:45:49 -0700470 if (combine == HC_AUTO) {
471 HttpHeader header;
472 // Unrecognized headers are collapsible
473 combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
Yves Gerey665174f2018-06-19 15:03:05 +0200474 ? HC_YES
475 : HC_NO;
deadbeeff137e972017-03-23 15:45:49 -0700476 } else if (combine == HC_REPLACE) {
477 headers_.erase(name);
478 combine = HC_NO;
479 }
480 // At this point, combine is one of (YES, NO, NEW)
481 if (combine != HC_NO) {
482 HeaderMap::iterator it = headers_.find(name);
483 if (it != headers_.end()) {
484 if (combine == HC_YES) {
485 it->second.append(",");
486 it->second.append(value);
487 }
488 return;
489 }
490 }
491 headers_.insert(HeaderMap::value_type(name, value));
492}
493
494size_t HttpData::clearHeader(const std::string& name) {
495 return headers_.erase(name);
496}
497
498HttpData::iterator HttpData::clearHeader(iterator header) {
499 iterator deprecated = header++;
500 headers_.erase(deprecated);
501 return header;
502}
503
Yves Gerey665174f2018-06-19 15:03:05 +0200504bool HttpData::hasHeader(const std::string& name, std::string* value) const {
deadbeeff137e972017-03-23 15:45:49 -0700505 HeaderMap::const_iterator it = headers_.find(name);
506 if (it == headers_.end()) {
507 return false;
508 } else if (value) {
509 *value = it->second;
510 }
511 return true;
512}
513
514void HttpData::setContent(const std::string& content_type,
515 StreamInterface* document) {
516 setHeader(HH_CONTENT_TYPE, content_type);
517 setDocumentAndLength(document);
518}
519
520void HttpData::setDocumentAndLength(StreamInterface* document) {
521 // TODO: Consider calling Rewind() here?
522 RTC_DCHECK(!hasHeader(HH_CONTENT_LENGTH, nullptr));
523 RTC_DCHECK(!hasHeader(HH_TRANSFER_ENCODING, nullptr));
524 RTC_DCHECK(document != nullptr);
525 this->document.reset(document);
526 size_t content_length = 0;
527 if (this->document->GetAvailable(&content_length)) {
528 char buffer[32];
529 sprintfn(buffer, sizeof(buffer), "%d", content_length);
530 setHeader(HH_CONTENT_LENGTH, buffer);
531 } else {
532 setHeader(HH_TRANSFER_ENCODING, "chunked");
533 }
534}
535
536//
537// HttpRequestData
538//
539
Yves Gerey665174f2018-06-19 15:03:05 +0200540void HttpRequestData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700541 path.clear();
542 HttpData::clear(release_document);
543}
544
Yves Gerey665174f2018-06-19 15:03:05 +0200545size_t HttpRequestData::formatLeader(char* buffer, size_t size) const {
deadbeeff137e972017-03-23 15:45:49 -0700546 RTC_DCHECK(path.find(' ') == std::string::npos);
Niels Möllerb4731ff2018-06-25 10:41:46 +0200547 return sprintfn(buffer, size, "GET %.*s HTTP/%s", path.size(), path.data(),
548 ToString(version));
deadbeeff137e972017-03-23 15:45:49 -0700549}
550
Yves Gerey665174f2018-06-19 15:03:05 +0200551HttpError HttpRequestData::parseLeader(const char* line, size_t len) {
deadbeeff137e972017-03-23 15:45:49 -0700552 unsigned int vmajor, vminor;
553 int vend, dstart, dend;
554 // sscanf isn't safe with strings that aren't null-terminated, and there is
555 // no guarantee that |line| is. Create a local copy that is null-terminated.
556 std::string line_str(line, len);
557 line = line_str.c_str();
Yves Gerey665174f2018-06-19 15:03:05 +0200558 if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", &vend, &dstart, &dend, &vmajor,
559 &vminor) != 2) ||
560 (vmajor != 1)) {
deadbeeff137e972017-03-23 15:45:49 -0700561 return HE_PROTOCOL;
562 }
563 if (vminor == 0) {
564 version = HVER_1_0;
565 } else if (vminor == 1) {
566 version = HVER_1_1;
567 } else {
568 return HE_PROTOCOL;
569 }
Niels Möllerb4731ff2018-06-25 10:41:46 +0200570 if (vend != 3 || memcmp(line, "GET", 3)) {
Yves Gerey665174f2018-06-19 15:03:05 +0200571 return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
deadbeeff137e972017-03-23 15:45:49 -0700572 }
573 path.assign(line + dstart, line + dend);
574 return HE_NONE;
575}
576
577bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
deadbeeff137e972017-03-23 15:45:49 -0700578 Url<char> url(path);
579 if (url.valid()) {
580 uri->assign(path);
581 return true;
582 }
583 std::string host;
584 if (!hasHeader(HH_HOST, &host))
585 return false;
586 url.set_address(host);
587 url.set_full_path(path);
588 uri->assign(url.url());
589 return url.valid();
590}
591
592bool HttpRequestData::getRelativeUri(std::string* host,
Yves Gerey665174f2018-06-19 15:03:05 +0200593 std::string* path) const {
deadbeeff137e972017-03-23 15:45:49 -0700594 Url<char> url(this->path);
595 if (url.valid()) {
596 host->assign(url.address());
597 path->assign(url.full_path());
598 return true;
599 }
600 if (!hasHeader(HH_HOST, host))
601 return false;
602 path->assign(this->path);
603 return true;
604}
605
606//
607// HttpResponseData
608//
609
Yves Gerey665174f2018-06-19 15:03:05 +0200610void HttpResponseData::clear(bool release_document) {
deadbeeff137e972017-03-23 15:45:49 -0700611 scode = HC_INTERNAL_SERVER_ERROR;
612 message.clear();
613 HttpData::clear(release_document);
614}
615
deadbeeff137e972017-03-23 15:45:49 -0700616void HttpResponseData::set_success(uint32_t scode) {
617 this->scode = scode;
618 message.clear();
619 setHeader(HH_CONTENT_LENGTH, "0", false);
620}
621
deadbeeff137e972017-03-23 15:45:49 -0700622void HttpResponseData::set_error(uint32_t scode) {
623 this->scode = scode;
624 message.clear();
625 setHeader(HH_CONTENT_LENGTH, "0", false);
626}
627
Yves Gerey665174f2018-06-19 15:03:05 +0200628size_t HttpResponseData::formatLeader(char* buffer, size_t size) const {
deadbeeff137e972017-03-23 15:45:49 -0700629 size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
630 if (!message.empty()) {
Yves Gerey665174f2018-06-19 15:03:05 +0200631 len += sprintfn(buffer + len, size - len, " %.*s", message.size(),
632 message.data());
deadbeeff137e972017-03-23 15:45:49 -0700633 }
634 return len;
635}
636
Yves Gerey665174f2018-06-19 15:03:05 +0200637HttpError HttpResponseData::parseLeader(const char* line, size_t len) {
deadbeeff137e972017-03-23 15:45:49 -0700638 size_t pos = 0;
639 unsigned int vmajor, vminor, temp_scode;
640 int temp_pos;
641 // sscanf isn't safe with strings that aren't null-terminated, and there is
642 // no guarantee that |line| is. Create a local copy that is null-terminated.
643 std::string line_str(line, len);
644 line = line_str.c_str();
Yves Gerey665174f2018-06-19 15:03:05 +0200645 if (sscanf(line, "HTTP %u%n", &temp_scode, &temp_pos) == 1) {
deadbeeff137e972017-03-23 15:45:49 -0700646 // This server's response has no version. :( NOTE: This happens for every
647 // response to requests made from Chrome plugins, regardless of the server's
648 // behaviour.
Mirko Bonadei675513b2017-11-09 11:09:25 +0100649 RTC_LOG(LS_VERBOSE) << "HTTP version missing from response";
deadbeeff137e972017-03-23 15:45:49 -0700650 version = HVER_UNKNOWN;
Yves Gerey665174f2018-06-19 15:03:05 +0200651 } else if ((sscanf(line, "HTTP/%u.%u %u%n", &vmajor, &vminor, &temp_scode,
652 &temp_pos) == 3) &&
653 (vmajor == 1)) {
deadbeeff137e972017-03-23 15:45:49 -0700654 // This server's response does have a version.
655 if (vminor == 0) {
656 version = HVER_1_0;
657 } else if (vminor == 1) {
658 version = HVER_1_1;
659 } else {
660 return HE_PROTOCOL;
661 }
662 } else {
663 return HE_PROTOCOL;
664 }
665 scode = temp_scode;
666 pos = static_cast<size_t>(temp_pos);
Yves Gerey665174f2018-06-19 15:03:05 +0200667 while ((pos < len) && isspace(static_cast<unsigned char>(line[pos])))
668 ++pos;
deadbeeff137e972017-03-23 15:45:49 -0700669 message.assign(line + pos, len - pos);
670 return HE_NONE;
671}
672
673//////////////////////////////////////////////////////////////////////
674// Http Authentication
675//////////////////////////////////////////////////////////////////////
676
677std::string quote(const std::string& str) {
678 std::string result;
679 result.push_back('"');
Yves Gerey665174f2018-06-19 15:03:05 +0200680 for (size_t i = 0; i < str.size(); ++i) {
deadbeeff137e972017-03-23 15:45:49 -0700681 if ((str[i] == '"') || (str[i] == '\\'))
682 result.push_back('\\');
683 result.push_back(str[i]);
684 }
685 result.push_back('"');
686 return result;
687}
688
689#if defined(WEBRTC_WIN)
690struct NegotiateAuthContext : public HttpAuthContext {
691 CredHandle cred;
692 CtxtHandle ctx;
693 size_t steps;
694 bool specified_credentials;
695
696 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
Yves Gerey665174f2018-06-19 15:03:05 +0200697 : HttpAuthContext(auth),
698 cred(c1),
699 ctx(c2),
700 steps(0),
701 specified_credentials(false) {}
deadbeeff137e972017-03-23 15:45:49 -0700702
Steve Anton9de3aac2017-10-24 10:08:26 -0700703 ~NegotiateAuthContext() override {
deadbeeff137e972017-03-23 15:45:49 -0700704 DeleteSecurityContext(&ctx);
705 FreeCredentialsHandle(&cred);
706 }
707};
Yves Gerey665174f2018-06-19 15:03:05 +0200708#endif // WEBRTC_WIN
deadbeeff137e972017-03-23 15:45:49 -0700709
Yves Gerey665174f2018-06-19 15:03:05 +0200710HttpAuthResult HttpAuthenticate(const char* challenge,
711 size_t len,
712 const SocketAddress& server,
713 const std::string& method,
714 const std::string& uri,
715 const std::string& username,
716 const CryptString& password,
717 HttpAuthContext*& context,
718 std::string& response,
719 std::string& auth_method) {
deadbeeff137e972017-03-23 15:45:49 -0700720 HttpAttributeList args;
721 HttpParseAttributes(challenge, len, args);
722 HttpHasNthAttribute(args, 0, &auth_method, nullptr);
723
724 if (context && (context->auth_method != auth_method))
725 return HAR_IGNORE;
726
727 // BASIC
728 if (_stricmp(auth_method.c_str(), "basic") == 0) {
729 if (context)
Yves Gerey665174f2018-06-19 15:03:05 +0200730 return HAR_CREDENTIALS; // Bad credentials
deadbeeff137e972017-03-23 15:45:49 -0700731 if (username.empty())
Yves Gerey665174f2018-06-19 15:03:05 +0200732 return HAR_CREDENTIALS; // Missing credentials
deadbeeff137e972017-03-23 15:45:49 -0700733
734 context = new HttpAuthContext(auth_method);
735
Joachim Bauch5b32f232018-03-07 20:02:26 +0100736 // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also
737 // return response as CryptString so contents get securely deleted
738 // automatically.
739 // std::string decoded = username + ":" + password;
deadbeeff137e972017-03-23 15:45:49 -0700740 size_t len = username.size() + password.GetLength() + 2;
Yves Gerey665174f2018-06-19 15:03:05 +0200741 char* sensitive = new char[len];
deadbeeff137e972017-03-23 15:45:49 -0700742 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
743 pos += strcpyn(sensitive + pos, len - pos, ":");
744 password.CopyTo(sensitive + pos, true);
745
746 response = auth_method;
747 response.append(" ");
748 // TODO: create a sensitive-source version of Base64::encode
749 response.append(Base64::Encode(sensitive));
Joachim Bauch5b32f232018-03-07 20:02:26 +0100750 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200751 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700752 return HAR_RESPONSE;
753 }
754
755 // DIGEST
756 if (_stricmp(auth_method.c_str(), "digest") == 0) {
757 if (context)
Yves Gerey665174f2018-06-19 15:03:05 +0200758 return HAR_CREDENTIALS; // Bad credentials
deadbeeff137e972017-03-23 15:45:49 -0700759 if (username.empty())
Yves Gerey665174f2018-06-19 15:03:05 +0200760 return HAR_CREDENTIALS; // Missing credentials
deadbeeff137e972017-03-23 15:45:49 -0700761
762 context = new HttpAuthContext(auth_method);
763
764 std::string cnonce, ncount;
765 char buffer[256];
766 sprintf(buffer, "%d", static_cast<int>(time(0)));
767 cnonce = MD5(buffer);
768 ncount = "00000001";
769
770 std::string realm, nonce, qop, opaque;
771 HttpHasAttribute(args, "realm", &realm);
772 HttpHasAttribute(args, "nonce", &nonce);
773 bool has_qop = HttpHasAttribute(args, "qop", &qop);
774 bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
775
Joachim Bauch5b32f232018-03-07 20:02:26 +0100776 // TODO(bugs.webrtc.org/8905): Convert sensitive to a CryptString and also
777 // return response as CryptString so contents get securely deleted
778 // automatically.
779 // std::string A1 = username + ":" + realm + ":" + password;
deadbeeff137e972017-03-23 15:45:49 -0700780 size_t len = username.size() + realm.size() + password.GetLength() + 3;
Yves Gerey665174f2018-06-19 15:03:05 +0200781 char* sensitive = new char[len]; // A1
deadbeeff137e972017-03-23 15:45:49 -0700782 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
783 pos += strcpyn(sensitive + pos, len - pos, ":");
784 pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
785 pos += strcpyn(sensitive + pos, len - pos, ":");
786 password.CopyTo(sensitive + pos, true);
787
788 std::string A2 = method + ":" + uri;
789 std::string middle;
790 if (has_qop) {
791 qop = "auth";
792 middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
793 } else {
794 middle = nonce;
795 }
796 std::string HA1 = MD5(sensitive);
Joachim Bauch5b32f232018-03-07 20:02:26 +0100797 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200798 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700799 std::string HA2 = MD5(A2);
800 std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
801
Jonas Olsson366a50c2018-09-06 13:41:30 +0200802 rtc::StringBuilder ss;
deadbeeff137e972017-03-23 15:45:49 -0700803 ss << auth_method;
804 ss << " username=" << quote(username);
805 ss << ", realm=" << quote(realm);
806 ss << ", nonce=" << quote(nonce);
807 ss << ", uri=" << quote(uri);
808 if (has_qop) {
809 ss << ", qop=" << qop;
Yves Gerey665174f2018-06-19 15:03:05 +0200810 ss << ", nc=" << ncount;
deadbeeff137e972017-03-23 15:45:49 -0700811 ss << ", cnonce=" << quote(cnonce);
812 }
813 ss << ", response=\"" << dig_response << "\"";
814 if (has_opaque) {
815 ss << ", opaque=" << quote(opaque);
816 }
817 response = ss.str();
818 return HAR_RESPONSE;
819 }
820
821#if defined(WEBRTC_WIN)
822#if 1
823 bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
824 bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
825 // SPNEGO & NTLM
826 if (want_negotiate || want_ntlm) {
827 const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
828 char out_buf[MAX_MESSAGE], spn[MAX_SPN];
829
Yves Gerey665174f2018-06-19 15:03:05 +0200830#if 0 // Requires funky windows versions
deadbeeff137e972017-03-23 15:45:49 -0700831 DWORD len = MAX_SPN;
832 if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), nullptr,
833 server.port(),
834 0, &len, spn) != ERROR_SUCCESS) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100835 RTC_LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
deadbeeff137e972017-03-23 15:45:49 -0700836 return HAR_IGNORE;
837 }
838#else
839 sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
840#endif
841
842 SecBuffer out_sec;
Yves Gerey665174f2018-06-19 15:03:05 +0200843 out_sec.pvBuffer = out_buf;
844 out_sec.cbBuffer = sizeof(out_buf);
deadbeeff137e972017-03-23 15:45:49 -0700845 out_sec.BufferType = SECBUFFER_TOKEN;
846
847 SecBufferDesc out_buf_desc;
848 out_buf_desc.ulVersion = 0;
Yves Gerey665174f2018-06-19 15:03:05 +0200849 out_buf_desc.cBuffers = 1;
850 out_buf_desc.pBuffers = &out_sec;
deadbeeff137e972017-03-23 15:45:49 -0700851
852 const ULONG NEG_FLAGS_DEFAULT =
Yves Gerey665174f2018-06-19 15:03:05 +0200853 // ISC_REQ_ALLOCATE_MEMORY
854 ISC_REQ_CONFIDENTIALITY
855 //| ISC_REQ_EXTENDED_ERROR
856 //| ISC_REQ_INTEGRITY
857 | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT
858 //| ISC_REQ_STREAM
859 //| ISC_REQ_USE_SUPPLIED_CREDS
860 ;
deadbeeff137e972017-03-23 15:45:49 -0700861
862 ::TimeStamp lifetime;
863 SECURITY_STATUS ret = S_OK;
864 ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
865
866 bool specify_credentials = !username.empty();
867 size_t steps = 0;
868
869 // uint32_t now = Time();
870
Yves Gerey665174f2018-06-19 15:03:05 +0200871 NegotiateAuthContext* neg = static_cast<NegotiateAuthContext*>(context);
deadbeeff137e972017-03-23 15:45:49 -0700872 if (neg) {
873 const size_t max_steps = 10;
874 if (++neg->steps >= max_steps) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100875 RTC_LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) "
876 "too many retries";
deadbeeff137e972017-03-23 15:45:49 -0700877 return HAR_ERROR;
878 }
879 steps = neg->steps;
880
881 std::string challenge, decoded_challenge;
882 if (HttpHasNthAttribute(args, 1, &challenge, nullptr) &&
883 Base64::Decode(challenge, Base64::DO_STRICT, &decoded_challenge,
884 nullptr)) {
885 SecBuffer in_sec;
Yves Gerey665174f2018-06-19 15:03:05 +0200886 in_sec.pvBuffer = const_cast<char*>(decoded_challenge.data());
887 in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
deadbeeff137e972017-03-23 15:45:49 -0700888 in_sec.BufferType = SECBUFFER_TOKEN;
889
890 SecBufferDesc in_buf_desc;
891 in_buf_desc.ulVersion = 0;
Yves Gerey665174f2018-06-19 15:03:05 +0200892 in_buf_desc.cBuffers = 1;
893 in_buf_desc.pBuffers = &in_sec;
deadbeeff137e972017-03-23 15:45:49 -0700894
Yves Gerey665174f2018-06-19 15:03:05 +0200895 ret = InitializeSecurityContextA(
896 &neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP,
897 &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700898 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100899 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +0100900 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700901 return HAR_ERROR;
902 }
903 } else if (neg->specified_credentials) {
904 // Try again with default credentials
905 specify_credentials = false;
906 delete context;
907 context = neg = 0;
908 } else {
909 return HAR_CREDENTIALS;
910 }
911 }
912
913 if (!neg) {
914 unsigned char userbuf[256], passbuf[256], domainbuf[16];
Yves Gerey665174f2018-06-19 15:03:05 +0200915 SEC_WINNT_AUTH_IDENTITY_A auth_id, *pauth_id = 0;
deadbeeff137e972017-03-23 15:45:49 -0700916 if (specify_credentials) {
917 memset(&auth_id, 0, sizeof(auth_id));
Yves Gerey665174f2018-06-19 15:03:05 +0200918 size_t len = password.GetLength() + 1;
919 char* sensitive = new char[len];
deadbeeff137e972017-03-23 15:45:49 -0700920 password.CopyTo(sensitive, true);
921 std::string::size_type pos = username.find('\\');
922 if (pos == std::string::npos) {
923 auth_id.UserLength = static_cast<unsigned long>(
924 std::min(sizeof(userbuf) - 1, username.size()));
925 memcpy(userbuf, username.c_str(), auth_id.UserLength);
926 userbuf[auth_id.UserLength] = 0;
927 auth_id.DomainLength = 0;
928 domainbuf[auth_id.DomainLength] = 0;
929 auth_id.PasswordLength = static_cast<unsigned long>(
930 std::min(sizeof(passbuf) - 1, password.GetLength()));
931 memcpy(passbuf, sensitive, auth_id.PasswordLength);
932 passbuf[auth_id.PasswordLength] = 0;
933 } else {
934 auth_id.UserLength = static_cast<unsigned long>(
935 std::min(sizeof(userbuf) - 1, username.size() - pos - 1));
936 memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
937 userbuf[auth_id.UserLength] = 0;
938 auth_id.DomainLength =
939 static_cast<unsigned long>(std::min(sizeof(domainbuf) - 1, pos));
940 memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
941 domainbuf[auth_id.DomainLength] = 0;
942 auth_id.PasswordLength = static_cast<unsigned long>(
943 std::min(sizeof(passbuf) - 1, password.GetLength()));
944 memcpy(passbuf, sensitive, auth_id.PasswordLength);
945 passbuf[auth_id.PasswordLength] = 0;
946 }
Joachim Bauch5b32f232018-03-07 20:02:26 +0100947 ExplicitZeroMemory(sensitive, len);
Yves Gerey665174f2018-06-19 15:03:05 +0200948 delete[] sensitive;
deadbeeff137e972017-03-23 15:45:49 -0700949 auth_id.User = userbuf;
950 auth_id.Domain = domainbuf;
951 auth_id.Password = passbuf;
952 auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
953 pauth_id = &auth_id;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100954 RTC_LOG(LS_VERBOSE)
955 << "Negotiate protocol: Using specified credentials";
deadbeeff137e972017-03-23 15:45:49 -0700956 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100957 RTC_LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
deadbeeff137e972017-03-23 15:45:49 -0700958 }
959
960 CredHandle cred;
961 ret = AcquireCredentialsHandleA(
962 0, const_cast<char*>(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A),
963 SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700964 if (ret != SEC_E_OK) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100965 RTC_LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
Tommie51a0a82018-02-27 15:30:29 +0100966 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700967 return HAR_IGNORE;
968 }
969
Yves Gerey665174f2018-06-19 15:03:05 +0200970 // CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
deadbeeff137e972017-03-23 15:45:49 -0700971
972 CtxtHandle ctx;
Yves Gerey665174f2018-06-19 15:03:05 +0200973 ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0,
974 SECURITY_NATIVE_DREP, 0, 0, &ctx,
975 &out_buf_desc, &ret_flags, &lifetime);
deadbeeff137e972017-03-23 15:45:49 -0700976 if (FAILED(ret)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100977 RTC_LOG(LS_ERROR) << "InitializeSecurityContext returned: "
Tommie51a0a82018-02-27 15:30:29 +0100978 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700979 FreeCredentialsHandle(&cred);
980 return HAR_IGNORE;
981 }
982
983 RTC_DCHECK(!context);
984 context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
985 neg->specified_credentials = specify_credentials;
986 neg->steps = steps;
987 }
988
Yves Gerey665174f2018-06-19 15:03:05 +0200989 if ((ret == SEC_I_COMPLETE_NEEDED) ||
990 (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
deadbeeff137e972017-03-23 15:45:49 -0700991 ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100992 RTC_LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
Tommie51a0a82018-02-27 15:30:29 +0100993 << GetErrorName(ret, SECURITY_ERRORS);
deadbeeff137e972017-03-23 15:45:49 -0700994 if (FAILED(ret)) {
995 return HAR_ERROR;
996 }
997 }
998
deadbeeff137e972017-03-23 15:45:49 -0700999 std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
1000 response = auth_method;
1001 response.append(" ");
1002 response.append(Base64::Encode(decoded));
1003 return HAR_RESPONSE;
1004 }
1005#endif
Yves Gerey665174f2018-06-19 15:03:05 +02001006#endif // WEBRTC_WIN
deadbeeff137e972017-03-23 15:45:49 -07001007
1008 return HAR_IGNORE;
1009}
1010
1011//////////////////////////////////////////////////////////////////////
1012
Yves Gerey665174f2018-06-19 15:03:05 +02001013} // namespace rtc