Fix typo in //ui/base/BUILD.gn.
[chromium-blink-merge.git] / net / http / http_util.cc
blob53693f4bae5b98b8ba2fcf1fd2c98832d462feb8
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // The rules for parsing content-types were borrowed from Firefox:
6 // http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834
8 #include "net/http/http_util.h"
10 #include <algorithm>
12 #include "base/basictypes.h"
13 #include "base/logging.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_piece.h"
16 #include "base/strings/string_tokenizer.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/time/time.h"
22 namespace net {
24 // Helpers --------------------------------------------------------------------
26 // Returns the index of the closing quote of the string, if any. |start| points
27 // at the opening quote.
28 static size_t FindStringEnd(const std::string& line, size_t start, char delim) {
29 DCHECK_LT(start, line.length());
30 DCHECK_EQ(line[start], delim);
31 DCHECK((delim == '"') || (delim == '\''));
33 const char set[] = { delim, '\\', '\0' };
34 for (size_t end = line.find_first_of(set, start + 1);
35 end != std::string::npos; end = line.find_first_of(set, end + 2)) {
36 if (line[end] != '\\')
37 return end;
39 return line.length();
43 // HttpUtil -------------------------------------------------------------------
45 // static
46 size_t HttpUtil::FindDelimiter(const std::string& line,
47 size_t search_start,
48 char delimiter) {
49 do {
50 // search_start points to the spot from which we should start looking
51 // for the delimiter.
52 const char delim_str[] = { delimiter, '"', '\'', '\0' };
53 size_t cur_delim_pos = line.find_first_of(delim_str, search_start);
54 if (cur_delim_pos == std::string::npos)
55 return line.length();
57 char ch = line[cur_delim_pos];
58 if (ch == delimiter) {
59 // Found delimiter
60 return cur_delim_pos;
63 // We hit the start of a quoted string. Look for its end.
64 search_start = FindStringEnd(line, cur_delim_pos, ch);
65 if (search_start == line.length())
66 return search_start;
68 ++search_start;
70 // search_start now points to the first char after the end of the
71 // string, so just go back to the top of the loop and look for
72 // |delimiter| again.
73 } while (true);
75 NOTREACHED();
76 return line.length();
79 // static
80 void HttpUtil::ParseContentType(const std::string& content_type_str,
81 std::string* mime_type,
82 std::string* charset,
83 bool* had_charset,
84 std::string* boundary) {
85 const std::string::const_iterator begin = content_type_str.begin();
87 // Trim leading and trailing whitespace from type. We include '(' in
88 // the trailing trim set to catch media-type comments, which are not at all
89 // standard, but may occur in rare cases.
90 size_t type_val = content_type_str.find_first_not_of(HTTP_LWS);
91 type_val = std::min(type_val, content_type_str.length());
92 size_t type_end = content_type_str.find_first_of(HTTP_LWS ";(", type_val);
93 if (type_end == std::string::npos)
94 type_end = content_type_str.length();
96 size_t charset_val = 0;
97 size_t charset_end = 0;
98 bool type_has_charset = false;
100 // Iterate over parameters
101 size_t param_start = content_type_str.find_first_of(';', type_end);
102 if (param_start != std::string::npos) {
103 base::StringTokenizer tokenizer(begin + param_start, content_type_str.end(),
104 ";");
105 tokenizer.set_quote_chars("\"");
106 while (tokenizer.GetNext()) {
107 std::string::const_iterator equals_sign =
108 std::find(tokenizer.token_begin(), tokenizer.token_end(), '=');
109 if (equals_sign == tokenizer.token_end())
110 continue;
112 std::string::const_iterator param_name_begin = tokenizer.token_begin();
113 std::string::const_iterator param_name_end = equals_sign;
114 TrimLWS(&param_name_begin, &param_name_end);
116 std::string::const_iterator param_value_begin = equals_sign + 1;
117 std::string::const_iterator param_value_end = tokenizer.token_end();
118 DCHECK(param_value_begin <= tokenizer.token_end());
119 TrimLWS(&param_value_begin, &param_value_end);
121 if (base::LowerCaseEqualsASCII(param_name_begin, param_name_end,
122 "charset")) {
123 // TODO(abarth): Refactor this function to consistently use iterators.
124 charset_val = param_value_begin - begin;
125 charset_end = param_value_end - begin;
126 type_has_charset = true;
127 } else if (base::LowerCaseEqualsASCII(param_name_begin, param_name_end,
128 "boundary")) {
129 if (boundary)
130 boundary->assign(param_value_begin, param_value_end);
135 if (type_has_charset) {
136 // Trim leading and trailing whitespace from charset_val. We include
137 // '(' in the trailing trim set to catch media-type comments, which are
138 // not at all standard, but may occur in rare cases.
139 charset_val = content_type_str.find_first_not_of(HTTP_LWS, charset_val);
140 charset_val = std::min(charset_val, charset_end);
141 char first_char = content_type_str[charset_val];
142 if (first_char == '"' || first_char == '\'') {
143 charset_end = FindStringEnd(content_type_str, charset_val, first_char);
144 ++charset_val;
145 DCHECK(charset_end >= charset_val);
146 } else {
147 charset_end = std::min(content_type_str.find_first_of(HTTP_LWS ";(",
148 charset_val),
149 charset_end);
153 // if the server sent "*/*", it is meaningless, so do not store it.
154 // also, if type_val is the same as mime_type, then just update the
155 // charset. however, if charset is empty and mime_type hasn't
156 // changed, then don't wipe-out an existing charset. We
157 // also want to reject a mime-type if it does not include a slash.
158 // some servers give junk after the charset parameter, which may
159 // include a comma, so this check makes us a bit more tolerant.
160 if (content_type_str.length() != 0 &&
161 content_type_str != "*/*" &&
162 content_type_str.find_first_of('/') != std::string::npos) {
163 // Common case here is that mime_type is empty
164 bool eq = !mime_type->empty() &&
165 base::LowerCaseEqualsASCII(begin + type_val, begin + type_end,
166 mime_type->data());
167 if (!eq) {
168 mime_type->assign(begin + type_val, begin + type_end);
169 base::StringToLowerASCII(mime_type);
171 if ((!eq && *had_charset) || type_has_charset) {
172 *had_charset = true;
173 charset->assign(begin + charset_val, begin + charset_end);
174 base::StringToLowerASCII(charset);
179 // static
180 // Parse the Range header according to RFC 2616 14.35.1
181 // ranges-specifier = byte-ranges-specifier
182 // byte-ranges-specifier = bytes-unit "=" byte-range-set
183 // byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
184 // byte-range-spec = first-byte-pos "-" [last-byte-pos]
185 // first-byte-pos = 1*DIGIT
186 // last-byte-pos = 1*DIGIT
187 bool HttpUtil::ParseRanges(const std::string& headers,
188 std::vector<HttpByteRange>* ranges) {
189 std::string ranges_specifier;
190 HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
192 while (it.GetNext()) {
193 // Look for "Range" header.
194 if (!base::LowerCaseEqualsASCII(it.name(), "range"))
195 continue;
196 ranges_specifier = it.values();
197 // We just care about the first "Range" header, so break here.
198 break;
201 if (ranges_specifier.empty())
202 return false;
204 return ParseRangeHeader(ranges_specifier, ranges);
207 // static
208 bool HttpUtil::ParseRangeHeader(const std::string& ranges_specifier,
209 std::vector<HttpByteRange>* ranges) {
210 size_t equal_char_offset = ranges_specifier.find('=');
211 if (equal_char_offset == std::string::npos)
212 return false;
214 // Try to extract bytes-unit part.
215 std::string::const_iterator bytes_unit_begin = ranges_specifier.begin();
216 std::string::const_iterator bytes_unit_end = bytes_unit_begin +
217 equal_char_offset;
218 std::string::const_iterator byte_range_set_begin = bytes_unit_end + 1;
219 std::string::const_iterator byte_range_set_end = ranges_specifier.end();
221 TrimLWS(&bytes_unit_begin, &bytes_unit_end);
222 // "bytes" unit identifier is not found.
223 if (!base::LowerCaseEqualsASCII(bytes_unit_begin, bytes_unit_end, "bytes"))
224 return false;
226 ValuesIterator byte_range_set_iterator(byte_range_set_begin,
227 byte_range_set_end, ',');
228 while (byte_range_set_iterator.GetNext()) {
229 size_t minus_char_offset = byte_range_set_iterator.value().find('-');
230 // If '-' character is not found, reports failure.
231 if (minus_char_offset == std::string::npos)
232 return false;
234 std::string::const_iterator first_byte_pos_begin =
235 byte_range_set_iterator.value_begin();
236 std::string::const_iterator first_byte_pos_end =
237 first_byte_pos_begin + minus_char_offset;
238 TrimLWS(&first_byte_pos_begin, &first_byte_pos_end);
239 std::string first_byte_pos(first_byte_pos_begin, first_byte_pos_end);
241 HttpByteRange range;
242 // Try to obtain first-byte-pos.
243 if (!first_byte_pos.empty()) {
244 int64 first_byte_position = -1;
245 if (!base::StringToInt64(first_byte_pos, &first_byte_position))
246 return false;
247 range.set_first_byte_position(first_byte_position);
250 std::string::const_iterator last_byte_pos_begin =
251 byte_range_set_iterator.value_begin() + minus_char_offset + 1;
252 std::string::const_iterator last_byte_pos_end =
253 byte_range_set_iterator.value_end();
254 TrimLWS(&last_byte_pos_begin, &last_byte_pos_end);
255 std::string last_byte_pos(last_byte_pos_begin, last_byte_pos_end);
257 // We have last-byte-pos or suffix-byte-range-spec in this case.
258 if (!last_byte_pos.empty()) {
259 int64 last_byte_position;
260 if (!base::StringToInt64(last_byte_pos, &last_byte_position))
261 return false;
262 if (range.HasFirstBytePosition())
263 range.set_last_byte_position(last_byte_position);
264 else
265 range.set_suffix_length(last_byte_position);
266 } else if (!range.HasFirstBytePosition()) {
267 return false;
270 // Do a final check on the HttpByteRange object.
271 if (!range.IsValid())
272 return false;
273 ranges->push_back(range);
275 return !ranges->empty();
278 // static
279 bool HttpUtil::ParseRetryAfterHeader(const std::string& retry_after_string,
280 base::Time now,
281 base::TimeDelta* retry_after) {
282 int seconds;
283 base::Time time;
284 base::TimeDelta interval;
286 if (base::StringToInt(retry_after_string, &seconds)) {
287 interval = base::TimeDelta::FromSeconds(seconds);
288 } else if (base::Time::FromUTCString(retry_after_string.c_str(), &time)) {
289 interval = time - now;
290 } else {
291 return false;
294 if (interval < base::TimeDelta::FromSeconds(0))
295 return false;
297 *retry_after = interval;
298 return true;
301 // static
302 bool HttpUtil::HasHeader(const std::string& headers, const char* name) {
303 size_t name_len = strlen(name);
304 std::string::const_iterator it =
305 std::search(headers.begin(),
306 headers.end(),
307 name,
308 name + name_len,
309 base::CaseInsensitiveCompareASCII<char>());
310 if (it == headers.end())
311 return false;
313 // ensure match is prefixed by newline
314 if (it != headers.begin() && it[-1] != '\n')
315 return false;
317 // ensure match is suffixed by colon
318 if (it + name_len >= headers.end() || it[name_len] != ':')
319 return false;
321 return true;
324 namespace {
325 // A header string containing any of the following fields will cause
326 // an error. The list comes from the XMLHttpRequest standard.
327 // http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader-method
328 const char* const kForbiddenHeaderFields[] = {
329 "accept-charset",
330 "accept-encoding",
331 "access-control-request-headers",
332 "access-control-request-method",
333 "connection",
334 "content-length",
335 "cookie",
336 "cookie2",
337 "content-transfer-encoding",
338 "date",
339 "expect",
340 "host",
341 "keep-alive",
342 "origin",
343 "referer",
344 "te",
345 "trailer",
346 "transfer-encoding",
347 "upgrade",
348 "user-agent",
349 "via",
351 } // anonymous namespace
353 // static
354 bool HttpUtil::IsSafeHeader(const std::string& name) {
355 std::string lower_name(base::StringToLowerASCII(name));
356 if (base::StartsWith(lower_name, "proxy-", base::CompareCase::SENSITIVE) ||
357 base::StartsWith(lower_name, "sec-", base::CompareCase::SENSITIVE))
358 return false;
359 for (size_t i = 0; i < arraysize(kForbiddenHeaderFields); ++i) {
360 if (lower_name == kForbiddenHeaderFields[i])
361 return false;
363 return true;
366 // static
367 bool HttpUtil::IsValidHeaderName(const std::string& name) {
368 // Check whether the header name is RFC 2616-compliant.
369 return HttpUtil::IsToken(name);
372 // static
373 bool HttpUtil::IsValidHeaderValue(const std::string& value) {
374 // Just a sanity check: disallow NUL and CRLF.
375 return value.find('\0') == std::string::npos &&
376 value.find("\r\n") == std::string::npos;
379 // static
380 std::string HttpUtil::StripHeaders(const std::string& headers,
381 const char* const headers_to_remove[],
382 size_t headers_to_remove_len) {
383 std::string stripped_headers;
384 HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
386 while (it.GetNext()) {
387 bool should_remove = false;
388 for (size_t i = 0; i < headers_to_remove_len; ++i) {
389 if (base::LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
390 headers_to_remove[i])) {
391 should_remove = true;
392 break;
395 if (!should_remove) {
396 // Assume that name and values are on the same line.
397 stripped_headers.append(it.name_begin(), it.values_end());
398 stripped_headers.append("\r\n");
401 return stripped_headers;
404 // static
405 bool HttpUtil::IsNonCoalescingHeader(std::string::const_iterator name_begin,
406 std::string::const_iterator name_end) {
407 // NOTE: "set-cookie2" headers do not support expires attributes, so we don't
408 // have to list them here.
409 const char* const kNonCoalescingHeaders[] = {
410 "date",
411 "expires",
412 "last-modified",
413 "location", // See bug 1050541 for details
414 "retry-after",
415 "set-cookie",
416 // The format of auth-challenges mixes both space separated tokens and
417 // comma separated properties, so coalescing on comma won't work.
418 "www-authenticate",
419 "proxy-authenticate",
420 // STS specifies that UAs must not process any STS headers after the first
421 // one.
422 "strict-transport-security"
424 for (size_t i = 0; i < arraysize(kNonCoalescingHeaders); ++i) {
425 if (base::LowerCaseEqualsASCII(name_begin, name_end,
426 kNonCoalescingHeaders[i]))
427 return true;
429 return false;
432 bool HttpUtil::IsLWS(char c) {
433 return strchr(HTTP_LWS, c) != NULL;
436 void HttpUtil::TrimLWS(std::string::const_iterator* begin,
437 std::string::const_iterator* end) {
438 // leading whitespace
439 while (*begin < *end && IsLWS((*begin)[0]))
440 ++(*begin);
442 // trailing whitespace
443 while (*begin < *end && IsLWS((*end)[-1]))
444 --(*end);
447 bool HttpUtil::IsQuote(char c) {
448 // Single quote mark isn't actually part of quoted-text production,
449 // but apparently some servers rely on this.
450 return c == '"' || c == '\'';
453 // See RFC 2616 Sec 2.2 for the definition of |token|.
454 bool HttpUtil::IsToken(std::string::const_iterator begin,
455 std::string::const_iterator end) {
456 if (begin == end)
457 return false;
458 for (std::string::const_iterator iter = begin; iter != end; ++iter) {
459 unsigned char c = *iter;
460 if (c >= 0x80 || c <= 0x1F || c == 0x7F ||
461 c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
462 c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
463 c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
464 c == '{' || c == '}' || c == ' ' || c == '\t')
465 return false;
467 return true;
470 std::string HttpUtil::Unquote(std::string::const_iterator begin,
471 std::string::const_iterator end) {
472 // Empty string
473 if (begin == end)
474 return std::string();
476 // Nothing to unquote.
477 if (!IsQuote(*begin))
478 return std::string(begin, end);
480 // No terminal quote mark.
481 if (end - begin < 2 || *begin != *(end - 1))
482 return std::string(begin, end);
484 // Strip quotemarks
485 ++begin;
486 --end;
488 // Unescape quoted-pair (defined in RFC 2616 section 2.2)
489 std::string unescaped;
490 bool prev_escape = false;
491 for (; begin != end; ++begin) {
492 char c = *begin;
493 if (c == '\\' && !prev_escape) {
494 prev_escape = true;
495 continue;
497 prev_escape = false;
498 unescaped.push_back(c);
500 return unescaped;
503 // static
504 std::string HttpUtil::Unquote(const std::string& str) {
505 return Unquote(str.begin(), str.end());
508 // static
509 std::string HttpUtil::Quote(const std::string& str) {
510 std::string escaped;
511 escaped.reserve(2 + str.size());
513 std::string::const_iterator begin = str.begin();
514 std::string::const_iterator end = str.end();
516 // Esape any backslashes or quotemarks within the string, and
517 // then surround with quotes.
518 escaped.push_back('"');
519 for (; begin != end; ++begin) {
520 char c = *begin;
521 if (c == '"' || c == '\\')
522 escaped.push_back('\\');
523 escaped.push_back(c);
525 escaped.push_back('"');
526 return escaped;
529 // Find the "http" substring in a status line. This allows for
530 // some slop at the start. If the "http" string could not be found
531 // then returns -1.
532 // static
533 int HttpUtil::LocateStartOfStatusLine(const char* buf, int buf_len) {
534 const int slop = 4;
535 const int http_len = 4;
537 if (buf_len >= http_len) {
538 int i_max = std::min(buf_len - http_len, slop);
539 for (int i = 0; i <= i_max; ++i) {
540 if (base::LowerCaseEqualsASCII(buf + i, buf + i + http_len, "http"))
541 return i;
544 return -1; // Not found
547 static int LocateEndOfHeadersHelper(const char* buf,
548 int buf_len,
549 int i,
550 bool accept_empty_header_list) {
551 char last_c = '\0';
552 bool was_lf = false;
553 if (accept_empty_header_list) {
554 // Normally two line breaks signal the end of a header list. An empty header
555 // list ends with a single line break at the start of the buffer.
556 last_c = '\n';
557 was_lf = true;
560 for (; i < buf_len; ++i) {
561 char c = buf[i];
562 if (c == '\n') {
563 if (was_lf)
564 return i + 1;
565 was_lf = true;
566 } else if (c != '\r' || last_c != '\n') {
567 was_lf = false;
569 last_c = c;
571 return -1;
574 int HttpUtil::LocateEndOfAdditionalHeaders(const char* buf,
575 int buf_len,
576 int i) {
577 return LocateEndOfHeadersHelper(buf, buf_len, i, true);
580 int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len, int i) {
581 return LocateEndOfHeadersHelper(buf, buf_len, i, false);
584 // In order for a line to be continuable, it must specify a
585 // non-blank header-name. Line continuations are specifically for
586 // header values -- do not allow headers names to span lines.
587 static bool IsLineSegmentContinuable(const char* begin, const char* end) {
588 if (begin == end)
589 return false;
591 const char* colon = std::find(begin, end, ':');
592 if (colon == end)
593 return false;
595 const char* name_begin = begin;
596 const char* name_end = colon;
598 // Name can't be empty.
599 if (name_begin == name_end)
600 return false;
602 // Can't start with LWS (this would imply the segment is a continuation)
603 if (HttpUtil::IsLWS(*name_begin))
604 return false;
606 return true;
609 // Helper used by AssembleRawHeaders, to find the end of the status line.
610 static const char* FindStatusLineEnd(const char* begin, const char* end) {
611 size_t i = base::StringPiece(begin, end - begin).find_first_of("\r\n");
612 if (i == base::StringPiece::npos)
613 return end;
614 return begin + i;
617 // Helper used by AssembleRawHeaders, to skip past leading LWS.
618 static const char* FindFirstNonLWS(const char* begin, const char* end) {
619 for (const char* cur = begin; cur != end; ++cur) {
620 if (!HttpUtil::IsLWS(*cur))
621 return cur;
623 return end; // Not found.
626 std::string HttpUtil::AssembleRawHeaders(const char* input_begin,
627 int input_len) {
628 std::string raw_headers;
629 raw_headers.reserve(input_len);
631 const char* input_end = input_begin + input_len;
633 // Skip any leading slop, since the consumers of this output
634 // (HttpResponseHeaders) don't deal with it.
635 int status_begin_offset = LocateStartOfStatusLine(input_begin, input_len);
636 if (status_begin_offset != -1)
637 input_begin += status_begin_offset;
639 // Copy the status line.
640 const char* status_line_end = FindStatusLineEnd(input_begin, input_end);
641 raw_headers.append(input_begin, status_line_end);
643 // After the status line, every subsequent line is a header line segment.
644 // Should a segment start with LWS, it is a continuation of the previous
645 // line's field-value.
647 // TODO(ericroman): is this too permissive? (delimits on [\r\n]+)
648 base::CStringTokenizer lines(status_line_end, input_end, "\r\n");
650 // This variable is true when the previous line was continuable.
651 bool prev_line_continuable = false;
653 while (lines.GetNext()) {
654 const char* line_begin = lines.token_begin();
655 const char* line_end = lines.token_end();
657 if (prev_line_continuable && IsLWS(*line_begin)) {
658 // Join continuation; reduce the leading LWS to a single SP.
659 raw_headers.push_back(' ');
660 raw_headers.append(FindFirstNonLWS(line_begin, line_end), line_end);
661 } else {
662 // Terminate the previous line.
663 raw_headers.push_back('\n');
665 // Copy the raw data to output.
666 raw_headers.append(line_begin, line_end);
668 // Check if the current line can be continued.
669 prev_line_continuable = IsLineSegmentContinuable(line_begin, line_end);
673 raw_headers.append("\n\n", 2);
675 // Use '\0' as the canonical line terminator. If the input already contained
676 // any embeded '\0' characters we will strip them first to avoid interpreting
677 // them as line breaks.
678 raw_headers.erase(std::remove(raw_headers.begin(), raw_headers.end(), '\0'),
679 raw_headers.end());
680 std::replace(raw_headers.begin(), raw_headers.end(), '\n', '\0');
682 return raw_headers;
685 std::string HttpUtil::ConvertHeadersBackToHTTPResponse(const std::string& str) {
686 std::string disassembled_headers;
687 base::StringTokenizer tokenizer(str, std::string(1, '\0'));
688 while (tokenizer.GetNext()) {
689 disassembled_headers.append(tokenizer.token_begin(), tokenizer.token_end());
690 disassembled_headers.append("\r\n");
692 disassembled_headers.append("\r\n");
694 return disassembled_headers;
697 // TODO(jungshik): 1. If the list is 'fr-CA,fr-FR,en,de', we have to add
698 // 'fr' after 'fr-CA' with the same q-value as 'fr-CA' because
699 // web servers, in general, do not fall back to 'fr' and may end up picking
700 // 'en' which has a lower preference than 'fr-CA' and 'fr-FR'.
701 // 2. This function assumes that the input is a comma separated list
702 // without any whitespace. As long as it comes from the preference and
703 // a user does not manually edit the preference file, it's the case. Still,
704 // we may have to make it more robust.
705 std::string HttpUtil::GenerateAcceptLanguageHeader(
706 const std::string& raw_language_list) {
707 // We use integers for qvalue and qvalue decrement that are 10 times
708 // larger than actual values to avoid a problem with comparing
709 // two floating point numbers.
710 const unsigned int kQvalueDecrement10 = 2;
711 unsigned int qvalue10 = 10;
712 base::StringTokenizer t(raw_language_list, ",");
713 std::string lang_list_with_q;
714 while (t.GetNext()) {
715 std::string language = t.token();
716 if (qvalue10 == 10) {
717 // q=1.0 is implicit.
718 lang_list_with_q = language;
719 } else {
720 DCHECK_LT(qvalue10, 10U);
721 base::StringAppendF(&lang_list_with_q, ",%s;q=0.%d", language.c_str(),
722 qvalue10);
724 // It does not make sense to have 'q=0'.
725 if (qvalue10 > kQvalueDecrement10)
726 qvalue10 -= kQvalueDecrement10;
728 return lang_list_with_q;
731 void HttpUtil::AppendHeaderIfMissing(const char* header_name,
732 const std::string& header_value,
733 std::string* headers) {
734 if (header_value.empty())
735 return;
736 if (HttpUtil::HasHeader(*headers, header_name))
737 return;
738 *headers += std::string(header_name) + ": " + header_value + "\r\n";
741 bool HttpUtil::HasStrongValidators(HttpVersion version,
742 const std::string& etag_header,
743 const std::string& last_modified_header,
744 const std::string& date_header) {
745 if (version < HttpVersion(1, 1))
746 return false;
748 if (!etag_header.empty()) {
749 size_t slash = etag_header.find('/');
750 if (slash == std::string::npos || slash == 0)
751 return true;
753 std::string::const_iterator i = etag_header.begin();
754 std::string::const_iterator j = etag_header.begin() + slash;
755 TrimLWS(&i, &j);
756 if (!base::LowerCaseEqualsASCII(i, j, "w"))
757 return true;
760 base::Time last_modified;
761 if (!base::Time::FromString(last_modified_header.c_str(), &last_modified))
762 return false;
764 base::Time date;
765 if (!base::Time::FromString(date_header.c_str(), &date))
766 return false;
768 return ((date - last_modified).InSeconds() >= 60);
771 // Functions for histogram initialization. The code 0 is put in the map to
772 // track status codes that are invalid.
773 // TODO(gavinp): Greatly prune the collected codes once we learn which
774 // ones are not sent in practice, to reduce upload size & memory use.
776 enum {
777 HISTOGRAM_MIN_HTTP_STATUS_CODE = 100,
778 HISTOGRAM_MAX_HTTP_STATUS_CODE = 599,
781 // static
782 std::vector<int> HttpUtil::GetStatusCodesForHistogram() {
783 std::vector<int> codes;
784 codes.reserve(
785 HISTOGRAM_MAX_HTTP_STATUS_CODE - HISTOGRAM_MIN_HTTP_STATUS_CODE + 2);
786 codes.push_back(0);
787 for (int i = HISTOGRAM_MIN_HTTP_STATUS_CODE;
788 i <= HISTOGRAM_MAX_HTTP_STATUS_CODE; ++i)
789 codes.push_back(i);
790 return codes;
793 // static
794 int HttpUtil::MapStatusCodeForHistogram(int code) {
795 if (HISTOGRAM_MIN_HTTP_STATUS_CODE <= code &&
796 code <= HISTOGRAM_MAX_HTTP_STATUS_CODE)
797 return code;
798 return 0;
801 // BNF from section 4.2 of RFC 2616:
803 // message-header = field-name ":" [ field-value ]
804 // field-name = token
805 // field-value = *( field-content | LWS )
806 // field-content = <the OCTETs making up the field-value
807 // and consisting of either *TEXT or combinations
808 // of token, separators, and quoted-string>
811 HttpUtil::HeadersIterator::HeadersIterator(
812 std::string::const_iterator headers_begin,
813 std::string::const_iterator headers_end,
814 const std::string& line_delimiter)
815 : lines_(headers_begin, headers_end, line_delimiter) {
818 HttpUtil::HeadersIterator::~HeadersIterator() {
821 bool HttpUtil::HeadersIterator::GetNext() {
822 while (lines_.GetNext()) {
823 name_begin_ = lines_.token_begin();
824 values_end_ = lines_.token_end();
826 std::string::const_iterator colon(std::find(name_begin_, values_end_, ':'));
827 if (colon == values_end_)
828 continue; // skip malformed header
830 name_end_ = colon;
832 // If the name starts with LWS, it is an invalid line.
833 // Leading LWS implies a line continuation, and these should have
834 // already been joined by AssembleRawHeaders().
835 if (name_begin_ == name_end_ || IsLWS(*name_begin_))
836 continue;
838 TrimLWS(&name_begin_, &name_end_);
839 if (name_begin_ == name_end_)
840 continue; // skip malformed header
842 values_begin_ = colon + 1;
843 TrimLWS(&values_begin_, &values_end_);
845 // if we got a header name, then we are done.
846 return true;
848 return false;
851 bool HttpUtil::HeadersIterator::AdvanceTo(const char* name) {
852 DCHECK(name != NULL);
853 DCHECK_EQ(0, base::StringToLowerASCII<std::string>(name).compare(name))
854 << "the header name must be in all lower case";
856 while (GetNext()) {
857 if (base::LowerCaseEqualsASCII(name_begin_, name_end_, name)) {
858 return true;
862 return false;
865 HttpUtil::ValuesIterator::ValuesIterator(
866 std::string::const_iterator values_begin,
867 std::string::const_iterator values_end,
868 char delimiter)
869 : values_(values_begin, values_end, std::string(1, delimiter)) {
870 values_.set_quote_chars("\'\"");
873 HttpUtil::ValuesIterator::~ValuesIterator() {
876 bool HttpUtil::ValuesIterator::GetNext() {
877 while (values_.GetNext()) {
878 value_begin_ = values_.token_begin();
879 value_end_ = values_.token_end();
880 TrimLWS(&value_begin_, &value_end_);
882 // bypass empty values.
883 if (value_begin_ != value_end_)
884 return true;
886 return false;
889 HttpUtil::NameValuePairsIterator::NameValuePairsIterator(
890 std::string::const_iterator begin,
891 std::string::const_iterator end,
892 char delimiter)
893 : props_(begin, end, delimiter),
894 valid_(true),
895 name_begin_(end),
896 name_end_(end),
897 value_begin_(end),
898 value_end_(end),
899 value_is_quoted_(false) {
902 HttpUtil::NameValuePairsIterator::~NameValuePairsIterator() {}
904 // We expect properties to be formatted as one of:
905 // name="value"
906 // name='value'
907 // name='\'value\''
908 // name=value
909 // name = value
910 // name=
911 // Due to buggy implementations found in some embedded devices, we also
912 // accept values with missing close quotemark (http://crbug.com/39836):
913 // name="value
914 bool HttpUtil::NameValuePairsIterator::GetNext() {
915 if (!props_.GetNext())
916 return false;
918 // Set the value as everything. Next we will split out the name.
919 value_begin_ = props_.value_begin();
920 value_end_ = props_.value_end();
921 name_begin_ = name_end_ = value_end_;
923 // Scan for the equals sign.
924 std::string::const_iterator equals = std::find(value_begin_, value_end_, '=');
925 if (equals == value_end_ || equals == value_begin_)
926 return valid_ = false; // Malformed, no equals sign
928 // Verify that the equals sign we found wasn't inside of quote marks.
929 for (std::string::const_iterator it = value_begin_; it != equals; ++it) {
930 if (HttpUtil::IsQuote(*it))
931 return valid_ = false; // Malformed, quote appears before equals sign
934 name_begin_ = value_begin_;
935 name_end_ = equals;
936 value_begin_ = equals + 1;
938 TrimLWS(&name_begin_, &name_end_);
939 TrimLWS(&value_begin_, &value_end_);
940 value_is_quoted_ = false;
941 unquoted_value_.clear();
943 if (value_begin_ == value_end_)
944 return valid_ = false; // Malformed, value is empty
946 if (HttpUtil::IsQuote(*value_begin_)) {
947 // Trim surrounding quotemarks off the value
948 if (*value_begin_ != *(value_end_ - 1) || value_begin_ + 1 == value_end_) {
949 // NOTE: This is not as graceful as it sounds:
950 // * quoted-pairs will no longer be unquoted
951 // (["\"hello] should give ["hello]).
952 // * Does not detect when the final quote is escaped
953 // (["value\"] should give [value"])
954 ++value_begin_; // Gracefully recover from mismatching quotes.
955 } else {
956 value_is_quoted_ = true;
957 // Do not store iterators into this. See declaration of unquoted_value_.
958 unquoted_value_ = HttpUtil::Unquote(value_begin_, value_end_);
962 return true;
965 } // namespace net