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