| /* |
| * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) |
| * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. |
| * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ |
| * Copyright (C) 2009 Google Inc. All rights reserved. |
| * Copyright (C) 2011 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/platform/network/http_parsers.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/containers/flat_map.h" |
| #include "net/http/http_content_disposition.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_util.h" |
| #include "services/network/public/cpp/content_security_policy/content_security_policy.h" |
| #include "services/network/public/cpp/parsed_headers.h" |
| #include "services/network/public/cpp/timing_allow_origin_parser.h" |
| #include "services/network/public/mojom/parsed_headers.mojom-blink.h" |
| #include "services/network/public/mojom/timing_allow_origin.mojom-blink.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" |
| #include "third_party/blink/renderer/platform/network/header_field_tokenizer.h" |
| #include "third_party/blink/renderer/platform/network/http_names.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/wtf/date_math.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" |
| #include "third_party/blink/renderer/platform/wtf/text/character_names.h" |
| #include "third_party/blink/renderer/platform/wtf/text/parsing_utilities.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| #include "third_party/blink/renderer/platform/wtf/wtf.h" |
| |
| // We would like finding a way to convert from/to blink type automatically. |
| // The following attempt has been withdrawn: |
| // https://chromium-review.googlesource.com/c/chromium/src/+/2126933/7 |
| // |
| // Note: nesting these helpers inside network::mojom bypasses warnings from |
| // audit_non_blink_style.py, as well as saving a bunch of typing to qualify the |
| // types below. |
| namespace network { |
| namespace mojom { |
| |
| // When adding a new conversion, define a new `ConvertToBlink` overload to map |
| // the non-Blink type (passing by value for primitive types or passing by const |
| // reference otherwise). The generic converters for container types relies on |
| // the presence of `ConvertToBlink` overloads to determine the correct return |
| // type. |
| |
| // ===== Identity converters ===== |
| // Converts where the input type and output type are identical(-ish). |
| uint8_t ConvertToBlink(uint8_t in) { |
| return in; |
| } |
| |
| // Note: for identity enum conversions, there should be `static_assert`s that |
| // the input enumerator and the output enumerator define matching values. |
| blink::CSPDirectiveName ConvertToBlink(CSPDirectiveName name) { |
| return static_cast<blink::CSPDirectiveName>(name); |
| } |
| |
| // `in` is a Mojo enum type, which is type aliased to the same underlying type |
| // by both the non-Blink Mojo variant and the Blink Mojo variant. |
| blink::WebClientHintsType ConvertToBlink(WebClientHintsType in) { |
| return in; |
| } |
| |
| // ===== Converters for other basic Blink types ===== |
| String ConvertToBlink(const std::string& in) { |
| return String::FromUTF8(in); |
| } |
| |
| String ConvertToBlink(const absl::optional<std::string>& in) { |
| return in ? String::FromUTF8(*in) : String(); |
| } |
| |
| ::blink::KURL ConvertToBlink(const GURL& in) { |
| return ::blink::KURL(in); |
| } |
| |
| scoped_refptr<const ::blink::SecurityOrigin> ConvertToBlink( |
| const url::Origin& in) { |
| return ::blink::SecurityOrigin::CreateFromUrlOrigin(in); |
| } |
| |
| // ====== Generic container converters ===== |
| template < |
| typename InElement, |
| typename OutElement = decltype(ConvertToBlink(std::declval<InElement>()))> |
| Vector<OutElement> ConvertToBlink(const std::vector<InElement>& in) { |
| Vector<OutElement> out; |
| out.ReserveCapacity(in.size()); |
| for (const auto& element : in) { |
| out.push_back(ConvertToBlink(element)); |
| } |
| return out; |
| } |
| |
| template <typename InKey, |
| typename InValue, |
| typename OutKey = decltype(ConvertToBlink(std::declval<InKey>())), |
| typename OutValue = decltype(ConvertToBlink(std::declval<InValue>()))> |
| HashMap<OutKey, OutValue> ConvertToBlink( |
| const base::flat_map<InKey, InValue>& in) { |
| HashMap<OutKey, OutValue> out; |
| for (const auto& element : in) { |
| out.insert(ConvertToBlink(element.first), ConvertToBlink(element.second)); |
| } |
| return out; |
| } |
| |
| // ===== Converters from non-Blink to Blink variant of Mojo structs ===== |
| blink::CSPSourcePtr ConvertToBlink(const CSPSourcePtr& in) { |
| DCHECK(in); |
| return blink::CSPSource::New( |
| ConvertToBlink(in->scheme), ConvertToBlink(in->host), in->port, |
| ConvertToBlink(in->path), in->is_host_wildcard, in->is_port_wildcard); |
| } |
| |
| blink::CSPHashSourcePtr ConvertToBlink(const CSPHashSourcePtr& in) { |
| DCHECK(in); |
| Vector<uint8_t> hash_value = ConvertToBlink(in->value); |
| |
| return blink::CSPHashSource::New(in->algorithm, std::move(hash_value)); |
| } |
| |
| blink::CSPSourceListPtr ConvertToBlink(const CSPSourceListPtr& source_list) { |
| DCHECK(source_list); |
| |
| Vector<blink::CSPSourcePtr> sources = ConvertToBlink(source_list->sources); |
| Vector<String> nonces = ConvertToBlink(source_list->nonces); |
| Vector<blink::CSPHashSourcePtr> hashes = ConvertToBlink(source_list->hashes); |
| |
| return blink::CSPSourceList::New( |
| std::move(sources), std::move(nonces), std::move(hashes), |
| source_list->allow_self, source_list->allow_star, |
| source_list->allow_response_redirects, source_list->allow_inline, |
| source_list->allow_eval, source_list->allow_wasm_eval, |
| source_list->allow_dynamic, source_list->allow_unsafe_hashes, |
| source_list->report_sample); |
| } |
| |
| blink::ContentSecurityPolicyHeaderPtr ConvertToBlink( |
| const ContentSecurityPolicyHeaderPtr& in) { |
| DCHECK(in); |
| return blink::ContentSecurityPolicyHeader::New( |
| ConvertToBlink(in->header_value), in->type, in->source); |
| } |
| |
| blink::CSPTrustedTypesPtr ConvertToBlink(const CSPTrustedTypesPtr& in) { |
| if (!in) |
| return nullptr; |
| return blink::CSPTrustedTypes::New(ConvertToBlink(in->list), in->allow_any, |
| in->allow_duplicates); |
| } |
| |
| blink::ContentSecurityPolicyPtr ConvertToBlink( |
| const ContentSecurityPolicyPtr& in) { |
| DCHECK(in); |
| return blink::ContentSecurityPolicy::New( |
| ConvertToBlink(in->self_origin), ConvertToBlink(in->raw_directives), |
| ConvertToBlink(in->directives), in->upgrade_insecure_requests, |
| in->treat_as_public_address, in->block_all_mixed_content, in->sandbox, |
| ConvertToBlink(in->header), in->use_reporting_api, |
| ConvertToBlink(in->report_endpoints), in->require_trusted_types_for, |
| ConvertToBlink(in->trusted_types), ConvertToBlink(in->parsing_errors)); |
| } |
| |
| blink::AllowCSPFromHeaderValuePtr ConvertToBlink( |
| const AllowCSPFromHeaderValuePtr& allow_csp_from) { |
| if (!allow_csp_from) |
| return nullptr; |
| switch (allow_csp_from->which()) { |
| case AllowCSPFromHeaderValue::Tag::ALLOW_STAR: |
| return blink::AllowCSPFromHeaderValue::NewAllowStar( |
| allow_csp_from->get_allow_star()); |
| case AllowCSPFromHeaderValue::Tag::ORIGIN: |
| return blink::AllowCSPFromHeaderValue::NewOrigin( |
| ConvertToBlink(allow_csp_from->get_origin())); |
| case AllowCSPFromHeaderValue::Tag::ERROR_MESSAGE: |
| return blink::AllowCSPFromHeaderValue::NewErrorMessage( |
| ConvertToBlink(allow_csp_from->get_error_message())); |
| } |
| } |
| |
| blink::LinkHeaderPtr ConvertToBlink(const LinkHeaderPtr& in) { |
| DCHECK(in); |
| return blink::LinkHeader::New( |
| ConvertToBlink(in->href), |
| // TODO(dcheng): Make these use ConvertToBlink |
| static_cast<blink::LinkRelAttribute>(in->rel), |
| static_cast<blink::LinkAsAttribute>(in->as), |
| static_cast<blink::CrossOriginAttribute>(in->cross_origin), |
| ConvertToBlink(in->mime_type)); |
| } |
| |
| blink::TimingAllowOriginPtr ConvertToBlink(const TimingAllowOriginPtr& in) { |
| if (!in) { |
| return nullptr; |
| } |
| |
| switch (in->which()) { |
| case TimingAllowOrigin::Tag::kSerializedOrigins: |
| return blink::TimingAllowOrigin::NewSerializedOrigins( |
| ConvertToBlink(in->get_serialized_origins())); |
| case TimingAllowOrigin::Tag::kAll: |
| return blink::TimingAllowOrigin::NewAll(/*ignored=*/0); |
| } |
| } |
| |
| blink::ParsedHeadersPtr ConvertToBlink(const ParsedHeadersPtr& in) { |
| DCHECK(in); |
| return blink::ParsedHeaders::New( |
| ConvertToBlink(in->content_security_policy), |
| ConvertToBlink(in->allow_csp_from), in->cross_origin_embedder_policy, |
| in->cross_origin_opener_policy, in->origin_agent_cluster, |
| in->accept_ch.has_value() |
| ? absl::make_optional(ConvertToBlink(in->accept_ch.value())) |
| : absl::nullopt, |
| in->accept_ch_lifetime, |
| in->critical_ch.has_value() |
| ? absl::make_optional(ConvertToBlink(in->critical_ch.value())) |
| : absl::nullopt, |
| in->xfo, ConvertToBlink(in->link_headers), |
| ConvertToBlink(in->timing_allow_origin), in->bfcache_opt_in_unload); |
| } |
| |
| } // namespace mojom |
| } // namespace network |
| |
| namespace blink { |
| |
| namespace { |
| |
| const Vector<AtomicString>& ReplaceHeaders() { |
| // The list of response headers that we do not copy from the original |
| // response when generating a ResourceResponse for a MIME payload. |
| // Note: this is called only on the main thread. |
| DEFINE_STATIC_LOCAL(Vector<AtomicString>, headers, |
| ({"content-type", "content-length", "content-disposition", |
| "content-range", "range", "set-cookie"})); |
| return headers; |
| } |
| |
| bool IsWhitespace(UChar chr) { |
| return (chr == ' ') || (chr == '\t'); |
| } |
| |
| // true if there is more to parse, after incrementing pos past whitespace. |
| // Note: Might return pos == str.length() |
| // if |matcher| is nullptr, isWhitespace() is used. |
| inline bool SkipWhiteSpace(const String& str, |
| unsigned& pos, |
| WTF::CharacterMatchFunctionPtr matcher = nullptr) { |
| unsigned len = str.length(); |
| |
| if (matcher) { |
| while (pos < len && matcher(str[pos])) |
| ++pos; |
| } else { |
| while (pos < len && IsWhitespace(str[pos])) |
| ++pos; |
| } |
| |
| return pos < len; |
| } |
| |
| template <typename CharType> |
| inline bool IsASCIILowerAlphaOrDigit(CharType c) { |
| return IsASCIILower(c) || IsASCIIDigit(c); |
| } |
| |
| template <typename CharType> |
| inline bool IsASCIILowerAlphaOrDigitOrHyphen(CharType c) { |
| return IsASCIILowerAlphaOrDigit(c) || c == '-'; |
| } |
| |
| // Parse a number with ignoring trailing [0-9.]. |
| // Returns false if the source contains invalid characters. |
| bool ParseRefreshTime(const String& source, base::TimeDelta& delay) { |
| int full_stop_count = 0; |
| unsigned number_end = source.length(); |
| for (unsigned i = 0; i < source.length(); ++i) { |
| UChar ch = source[i]; |
| if (ch == kFullstopCharacter) { |
| // TODO(tkent): According to the HTML specification, we should support |
| // only integers. However we support fractional numbers. |
| if (++full_stop_count == 2) |
| number_end = i; |
| } else if (!IsASCIIDigit(ch)) { |
| return false; |
| } |
| } |
| bool ok; |
| double time = source.Left(number_end).ToDouble(&ok); |
| if (!ok) |
| return false; |
| delay = base::TimeDelta::FromSecondsD(time); |
| return true; |
| } |
| |
| } // namespace |
| |
| bool IsValidHTTPHeaderValue(const String& name) { |
| // FIXME: This should really match name against |
| // field-value in section 4.2 of RFC 2616. |
| |
| return name.ContainsOnlyLatin1OrEmpty() && !name.Contains('\r') && |
| !name.Contains('\n') && !name.Contains('\0'); |
| } |
| |
| // See RFC 7230, Section 3.2.6. |
| bool IsValidHTTPToken(const String& characters) { |
| if (characters.IsEmpty()) |
| return false; |
| for (unsigned i = 0; i < characters.length(); ++i) { |
| UChar c = characters[i]; |
| if (c > 0x7F || !net::HttpUtil::IsTokenChar(c)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool IsContentDispositionAttachment(const String& content_disposition) { |
| return net::HttpContentDisposition(content_disposition.Utf8(), std::string()) |
| .is_attachment(); |
| } |
| |
| // https://html.spec.whatwg.org/C/#attr-meta-http-equiv-refresh |
| bool ParseHTTPRefresh(const String& refresh, |
| WTF::CharacterMatchFunctionPtr matcher, |
| base::TimeDelta& delay, |
| String& url) { |
| unsigned len = refresh.length(); |
| unsigned pos = 0; |
| matcher = matcher ? matcher : IsWhitespace; |
| |
| if (!SkipWhiteSpace(refresh, pos, matcher)) |
| return false; |
| |
| while (pos != len && refresh[pos] != ',' && refresh[pos] != ';' && |
| !matcher(refresh[pos])) |
| ++pos; |
| |
| if (pos == len) { // no URL |
| url = String(); |
| return ParseRefreshTime(refresh.StripWhiteSpace(), delay); |
| } else { |
| if (!ParseRefreshTime(refresh.Left(pos).StripWhiteSpace(), delay)) |
| return false; |
| |
| SkipWhiteSpace(refresh, pos, matcher); |
| if (pos < len && (refresh[pos] == ',' || refresh[pos] == ';')) |
| ++pos; |
| SkipWhiteSpace(refresh, pos, matcher); |
| unsigned url_start_pos = pos; |
| if (refresh.FindIgnoringASCIICase("url", url_start_pos) == url_start_pos) { |
| url_start_pos += 3; |
| SkipWhiteSpace(refresh, url_start_pos, matcher); |
| if (refresh[url_start_pos] == '=') { |
| ++url_start_pos; |
| SkipWhiteSpace(refresh, url_start_pos, matcher); |
| } else { |
| url_start_pos = pos; // e.g. "Refresh: 0; url.html" |
| } |
| } |
| |
| unsigned url_end_pos = len; |
| |
| if (refresh[url_start_pos] == '"' || refresh[url_start_pos] == '\'') { |
| UChar quotation_mark = refresh[url_start_pos]; |
| url_start_pos++; |
| while (url_end_pos > url_start_pos) { |
| url_end_pos--; |
| if (refresh[url_end_pos] == quotation_mark) |
| break; |
| } |
| |
| // https://bugs.webkit.org/show_bug.cgi?id=27868 |
| // Sometimes there is no closing quote for the end of the URL even though |
| // there was an opening quote. If we looped over the entire alleged URL |
| // string back to the opening quote, just go ahead and use everything |
| // after the opening quote instead. |
| if (url_end_pos == url_start_pos) |
| url_end_pos = len; |
| } |
| |
| url = refresh.Substring(url_start_pos, url_end_pos - url_start_pos) |
| .StripWhiteSpace(); |
| return true; |
| } |
| } |
| |
| absl::optional<base::Time> ParseDate(const String& value) { |
| return ParseDateFromNullTerminatedCharacters(value.Utf8().c_str()); |
| } |
| |
| AtomicString ExtractMIMETypeFromMediaType(const AtomicString& media_type) { |
| unsigned length = media_type.length(); |
| |
| unsigned pos = 0; |
| |
| while (pos < length) { |
| UChar c = media_type[pos]; |
| if (c != '\t' && c != ' ') |
| break; |
| ++pos; |
| } |
| |
| if (pos == length) |
| return media_type; |
| |
| unsigned type_start = pos; |
| |
| unsigned type_end = pos; |
| while (pos < length) { |
| UChar c = media_type[pos]; |
| |
| // While RFC 2616 does not allow it, other browsers allow multiple values in |
| // the HTTP media type header field, Content-Type. In such cases, the media |
| // type string passed here may contain the multiple values separated by |
| // commas. For now, this code ignores text after the first comma, which |
| // prevents it from simply failing to parse such types altogether. Later |
| // for better compatibility we could consider using the first or last valid |
| // MIME type instead. |
| // See https://bugs.webkit.org/show_bug.cgi?id=25352 for more discussion. |
| if (c == ',' || c == ';') |
| break; |
| |
| if (c != '\t' && c != ' ') |
| type_end = pos + 1; |
| |
| ++pos; |
| } |
| |
| return AtomicString( |
| media_type.GetString().Substring(type_start, type_end - type_start)); |
| } |
| |
| ContentTypeOptionsDisposition ParseContentTypeOptionsHeader( |
| const String& value) { |
| if (value.IsEmpty()) |
| return kContentTypeOptionsNone; |
| |
| Vector<String> results; |
| value.Split(",", results); |
| if (results.size() && results[0].StripWhiteSpace().LowerASCII() == "nosniff") |
| return kContentTypeOptionsNosniff; |
| return kContentTypeOptionsNone; |
| } |
| |
| static bool IsCacheHeaderSeparator(UChar c) { |
| // See RFC 2616, Section 2.2 |
| switch (c) { |
| case '(': |
| case ')': |
| case '<': |
| case '>': |
| case '@': |
| case ',': |
| case ';': |
| case ':': |
| case '\\': |
| case '"': |
| case '/': |
| case '[': |
| case ']': |
| case '?': |
| case '=': |
| case '{': |
| case '}': |
| case ' ': |
| case '\t': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool IsControlCharacter(UChar c) { |
| return c < ' ' || c == 127; |
| } |
| |
| static inline String TrimToNextSeparator(const String& str) { |
| return str.Substring(0, str.Find(IsCacheHeaderSeparator)); |
| } |
| |
| static void ParseCacheHeader(const String& header, |
| Vector<std::pair<String, String>>& result) { |
| const String safe_header = header.RemoveCharacters(IsControlCharacter); |
| wtf_size_t max = safe_header.length(); |
| for (wtf_size_t pos = 0; pos < max; /* pos incremented in loop */) { |
| wtf_size_t next_comma_position = safe_header.find(',', pos); |
| wtf_size_t next_equal_sign_position = safe_header.find('=', pos); |
| if (next_equal_sign_position != kNotFound && |
| (next_equal_sign_position < next_comma_position || |
| next_comma_position == kNotFound)) { |
| // Get directive name, parse right hand side of equal sign, then add to |
| // map |
| String directive = TrimToNextSeparator( |
| safe_header.Substring(pos, next_equal_sign_position - pos) |
| .StripWhiteSpace()); |
| pos += next_equal_sign_position - pos + 1; |
| |
| String value = safe_header.Substring(pos, max - pos).StripWhiteSpace(); |
| if (value[0] == '"') { |
| // The value is a quoted string |
| wtf_size_t next_double_quote_position = value.find('"', 1); |
| if (next_double_quote_position != kNotFound) { |
| // Store the value as a quoted string without quotes |
| result.push_back(std::pair<String, String>( |
| directive, value.Substring(1, next_double_quote_position - 1) |
| .StripWhiteSpace())); |
| pos += (safe_header.find('"', pos) - pos) + |
| next_double_quote_position + 1; |
| // Move past next comma, if there is one |
| wtf_size_t next_comma_position2 = safe_header.find(',', pos); |
| if (next_comma_position2 != kNotFound) |
| pos += next_comma_position2 - pos + 1; |
| else |
| return; // Parse error if there is anything left with no comma |
| } else { |
| // Parse error; just use the rest as the value |
| result.push_back(std::pair<String, String>( |
| directive, |
| TrimToNextSeparator( |
| value.Substring(1, value.length() - 1).StripWhiteSpace()))); |
| return; |
| } |
| } else { |
| // The value is a token until the next comma |
| wtf_size_t next_comma_position2 = value.find(','); |
| if (next_comma_position2 != kNotFound) { |
| // The value is delimited by the next comma |
| result.push_back(std::pair<String, String>( |
| directive, |
| TrimToNextSeparator( |
| value.Substring(0, next_comma_position2).StripWhiteSpace()))); |
| pos += (safe_header.find(',', pos) - pos) + 1; |
| } else { |
| // The rest is the value; no change to value needed |
| result.push_back( |
| std::pair<String, String>(directive, TrimToNextSeparator(value))); |
| return; |
| } |
| } |
| } else if (next_comma_position != kNotFound && |
| (next_comma_position < next_equal_sign_position || |
| next_equal_sign_position == kNotFound)) { |
| // Add directive to map with empty string as value |
| result.push_back(std::pair<String, String>( |
| TrimToNextSeparator( |
| safe_header.Substring(pos, next_comma_position - pos) |
| .StripWhiteSpace()), |
| "")); |
| pos += next_comma_position - pos + 1; |
| } else { |
| // Add last directive to map with empty string as value |
| result.push_back(std::pair<String, String>( |
| TrimToNextSeparator( |
| safe_header.Substring(pos, max - pos).StripWhiteSpace()), |
| "")); |
| return; |
| } |
| } |
| } |
| |
| CacheControlHeader ParseCacheControlDirectives( |
| const AtomicString& cache_control_value, |
| const AtomicString& pragma_value) { |
| CacheControlHeader cache_control_header; |
| cache_control_header.parsed = true; |
| cache_control_header.max_age = absl::nullopt; |
| cache_control_header.stale_while_revalidate = absl::nullopt; |
| |
| static const char kNoCacheDirective[] = "no-cache"; |
| static const char kNoStoreDirective[] = "no-store"; |
| static const char kMustRevalidateDirective[] = "must-revalidate"; |
| static const char kMaxAgeDirective[] = "max-age"; |
| static const char kStaleWhileRevalidateDirective[] = "stale-while-revalidate"; |
| |
| if (!cache_control_value.IsEmpty()) { |
| Vector<std::pair<String, String>> directives; |
| ParseCacheHeader(cache_control_value, directives); |
| |
| wtf_size_t directives_size = directives.size(); |
| for (wtf_size_t i = 0; i < directives_size; ++i) { |
| // RFC2616 14.9.1: A no-cache directive with a value is only meaningful |
| // for proxy caches. It should be ignored by a browser level cache. |
| if (EqualIgnoringASCIICase(directives[i].first, kNoCacheDirective) && |
| directives[i].second.IsEmpty()) { |
| cache_control_header.contains_no_cache = true; |
| } else if (EqualIgnoringASCIICase(directives[i].first, |
| kNoStoreDirective)) { |
| cache_control_header.contains_no_store = true; |
| } else if (EqualIgnoringASCIICase(directives[i].first, |
| kMustRevalidateDirective)) { |
| cache_control_header.contains_must_revalidate = true; |
| } else if (EqualIgnoringASCIICase(directives[i].first, |
| kMaxAgeDirective)) { |
| if (cache_control_header.max_age) { |
| // First max-age directive wins if there are multiple ones. |
| continue; |
| } |
| bool ok; |
| double max_age = directives[i].second.ToDouble(&ok); |
| if (ok) |
| cache_control_header.max_age = base::TimeDelta::FromSecondsD(max_age); |
| } else if (EqualIgnoringASCIICase(directives[i].first, |
| kStaleWhileRevalidateDirective)) { |
| if (cache_control_header.stale_while_revalidate) { |
| // First stale-while-revalidate directive wins if there are multiple |
| // ones. |
| continue; |
| } |
| bool ok; |
| double stale_while_revalidate = directives[i].second.ToDouble(&ok); |
| if (ok) { |
| cache_control_header.stale_while_revalidate = |
| base::TimeDelta::FromSecondsD(stale_while_revalidate); |
| } |
| } |
| } |
| } |
| |
| if (!cache_control_header.contains_no_cache) { |
| // Handle Pragma: no-cache |
| // This is deprecated and equivalent to Cache-control: no-cache |
| // Don't bother tokenizing the value, it is not important |
| cache_control_header.contains_no_cache = |
| pragma_value.LowerASCII().Contains(kNoCacheDirective); |
| } |
| return cache_control_header; |
| } |
| |
| void ParseCommaDelimitedHeader(const String& header_value, |
| CommaDelimitedHeaderSet& header_set) { |
| Vector<String> results; |
| header_value.Split(",", results); |
| for (auto& value : results) |
| header_set.insert(value.StripWhiteSpace(IsWhitespace)); |
| } |
| |
| bool ParseMultipartHeadersFromBody(const char* bytes, |
| wtf_size_t size, |
| ResourceResponse* response, |
| wtf_size_t* end) { |
| DCHECK(IsMainThread()); |
| |
| size_t headers_end_pos = |
| net::HttpUtil::LocateEndOfAdditionalHeaders(bytes, size, 0); |
| |
| if (headers_end_pos == std::string::npos) |
| return false; |
| |
| *end = static_cast<wtf_size_t>(headers_end_pos); |
| |
| // Eat headers and prepend a status line as is required by |
| // HttpResponseHeaders. |
| std::string headers("HTTP/1.1 200 OK\r\n"); |
| headers.append(bytes, headers_end_pos); |
| |
| auto response_headers = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(headers)); |
| |
| std::string mime_type, charset; |
| response_headers->GetMimeTypeAndCharset(&mime_type, &charset); |
| response->SetMimeType(WebString::FromUTF8(mime_type)); |
| response->SetTextEncodingName(WebString::FromUTF8(charset)); |
| |
| // Copy headers listed in replaceHeaders to the response. |
| for (const AtomicString& header : ReplaceHeaders()) { |
| std::string value; |
| StringUTF8Adaptor adaptor(header); |
| base::StringPiece header_string_piece(adaptor.AsStringPiece()); |
| size_t iterator = 0; |
| |
| response->ClearHttpHeaderField(header); |
| Vector<AtomicString> values; |
| while (response_headers->EnumerateHeader(&iterator, header_string_piece, |
| &value)) { |
| const AtomicString atomic_value = WebString::FromLatin1(value); |
| values.push_back(atomic_value); |
| } |
| response->AddHttpHeaderFieldWithMultipleValues(header, values); |
| } |
| return true; |
| } |
| |
| bool ParseMultipartFormHeadersFromBody(const char* bytes, |
| wtf_size_t size, |
| HTTPHeaderMap* header_fields, |
| wtf_size_t* end) { |
| DCHECK_EQ(0u, header_fields->size()); |
| |
| size_t headers_end_pos = |
| net::HttpUtil::LocateEndOfAdditionalHeaders(bytes, size, 0); |
| |
| if (headers_end_pos == std::string::npos) |
| return false; |
| |
| *end = static_cast<wtf_size_t>(headers_end_pos); |
| |
| // Eat headers and prepend a status line as is required by |
| // HttpResponseHeaders. |
| std::string headers("HTTP/1.1 200 OK\r\n"); |
| headers.append(bytes, headers_end_pos); |
| |
| auto responseHeaders = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(headers)); |
| |
| // Copy selected header fields. |
| const AtomicString* const headerNamePointers[] = { |
| &http_names::kContentDisposition, &http_names::kContentType}; |
| for (const AtomicString* headerNamePointer : headerNamePointers) { |
| StringUTF8Adaptor adaptor(*headerNamePointer); |
| size_t iterator = 0; |
| base::StringPiece headerNameStringPiece = adaptor.AsStringPiece(); |
| std::string value; |
| while (responseHeaders->EnumerateHeader(&iterator, headerNameStringPiece, |
| &value)) { |
| header_fields->Add(*headerNamePointer, WebString::FromUTF8(value)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ParseContentRangeHeaderFor206(const String& content_range, |
| int64_t* first_byte_position, |
| int64_t* last_byte_position, |
| int64_t* instance_length) { |
| return net::HttpUtil::ParseContentRangeHeaderFor206( |
| StringUTF8Adaptor(content_range).AsStringPiece(), first_byte_position, |
| last_byte_position, instance_length); |
| } |
| |
| std::unique_ptr<ServerTimingHeaderVector> ParseServerTimingHeader( |
| const String& headerValue) { |
| std::unique_ptr<ServerTimingHeaderVector> headers = |
| std::make_unique<ServerTimingHeaderVector>(); |
| |
| if (!headerValue.IsNull()) { |
| DCHECK(headerValue.Is8Bit()); |
| |
| HeaderFieldTokenizer tokenizer(headerValue); |
| while (!tokenizer.IsConsumed()) { |
| StringView name; |
| if (!tokenizer.ConsumeToken(ParsedContentType::Mode::kNormal, name)) { |
| break; |
| } |
| |
| ServerTimingHeader header(name.ToString()); |
| |
| while (tokenizer.Consume(';')) { |
| StringView parameter_name; |
| if (!tokenizer.ConsumeToken(ParsedContentType::Mode::kNormal, |
| parameter_name)) { |
| break; |
| } |
| |
| String value = ""; |
| if (tokenizer.Consume('=')) { |
| tokenizer.ConsumeTokenOrQuotedString(ParsedContentType::Mode::kNormal, |
| value); |
| tokenizer.ConsumeBeforeAnyCharMatch({',', ';'}); |
| } |
| header.SetParameter(parameter_name, value); |
| } |
| |
| headers->push_back(std::make_unique<ServerTimingHeader>(header)); |
| |
| if (!tokenizer.Consume(',')) { |
| break; |
| } |
| } |
| } |
| return headers; |
| } |
| |
| // This function is simply calling network::ParseHeaders and convert from/to |
| // blink types. It is used for navigation requests served by a ServiceWorker. It |
| // is tested by FetchResponseDataTest.ContentSecurityPolicy. |
| network::mojom::blink::ParsedHeadersPtr ParseHeaders(const String& raw_headers, |
| const KURL& url) { |
| auto headers = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(raw_headers.Latin1())); |
| return network::mojom::ConvertToBlink( |
| network::PopulateParsedHeaders(headers.get(), url)); |
| } |
| |
| // This function is simply calling network::ParseContentSecurityPolicies and |
| // converting from/to blink types. |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> |
| ParseContentSecurityPolicies( |
| const String& raw_policies, |
| network::mojom::blink::ContentSecurityPolicyType type, |
| network::mojom::blink::ContentSecurityPolicySource source, |
| const KURL& base_url) { |
| return network::mojom::ConvertToBlink(network::ParseContentSecurityPolicies( |
| raw_policies.Utf8(), type, source, base_url)); |
| } |
| |
| // This function is simply calling network::ParseContentSecurityPolicies and |
| // converting from/to blink types. |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> |
| ParseContentSecurityPolicies( |
| const String& raw_policies, |
| network::mojom::blink::ContentSecurityPolicyType type, |
| network::mojom::blink::ContentSecurityPolicySource source, |
| const SecurityOrigin& self_origin) { |
| const SecurityOrigin* precursor_origin = |
| self_origin.GetOriginOrPrecursorOriginIfOpaque(); |
| KURL base_url; |
| base_url.SetProtocol(precursor_origin->Protocol()); |
| base_url.SetHost(precursor_origin->Host()); |
| base_url.SetPort(precursor_origin->Port()); |
| return ParseContentSecurityPolicies(raw_policies, type, source, base_url); |
| } |
| |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> |
| ParseContentSecurityPolicyHeaders( |
| const ContentSecurityPolicyResponseHeaders& headers) { |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> parsed_csps = |
| ParseContentSecurityPolicies( |
| headers.ContentSecurityPolicy(), |
| network::mojom::blink::ContentSecurityPolicyType::kEnforce, |
| network::mojom::blink::ContentSecurityPolicySource::kHTTP, |
| headers.ResponseUrl()); |
| Vector<network::mojom::blink::ContentSecurityPolicyPtr> report_only_csps = |
| ParseContentSecurityPolicies( |
| headers.ContentSecurityPolicyReportOnly(), |
| network::mojom::blink::ContentSecurityPolicyType::kReport, |
| network::mojom::blink::ContentSecurityPolicySource::kHTTP, |
| headers.ResponseUrl()); |
| parsed_csps.AppendRange(std::make_move_iterator(report_only_csps.begin()), |
| std::make_move_iterator(report_only_csps.end())); |
| return parsed_csps; |
| } |
| |
| network::mojom::blink::TimingAllowOriginPtr ParseTimingAllowOrigin( |
| const String& header_value) { |
| return network::mojom::ConvertToBlink( |
| network::ParseTimingAllowOrigin(header_value.Latin1())); |
| } |
| |
| } // namespace blink |