• 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 
443   if (buf_len >= http_len) {
444     int i_max = std::min(buf_len - http_len, slop);
445     for (int i = 0; i <= i_max; ++i) {
446       if (LowerCaseEqualsASCII(buf + i, buf + i + http_len, "http"))
447         return i;
448     }
449   }
450   return -1;  // Not found
451 }
452 
LocateEndOfHeaders(const char * buf,int buf_len,int i)453 int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len, int i) {
454   bool was_lf = false;
455   char last_c = '\0';
456   for (; i < buf_len; ++i) {
457     char c = buf[i];
458     if (c == '\n') {
459       if (was_lf)
460         return i + 1;
461       was_lf = true;
462     } else if (c != '\r' || last_c != '\n') {
463       was_lf = false;
464     }
465     last_c = c;
466   }
467   return -1;
468 }
469 
470 // In order for a line to be continuable, it must specify a
471 // non-blank header-name. Line continuations are specifically for
472 // header values -- do not allow headers names to span lines.
IsLineSegmentContinuable(const char * begin,const char * end)473 static bool IsLineSegmentContinuable(const char* begin, const char* end) {
474   if (begin == end)
475     return false;
476 
477   const char* colon = std::find(begin, end, ':');
478   if (colon == end)
479     return false;
480 
481   const char* name_begin = begin;
482   const char* name_end = colon;
483 
484   // Name can't be empty.
485   if (name_begin == name_end)
486     return false;
487 
488   // Can't start with LWS (this would imply the segment is a continuation)
489   if (HttpUtil::IsLWS(*name_begin))
490     return false;
491 
492   return true;
493 }
494 
495 // Helper used by AssembleRawHeaders, to find the end of the status line.
FindStatusLineEnd(const char * begin,const char * end)496 static const char* FindStatusLineEnd(const char* begin, const char* end) {
497   size_t i = base::StringPiece(begin, end - begin).find_first_of("\r\n");
498   if (i == base::StringPiece::npos)
499     return end;
500   return begin + i;
501 }
502 
503 // Helper used by AssembleRawHeaders, to skip past leading LWS.
FindFirstNonLWS(const char * begin,const char * end)504 static const char* FindFirstNonLWS(const char* begin, const char* end) {
505   for (const char* cur = begin; cur != end; ++cur) {
506     if (!HttpUtil::IsLWS(*cur))
507       return cur;
508   }
509   return end;  // Not found.
510 }
511 
AssembleRawHeaders(const char * input_begin,int input_len)512 std::string HttpUtil::AssembleRawHeaders(const char* input_begin,
513                                          int input_len) {
514   std::string raw_headers;
515   raw_headers.reserve(input_len);
516 
517   const char* input_end = input_begin + input_len;
518 
519   // Skip any leading slop, since the consumers of this output
520   // (HttpResponseHeaders) don't deal with it.
521   int status_begin_offset = LocateStartOfStatusLine(input_begin, input_len);
522   if (status_begin_offset != -1)
523     input_begin += status_begin_offset;
524 
525   // Copy the status line.
526   const char* status_line_end = FindStatusLineEnd(input_begin, input_end);
527   raw_headers.append(input_begin, status_line_end);
528 
529   // After the status line, every subsequent line is a header line segment.
530   // Should a segment start with LWS, it is a continuation of the previous
531   // line's field-value.
532 
533   // TODO(ericroman): is this too permissive? (delimits on [\r\n]+)
534   CStringTokenizer lines(status_line_end, input_end, "\r\n");
535 
536   // This variable is true when the previous line was continuable.
537   bool prev_line_continuable = false;
538 
539   while (lines.GetNext()) {
540     const char* line_begin = lines.token_begin();
541     const char* line_end = lines.token_end();
542 
543     if (prev_line_continuable && IsLWS(*line_begin)) {
544       // Join continuation; reduce the leading LWS to a single SP.
545       raw_headers.push_back(' ');
546       raw_headers.append(FindFirstNonLWS(line_begin, line_end), line_end);
547     } else {
548       // Terminate the previous line.
549       raw_headers.push_back('\0');
550 
551       // Copy the raw data to output.
552       raw_headers.append(line_begin, line_end);
553 
554       // Check if the current line can be continued.
555       prev_line_continuable = IsLineSegmentContinuable(line_begin, line_end);
556     }
557   }
558 
559   raw_headers.append("\0\0", 2);
560   return raw_headers;
561 }
562 
563 // TODO(jungshik): 1. If the list is 'fr-CA,fr-FR,en,de', we have to add
564 // 'fr' after 'fr-CA' with the same q-value as 'fr-CA' because
565 // web servers, in general, do not fall back to 'fr' and may end up picking
566 // 'en' which has a lower preference than 'fr-CA' and 'fr-FR'.
567 // 2. This function assumes that the input is a comma separated list
568 // without any whitespace. As long as it comes from the preference and
569 // a user does not manually edit the preference file, it's the case. Still,
570 // we may have to make it more robust.
GenerateAcceptLanguageHeader(const std::string & raw_language_list)571 std::string HttpUtil::GenerateAcceptLanguageHeader(
572     const std::string& raw_language_list) {
573   // We use integers for qvalue and qvalue decrement that are 10 times
574   // larger than actual values to avoid a problem with comparing
575   // two floating point numbers.
576   const unsigned int kQvalueDecrement10 = 2;
577   unsigned int qvalue10 = 10;
578   StringTokenizer t(raw_language_list, ",");
579   std::string lang_list_with_q;
580   while (t.GetNext()) {
581     std::string language = t.token();
582     if (qvalue10 == 10) {
583       // q=1.0 is implicit.
584       lang_list_with_q = language;
585     } else {
586       DCHECK_LT(qvalue10, 10U);
587       base::StringAppendF(&lang_list_with_q, ",%s;q=0.%d", language.c_str(),
588                           qvalue10);
589     }
590     // It does not make sense to have 'q=0'.
591     if (qvalue10 > kQvalueDecrement10)
592       qvalue10 -= kQvalueDecrement10;
593   }
594   return lang_list_with_q;
595 }
596 
GenerateAcceptCharsetHeader(const std::string & charset)597 std::string HttpUtil::GenerateAcceptCharsetHeader(const std::string& charset) {
598   std::string charset_with_q = charset;
599   if (LowerCaseEqualsASCII(charset, "utf-8")) {
600     charset_with_q += ",*;q=0.5";
601   } else {
602     charset_with_q += ",utf-8;q=0.7,*;q=0.3";
603   }
604   return charset_with_q;
605 }
606 
AppendHeaderIfMissing(const char * header_name,const std::string & header_value,std::string * headers)607 void HttpUtil::AppendHeaderIfMissing(const char* header_name,
608                                      const std::string& header_value,
609                                      std::string* headers) {
610   if (header_value.empty())
611     return;
612   if (net::HttpUtil::HasHeader(*headers, header_name))
613     return;
614   *headers += std::string(header_name) + ": " + header_value + "\r\n";
615 }
616 
617 // BNF from section 4.2 of RFC 2616:
618 //
619 //   message-header = field-name ":" [ field-value ]
620 //   field-name     = token
621 //   field-value    = *( field-content | LWS )
622 //   field-content  = <the OCTETs making up the field-value
623 //                     and consisting of either *TEXT or combinations
624 //                     of token, separators, and quoted-string>
625 //
626 
HeadersIterator(string::const_iterator headers_begin,string::const_iterator headers_end,const std::string & line_delimiter)627 HttpUtil::HeadersIterator::HeadersIterator(string::const_iterator headers_begin,
628                                            string::const_iterator headers_end,
629                                            const std::string& line_delimiter)
630     : lines_(headers_begin, headers_end, line_delimiter) {
631 }
632 
~HeadersIterator()633 HttpUtil::HeadersIterator::~HeadersIterator() {
634 }
635 
GetNext()636 bool HttpUtil::HeadersIterator::GetNext() {
637   while (lines_.GetNext()) {
638     name_begin_ = lines_.token_begin();
639     values_end_ = lines_.token_end();
640 
641     string::const_iterator colon = find(name_begin_, values_end_, ':');
642     if (colon == values_end_)
643       continue;  // skip malformed header
644 
645     name_end_ = colon;
646 
647     // If the name starts with LWS, it is an invalid line.
648     // Leading LWS implies a line continuation, and these should have
649     // already been joined by AssembleRawHeaders().
650     if (name_begin_ == name_end_ || IsLWS(*name_begin_))
651       continue;
652 
653     TrimLWS(&name_begin_, &name_end_);
654     if (name_begin_ == name_end_)
655       continue;  // skip malformed header
656 
657     values_begin_ = colon + 1;
658     TrimLWS(&values_begin_, &values_end_);
659 
660     // if we got a header name, then we are done.
661     return true;
662   }
663   return false;
664 }
665 
AdvanceTo(const char * name)666 bool HttpUtil::HeadersIterator::AdvanceTo(const char* name) {
667   DCHECK(name != NULL);
668   DCHECK_EQ(0, StringToLowerASCII<std::string>(name).compare(name))
669       << "the header name must be in all lower case";
670 
671   while (GetNext()) {
672     if (LowerCaseEqualsASCII(name_begin_, name_end_, name)) {
673       return true;
674     }
675   }
676 
677   return false;
678 }
679 
ValuesIterator(string::const_iterator values_begin,string::const_iterator values_end,char delimiter)680 HttpUtil::ValuesIterator::ValuesIterator(
681     string::const_iterator values_begin,
682     string::const_iterator values_end,
683     char delimiter)
684     : values_(values_begin, values_end, string(1, delimiter)) {
685   values_.set_quote_chars("\'\"");
686 }
687 
~ValuesIterator()688 HttpUtil::ValuesIterator::~ValuesIterator() {
689 }
690 
GetNext()691 bool HttpUtil::ValuesIterator::GetNext() {
692   while (values_.GetNext()) {
693     value_begin_ = values_.token_begin();
694     value_end_ = values_.token_end();
695     TrimLWS(&value_begin_, &value_end_);
696 
697     // bypass empty values.
698     if (value_begin_ != value_end_)
699       return true;
700   }
701   return false;
702 }
703 
NameValuePairsIterator(string::const_iterator begin,string::const_iterator end,char delimiter)704 HttpUtil::NameValuePairsIterator::NameValuePairsIterator(
705     string::const_iterator begin,
706     string::const_iterator end,
707     char delimiter)
708     : props_(begin, end, delimiter),
709       valid_(true),
710       begin_(begin),
711       end_(end),
712       name_begin_(end),
713       name_end_(end),
714       value_begin_(end),
715       value_end_(end),
716       value_is_quoted_(false) {
717 }
718 
~NameValuePairsIterator()719 HttpUtil::NameValuePairsIterator::~NameValuePairsIterator() {}
720 
721 // We expect properties to be formatted as one of:
722 //   name="value"
723 //   name='value'
724 //   name='\'value\''
725 //   name=value
726 //   name = value
727 //   name=
728 // Due to buggy implementations found in some embedded devices, we also
729 // accept values with missing close quotemark (http://crbug.com/39836):
730 //   name="value
GetNext()731 bool HttpUtil::NameValuePairsIterator::GetNext() {
732   if (!props_.GetNext())
733     return false;
734 
735   // Set the value as everything. Next we will split out the name.
736   value_begin_ = props_.value_begin();
737   value_end_ = props_.value_end();
738   name_begin_ = name_end_ = value_end_;
739 
740   // Scan for the equals sign.
741   std::string::const_iterator equals = std::find(value_begin_, value_end_, '=');
742   if (equals == value_end_ || equals == value_begin_)
743     return valid_ = false;  // Malformed, no equals sign
744 
745   // Verify that the equals sign we found wasn't inside of quote marks.
746   for (std::string::const_iterator it = value_begin_; it != equals; ++it) {
747     if (HttpUtil::IsQuote(*it))
748       return valid_ = false;  // Malformed, quote appears before equals sign
749   }
750 
751   name_begin_ = value_begin_;
752   name_end_ = equals;
753   value_begin_ = equals + 1;
754 
755   TrimLWS(&name_begin_, &name_end_);
756   TrimLWS(&value_begin_, &value_end_);
757   value_is_quoted_ = false;
758   unquoted_value_.clear();
759 
760   if (value_begin_ == value_end_)
761     return valid_ = false;  // Malformed, value is empty
762 
763   if (HttpUtil::IsQuote(*value_begin_)) {
764     // Trim surrounding quotemarks off the value
765     if (*value_begin_ != *(value_end_ - 1) || value_begin_ + 1 == value_end_) {
766       // NOTE: This is not as graceful as it sounds:
767       // * quoted-pairs will no longer be unquoted
768       //   (["\"hello] should give ["hello]).
769       // * Does not detect when the final quote is escaped
770       //   (["value\"] should give [value"])
771       ++value_begin_;  // Gracefully recover from mismatching quotes.
772     } else {
773       value_is_quoted_ = true;
774       // Do not store iterators into this. See declaration of unquoted_value_.
775       unquoted_value_ = HttpUtil::Unquote(value_begin_, value_end_);
776     }
777   }
778 
779   return true;
780 }
781 
782 }  // namespace net
783