1 // Copyright 2012 The Chromium Authors
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 header parsing were borrowed from Firefox:
6 // http://lxr.mozilla.org/seamonkey/source/netwerk/protocol/http/src/nsHttpResponseHead.cpp
7 // The rules for parsing content-types were also borrowed from Firefox:
8 // http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834
9
10 #include "net/http/http_response_headers.h"
11
12 #include <algorithm>
13 #include <limits>
14 #include <memory>
15 #include <unordered_map>
16 #include <utility>
17
18 #include "base/format_macros.h"
19 #include "base/logging.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/pickle.h"
22 #include "base/ranges/algorithm.h"
23 #include "base/strings/escape.h"
24 #include "base/strings/strcat.h"
25 #include "base/strings/string_number_conversions.h"
26 #include "base/strings/string_piece.h"
27 #include "base/strings/string_util.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/time/time.h"
30 #include "base/values.h"
31 #include "net/base/parse_number.h"
32 #include "net/base/tracing.h"
33 #include "net/http/http_byte_range.h"
34 #include "net/http/http_log_util.h"
35 #include "net/http/http_status_code.h"
36 #include "net/http/http_util.h"
37 #include "net/log/net_log_capture_mode.h"
38 #include "net/log/net_log_values.h"
39
40 using base::Time;
41
42 namespace net {
43
44 //-----------------------------------------------------------------------------
45
46 namespace {
47
48 // These headers are RFC 2616 hop-by-hop headers;
49 // not to be stored by caches.
50 const char* const kHopByHopResponseHeaders[] = {
51 "connection",
52 "proxy-connection",
53 "keep-alive",
54 "trailer",
55 "transfer-encoding",
56 "upgrade"
57 };
58
59 // These headers are challenge response headers;
60 // not to be stored by caches.
61 const char* const kChallengeResponseHeaders[] = {
62 "www-authenticate",
63 "proxy-authenticate"
64 };
65
66 // These headers are cookie setting headers;
67 // not to be stored by caches or disclosed otherwise.
68 const char* const kCookieResponseHeaders[] = {
69 "set-cookie",
70 "set-cookie2",
71 "clear-site-data",
72 };
73
74 // By default, do not cache Strict-Transport-Security.
75 // This avoids erroneously re-processing it on page loads from cache ---
76 // it is defined to be valid only on live and error-free HTTPS connections.
77 const char* const kSecurityStateHeaders[] = {
78 "strict-transport-security",
79 };
80
81 // These response headers are not copied from a 304/206 response to the cached
82 // response headers. This list is based on Mozilla's nsHttpResponseHead.cpp.
83 const char* const kNonUpdatedHeaders[] = {
84 "connection",
85 "proxy-connection",
86 "keep-alive",
87 "www-authenticate",
88 "proxy-authenticate",
89 "proxy-authorization",
90 "te",
91 "trailer",
92 "transfer-encoding",
93 "upgrade",
94 "content-location",
95 "content-md5",
96 "etag",
97 "content-encoding",
98 "content-range",
99 "content-type",
100 "content-length",
101 "x-frame-options",
102 "x-xss-protection",
103 };
104
105 // Some header prefixes mean "Don't copy this header from a 304 response.".
106 // Rather than listing all the relevant headers, we can consolidate them into
107 // this list:
108 const char* const kNonUpdatedHeaderPrefixes[] = {
109 "x-content-",
110 "x-webkit-"
111 };
112
ShouldUpdateHeader(base::StringPiece name)113 bool ShouldUpdateHeader(base::StringPiece name) {
114 for (const auto* header : kNonUpdatedHeaders) {
115 if (base::EqualsCaseInsensitiveASCII(name, header))
116 return false;
117 }
118 for (const auto* prefix : kNonUpdatedHeaderPrefixes) {
119 if (base::StartsWith(name, prefix, base::CompareCase::INSENSITIVE_ASCII))
120 return false;
121 }
122 return true;
123 }
124
HasEmbeddedNulls(base::StringPiece str)125 bool HasEmbeddedNulls(base::StringPiece str) {
126 for (char c : str) {
127 if (c == '\0')
128 return true;
129 }
130 return false;
131 }
132
CheckDoesNotHaveEmbeddedNulls(base::StringPiece str)133 void CheckDoesNotHaveEmbeddedNulls(base::StringPiece str) {
134 // Care needs to be taken when adding values to the raw headers string to
135 // make sure it does not contain embeded NULLs. Any embeded '\0' may be
136 // understood as line terminators and change how header lines get tokenized.
137 CHECK(!HasEmbeddedNulls(str));
138 }
139
140 } // namespace
141
142 const char HttpResponseHeaders::kContentRange[] = "Content-Range";
143 const char HttpResponseHeaders::kLastModified[] = "Last-Modified";
144 const char HttpResponseHeaders::kVary[] = "Vary";
145
146 struct HttpResponseHeaders::ParsedHeader {
147 // A header "continuation" contains only a subsequent value for the
148 // preceding header. (Header values are comma separated.)
is_continuationnet::HttpResponseHeaders::ParsedHeader149 bool is_continuation() const { return name_begin == name_end; }
150
151 std::string::const_iterator name_begin;
152 std::string::const_iterator name_end;
153 std::string::const_iterator value_begin;
154 std::string::const_iterator value_end;
155
156 // Write a representation of this object into a tracing proto.
WriteIntoTracenet::HttpResponseHeaders::ParsedHeader157 void WriteIntoTrace(perfetto::TracedValue context) const {
158 auto dict = std::move(context).WriteDictionary();
159 dict.Add("name", base::MakeStringPiece(name_begin, name_end));
160 dict.Add("value", base::MakeStringPiece(value_begin, value_end));
161 }
162 };
163
164 //-----------------------------------------------------------------------------
165
HttpResponseHeaders(const std::string & raw_input)166 HttpResponseHeaders::HttpResponseHeaders(const std::string& raw_input)
167 : response_code_(-1) {
168 Parse(raw_input);
169
170 // The most important thing to do with this histogram is find out
171 // the existence of unusual HTTP status codes. As it happens
172 // right now, there aren't double-constructions of response headers
173 // using this constructor, so our counts should also be accurate,
174 // without instantiating the histogram in two places. It is also
175 // important that this histogram not collect data in the other
176 // constructor, which rebuilds an histogram from a pickle, since
177 // that would actually create a double call between the original
178 // HttpResponseHeader that was serialized, and initialization of the
179 // new object from that pickle.
180 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
181 "Net.HttpResponseCode",
182 HttpUtil::MapStatusCodeForHistogram(response_code_),
183 // Note the third argument is only
184 // evaluated once, see macro
185 // definition for details.
186 HttpUtil::GetStatusCodesForHistogram());
187 }
188
HttpResponseHeaders(base::PickleIterator * iter)189 HttpResponseHeaders::HttpResponseHeaders(base::PickleIterator* iter)
190 : response_code_(-1) {
191 std::string raw_input;
192 if (iter->ReadString(&raw_input))
193 Parse(raw_input);
194 }
195
TryToCreate(base::StringPiece headers)196 scoped_refptr<HttpResponseHeaders> HttpResponseHeaders::TryToCreate(
197 base::StringPiece headers) {
198 // Reject strings with nulls.
199 if (HasEmbeddedNulls(headers) ||
200 headers.size() > std::numeric_limits<int>::max()) {
201 return nullptr;
202 }
203
204 return base::MakeRefCounted<HttpResponseHeaders>(
205 HttpUtil::AssembleRawHeaders(headers));
206 }
207
Persist(base::Pickle * pickle,PersistOptions options)208 void HttpResponseHeaders::Persist(base::Pickle* pickle,
209 PersistOptions options) {
210 if (options == PERSIST_RAW) {
211 pickle->WriteString(raw_headers_);
212 return; // Done.
213 }
214
215 HeaderSet filter_headers;
216
217 // Construct set of headers to filter out based on options.
218 if ((options & PERSIST_SANS_NON_CACHEABLE) == PERSIST_SANS_NON_CACHEABLE)
219 AddNonCacheableHeaders(&filter_headers);
220
221 if ((options & PERSIST_SANS_COOKIES) == PERSIST_SANS_COOKIES)
222 AddCookieHeaders(&filter_headers);
223
224 if ((options & PERSIST_SANS_CHALLENGES) == PERSIST_SANS_CHALLENGES)
225 AddChallengeHeaders(&filter_headers);
226
227 if ((options & PERSIST_SANS_HOP_BY_HOP) == PERSIST_SANS_HOP_BY_HOP)
228 AddHopByHopHeaders(&filter_headers);
229
230 if ((options & PERSIST_SANS_RANGES) == PERSIST_SANS_RANGES)
231 AddHopContentRangeHeaders(&filter_headers);
232
233 if ((options & PERSIST_SANS_SECURITY_STATE) == PERSIST_SANS_SECURITY_STATE)
234 AddSecurityStateHeaders(&filter_headers);
235
236 std::string blob;
237 blob.reserve(raw_headers_.size());
238
239 // This copies the status line w/ terminator null.
240 // Note raw_headers_ has embedded nulls instead of \n,
241 // so this just copies the first header line.
242 blob.assign(raw_headers_.c_str(), strlen(raw_headers_.c_str()) + 1);
243
244 for (size_t i = 0; i < parsed_.size(); ++i) {
245 DCHECK(!parsed_[i].is_continuation());
246
247 // Locate the start of the next header.
248 size_t k = i;
249 while (++k < parsed_.size() && parsed_[k].is_continuation()) {}
250 --k;
251
252 std::string header_name = base::ToLowerASCII(
253 base::MakeStringPiece(parsed_[i].name_begin, parsed_[i].name_end));
254 if (filter_headers.find(header_name) == filter_headers.end()) {
255 // Make sure there is a null after the value.
256 blob.append(parsed_[i].name_begin, parsed_[k].value_end);
257 blob.push_back('\0');
258 }
259
260 i = k;
261 }
262 blob.push_back('\0');
263
264 pickle->WriteString(blob);
265 }
266
Update(const HttpResponseHeaders & new_headers)267 void HttpResponseHeaders::Update(const HttpResponseHeaders& new_headers) {
268 DCHECK(new_headers.response_code() == net::HTTP_NOT_MODIFIED ||
269 new_headers.response_code() == net::HTTP_PARTIAL_CONTENT);
270
271 // Copy up to the null byte. This just copies the status line.
272 std::string new_raw_headers(raw_headers_.c_str());
273 new_raw_headers.push_back('\0');
274
275 HeaderSet updated_headers;
276
277 // NOTE: we write the new headers then the old headers for convenience. The
278 // order should not matter.
279
280 // Figure out which headers we want to take from new_headers:
281 for (size_t i = 0; i < new_headers.parsed_.size(); ++i) {
282 const HeaderList& new_parsed = new_headers.parsed_;
283
284 DCHECK(!new_parsed[i].is_continuation());
285
286 // Locate the start of the next header.
287 size_t k = i;
288 while (++k < new_parsed.size() && new_parsed[k].is_continuation()) {}
289 --k;
290
291 auto name =
292 base::MakeStringPiece(new_parsed[i].name_begin, new_parsed[i].name_end);
293 if (ShouldUpdateHeader(name)) {
294 std::string name_lower = base::ToLowerASCII(name);
295 updated_headers.insert(name_lower);
296
297 // Preserve this header line in the merged result, making sure there is
298 // a null after the value.
299 new_raw_headers.append(new_parsed[i].name_begin, new_parsed[k].value_end);
300 new_raw_headers.push_back('\0');
301 }
302
303 i = k;
304 }
305
306 // Now, build the new raw headers.
307 MergeWithHeaders(std::move(new_raw_headers), updated_headers);
308 }
309
MergeWithHeaders(std::string raw_headers,const HeaderSet & headers_to_remove)310 void HttpResponseHeaders::MergeWithHeaders(std::string raw_headers,
311 const HeaderSet& headers_to_remove) {
312 for (size_t i = 0; i < parsed_.size(); ++i) {
313 DCHECK(!parsed_[i].is_continuation());
314
315 // Locate the start of the next header.
316 size_t k = i;
317 while (++k < parsed_.size() && parsed_[k].is_continuation()) {}
318 --k;
319
320 std::string name = base::ToLowerASCII(
321 base::MakeStringPiece(parsed_[i].name_begin, parsed_[i].name_end));
322 if (headers_to_remove.find(name) == headers_to_remove.end()) {
323 // It's ok to preserve this header in the final result.
324 raw_headers.append(parsed_[i].name_begin, parsed_[k].value_end);
325 raw_headers.push_back('\0');
326 }
327
328 i = k;
329 }
330 raw_headers.push_back('\0');
331
332 // Make this object hold the new data.
333 raw_headers_.clear();
334 parsed_.clear();
335 Parse(raw_headers);
336 }
337
RemoveHeader(base::StringPiece name)338 void HttpResponseHeaders::RemoveHeader(base::StringPiece name) {
339 // Copy up to the null byte. This just copies the status line.
340 std::string new_raw_headers(raw_headers_.c_str());
341 new_raw_headers.push_back('\0');
342
343 HeaderSet to_remove;
344 to_remove.insert(base::ToLowerASCII(name));
345 MergeWithHeaders(std::move(new_raw_headers), to_remove);
346 }
347
RemoveHeaders(const std::unordered_set<std::string> & header_names)348 void HttpResponseHeaders::RemoveHeaders(
349 const std::unordered_set<std::string>& header_names) {
350 // Copy up to the null byte. This just copies the status line.
351 std::string new_raw_headers(raw_headers_.c_str());
352 new_raw_headers.push_back('\0');
353
354 HeaderSet to_remove;
355 for (const auto& header_name : header_names) {
356 to_remove.insert(base::ToLowerASCII(header_name));
357 }
358 MergeWithHeaders(std::move(new_raw_headers), to_remove);
359 }
360
RemoveHeaderLine(const std::string & name,const std::string & value)361 void HttpResponseHeaders::RemoveHeaderLine(const std::string& name,
362 const std::string& value) {
363 std::string name_lowercase = base::ToLowerASCII(name);
364
365 std::string new_raw_headers(GetStatusLine());
366 new_raw_headers.push_back('\0');
367
368 new_raw_headers.reserve(raw_headers_.size());
369
370 size_t iter = 0;
371 std::string old_header_name;
372 std::string old_header_value;
373 while (EnumerateHeaderLines(&iter, &old_header_name, &old_header_value)) {
374 std::string old_header_name_lowercase = base::ToLowerASCII(old_header_name);
375 if (name_lowercase == old_header_name_lowercase &&
376 value == old_header_value)
377 continue;
378
379 new_raw_headers.append(old_header_name);
380 new_raw_headers.push_back(':');
381 new_raw_headers.push_back(' ');
382 new_raw_headers.append(old_header_value);
383 new_raw_headers.push_back('\0');
384 }
385 new_raw_headers.push_back('\0');
386
387 // Make this object hold the new data.
388 raw_headers_.clear();
389 parsed_.clear();
390 Parse(new_raw_headers);
391 }
392
AddHeader(base::StringPiece name,base::StringPiece value)393 void HttpResponseHeaders::AddHeader(base::StringPiece name,
394 base::StringPiece value) {
395 DCHECK(HttpUtil::IsValidHeaderName(name));
396 DCHECK(HttpUtil::IsValidHeaderValue(value));
397
398 // Don't copy the last null.
399 std::string new_raw_headers(raw_headers_, 0, raw_headers_.size() - 1);
400 new_raw_headers.append(name.begin(), name.end());
401 new_raw_headers.append(": ");
402 new_raw_headers.append(value.begin(), value.end());
403 new_raw_headers.push_back('\0');
404 new_raw_headers.push_back('\0');
405
406 // Make this object hold the new data.
407 raw_headers_.clear();
408 parsed_.clear();
409 Parse(new_raw_headers);
410 }
411
SetHeader(base::StringPiece name,base::StringPiece value)412 void HttpResponseHeaders::SetHeader(base::StringPiece name,
413 base::StringPiece value) {
414 RemoveHeader(name);
415 AddHeader(name, value);
416 }
417
AddCookie(const std::string & cookie_string)418 void HttpResponseHeaders::AddCookie(const std::string& cookie_string) {
419 AddHeader("Set-Cookie", cookie_string);
420 }
421
ReplaceStatusLine(const std::string & new_status)422 void HttpResponseHeaders::ReplaceStatusLine(const std::string& new_status) {
423 CheckDoesNotHaveEmbeddedNulls(new_status);
424 // Copy up to the null byte. This just copies the status line.
425 std::string new_raw_headers(new_status);
426 new_raw_headers.push_back('\0');
427
428 HeaderSet empty_to_remove;
429 MergeWithHeaders(std::move(new_raw_headers), empty_to_remove);
430 }
431
UpdateWithNewRange(const HttpByteRange & byte_range,int64_t resource_size,bool replace_status_line)432 void HttpResponseHeaders::UpdateWithNewRange(const HttpByteRange& byte_range,
433 int64_t resource_size,
434 bool replace_status_line) {
435 DCHECK(byte_range.IsValid());
436 DCHECK(byte_range.HasFirstBytePosition());
437 DCHECK(byte_range.HasLastBytePosition());
438
439 const char kLengthHeader[] = "Content-Length";
440 const char kRangeHeader[] = "Content-Range";
441
442 RemoveHeader(kLengthHeader);
443 RemoveHeader(kRangeHeader);
444
445 int64_t start = byte_range.first_byte_position();
446 int64_t end = byte_range.last_byte_position();
447 int64_t range_len = end - start + 1;
448
449 if (replace_status_line)
450 ReplaceStatusLine("HTTP/1.1 206 Partial Content");
451
452 AddHeader(kRangeHeader,
453 base::StringPrintf("bytes %" PRId64 "-%" PRId64 "/%" PRId64, start,
454 end, resource_size));
455 AddHeader(kLengthHeader, base::StringPrintf("%" PRId64, range_len));
456 }
457
Parse(const std::string & raw_input)458 void HttpResponseHeaders::Parse(const std::string& raw_input) {
459 raw_headers_.reserve(raw_input.size());
460
461 // ParseStatusLine adds a normalized status line to raw_headers_
462 std::string::const_iterator line_begin = raw_input.begin();
463 std::string::const_iterator line_end = base::ranges::find(raw_input, '\0');
464 // has_headers = true, if there is any data following the status line.
465 // Used by ParseStatusLine() to decide if a HTTP/0.9 is really a HTTP/1.0.
466 bool has_headers =
467 (line_end != raw_input.end() && (line_end + 1) != raw_input.end() &&
468 *(line_end + 1) != '\0');
469 ParseStatusLine(line_begin, line_end, has_headers);
470 raw_headers_.push_back('\0'); // Terminate status line with a null.
471
472 if (line_end == raw_input.end()) {
473 raw_headers_.push_back('\0'); // Ensure the headers end with a double null.
474
475 DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]);
476 DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 1]);
477 return;
478 }
479
480 // Including a terminating null byte.
481 size_t status_line_len = raw_headers_.size();
482
483 // Now, we add the rest of the raw headers to raw_headers_, and begin parsing
484 // it (to populate our parsed_ vector).
485 raw_headers_.append(line_end + 1, raw_input.end());
486
487 // Ensure the headers end with a double null.
488 while (raw_headers_.size() < 2 ||
489 raw_headers_[raw_headers_.size() - 2] != '\0' ||
490 raw_headers_[raw_headers_.size() - 1] != '\0') {
491 raw_headers_.push_back('\0');
492 }
493
494 // Adjust to point at the null byte following the status line
495 line_end = raw_headers_.begin() + status_line_len - 1;
496
497 HttpUtil::HeadersIterator headers(line_end + 1, raw_headers_.end(),
498 std::string(1, '\0'));
499 while (headers.GetNext()) {
500 AddHeader(headers.name_begin(), headers.name_end(), headers.values_begin(),
501 headers.values_end());
502 }
503
504 DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]);
505 DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 1]);
506 }
507
GetNormalizedHeader(base::StringPiece name,std::string * value) const508 bool HttpResponseHeaders::GetNormalizedHeader(base::StringPiece name,
509 std::string* value) const {
510 // If you hit this assertion, please use EnumerateHeader instead!
511 DCHECK(!HttpUtil::IsNonCoalescingHeader(name));
512
513 value->clear();
514
515 bool found = false;
516 size_t i = 0;
517 while (i < parsed_.size()) {
518 i = FindHeader(i, name);
519 if (i == std::string::npos)
520 break;
521
522 if (found)
523 value->append(", ");
524
525 found = true;
526
527 std::string::const_iterator value_begin = parsed_[i].value_begin;
528 std::string::const_iterator value_end = parsed_[i].value_end;
529 while (++i < parsed_.size() && parsed_[i].is_continuation())
530 value_end = parsed_[i].value_end;
531 value->append(value_begin, value_end);
532 }
533
534 return found;
535 }
536
GetStatusLine() const537 std::string HttpResponseHeaders::GetStatusLine() const {
538 // copy up to the null byte.
539 return std::string(raw_headers_.c_str());
540 }
541
GetStatusText() const542 std::string HttpResponseHeaders::GetStatusText() const {
543 // GetStatusLine() is already normalized, so it has the format:
544 // '<http_version> SP <response_code>' or
545 // '<http_version> SP <response_code> SP <status_text>'.
546 std::string status_text = GetStatusLine();
547 // Seek to beginning of <response_code>.
548 std::string::const_iterator begin = base::ranges::find(status_text, ' ');
549 std::string::const_iterator end = status_text.end();
550 CHECK(begin != end);
551 ++begin;
552 CHECK(begin != end);
553 // See if there is another space.
554 begin = std::find(begin, end, ' ');
555 if (begin == end)
556 return std::string();
557 ++begin;
558 CHECK(begin != end);
559 return std::string(begin, end);
560 }
561
EnumerateHeaderLines(size_t * iter,std::string * name,std::string * value) const562 bool HttpResponseHeaders::EnumerateHeaderLines(size_t* iter,
563 std::string* name,
564 std::string* value) const {
565 size_t i = *iter;
566 if (i == parsed_.size())
567 return false;
568
569 DCHECK(!parsed_[i].is_continuation());
570
571 name->assign(parsed_[i].name_begin, parsed_[i].name_end);
572
573 std::string::const_iterator value_begin = parsed_[i].value_begin;
574 std::string::const_iterator value_end = parsed_[i].value_end;
575 while (++i < parsed_.size() && parsed_[i].is_continuation())
576 value_end = parsed_[i].value_end;
577
578 value->assign(value_begin, value_end);
579
580 *iter = i;
581 return true;
582 }
583
EnumerateHeader(size_t * iter,base::StringPiece name,std::string * value) const584 bool HttpResponseHeaders::EnumerateHeader(size_t* iter,
585 base::StringPiece name,
586 std::string* value) const {
587 size_t i;
588 if (!iter || !*iter) {
589 i = FindHeader(0, name);
590 } else {
591 i = *iter;
592 if (i >= parsed_.size()) {
593 i = std::string::npos;
594 } else if (!parsed_[i].is_continuation()) {
595 i = FindHeader(i, name);
596 }
597 }
598
599 if (i == std::string::npos) {
600 value->clear();
601 return false;
602 }
603
604 if (iter)
605 *iter = i + 1;
606 value->assign(parsed_[i].value_begin, parsed_[i].value_end);
607 return true;
608 }
609
HasHeaderValue(base::StringPiece name,base::StringPiece value) const610 bool HttpResponseHeaders::HasHeaderValue(base::StringPiece name,
611 base::StringPiece value) const {
612 // The value has to be an exact match. This is important since
613 // 'cache-control: no-cache' != 'cache-control: no-cache="foo"'
614 size_t iter = 0;
615 std::string temp;
616 while (EnumerateHeader(&iter, name, &temp)) {
617 if (base::EqualsCaseInsensitiveASCII(value, temp))
618 return true;
619 }
620 return false;
621 }
622
HasHeader(base::StringPiece name) const623 bool HttpResponseHeaders::HasHeader(base::StringPiece name) const {
624 return FindHeader(0, name) != std::string::npos;
625 }
626
627 HttpResponseHeaders::~HttpResponseHeaders() = default;
628
629 // Note: this implementation implicitly assumes that line_end points at a valid
630 // sentinel character (such as '\0').
631 // static
ParseVersion(std::string::const_iterator line_begin,std::string::const_iterator line_end)632 HttpVersion HttpResponseHeaders::ParseVersion(
633 std::string::const_iterator line_begin,
634 std::string::const_iterator line_end) {
635 std::string::const_iterator p = line_begin;
636
637 // RFC9112 Section 2.3:
638 // HTTP-version = HTTP-name "/" DIGIT "." DIGIT
639 // HTTP-name = %s"HTTP"
640
641 if (!base::StartsWith(base::MakeStringPiece(line_begin, line_end), "http",
642 base::CompareCase::INSENSITIVE_ASCII)) {
643 DVLOG(1) << "missing status line";
644 return HttpVersion();
645 }
646
647 p += 4;
648
649 if (p >= line_end || *p != '/') {
650 DVLOG(1) << "missing version";
651 return HttpVersion();
652 }
653
654 std::string::const_iterator dot = std::find(p, line_end, '.');
655 if (dot == line_end) {
656 DVLOG(1) << "malformed version";
657 return HttpVersion();
658 }
659
660 ++p; // from / to first digit.
661 ++dot; // from . to second digit.
662
663 if (!(base::IsAsciiDigit(*p) && base::IsAsciiDigit(*dot))) {
664 DVLOG(1) << "malformed version number";
665 return HttpVersion();
666 }
667
668 uint16_t major = *p - '0';
669 uint16_t minor = *dot - '0';
670
671 return HttpVersion(major, minor);
672 }
673
674 // Note: this implementation implicitly assumes that line_end points at a valid
675 // sentinel character (such as '\0').
ParseStatusLine(std::string::const_iterator line_begin,std::string::const_iterator line_end,bool has_headers)676 void HttpResponseHeaders::ParseStatusLine(
677 std::string::const_iterator line_begin,
678 std::string::const_iterator line_end,
679 bool has_headers) {
680 // Extract the version number
681 HttpVersion parsed_http_version = ParseVersion(line_begin, line_end);
682
683 // Clamp the version number to one of: {0.9, 1.0, 1.1, 2.0}
684 if (parsed_http_version == HttpVersion(0, 9) && !has_headers) {
685 http_version_ = HttpVersion(0, 9);
686 raw_headers_ = "HTTP/0.9";
687 } else if (parsed_http_version == HttpVersion(2, 0)) {
688 http_version_ = HttpVersion(2, 0);
689 raw_headers_ = "HTTP/2.0";
690 } else if (parsed_http_version >= HttpVersion(1, 1)) {
691 http_version_ = HttpVersion(1, 1);
692 raw_headers_ = "HTTP/1.1";
693 } else {
694 // Treat everything else like HTTP 1.0
695 http_version_ = HttpVersion(1, 0);
696 raw_headers_ = "HTTP/1.0";
697 }
698 if (parsed_http_version != http_version_) {
699 DVLOG(1) << "assuming HTTP/" << http_version_.major_value() << "."
700 << http_version_.minor_value();
701 }
702
703 // TODO(eroman): this doesn't make sense if ParseVersion failed.
704 std::string::const_iterator p = std::find(line_begin, line_end, ' ');
705
706 if (p == line_end) {
707 DVLOG(1) << "missing response status; assuming 200 OK";
708 raw_headers_.append(" 200 OK");
709 response_code_ = net::HTTP_OK;
710 return;
711 }
712
713 // Skip whitespace.
714 while (p < line_end && *p == ' ')
715 ++p;
716
717 std::string::const_iterator code = p;
718 while (p < line_end && base::IsAsciiDigit(*p))
719 ++p;
720
721 if (p == code) {
722 DVLOG(1) << "missing response status number; assuming 200";
723 raw_headers_.append(" 200");
724 response_code_ = net::HTTP_OK;
725 return;
726 }
727 raw_headers_.push_back(' ');
728 raw_headers_.append(code, p);
729 base::StringToInt(base::MakeStringPiece(code, p), &response_code_);
730
731 // Skip whitespace.
732 while (p < line_end && *p == ' ')
733 ++p;
734
735 // Trim trailing whitespace.
736 while (line_end > p && line_end[-1] == ' ')
737 --line_end;
738
739 if (p == line_end)
740 return;
741
742 raw_headers_.push_back(' ');
743 raw_headers_.append(p, line_end);
744 }
745
FindHeader(size_t from,base::StringPiece search) const746 size_t HttpResponseHeaders::FindHeader(size_t from,
747 base::StringPiece search) const {
748 for (size_t i = from; i < parsed_.size(); ++i) {
749 if (parsed_[i].is_continuation())
750 continue;
751 auto name =
752 base::MakeStringPiece(parsed_[i].name_begin, parsed_[i].name_end);
753 if (base::EqualsCaseInsensitiveASCII(search, name))
754 return i;
755 }
756
757 return std::string::npos;
758 }
759
GetCacheControlDirective(base::StringPiece directive,base::TimeDelta * result) const760 bool HttpResponseHeaders::GetCacheControlDirective(
761 base::StringPiece directive,
762 base::TimeDelta* result) const {
763 static constexpr base::StringPiece name("cache-control");
764 std::string value;
765
766 size_t directive_size = directive.size();
767
768 size_t iter = 0;
769 while (EnumerateHeader(&iter, name, &value)) {
770 if (!base::StartsWith(value, directive,
771 base::CompareCase::INSENSITIVE_ASCII)) {
772 continue;
773 }
774 if (value.size() == directive_size || value[directive_size] != '=')
775 continue;
776 // 1*DIGIT with leading and trailing spaces, as described at
777 // https://datatracker.ietf.org/doc/html/rfc7234#section-1.2.1.
778 auto start = value.cbegin() + directive_size + 1;
779 auto end = value.cend();
780 while (start < end && *start == ' ') {
781 // leading spaces
782 ++start;
783 }
784 while (start < end - 1 && *(end - 1) == ' ') {
785 // trailing spaces
786 --end;
787 }
788 if (start == end ||
789 !std::all_of(start, end, [](char c) { return '0' <= c && c <= '9'; })) {
790 continue;
791 }
792 int64_t seconds = 0;
793 base::StringToInt64(base::MakeStringPiece(start, end), &seconds);
794 // We ignore the return value because we've already checked the input
795 // string. For the overflow case we use
796 // base::TimeDelta::FiniteMax().InSeconds().
797 seconds = std::min(seconds, base::TimeDelta::FiniteMax().InSeconds());
798 *result = base::Seconds(seconds);
799 return true;
800 }
801
802 return false;
803 }
804
AddHeader(std::string::const_iterator name_begin,std::string::const_iterator name_end,std::string::const_iterator values_begin,std::string::const_iterator values_end)805 void HttpResponseHeaders::AddHeader(std::string::const_iterator name_begin,
806 std::string::const_iterator name_end,
807 std::string::const_iterator values_begin,
808 std::string::const_iterator values_end) {
809 // If the header can be coalesced, then we should split it up.
810 if (values_begin == values_end ||
811 HttpUtil::IsNonCoalescingHeader(
812 base::MakeStringPiece(name_begin, name_end))) {
813 AddToParsed(name_begin, name_end, values_begin, values_end);
814 } else {
815 HttpUtil::ValuesIterator it(values_begin, values_end, ',',
816 false /* ignore_empty_values */);
817 while (it.GetNext()) {
818 AddToParsed(name_begin, name_end, it.value_begin(), it.value_end());
819 // clobber these so that subsequent values are treated as continuations
820 name_begin = name_end = raw_headers_.end();
821 }
822 }
823 }
824
AddToParsed(std::string::const_iterator name_begin,std::string::const_iterator name_end,std::string::const_iterator value_begin,std::string::const_iterator value_end)825 void HttpResponseHeaders::AddToParsed(std::string::const_iterator name_begin,
826 std::string::const_iterator name_end,
827 std::string::const_iterator value_begin,
828 std::string::const_iterator value_end) {
829 ParsedHeader header;
830 header.name_begin = name_begin;
831 header.name_end = name_end;
832 header.value_begin = value_begin;
833 header.value_end = value_end;
834 parsed_.push_back(header);
835 }
836
AddNonCacheableHeaders(HeaderSet * result) const837 void HttpResponseHeaders::AddNonCacheableHeaders(HeaderSet* result) const {
838 // Add server specified transients. Any 'cache-control: no-cache="foo,bar"'
839 // headers present in the response specify additional headers that we should
840 // not store in the cache.
841 const char kCacheControl[] = "cache-control";
842 const char kPrefix[] = "no-cache=\"";
843 const size_t kPrefixLen = sizeof(kPrefix) - 1;
844
845 std::string value;
846 size_t iter = 0;
847 while (EnumerateHeader(&iter, kCacheControl, &value)) {
848 // If the value is smaller than the prefix and a terminal quote, skip
849 // it.
850 if (value.size() <= kPrefixLen ||
851 value.compare(0, kPrefixLen, kPrefix) != 0) {
852 continue;
853 }
854 // if it doesn't end with a quote, then treat as malformed
855 if (value[value.size() - 1] != '\"')
856 continue;
857
858 // process the value as a comma-separated list of items. Each
859 // item can be wrapped by linear white space.
860 std::string::const_iterator item = value.begin() + kPrefixLen;
861 std::string::const_iterator end = value.end() - 1;
862 while (item != end) {
863 // Find the comma to compute the length of the current item,
864 // and the position of the next one.
865 std::string::const_iterator item_next = std::find(item, end, ',');
866 std::string::const_iterator item_end = end;
867 if (item_next != end) {
868 // Skip over comma for next position.
869 item_end = item_next;
870 item_next++;
871 }
872 // trim off leading and trailing whitespace in this item.
873 HttpUtil::TrimLWS(&item, &item_end);
874
875 // assuming the header is not empty, lowercase and insert into set
876 if (item_end > item) {
877 result->insert(
878 base::ToLowerASCII(base::StringPiece(&*item, item_end - item)));
879 }
880
881 // Continue to next item.
882 item = item_next;
883 }
884 }
885 }
886
AddHopByHopHeaders(HeaderSet * result)887 void HttpResponseHeaders::AddHopByHopHeaders(HeaderSet* result) {
888 for (const auto* header : kHopByHopResponseHeaders)
889 result->insert(std::string(header));
890 }
891
AddCookieHeaders(HeaderSet * result)892 void HttpResponseHeaders::AddCookieHeaders(HeaderSet* result) {
893 for (const auto* header : kCookieResponseHeaders)
894 result->insert(std::string(header));
895 }
896
AddChallengeHeaders(HeaderSet * result)897 void HttpResponseHeaders::AddChallengeHeaders(HeaderSet* result) {
898 for (const auto* header : kChallengeResponseHeaders)
899 result->insert(std::string(header));
900 }
901
AddHopContentRangeHeaders(HeaderSet * result)902 void HttpResponseHeaders::AddHopContentRangeHeaders(HeaderSet* result) {
903 result->insert(kContentRange);
904 }
905
AddSecurityStateHeaders(HeaderSet * result)906 void HttpResponseHeaders::AddSecurityStateHeaders(HeaderSet* result) {
907 for (const auto* header : kSecurityStateHeaders)
908 result->insert(std::string(header));
909 }
910
GetMimeTypeAndCharset(std::string * mime_type,std::string * charset) const911 void HttpResponseHeaders::GetMimeTypeAndCharset(std::string* mime_type,
912 std::string* charset) const {
913 mime_type->clear();
914 charset->clear();
915
916 std::string name = "content-type";
917 std::string value;
918
919 bool had_charset = false;
920
921 size_t iter = 0;
922 while (EnumerateHeader(&iter, name, &value))
923 HttpUtil::ParseContentType(value, mime_type, charset, &had_charset,
924 nullptr);
925 }
926
GetMimeType(std::string * mime_type) const927 bool HttpResponseHeaders::GetMimeType(std::string* mime_type) const {
928 std::string unused;
929 GetMimeTypeAndCharset(mime_type, &unused);
930 return !mime_type->empty();
931 }
932
GetCharset(std::string * charset) const933 bool HttpResponseHeaders::GetCharset(std::string* charset) const {
934 std::string unused;
935 GetMimeTypeAndCharset(&unused, charset);
936 return !charset->empty();
937 }
938
IsRedirect(std::string * location) const939 bool HttpResponseHeaders::IsRedirect(std::string* location) const {
940 if (!IsRedirectResponseCode(response_code_))
941 return false;
942
943 // If we lack a Location header, then we can't treat this as a redirect.
944 // We assume that the first non-empty location value is the target URL that
945 // we want to follow. TODO(darin): Is this consistent with other browsers?
946 size_t i = std::string::npos;
947 do {
948 i = FindHeader(++i, "location");
949 if (i == std::string::npos)
950 return false;
951 // If the location value is empty, then it doesn't count.
952 } while (parsed_[i].value_begin == parsed_[i].value_end);
953
954 if (location) {
955 auto location_strpiece =
956 base::MakeStringPiece(parsed_[i].value_begin, parsed_[i].value_end);
957 // Escape any non-ASCII characters to preserve them. The server should
958 // only be returning ASCII here, but for compat we need to do this.
959 //
960 // The URL parser escapes things internally, but it expect the bytes to be
961 // valid UTF-8, so encoding errors turn into replacement characters before
962 // escaping. Escaping here preserves the bytes as-is. See
963 // https://crbug.com/942073#c14.
964 *location = base::EscapeNonASCII(location_strpiece);
965 }
966
967 return true;
968 }
969
970 // static
IsRedirectResponseCode(int response_code)971 bool HttpResponseHeaders::IsRedirectResponseCode(int response_code) {
972 // Users probably want to see 300 (multiple choice) pages, so we don't count
973 // them as redirects that need to be followed.
974 return (response_code == net::HTTP_MOVED_PERMANENTLY ||
975 response_code == net::HTTP_FOUND ||
976 response_code == net::HTTP_SEE_OTHER ||
977 response_code == net::HTTP_TEMPORARY_REDIRECT ||
978 response_code == net::HTTP_PERMANENT_REDIRECT);
979 }
980
981 // From RFC 2616 section 13.2.4:
982 //
983 // The calculation to determine if a response has expired is quite simple:
984 //
985 // response_is_fresh = (freshness_lifetime > current_age)
986 //
987 // Of course, there are other factors that can force a response to always be
988 // validated or re-fetched.
989 //
990 // From RFC 5861 section 3, a stale response may be used while revalidation is
991 // performed in the background if
992 //
993 // freshness_lifetime + stale_while_revalidate > current_age
994 //
RequiresValidation(const Time & request_time,const Time & response_time,const Time & current_time) const995 ValidationType HttpResponseHeaders::RequiresValidation(
996 const Time& request_time,
997 const Time& response_time,
998 const Time& current_time) const {
999 FreshnessLifetimes lifetimes = GetFreshnessLifetimes(response_time);
1000 if (lifetimes.freshness.is_zero() && lifetimes.staleness.is_zero())
1001 return VALIDATION_SYNCHRONOUS;
1002
1003 base::TimeDelta age =
1004 GetCurrentAge(request_time, response_time, current_time);
1005
1006 if (lifetimes.freshness > age)
1007 return VALIDATION_NONE;
1008
1009 if (lifetimes.freshness + lifetimes.staleness > age)
1010 return VALIDATION_ASYNCHRONOUS;
1011
1012 return VALIDATION_SYNCHRONOUS;
1013 }
1014
1015 // From RFC 2616 section 13.2.4:
1016 //
1017 // The max-age directive takes priority over Expires, so if max-age is present
1018 // in a response, the calculation is simply:
1019 //
1020 // freshness_lifetime = max_age_value
1021 //
1022 // Otherwise, if Expires is present in the response, the calculation is:
1023 //
1024 // freshness_lifetime = expires_value - date_value
1025 //
1026 // Note that neither of these calculations is vulnerable to clock skew, since
1027 // all of the information comes from the origin server.
1028 //
1029 // Also, if the response does have a Last-Modified time, the heuristic
1030 // expiration value SHOULD be no more than some fraction of the interval since
1031 // that time. A typical setting of this fraction might be 10%:
1032 //
1033 // freshness_lifetime = (date_value - last_modified_value) * 0.10
1034 //
1035 // If the stale-while-revalidate directive is present, then it is used to set
1036 // the |staleness| time, unless it overridden by another directive.
1037 //
1038 HttpResponseHeaders::FreshnessLifetimes
GetFreshnessLifetimes(const Time & response_time) const1039 HttpResponseHeaders::GetFreshnessLifetimes(const Time& response_time) const {
1040 FreshnessLifetimes lifetimes;
1041 // Check for headers that force a response to never be fresh. For backwards
1042 // compat, we treat "Pragma: no-cache" as a synonym for "Cache-Control:
1043 // no-cache" even though RFC 2616 does not specify it.
1044 if (HasHeaderValue("cache-control", "no-cache") ||
1045 HasHeaderValue("cache-control", "no-store") ||
1046 HasHeaderValue("pragma", "no-cache")) {
1047 return lifetimes;
1048 }
1049
1050 // Cache-Control directive must_revalidate overrides stale-while-revalidate.
1051 bool must_revalidate = HasHeaderValue("cache-control", "must-revalidate");
1052
1053 if (must_revalidate || !GetStaleWhileRevalidateValue(&lifetimes.staleness)) {
1054 DCHECK_EQ(base::TimeDelta(), lifetimes.staleness);
1055 }
1056
1057 // NOTE: "Cache-Control: max-age" overrides Expires, so we only check the
1058 // Expires header after checking for max-age in GetFreshnessLifetimes. This
1059 // is important since "Expires: <date in the past>" means not fresh, but
1060 // it should not trump a max-age value.
1061 if (GetMaxAgeValue(&lifetimes.freshness))
1062 return lifetimes;
1063
1064 // If there is no Date header, then assume that the server response was
1065 // generated at the time when we received the response.
1066 Time date_value;
1067 if (!GetDateValue(&date_value))
1068 date_value = response_time;
1069
1070 Time expires_value;
1071 if (GetExpiresValue(&expires_value)) {
1072 // The expires value can be a date in the past!
1073 if (expires_value > date_value) {
1074 lifetimes.freshness = expires_value - date_value;
1075 return lifetimes;
1076 }
1077
1078 DCHECK_EQ(base::TimeDelta(), lifetimes.freshness);
1079 return lifetimes;
1080 }
1081
1082 // From RFC 2616 section 13.4:
1083 //
1084 // A response received with a status code of 200, 203, 206, 300, 301 or 410
1085 // MAY be stored by a cache and used in reply to a subsequent request,
1086 // subject to the expiration mechanism, unless a cache-control directive
1087 // prohibits caching.
1088 // ...
1089 // A response received with any other status code (e.g. status codes 302
1090 // and 307) MUST NOT be returned in a reply to a subsequent request unless
1091 // there are cache-control directives or another header(s) that explicitly
1092 // allow it.
1093 //
1094 // From RFC 2616 section 14.9.4:
1095 //
1096 // When the must-revalidate directive is present in a response received by
1097 // a cache, that cache MUST NOT use the entry after it becomes stale to
1098 // respond to a subsequent request without first revalidating it with the
1099 // origin server. (I.e., the cache MUST do an end-to-end revalidation every
1100 // time, if, based solely on the origin server's Expires or max-age value,
1101 // the cached response is stale.)
1102 //
1103 // https://datatracker.ietf.org/doc/draft-reschke-http-status-308/ is an
1104 // experimental RFC that adds 308 permanent redirect as well, for which "any
1105 // future references ... SHOULD use one of the returned URIs."
1106 if ((response_code_ == net::HTTP_OK ||
1107 response_code_ == net::HTTP_NON_AUTHORITATIVE_INFORMATION ||
1108 response_code_ == net::HTTP_PARTIAL_CONTENT) &&
1109 !must_revalidate) {
1110 // TODO(darin): Implement a smarter heuristic.
1111 Time last_modified_value;
1112 if (GetLastModifiedValue(&last_modified_value)) {
1113 // The last-modified value can be a date in the future!
1114 if (last_modified_value <= date_value) {
1115 lifetimes.freshness = (date_value - last_modified_value) / 10;
1116 return lifetimes;
1117 }
1118 }
1119 }
1120
1121 // These responses are implicitly fresh (unless otherwise overruled):
1122 if (response_code_ == net::HTTP_MULTIPLE_CHOICES ||
1123 response_code_ == net::HTTP_MOVED_PERMANENTLY ||
1124 response_code_ == net::HTTP_PERMANENT_REDIRECT ||
1125 response_code_ == net::HTTP_GONE) {
1126 lifetimes.freshness = base::TimeDelta::Max();
1127 lifetimes.staleness = base::TimeDelta(); // It should never be stale.
1128 return lifetimes;
1129 }
1130
1131 // Our heuristic freshness estimate for this resource is 0 seconds, in
1132 // accordance with common browser behaviour. However, stale-while-revalidate
1133 // may still apply.
1134 DCHECK_EQ(base::TimeDelta(), lifetimes.freshness);
1135 return lifetimes;
1136 }
1137
1138 // From RFC 7234 section 4.2.3:
1139 //
1140 // The following data is used for the age calculation:
1141 //
1142 // age_value
1143 //
1144 // The term "age_value" denotes the value of the Age header field
1145 // (Section 5.1), in a form appropriate for arithmetic operation; or
1146 // 0, if not available.
1147 //
1148 // date_value
1149 //
1150 // The term "date_value" denotes the value of the Date header field,
1151 // in a form appropriate for arithmetic operations. See Section
1152 // 7.1.1.2 of [RFC7231] for the definition of the Date header field,
1153 // and for requirements regarding responses without it.
1154 //
1155 // now
1156 //
1157 // The term "now" means "the current value of the clock at the host
1158 // performing the calculation". A host ought to use NTP ([RFC5905])
1159 // or some similar protocol to synchronize its clocks to Coordinated
1160 // Universal Time.
1161 //
1162 // request_time
1163 //
1164 // The current value of the clock at the host at the time the request
1165 // resulting in the stored response was made.
1166 //
1167 // response_time
1168 //
1169 // The current value of the clock at the host at the time the
1170 // response was received.
1171 //
1172 // The age is then calculated as
1173 //
1174 // apparent_age = max(0, response_time - date_value);
1175 // response_delay = response_time - request_time;
1176 // corrected_age_value = age_value + response_delay;
1177 // corrected_initial_age = max(apparent_age, corrected_age_value);
1178 // resident_time = now - response_time;
1179 // current_age = corrected_initial_age + resident_time;
1180 //
GetCurrentAge(const Time & request_time,const Time & response_time,const Time & current_time) const1181 base::TimeDelta HttpResponseHeaders::GetCurrentAge(
1182 const Time& request_time,
1183 const Time& response_time,
1184 const Time& current_time) const {
1185 // If there is no Date header, then assume that the server response was
1186 // generated at the time when we received the response.
1187 Time date_value;
1188 if (!GetDateValue(&date_value))
1189 date_value = response_time;
1190
1191 // If there is no Age header, then assume age is zero. GetAgeValue does not
1192 // modify its out param if the value does not exist.
1193 base::TimeDelta age_value;
1194 GetAgeValue(&age_value);
1195
1196 base::TimeDelta apparent_age =
1197 std::max(base::TimeDelta(), response_time - date_value);
1198 base::TimeDelta response_delay = response_time - request_time;
1199 base::TimeDelta corrected_age_value = age_value + response_delay;
1200 base::TimeDelta corrected_initial_age =
1201 std::max(apparent_age, corrected_age_value);
1202 base::TimeDelta resident_time = current_time - response_time;
1203 base::TimeDelta current_age = corrected_initial_age + resident_time;
1204
1205 return current_age;
1206 }
1207
GetMaxAgeValue(base::TimeDelta * result) const1208 bool HttpResponseHeaders::GetMaxAgeValue(base::TimeDelta* result) const {
1209 return GetCacheControlDirective("max-age", result);
1210 }
1211
GetAgeValue(base::TimeDelta * result) const1212 bool HttpResponseHeaders::GetAgeValue(base::TimeDelta* result) const {
1213 std::string value;
1214 if (!EnumerateHeader(nullptr, "Age", &value))
1215 return false;
1216
1217 // Parse the delta-seconds as 1*DIGIT.
1218 uint32_t seconds;
1219 ParseIntError error;
1220 if (!ParseUint32(value, ParseIntFormat::NON_NEGATIVE, &seconds, &error)) {
1221 if (error == ParseIntError::FAILED_OVERFLOW) {
1222 // If the Age value cannot fit in a uint32_t, saturate it to a maximum
1223 // value. This is similar to what RFC 2616 says in section 14.6 for how
1224 // caches should transmit values that overflow.
1225 seconds = std::numeric_limits<decltype(seconds)>::max();
1226 } else {
1227 return false;
1228 }
1229 }
1230
1231 *result = base::Seconds(seconds);
1232 return true;
1233 }
1234
GetDateValue(Time * result) const1235 bool HttpResponseHeaders::GetDateValue(Time* result) const {
1236 return GetTimeValuedHeader("Date", result);
1237 }
1238
GetLastModifiedValue(Time * result) const1239 bool HttpResponseHeaders::GetLastModifiedValue(Time* result) const {
1240 return GetTimeValuedHeader("Last-Modified", result);
1241 }
1242
GetExpiresValue(Time * result) const1243 bool HttpResponseHeaders::GetExpiresValue(Time* result) const {
1244 return GetTimeValuedHeader("Expires", result);
1245 }
1246
GetStaleWhileRevalidateValue(base::TimeDelta * result) const1247 bool HttpResponseHeaders::GetStaleWhileRevalidateValue(
1248 base::TimeDelta* result) const {
1249 return GetCacheControlDirective("stale-while-revalidate", result);
1250 }
1251
GetTimeValuedHeader(const std::string & name,Time * result) const1252 bool HttpResponseHeaders::GetTimeValuedHeader(const std::string& name,
1253 Time* result) const {
1254 std::string value;
1255 if (!EnumerateHeader(nullptr, name, &value))
1256 return false;
1257
1258 // When parsing HTTP dates it's beneficial to default to GMT because:
1259 // 1. RFC2616 3.3.1 says times should always be specified in GMT
1260 // 2. Only counter-example incorrectly appended "UTC" (crbug.com/153759)
1261 // 3. When adjusting cookie expiration times for clock skew
1262 // (crbug.com/135131) this better matches our cookie expiration
1263 // time parser which ignores timezone specifiers and assumes GMT.
1264 // 4. This is exactly what Firefox does.
1265 // TODO(pauljensen): The ideal solution would be to return false if the
1266 // timezone could not be understood so as to avoid makeing other calculations
1267 // based on an incorrect time. This would require modifying the time
1268 // library or duplicating the code. (http://crbug.com/158327)
1269 return Time::FromUTCString(value.c_str(), result);
1270 }
1271
1272 // We accept the first value of "close" or "keep-alive" in a Connection or
1273 // Proxy-Connection header, in that order. Obeying "keep-alive" in HTTP/1.1 or
1274 // "close" in 1.0 is not strictly standards-compliant, but we'd like to
1275 // avoid looking at the Proxy-Connection header whenever it is reasonable to do
1276 // so.
1277 // TODO(ricea): Measure real-world usage of the "Proxy-Connection" header,
1278 // with a view to reducing support for it in order to make our Connection header
1279 // handling more RFC 7230 compliant.
IsKeepAlive() const1280 bool HttpResponseHeaders::IsKeepAlive() const {
1281 // NOTE: It is perhaps risky to assume that a Proxy-Connection header is
1282 // meaningful when we don't know that this response was from a proxy, but
1283 // Mozilla also does this, so we'll do the same.
1284 static const char* const kConnectionHeaders[] = {"connection",
1285 "proxy-connection"};
1286 struct KeepAliveToken {
1287 const char* const token;
1288 bool keep_alive;
1289 };
1290 static const KeepAliveToken kKeepAliveTokens[] = {{"keep-alive", true},
1291 {"close", false}};
1292
1293 if (http_version_ < HttpVersion(1, 0))
1294 return false;
1295
1296 for (const char* header : kConnectionHeaders) {
1297 size_t iterator = 0;
1298 std::string token;
1299 while (EnumerateHeader(&iterator, header, &token)) {
1300 for (const KeepAliveToken& keep_alive_token : kKeepAliveTokens) {
1301 if (base::EqualsCaseInsensitiveASCII(token, keep_alive_token.token))
1302 return keep_alive_token.keep_alive;
1303 }
1304 }
1305 }
1306 return http_version_ != HttpVersion(1, 0);
1307 }
1308
HasStrongValidators() const1309 bool HttpResponseHeaders::HasStrongValidators() const {
1310 std::string etag_header;
1311 EnumerateHeader(nullptr, "etag", &etag_header);
1312 std::string last_modified_header;
1313 EnumerateHeader(nullptr, "Last-Modified", &last_modified_header);
1314 std::string date_header;
1315 EnumerateHeader(nullptr, "Date", &date_header);
1316 return HttpUtil::HasStrongValidators(GetHttpVersion(), etag_header,
1317 last_modified_header, date_header);
1318 }
1319
HasValidators() const1320 bool HttpResponseHeaders::HasValidators() const {
1321 std::string etag_header;
1322 EnumerateHeader(nullptr, "etag", &etag_header);
1323 std::string last_modified_header;
1324 EnumerateHeader(nullptr, "Last-Modified", &last_modified_header);
1325 return HttpUtil::HasValidators(GetHttpVersion(), etag_header,
1326 last_modified_header);
1327 }
1328
1329 // From RFC 2616:
1330 // Content-Length = "Content-Length" ":" 1*DIGIT
GetContentLength() const1331 int64_t HttpResponseHeaders::GetContentLength() const {
1332 return GetInt64HeaderValue("content-length");
1333 }
1334
GetInt64HeaderValue(const std::string & header) const1335 int64_t HttpResponseHeaders::GetInt64HeaderValue(
1336 const std::string& header) const {
1337 size_t iter = 0;
1338 std::string content_length_val;
1339 if (!EnumerateHeader(&iter, header, &content_length_val))
1340 return -1;
1341
1342 if (content_length_val.empty())
1343 return -1;
1344
1345 if (content_length_val[0] == '+')
1346 return -1;
1347
1348 int64_t result;
1349 bool ok = base::StringToInt64(content_length_val, &result);
1350 if (!ok || result < 0)
1351 return -1;
1352
1353 return result;
1354 }
1355
GetContentRangeFor206(int64_t * first_byte_position,int64_t * last_byte_position,int64_t * instance_length) const1356 bool HttpResponseHeaders::GetContentRangeFor206(
1357 int64_t* first_byte_position,
1358 int64_t* last_byte_position,
1359 int64_t* instance_length) const {
1360 size_t iter = 0;
1361 std::string content_range_spec;
1362 if (!EnumerateHeader(&iter, kContentRange, &content_range_spec)) {
1363 *first_byte_position = *last_byte_position = *instance_length = -1;
1364 return false;
1365 }
1366
1367 return HttpUtil::ParseContentRangeHeaderFor206(
1368 content_range_spec, first_byte_position, last_byte_position,
1369 instance_length);
1370 }
1371
NetLogParams(NetLogCaptureMode capture_mode) const1372 base::Value::Dict HttpResponseHeaders::NetLogParams(
1373 NetLogCaptureMode capture_mode) const {
1374 base::Value::Dict dict;
1375 base::Value::List headers;
1376 headers.Append(NetLogStringValue(GetStatusLine()));
1377 size_t iterator = 0;
1378 std::string name;
1379 std::string value;
1380 while (EnumerateHeaderLines(&iterator, &name, &value)) {
1381 std::string log_value =
1382 ElideHeaderValueForNetLog(capture_mode, name, value);
1383 headers.Append(NetLogStringValue(base::StrCat({name, ": ", log_value})));
1384 }
1385 dict.Set("headers", std::move(headers));
1386 return dict;
1387 }
1388
IsChunkEncoded() const1389 bool HttpResponseHeaders::IsChunkEncoded() const {
1390 // Ignore spurious chunked responses from HTTP/1.0 servers and proxies.
1391 return GetHttpVersion() >= HttpVersion(1, 1) &&
1392 HasHeaderValue("Transfer-Encoding", "chunked");
1393 }
1394
IsCookieResponseHeader(base::StringPiece name)1395 bool HttpResponseHeaders::IsCookieResponseHeader(base::StringPiece name) {
1396 for (const char* cookie_header : kCookieResponseHeaders) {
1397 if (base::EqualsCaseInsensitiveASCII(cookie_header, name))
1398 return true;
1399 }
1400 return false;
1401 }
1402
WriteIntoTrace(perfetto::TracedValue context) const1403 void HttpResponseHeaders::WriteIntoTrace(perfetto::TracedValue context) const {
1404 perfetto::TracedDictionary dict = std::move(context).WriteDictionary();
1405 dict.Add("response_code", response_code_);
1406 dict.Add("headers", parsed_);
1407 }
1408
1409 } // namespace net
1410