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