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
find_string(size_t & index,const std::string & needle,const char * const haystack[],size_t max_index)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
Nametalk_base::Enum75 static inline const char* Name(E val) { return Names[val]; }
Parsetalk_base::Enum76 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
operator E&talk_base::Enum86 inline operator E&() { return val; }
operator =talk_base::Enum87 inline Enum& operator=(E rhs) { val = rhs; return *this; }
88
nametalk_base::Enum89 inline const char* name() const { return Name(val); }
assigntalk_base::Enum90 inline bool assign(const std::string& name) { return Parse(val, name); }
operator =talk_base::Enum91 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
ToString(HttpVersion version)144 const char* ToString(HttpVersion version) {
145 return Enum<HttpVersion>::Name(version);
146 }
147
FromString(HttpVersion & version,const std::string & str)148 bool FromString(HttpVersion& version, const std::string& str) {
149 return Enum<HttpVersion>::Parse(version, str);
150 }
151
ToString(HttpVerb verb)152 const char* ToString(HttpVerb verb) {
153 return Enum<HttpVerb>::Name(verb);
154 }
155
FromString(HttpVerb & verb,const std::string & str)156 bool FromString(HttpVerb& verb, const std::string& str) {
157 return Enum<HttpVerb>::Parse(verb, str);
158 }
159
ToString(HttpHeader header)160 const char* ToString(HttpHeader header) {
161 return Enum<HttpHeader>::Name(header);
162 }
163
FromString(HttpHeader & header,const std::string & str)164 bool FromString(HttpHeader& header, const std::string& str) {
165 return Enum<HttpHeader>::Parse(header, str);
166 }
167
HttpCodeHasBody(uint32 code)168 bool HttpCodeHasBody(uint32 code) {
169 return !HttpCodeIsInformational(code)
170 && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
171 }
172
HttpCodeIsCacheable(uint32 code)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
HttpHeaderIsEndToEnd(HttpHeader header)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
HttpHeaderIsCollapsible(HttpHeader header)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
HttpShouldKeepAlive(const HttpData & data)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
IsEndOfAttributeName(size_t pos,size_t len,const char * data)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
EscapeAttribute(const std::string & attribute)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
HttpComposeAttributes(const HttpAttributeList & attributes,char separator,std::string * composed)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
HttpParseAttributes(const char * data,size_t len,HttpAttributeList & attributes)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
HttpHasAttribute(const HttpAttributeList & attributes,const std::string & name,std::string * value)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
HttpHasNthAttribute(HttpAttributeList & attributes,size_t index,std::string * name,std::string * value)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
HttpDateToSeconds(const std::string & date,unsigned long * seconds)347 bool HttpDateToSeconds(const std::string& date, unsigned long* 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 unsigned long 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
HttpAddress(const SocketAddress & address,bool secure)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
clear(bool release_document)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
copy(const HttpData & src)431 HttpData::copy(const HttpData& src) {
432 headers_ = src.headers_;
433 }
434
435 void
changeHeader(const std::string & name,const std::string & value,HeaderCombine combine)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
clearHeader(const std::string & name)461 size_t HttpData::clearHeader(const std::string& name) {
462 return headers_.erase(name);
463 }
464
clearHeader(iterator header)465 HttpData::iterator HttpData::clearHeader(iterator header) {
466 iterator deprecated = header++;
467 headers_.erase(deprecated);
468 return header;
469 }
470
471 bool
hasHeader(const std::string & name,std::string * value) const472 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
setContent(const std::string & content_type,StreamInterface * document)482 void HttpData::setContent(const std::string& content_type,
483 StreamInterface* document) {
484 setHeader(HH_CONTENT_TYPE, content_type);
485 setDocumentAndLength(document);
486 }
487
setDocumentAndLength(StreamInterface * document)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
clear(bool release_document)509 HttpRequestData::clear(bool release_document) {
510 verb = HV_GET;
511 path.clear();
512 HttpData::clear(release_document);
513 }
514
515 void
copy(const HttpRequestData & src)516 HttpRequestData::copy(const HttpRequestData& src) {
517 verb = src.verb;
518 path = src.path;
519 HttpData::copy(src);
520 }
521
522 size_t
formatLeader(char * buffer,size_t size) const523 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
parseLeader(const char * line,size_t len)530 HttpRequestData::parseLeader(const char* line, size_t len) {
531 UNUSED(len);
532 unsigned int vmajor, vminor;
533 int vend, dstart, dend;
534 if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", &vend, &dstart, &dend,
535 &vmajor, &vminor) != 2)
536 || (vmajor != 1)) {
537 return HE_PROTOCOL;
538 }
539 if (vminor == 0) {
540 version = HVER_1_0;
541 } else if (vminor == 1) {
542 version = HVER_1_1;
543 } else {
544 return HE_PROTOCOL;
545 }
546 std::string sverb(line, vend);
547 if (!FromString(verb, sverb.c_str())) {
548 return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
549 }
550 path.assign(line + dstart, line + dend);
551 return HE_NONE;
552 }
553
getAbsoluteUri(std::string * uri) const554 bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
555 if (HV_CONNECT == verb)
556 return false;
557 Url<char> url(path);
558 if (url.valid()) {
559 uri->assign(path);
560 return true;
561 }
562 std::string host;
563 if (!hasHeader(HH_HOST, &host))
564 return false;
565 url.set_address(host);
566 url.set_full_path(path);
567 uri->assign(url.url());
568 return url.valid();
569 }
570
getRelativeUri(std::string * host,std::string * path) const571 bool HttpRequestData::getRelativeUri(std::string* host,
572 std::string* path) const
573 {
574 if (HV_CONNECT == verb)
575 return false;
576 Url<char> url(this->path);
577 if (url.valid()) {
578 host->assign(url.address());
579 path->assign(url.full_path());
580 return true;
581 }
582 if (!hasHeader(HH_HOST, host))
583 return false;
584 path->assign(this->path);
585 return true;
586 }
587
588 //
589 // HttpResponseData
590 //
591
592 void
clear(bool release_document)593 HttpResponseData::clear(bool release_document) {
594 scode = HC_INTERNAL_SERVER_ERROR;
595 message.clear();
596 HttpData::clear(release_document);
597 }
598
599 void
copy(const HttpResponseData & src)600 HttpResponseData::copy(const HttpResponseData& src) {
601 scode = src.scode;
602 message = src.message;
603 HttpData::copy(src);
604 }
605
606 void
set_success(uint32 scode)607 HttpResponseData::set_success(uint32 scode) {
608 this->scode = scode;
609 message.clear();
610 setHeader(HH_CONTENT_LENGTH, "0", false);
611 }
612
613 void
set_success(const std::string & content_type,StreamInterface * document,uint32 scode)614 HttpResponseData::set_success(const std::string& content_type,
615 StreamInterface* document,
616 uint32 scode) {
617 this->scode = scode;
618 message.erase(message.begin(), message.end());
619 setContent(content_type, document);
620 }
621
622 void
set_redirect(const std::string & location,uint32 scode)623 HttpResponseData::set_redirect(const std::string& location, uint32 scode) {
624 this->scode = scode;
625 message.clear();
626 setHeader(HH_LOCATION, location);
627 setHeader(HH_CONTENT_LENGTH, "0", false);
628 }
629
630 void
set_error(uint32 scode)631 HttpResponseData::set_error(uint32 scode) {
632 this->scode = scode;
633 message.clear();
634 setHeader(HH_CONTENT_LENGTH, "0", false);
635 }
636
637 size_t
formatLeader(char * buffer,size_t size) const638 HttpResponseData::formatLeader(char* buffer, size_t size) const {
639 size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
640 if (!message.empty()) {
641 len += sprintfn(buffer + len, size - len, " %.*s",
642 message.size(), message.data());
643 }
644 return len;
645 }
646
647 HttpError
parseLeader(const char * line,size_t len)648 HttpResponseData::parseLeader(const char* line, size_t len) {
649 size_t pos = 0;
650 unsigned int vmajor, vminor, temp_scode;
651 int temp_pos;
652 if (sscanf(line, "HTTP %u%n",
653 &temp_scode, &temp_pos) == 1) {
654 // This server's response has no version. :( NOTE: This happens for every
655 // response to requests made from Chrome plugins, regardless of the server's
656 // behaviour.
657 LOG(LS_VERBOSE) << "HTTP version missing from response";
658 version = HVER_UNKNOWN;
659 } else if ((sscanf(line, "HTTP/%u.%u %u%n",
660 &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
661 && (vmajor == 1)) {
662 // This server's response does have a version.
663 if (vminor == 0) {
664 version = HVER_1_0;
665 } else if (vminor == 1) {
666 version = HVER_1_1;
667 } else {
668 return HE_PROTOCOL;
669 }
670 } else {
671 return HE_PROTOCOL;
672 }
673 scode = temp_scode;
674 pos = static_cast<size_t>(temp_pos);
675 while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
676 message.assign(line + pos, len - pos);
677 return HE_NONE;
678 }
679
680 //////////////////////////////////////////////////////////////////////
681 // Http Authentication
682 //////////////////////////////////////////////////////////////////////
683
684 #define TEST_DIGEST 0
685 #if TEST_DIGEST
686 /*
687 const char * const DIGEST_CHALLENGE =
688 "Digest realm=\"testrealm@host.com\","
689 " qop=\"auth,auth-int\","
690 " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
691 " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
692 const char * const DIGEST_METHOD = "GET";
693 const char * const DIGEST_URI =
694 "/dir/index.html";;
695 const char * const DIGEST_CNONCE =
696 "0a4f113b";
697 const char * const DIGEST_RESPONSE =
698 "6629fae49393a05397450978507c4ef1";
699 //user_ = "Mufasa";
700 //pass_ = "Circle Of Life";
701 */
702 const char * const DIGEST_CHALLENGE =
703 "Digest realm=\"Squid proxy-caching web server\","
704 " nonce=\"Nny4QuC5PwiSDixJ\","
705 " qop=\"auth\","
706 " stale=false";
707 const char * const DIGEST_URI =
708 "/";
709 const char * const DIGEST_CNONCE =
710 "6501d58e9a21cee1e7b5fec894ded024";
711 const char * const DIGEST_RESPONSE =
712 "edffcb0829e755838b073a4a42de06bc";
713 #endif
714
quote(const std::string & str)715 std::string quote(const std::string& str) {
716 std::string result;
717 result.push_back('"');
718 for (size_t i=0; i<str.size(); ++i) {
719 if ((str[i] == '"') || (str[i] == '\\'))
720 result.push_back('\\');
721 result.push_back(str[i]);
722 }
723 result.push_back('"');
724 return result;
725 }
726
727 #ifdef WIN32
728 struct NegotiateAuthContext : public HttpAuthContext {
729 CredHandle cred;
730 CtxtHandle ctx;
731 size_t steps;
732 bool specified_credentials;
733
NegotiateAuthContexttalk_base::NegotiateAuthContext734 NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
735 : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
736 specified_credentials(false)
737 { }
738
~NegotiateAuthContexttalk_base::NegotiateAuthContext739 virtual ~NegotiateAuthContext() {
740 DeleteSecurityContext(&ctx);
741 FreeCredentialsHandle(&cred);
742 }
743 };
744 #endif // WIN32
745
HttpAuthenticate(const char * challenge,size_t len,const SocketAddress & server,const std::string & method,const std::string & uri,const std::string & username,const CryptString & password,HttpAuthContext * & context,std::string & response,std::string & auth_method)746 HttpAuthResult HttpAuthenticate(
747 const char * challenge, size_t len,
748 const SocketAddress& server,
749 const std::string& method, const std::string& uri,
750 const std::string& username, const CryptString& password,
751 HttpAuthContext *& context, std::string& response, std::string& auth_method)
752 {
753 #if TEST_DIGEST
754 challenge = DIGEST_CHALLENGE;
755 len = strlen(challenge);
756 #endif
757
758 HttpAttributeList args;
759 HttpParseAttributes(challenge, len, args);
760 HttpHasNthAttribute(args, 0, &auth_method, NULL);
761
762 if (context && (context->auth_method != auth_method))
763 return HAR_IGNORE;
764
765 // BASIC
766 if (_stricmp(auth_method.c_str(), "basic") == 0) {
767 if (context)
768 return HAR_CREDENTIALS; // Bad credentials
769 if (username.empty())
770 return HAR_CREDENTIALS; // Missing credentials
771
772 context = new HttpAuthContext(auth_method);
773
774 // TODO: convert sensitive to a secure buffer that gets securely deleted
775 //std::string decoded = username + ":" + password;
776 size_t len = username.size() + password.GetLength() + 2;
777 char * sensitive = new char[len];
778 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
779 pos += strcpyn(sensitive + pos, len - pos, ":");
780 password.CopyTo(sensitive + pos, true);
781
782 response = auth_method;
783 response.append(" ");
784 // TODO: create a sensitive-source version of Base64::encode
785 response.append(Base64::Encode(sensitive));
786 memset(sensitive, 0, len);
787 delete [] sensitive;
788 return HAR_RESPONSE;
789 }
790
791 // DIGEST
792 if (_stricmp(auth_method.c_str(), "digest") == 0) {
793 if (context)
794 return HAR_CREDENTIALS; // Bad credentials
795 if (username.empty())
796 return HAR_CREDENTIALS; // Missing credentials
797
798 context = new HttpAuthContext(auth_method);
799
800 std::string cnonce, ncount;
801 #if TEST_DIGEST
802 method = DIGEST_METHOD;
803 uri = DIGEST_URI;
804 cnonce = DIGEST_CNONCE;
805 #else
806 char buffer[256];
807 sprintf(buffer, "%d", static_cast<int>(time(0)));
808 cnonce = MD5(buffer);
809 #endif
810 ncount = "00000001";
811
812 std::string realm, nonce, qop, opaque;
813 HttpHasAttribute(args, "realm", &realm);
814 HttpHasAttribute(args, "nonce", &nonce);
815 bool has_qop = HttpHasAttribute(args, "qop", &qop);
816 bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
817
818 // TODO: convert sensitive to be secure buffer
819 //std::string A1 = username + ":" + realm + ":" + password;
820 size_t len = username.size() + realm.size() + password.GetLength() + 3;
821 char * sensitive = new char[len]; // A1
822 size_t pos = strcpyn(sensitive, len, username.data(), username.size());
823 pos += strcpyn(sensitive + pos, len - pos, ":");
824 pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
825 pos += strcpyn(sensitive + pos, len - pos, ":");
826 password.CopyTo(sensitive + pos, true);
827
828 std::string A2 = method + ":" + uri;
829 std::string middle;
830 if (has_qop) {
831 qop = "auth";
832 middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
833 } else {
834 middle = nonce;
835 }
836 std::string HA1 = MD5(sensitive);
837 memset(sensitive, 0, len);
838 delete [] sensitive;
839 std::string HA2 = MD5(A2);
840 std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
841
842 #if TEST_DIGEST
843 ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0);
844 #endif
845
846 std::stringstream ss;
847 ss << auth_method;
848 ss << " username=" << quote(username);
849 ss << ", realm=" << quote(realm);
850 ss << ", nonce=" << quote(nonce);
851 ss << ", uri=" << quote(uri);
852 if (has_qop) {
853 ss << ", qop=" << qop;
854 ss << ", nc=" << ncount;
855 ss << ", cnonce=" << quote(cnonce);
856 }
857 ss << ", response=\"" << dig_response << "\"";
858 if (has_opaque) {
859 ss << ", opaque=" << quote(opaque);
860 }
861 response = ss.str();
862 return HAR_RESPONSE;
863 }
864
865 #ifdef WIN32
866 #if 1
867 bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
868 bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
869 // SPNEGO & NTLM
870 if (want_negotiate || want_ntlm) {
871 const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
872 char out_buf[MAX_MESSAGE], spn[MAX_SPN];
873
874 #if 0 // Requires funky windows versions
875 DWORD len = MAX_SPN;
876 if (DsMakeSpn("HTTP", server.IPAsString().c_str(), NULL, server.port(),
877 0, &len, spn) != ERROR_SUCCESS) {
878 LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
879 return HAR_IGNORE;
880 }
881 #else
882 sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
883 #endif
884
885 SecBuffer out_sec;
886 out_sec.pvBuffer = out_buf;
887 out_sec.cbBuffer = sizeof(out_buf);
888 out_sec.BufferType = SECBUFFER_TOKEN;
889
890 SecBufferDesc out_buf_desc;
891 out_buf_desc.ulVersion = 0;
892 out_buf_desc.cBuffers = 1;
893 out_buf_desc.pBuffers = &out_sec;
894
895 const ULONG NEG_FLAGS_DEFAULT =
896 //ISC_REQ_ALLOCATE_MEMORY
897 ISC_REQ_CONFIDENTIALITY
898 //| ISC_REQ_EXTENDED_ERROR
899 //| ISC_REQ_INTEGRITY
900 | ISC_REQ_REPLAY_DETECT
901 | ISC_REQ_SEQUENCE_DETECT
902 //| ISC_REQ_STREAM
903 //| ISC_REQ_USE_SUPPLIED_CREDS
904 ;
905
906 ::TimeStamp lifetime;
907 SECURITY_STATUS ret = S_OK;
908 ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
909
910 bool specify_credentials = !username.empty();
911 size_t steps = 0;
912
913 //uint32 now = Time();
914
915 NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
916 if (neg) {
917 const size_t max_steps = 10;
918 if (++neg->steps >= max_steps) {
919 LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries";
920 return HAR_ERROR;
921 }
922 steps = neg->steps;
923
924 std::string challenge, decoded_challenge;
925 if (HttpHasNthAttribute(args, 1, &challenge, NULL)
926 && Base64::Decode(challenge, Base64::DO_STRICT,
927 &decoded_challenge, NULL)) {
928 SecBuffer in_sec;
929 in_sec.pvBuffer = const_cast<char *>(decoded_challenge.data());
930 in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
931 in_sec.BufferType = SECBUFFER_TOKEN;
932
933 SecBufferDesc in_buf_desc;
934 in_buf_desc.ulVersion = 0;
935 in_buf_desc.cBuffers = 1;
936 in_buf_desc.pBuffers = &in_sec;
937
938 ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
939 //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
940 if (FAILED(ret)) {
941 LOG(LS_ERROR) << "InitializeSecurityContext returned: "
942 << ErrorName(ret, SECURITY_ERRORS);
943 return HAR_ERROR;
944 }
945 } else if (neg->specified_credentials) {
946 // Try again with default credentials
947 specify_credentials = false;
948 delete context;
949 context = neg = 0;
950 } else {
951 return HAR_CREDENTIALS;
952 }
953 }
954
955 if (!neg) {
956 unsigned char userbuf[256], passbuf[256], domainbuf[16];
957 SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
958 if (specify_credentials) {
959 memset(&auth_id, 0, sizeof(auth_id));
960 size_t len = password.GetLength()+1;
961 char * sensitive = new char[len];
962 password.CopyTo(sensitive, true);
963 std::string::size_type pos = username.find('\\');
964 if (pos == std::string::npos) {
965 auth_id.UserLength = static_cast<unsigned long>(
966 _min(sizeof(userbuf) - 1, username.size()));
967 memcpy(userbuf, username.c_str(), auth_id.UserLength);
968 userbuf[auth_id.UserLength] = 0;
969 auth_id.DomainLength = 0;
970 domainbuf[auth_id.DomainLength] = 0;
971 auth_id.PasswordLength = static_cast<unsigned long>(
972 _min(sizeof(passbuf) - 1, password.GetLength()));
973 memcpy(passbuf, sensitive, auth_id.PasswordLength);
974 passbuf[auth_id.PasswordLength] = 0;
975 } else {
976 auth_id.UserLength = static_cast<unsigned long>(
977 _min(sizeof(userbuf) - 1, username.size() - pos - 1));
978 memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
979 userbuf[auth_id.UserLength] = 0;
980 auth_id.DomainLength = static_cast<unsigned long>(
981 _min(sizeof(domainbuf) - 1, pos));
982 memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
983 domainbuf[auth_id.DomainLength] = 0;
984 auth_id.PasswordLength = static_cast<unsigned long>(
985 _min(sizeof(passbuf) - 1, password.GetLength()));
986 memcpy(passbuf, sensitive, auth_id.PasswordLength);
987 passbuf[auth_id.PasswordLength] = 0;
988 }
989 memset(sensitive, 0, len);
990 delete [] sensitive;
991 auth_id.User = userbuf;
992 auth_id.Domain = domainbuf;
993 auth_id.Password = passbuf;
994 auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
995 pauth_id = &auth_id;
996 LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials";
997 } else {
998 LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
999 }
1000
1001 CredHandle cred;
1002 ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
1003 //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now);
1004 if (ret != SEC_E_OK) {
1005 LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
1006 << ErrorName(ret, SECURITY_ERRORS);
1007 return HAR_IGNORE;
1008 }
1009
1010 //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
1011
1012 CtxtHandle ctx;
1013 ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
1014 //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
1015 if (FAILED(ret)) {
1016 LOG(LS_ERROR) << "InitializeSecurityContext returned: "
1017 << ErrorName(ret, SECURITY_ERRORS);
1018 FreeCredentialsHandle(&cred);
1019 return HAR_IGNORE;
1020 }
1021
1022 ASSERT(!context);
1023 context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
1024 neg->specified_credentials = specify_credentials;
1025 neg->steps = steps;
1026 }
1027
1028 if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
1029 ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
1030 //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now);
1031 LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
1032 << ErrorName(ret, SECURITY_ERRORS);
1033 if (FAILED(ret)) {
1034 return HAR_ERROR;
1035 }
1036 }
1037
1038 //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms";
1039
1040 std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
1041 response = auth_method;
1042 response.append(" ");
1043 response.append(Base64::Encode(decoded));
1044 return HAR_RESPONSE;
1045 }
1046 #endif
1047 #endif // WIN32
1048
1049 return HAR_IGNORE;
1050 }
1051
1052 //////////////////////////////////////////////////////////////////////
1053
1054 } // namespace talk_base
1055