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