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