Home | History | Annotate | Download | only in base
      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 
     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 
     58   static inline const char* Name(E val) { return Names[val]; }
     59   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 
     69   inline operator E&() { return val; }
     70   inline Enum& operator=(E rhs) { val = rhs; return *this; }
     71 
     72   inline const char* name() const { return Name(val); }
     73   inline bool assign(const std::string& name) { return Parse(val, name); }
     74   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 
    127 const char* ToString(HttpVersion version) {
    128   return Enum<HttpVersion>::Name(version);
    129 }
    130 
    131 bool FromString(HttpVersion& version, const std::string& str) {
    132   return Enum<HttpVersion>::Parse(version, str);
    133 }
    134 
    135 const char* ToString(HttpVerb verb) {
    136   return Enum<HttpVerb>::Name(verb);
    137 }
    138 
    139 bool FromString(HttpVerb& verb, const std::string& str) {
    140   return Enum<HttpVerb>::Parse(verb, str);
    141 }
    142 
    143 const char* ToString(HttpHeader header) {
    144   return Enum<HttpHeader>::Name(header);
    145 }
    146 
    147 bool FromString(HttpHeader& header, const std::string& str) {
    148   return Enum<HttpHeader>::Parse(header, str);
    149 }
    150 
    151 bool HttpCodeHasBody(uint32 code) {
    152   return !HttpCodeIsInformational(code)
    153          && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
    154 }
    155 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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
    414 HttpData::copy(const HttpData& src) {
    415   headers_ = src.headers_;
    416 }
    417 
    418 void
    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 
    444 size_t HttpData::clearHeader(const std::string& name) {
    445   return headers_.erase(name);
    446 }
    447 
    448 HttpData::iterator HttpData::clearHeader(iterator header) {
    449   iterator deprecated = header++;
    450   headers_.erase(deprecated);
    451   return header;
    452 }
    453 
    454 bool
    455 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 
    465 void HttpData::setContent(const std::string& content_type,
    466                           StreamInterface* document) {
    467   setHeader(HH_CONTENT_TYPE, content_type);
    468   setDocumentAndLength(document);
    469 }
    470 
    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
    492 HttpRequestData::clear(bool release_document) {
    493   verb = HV_GET;
    494   path.clear();
    495   HttpData::clear(release_document);
    496 }
    497 
    498 void
    499 HttpRequestData::copy(const HttpRequestData& src) {
    500   verb = src.verb;
    501   path = src.path;
    502   HttpData::copy(src);
    503 }
    504 
    505 size_t
    506 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
    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 
    540 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 
    557 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
    579 HttpResponseData::clear(bool release_document) {
    580   scode = HC_INTERNAL_SERVER_ERROR;
    581   message.clear();
    582   HttpData::clear(release_document);
    583 }
    584 
    585 void
    586 HttpResponseData::copy(const HttpResponseData& src) {
    587   scode = src.scode;
    588   message = src.message;
    589   HttpData::copy(src);
    590 }
    591 
    592 void
    593 HttpResponseData::set_success(uint32 scode) {
    594   this->scode = scode;
    595   message.clear();
    596   setHeader(HH_CONTENT_LENGTH, "0", false);
    597 }
    598 
    599 void
    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
    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
    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
    624 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
    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 (at) 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 
    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 
    724   NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
    725   : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
    726     specified_credentials(false)
    727   { }
    728 
    729   virtual ~NegotiateAuthContext() {
    730     DeleteSecurityContext(&ctx);
    731     FreeCredentialsHandle(&cred);
    732   }
    733 };
    734 #endif // WEBRTC_WIN
    735 
    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