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
35namespace rtc {
36
37#if defined(WEBRTC_WIN)
38extern const ConstantLabel SECURITY_ERRORS[];
39#endif
40
41//////////////////////////////////////////////////////////////////////
42// Enum - TODO: expose globally later?
43//////////////////////////////////////////////////////////////////////
44
45bool 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
55template<class E>
56struct 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
87static const char* kHttpVersions[HVER_LAST+1] = {
88  "1.0", "1.1", "Unknown"
89};
90ENUM(HttpVersion, kHttpVersions);
91
92static const char* kHttpVerbs[HV_LAST+1] = {
93  "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD"
94};
95ENUM(HttpVerb, kHttpVerbs);
96
97static 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};
127ENUM(HttpHeader, kHttpHeaders);
128
129const char* ToString(HttpVersion version) {
130  return Enum<HttpVersion>::Name(version);
131}
132
133bool FromString(HttpVersion& version, const std::string& str) {
134  return Enum<HttpVersion>::Parse(version, str);
135}
136
137const char* ToString(HttpVerb verb) {
138  return Enum<HttpVerb>::Name(verb);
139}
140
141bool FromString(HttpVerb& verb, const std::string& str) {
142  return Enum<HttpVerb>::Parse(verb, str);
143}
144
145const char* ToString(HttpHeader header) {
146  return Enum<HttpHeader>::Name(header);
147}
148
149bool FromString(HttpHeader& header, const std::string& str) {
150  return Enum<HttpHeader>::Parse(header, str);
151}
152
153bool HttpCodeHasBody(uint32_t code) {
154  return !HttpCodeIsInformational(code)
155         && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
156}
157
158bool 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
172bool 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
189bool 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
200bool 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
209namespace {
210
211inline 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
228std::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
238void 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
253void 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
303bool 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
318bool 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
332bool 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
400std::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
409HttpData::HttpData() : version(HVER_1_1) {
410}
411
412HttpData::~HttpData() = default;
413
414void
415HttpData::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
424void
425HttpData::copy(const HttpData& src) {
426  headers_ = src.headers_;
427}
428
429void
430HttpData::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
455size_t HttpData::clearHeader(const std::string& name) {
456  return headers_.erase(name);
457}
458
459HttpData::iterator HttpData::clearHeader(iterator header) {
460  iterator deprecated = header++;
461  headers_.erase(deprecated);
462  return header;
463}
464
465bool
466HttpData::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
476void HttpData::setContent(const std::string& content_type,
477                          StreamInterface* document) {
478  setHeader(HH_CONTENT_TYPE, content_type);
479  setDocumentAndLength(document);
480}
481
482void 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
502void
503HttpRequestData::clear(bool release_document) {
504  verb = HV_GET;
505  path.clear();
506  HttpData::clear(release_document);
507}
508
509void
510HttpRequestData::copy(const HttpRequestData& src) {
511  verb = src.verb;
512  path = src.path;
513  HttpData::copy(src);
514}
515
516size_t
517HttpRequestData::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
523HttpError
524HttpRequestData::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
551bool 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
568bool 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
589void
590HttpResponseData::clear(bool release_document) {
591  scode = HC_INTERNAL_SERVER_ERROR;
592  message.clear();
593  HttpData::clear(release_document);
594}
595
596void
597HttpResponseData::copy(const HttpResponseData& src) {
598  scode = src.scode;
599  message = src.message;
600  HttpData::copy(src);
601}
602
603void HttpResponseData::set_success(uint32_t scode) {
604  this->scode = scode;
605  message.clear();
606  setHeader(HH_CONTENT_LENGTH, "0", false);
607}
608
609void 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
617void 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
625void HttpResponseData::set_error(uint32_t scode) {
626  this->scode = scode;
627  message.clear();
628  setHeader(HH_CONTENT_LENGTH, "0", false);
629}
630
631size_t
632HttpResponseData::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
641HttpError
642HttpResponseData::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/*
685const char * const DIGEST_CHALLENGE =
686  "Digest realm=\"testrealm@host.com\","
687  " qop=\"auth,auth-int\","
688  " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
689  " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
690const char * const DIGEST_METHOD = "GET";
691const char * const DIGEST_URI =
692  "/dir/index.html";;
693const char * const DIGEST_CNONCE =
694  "0a4f113b";
695const char * const DIGEST_RESPONSE =
696  "6629fae49393a05397450978507c4ef1";
697//user_ = "Mufasa";
698//pass_ = "Circle Of Life";
699*/
700const char * const DIGEST_CHALLENGE =
701  "Digest realm=\"Squid proxy-caching web server\","
702  " nonce=\"Nny4QuC5PwiSDixJ\","
703  " qop=\"auth\","
704  " stale=false";
705const char * const DIGEST_URI =
706  "/";
707const char * const DIGEST_CNONCE =
708  "6501d58e9a21cee1e7b5fec894ded024";
709const char * const DIGEST_RESPONSE =
710  "edffcb0829e755838b073a4a42de06bc";
711#endif
712
713std::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)
726struct 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
744HttpAuthResult 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