1 // Copyright (c) 2011 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.
4
5 // The rules for parsing content-types were borrowed from Firefox:
6 // http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834
7
8 #include "net/http/http_util.h"
9
10 #include <algorithm>
11
12 #include "base/basictypes.h"
13 #include "base/logging.h"
14 #include "base/string_number_conversions.h"
15 #include "base/string_piece.h"
16 #include "base/string_util.h"
17
18 using std::string;
19
20 namespace net {
21
22 //-----------------------------------------------------------------------------
23
24 // Return the index of the closing quote of the string, if any.
FindStringEnd(const string & line,size_t start,char delim)25 static size_t FindStringEnd(const string& line, size_t start, char delim) {
26 DCHECK(start < line.length() && line[start] == delim &&
27 (delim == '"' || delim == '\''));
28
29 const char set[] = { delim, '\\', '\0' };
30 for (;;) {
31 // start points to either the start quote or the last
32 // escaped char (the char following a '\\')
33
34 size_t end = line.find_first_of(set, start + 1);
35 if (end == string::npos)
36 return line.length();
37
38 if (line[end] == '\\') {
39 // Hit a backslash-escaped char. Need to skip over it.
40 start = end + 1;
41 if (start == line.length())
42 return start;
43
44 // Go back to looking for the next escape or the string end
45 continue;
46 }
47
48 return end;
49 }
50
51 NOTREACHED();
52 return line.length();
53 }
54
55 //-----------------------------------------------------------------------------
56
57 // static
FindDelimiter(const string & line,size_t search_start,char delimiter)58 size_t HttpUtil::FindDelimiter(const string& line, size_t search_start,
59 char delimiter) {
60 do {
61 // search_start points to the spot from which we should start looking
62 // for the delimiter.
63 const char delim_str[] = { delimiter, '"', '\'', '\0' };
64 size_t cur_delim_pos = line.find_first_of(delim_str, search_start);
65 if (cur_delim_pos == string::npos)
66 return line.length();
67
68 char ch = line[cur_delim_pos];
69 if (ch == delimiter) {
70 // Found delimiter
71 return cur_delim_pos;
72 }
73
74 // We hit the start of a quoted string. Look for its end.
75 search_start = FindStringEnd(line, cur_delim_pos, ch);
76 if (search_start == line.length())
77 return search_start;
78
79 ++search_start;
80
81 // search_start now points to the first char after the end of the
82 // string, so just go back to the top of the loop and look for
83 // |delimiter| again.
84 } while (true);
85
86 NOTREACHED();
87 return line.length();
88 }
89
90 // static
ParseContentType(const string & content_type_str,string * mime_type,string * charset,bool * had_charset)91 void HttpUtil::ParseContentType(const string& content_type_str,
92 string* mime_type, string* charset,
93 bool *had_charset) {
94 // Trim leading and trailing whitespace from type. We include '(' in
95 // the trailing trim set to catch media-type comments, which are not at all
96 // standard, but may occur in rare cases.
97 size_t type_val = content_type_str.find_first_not_of(HTTP_LWS);
98 type_val = std::min(type_val, content_type_str.length());
99 size_t type_end = content_type_str.find_first_of(HTTP_LWS ";(", type_val);
100 if (string::npos == type_end)
101 type_end = content_type_str.length();
102
103 size_t charset_val = 0;
104 size_t charset_end = 0;
105
106 // Iterate over parameters
107 bool type_has_charset = false;
108 size_t param_start = content_type_str.find_first_of(';', type_end);
109 if (param_start != string::npos) {
110 // We have parameters. Iterate over them.
111 size_t cur_param_start = param_start + 1;
112 do {
113 size_t cur_param_end =
114 FindDelimiter(content_type_str, cur_param_start, ';');
115
116 size_t param_name_start = content_type_str.find_first_not_of(
117 HTTP_LWS, cur_param_start);
118 param_name_start = std::min(param_name_start, cur_param_end);
119
120 static const char charset_str[] = "charset=";
121 size_t charset_end_offset = std::min(
122 param_name_start + sizeof(charset_str) - 1, cur_param_end);
123 if (LowerCaseEqualsASCII(
124 content_type_str.begin() + param_name_start,
125 content_type_str.begin() + charset_end_offset, charset_str)) {
126 charset_val = param_name_start + sizeof(charset_str) - 1;
127 charset_end = cur_param_end;
128 type_has_charset = true;
129 }
130
131 cur_param_start = cur_param_end + 1;
132 } while (cur_param_start < content_type_str.length());
133 }
134
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);
150 }
151 }
152
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('/') != string::npos) {
163 // Common case here is that mime_type is empty
164 bool eq = !mime_type->empty() &&
165 LowerCaseEqualsASCII(content_type_str.begin() + type_val,
166 content_type_str.begin() + type_end,
167 mime_type->data());
168 if (!eq) {
169 mime_type->assign(content_type_str.begin() + type_val,
170 content_type_str.begin() + type_end);
171 StringToLowerASCII(mime_type);
172 }
173 if ((!eq && *had_charset) || type_has_charset) {
174 *had_charset = true;
175 charset->assign(content_type_str.begin() + charset_val,
176 content_type_str.begin() + charset_end);
177 StringToLowerASCII(charset);
178 }
179 }
180 }
181
182 // static
183 // Parse the Range header according to RFC 2616 14.35.1
184 // ranges-specifier = byte-ranges-specifier
185 // byte-ranges-specifier = bytes-unit "=" byte-range-set
186 // byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
187 // byte-range-spec = first-byte-pos "-" [last-byte-pos]
188 // first-byte-pos = 1*DIGIT
189 // last-byte-pos = 1*DIGIT
ParseRanges(const std::string & headers,std::vector<HttpByteRange> * ranges)190 bool HttpUtil::ParseRanges(const std::string& headers,
191 std::vector<HttpByteRange>* ranges) {
192 std::string ranges_specifier;
193 HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
194
195 while (it.GetNext()) {
196 // Look for "Range" header.
197 if (!LowerCaseEqualsASCII(it.name(), "range"))
198 continue;
199 ranges_specifier = it.values();
200 // We just care about the first "Range" header, so break here.
201 break;
202 }
203
204 if (ranges_specifier.empty())
205 return false;
206
207 return ParseRangeHeader(ranges_specifier, ranges);
208 }
209
210 // static
ParseRangeHeader(const std::string & ranges_specifier,std::vector<HttpByteRange> * ranges)211 bool HttpUtil::ParseRangeHeader(const std::string& ranges_specifier,
212 std::vector<HttpByteRange>* ranges) {
213 size_t equal_char_offset = ranges_specifier.find('=');
214 if (equal_char_offset == std::string::npos)
215 return false;
216
217 // Try to extract bytes-unit part.
218 std::string::const_iterator bytes_unit_begin = ranges_specifier.begin();
219 std::string::const_iterator bytes_unit_end = bytes_unit_begin +
220 equal_char_offset;
221 std::string::const_iterator byte_range_set_begin = bytes_unit_end + 1;
222 std::string::const_iterator byte_range_set_end = ranges_specifier.end();
223
224 TrimLWS(&bytes_unit_begin, &bytes_unit_end);
225 // "bytes" unit identifier is not found.
226 if (!LowerCaseEqualsASCII(bytes_unit_begin, bytes_unit_end, "bytes"))
227 return false;
228
229 ValuesIterator byte_range_set_iterator(byte_range_set_begin,
230 byte_range_set_end, ',');
231 while (byte_range_set_iterator.GetNext()) {
232 size_t minus_char_offset = byte_range_set_iterator.value().find('-');
233 // If '-' character is not found, reports failure.
234 if (minus_char_offset == std::string::npos)
235 return false;
236
237 std::string::const_iterator first_byte_pos_begin =
238 byte_range_set_iterator.value_begin();
239 std::string::const_iterator first_byte_pos_end =
240 first_byte_pos_begin + minus_char_offset;
241 TrimLWS(&first_byte_pos_begin, &first_byte_pos_end);
242 std::string first_byte_pos(first_byte_pos_begin, first_byte_pos_end);
243
244 HttpByteRange range;
245 // Try to obtain first-byte-pos.
246 if (!first_byte_pos.empty()) {
247 int64 first_byte_position = -1;
248 if (!base::StringToInt64(first_byte_pos, &first_byte_position))
249 return false;
250 range.set_first_byte_position(first_byte_position);
251 }
252
253 std::string::const_iterator last_byte_pos_begin =
254 byte_range_set_iterator.value_begin() + minus_char_offset + 1;
255 std::string::const_iterator last_byte_pos_end =
256 byte_range_set_iterator.value_end();
257 TrimLWS(&last_byte_pos_begin, &last_byte_pos_end);
258 std::string last_byte_pos(last_byte_pos_begin, last_byte_pos_end);
259
260 // We have last-byte-pos or suffix-byte-range-spec in this case.
261 if (!last_byte_pos.empty()) {
262 int64 last_byte_position;
263 if (!base::StringToInt64(last_byte_pos, &last_byte_position))
264 return false;
265 if (range.HasFirstBytePosition())
266 range.set_last_byte_position(last_byte_position);
267 else
268 range.set_suffix_length(last_byte_position);
269 } else if (!range.HasFirstBytePosition()) {
270 return false;
271 }
272
273 // Do a final check on the HttpByteRange object.
274 if (!range.IsValid())
275 return false;
276 ranges->push_back(range);
277 }
278 return !ranges->empty();
279 }
280
281 // static
HasHeader(const std::string & headers,const char * name)282 bool HttpUtil::HasHeader(const std::string& headers, const char* name) {
283 size_t name_len = strlen(name);
284 string::const_iterator it =
285 std::search(headers.begin(),
286 headers.end(),
287 name,
288 name + name_len,
289 base::CaseInsensitiveCompareASCII<char>());
290 if (it == headers.end())
291 return false;
292
293 // ensure match is prefixed by newline
294 if (it != headers.begin() && it[-1] != '\n')
295 return false;
296
297 // ensure match is suffixed by colon
298 if (it + name_len >= headers.end() || it[name_len] != ':')
299 return false;
300
301 return true;
302 }
303
304 // static
StripHeaders(const std::string & headers,const char * const headers_to_remove[],size_t headers_to_remove_len)305 std::string HttpUtil::StripHeaders(const std::string& headers,
306 const char* const headers_to_remove[],
307 size_t headers_to_remove_len) {
308 std::string stripped_headers;
309 net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
310
311 while (it.GetNext()) {
312 bool should_remove = false;
313 for (size_t i = 0; i < headers_to_remove_len; ++i) {
314 if (LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
315 headers_to_remove[i])) {
316 should_remove = true;
317 break;
318 }
319 }
320 if (!should_remove) {
321 // Assume that name and values are on the same line.
322 stripped_headers.append(it.name_begin(), it.values_end());
323 stripped_headers.append("\r\n");
324 }
325 }
326 return stripped_headers;
327 }
328
329 // static
IsNonCoalescingHeader(string::const_iterator name_begin,string::const_iterator name_end)330 bool HttpUtil::IsNonCoalescingHeader(string::const_iterator name_begin,
331 string::const_iterator name_end) {
332 // NOTE: "set-cookie2" headers do not support expires attributes, so we don't
333 // have to list them here.
334 const char* kNonCoalescingHeaders[] = {
335 "date",
336 "expires",
337 "last-modified",
338 "location", // See bug 1050541 for details
339 "retry-after",
340 "set-cookie",
341 // The format of auth-challenges mixes both space separated tokens and
342 // comma separated properties, so coalescing on comma won't work.
343 "www-authenticate",
344 "proxy-authenticate"
345 };
346 for (size_t i = 0; i < arraysize(kNonCoalescingHeaders); ++i) {
347 if (LowerCaseEqualsASCII(name_begin, name_end, kNonCoalescingHeaders[i]))
348 return true;
349 }
350 return false;
351 }
352
IsLWS(char c)353 bool HttpUtil::IsLWS(char c) {
354 return strchr(HTTP_LWS, c) != NULL;
355 }
356
TrimLWS(string::const_iterator * begin,string::const_iterator * end)357 void HttpUtil::TrimLWS(string::const_iterator* begin,
358 string::const_iterator* end) {
359 // leading whitespace
360 while (*begin < *end && IsLWS((*begin)[0]))
361 ++(*begin);
362
363 // trailing whitespace
364 while (*begin < *end && IsLWS((*end)[-1]))
365 --(*end);
366 }
367
368 // static
IsQuote(char c)369 bool HttpUtil::IsQuote(char c) {
370 // Single quote mark isn't actually part of quoted-text production,
371 // but apparently some servers rely on this.
372 return c == '"' || c == '\'';
373 }
374
375 // static
Unquote(std::string::const_iterator begin,std::string::const_iterator end)376 std::string HttpUtil::Unquote(std::string::const_iterator begin,
377 std::string::const_iterator end) {
378 // Empty string
379 if (begin == end)
380 return std::string();
381
382 // Nothing to unquote.
383 if (!IsQuote(*begin))
384 return std::string(begin, end);
385
386 // No terminal quote mark.
387 if (end - begin < 2 || *begin != *(end - 1))
388 return std::string(begin, end);
389
390 // Strip quotemarks
391 ++begin;
392 --end;
393
394 // Unescape quoted-pair (defined in RFC 2616 section 2.2)
395 std::string unescaped;
396 bool prev_escape = false;
397 for (; begin != end; ++begin) {
398 char c = *begin;
399 if (c == '\\' && !prev_escape) {
400 prev_escape = true;
401 continue;
402 }
403 prev_escape = false;
404 unescaped.push_back(c);
405 }
406 return unescaped;
407 }
408
409 // static
Unquote(const std::string & str)410 std::string HttpUtil::Unquote(const std::string& str) {
411 return Unquote(str.begin(), str.end());
412 }
413
414 // static
Quote(const std::string & str)415 std::string HttpUtil::Quote(const std::string& str) {
416 std::string escaped;
417 escaped.reserve(2 + str.size());
418
419 std::string::const_iterator begin = str.begin();
420 std::string::const_iterator end = str.end();
421
422 // Esape any backslashes or quotemarks within the string, and
423 // then surround with quotes.
424 escaped.push_back('"');
425 for (; begin != end; ++begin) {
426 char c = *begin;
427 if (c == '"' || c == '\\')
428 escaped.push_back('\\');
429 escaped.push_back(c);
430 }
431 escaped.push_back('"');
432 return escaped;
433 }
434
435 // Find the "http" substring in a status line. This allows for
436 // some slop at the start. If the "http" string could not be found
437 // then returns -1.
438 // static
LocateStartOfStatusLine(const char * buf,int buf_len)439 int HttpUtil::LocateStartOfStatusLine(const char* buf, int buf_len) {
440 const int slop = 4;
441 const int http_len = 4;
442 #ifdef ANDROID
443 const int icy_len = 3;
444 #endif
445
446 if (buf_len >= http_len) {
447 int i_max = std::min(buf_len - http_len, slop);
448 for (int i = 0; i <= i_max; ++i) {
449 if (LowerCaseEqualsASCII(buf + i, buf + i + http_len, "http"))
450 return i;
451 #ifdef ANDROID
452 if (LowerCaseEqualsASCII(buf + i, buf + i + icy_len, "icy"))
453 return i;
454 #endif
455 }
456 }
457 return -1; // Not found
458 }
459
LocateEndOfHeaders(const char * buf,int buf_len,int i)460 int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len, int i) {
461 bool was_lf = false;
462 char last_c = '\0';
463 for (; i < buf_len; ++i) {
464 char c = buf[i];
465 if (c == '\n') {
466 if (was_lf)
467 return i + 1;
468 was_lf = true;
469 } else if (c != '\r' || last_c != '\n') {
470 was_lf = false;
471 }
472 last_c = c;
473 }
474 return -1;
475 }
476
477 // In order for a line to be continuable, it must specify a
478 // non-blank header-name. Line continuations are specifically for
479 // header values -- do not allow headers names to span lines.
IsLineSegmentContinuable(const char * begin,const char * end)480 static bool IsLineSegmentContinuable(const char* begin, const char* end) {
481 if (begin == end)
482 return false;
483
484 const char* colon = std::find(begin, end, ':');
485 if (colon == end)
486 return false;
487
488 const char* name_begin = begin;
489 const char* name_end = colon;
490
491 // Name can't be empty.
492 if (name_begin == name_end)
493 return false;
494
495 // Can't start with LWS (this would imply the segment is a continuation)
496 if (HttpUtil::IsLWS(*name_begin))
497 return false;
498
499 return true;
500 }
501
502 // Helper used by AssembleRawHeaders, to find the end of the status line.
FindStatusLineEnd(const char * begin,const char * end)503 static const char* FindStatusLineEnd(const char* begin, const char* end) {
504 size_t i = base::StringPiece(begin, end - begin).find_first_of("\r\n");
505 if (i == base::StringPiece::npos)
506 return end;
507 return begin + i;
508 }
509
510 // Helper used by AssembleRawHeaders, to skip past leading LWS.
FindFirstNonLWS(const char * begin,const char * end)511 static const char* FindFirstNonLWS(const char* begin, const char* end) {
512 for (const char* cur = begin; cur != end; ++cur) {
513 if (!HttpUtil::IsLWS(*cur))
514 return cur;
515 }
516 return end; // Not found.
517 }
518
AssembleRawHeaders(const char * input_begin,int input_len)519 std::string HttpUtil::AssembleRawHeaders(const char* input_begin,
520 int input_len) {
521 std::string raw_headers;
522 raw_headers.reserve(input_len);
523
524 const char* input_end = input_begin + input_len;
525
526 // Skip any leading slop, since the consumers of this output
527 // (HttpResponseHeaders) don't deal with it.
528 int status_begin_offset = LocateStartOfStatusLine(input_begin, input_len);
529 if (status_begin_offset != -1)
530 input_begin += status_begin_offset;
531
532 // Copy the status line.
533 const char* status_line_end = FindStatusLineEnd(input_begin, input_end);
534 raw_headers.append(input_begin, status_line_end);
535
536 // After the status line, every subsequent line is a header line segment.
537 // Should a segment start with LWS, it is a continuation of the previous
538 // line's field-value.
539
540 // TODO(ericroman): is this too permissive? (delimits on [\r\n]+)
541 CStringTokenizer lines(status_line_end, input_end, "\r\n");
542
543 // This variable is true when the previous line was continuable.
544 bool prev_line_continuable = false;
545
546 while (lines.GetNext()) {
547 const char* line_begin = lines.token_begin();
548 const char* line_end = lines.token_end();
549
550 if (prev_line_continuable && IsLWS(*line_begin)) {
551 // Join continuation; reduce the leading LWS to a single SP.
552 raw_headers.push_back(' ');
553 raw_headers.append(FindFirstNonLWS(line_begin, line_end), line_end);
554 } else {
555 // Terminate the previous line.
556 raw_headers.push_back('\0');
557
558 // Copy the raw data to output.
559 raw_headers.append(line_begin, line_end);
560
561 // Check if the current line can be continued.
562 prev_line_continuable = IsLineSegmentContinuable(line_begin, line_end);
563 }
564 }
565
566 raw_headers.append("\0\0", 2);
567 return raw_headers;
568 }
569
570 // TODO(jungshik): 1. If the list is 'fr-CA,fr-FR,en,de', we have to add
571 // 'fr' after 'fr-CA' with the same q-value as 'fr-CA' because
572 // web servers, in general, do not fall back to 'fr' and may end up picking
573 // 'en' which has a lower preference than 'fr-CA' and 'fr-FR'.
574 // 2. This function assumes that the input is a comma separated list
575 // without any whitespace. As long as it comes from the preference and
576 // a user does not manually edit the preference file, it's the case. Still,
577 // we may have to make it more robust.
GenerateAcceptLanguageHeader(const std::string & raw_language_list)578 std::string HttpUtil::GenerateAcceptLanguageHeader(
579 const std::string& raw_language_list) {
580 // We use integers for qvalue and qvalue decrement that are 10 times
581 // larger than actual values to avoid a problem with comparing
582 // two floating point numbers.
583 const unsigned int kQvalueDecrement10 = 2;
584 unsigned int qvalue10 = 10;
585 StringTokenizer t(raw_language_list, ",");
586 std::string lang_list_with_q;
587 while (t.GetNext()) {
588 std::string language = t.token();
589 if (qvalue10 == 10) {
590 // q=1.0 is implicit.
591 lang_list_with_q = language;
592 } else {
593 DCHECK_LT(qvalue10, 10U);
594 base::StringAppendF(&lang_list_with_q, ",%s;q=0.%d", language.c_str(),
595 qvalue10);
596 }
597 // It does not make sense to have 'q=0'.
598 if (qvalue10 > kQvalueDecrement10)
599 qvalue10 -= kQvalueDecrement10;
600 }
601 return lang_list_with_q;
602 }
603
GenerateAcceptCharsetHeader(const std::string & charset)604 std::string HttpUtil::GenerateAcceptCharsetHeader(const std::string& charset) {
605 std::string charset_with_q = charset;
606 if (LowerCaseEqualsASCII(charset, "utf-8")) {
607 charset_with_q += ",*;q=0.5";
608 } else {
609 charset_with_q += ",utf-8;q=0.7,*;q=0.3";
610 }
611 return charset_with_q;
612 }
613
AppendHeaderIfMissing(const char * header_name,const std::string & header_value,std::string * headers)614 void HttpUtil::AppendHeaderIfMissing(const char* header_name,
615 const std::string& header_value,
616 std::string* headers) {
617 if (header_value.empty())
618 return;
619 if (net::HttpUtil::HasHeader(*headers, header_name))
620 return;
621 *headers += std::string(header_name) + ": " + header_value + "\r\n";
622 }
623
624 // BNF from section 4.2 of RFC 2616:
625 //
626 // message-header = field-name ":" [ field-value ]
627 // field-name = token
628 // field-value = *( field-content | LWS )
629 // field-content = <the OCTETs making up the field-value
630 // and consisting of either *TEXT or combinations
631 // of token, separators, and quoted-string>
632 //
633
HeadersIterator(string::const_iterator headers_begin,string::const_iterator headers_end,const std::string & line_delimiter)634 HttpUtil::HeadersIterator::HeadersIterator(string::const_iterator headers_begin,
635 string::const_iterator headers_end,
636 const std::string& line_delimiter)
637 : lines_(headers_begin, headers_end, line_delimiter) {
638 }
639
~HeadersIterator()640 HttpUtil::HeadersIterator::~HeadersIterator() {
641 }
642
GetNext()643 bool HttpUtil::HeadersIterator::GetNext() {
644 while (lines_.GetNext()) {
645 name_begin_ = lines_.token_begin();
646 values_end_ = lines_.token_end();
647
648 string::const_iterator colon = find(name_begin_, values_end_, ':');
649 if (colon == values_end_)
650 continue; // skip malformed header
651
652 name_end_ = colon;
653
654 // If the name starts with LWS, it is an invalid line.
655 // Leading LWS implies a line continuation, and these should have
656 // already been joined by AssembleRawHeaders().
657 if (name_begin_ == name_end_ || IsLWS(*name_begin_))
658 continue;
659
660 TrimLWS(&name_begin_, &name_end_);
661 if (name_begin_ == name_end_)
662 continue; // skip malformed header
663
664 values_begin_ = colon + 1;
665 TrimLWS(&values_begin_, &values_end_);
666
667 // if we got a header name, then we are done.
668 return true;
669 }
670 return false;
671 }
672
AdvanceTo(const char * name)673 bool HttpUtil::HeadersIterator::AdvanceTo(const char* name) {
674 DCHECK(name != NULL);
675 DCHECK_EQ(0, StringToLowerASCII<std::string>(name).compare(name))
676 << "the header name must be in all lower case";
677
678 while (GetNext()) {
679 if (LowerCaseEqualsASCII(name_begin_, name_end_, name)) {
680 return true;
681 }
682 }
683
684 return false;
685 }
686
ValuesIterator(string::const_iterator values_begin,string::const_iterator values_end,char delimiter)687 HttpUtil::ValuesIterator::ValuesIterator(
688 string::const_iterator values_begin,
689 string::const_iterator values_end,
690 char delimiter)
691 : values_(values_begin, values_end, string(1, delimiter)) {
692 values_.set_quote_chars("\'\"");
693 }
694
~ValuesIterator()695 HttpUtil::ValuesIterator::~ValuesIterator() {
696 }
697
GetNext()698 bool HttpUtil::ValuesIterator::GetNext() {
699 while (values_.GetNext()) {
700 value_begin_ = values_.token_begin();
701 value_end_ = values_.token_end();
702 TrimLWS(&value_begin_, &value_end_);
703
704 // bypass empty values.
705 if (value_begin_ != value_end_)
706 return true;
707 }
708 return false;
709 }
710
NameValuePairsIterator(string::const_iterator begin,string::const_iterator end,char delimiter)711 HttpUtil::NameValuePairsIterator::NameValuePairsIterator(
712 string::const_iterator begin,
713 string::const_iterator end,
714 char delimiter)
715 : props_(begin, end, delimiter),
716 valid_(true),
717 begin_(begin),
718 end_(end),
719 name_begin_(end),
720 name_end_(end),
721 value_begin_(end),
722 value_end_(end),
723 value_is_quoted_(false) {
724 }
725
~NameValuePairsIterator()726 HttpUtil::NameValuePairsIterator::~NameValuePairsIterator() {}
727
728 // We expect properties to be formatted as one of:
729 // name="value"
730 // name='value'
731 // name='\'value\''
732 // name=value
733 // name = value
734 // name=
735 // Due to buggy implementations found in some embedded devices, we also
736 // accept values with missing close quotemark (http://crbug.com/39836):
737 // name="value
GetNext()738 bool HttpUtil::NameValuePairsIterator::GetNext() {
739 if (!props_.GetNext())
740 return false;
741
742 // Set the value as everything. Next we will split out the name.
743 value_begin_ = props_.value_begin();
744 value_end_ = props_.value_end();
745 name_begin_ = name_end_ = value_end_;
746
747 // Scan for the equals sign.
748 std::string::const_iterator equals = std::find(value_begin_, value_end_, '=');
749 if (equals == value_end_ || equals == value_begin_)
750 return valid_ = false; // Malformed, no equals sign
751
752 // Verify that the equals sign we found wasn't inside of quote marks.
753 for (std::string::const_iterator it = value_begin_; it != equals; ++it) {
754 if (HttpUtil::IsQuote(*it))
755 return valid_ = false; // Malformed, quote appears before equals sign
756 }
757
758 name_begin_ = value_begin_;
759 name_end_ = equals;
760 value_begin_ = equals + 1;
761
762 TrimLWS(&name_begin_, &name_end_);
763 TrimLWS(&value_begin_, &value_end_);
764 value_is_quoted_ = false;
765 unquoted_value_.clear();
766
767 if (value_begin_ == value_end_)
768 return valid_ = false; // Malformed, value is empty
769
770 if (HttpUtil::IsQuote(*value_begin_)) {
771 // Trim surrounding quotemarks off the value
772 if (*value_begin_ != *(value_end_ - 1) || value_begin_ + 1 == value_end_) {
773 // NOTE: This is not as graceful as it sounds:
774 // * quoted-pairs will no longer be unquoted
775 // (["\"hello] should give ["hello]).
776 // * Does not detect when the final quote is escaped
777 // (["value\"] should give [value"])
778 ++value_begin_; // Gracefully recover from mismatching quotes.
779 } else {
780 value_is_quoted_ = true;
781 // Do not store iterators into this. See declaration of unquoted_value_.
782 unquoted_value_ = HttpUtil::Unquote(value_begin_, value_end_);
783 }
784 }
785
786 return true;
787 }
788
789 } // namespace net
790