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