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