• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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