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