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
33namespace rtc {
34
35#if defined(WEBRTC_WIN)
36extern const ConstantLabel SECURITY_ERRORS[];
37#endif
38
39//////////////////////////////////////////////////////////////////////
40// Enum - TODO: expose globally later?
41//////////////////////////////////////////////////////////////////////
42
43bool 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
53template<class E>
54struct 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
85static const char* kHttpVersions[HVER_LAST+1] = {
86  "1.0", "1.1", "Unknown"
87};
88ENUM(HttpVersion, kHttpVersions);
89
90static const char* kHttpVerbs[HV_LAST+1] = {
91  "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD"
92};
93ENUM(HttpVerb, kHttpVerbs);
94
95static 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};
125ENUM(HttpHeader, kHttpHeaders);
126
127const char* ToString(HttpVersion version) {
128  return Enum<HttpVersion>::Name(version);
129}
130
131bool FromString(HttpVersion& version, const std::string& str) {
132  return Enum<HttpVersion>::Parse(version, str);
133}
134
135const char* ToString(HttpVerb verb) {
136  return Enum<HttpVerb>::Name(verb);
137}
138
139bool FromString(HttpVerb& verb, const std::string& str) {
140  return Enum<HttpVerb>::Parse(verb, str);
141}
142
143const char* ToString(HttpHeader header) {
144  return Enum<HttpHeader>::Name(header);
145}
146
147bool FromString(HttpHeader& header, const std::string& str) {
148  return Enum<HttpHeader>::Parse(header, str);
149}
150
151bool HttpCodeHasBody(uint32 code) {
152  return !HttpCodeIsInformational(code)
153         && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
154}
155
156bool 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
170bool 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
187bool 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
198bool 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
207namespace {
208
209inline 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
226std::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
236void 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
251void 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
301bool 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
316bool 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
330bool 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
394std::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
403void
404HttpData::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
413void
414HttpData::copy(const HttpData& src) {
415  headers_ = src.headers_;
416}
417
418void
419HttpData::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
444size_t HttpData::clearHeader(const std::string& name) {
445  return headers_.erase(name);
446}
447
448HttpData::iterator HttpData::clearHeader(iterator header) {
449  iterator deprecated = header++;
450  headers_.erase(deprecated);
451  return header;
452}
453
454bool
455HttpData::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
465void HttpData::setContent(const std::string& content_type,
466                          StreamInterface* document) {
467  setHeader(HH_CONTENT_TYPE, content_type);
468  setDocumentAndLength(document);
469}
470
471void 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
491void
492HttpRequestData::clear(bool release_document) {
493  verb = HV_GET;
494  path.clear();
495  HttpData::clear(release_document);
496}
497
498void
499HttpRequestData::copy(const HttpRequestData& src) {
500  verb = src.verb;
501  path = src.path;
502  HttpData::copy(src);
503}
504
505size_t
506HttpRequestData::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
512HttpError
513HttpRequestData::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
540bool 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
557bool 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
578void
579HttpResponseData::clear(bool release_document) {
580  scode = HC_INTERNAL_SERVER_ERROR;
581  message.clear();
582  HttpData::clear(release_document);
583}
584
585void
586HttpResponseData::copy(const HttpResponseData& src) {
587  scode = src.scode;
588  message = src.message;
589  HttpData::copy(src);
590}
591
592void
593HttpResponseData::set_success(uint32 scode) {
594  this->scode = scode;
595  message.clear();
596  setHeader(HH_CONTENT_LENGTH, "0", false);
597}
598
599void
600HttpResponseData::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
608void
609HttpResponseData::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
616void
617HttpResponseData::set_error(uint32 scode) {
618  this->scode = scode;
619  message.clear();
620  setHeader(HH_CONTENT_LENGTH, "0", false);
621}
622
623size_t
624HttpResponseData::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
633HttpError
634HttpResponseData::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/*
677const char * const DIGEST_CHALLENGE =
678  "Digest realm=\"testrealm@host.com\","
679  " qop=\"auth,auth-int\","
680  " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
681  " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
682const char * const DIGEST_METHOD = "GET";
683const char * const DIGEST_URI =
684  "/dir/index.html";;
685const char * const DIGEST_CNONCE =
686  "0a4f113b";
687const char * const DIGEST_RESPONSE =
688  "6629fae49393a05397450978507c4ef1";
689//user_ = "Mufasa";
690//pass_ = "Circle Of Life";
691*/
692const char * const DIGEST_CHALLENGE =
693  "Digest realm=\"Squid proxy-caching web server\","
694  " nonce=\"Nny4QuC5PwiSDixJ\","
695  " qop=\"auth\","
696  " stale=false";
697const char * const DIGEST_URI =
698  "/";
699const char * const DIGEST_CNONCE =
700  "6501d58e9a21cee1e7b5fec894ded024";
701const char * const DIGEST_RESPONSE =
702  "edffcb0829e755838b073a4a42de06bc";
703#endif
704
705std::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)
718struct 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
736HttpAuthResult 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