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