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