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