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_t code)152 bool HttpCodeHasBody(uint32_t code) {
153   return !HttpCodeIsInformational(code)
154          && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
155 }
156 
HttpCodeIsCacheable(uint32_t code)157 bool HttpCodeIsCacheable(uint32_t 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 #if _MSC_VER >= 1900
391   long timezone = 0;
392   _get_timezone(&timezone);
393 #endif
394   *seconds = gmt - timezone;
395 #endif
396   return true;
397 }
398 
HttpAddress(const SocketAddress & address,bool secure)399 std::string HttpAddress(const SocketAddress& address, bool secure) {
400   return (address.port() == HttpDefaultPort(secure))
401           ? address.hostname() : address.ToString();
402 }
403 
404 //////////////////////////////////////////////////////////////////////
405 // HttpData
406 //////////////////////////////////////////////////////////////////////
407 
HttpData()408 HttpData::HttpData() : version(HVER_1_1) {
409 }
410 
411 HttpData::~HttpData() = default;
412 
413 void
clear(bool release_document)414 HttpData::clear(bool release_document) {
415   // Clear headers first, since releasing a document may have far-reaching
416   // effects.
417   headers_.clear();
418   if (release_document) {
419     document.reset();
420   }
421 }
422 
423 void
copy(const HttpData & src)424 HttpData::copy(const HttpData& src) {
425   headers_ = src.headers_;
426 }
427 
428 void
changeHeader(const std::string & name,const std::string & value,HeaderCombine combine)429 HttpData::changeHeader(const std::string& name, const std::string& value,
430                        HeaderCombine combine) {
431   if (combine == HC_AUTO) {
432     HttpHeader header;
433     // Unrecognized headers are collapsible
434     combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
435               ? HC_YES : HC_NO;
436   } else if (combine == HC_REPLACE) {
437     headers_.erase(name);
438     combine = HC_NO;
439   }
440   // At this point, combine is one of (YES, NO, NEW)
441   if (combine != HC_NO) {
442     HeaderMap::iterator it = headers_.find(name);
443     if (it != headers_.end()) {
444       if (combine == HC_YES) {
445         it->second.append(",");
446         it->second.append(value);
447       }
448       return;
449     }
450   }
451   headers_.insert(HeaderMap::value_type(name, value));
452 }
453 
clearHeader(const std::string & name)454 size_t HttpData::clearHeader(const std::string& name) {
455   return headers_.erase(name);
456 }
457 
clearHeader(iterator header)458 HttpData::iterator HttpData::clearHeader(iterator header) {
459   iterator deprecated = header++;
460   headers_.erase(deprecated);
461   return header;
462 }
463 
464 bool
hasHeader(const std::string & name,std::string * value) const465 HttpData::hasHeader(const std::string& name, std::string* value) const {
466   HeaderMap::const_iterator it = headers_.find(name);
467   if (it == headers_.end()) {
468     return false;
469   } else if (value) {
470     *value = it->second;
471   }
472   return true;
473 }
474 
setContent(const std::string & content_type,StreamInterface * document)475 void HttpData::setContent(const std::string& content_type,
476                           StreamInterface* document) {
477   setHeader(HH_CONTENT_TYPE, content_type);
478   setDocumentAndLength(document);
479 }
480 
setDocumentAndLength(StreamInterface * document)481 void HttpData::setDocumentAndLength(StreamInterface* document) {
482   // TODO: Consider calling Rewind() here?
483   ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL));
484   ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL));
485   ASSERT(document != NULL);
486   this->document.reset(document);
487   size_t content_length = 0;
488   if (this->document->GetAvailable(&content_length)) {
489     char buffer[32];
490     sprintfn(buffer, sizeof(buffer), "%d", content_length);
491     setHeader(HH_CONTENT_LENGTH, buffer);
492   } else {
493     setHeader(HH_TRANSFER_ENCODING, "chunked");
494   }
495 }
496 
497 //
498 // HttpRequestData
499 //
500 
501 void
clear(bool release_document)502 HttpRequestData::clear(bool release_document) {
503   verb = HV_GET;
504   path.clear();
505   HttpData::clear(release_document);
506 }
507 
508 void
copy(const HttpRequestData & src)509 HttpRequestData::copy(const HttpRequestData& src) {
510   verb = src.verb;
511   path = src.path;
512   HttpData::copy(src);
513 }
514 
515 size_t
formatLeader(char * buffer,size_t size) const516 HttpRequestData::formatLeader(char* buffer, size_t size) const {
517   ASSERT(path.find(' ') == std::string::npos);
518   return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(),
519                   path.data(), ToString(version));
520 }
521 
522 HttpError
parseLeader(const char * line,size_t len)523 HttpRequestData::parseLeader(const char* line, size_t len) {
524   unsigned int vmajor, vminor;
525   int vend, dstart, dend;
526   // sscanf isn't safe with strings that aren't null-terminated, and there is
527   // no guarantee that |line| is. Create a local copy that is null-terminated.
528   std::string line_str(line, len);
529   line = line_str.c_str();
530   if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u",
531               &vend, &dstart, &dend, &vmajor, &vminor) != 2)
532       || (vmajor != 1)) {
533     return HE_PROTOCOL;
534   }
535   if (vminor == 0) {
536     version = HVER_1_0;
537   } else if (vminor == 1) {
538     version = HVER_1_1;
539   } else {
540     return HE_PROTOCOL;
541   }
542   std::string sverb(line, vend);
543   if (!FromString(verb, sverb.c_str())) {
544     return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
545   }
546   path.assign(line + dstart, line + dend);
547   return HE_NONE;
548 }
549 
getAbsoluteUri(std::string * uri) const550 bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
551   if (HV_CONNECT == verb)
552     return false;
553   Url<char> url(path);
554   if (url.valid()) {
555     uri->assign(path);
556     return true;
557   }
558   std::string host;
559   if (!hasHeader(HH_HOST, &host))
560     return false;
561   url.set_address(host);
562   url.set_full_path(path);
563   uri->assign(url.url());
564   return url.valid();
565 }
566 
getRelativeUri(std::string * host,std::string * path) const567 bool HttpRequestData::getRelativeUri(std::string* host,
568                                      std::string* path) const
569 {
570   if (HV_CONNECT == verb)
571     return false;
572   Url<char> url(this->path);
573   if (url.valid()) {
574     host->assign(url.address());
575     path->assign(url.full_path());
576     return true;
577   }
578   if (!hasHeader(HH_HOST, host))
579     return false;
580   path->assign(this->path);
581   return true;
582 }
583 
584 //
585 // HttpResponseData
586 //
587 
588 void
clear(bool release_document)589 HttpResponseData::clear(bool release_document) {
590   scode = HC_INTERNAL_SERVER_ERROR;
591   message.clear();
592   HttpData::clear(release_document);
593 }
594 
595 void
copy(const HttpResponseData & src)596 HttpResponseData::copy(const HttpResponseData& src) {
597   scode = src.scode;
598   message = src.message;
599   HttpData::copy(src);
600 }
601 
set_success(uint32_t scode)602 void HttpResponseData::set_success(uint32_t scode) {
603   this->scode = scode;
604   message.clear();
605   setHeader(HH_CONTENT_LENGTH, "0", false);
606 }
607 
set_success(const std::string & content_type,StreamInterface * document,uint32_t scode)608 void HttpResponseData::set_success(const std::string& content_type,
609                                    StreamInterface* document,
610                                    uint32_t scode) {
611   this->scode = scode;
612   message.erase(message.begin(), message.end());
613   setContent(content_type, document);
614 }
615 
set_redirect(const std::string & location,uint32_t scode)616 void HttpResponseData::set_redirect(const std::string& location,
617                                     uint32_t scode) {
618   this->scode = scode;
619   message.clear();
620   setHeader(HH_LOCATION, location);
621   setHeader(HH_CONTENT_LENGTH, "0", false);
622 }
623 
set_error(uint32_t scode)624 void HttpResponseData::set_error(uint32_t scode) {
625   this->scode = scode;
626   message.clear();
627   setHeader(HH_CONTENT_LENGTH, "0", false);
628 }
629 
630 size_t
formatLeader(char * buffer,size_t size) const631 HttpResponseData::formatLeader(char* buffer, size_t size) const {
632   size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
633   if (!message.empty()) {
634     len += sprintfn(buffer + len, size - len, " %.*s",
635                     message.size(), message.data());
636   }
637   return len;
638 }
639 
640 HttpError
parseLeader(const char * line,size_t len)641 HttpResponseData::parseLeader(const char* line, size_t len) {
642   size_t pos = 0;
643   unsigned int vmajor, vminor, temp_scode;
644   int temp_pos;
645   // sscanf isn't safe with strings that aren't null-terminated, and there is
646   // no guarantee that |line| is. Create a local copy that is null-terminated.
647   std::string line_str(line, len);
648   line = line_str.c_str();
649   if (sscanf(line, "HTTP %u%n",
650              &temp_scode, &temp_pos) == 1) {
651     // This server's response has no version. :( NOTE: This happens for every
652     // response to requests made from Chrome plugins, regardless of the server's
653     // behaviour.
654     LOG(LS_VERBOSE) << "HTTP version missing from response";
655     version = HVER_UNKNOWN;
656   } else if ((sscanf(line, "HTTP/%u.%u %u%n",
657                      &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
658              && (vmajor == 1)) {
659     // This server's response does have a version.
660     if (vminor == 0) {
661       version = HVER_1_0;
662     } else if (vminor == 1) {
663       version = HVER_1_1;
664     } else {
665       return HE_PROTOCOL;
666     }
667   } else {
668     return HE_PROTOCOL;
669   }
670   scode = temp_scode;
671   pos = static_cast<size_t>(temp_pos);
672   while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
673   message.assign(line + pos, len - pos);
674   return HE_NONE;
675 }
676 
677 //////////////////////////////////////////////////////////////////////
678 // Http Authentication
679 //////////////////////////////////////////////////////////////////////
680 
681 #define TEST_DIGEST 0
682 #if TEST_DIGEST
683 /*
684 const char * const DIGEST_CHALLENGE =
685   "Digest realm=\"testrealm@host.com\","
686   " qop=\"auth,auth-int\","
687   " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
688   " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
689 const char * const DIGEST_METHOD = "GET";
690 const char * const DIGEST_URI =
691   "/dir/index.html";;
692 const char * const DIGEST_CNONCE =
693   "0a4f113b";
694 const char * const DIGEST_RESPONSE =
695   "6629fae49393a05397450978507c4ef1";
696 //user_ = "Mufasa";
697 //pass_ = "Circle Of Life";
698 */
699 const char * const DIGEST_CHALLENGE =
700   "Digest realm=\"Squid proxy-caching web server\","
701   " nonce=\"Nny4QuC5PwiSDixJ\","
702   " qop=\"auth\","
703   " stale=false";
704 const char * const DIGEST_URI =
705   "/";
706 const char * const DIGEST_CNONCE =
707   "6501d58e9a21cee1e7b5fec894ded024";
708 const char * const DIGEST_RESPONSE =
709   "edffcb0829e755838b073a4a42de06bc";
710 #endif
711 
quote(const std::string & str)712 std::string quote(const std::string& str) {
713   std::string result;
714   result.push_back('"');
715   for (size_t i=0; i<str.size(); ++i) {
716     if ((str[i] == '"') || (str[i] == '\\'))
717       result.push_back('\\');
718     result.push_back(str[i]);
719   }
720   result.push_back('"');
721   return result;
722 }
723 
724 #if defined(WEBRTC_WIN)
725 struct NegotiateAuthContext : public HttpAuthContext {
726   CredHandle cred;
727   CtxtHandle ctx;
728   size_t steps;
729   bool specified_credentials;
730 
NegotiateAuthContextrtc::NegotiateAuthContext731   NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
732   : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
733     specified_credentials(false)
734   { }
735 
~NegotiateAuthContextrtc::NegotiateAuthContext736   virtual ~NegotiateAuthContext() {
737     DeleteSecurityContext(&ctx);
738     FreeCredentialsHandle(&cred);
739   }
740 };
741 #endif // WEBRTC_WIN
742 
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)743 HttpAuthResult HttpAuthenticate(
744   const char * challenge, size_t len,
745   const SocketAddress& server,
746   const std::string& method, const std::string& uri,
747   const std::string& username, const CryptString& password,
748   HttpAuthContext *& context, std::string& response, std::string& auth_method)
749 {
750 #if TEST_DIGEST
751   challenge = DIGEST_CHALLENGE;
752   len = strlen(challenge);
753 #endif
754 
755   HttpAttributeList args;
756   HttpParseAttributes(challenge, len, args);
757   HttpHasNthAttribute(args, 0, &auth_method, NULL);
758 
759   if (context && (context->auth_method != auth_method))
760     return HAR_IGNORE;
761 
762   // BASIC
763   if (_stricmp(auth_method.c_str(), "basic") == 0) {
764     if (context)
765       return HAR_CREDENTIALS; // Bad credentials
766     if (username.empty())
767       return HAR_CREDENTIALS; // Missing credentials
768 
769     context = new HttpAuthContext(auth_method);
770 
771     // TODO: convert sensitive to a secure buffer that gets securely deleted
772     //std::string decoded = username + ":" + password;
773     size_t len = username.size() + password.GetLength() + 2;
774     char * sensitive = new char[len];
775     size_t pos = strcpyn(sensitive, len, username.data(), username.size());
776     pos += strcpyn(sensitive + pos, len - pos, ":");
777     password.CopyTo(sensitive + pos, true);
778 
779     response = auth_method;
780     response.append(" ");
781     // TODO: create a sensitive-source version of Base64::encode
782     response.append(Base64::Encode(sensitive));
783     memset(sensitive, 0, len);
784     delete [] sensitive;
785     return HAR_RESPONSE;
786   }
787 
788   // DIGEST
789   if (_stricmp(auth_method.c_str(), "digest") == 0) {
790     if (context)
791       return HAR_CREDENTIALS; // Bad credentials
792     if (username.empty())
793       return HAR_CREDENTIALS; // Missing credentials
794 
795     context = new HttpAuthContext(auth_method);
796 
797     std::string cnonce, ncount;
798 #if TEST_DIGEST
799     method = DIGEST_METHOD;
800     uri    = DIGEST_URI;
801     cnonce = DIGEST_CNONCE;
802 #else
803     char buffer[256];
804     sprintf(buffer, "%d", static_cast<int>(time(0)));
805     cnonce = MD5(buffer);
806 #endif
807     ncount = "00000001";
808 
809     std::string realm, nonce, qop, opaque;
810     HttpHasAttribute(args, "realm", &realm);
811     HttpHasAttribute(args, "nonce", &nonce);
812     bool has_qop = HttpHasAttribute(args, "qop", &qop);
813     bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
814 
815     // TODO: convert sensitive to be secure buffer
816     //std::string A1 = username + ":" + realm + ":" + password;
817     size_t len = username.size() + realm.size() + password.GetLength() + 3;
818     char * sensitive = new char[len];  // A1
819     size_t pos = strcpyn(sensitive, len, username.data(), username.size());
820     pos += strcpyn(sensitive + pos, len - pos, ":");
821     pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
822     pos += strcpyn(sensitive + pos, len - pos, ":");
823     password.CopyTo(sensitive + pos, true);
824 
825     std::string A2 = method + ":" + uri;
826     std::string middle;
827     if (has_qop) {
828       qop = "auth";
829       middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
830     } else {
831       middle = nonce;
832     }
833     std::string HA1 = MD5(sensitive);
834     memset(sensitive, 0, len);
835     delete [] sensitive;
836     std::string HA2 = MD5(A2);
837     std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
838 
839 #if TEST_DIGEST
840     ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0);
841 #endif
842 
843     std::stringstream ss;
844     ss << auth_method;
845     ss << " username=" << quote(username);
846     ss << ", realm=" << quote(realm);
847     ss << ", nonce=" << quote(nonce);
848     ss << ", uri=" << quote(uri);
849     if (has_qop) {
850       ss << ", qop=" << qop;
851       ss << ", nc="  << ncount;
852       ss << ", cnonce=" << quote(cnonce);
853     }
854     ss << ", response=\"" << dig_response << "\"";
855     if (has_opaque) {
856       ss << ", opaque=" << quote(opaque);
857     }
858     response = ss.str();
859     return HAR_RESPONSE;
860   }
861 
862 #if defined(WEBRTC_WIN)
863 #if 1
864   bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
865   bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
866   // SPNEGO & NTLM
867   if (want_negotiate || want_ntlm) {
868     const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
869     char out_buf[MAX_MESSAGE], spn[MAX_SPN];
870 
871 #if 0 // Requires funky windows versions
872     DWORD len = MAX_SPN;
873     if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), NULL,
874                   server.port(),
875                   0, &len, spn) != ERROR_SUCCESS) {
876       LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
877       return HAR_IGNORE;
878     }
879 #else
880     sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
881 #endif
882 
883     SecBuffer out_sec;
884     out_sec.pvBuffer   = out_buf;
885     out_sec.cbBuffer   = sizeof(out_buf);
886     out_sec.BufferType = SECBUFFER_TOKEN;
887 
888     SecBufferDesc out_buf_desc;
889     out_buf_desc.ulVersion = 0;
890     out_buf_desc.cBuffers  = 1;
891     out_buf_desc.pBuffers  = &out_sec;
892 
893     const ULONG NEG_FLAGS_DEFAULT =
894       //ISC_REQ_ALLOCATE_MEMORY
895       ISC_REQ_CONFIDENTIALITY
896       //| ISC_REQ_EXTENDED_ERROR
897       //| ISC_REQ_INTEGRITY
898       | ISC_REQ_REPLAY_DETECT
899       | ISC_REQ_SEQUENCE_DETECT
900       //| ISC_REQ_STREAM
901       //| ISC_REQ_USE_SUPPLIED_CREDS
902       ;
903 
904     ::TimeStamp lifetime;
905     SECURITY_STATUS ret = S_OK;
906     ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
907 
908     bool specify_credentials = !username.empty();
909     size_t steps = 0;
910 
911     // uint32_t now = Time();
912 
913     NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
914     if (neg) {
915       const size_t max_steps = 10;
916       if (++neg->steps >= max_steps) {
917         LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries";
918         return HAR_ERROR;
919       }
920       steps = neg->steps;
921 
922       std::string challenge, decoded_challenge;
923       if (HttpHasNthAttribute(args, 1, &challenge, NULL)
924           && Base64::Decode(challenge, Base64::DO_STRICT,
925                             &decoded_challenge, NULL)) {
926         SecBuffer in_sec;
927         in_sec.pvBuffer   = const_cast<char *>(decoded_challenge.data());
928         in_sec.cbBuffer   = static_cast<unsigned long>(decoded_challenge.size());
929         in_sec.BufferType = SECBUFFER_TOKEN;
930 
931         SecBufferDesc in_buf_desc;
932         in_buf_desc.ulVersion = 0;
933         in_buf_desc.cBuffers  = 1;
934         in_buf_desc.pBuffers  = &in_sec;
935 
936         ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
937         //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
938         if (FAILED(ret)) {
939           LOG(LS_ERROR) << "InitializeSecurityContext returned: "
940                       << ErrorName(ret, SECURITY_ERRORS);
941           return HAR_ERROR;
942         }
943       } else if (neg->specified_credentials) {
944         // Try again with default credentials
945         specify_credentials = false;
946         delete context;
947         context = neg = 0;
948       } else {
949         return HAR_CREDENTIALS;
950       }
951     }
952 
953     if (!neg) {
954       unsigned char userbuf[256], passbuf[256], domainbuf[16];
955       SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
956       if (specify_credentials) {
957         memset(&auth_id, 0, sizeof(auth_id));
958         size_t len = password.GetLength()+1;
959         char * sensitive = new char[len];
960         password.CopyTo(sensitive, true);
961         std::string::size_type pos = username.find('\\');
962         if (pos == std::string::npos) {
963           auth_id.UserLength = static_cast<unsigned long>(
964               std::min(sizeof(userbuf) - 1, username.size()));
965           memcpy(userbuf, username.c_str(), auth_id.UserLength);
966           userbuf[auth_id.UserLength] = 0;
967           auth_id.DomainLength = 0;
968           domainbuf[auth_id.DomainLength] = 0;
969           auth_id.PasswordLength = static_cast<unsigned long>(
970               std::min(sizeof(passbuf) - 1, password.GetLength()));
971           memcpy(passbuf, sensitive, auth_id.PasswordLength);
972           passbuf[auth_id.PasswordLength] = 0;
973         } else {
974           auth_id.UserLength = static_cast<unsigned long>(
975               std::min(sizeof(userbuf) - 1, username.size() - pos - 1));
976           memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
977           userbuf[auth_id.UserLength] = 0;
978           auth_id.DomainLength =
979               static_cast<unsigned long>(std::min(sizeof(domainbuf) - 1, pos));
980           memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
981           domainbuf[auth_id.DomainLength] = 0;
982           auth_id.PasswordLength = static_cast<unsigned long>(
983               std::min(sizeof(passbuf) - 1, password.GetLength()));
984           memcpy(passbuf, sensitive, auth_id.PasswordLength);
985           passbuf[auth_id.PasswordLength] = 0;
986         }
987         memset(sensitive, 0, len);
988         delete [] sensitive;
989         auth_id.User = userbuf;
990         auth_id.Domain = domainbuf;
991         auth_id.Password = passbuf;
992         auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
993         pauth_id = &auth_id;
994         LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials";
995       } else {
996         LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
997       }
998 
999       CredHandle cred;
1000       ret = AcquireCredentialsHandleA(
1001           0, const_cast<char*>(want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A),
1002           SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
1003       //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now);
1004       if (ret != SEC_E_OK) {
1005         LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
1006                     << ErrorName(ret, SECURITY_ERRORS);
1007         return HAR_IGNORE;
1008       }
1009 
1010       //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
1011 
1012       CtxtHandle ctx;
1013       ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
1014       //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
1015       if (FAILED(ret)) {
1016         LOG(LS_ERROR) << "InitializeSecurityContext returned: "
1017                     << ErrorName(ret, SECURITY_ERRORS);
1018         FreeCredentialsHandle(&cred);
1019         return HAR_IGNORE;
1020       }
1021 
1022       ASSERT(!context);
1023       context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
1024       neg->specified_credentials = specify_credentials;
1025       neg->steps = steps;
1026     }
1027 
1028     if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
1029       ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
1030       //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now);
1031       LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
1032                       << ErrorName(ret, SECURITY_ERRORS);
1033       if (FAILED(ret)) {
1034         return HAR_ERROR;
1035       }
1036     }
1037 
1038     //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms";
1039 
1040     std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
1041     response = auth_method;
1042     response.append(" ");
1043     response.append(Base64::Encode(decoded));
1044     return HAR_RESPONSE;
1045   }
1046 #endif
1047 #endif // WEBRTC_WIN
1048 
1049   return HAR_IGNORE;
1050 }
1051 
1052 //////////////////////////////////////////////////////////////////////
1053 
1054 } // namespace rtc
1055