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