diff options
Diffstat (limited to 'chromium/net/http')
107 files changed, 2389 insertions, 6756 deletions
diff --git a/chromium/net/http/OWNERS b/chromium/net/http/OWNERS index c01a1d53643..49c38c96624 100644 --- a/chromium/net/http/OWNERS +++ b/chromium/net/http/OWNERS @@ -2,4 +2,6 @@ per-file http_cache_*=shivanisha@chromium.org per-file transport_security_state_static.*=estark@chromium.org per-file transport_security_state_static.*=cthomp@chromium.org per-file transport_security_state_static.*=jdeblasio@chromium.org +# For automated updates +per-file transport_security_state_static.*=mdb.chrome-pki-metadata@google.com per-file *test*=file://net/quic/OWNERS # for QUIC refactors diff --git a/chromium/net/http/bidirectional_stream.cc b/chromium/net/http/bidirectional_stream.cc index cdd0aa3586e..13faebf3845 100644 --- a/chromium/net/http/bidirectional_stream.cc +++ b/chromium/net/http/bidirectional_stream.cc @@ -39,23 +39,22 @@ namespace { base::Value NetLogHeadersParams(const spdy::Http2HeaderBlock* headers, NetLogCaptureMode capture_mode) { - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetKey("headers", - ElideHttp2HeaderBlockForNetLog(*headers, capture_mode)); - return dict; + base::Value::Dict dict; + dict.Set("headers", ElideHttp2HeaderBlockForNetLog(*headers, capture_mode)); + return base::Value(std::move(dict)); } base::Value NetLogParams(const GURL& url, const std::string& method, const HttpRequestHeaders* headers, NetLogCaptureMode capture_mode) { - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetStringKey("url", url.possibly_invalid_spec()); - dict.SetStringKey("method", method); - std::string empty; - base::Value headers_param(headers->NetLogParams(empty, capture_mode)); - dict.SetKey("headers", std::move(headers_param)); - return dict; + base::Value::Dict dict; + dict.Set("url", url.possibly_invalid_spec()); + dict.Set("method", method); + base::Value headers_param( + headers->NetLogParams(/*request_line=*/std::string(), capture_mode)); + dict.Set("headers", std::move(headers_param)); + return base::Value(std::move(dict)); } } // namespace @@ -86,7 +85,6 @@ BidirectionalStream::BidirectionalStream( NetLogSourceType::BIDIRECTIONAL_STREAM)), session_(session), send_request_headers_automatically_(send_request_headers_automatically), - request_headers_sent_(false), delegate_(delegate), timer_(std::move(timer)) { DCHECK(delegate_); diff --git a/chromium/net/http/bidirectional_stream.h b/chromium/net/http/bidirectional_stream.h index 17413441445..debf81b1ed0 100644 --- a/chromium/net/http/bidirectional_stream.h +++ b/chromium/net/http/bidirectional_stream.h @@ -234,7 +234,7 @@ class NET_EXPORT BidirectionalStream : public BidirectionalStreamImpl::Delegate, bool send_request_headers_automatically_; // Whether request headers have been sent, as indicated in OnStreamReady() // callback. - bool request_headers_sent_; + bool request_headers_sent_ = false; const raw_ptr<Delegate> delegate_; diff --git a/chromium/net/http/bidirectional_stream_request_info.cc b/chromium/net/http/bidirectional_stream_request_info.cc index 18d9ba7b807..2b8cabfd09c 100644 --- a/chromium/net/http/bidirectional_stream_request_info.cc +++ b/chromium/net/http/bidirectional_stream_request_info.cc @@ -6,11 +6,7 @@ namespace net { -BidirectionalStreamRequestInfo::BidirectionalStreamRequestInfo() - : allow_early_data_override(false), - priority(LOW), - end_stream_on_headers(false), - detect_broken_connection(false) {} +BidirectionalStreamRequestInfo::BidirectionalStreamRequestInfo() = default; BidirectionalStreamRequestInfo::~BidirectionalStreamRequestInfo() = default; diff --git a/chromium/net/http/bidirectional_stream_request_info.h b/chromium/net/http/bidirectional_stream_request_info.h index 2ed8a22cc4d..51c75ec5d33 100644 --- a/chromium/net/http/bidirectional_stream_request_info.h +++ b/chromium/net/http/bidirectional_stream_request_info.h @@ -29,10 +29,10 @@ struct NET_EXPORT BidirectionalStreamRequestInfo { // Whether to allow early data to be used with this request, overriding the // early data based on the |method| semantics. - bool allow_early_data_override; + bool allow_early_data_override = false; // Request priority. - RequestPriority priority; + RequestPriority priority = LOW; // Socket tag to apply to sockets used to process this request. SocketTag socket_tag; @@ -41,11 +41,11 @@ struct NET_EXPORT BidirectionalStreamRequestInfo { HttpRequestHeaders extra_headers; // Whether END_STREAM should be set on the request HEADER frame. - bool end_stream_on_headers; + bool end_stream_on_headers = false; // Whether the implementor of the BidirectionalStream should monitor // the status of the connection for the lifetime of this stream. - bool detect_broken_connection; + bool detect_broken_connection = false; // Suggests the period the broken connection detector should use to check // the status of the connection. diff --git a/chromium/net/http/bidirectional_stream_unittest.cc b/chromium/net/http/bidirectional_stream_unittest.cc index 60c45dd446a..d39ee35be51 100644 --- a/chromium/net/http/bidirectional_stream_unittest.cc +++ b/chromium/net/http/bidirectional_stream_unittest.cc @@ -104,16 +104,7 @@ class TestDelegateBase : public BidirectionalStream::Delegate { std::unique_ptr<base::OneShotTimer> timer) : read_buf_(read_buf), read_buf_len_(read_buf_len), - timer_(std::move(timer)), - loop_(nullptr), - received_bytes_(0), - sent_bytes_(0), - error_(OK), - on_data_read_count_(0), - on_data_sent_count_(0), - do_not_start_read_(false), - run_until_completion_(false), - not_expect_callback_(false) {} + timer_(std::move(timer)) {} TestDelegateBase(const TestDelegateBase&) = delete; TestDelegateBase& operator=(const TestDelegateBase&) = delete; @@ -298,17 +289,17 @@ class TestDelegateBase : public BidirectionalStream::Delegate { spdy::Http2HeaderBlock response_headers_; spdy::Http2HeaderBlock trailers_; NextProto next_proto_; - int64_t received_bytes_; - int64_t sent_bytes_; + int64_t received_bytes_ = 0; + int64_t sent_bytes_ = 0; LoadTimingInfo load_timing_info_; - int error_; - int on_data_read_count_; - int on_data_sent_count_; - bool do_not_start_read_; - bool run_until_completion_; + int error_ = OK; + int on_data_read_count_ = 0; + int on_data_sent_count_ = 0; + bool do_not_start_read_ = false; + bool run_until_completion_ = false; // This is to ensure that delegate callback is not invoked synchronously when // calling into |stream_|. - bool not_expect_callback_; + bool not_expect_callback_ = false; CompletionOnceCallback callback_; }; diff --git a/chromium/net/http/broken_alternative_services.cc b/chromium/net/http/broken_alternative_services.cc index 9bde04df272..865277568c4 100644 --- a/chromium/net/http/broken_alternative_services.cc +++ b/chromium/net/http/broken_alternative_services.cc @@ -82,8 +82,7 @@ BrokenAlternativeServices::BrokenAlternativeServices( clock_(clock), recently_broken_alternative_services_( max_recently_broken_alternative_service_entries), - initial_delay_(kDefaultBrokenAlternativeProtocolDelay), - exponential_backoff_on_initial_delay_(true) { + initial_delay_(kDefaultBrokenAlternativeProtocolDelay) { DCHECK(delegate_); DCHECK(clock_); } diff --git a/chromium/net/http/broken_alternative_services.h b/chromium/net/http/broken_alternative_services.h index 56c8dfd6b74..72a36cbd176 100644 --- a/chromium/net/http/broken_alternative_services.h +++ b/chromium/net/http/broken_alternative_services.h @@ -229,7 +229,7 @@ class NET_EXPORT_PRIVATE BrokenAlternativeServices { // initial_delay_for_broken_alternative_service * (1 << broken_count). // Otherwise, the delay would be initial_delay_for_broken_alternative_service, // 5min, 10min.. and so on. - bool exponential_backoff_on_initial_delay_; + bool exponential_backoff_on_initial_delay_ = true; base::WeakPtrFactory<BrokenAlternativeServices> weak_ptr_factory_{this}; }; diff --git a/chromium/net/http/http_auth.cc b/chromium/net/http/http_auth.cc index bd9cbc1e57e..55240b53bc6 100644 --- a/chromium/net/http/http_auth.cc +++ b/chromium/net/http/http_auth.cc @@ -29,7 +29,7 @@ const char* const kSchemeNames[] = {kBasicAuthScheme, kDigestAuthScheme, kSpdyProxyAuthScheme, kMockAuthScheme}; } // namespace -HttpAuth::Identity::Identity() : source(IDENT_SRC_NONE), invalid(true) {} +HttpAuth::Identity::Identity() = default; // static void HttpAuth::ChooseBestChallenge( diff --git a/chromium/net/http/http_auth.h b/chromium/net/http/http_auth.h index 4a0f3b84daf..319d2d31189 100644 --- a/chromium/net/http/http_auth.h +++ b/chromium/net/http/http_auth.h @@ -128,8 +128,8 @@ class NET_EXPORT_PRIVATE HttpAuth { struct Identity { Identity(); - IdentitySource source; - bool invalid; + IdentitySource source = IDENT_SRC_NONE; + bool invalid = true; AuthCredentials credentials; }; diff --git a/chromium/net/http/http_auth_cache.cc b/chromium/net/http/http_auth_cache.cc index bbcb9798d68..81f82ea9555 100644 --- a/chromium/net/http/http_auth_cache.cc +++ b/chromium/net/http/http_auth_cache.cc @@ -236,10 +236,7 @@ bool HttpAuthCache::Entry::IsEqualForTesting(const Entry& other) const { return true; } -HttpAuthCache::Entry::Entry() - : scheme_(HttpAuth::AUTH_SCHEME_MAX), - nonce_count_(0) { -} +HttpAuthCache::Entry::Entry() = default; void HttpAuthCache::Entry::AddPath(const std::string& path) { std::string parent_dir = GetParentDirectory(path); diff --git a/chromium/net/http/http_auth_cache.h b/chromium/net/http/http_auth_cache.h index ef75a9473ee..030de45ddc1 100644 --- a/chromium/net/http/http_auth_cache.h +++ b/chromium/net/http/http_auth_cache.h @@ -98,13 +98,13 @@ class NET_EXPORT HttpAuthCache { // SchemeHostPort of the server. url::SchemeHostPort scheme_host_port_; std::string realm_; - HttpAuth::Scheme scheme_; + HttpAuth::Scheme scheme_ = HttpAuth::AUTH_SCHEME_MAX; // Identity. std::string auth_challenge_; AuthCredentials credentials_; - int nonce_count_; + int nonce_count_ = 0; // List of paths that define the realm's protection space. PathList paths_; diff --git a/chromium/net/http/http_auth_controller.cc b/chromium/net/http/http_auth_controller.cc index 90509517519..576cfb17e12 100644 --- a/chromium/net/http/http_auth_controller.cc +++ b/chromium/net/http/http_auth_controller.cc @@ -11,7 +11,6 @@ #include "base/metrics/histogram_macros.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" -#include "base/threading/platform_thread.h" #include "base/values.h" #include "net/base/auth.h" #include "net/base/url_util.h" @@ -32,102 +31,6 @@ namespace net { namespace { -enum AuthEvent { - AUTH_EVENT_START = 0, - AUTH_EVENT_REJECT, - AUTH_EVENT_MAX, -}; - -enum AuthTarget { - AUTH_TARGET_PROXY = 0, - AUTH_TARGET_SECURE_PROXY, - AUTH_TARGET_SERVER, - AUTH_TARGET_SECURE_SERVER, - AUTH_TARGET_MAX, -}; - -AuthTarget DetermineAuthTarget(const HttpAuthHandler* handler) { - switch (handler->target()) { - case HttpAuth::AUTH_PROXY: - if (GURL::SchemeIsCryptographic(handler->scheme_host_port().scheme())) - return AUTH_TARGET_SECURE_PROXY; - else - return AUTH_TARGET_PROXY; - case HttpAuth::AUTH_SERVER: - if (GURL::SchemeIsCryptographic(handler->scheme_host_port().scheme())) - return AUTH_TARGET_SECURE_SERVER; - else - return AUTH_TARGET_SERVER; - default: - NOTREACHED(); - return AUTH_TARGET_MAX; - } -} - -// Records the number of authentication events per authentication scheme. -void HistogramAuthEvent(HttpAuthHandler* handler, AuthEvent auth_event) { -#if !defined(NDEBUG) - // Note: The on-same-thread check is intentionally not using a lock - // to protect access to first_thread. This method is meant to be only - // used on the same thread, in which case there are no race conditions. If - // there are race conditions (say, a read completes during a partial write), - // the DCHECK will correctly fail. - static base::PlatformThreadId first_thread = - base::PlatformThread::CurrentId(); - DCHECK_EQ(first_thread, base::PlatformThread::CurrentId()); -#endif - - HttpAuth::Scheme auth_scheme = handler->auth_scheme(); - DCHECK(auth_scheme >= 0 && auth_scheme < HttpAuth::AUTH_SCHEME_MAX); - - // Record start and rejection events for authentication. - // - // The results map to: - // Basic Start: 0 - // Basic Reject: 1 - // Digest Start: 2 - // Digest Reject: 3 - // NTLM Start: 4 - // NTLM Reject: 5 - // Negotiate Start: 6 - // Negotiate Reject: 7 - static const int kEventBucketsEnd = - HttpAuth::AUTH_SCHEME_MAX * AUTH_EVENT_MAX; - int event_bucket = auth_scheme * AUTH_EVENT_MAX + auth_event; - DCHECK(event_bucket >= 0 && event_bucket < kEventBucketsEnd); - UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthCount", event_bucket, - kEventBucketsEnd); - - // Record the target of the authentication. - // - // The results map to: - // Basic Proxy: 0 - // Basic Secure Proxy: 1 - // Basic Server: 2 - // Basic Secure Server: 3 - // Digest Proxy: 4 - // Digest Secure Proxy: 5 - // Digest Server: 6 - // Digest Secure Server: 7 - // NTLM Proxy: 8 - // NTLM Secure Proxy: 9 - // NTLM Server: 10 - // NTLM Secure Server: 11 - // Negotiate Proxy: 12 - // Negotiate Secure Proxy: 13 - // Negotiate Server: 14 - // Negotiate Secure Server: 15 - if (auth_event != AUTH_EVENT_START) - return; - static const int kTargetBucketsEnd = - HttpAuth::AUTH_SCHEME_MAX * AUTH_TARGET_MAX; - AuthTarget auth_target = DetermineAuthTarget(handler); - int target_bucket = auth_scheme * AUTH_TARGET_MAX + auth_target; - DCHECK(target_bucket >= 0 && target_bucket < kTargetBucketsEnd); - UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthTarget", target_bucket, - kTargetBucketsEnd); -} - base::Value ControllerParamsToValue(HttpAuth::Target target, const GURL& url) { base::Value params(base::Value::Type::DICTIONARY); params.SetStringPath("target", HttpAuth::GetAuthTargetString(target)); @@ -149,8 +52,6 @@ HttpAuthController::HttpAuthController( auth_scheme_host_port_(auth_url), auth_path_(auth_url.path()), network_isolation_key_(network_isolation_key), - embedded_identity_used_(false), - default_credentials_used_(false), http_auth_cache_(http_auth_cache), http_auth_handler_factory_(http_auth_handler_factory), host_resolver_(host_resolver) { @@ -290,7 +191,6 @@ int HttpAuthController::HandleAuthChallenge( InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); break; case HttpAuth::AUTHORIZATION_RESULT_REJECT: - HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT); InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); break; case HttpAuth::AUTHORIZATION_RESULT_STALE: @@ -334,8 +234,6 @@ int HttpAuthController::HandleAuthChallenge( ssl_info, network_isolation_key_, target_, auth_scheme_host_port_, disabled_schemes_, net_log_, host_resolver_, &handler_); - if (handler_.get()) - HistogramAuthEvent(handler_.get(), AUTH_EVENT_START); } if (!handler_.get()) { @@ -370,7 +268,6 @@ int HttpAuthController::HandleAuthChallenge( if (!handler_->AllowsExplicitCredentials()) { // If the handler doesn't accept explicit credentials, then we need to // choose a different auth scheme. - HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT); InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME); } else { // Pass the challenge information back to the client. diff --git a/chromium/net/http/http_auth_controller.h b/chromium/net/http/http_auth_controller.h index 5d9b4b04b5a..6b346bd1be7 100644 --- a/chromium/net/http/http_auth_controller.h +++ b/chromium/net/http/http_auth_controller.h @@ -226,11 +226,11 @@ class NET_EXPORT_PRIVATE HttpAuthController // True if we've used the username:password embedded in the URL. This // makes sure we use the embedded identity only once for the transaction, // preventing an infinite auth restart loop. - bool embedded_identity_used_; + bool embedded_identity_used_ = false; // True if default credentials have already been tried for this transaction // in response to an HTTP authentication challenge. - bool default_credentials_used_; + bool default_credentials_used_ = false; // These two are owned by the HttpNetworkSession/IOThread, which own the // objects which reference |this|. Therefore, these raw pointers are valid diff --git a/chromium/net/http/http_auth_handler.cc b/chromium/net/http/http_auth_handler.cc index 64f7851e20d..eb138c893c3 100644 --- a/chromium/net/http/http_auth_handler.cc +++ b/chromium/net/http/http_auth_handler.cc @@ -16,12 +16,7 @@ namespace net { -HttpAuthHandler::HttpAuthHandler() - : auth_scheme_(HttpAuth::AUTH_SCHEME_MAX), - score_(-1), - target_(HttpAuth::AUTH_NONE), - properties_(-1) { -} +HttpAuthHandler::HttpAuthHandler() = default; HttpAuthHandler::~HttpAuthHandler() = default; @@ -41,8 +36,12 @@ bool HttpAuthHandler::InitFromChallenge( auth_challenge_ = challenge->challenge_text(); net_log_.BeginEvent(NetLogEventType::AUTH_HANDLER_INIT); bool ok = Init(challenge, ssl_info, network_isolation_key); - net_log_.AddEntryWithBoolParams(NetLogEventType::AUTH_HANDLER_INIT, - NetLogEventPhase::END, "succeeded", ok); + net_log_.EndEvent(NetLogEventType::AUTH_HANDLER_INIT, [&]() { + base::Value::Dict params; + params.Set("succeeded", ok); + params.Set("allows_default_credentials", AllowsDefaultCredentials()); + return base::Value(std::move(params)); + }); // Init() is expected to set the scheme, realm, score, and properties. The // realm may be empty. diff --git a/chromium/net/http/http_auth_handler.h b/chromium/net/http/http_auth_handler.h index b0b4de856d8..5348b90c000 100644 --- a/chromium/net/http/http_auth_handler.h +++ b/chromium/net/http/http_auth_handler.h @@ -208,7 +208,7 @@ class NET_EXPORT_PRIVATE HttpAuthHandler { HttpAuthChallengeTokenizer* challenge) = 0; // The auth-scheme as an enumerated value. - HttpAuth::Scheme auth_scheme_; + HttpAuth::Scheme auth_scheme_ = HttpAuth::AUTH_SCHEME_MAX; // The realm, encoded as UTF-8. Used by "basic" and "digest". std::string realm_; @@ -221,14 +221,14 @@ class NET_EXPORT_PRIVATE HttpAuthHandler { url::SchemeHostPort scheme_host_port_; // The score for this challenge. Higher numbers are better. - int score_; + int score_ = -1; // Whether this authentication request is for a proxy server, or an // origin server. - HttpAuth::Target target_; + HttpAuth::Target target_ = HttpAuth::AUTH_NONE; // A bitmask of the properties of the authentication scheme. - int properties_; + int properties_ = -1; private: void OnGenerateAuthTokenComplete(int rv); diff --git a/chromium/net/http/http_auth_handler_basic.cc b/chromium/net/http/http_auth_handler_basic.cc index 009f847dd3b..1b187631747 100644 --- a/chromium/net/http/http_auth_handler_basic.cc +++ b/chromium/net/http/http_auth_handler_basic.cc @@ -45,7 +45,7 @@ bool ParseRealm(const HttpAuthChallengeTokenizer& tokenizer, realm->clear(); HttpUtil::NameValuePairsIterator parameters = tokenizer.param_pairs(); while (parameters.GetNext()) { - if (!base::LowerCaseEqualsASCII(parameters.name_piece(), "realm")) + if (!base::EqualsCaseInsensitiveASCII(parameters.name_piece(), "realm")) continue; if (!ConvertToUtf8AndNormalize(parameters.value_piece(), kCharsetLatin1, diff --git a/chromium/net/http/http_auth_handler_digest.cc b/chromium/net/http/http_auth_handler_digest.cc index a8018dccbc5..0557b3dc6c5 100644 --- a/chromium/net/http/http_auth_handler_digest.cc +++ b/chromium/net/http/http_auth_handler_digest.cc @@ -153,10 +153,11 @@ HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallengeImpl( // for the new challenge. std::string original_realm; while (parameters.GetNext()) { - if (base::LowerCaseEqualsASCII(parameters.name_piece(), "stale")) { - if (base::LowerCaseEqualsASCII(parameters.value_piece(), "true")) + if (base::EqualsCaseInsensitiveASCII(parameters.name_piece(), "stale")) { + if (base::EqualsCaseInsensitiveASCII(parameters.value_piece(), "true")) return HttpAuth::AUTHORIZATION_RESULT_STALE; - } else if (base::LowerCaseEqualsASCII(parameters.name_piece(), "realm")) { + } else if (base::EqualsCaseInsensitiveASCII(parameters.name_piece(), + "realm")) { original_realm = parameters.value(); } } @@ -166,12 +167,9 @@ HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallengeImpl( } HttpAuthHandlerDigest::HttpAuthHandlerDigest( - int nonce_count, const NonceGenerator* nonce_generator) - : stale_(false), - algorithm_(ALGORITHM_UNSPECIFIED), - qop_(QOP_UNSPECIFIED), - nonce_count_(nonce_count), - nonce_generator_(nonce_generator) { + int nonce_count, + const NonceGenerator* nonce_generator) + : nonce_count_(nonce_count), nonce_generator_(nonce_generator) { DCHECK(nonce_generator_); } @@ -234,32 +232,32 @@ bool HttpAuthHandlerDigest::ParseChallenge( bool HttpAuthHandlerDigest::ParseChallengeProperty(base::StringPiece name, base::StringPiece value) { - if (base::LowerCaseEqualsASCII(name, "realm")) { + if (base::EqualsCaseInsensitiveASCII(name, "realm")) { std::string realm; if (!ConvertToUtf8AndNormalize(value, kCharsetLatin1, &realm)) return false; realm_ = realm; original_realm_ = std::string(value); - } else if (base::LowerCaseEqualsASCII(name, "nonce")) { + } else if (base::EqualsCaseInsensitiveASCII(name, "nonce")) { nonce_ = std::string(value); - } else if (base::LowerCaseEqualsASCII(name, "domain")) { + } else if (base::EqualsCaseInsensitiveASCII(name, "domain")) { domain_ = std::string(value); - } else if (base::LowerCaseEqualsASCII(name, "opaque")) { + } else if (base::EqualsCaseInsensitiveASCII(name, "opaque")) { opaque_ = std::string(value); - } else if (base::LowerCaseEqualsASCII(name, "stale")) { + } else if (base::EqualsCaseInsensitiveASCII(name, "stale")) { // Parse the stale boolean. - stale_ = base::LowerCaseEqualsASCII(value, "true"); - } else if (base::LowerCaseEqualsASCII(name, "algorithm")) { + stale_ = base::EqualsCaseInsensitiveASCII(value, "true"); + } else if (base::EqualsCaseInsensitiveASCII(name, "algorithm")) { // Parse the algorithm. - if (base::LowerCaseEqualsASCII(value, "md5")) { + if (base::EqualsCaseInsensitiveASCII(value, "md5")) { algorithm_ = ALGORITHM_MD5; - } else if (base::LowerCaseEqualsASCII(value, "md5-sess")) { + } else if (base::EqualsCaseInsensitiveASCII(value, "md5-sess")) { algorithm_ = ALGORITHM_MD5_SESS; } else { DVLOG(1) << "Unknown value of algorithm"; return false; // FAIL -- unsupported value of algorithm. } - } else if (base::LowerCaseEqualsASCII(name, "qop")) { + } else if (base::EqualsCaseInsensitiveASCII(name, "qop")) { // Parse the comma separated list of qops. // auth is the only supported qop, and all other values are ignored. // @@ -270,7 +268,7 @@ bool HttpAuthHandlerDigest::ParseChallengeProperty(base::StringPiece name, ','); qop_ = QOP_UNSPECIFIED; while (qop_values.GetNext()) { - if (base::LowerCaseEqualsASCII(qop_values.value_piece(), "auth")) { + if (base::EqualsCaseInsensitiveASCII(qop_values.value_piece(), "auth")) { qop_ = QOP_AUTH; break; } diff --git a/chromium/net/http/http_auth_handler_digest.h b/chromium/net/http/http_auth_handler_digest.h index dad4683147d..a170f7a81e1 100644 --- a/chromium/net/http/http_auth_handler_digest.h +++ b/chromium/net/http/http_auth_handler_digest.h @@ -174,9 +174,9 @@ class NET_EXPORT_PRIVATE HttpAuthHandlerDigest : public HttpAuthHandler { std::string nonce_; std::string domain_; std::string opaque_; - bool stale_; - DigestAlgorithm algorithm_; - QualityOfProtection qop_; + bool stale_ = false; + DigestAlgorithm algorithm_ = ALGORITHM_UNSPECIFIED; + QualityOfProtection qop_ = QOP_UNSPECIFIED; // The realm as initially encoded over-the-wire. This is used in the // challenge text, rather than |realm_| which has been converted to diff --git a/chromium/net/http/http_auth_handler_factory.h b/chromium/net/http/http_auth_handler_factory.h index 683b63f3f26..d9b9138cc91 100644 --- a/chromium/net/http/http_auth_handler_factory.h +++ b/chromium/net/http/http_auth_handler_factory.h @@ -45,7 +45,7 @@ class NET_EXPORT HttpAuthHandlerFactory { CREATE_PREEMPTIVE, // Create a handler preemptively. }; - HttpAuthHandlerFactory() : http_auth_preferences_(nullptr) {} + HttpAuthHandlerFactory() = default; HttpAuthHandlerFactory(const HttpAuthHandlerFactory&) = delete; HttpAuthHandlerFactory& operator=(const HttpAuthHandlerFactory&) = delete; @@ -163,7 +163,7 @@ class NET_EXPORT HttpAuthHandlerFactory { private: // The preferences for HTTP authentication. - raw_ptr<const HttpAuthPreferences> http_auth_preferences_; + raw_ptr<const HttpAuthPreferences> http_auth_preferences_ = nullptr; }; // The HttpAuthHandlerRegistryFactory dispatches create requests out diff --git a/chromium/net/http/http_auth_handler_mock.cc b/chromium/net/http/http_auth_handler_mock.cc index 0e7fb96c345..9e76f02a001 100644 --- a/chromium/net/http/http_auth_handler_mock.cc +++ b/chromium/net/http/http_auth_handler_mock.cc @@ -41,15 +41,7 @@ void PrintTo(const HttpAuthHandlerMock::State& state, ::std::ostream* os) { } } -HttpAuthHandlerMock::HttpAuthHandlerMock() - : state_(State::WAIT_FOR_INIT), - generate_async_(false), - generate_rv_(OK), - auth_token_(nullptr), - first_round_(true), - connection_based_(false), - allows_default_credentials_(false), - allows_explicit_credentials_(true) {} +HttpAuthHandlerMock::HttpAuthHandlerMock() = default; HttpAuthHandlerMock::~HttpAuthHandlerMock() = default; @@ -147,8 +139,7 @@ void HttpAuthHandlerMock::OnGenerateAuthToken() { std::move(callback_).Run(generate_rv_); } -HttpAuthHandlerMock::Factory::Factory() - : do_init_from_challenge_(false) { +HttpAuthHandlerMock::Factory::Factory() { // TODO(cbentzel): Default do_init_from_challenge_ to true. } diff --git a/chromium/net/http/http_auth_handler_mock.h b/chromium/net/http/http_auth_handler_mock.h index b98a92478cd..ef2be2f1686 100644 --- a/chromium/net/http/http_auth_handler_mock.h +++ b/chromium/net/http/http_auth_handler_mock.h @@ -13,6 +13,7 @@ #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "net/base/completion_once_callback.h" +#include "net/base/net_errors.h" #include "net/http/http_auth_handler.h" #include "net/http/http_auth_handler_factory.h" #include "url/gurl.h" @@ -62,7 +63,7 @@ class HttpAuthHandlerMock : public HttpAuthHandler { private: std::vector<std::unique_ptr<HttpAuthHandler>> handlers_[HttpAuth::AUTH_NUM_TARGETS]; - bool do_init_from_challenge_; + bool do_init_from_challenge_ = false; }; HttpAuthHandlerMock(); @@ -108,15 +109,15 @@ class HttpAuthHandlerMock : public HttpAuthHandler { private: void OnGenerateAuthToken(); - State state_; + State state_ = State::WAIT_FOR_INIT; CompletionOnceCallback callback_; - bool generate_async_; - int generate_rv_; - raw_ptr<std::string> auth_token_; - bool first_round_; - bool connection_based_; - bool allows_default_credentials_; - bool allows_explicit_credentials_; + bool generate_async_ = false; + int generate_rv_ = OK; + raw_ptr<std::string> auth_token_ = nullptr; + bool first_round_ = true; + bool connection_based_ = false; + bool allows_default_credentials_ = false; + bool allows_explicit_credentials_ = true; GURL request_url_; base::WeakPtrFactory<HttpAuthHandlerMock> weak_factory_{this}; }; diff --git a/chromium/net/http/http_auth_handler_negotiate.cc b/chromium/net/http/http_auth_handler_negotiate.cc index 73d2b47132e..bab8b1b551a 100644 --- a/chromium/net/http/http_auth_handler_negotiate.cc +++ b/chromium/net/http/http_auth_handler_negotiate.cc @@ -150,10 +150,6 @@ HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate( HostResolver* resolver) : auth_system_(std::move(auth_system)), resolver_(resolver), - already_called_(false), - has_credentials_(false), - auth_token_(nullptr), - next_state_(STATE_NONE), http_auth_preferences_(prefs) {} HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() = default; diff --git a/chromium/net/http/http_auth_handler_negotiate.h b/chromium/net/http/http_auth_handler_negotiate.h index ed0551f1afb..73f2c22780c 100644 --- a/chromium/net/http/http_auth_handler_negotiate.h +++ b/chromium/net/http/http_auth_handler_negotiate.h @@ -141,17 +141,17 @@ class NET_EXPORT_PRIVATE HttpAuthHandlerNegotiate : public HttpAuthHandler { std::unique_ptr<HostResolver::ResolveHostRequest> resolve_host_request_; // Things which should be consistent after first call to GenerateAuthToken. - bool already_called_; - bool has_credentials_; + bool already_called_ = false; + bool has_credentials_ = false; AuthCredentials credentials_; std::string spn_; std::string channel_bindings_; // Things which vary each round. CompletionOnceCallback callback_; - raw_ptr<std::string> auth_token_; + raw_ptr<std::string> auth_token_ = nullptr; - State next_state_; + State next_state_ = STATE_NONE; raw_ptr<const HttpAuthPreferences> http_auth_preferences_; }; diff --git a/chromium/net/http/http_basic_stream.cc b/chromium/net/http/http_basic_stream.cc index d82e153e47e..1dcf2b018f0 100644 --- a/chromium/net/http/http_basic_stream.cc +++ b/chromium/net/http/http_basic_stream.cc @@ -176,11 +176,11 @@ void HttpBasicStream::GetSSLCertRequestInfo( parser()->GetSSLCertRequestInfo(cert_request_info); } -bool HttpBasicStream::GetRemoteEndpoint(IPEndPoint* endpoint) { +int HttpBasicStream::GetRemoteEndpoint(IPEndPoint* endpoint) { if (!state_.connection() || !state_.connection()->socket()) - return false; + return ERR_SOCKET_NOT_CONNECTED; - return state_.connection()->socket()->GetPeerAddress(endpoint) == OK; + return state_.connection()->socket()->GetPeerAddress(endpoint); } void HttpBasicStream::Drain(HttpNetworkSession* session) { diff --git a/chromium/net/http/http_basic_stream.h b/chromium/net/http/http_basic_stream.h index 8c765a8e13c..2d1c561a2c0 100644 --- a/chromium/net/http/http_basic_stream.h +++ b/chromium/net/http/http_basic_stream.h @@ -87,7 +87,7 @@ class NET_EXPORT_PRIVATE HttpBasicStream : public HttpStream { void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override; - bool GetRemoteEndpoint(IPEndPoint* endpoint) override; + int GetRemoteEndpoint(IPEndPoint* endpoint) override; void Drain(HttpNetworkSession* session) override; diff --git a/chromium/net/http/http_byte_range.cc b/chromium/net/http/http_byte_range.cc index 30a5d72629c..95a46785606 100644 --- a/chromium/net/http/http_byte_range.cc +++ b/chromium/net/http/http_byte_range.cc @@ -20,9 +20,7 @@ namespace net { HttpByteRange::HttpByteRange() : first_byte_position_(kPositionNotSpecified), last_byte_position_(kPositionNotSpecified), - suffix_length_(kPositionNotSpecified), - has_computed_bounds_(false) { -} + suffix_length_(kPositionNotSpecified) {} // static HttpByteRange HttpByteRange::Bounded(int64_t first_byte_position, diff --git a/chromium/net/http/http_byte_range.h b/chromium/net/http/http_byte_range.h index f83187407b7..5b6a76c9858 100644 --- a/chromium/net/http/http_byte_range.h +++ b/chromium/net/http/http_byte_range.h @@ -64,7 +64,7 @@ class NET_EXPORT HttpByteRange { int64_t first_byte_position_; int64_t last_byte_position_; int64_t suffix_length_; - bool has_computed_bounds_; + bool has_computed_bounds_ = false; }; } // namespace net diff --git a/chromium/net/http/http_cache.cc b/chromium/net/http/http_cache.cc index 5aed1148698..9515e6f0915 100644 --- a/chromium/net/http/http_cache.cc +++ b/chromium/net/http/http_cache.cc @@ -67,14 +67,20 @@ bool g_enable_split_cache = false; const char HttpCache::kDoubleKeyPrefix[] = "_dk_"; const char HttpCache::kDoubleKeySeparator[] = " "; const char HttpCache::kSubframeDocumentResourcePrefix[] = "s_"; - -HttpCache::DefaultBackend::DefaultBackend(CacheType type, - BackendType backend_type, - const base::FilePath& path, - int max_bytes, - bool hard_reset) +const char HttpCache::kSingleKeyPrefix[] = "_sk_"; +const char HttpCache::kSingleKeySeparator[] = " "; + +HttpCache::DefaultBackend::DefaultBackend( + CacheType type, + BackendType backend_type, + scoped_refptr<disk_cache::BackendFileOperationsFactory> + file_operations_factory, + const base::FilePath& path, + int max_bytes, + bool hard_reset) : type_(type), backend_type_(backend_type), + file_operations_factory_(std::move(file_operations_factory)), path_(path), max_bytes_(max_bytes), hard_reset_(hard_reset) {} @@ -85,6 +91,7 @@ HttpCache::DefaultBackend::~DefaultBackend() = default; std::unique_ptr<HttpCache::BackendFactory> HttpCache::DefaultBackend::InMemory( int max_bytes) { return std::make_unique<DefaultBackend>(MEMORY_CACHE, CACHE_BACKEND_DEFAULT, + /*file_operations_factory=*/nullptr, base::FilePath(), max_bytes, false); } @@ -100,13 +107,13 @@ int HttpCache::DefaultBackend::CreateBackend( #if BUILDFLAG(IS_ANDROID) if (app_status_listener_) { return disk_cache::CreateCacheBackend( - type_, backend_type_, /*file_operations=*/nullptr, path_, max_bytes_, + type_, backend_type_, file_operations_factory_, path_, max_bytes_, reset_handling, net_log, backend, std::move(callback), app_status_listener_); } #endif return disk_cache::CreateCacheBackend( - type_, backend_type_, /*file_operations=*/nullptr, path_, max_bytes_, + type_, backend_type_, file_operations_factory_, path_, max_bytes_, reset_handling, net_log, backend, std::move(callback)); } @@ -120,13 +127,12 @@ void HttpCache::DefaultBackend::SetAppStatusListener( //----------------------------------------------------------------------------- HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry* entry, bool opened_in) - : disk_entry(entry), opened(opened_in) {} + : disk_entry(entry), opened(opened_in) { + DCHECK(disk_entry); +} HttpCache::ActiveEntry::~ActiveEntry() { - if (disk_entry) { - disk_entry->Close(); - disk_entry = nullptr; - } + disk_entry->Close(); } bool HttpCache::ActiveEntry::HasNoTransactions() { @@ -149,12 +155,11 @@ bool HttpCache::ActiveEntry::TransactionInReaders( // This structure keeps track of work items that are attempting to create or // open cache entries or the backend itself. struct HttpCache::PendingOp { - PendingOp() - : entry(nullptr), entry_opened(false), callback_will_delete(false) {} + PendingOp() = default; ~PendingOp() = default; - raw_ptr<disk_cache::Entry> entry; - bool entry_opened; // rather than created. + raw_ptr<disk_cache::Entry> entry = nullptr; + bool entry_opened = false; // rather than created. std::unique_ptr<disk_cache::Backend> backend; std::unique_ptr<WorkItem> writer; @@ -162,7 +167,7 @@ struct HttpCache::PendingOp { // |this| without removing it from |pending_ops_|. Note that since // OnPendingOpComplete() is static, it will not get cancelled when HttpCache // is destroyed. - bool callback_will_delete; + bool callback_will_delete = false; WorkItemList pending_queue; }; @@ -192,7 +197,6 @@ class HttpCache::WorkItem { // Calls back the transaction with the result of the operation. void NotifyTransaction(int result, ActiveEntry* entry) { - DCHECK(!entry || entry->disk_entry); if (entry_) *entry_ = entry; if (transaction_) @@ -232,25 +236,11 @@ class HttpCache::WorkItem { //----------------------------------------------------------------------------- -HttpCache::HttpCache(HttpNetworkSession* session, - std::unique_ptr<BackendFactory> backend_factory, - bool is_main_cache) - : HttpCache(std::make_unique<HttpNetworkLayer>(session), - std::move(backend_factory), - is_main_cache) { - g_init_cache = true; -} - HttpCache::HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer, - std::unique_ptr<BackendFactory> backend_factory, - bool is_main_cache) + std::unique_ptr<BackendFactory> backend_factory) : net_log_(nullptr), backend_factory_(std::move(backend_factory)), - building_backend_(false), - bypass_lock_for_test_(false), - bypass_lock_after_headers_for_test_(false), - fail_conditionalization_for_test_(false), - mode_(NORMAL), + network_layer_(std::move(network_layer)), clock_(base::DefaultClock::GetInstance()) { g_init_cache = true; @@ -262,8 +252,6 @@ HttpCache::HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer, return; net_log_ = session->net_log(); - if (!is_main_cache) - return; session->SetServerPushDelegate( std::make_unique<HttpCacheLookupManager>(this)); @@ -373,7 +361,8 @@ void HttpCache::OnExternalCacheHit( request_info.load_flags |= ~LOAD_DO_NOT_SAVE_COOKIES; } - std::string key = GenerateCacheKey(&request_info); + std::string key = + GenerateCacheKey(&request_info, /*use_single_keyed_cache=*/false); disk_cache_->OnExternalCacheHit(key); } @@ -445,6 +434,11 @@ std::string HttpCache::GetResourceURLFromHttpCacheKey(const std::string& key) { DCHECK_NE(pos, std::string::npos); pos += strlen(kDoubleKeySeparator); DCHECK_LE(pos, key.size() - 1); + } else if (pos == key.find(kSingleKeyPrefix, pos)) { + pos = key.rfind(kSingleKeySeparator); + DCHECK_NE(pos, std::string::npos); + pos += strlen(kSingleKeySeparator); + DCHECK_LE(pos, key.size() - 1); } return key.substr(pos); } @@ -464,7 +458,10 @@ Error HttpCache::CheckResourceExistence( request_info.network_isolation_key = network_isolation_key; request_info.is_subframe_document_resource = is_subframe; - std::string key = GenerateCacheKey(&request_info); + // TODO(https://crbug.com/1325315): Support looking in the single-keyed cache + // for the resource. + std::string key = + GenerateCacheKey(&request_info, /*use_single_keyed_cache=*/false); disk_cache::EntryResult entry_result = disk_cache_->OpenEntry( key, net::IDLE, base::BindOnce(&HttpCache::ResourceExistenceCheckCallback, GetWeakPtr(), @@ -478,7 +475,7 @@ Error HttpCache::CheckResourceExistence( // static std::string HttpCache::GenerateCacheKeyForTest(const HttpRequestInfo* request) { - return GenerateCacheKey(request); + return GenerateCacheKey(request, /*use_single_keyed_cache=*/false); } // static @@ -578,18 +575,35 @@ int HttpCache::GetBackendForTransaction(Transaction* transaction) { // static // Generate a key that can be used inside the cache. -std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) { - const char credential_key = (base::FeatureList::IsEnabled( - features::kSplitCacheByIncludeCredentials) && - (request->load_flags & LOAD_DO_NOT_SAVE_COOKIES)) - ? '0' - : '1'; +std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request, + bool use_single_keyed_cache) { + // The first character of the key may vary depending on whether or not sending + // credentials is permitted for this request. This only happens if the + // SplitCacheByIncludeCredentials feature is enabled, or if the single-keyed + // cache is enabled. The single-keyed cache must always be split by + // credentials in order to make coep:credentialless work safely. + const char credential_key = + ((base::FeatureList::IsEnabled( + features::kSplitCacheByIncludeCredentials) || + use_single_keyed_cache) && + (request->load_flags & LOAD_DO_NOT_SAVE_COOKIES)) + ? '0' + : '1'; const int64_t post_key = request->upload_data_stream ? request->upload_data_stream->identifier() : int64_t(0); std::string isolation_key; - if (IsSplitCacheEnabled()) { + if (use_single_keyed_cache) { + DCHECK(IsSplitCacheEnabled()); + DCHECK(!request->checksum.empty()); + DCHECK(!(request->load_flags & + (net::LOAD_VALIDATE_CACHE | net::LOAD_BYPASS_CACHE | + net::LOAD_SKIP_CACHE_VALIDATION | net::LOAD_ONLY_FROM_CACHE | + net::LOAD_DISABLE_CACHE | net::LOAD_SKIP_VARY_CHECK))); + isolation_key = base::StrCat( + {kSingleKeyPrefix, request->checksum, kSingleKeySeparator}); + } else if (IsSplitCacheEnabled()) { // Prepend the key with |kDoubleKeyPrefix| = "_dk_" to mark it as // double-keyed (and makes it an invalid url so that it doesn't get // confused with a single-keyed entry). Separate the origin and url @@ -686,7 +700,11 @@ void HttpCache::DoomMainEntryForUrl(const GURL& url, temp_info.method = "GET"; temp_info.network_isolation_key = isolation_key; temp_info.is_subframe_document_resource = is_subframe_document_resource; - std::string key = GenerateCacheKey(&temp_info); + // This method is always used for "POST" requests, which never use the + // single-keyed cache, so therefore it is correct that use_single_keyed_cache + // be false. + std::string key = + GenerateCacheKey(&temp_info, /*use_single_keyed_cache=*/false); // Defer to DoomEntry if there is an active entry, otherwise call // AsyncDoomEntry without triggering a callback. @@ -720,7 +738,6 @@ HttpCache::ActiveEntry* HttpCache::ActivateEntry(disk_cache::Entry* disk_entry, void HttpCache::DeactivateEntry(ActiveEntry* entry) { DCHECK(!entry->doomed); - DCHECK(entry->disk_entry); DCHECK(entry->SafeToDestroy()); std::string key = entry->disk_entry->GetKey(); @@ -1144,8 +1161,6 @@ void HttpCache::ProcessDoneHeadersQueue(ActiveEntry* entry) { ParallelWritingPattern parallel_writing_pattern = CanTransactionJoinExistingWriters(transaction); if (IsWritingInProgress(entry)) { - transaction->MaybeSetParallelWritingPatternForMetrics( - parallel_writing_pattern); if (parallel_writing_pattern != PARALLEL_WRITING_JOIN) { // TODO(shivanisha): Returning from here instead of checking the next // transaction in the queue because the FIFO order is maintained @@ -1172,14 +1187,10 @@ void HttpCache::ProcessDoneHeadersQueue(ActiveEntry* entry) { transaction->WriteModeTransactionAboutToBecomeReader(); auto return_val = entry->readers.insert(transaction); DCHECK(return_val.second); - transaction->MaybeSetParallelWritingPatternForMetrics( - PARALLEL_WRITING_NONE_CACHE_READ); } } else { // mode READ auto return_val = entry->readers.insert(transaction); DCHECK(return_val.second); - transaction->MaybeSetParallelWritingPatternForMetrics( - PARALLEL_WRITING_NONE_CACHE_READ); } } @@ -1197,8 +1208,6 @@ void HttpCache::AddTransactionToWriters( ParallelWritingPattern parallel_writing_pattern) { if (!entry->writers) { entry->writers = std::make_unique<Writers>(this, entry); - transaction->MaybeSetParallelWritingPatternForMetrics( - PARALLEL_WRITING_CREATE); } else { ParallelWritingPattern writers_pattern; DCHECK(entry->writers->CanAddWriters(&writers_pattern)); @@ -1345,15 +1354,8 @@ void HttpCache::OnProcessQueuedTransactions(ActiveEntry* entry) { // wait till the response is complete. If the response is not yet started, the // done_headers_queue transaction should start writing it. if (!entry->done_headers_queue.empty()) { - ParallelWritingPattern reason = PARALLEL_WRITING_NONE; - if (entry->writers && !entry->writers->CanAddWriters(&reason)) { - if (reason != PARALLEL_WRITING_NONE) { - for (auto* done_headers_transaction : entry->done_headers_queue) { - done_headers_transaction->MaybeSetParallelWritingPatternForMetrics( - reason); - } - } - } else { + ParallelWritingPattern unused_reason; + if (!entry->writers || entry->writers->CanAddWriters(&unused_reason)) { ProcessDoneHeadersQueue(entry); return; } @@ -1380,6 +1382,7 @@ void HttpCache::OnIOComplete(int result, PendingOp* pending_op) { // Anything after a Doom has to be restarted. try_restart_requests = true; } else if (item->IsValid()) { + DCHECK(pending_op->entry); key = pending_op->entry->GetKey(); entry = ActivateEntry(pending_op->entry, pending_op->entry_opened); } else { @@ -1404,8 +1407,7 @@ void HttpCache::OnIOComplete(int result, PendingOp* pending_op) { // to move the callback used to be a CancelableOnceCallback. By the way, for // this to happen the action (to cancel B) has to be synchronous to the // notification for request A. - WorkItemList pending_items; - pending_items.swap(pending_op->pending_queue); + WorkItemList pending_items = std::move(pending_op->pending_queue); DeletePendingOp(pending_op); item->NotifyTransaction(result, entry); diff --git a/chromium/net/http/http_cache.h b/chromium/net/http/http_cache.h index 6dcf81a1bed..c97adfe07fa 100644 --- a/chromium/net/http/http_cache.h +++ b/chromium/net/http/http_cache.h @@ -47,6 +47,7 @@ class ApplicationStatusListener; namespace disk_cache { class Backend; +class BackendFileOperationsFactory; class Entry; class EntryResult; } // namespace disk_cache @@ -94,10 +95,14 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory { // A default backend factory for the common use cases. class NET_EXPORT DefaultBackend : public BackendFactory { public: - // |path| is the destination for any files used by the backend. If - // |max_bytes| is zero, a default value will be calculated automatically. + // `file_operations_factory` can be null, in that case + // TrivialFileOperationsFactory is used. `path` is the destination for any + // files used by the backend. If `max_bytes` is zero, a default value + // will be calculated automatically. DefaultBackend(CacheType type, BackendType backend_type, + scoped_refptr<disk_cache::BackendFileOperationsFactory> + file_operations_factory, const base::FilePath& path, int max_bytes, bool hard_reset); @@ -119,6 +124,8 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory { private: CacheType type_; BackendType backend_type_; + const scoped_refptr<disk_cache::BackendFileOperationsFactory> + file_operations_factory_; const base::FilePath path_; int max_bytes_; bool hard_reset_; @@ -164,26 +171,10 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory { // again without validation. static const int kPrefetchReuseMins = 5; - // The disk cache is initialized lazily (by CreateTransaction) in this case. - // Provide an existing HttpNetworkSession, the cache can construct a - // network layer with a shared HttpNetworkSession in order for multiple - // network layers to share information (e.g. authentication data). The - // HttpCache takes ownership of the |backend_factory|. - // - // The HttpCache must be destroyed before the HttpNetworkSession. - // - // If |is_main_cache| is true, configures the cache to track - // information about servers supporting QUIC. - // TODO(zhongyi): remove |is_main_cache| when we get rid of cache split. - HttpCache(HttpNetworkSession* session, - std::unique_ptr<BackendFactory> backend_factory, - bool is_main_cache); - // Initialize the cache from its component parts. |network_layer| and // |backend_factory| will be destroyed when the HttpCache is. HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer, - std::unique_ptr<BackendFactory> backend_factory, - bool is_main_cache); + std::unique_ptr<BackendFactory> backend_factory); HttpCache(const HttpCache&) = delete; HttpCache& operator=(const HttpCache&) = delete; @@ -369,7 +360,7 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory { bool TransactionInReaders(Transaction* transaction) const; - raw_ptr<disk_cache::Entry> disk_entry = nullptr; + const raw_ptr<disk_cache::Entry> disk_entry; // Indicates if the disk_entry was opened or not (i.e.: created). // It is set to true when a transaction is added to an entry so that other, @@ -429,7 +420,8 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory { int GetBackendForTransaction(Transaction* transaction); // Generates the cache key for this request. - static std::string GenerateCacheKey(const HttpRequestInfo*); + static std::string GenerateCacheKey(const HttpRequestInfo*, + bool use_single_keyed_cache); // Dooms the entry selected by |key|, if it is currently in the list of active // entries. @@ -654,18 +646,22 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory { static const char kDoubleKeySeparator[]; static const char kSubframeDocumentResourcePrefix[]; + // Used for single-keyed entries if the cache is split. + static const char kSingleKeyPrefix[]; + static const char kSingleKeySeparator[]; + // Variables ---------------------------------------------------------------- raw_ptr<NetLog> net_log_; // Used when lazily constructing the disk_cache_. std::unique_ptr<BackendFactory> backend_factory_; - bool building_backend_; - bool bypass_lock_for_test_; - bool bypass_lock_after_headers_for_test_; - bool fail_conditionalization_for_test_; + bool building_backend_ = false; + bool bypass_lock_for_test_ = false; + bool bypass_lock_after_headers_for_test_ = false; + bool fail_conditionalization_for_test_ = false; - Mode mode_; + Mode mode_ = NORMAL; std::unique_ptr<HttpTransactionFactory> network_layer_; diff --git a/chromium/net/http/http_cache_lookup_manager.cc b/chromium/net/http/http_cache_lookup_manager.cc index 3f71c650c1b..6ed9cd0d36e 100644 --- a/chromium/net/http/http_cache_lookup_manager.cc +++ b/chromium/net/http/http_cache_lookup_manager.cc @@ -30,7 +30,6 @@ HttpCacheLookupManager::LookupTransaction::LookupTransaction( NetLog* net_log) : push_helper_(std::move(server_push_helper)), request_(new HttpRequestInfo()), - transaction_(nullptr), net_log_(NetLogWithSource::Make( net_log, NetLogSourceType::SERVER_PUSH_LOOKUP_TRANSACTION)) {} diff --git a/chromium/net/http/http_cache_transaction.cc b/chromium/net/http/http_cache_transaction.cc index 6bb6deaafdb..764fd4d65bc 100644 --- a/chromium/net/http/http_cache_transaction.cc +++ b/chromium/net/http/http_cache_transaction.cc @@ -19,6 +19,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/compiler_specific.h" +#include "base/containers/fixed_flat_set.h" #include "base/cxx17_backports.h" #include "base/format_macros.h" #include "base/location.h" @@ -27,13 +28,15 @@ #include "base/power_monitor/power_monitor.h" #include "base/strings/string_number_conversions.h" // For HexEncode. #include "base/strings/string_piece.h" -#include "base/strings/string_util.h" // For LowerCaseEqualsASCII. +#include "base/strings/string_util.h" // For EqualsCaseInsensitiveASCII. #include "base/task/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/clock.h" #include "base/trace_event/common/trace_event_common.h" #include "base/trace_event/trace_event.h" #include "base/values.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" #include "net/base/auth.h" #include "net/base/cache_metrics.h" #include "net/base/features.h" @@ -49,6 +52,7 @@ #include "net/http/http_log_util.h" #include "net/http/http_network_session.h" #include "net/http/http_request_info.h" +#include "net/http/http_status_code.h" #include "net/http/http_util.h" #include "net/http/webfonts_histogram.h" #include "net/log/net_log_event_type.h" @@ -87,6 +91,12 @@ enum ExternallyConditionalizedType { EXTERNALLY_CONDITIONALIZED_MAX }; +void RecordPervasivePayloadIndex(const char* histogram_name, int index) { + if (index != -1) { + base::UmaHistogramExactLinear(histogram_name, index, 101); + } +} + } // namespace #define CACHE_STATUS_HISTOGRAMS(type) \ @@ -141,7 +151,7 @@ static bool HeaderMatches(const HttpRequestHeaders& headers, HttpUtil::ValuesIterator v(header_value.begin(), header_value.end(), ','); while (v.GetNext()) { - if (base::LowerCaseEqualsASCII(v.value_piece(), search->value)) + if (base::EqualsCaseInsensitiveASCII(v.value_piece(), search->value)) return true; } } @@ -151,40 +161,8 @@ static bool HeaderMatches(const HttpRequestHeaders& headers, //----------------------------------------------------------------------------- HttpCache::Transaction::Transaction(RequestPriority priority, HttpCache* cache) - : initial_request_(nullptr), - request_(nullptr), - priority_(priority), - cache_(cache->GetWeakPtr()), - entry_(nullptr), - new_entry_(nullptr), - new_response_(nullptr), - mode_(NONE), - reading_(false), - invalid_range_(false), - truncated_(false), - is_sparse_(false), - range_requested_(false), - handling_206_(false), - cache_pending_(false), - done_headers_create_new_entry_(false), - vary_mismatch_(false), - couldnt_conditionalize_request_(false), - bypass_lock_for_test_(false), - bypass_lock_after_headers_for_test_(false), - fail_conditionalization_for_test_(false), - read_buf_len_(0), - io_buf_len_(0), - read_offset_(0), - effective_load_flags_(0), - shared_writing_error_(OK), - cache_entry_status_(CacheEntryStatus::ENTRY_UNDEFINED), - validation_cause_(VALIDATION_CAUSE_UNDEFINED), - recorded_histograms_(false), - parallel_writing_pattern_(PARALLEL_WRITING_NONE), - moved_network_transaction_to_writers_(false), - websocket_handshake_stream_base_create_helper_(nullptr), - in_do_loop_(false) { - TRACE_EVENT1("io", "HttpCacheTransaction::Transaction", "priority", + : priority_(priority), cache_(cache->GetWeakPtr()) { + TRACE_EVENT1("net", "HttpCacheTransaction::Transaction", "priority", RequestPriorityToString(priority)); static_assert(HttpCache::Transaction::kNumValidationHeaders == std::size(kValidationHeaders), @@ -195,7 +173,7 @@ HttpCache::Transaction::Transaction(RequestPriority priority, HttpCache* cache) } HttpCache::Transaction::~Transaction() { - TRACE_EVENT0("io", "HttpCacheTransaction::~Transaction"); + TRACE_EVENT0("net", "HttpCacheTransaction::~Transaction"); RecordHistograms(); // We may have to issue another IO, but we should never invoke the callback_ @@ -233,9 +211,9 @@ int HttpCache::Transaction::Start(const HttpRequestInfo* request, const NetLogWithSource& net_log) { DCHECK(request); DCHECK(!callback.is_null()); - TRACE_EVENT_WITH_FLOW1("io", "HttpCacheTransaction::Start", + TRACE_EVENT_WITH_FLOW1("net", "HttpCacheTransaction::Start", net_log.source().id, TRACE_EVENT_FLAG_FLOW_OUT, "url", - request->url); + request->url.spec()); // Ensure that we only have one asynchronous call at a time. DCHECK(callback_.is_null()); @@ -632,6 +610,10 @@ void HttpCache::Transaction::SetValidatingCannotProceed() { } void HttpCache::Transaction::WriterAboutToBeRemovedFromEntry(int result) { + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::WriterAboutToBeRemovedFromEntry", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); // Since the transaction can no longer access the network transaction, save // all network related info now. if (moved_network_transaction_to_writers_ && @@ -650,6 +632,10 @@ void HttpCache::Transaction::WriterAboutToBeRemovedFromEntry(int result) { } void HttpCache::Transaction::WriteModeTransactionAboutToBecomeReader() { + TRACE_EVENT_WITH_FLOW0( + "net", "HttpCacheTransaction::WriteModeTransactionAboutToBecomeReader", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); mode_ = READ; if (moved_network_transaction_to_writers_ && entry_->writers->network_transaction()) { @@ -657,13 +643,23 @@ void HttpCache::Transaction::WriteModeTransactionAboutToBecomeReader() { } } -void HttpCache::Transaction::MaybeSetParallelWritingPatternForMetrics( - HttpCache::ParallelWritingPattern pattern) { - // It's possible a transaction could not join existing writers and then - // creates a new writers. In that case the original reason for not being able - // to join writers should be logged. - if (parallel_writing_pattern_ == PARALLEL_WRITING_NONE) - parallel_writing_pattern_ = pattern; +bool HttpCache::Transaction::ResponseChecksumMatches( + std::unique_ptr<crypto::SecureHash> checksum) const { + DCHECK(checksum); + uint8_t result[crypto::kSHA256Length]; + checksum->Finish(result, crypto::kSHA256Length); + const std::string hex_result = base::HexEncode(result); + if (hex_result != request_->checksum) { + DVLOG(2) << "Pervasive payload checksum mismatch for \"" << request_->url + << "\": got " << hex_result << ", expected " << request_->checksum; + RecordPervasivePayloadIndex("Network.CacheTransparency.MismatchedChecksums", + request_->pervasive_payloads_index_for_logging); + return false; + } + RecordPervasivePayloadIndex( + "Network.CacheTransparency.SingleKeyedCacheIsUsed", + request_->pervasive_payloads_index_for_logging); + return true; } //----------------------------------------------------------------------------- @@ -980,6 +976,13 @@ int HttpCache::Transaction::DoLoop(int result) { case STATE_NETWORK_READ_COMPLETE: rv = DoNetworkReadComplete(rv); break; + case STATE_MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE: + DCHECK_EQ(0, rv); // Here "rv" is a count of bytes. + rv = DoMarkSingleKeyedCacheEntryUnusable(); + break; + case STATE_MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE_COMPLETE: + rv = DoMarkSingleKeyedCacheEntryUnusableComplete(rv); + break; default: NOTREACHED() << "bad state " << state; rv = ERR_FAILED; @@ -1020,7 +1023,12 @@ int HttpCache::Transaction::DoGetBackendComplete(int result) { mode_ = NONE; if (!ShouldPassThrough()) { - cache_key_ = cache_->GenerateCacheKey(request_); + // The flag LOAD_USE_SINGLE_KEYED_CACHE will have been changed to false if + // the entry was marked unusable and the transaction was restarted in + // DoCacheReadResponseComplete(), so it will no longer match the value in + // `request_`. So we pass it through explicitly. + cache_key_ = cache_->GenerateCacheKey( + request_, effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE); // Requested cache access mode. if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) { @@ -1086,7 +1094,7 @@ int HttpCache::Transaction::DoGetBackendComplete(int result) { } int HttpCache::Transaction::DoInitEntry() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoInitEntry", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoInitEntry", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(!new_entry_); @@ -1106,7 +1114,7 @@ int HttpCache::Transaction::DoInitEntry() { } int HttpCache::Transaction::DoOpenOrCreateEntry() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoOpenOrCreateEntry", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoOpenOrCreateEntry", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(!new_entry_); @@ -1114,6 +1122,9 @@ int HttpCache::Transaction::DoOpenOrCreateEntry() { cache_pending_ = true; net_log_.BeginEvent(NetLogEventType::HTTP_CACHE_OPEN_OR_CREATE_ENTRY); first_cache_access_since_ = TimeTicks::Now(); + const bool has_opened_or_created_entry = has_opened_or_created_entry_; + has_opened_or_created_entry_ = true; + record_entry_open_or_creation_time_ = false; // See if we already have something working with this cache key. new_entry_ = cache_->FindActiveEntry(cache_key_); @@ -1142,6 +1153,10 @@ int HttpCache::Transaction::DoOpenOrCreateEntry() { UpdateCacheEntryStatus(CacheEntryStatus::ENTRY_CANT_CONDITIONALIZE); } + if (!has_opened_or_created_entry) { + record_entry_open_or_creation_time_ = true; + } + // mode_ can be anything but NONE or WRITE at this point (READ, UPDATE, or // READ_WRITE). // READ, UPDATE, certain READ_WRITEs, and some methods shouldn't create, so @@ -1159,10 +1174,18 @@ int HttpCache::Transaction::DoOpenOrCreateEntry() { } int HttpCache::Transaction::DoOpenOrCreateEntryComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", - "HttpCacheTransaction::DoOpenOrCreateEntryComplete", - net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoOpenOrCreateEntryComplete", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", + (result == OK ? (new_entry_->opened ? "opened" : "created") : "failed")); + + const bool record_uma = + record_entry_open_or_creation_time_ && cache_ && + cache_->GetCurrentBackend() && + cache_->GetCurrentBackend()->GetCacheType() != MEMORY_CACHE; + record_entry_open_or_creation_time_ = false; + // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is // OK, otherwise the cache will end up with an active entry without any // transaction attached. @@ -1172,7 +1195,19 @@ int HttpCache::Transaction::DoOpenOrCreateEntryComplete(int result) { cache_pending_ = false; if (result == OK) { - if (new_entry_->opened == false) { + if (new_entry_->opened) { + if (record_uma) { + base::UmaHistogramTimes( + "HttpCache.OpenDiskEntry", + base::TimeTicks::Now() - first_cache_access_since_); + } + } else { + if (record_uma) { + base::UmaHistogramTimes( + "HttpCache.CreateDiskEntry", + base::TimeTicks::Now() - first_cache_access_since_); + } + // Entry was created so mode changes to WRITE. mode_ = WRITE; } @@ -1230,7 +1265,7 @@ int HttpCache::Transaction::DoOpenOrCreateEntryComplete(int result) { } int HttpCache::Transaction::DoDoomEntry() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoDoomEntry", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoDoomEntry", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); TransitionToState(STATE_DOOM_ENTRY_COMPLETE); @@ -1242,9 +1277,9 @@ int HttpCache::Transaction::DoDoomEntry() { } int HttpCache::Transaction::DoDoomEntryComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoDoomEntryComplete", - net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoDoomEntryComplete", net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_DOOM_ENTRY, result); cache_pending_ = false; @@ -1255,7 +1290,7 @@ int HttpCache::Transaction::DoDoomEntryComplete(int result) { } int HttpCache::Transaction::DoCreateEntry() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoCreateEntry", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoCreateEntry", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(!new_entry_); @@ -1266,9 +1301,10 @@ int HttpCache::Transaction::DoCreateEntry() { } int HttpCache::Transaction::DoCreateEntryComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoCreateEntryComplete", + TRACE_EVENT_WITH_FLOW1("net", "HttpCacheTransaction::DoCreateEntryComplete", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "result", result); // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is // OK, otherwise the cache will end up with an active entry without any // transaction attached. @@ -1308,7 +1344,7 @@ int HttpCache::Transaction::DoCreateEntryComplete(int result) { } int HttpCache::Transaction::DoAddToEntry() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoAddToEntry", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoAddToEntry", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(new_entry_); @@ -1384,9 +1420,10 @@ void HttpCache::Transaction::AddCacheLockTimeoutHandler(ActiveEntry* entry) { } int HttpCache::Transaction::DoAddToEntryComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoAddToEntryComplete", + TRACE_EVENT_WITH_FLOW1("net", "HttpCacheTransaction::DoAddToEntryComplete", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "result", result); net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY, result); const base::TimeDelta entry_lock_wait = @@ -1450,6 +1487,10 @@ int HttpCache::Transaction::DoAddToEntryComplete(int result) { } int HttpCache::Transaction::DoDoneHeadersAddToEntryComplete(int result) { + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoDoneHeadersAddToEntryComplete", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); // This transaction's response headers did not match its ActiveEntry so it // created a new ActiveEntry (new_entry_) to write to (and doomed the old // one). Now that the new entry has been created, start writing the response. @@ -1470,7 +1511,7 @@ int HttpCache::Transaction::DoDoneHeadersAddToEntryComplete(int result) { } entry_ = new_entry_; - DCHECK_NE(response_.headers->response_code(), 304); + DCHECK_NE(response_.headers->response_code(), net::HTTP_NOT_MODIFIED); DCHECK(cache_->CanTransactionWriteResponseHeaders( entry_, this, partial_ != nullptr, false)); TransitionToState(STATE_CACHE_WRITE_RESPONSE); @@ -1478,7 +1519,7 @@ int HttpCache::Transaction::DoDoneHeadersAddToEntryComplete(int result) { } int HttpCache::Transaction::DoCacheReadResponse() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoCacheReadResponse", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoCacheReadResponse", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(entry_); @@ -1493,10 +1534,11 @@ int HttpCache::Transaction::DoCacheReadResponse() { } int HttpCache::Transaction::DoCacheReadResponseComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", + TRACE_EVENT_WITH_FLOW2("net", "HttpCacheTransaction::DoCacheReadResponseComplete", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "result", result, "io_buf_len", io_buf_len_); net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_READ_INFO, result); @@ -1508,6 +1550,25 @@ int HttpCache::Transaction::DoCacheReadResponseComplete(int result) { return OnCacheReadError(result, true); } + if (response_.single_keyed_cache_entry_unusable) { + RecordPervasivePayloadIndex("Network.CacheTransparency.MarkedUnusable", + request_->pervasive_payloads_index_for_logging); + + // We've read the single keyed entry and it turned out to be unusable. Let's + // retry reading from the split cache. + if (effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE) { + DCHECK(!network_trans_); + effective_load_flags_ &= ~LOAD_USE_SINGLE_KEYED_CACHE; + DoneWithEntryForRestartWithCache(); + TransitionToState(STATE_GET_BACKEND); + return OK; + } else { + LOG(WARNING) << "Unusable flag set on non-single-keyed cache entry; " + << "possible disk corruption? (cache key: " << cache_key_ + << ")"; + } + } + // TODO(crbug.com/713354) Only get data size if there is no other transaction // currently writing the response body due to the data race mentioned in the // associated bug. @@ -1525,7 +1586,8 @@ int HttpCache::Transaction::DoCacheReadResponseComplete(int result) { // the following logic is put in place to defer such requests to the // network. The cache should not be storing multi gigabyte resources. See // http://crbug.com/89567. - if ((truncated_ || response_.headers->response_code() == 206) && + if ((truncated_ || + response_.headers->response_code() == net::HTTP_PARTIAL_CONTENT) && !range_requested_ && full_response_length > std::numeric_limits<int32_t>::max()) { DCHECK(!partial_); @@ -1575,7 +1637,7 @@ int HttpCache::Transaction::DoCacheReadResponseComplete(int result) { int HttpCache::Transaction::DoCacheWriteUpdatedPrefetchResponse(int result) { TRACE_EVENT_WITH_FLOW0( - "io", "HttpCacheTransaction::DoCacheWriteUpdatedPrefetchResponse", + "net", "HttpCacheTransaction::DoCacheWriteUpdatedPrefetchResponse", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(updated_prefetch_response_); @@ -1589,7 +1651,8 @@ int HttpCache::Transaction::DoCacheWriteUpdatedPrefetchResponse(int result) { int HttpCache::Transaction::DoCacheWriteUpdatedPrefetchResponseComplete( int result) { TRACE_EVENT_WITH_FLOW0( - "io", "HttpCacheTransaction::DoCacheWriteUpdatedPrefetchResponseComplete", + "net", + "HttpCacheTransaction::DoCacheWriteUpdatedPrefetchResponseComplete", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); updated_prefetch_response_.reset(); @@ -1598,7 +1661,7 @@ int HttpCache::Transaction::DoCacheWriteUpdatedPrefetchResponseComplete( } int HttpCache::Transaction::DoCacheDispatchValidation() { - TRACE_EVENT_WITH_FLOW0("io", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoCacheDispatchValidation", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); @@ -1694,7 +1757,7 @@ int HttpCache::Transaction::DoCompletePartialCacheValidation(int result) { int HttpCache::Transaction::DoCacheUpdateStaleWhileRevalidateTimeout() { TRACE_EVENT_WITH_FLOW0( - "io", "HttpCacheTransaction::DoCacheUpdateStaleWhileRevalidateTimeout", + "net", "HttpCacheTransaction::DoCacheUpdateStaleWhileRevalidateTimeout", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); response_.stale_revalidate_timeout = @@ -1706,7 +1769,7 @@ int HttpCache::Transaction::DoCacheUpdateStaleWhileRevalidateTimeout() { int HttpCache::Transaction::DoCacheUpdateStaleWhileRevalidateTimeoutComplete( int result) { TRACE_EVENT_WITH_FLOW0( - "io", + "net", "HttpCacheTransaction::DoCacheUpdateStaleWhileRevalidateTimeoutComplete", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); @@ -1716,7 +1779,7 @@ int HttpCache::Transaction::DoCacheUpdateStaleWhileRevalidateTimeoutComplete( } int HttpCache::Transaction::DoSendRequest() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoSendRequest", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoSendRequest", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(mode_ & WRITE || mode_ == NONE); @@ -1755,9 +1818,10 @@ int HttpCache::Transaction::DoSendRequest() { } int HttpCache::Transaction::DoSendRequestComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoSendRequestComplete", + TRACE_EVENT_WITH_FLOW1("net", "HttpCacheTransaction::DoSendRequestComplete", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "result", result); if (!cache_.get()) { TransitionToState(STATE_FINISH_HEADERS); return ERR_UNEXPECTED; @@ -1802,14 +1866,15 @@ int HttpCache::Transaction::DoSendRequestComplete(int result) { // We received the response headers and there is no error. int HttpCache::Transaction::DoSuccessfulSendRequest() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoSuccessfulSendRequest", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoSuccessfulSendRequest", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(!new_response_); const HttpResponseInfo* new_response = network_trans_->GetResponseInfo(); - if (new_response->headers->response_code() == 401 || - new_response->headers->response_code() == 407) { + if (new_response->headers->response_code() == net::HTTP_UNAUTHORIZED || + new_response->headers->response_code() == + net::HTTP_PROXY_AUTHENTICATION_REQUIRED) { SetAuthResponse(*new_response); if (!reading_) { TransitionToState(STATE_FINISH_HEADERS); @@ -1840,6 +1905,17 @@ int HttpCache::Transaction::DoSuccessfulSendRequest() { return ERR_CACHE_AUTH_FAILURE_AFTER_READ; } + // The single-keyed cache only accepts responses with code 200 or 304. + // Anything else is considered unusable. + if ((effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE) && + !(new_response->headers->response_code() == 200 || + new_response->headers->response_code() == 304)) { + // Either the new response will be written back to the cache, in which case + // it will not be reused due to the flag, or it will not be, in which case + // it will not be reused anyway. + mark_single_keyed_cache_entry_unusable_ = true; + } + new_response_ = new_response; if (!ValidatePartialResponse() && !auth_response_.headers.get()) { // Something went wrong with this request and we have to restart it. @@ -1890,7 +1966,8 @@ int HttpCache::Transaction::DoSuccessfulSendRequest() { request_->is_subframe_document_resource); } - if (new_response_->headers->response_code() == 416 && + if (new_response_->headers->response_code() == + net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE && (method_ == "GET" || method_ == "POST")) { // If there is an active entry it may be destroyed with this transaction. SetResponse(*new_response_); @@ -1900,7 +1977,8 @@ int HttpCache::Transaction::DoSuccessfulSendRequest() { // Are we expecting a response to a conditional query? if (mode_ == READ_WRITE || mode_ == UPDATE) { - if (new_response->headers->response_code() == 304 || handling_206_) { + if (new_response->headers->response_code() == net::HTTP_NOT_MODIFIED || + handling_206_) { UpdateCacheEntryStatus(CacheEntryStatus::ENTRY_VALIDATED); TransitionToState(STATE_UPDATE_CACHED_RESPONSE); return OK; @@ -1915,7 +1993,7 @@ int HttpCache::Transaction::DoSuccessfulSendRequest() { // We received 304 or 206 and we want to update the cached response headers. int HttpCache::Transaction::DoUpdateCachedResponse() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoUpdateCachedResponse", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoUpdateCachedResponse", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); int rv = OK; @@ -1930,6 +2008,12 @@ int HttpCache::Transaction::DoUpdateCachedResponse() { response_.restricted_prefetch = new_response_->restricted_prefetch; response_.ssl_info = new_response_->ssl_info; response_.dns_aliases = new_response_->dns_aliases; + + // Be careful never to set single_keyed_cache_entry_unusable back to false + // from true. + if (mark_single_keyed_cache_entry_unusable_) { + response_.single_keyed_cache_entry_unusable = true; + } if (new_response_->vary_data.is_valid()) { response_.vary_data = new_response_->vary_data; } else if (response_.vary_data.is_valid()) { @@ -1947,6 +2031,11 @@ int HttpCache::Transaction::DoUpdateCachedResponse() { } TransitionToState(STATE_UPDATE_CACHED_RESPONSE_COMPLETE); } else { + if (effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE) { + DCHECK_EQ(method_, "GET"); + ChecksumHeaders(); + } + // If we are already reading, we already updated the headers for this // request; doing it again will change Content-Length. if (!reading_) { @@ -1961,7 +2050,7 @@ int HttpCache::Transaction::DoUpdateCachedResponse() { } int HttpCache::Transaction::DoCacheWriteUpdatedResponse() { - TRACE_EVENT_WITH_FLOW0("io", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoCacheWriteUpdatedResponse", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); @@ -1971,7 +2060,7 @@ int HttpCache::Transaction::DoCacheWriteUpdatedResponse() { int HttpCache::Transaction::DoCacheWriteUpdatedResponseComplete(int result) { TRACE_EVENT_WITH_FLOW0( - "io", "HttpCacheTransaction::DoCacheWriteUpdatedResponseComplete", + "net", "HttpCacheTransaction::DoCacheWriteUpdatedResponseComplete", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); TransitionToState(STATE_UPDATE_CACHED_RESPONSE_COMPLETE); @@ -1979,10 +2068,10 @@ int HttpCache::Transaction::DoCacheWriteUpdatedResponseComplete(int result) { } int HttpCache::Transaction::DoUpdateCachedResponseComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", - "HttpCacheTransaction::DoUpdateCachedResponseComplete", - net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoUpdateCachedResponseComplete", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); if (mode_ == UPDATE) { DCHECK(!handling_206_); // We got a "not modified" response and already updated the corresponding @@ -2018,7 +2107,7 @@ int HttpCache::Transaction::DoUpdateCachedResponseComplete(int result) { } int HttpCache::Transaction::DoOverwriteCachedResponse() { - TRACE_EVENT_WITH_FLOW0("io", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoOverwriteCachedResponse", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); @@ -2033,6 +2122,11 @@ int HttpCache::Transaction::DoOverwriteCachedResponse() { SetResponse(*new_response_); + if (effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE) { + DCHECK_EQ(method_, "GET"); + ChecksumHeaders(); + } + if (method_ == "HEAD") { // This response is replacing the cached one. DoneWithEntry(false); @@ -2056,16 +2150,16 @@ int HttpCache::Transaction::DoOverwriteCachedResponse() { } int HttpCache::Transaction::DoCacheWriteResponse() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoCacheWriteResponse", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoCacheWriteResponse", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + DCHECK(response_.headers); // Invalidate any current entry with a successful response if this transaction // cannot write to this entry. This transaction then continues to read from // the network without writing to the backend. - bool is_match = response_.headers->response_code() == 304; - if (entry_ && response_.headers && - !cache_->CanTransactionWriteResponseHeaders( - entry_, this, partial_ != nullptr, is_match)) { + bool is_match = response_.headers->response_code() == net::HTTP_NOT_MODIFIED; + if (entry_ && !cache_->CanTransactionWriteResponseHeaders( + entry_, this, partial_ != nullptr, is_match)) { done_headers_create_new_entry_ = true; // The transaction needs to overwrite this response. Doom the current entry, @@ -2080,21 +2174,27 @@ int HttpCache::Transaction::DoCacheWriteResponse() { return OK; } + // Be careful never to set single_keyed_cache_entry_unusable back to false + // from true. + if (mark_single_keyed_cache_entry_unusable_) { + response_.single_keyed_cache_entry_unusable = true; + } + TransitionToState(STATE_CACHE_WRITE_RESPONSE_COMPLETE); return WriteResponseInfoToEntry(response_, truncated_); } int HttpCache::Transaction::DoCacheWriteResponseComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", - "HttpCacheTransaction::DoCacheWriteResponseComplete", - net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoCacheWriteResponseComplete", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); TransitionToState(STATE_TRUNCATE_CACHED_DATA); return OnWriteResponseInfoToEntryComplete(result); } int HttpCache::Transaction::DoTruncateCachedData() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoTruncateCachedData", + TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoTruncateCachedData", net_log().source().id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); TransitionToState(STATE_TRUNCATE_CACHED_DATA_COMPLETE); @@ -2102,14 +2202,16 @@ int HttpCache::Transaction::DoTruncateCachedData() { return OK; net_log_.BeginEvent(NetLogEventType::HTTP_CACHE_WRITE_DATA); // Truncate the stream. - return WriteToEntry(kResponseContentIndex, 0, nullptr, 0, io_callback_); + return entry_->disk_entry->WriteData(kResponseContentIndex, /*offset=*/0, + /*buf=*/nullptr, /*buf_len=*/0, + io_callback_, /*truncate=*/true); } int HttpCache::Transaction::DoTruncateCachedDataComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", - "HttpCacheTransaction::DoTruncateCachedDataComplete", - net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoTruncateCachedDataComplete", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); if (entry_) { net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_WRITE_DATA, result); @@ -2160,6 +2262,9 @@ int HttpCache::Transaction::DoHeadersPhaseCannotProceed(int result) { } int HttpCache::Transaction::DoFinishHeaders(int result) { + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoFinishHeaders", net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); if (!cache_.get() || !entry_ || result != OK) { TransitionToState(STATE_NONE); return result; @@ -2190,6 +2295,10 @@ int HttpCache::Transaction::DoFinishHeaders(int result) { } int HttpCache::Transaction::DoFinishHeadersComplete(int rv) { + TRACE_EVENT_WITH_FLOW1("net", "HttpCacheTransaction::DoFinishHeadersComplete", + net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "result", rv); entry_lock_waiting_since_ = TimeTicks(); if (rv == ERR_CACHE_RACE || rv == ERR_CACHE_LOCK_TIMEOUT) { TransitionToState(STATE_HEADERS_PHASE_CANNOT_PROCEED); @@ -2197,7 +2306,8 @@ int HttpCache::Transaction::DoFinishHeadersComplete(int rv) { } if (network_trans_ && InWriters()) { - entry_->writers->SetNetworkTransaction(this, std::move(network_trans_)); + entry_->writers->SetNetworkTransaction(this, std::move(network_trans_), + std::move(checksum_)); moved_network_transaction_to_writers_ = true; } @@ -2214,19 +2324,21 @@ int HttpCache::Transaction::DoFinishHeadersComplete(int rv) { } int HttpCache::Transaction::DoNetworkReadCacheWrite() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoNetworkReadCacheWrite", + TRACE_EVENT_WITH_FLOW2("net", "HttpCacheTransaction::DoNetworkReadCacheWrite", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "read_offset", read_offset_, "read_buf_len", + read_buf_len_); DCHECK(InWriters()); TransitionToState(STATE_NETWORK_READ_CACHE_WRITE_COMPLETE); return entry_->writers->Read(read_buf_, read_buf_len_, io_callback_, this); } int HttpCache::Transaction::DoNetworkReadCacheWriteComplete(int result) { - TRACE_EVENT_WITH_FLOW0( - "io", "HttpCacheTransaction::DoNetworkReadCacheWriteComplete", + TRACE_EVENT_WITH_FLOW1( + "net", "HttpCacheTransaction::DoNetworkReadCacheWriteComplete", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result); if (!cache_.get()) { TransitionToState(STATE_NONE); return ERR_UNEXPECTED; @@ -2253,6 +2365,8 @@ int HttpCache::Transaction::DoNetworkReadCacheWriteComplete(int result) { DCHECK(!entry_); } else { read_offset_ += result; + if (checksum_) + checksum_->Update(read_buf_->data(), result); } TransitionToState(STATE_NONE); return result; @@ -2293,17 +2407,19 @@ int HttpCache::Transaction::DoPartialNetworkReadCompleted(int result) { } int HttpCache::Transaction::DoNetworkRead() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoNetworkRead", - net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW2( + "net", "HttpCacheTransaction::DoNetworkRead", net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "read_offset", + read_offset_, "read_buf_len", read_buf_len_); TransitionToState(STATE_NETWORK_READ_COMPLETE); return network_trans_->Read(read_buf_.get(), read_buf_len_, io_callback_); } int HttpCache::Transaction::DoNetworkReadComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoNetworkReadComplete", + TRACE_EVENT_WITH_FLOW1("net", "HttpCacheTransaction::DoNetworkReadComplete", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "result", result); if (!cache_.get()) { TransitionToState(STATE_NONE); @@ -2318,9 +2434,10 @@ int HttpCache::Transaction::DoNetworkReadComplete(int result) { } int HttpCache::Transaction::DoCacheReadData() { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoCacheReadData", - net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW2( + "net", "HttpCacheTransaction::DoCacheReadData", net_log().source().id, + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "read_offset", + read_offset_, "read_buf_len", read_buf_len_); if (method_ == "HEAD") { TransitionToState(STATE_NONE); @@ -2342,9 +2459,10 @@ int HttpCache::Transaction::DoCacheReadData() { } int HttpCache::Transaction::DoCacheReadDataComplete(int result) { - TRACE_EVENT_WITH_FLOW0("io", "HttpCacheTransaction::DoCacheReadDataComplete", + TRACE_EVENT_WITH_FLOW1("net", "HttpCacheTransaction::DoCacheReadDataComplete", net_log().source().id, - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, + "result", result); net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_READ_DATA, result); @@ -2362,7 +2480,14 @@ int HttpCache::Transaction::DoCacheReadDataComplete(int result) { if (result > 0) { read_offset_ += result; + if (checksum_) + checksum_->Update(read_buf_->data(), result); } else if (result == 0) { // End of file. + if (!FinishAndCheckChecksum()) { + TransitionToState(STATE_MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE); + return result; + } + DoneWithEntry(true); } else { return OnCacheReadError(result, false); @@ -2372,6 +2497,25 @@ int HttpCache::Transaction::DoCacheReadDataComplete(int result) { return result; } +int HttpCache::Transaction::DoMarkSingleKeyedCacheEntryUnusable() { + DCHECK(effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE); + response_.single_keyed_cache_entry_unusable = true; + TransitionToState(STATE_MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE_COMPLETE); + return WriteResponseInfoToEntry(response_, /*truncated=*/false); +} + +int HttpCache::Transaction::DoMarkSingleKeyedCacheEntryUnusableComplete( + int result) { + DCHECK_NE(result, ERR_IO_PENDING); + TransitionToState(STATE_NONE); + DoneWithEntry(/*entry_is_complete=*/true); + if (result < 0) + return result; + + // Return 0 to indicate that we've finished reading the body. + return 0; +} + //----------------------------------------------------------------------------- void HttpCache::Transaction::SetRequest(const NetLogWithSource& net_log) { @@ -2532,7 +2676,8 @@ bool HttpCache::Transaction::ShouldPassThrough() { int HttpCache::Transaction::BeginCacheRead() { // We don't support any combination of LOAD_ONLY_FROM_CACHE and byte ranges. // TODO(jkarlin): Either handle this case or DCHECK. - if (response_.headers->response_code() == 206 || partial_) { + if (response_.headers->response_code() == net::HTTP_PARTIAL_CONTENT || + partial_) { NOTREACHED(); TransitionToState(STATE_FINISH_HEADERS); return ERR_CACHE_MISS; @@ -2573,8 +2718,8 @@ int HttpCache::Transaction::BeginCacheValidation() { response_.stale_revalidate_timeout.is_null(); } - if (method_ == "HEAD" && - (truncated_ || response_.headers->response_code() == 206)) { + if (method_ == "HEAD" && (truncated_ || response_.headers->response_code() == + net::HTTP_PARTIAL_CONTENT)) { DCHECK(!partial_); if (skip_validation) { DCHECK(!reading_); @@ -2638,7 +2783,7 @@ int HttpCache::Transaction::BeginCacheValidation() { if (partial_) return DoRestartPartialRequest(); - DCHECK_NE(206, response_.headers->response_code()); + DCHECK_NE(net::HTTP_PARTIAL_CONTENT, response_.headers->response_code()); } TransitionToState(STATE_SEND_REQUEST); } @@ -2648,7 +2793,8 @@ int HttpCache::Transaction::BeginCacheValidation() { int HttpCache::Transaction::BeginPartialCacheValidation() { DCHECK_EQ(mode_, READ_WRITE); - if (response_.headers->response_code() != 206 && !partial_ && !truncated_) + if (response_.headers->response_code() != net::HTTP_PARTIAL_CONTENT && + !partial_ && !truncated_) return BeginCacheValidation(); // Partial requests should not be recorded in histograms. @@ -2681,7 +2827,7 @@ int HttpCache::Transaction::ValidateEntryHeadersAndContinue() { return DoRestartPartialRequest(); } - if (response_.headers->response_code() == 206) + if (response_.headers->response_code() == net::HTTP_PARTIAL_CONTENT) is_sparse_ = true; if (!partial_->IsRequestedRangeOK()) { @@ -2718,7 +2864,7 @@ bool HttpCache::Transaction:: int HttpCache::Transaction::BeginExternallyConditionalizedRequest() { DCHECK_EQ(UPDATE, mode_); - if (response_.headers->response_code() != 200 || truncated_ || + if (response_.headers->response_code() != net::HTTP_OK || truncated_ || !ExternallyConditionalizedValidationHeadersMatchEntry()) { // The externally conditionalized request is not a validation request // for our existing cache entry. Proceed with caching disabled. @@ -2843,8 +2989,8 @@ bool HttpCache::Transaction::IsResponseConditionalizable( DCHECK(response_.headers.get()); // This only makes sense for cached 200 or 206 responses. - if (response_.headers->response_code() != 200 && - response_.headers->response_code() != 206) { + if (response_.headers->response_code() != net::HTTP_OK && + response_.headers->response_code() != net::HTTP_PARTIAL_CONTENT) { return false; } @@ -2884,7 +3030,7 @@ bool HttpCache::Transaction::ConditionalizeRequest() { if (!IsResponseConditionalizable(&etag_value, &last_modified_value)) return false; - DCHECK(response_.headers->response_code() != 206 || + DCHECK(response_.headers->response_code() != net::HTTP_PARTIAL_CONTENT || response_.headers->HasStrongValidators()); if (vary_mismatch_) { @@ -2997,7 +3143,7 @@ bool HttpCache::Transaction::ComputeUnusablePerCachingHeaders() { bool HttpCache::Transaction::ValidatePartialResponse() { const HttpResponseHeaders* headers = new_response_->headers.get(); int response_code = headers->response_code(); - bool partial_response = (response_code == 206); + bool partial_response = (response_code == net::HTTP_PARTIAL_CONTENT); handling_206_ = false; if (!entry_ || method_ != "GET") @@ -3008,11 +3154,11 @@ bool HttpCache::Transaction::ValidatePartialResponse() { // server is ok with the request, delete the entry, otherwise just ignore // this request DCHECK(!reading_); - if (partial_response || response_code == 200) { + if (partial_response || response_code == net::HTTP_OK) { DoomPartialEntry(true); mode_ = NONE; } else { - if (response_code == 304) { + if (response_code == net::HTTP_NOT_MODIFIED) { // Change the response code of the request to be 416 (Requested range // not satisfiable). SetResponse(*new_response_); @@ -3032,14 +3178,16 @@ bool HttpCache::Transaction::ValidatePartialResponse() { } // TODO(rvargas): Do we need to consider other results here?. - bool failure = response_code == 200 || response_code == 416; + bool failure = response_code == net::HTTP_OK || + response_code == net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE; if (partial_->IsCurrentRangeCached()) { // We asked for "If-None-Match: " so a 206 means a new object. if (partial_response) failure = true; - if (response_code == 304 && partial_->ResponseHeadersOK(headers)) + if (response_code == net::HTTP_NOT_MODIFIED && + partial_->ResponseHeadersOK(headers)) return true; } else { // We asked for "If-Range: " so a 206 means just another range. @@ -3057,8 +3205,9 @@ bool HttpCache::Transaction::ValidatePartialResponse() { // If the server sends 200, just store it. If it sends an error, redirect // or something else, we may store the response as long as we didn't have // anything already stored. - if (response_code == 200 || - (!truncated_ && response_code != 304 && response_code != 416)) { + if (response_code == net::HTTP_OK || + (!truncated_ && response_code != net::HTTP_NOT_MODIFIED && + response_code != net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE)) { // The server is sending something else, and we can save it. DCHECK((truncated_ && !partial_->IsLastRange()) || range_requested_); partial_.reset(); @@ -3116,9 +3265,10 @@ int HttpCache::Transaction::DoConnectedCallback() { return OK; } + auto type = response_.was_fetched_via_proxy ? TransportType::kCachedFromProxy + : TransportType::kCached; return connected_callback_.Run( - TransportInfo(TransportType::kCached, response_.remote_endpoint, ""), - io_callback_); + TransportInfo(type, response_.remote_endpoint, ""), io_callback_); } int HttpCache::Transaction::DoConnectedCallbackComplete(int result) { @@ -3165,7 +3315,7 @@ void HttpCache::Transaction::DoomInconsistentEntry() { } void HttpCache::Transaction::FixHeadersForHead() { - if (response_.headers->response_code() == 206) { + if (response_.headers->response_code() == net::HTTP_PARTIAL_CONTENT) { response_.headers->RemoveHeader("Content-Range"); response_.headers->ReplaceStatusLine("HTTP/1.1 200 OK"); } @@ -3183,8 +3333,9 @@ int HttpCache::Transaction::DoSetupEntryForRead() { if (partial_) { if (truncated_ || is_sparse_ || - (!invalid_range_ && (response_.headers->response_code() == 200 || - response_.headers->response_code() == 206))) { + (!invalid_range_ && + (response_.headers->response_code() == net::HTTP_OK || + response_.headers->response_code() == net::HTTP_PARTIAL_CONTENT))) { // We are going to return the saved response headers to the caller, so // we may need to adjust them first. In cases we are handling a range // request to a regular entry, we want the response to be a 200 or 206, @@ -3206,28 +3357,11 @@ int HttpCache::Transaction::DoSetupEntryForRead() { return OK; } -int HttpCache::Transaction::WriteToEntry(int index, - int offset, - IOBuffer* data, - int data_len, - CompletionOnceCallback callback) { - if (!entry_) - return data_len; - - int rv = 0; - if (!partial_ || !data_len) { - rv = entry_->disk_entry->WriteData(index, offset, data, data_len, - std::move(callback), true); - } else { - rv = partial_->CacheWrite(entry_->disk_entry, data, data_len, - std::move(callback)); - } - return rv; -} - int HttpCache::Transaction::WriteResponseInfoToEntry( const HttpResponseInfo& response, bool truncated) { + DCHECK(response.headers); + if (!entry_) return OK; @@ -3254,7 +3388,7 @@ int HttpCache::Transaction::WriteResponseInfoToEntry( } if (truncated) - DCHECK_EQ(200, response.headers->response_code()); + DCHECK_EQ(net::HTTP_OK, response.headers->response_code()); // When writing headers, we normally only write the non-transient headers. bool skip_transient_headers = true; @@ -3313,6 +3447,16 @@ void HttpCache::Transaction::DoneWithEntry(bool entry_is_complete) { mode_ = NONE; // switch to 'pass through' mode } +void HttpCache::Transaction::DoneWithEntryForRestartWithCache() { + if (!entry_) + return; + + cache_->DoneWithEntry(entry_, this, /*entry_is_complete=*/true, + partial_ != nullptr); + entry_ = nullptr; + new_entry_ = nullptr; +} + int HttpCache::Transaction::OnCacheReadError(int result, bool restart) { DLOG(ERROR) << "ReadData failed: " << result; const int result_for_histogram = std::max(0, -result); @@ -3530,9 +3674,6 @@ void HttpCache::Transaction::RecordHistograms() { cache_entry_status_, HttpCache::GetResourceURLFromHttpCacheKey(cache_key_)); - UMA_HISTOGRAM_ENUMERATION("HttpCache.ParallelWritingPattern", - parallel_writing_pattern_, PARALLEL_WRITING_MAX); - if (CacheEntryStatus::ENTRY_UNDEFINED == cache_entry_status_) return; @@ -3742,7 +3883,8 @@ bool HttpCache::Transaction::ShouldDisableCaching( std::string mime_type; base::CompareCase insensitive_ascii = base::CompareCase::INSENSITIVE_ASCII; if (headers.GetContentLength() > kMaxContentSize && - headers.response_code() != 304 && headers.GetMimeType(&mime_type) && + headers.response_code() != net::HTTP_NOT_MODIFIED && + headers.GetMimeType(&mime_type) && (base::StartsWith(mime_type, "video", insensitive_ascii) || base::StartsWith(mime_type, "audio", insensitive_ascii))) { disable_caching = true; @@ -3769,4 +3911,69 @@ void HttpCache::Transaction::UpdateSecurityHeadersBeforeForwarding() { return; } +void HttpCache::Transaction::ChecksumHeaders() { + DCHECK(effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE); + DCHECK(!checksum_); + checksum_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256); + // For efficiency and concision, we list known headers matching a wildcard + // explicitly rather than doing prefix matching. + constexpr auto kHeadersToInclude = base::MakeFixedFlatSet<base::StringPiece>({ + "access-control-allow-credentials", + "access-control-allow-headers", + "access-control-allow-methods", + "access-control-allow-origin", + "access-control-expose-headers", + "access-control-max-age", + "access-control-request-headers", + "access-control-request-method", + "clear-site-data", + "content-encoding", + "content-security-policy", + "content-type", + "cross-origin-embedder-policy", + "cross-origin-opener-policy", + "cross-origin-resource-policy", + "location" + "sec-websocket-accept", + "sec-websocket-extensions", + "sec-websocket-key", + "sec-websocket-protocol", + "sec-websocket-version", + "upgrade", + "vary", + }); + // Iterate the response headers looking for matches. + size_t iter = 0; + std::string name; + std::string value; + // Pairs of (lower_case_header_name, header_value). + std::vector<std::pair<std::string, std::string>> filtered_headers; + // It's good to set the initial allocation size of the vector to the + // expected size to avoid a lot of reallocations. This value was chosen as + // it is a nice round number. + filtered_headers.reserve(16); + while (response_.headers->EnumerateHeaderLines(&iter, &name, &value)) { + std::string lowered_name = base::ToLowerASCII(name); + if (kHeadersToInclude.contains(lowered_name)) { + filtered_headers.emplace_back(lowered_name, value); + } + } + std::sort(filtered_headers.begin(), filtered_headers.end()); + for (const auto& [name, value] : filtered_headers) { + checksum_->Update(name.data(), name.size()); + checksum_->Update(": ", 2); + checksum_->Update(value.data(), value.size()); + checksum_->Update("\n", 1); + } + checksum_->Update("\n", 1); +} + +bool HttpCache::Transaction::FinishAndCheckChecksum() { + if (!checksum_) + return true; + + DCHECK(effective_load_flags_ & LOAD_USE_SINGLE_KEYED_CACHE); + return ResponseChecksumMatches(std::move(checksum_)); +} + } // namespace net diff --git a/chromium/net/http/http_cache_transaction.h b/chromium/net/http/http_cache_transaction.h index cc359647d7c..88ed80876e4 100644 --- a/chromium/net/http/http_cache_transaction.h +++ b/chromium/net/http/http_cache_transaction.h @@ -24,6 +24,7 @@ #include "net/base/ip_endpoint.h" #include "net/base/load_states.h" #include "net/base/net_error_details.h" +#include "net/base/net_errors.h" #include "net/base/request_priority.h" #include "net/http/http_cache.h" #include "net/http/http_request_headers.h" @@ -35,6 +36,10 @@ #include "net/socket/connection_attempts.h" #include "net/websockets/websocket_handshake_stream_base.h" +namespace crypto { +class SecureHash; +} // namespace crypto + namespace net { class PartialData; @@ -188,18 +193,17 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // entry has finished writing. void WriteModeTransactionAboutToBecomeReader(); - // Invoked when HttpCache decides whether this transaction should join - // parallel writing or create a new writers object. This is then used - // for logging metrics. Can be called repeatedly, but doesn't change once the - // value has been set to something other than PARALLEL_WRITING_NONE. - void MaybeSetParallelWritingPatternForMetrics(ParallelWritingPattern pattern); + // True if the passed checksum calculated from the response matches the + // expected value from the HttpRequestInfo. Consumes `checksum`. + bool ResponseChecksumMatches( + std::unique_ptr<crypto::SecureHash> checksum) const; private: static const size_t kNumValidationHeaders = 2; // Helper struct to pair a header name with its value, for // headers used to validate cache entries. struct ValidationHeaders { - ValidationHeaders() : initialized(false) {} + ValidationHeaders() = default; std::string values[kNumValidationHeaders]; void Reset() { @@ -207,7 +211,7 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { for (auto& value : values) value.clear(); } - bool initialized; + bool initialized = false; }; struct NetworkTransactionInfo { @@ -287,6 +291,11 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // by the network layer (skipping the cache entirely). STATE_NETWORK_READ, STATE_NETWORK_READ_COMPLETE, + + // These states are only entered a single-keyed cache entry needs to be + // marked unusable. + STATE_MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE, + STATE_MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE_COMPLETE, }; // Used for categorizing validation triggers in histograms. @@ -365,6 +374,8 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { int DoCacheReadDataComplete(int result); int DoNetworkRead(); int DoNetworkReadComplete(int result); + int DoMarkSingleKeyedCacheEntryUnusable(); + int DoMarkSingleKeyedCacheEntryUnusableComplete(int result); // Adds time out handling while waiting to be added to entry or after headers // phase is complete. @@ -456,15 +467,6 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // Fixes the response headers to match expectations for a HEAD request. void FixHeadersForHead(); - // Called to write data to the cache entry. If the write fails, then the - // cache entry is destroyed. Future calls to this function will just do - // nothing without side-effect. Returns a network error code. - int WriteToEntry(int index, - int offset, - IOBuffer* data, - int data_len, - CompletionOnceCallback callback); - // Called to write a response to the cache entry. |truncated| indicates if the // entry should be marked as incomplete. int WriteResponseInfoToEntry(const HttpResponseInfo& response, @@ -492,6 +494,10 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // resumed or not. void DoneWithEntry(bool entry_is_complete); + // Informs the HttpCache that this transaction is done with the entry and + // resets related fields. + void DoneWithEntryForRestartWithCache(); + // Dooms the given entry so that it will not be re-used for other requests, // then calls `DoneWithEntry()`. // @@ -589,6 +595,15 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // headers. bool ShouldDisableCaching(const HttpResponseHeaders& headers) const; + // Checksum headers in `request_` for matching against the single-keyed cache + // checksum. Initializes `checksum_`. + void ChecksumHeaders(); + + // Finishes the checksum and validates that it matches the expected value. + // Returns true if the checksum matches. Returns false if it does not + // match. If no checksumming is taking place then returns true. + bool FinishAndCheckChecksum(); + // 304 revalidations of resources that set security headers and that get // forwarded might need to set these headers again to avoid being blocked. void UpdateSecurityHeadersBeforeForwarding(); @@ -596,9 +611,9 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { State next_state_{STATE_NONE}; // Initial request with which Start() was invoked. - raw_ptr<const HttpRequestInfo> initial_request_; + raw_ptr<const HttpRequestInfo> initial_request_ = nullptr; - raw_ptr<const HttpRequestInfo> request_; + raw_ptr<const HttpRequestInfo> request_ = nullptr; std::string method_; RequestPriority priority_; @@ -609,8 +624,8 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // |external_validation_| contains the value of those headers. ValidationHeaders external_validation_; base::WeakPtr<HttpCache> cache_; - raw_ptr<HttpCache::ActiveEntry> entry_; - HttpCache::ActiveEntry* new_entry_; + raw_ptr<HttpCache::ActiveEntry> entry_ = nullptr; + HttpCache::ActiveEntry* new_entry_ = nullptr; std::unique_ptr<HttpTransaction> network_trans_; CompletionOnceCallback callback_; // Consumer's callback. HttpResponseInfo response_; @@ -625,56 +640,64 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // WriteResponseInfoToEntry() resets this to absl::nullopt. std::unique_ptr<HttpResponseInfo> updated_prefetch_response_; - raw_ptr<const HttpResponseInfo> new_response_; + raw_ptr<const HttpResponseInfo> new_response_ = nullptr; std::string cache_key_; - Mode mode_; - bool reading_; // We are already reading. Never reverts to false once set. - bool invalid_range_; // We may bypass the cache for this request. - bool truncated_; // We don't have all the response data. - bool is_sparse_; // The data is stored in sparse byte ranges. - bool range_requested_; // The user requested a byte range. - bool handling_206_; // We must deal with this 206 response. - bool cache_pending_; // We are waiting for the HttpCache. + Mode mode_ = NONE; + bool reading_ = false; // We are already reading. Never reverts to + // false once set. + bool invalid_range_ = false; // We may bypass the cache for this request. + bool truncated_ = false; // We don't have all the response data. + bool is_sparse_ = false; // The data is stored in sparse byte ranges. + bool range_requested_ = false; // The user requested a byte range. + bool handling_206_ = false; // We must deal with this 206 response. + bool cache_pending_ = false; // We are waiting for the HttpCache. // Headers have been received from the network and it's not a match with the // existing entry. - bool done_headers_create_new_entry_; - - bool vary_mismatch_; // The request doesn't match the stored vary data. - bool couldnt_conditionalize_request_; - bool bypass_lock_for_test_; // A test is exercising the cache lock. - bool bypass_lock_after_headers_for_test_; // A test is exercising the cache - // lock. - bool fail_conditionalization_for_test_; // Fail ConditionalizeRequest. + bool done_headers_create_new_entry_ = false; + + bool vary_mismatch_ = false; // The request doesn't match the stored vary + // data. + bool couldnt_conditionalize_request_ = false; + bool bypass_lock_for_test_ = false; // A test is exercising the cache lock. + bool bypass_lock_after_headers_for_test_ = false; // A test is exercising the + // cache lock. + bool fail_conditionalization_for_test_ = + false; // Fail ConditionalizeRequest. + bool mark_single_keyed_cache_entry_unusable_ = + false; // Set single_keyed_cache_entry_unusable. + scoped_refptr<IOBuffer> read_buf_; // Length of the buffer passed in Read(). - int read_buf_len_; + int read_buf_len_ = 0; - int io_buf_len_; - int read_offset_; - int effective_load_flags_; + int io_buf_len_ = 0; + int read_offset_ = 0; + int effective_load_flags_ = 0; std::unique_ptr<PartialData> partial_; // We are dealing with range requests. CompletionRepeatingCallback io_callback_; // Error code to be returned from a subsequent Read call if shared writing // failed in a separate transaction. - int shared_writing_error_; + int shared_writing_error_ = OK; // Members used to track data for histograms. // This cache_entry_status_ takes precedence over // response_.cache_entry_status. In fact, response_.cache_entry_status must be // kept in sync with cache_entry_status_ (via SetResponse and // UpdateCacheEntryStatus). - HttpResponseInfo::CacheEntryStatus cache_entry_status_; - ValidationCause validation_cause_; + HttpResponseInfo::CacheEntryStatus cache_entry_status_ = + HttpResponseInfo::CacheEntryStatus::ENTRY_UNDEFINED; + ValidationCause validation_cause_ = VALIDATION_CAUSE_UNDEFINED; base::TimeTicks entry_lock_waiting_since_; base::TimeTicks first_cache_access_since_; base::TimeTicks send_request_since_; base::TimeTicks read_headers_since_; base::Time open_entry_last_used_; - bool recorded_histograms_; - ParallelWritingPattern parallel_writing_pattern_; + bool recorded_histograms_ = false; + bool has_opened_or_created_entry_ = false; + bool record_entry_open_or_creation_time_ = false; NetworkTransactionInfo network_transaction_info_; @@ -685,14 +708,19 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { // TODO(shivanisha) Note that if this transaction dies mid-way and there are // other writer transactions, no transaction then accounts for those // statistics. - bool moved_network_transaction_to_writers_; + bool moved_network_transaction_to_writers_ = false; // The helper object to use to create WebSocketHandshakeStreamBase // objects. Only relevant when establishing a WebSocket connection. // This is passed to the underlying network transaction. It is stored here in // case the transaction does not exist yet. raw_ptr<WebSocketHandshakeStreamBase::CreateHelper> - websocket_handshake_stream_base_create_helper_; + websocket_handshake_stream_base_create_helper_ = nullptr; + + // Set if we are currently calculating a checksum of the resource to validate + // it against the expected checksum for the single-keyed cache. Accumulates a + // hash of selected headers and the body of the response. + std::unique_ptr<crypto::SecureHash> checksum_; BeforeNetworkStartCallback before_network_start_callback_; ConnectedCallback connected_callback_; @@ -701,7 +729,7 @@ class NET_EXPORT_PRIVATE HttpCache::Transaction : public HttpTransaction { ResponseHeadersCallback response_headers_callback_; // True if the Transaction is currently processing the DoLoop. - bool in_do_loop_; + bool in_do_loop_ = false; base::WeakPtrFactory<Transaction> weak_factory_{this}; }; diff --git a/chromium/net/http/http_cache_unittest.cc b/chromium/net/http/http_cache_unittest.cc index ec0d6f2589c..09f1f94f4a6 100644 --- a/chromium/net/http/http_cache_unittest.cc +++ b/chromium/net/http/http_cache_unittest.cc @@ -685,9 +685,9 @@ struct Response { }; struct Context { - Context() : result(ERR_IO_PENDING) {} + Context() = default; - int result; + int result = ERR_IO_PENDING; TestCompletionCallback callback; std::unique_ptr<HttpTransaction> trans; }; @@ -1075,6 +1075,54 @@ TEST_F(HttpCacheTest, } } +// This test verifies that the callback passed to SetConnectedCallback() is +// called with the right transport type when the cached entry was originally +// fetched via proxy. +TEST_F(HttpCacheTest, SimpleGET_ConnectedCallbackOnCacheHitFromProxy) { + MockHttpCache cache; + + TransportInfo proxied_transport_info = TestTransportInfo(); + proxied_transport_info.type = TransportType::kProxied; + + { + // Populate the cache. + ScopedMockTransaction mock_transaction(kSimpleGET_Transaction); + mock_transaction.transport_info = proxied_transport_info; + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + } + + // Establish a baseline. + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + + // Load from the cache (only), observe the callback being called. + + ConnectedHandler connected_handler; + MockHttpRequest request(kSimpleGET_Transaction); + + std::unique_ptr<HttpTransaction> transaction; + EXPECT_THAT(cache.CreateTransaction(&transaction), IsOk()); + ASSERT_THAT(transaction, NotNull()); + + transaction->SetConnectedCallback(connected_handler.Callback()); + + TestCompletionCallback callback; + ASSERT_THAT( + transaction->Start(&request, callback.callback(), NetLogWithSource()), + IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + + // Still only 1 transaction for the previous request. The connected callback + // was not called by a second network transaction. + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + + // The transport info mentions both the cache and the original proxy. + TransportInfo expected_transport_info = TestTransportInfo(); + expected_transport_info.type = TransportType::kCachedFromProxy; + + EXPECT_THAT(connected_handler.transports(), + ElementsAre(expected_transport_info)); +} + class HttpCacheTest_SplitCacheFeature : public HttpCacheTest, public ::testing::WithParamInterface<bool> { @@ -1194,8 +1242,6 @@ TEST_F(HttpCacheTest, ReleaseBuffer) { TEST_F(HttpCacheTest, SimpleGETWithDiskFailures) { MockHttpCache cache; - base::HistogramTester histograms; - const std::string histogram_name = "HttpCache.ParallelWritingPattern"; cache.disk_cache()->set_soft_failures_mask(MockDiskEntry::FAIL_ALL); @@ -1212,11 +1258,6 @@ TEST_F(HttpCacheTest, SimpleGETWithDiskFailures) { EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); - - // Since the transactions were in headers phase when failed, - // PARALLEL_WRITING_NONE should be logged. - histograms.ExpectBucketCount( - histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_NONE), 2); } // Tests that disk failures after the transaction has started don't cause the @@ -1388,8 +1429,6 @@ TEST_F(HttpCacheTest, SimpleGET_LoadOnlyFromCache_Miss) { TEST_F(HttpCacheTest, SimpleGET_LoadPreferringCache_Hit) { MockHttpCache cache; - base::HistogramTester histograms; - const std::string histogram_name = "HttpCache.ParallelWritingPattern"; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); @@ -1403,12 +1442,6 @@ TEST_F(HttpCacheTest, SimpleGET_LoadPreferringCache_Hit) { EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); - - histograms.ExpectBucketCount( - histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_CREATE), 1); - histograms.ExpectBucketCount( - histogram_name, - static_cast<int>(HttpCache::PARALLEL_WRITING_NONE_CACHE_READ), 1); } TEST_F(HttpCacheTest, SimpleGET_LoadPreferringCache_Miss) { @@ -2836,8 +2869,6 @@ TEST_F(HttpCacheTest, RangeGET_ParallelValidationNoMatchDoomEntry1) { // Tests parallel validation on range requests with non-overlapping ranges. TEST_F(HttpCacheTest, RangeGET_ParallelValidationDifferentRanges) { - base::HistogramTester histograms; - const std::string histogram_name = "HttpCache.ParallelWritingPattern"; MockHttpCache cache; ScopedMockTransaction transaction(kRangeGET_TransactionOK); @@ -2937,11 +2968,6 @@ TEST_F(HttpCacheTest, RangeGET_ParallelValidationDifferentRanges) { EXPECT_EQ(1, cache.disk_cache()->create_count()); context_list.clear(); - histograms.ExpectBucketCount( - histogram_name, - static_cast<int>(HttpCache::PARALLEL_WRITING_NOT_JOIN_RANGE), 1); - histograms.ExpectBucketCount( - histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_CREATE), 2); } // Tests that a request does not create Writers when readers is not empty. @@ -3670,7 +3696,8 @@ TEST_F(HttpCacheTest, RangeGET_Enormous) { ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); auto backend_factory = std::make_unique<HttpCache::DefaultBackend>( - DISK_CACHE, CACHE_BACKEND_BLOCKFILE, temp_dir.GetPath(), 1024 * 1024, + DISK_CACHE, CACHE_BACKEND_BLOCKFILE, + /*file_operations_factory=*/nullptr, temp_dir.GetPath(), 1024 * 1024, false); MockHttpCache cache(std::move(backend_factory)); @@ -4420,8 +4447,6 @@ TEST_F(HttpCacheTest, SimpleGET_ParallelWritingCacheWriteFailed) { // like the code should disallow two POSTs without LOAD_ONLY_FROM_CACHE with the // same upload data identifier to map to the same entry. TEST_F(HttpCacheTest, SimplePOST_ParallelWritingDisallowed) { - base::HistogramTester histograms; - const std::string histogram_name = "HttpCache.ParallelWritingPattern"; MockHttpCache cache; MockTransaction transaction(kSimplePOST_Transaction); @@ -4478,18 +4503,11 @@ TEST_F(HttpCacheTest, SimplePOST_ParallelWritingDisallowed) { EXPECT_EQ(1, cache.disk_cache()->create_count()); context_list.clear(); - histograms.ExpectBucketCount( - histogram_name, - static_cast<int>(HttpCache::PARALLEL_WRITING_NOT_JOIN_METHOD_NOT_GET), 1); - histograms.ExpectBucketCount( - histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_CREATE), 1); } // Tests the case when parallel writing succeeds. Tests both idle and waiting // transactions. TEST_F(HttpCacheTest, SimpleGET_ParallelWritingSuccess) { - base::HistogramTester histograms; - const std::string histogram_name = "HttpCache.ParallelWritingPattern"; MockHttpCache cache; MockHttpRequest request(kSimpleGET_Transaction); @@ -4567,22 +4585,12 @@ TEST_F(HttpCacheTest, SimpleGET_ParallelWritingSuccess) { ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); } - // Verify metrics. context_list.clear(); - histograms.ExpectBucketCount( - histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_CREATE), 1); - histograms.ExpectBucketCount( - histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_JOIN), 2); - histograms.ExpectBucketCount( - histogram_name, - static_cast<int>(HttpCache::PARALLEL_WRITING_NOT_JOIN_READ_ONLY), 1); } // Tests the case when parallel writing involves things bigger than what cache // can store. In this case, the best we can do is re-fetch it. TEST_F(HttpCacheTest, SimpleGET_ParallelWritingHuge) { - base::HistogramTester histograms; - const std::string histogram_name = "HttpCache.ParallelWritingPattern"; MockHttpCache cache; cache.disk_cache()->set_max_file_size(10); @@ -4647,15 +4655,7 @@ TEST_F(HttpCacheTest, SimpleGET_ParallelWritingHuge) { // Sadly all of them have to hit the network EXPECT_EQ(kNumTransactions, cache.network_layer()->transaction_count()); - // Verify metrics. context_list.clear(); - histograms.ExpectBucketCount( - histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_CREATE), 1); - histograms.ExpectBucketCount( - histogram_name, - static_cast<int>(HttpCache::PARALLEL_WRITING_NOT_JOIN_TOO_BIG_FOR_CACHE), - kNumTransactions - 1); - RemoveMockTransaction(&transaction); } @@ -6765,8 +6765,6 @@ TEST_F(HttpCacheTest, SimplePOST_LoadOnlyFromCache_Miss) { TEST_F(HttpCacheTest, SimplePOST_LoadOnlyFromCache_Hit) { MockHttpCache cache; - base::HistogramTester histograms; - const std::string histogram_name = "HttpCache.ParallelWritingPattern"; // Test that we hit the cache for POST requests. @@ -6798,10 +6796,6 @@ TEST_F(HttpCacheTest, SimplePOST_LoadOnlyFromCache_Hit) { EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); - - histograms.ExpectBucketCount( - histogram_name, - static_cast<int>(HttpCache::PARALLEL_WRITING_NONE_CACHE_READ), 1); } // Test that we don't hit the cache for POST requests if there is a byte range. @@ -13506,4 +13500,298 @@ TEST_F(HttpCacheTest, SecurityHeadersAreCopiedToConditionalizedResponse) { EXPECT_EQ("cross-origin", response_corp_header); } +class HttpCacheSingleKeyedCacheTest : public HttpCacheTest { + public: + void SetUp() override { + // The single-keyed cache feature is meaningless when the split cache is not + // enabled. The //net layer doesn't care whether or not the + // "CacheTransparency" feature is enabled. + feature_list_.InitWithFeatureState( + net::features::kSplitCacheByNetworkIsolationKey, true); + HttpCacheTest::SetUp(); + } + + void RunTransactionTestForSingleKeyedCache( + HttpCache* cache, + const MockTransaction& trans_info, + const NetworkIsolationKey& network_isolation_key, + const std::string& checksum) { + MockTransaction transaction(trans_info); + transaction.load_flags |= LOAD_USE_SINGLE_KEYED_CACHE; + + AddMockTransaction(&transaction); + MockHttpRequest request(transaction); + request.network_isolation_key = network_isolation_key; + request.checksum = checksum; + + HttpResponseInfo response_info; + RunTransactionTestWithRequest(cache, transaction, request, &response_info); + } + + void RunSimpleTransactionTestForSingleKeyedCache( + HttpCache* cache, + const NetworkIsolationKey& network_isolation_key, + const std::string& checksum) { + RunTransactionTestForSingleKeyedCache(cache, kSimpleGET_Transaction, + network_isolation_key, checksum); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +constexpr char kChecksumForSimpleGET[] = + "80B4C37CEF5CFE69B4A90830282AA2BB772DC4CBC00491A219CE5F2AD75C7B58"; + +TEST_F(HttpCacheSingleKeyedCacheTest, SuccessfulGET) { + MockHttpCache cache; + // The first request adds the item to the cache. + { + const auto site_a = SchemefulSite(GURL("https://a.com/")); + RunSimpleTransactionTestForSingleKeyedCache( + cache.http_cache(), NetworkIsolationKey(site_a, site_a), + kChecksumForSimpleGET); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The second request verifies that the same cache entry is used with a + // different NetworkIsolationKey + { + const auto site_b = SchemefulSite(GURL("https://b.com/")); + RunSimpleTransactionTestForSingleKeyedCache( + cache.http_cache(), NetworkIsolationKey(site_b, site_b), + kChecksumForSimpleGET); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } +} + +TEST_F(HttpCacheSingleKeyedCacheTest, GETWithChecksumMismatch) { + MockHttpCache cache; + const auto site_a = SchemefulSite(GURL("https://a.com/")); + // The first request adds the item to the cache. + { + RunSimpleTransactionTestForSingleKeyedCache( + cache.http_cache(), NetworkIsolationKey(site_a, site_a), + "000000000000000000000000000000000000000000000000000000000000000"); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The second request doesn't use the item that was added to the single-keyed + // cache, but adds it to the split cache instead. + { + RunSimpleTransactionTestForSingleKeyedCache( + cache.http_cache(), NetworkIsolationKey(site_a, site_a), + "000000000000000000000000000000000000000000000000000000000000000"); + + // Fetches from the network again, this time into the split cache. + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); + } + + // The third request uses the split cache. + { + RunSimpleTransactionTestForSingleKeyedCache( + cache.http_cache(), NetworkIsolationKey(site_a, site_a), + "000000000000000000000000000000000000000000000000000000000000000"); + + // Fetches from the split cache. + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(3, cache.disk_cache()->open_count()); // opens both cache entries + EXPECT_EQ(2, cache.disk_cache()->create_count()); + } +} + +TEST_F(HttpCacheSingleKeyedCacheTest, GETWithBadResponseCode) { + MockHttpCache cache; + MockTransaction transaction = kSimpleGET_Transaction; + transaction.status = "HTTP/1.1 404 Not Found"; + const auto site_a = SchemefulSite(GURL("https://a.com/")); + // The first request adds the item to the single-keyed cache. + { + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_a, site_a), + kChecksumForSimpleGET); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The second request verifies that the cache entry is not re-used + // but a new one is created in the split cache. + { + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_a, site_a), + kChecksumForSimpleGET); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); + } +} + +TEST_F(HttpCacheSingleKeyedCacheTest, SuccessfulRevalidation) { + MockHttpCache cache; + MockTransaction transaction = kSimpleGET_Transaction; + // Add a cache control header to permit the entry to be cached, with max-age 0 + // to force relatidation next time. Add Etag to permit it to be revalidated. + transaction.response_headers = + "Etag: \"foo\"\n" + "Cache-Control: max-age=0\n"; + { + const auto site_a = SchemefulSite(GURL("https://a.com/")); + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_a, site_a), + kChecksumForSimpleGET); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The second request revalidates the existing entry. + { + const auto site_b = SchemefulSite(GURL("https://b.com/")); + transaction.status = "HTTP/1.1 304 Not Modified"; + // Allow it to be reused without validation next time by increasing max-age. + transaction.response_headers = + "Etag: \"foo\"\n" + "Cache-Control: max-age=10000\n"; + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_b, site_b), + kChecksumForSimpleGET); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The third request re-uses the entry. + { + const auto site_c = SchemefulSite(GURL("https://c.com/")); + // Load from cache again. + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_c, site_c), + kChecksumForSimpleGET); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } +} + +TEST_F(HttpCacheSingleKeyedCacheTest, RevalidationChangingUncheckedHeader) { + MockHttpCache cache; + MockTransaction transaction = kSimpleGET_Transaction; + // Add a cache control header to permit the entry to be cached, with max-age 0 + // to force relatidation next time. Add Etag to permit it to be revalidated. + transaction.response_headers = + "Etag: \"foo\"\n" + "Cache-Control: max-age=0\n"; + { + const auto site_a = SchemefulSite(GURL("https://a.com/")); + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_a, site_a), + kChecksumForSimpleGET); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The second request revalidates the existing entry. + { + const auto site_b = SchemefulSite(GURL("https://b.com/")); + transaction.status = "HTTP/1.1 304 Not Modified"; + // Add a response header. This is the only difference from the + // SuccessfulRevalidation test. + transaction.response_headers = + "Etag: \"foo\"\n" + "Cache-Control: max-age=10000\n" + "X-Unchecked-Header: 1\n"; + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_b, site_b), + kChecksumForSimpleGET); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The third request re-uses the entry. + { + const auto site_c = SchemefulSite(GURL("https://c.com/")); + // Load from cache again. + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_c, site_c), + kChecksumForSimpleGET); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } +} + +TEST_F(HttpCacheSingleKeyedCacheTest, RevalidationChangingCheckedHeader) { + MockHttpCache cache; + MockTransaction transaction = kSimpleGET_Transaction; + // Add a cache control header to permit the entry to be cached, with max-age 0 + // to force relatidation next time. Add Etag to permit it to be revalidated. + transaction.response_headers = + "Etag: \"foo\"\n" + "Cache-Control: max-age=0\n"; + { + const auto site_a = SchemefulSite(GURL("https://a.com/")); + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_a, site_a), + kChecksumForSimpleGET); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The second request marks the single-keyed cache entry unusable because the + // checksum no longer matches. + { + const auto site_b = SchemefulSite(GURL("https://b.com/")); + transaction.status = "HTTP/1.1 304 Not Modified"; + // Add the "Vary" response header. + transaction.response_headers = + "Etag: \"foo\"\n" + "Cache-Control: max-age=10000\n" + "Vary: Cookie\n"; + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_b, site_b), + kChecksumForSimpleGET); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + } + + // The third request has to go to the network because the single-keyed cache + // entry is unusable. It writes a new entry to the split cache. + { + const auto site_c = SchemefulSite(GURL("https://c.com/")); + RunTransactionTestForSingleKeyedCache(cache.http_cache(), transaction, + NetworkIsolationKey(site_c, site_c), + kChecksumForSimpleGET); + + EXPECT_EQ(3, cache.network_layer()->transaction_count()); + EXPECT_EQ(2, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); + } +} + } // namespace net diff --git a/chromium/net/http/http_cache_writers.cc b/chromium/net/http/http_cache_writers.cc index 5842b09cf3d..9a9b3b9647a 100644 --- a/chromium/net/http/http_cache_writers.cc +++ b/chromium/net/http/http_cache_writers.cc @@ -15,10 +15,13 @@ #include "base/logging.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_task_runner_handle.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" #include "net/base/net_errors.h" #include "net/disk_cache/disk_cache.h" #include "net/http/http_cache_transaction.h" #include "net/http/http_response_info.h" +#include "net/http/http_status_code.h" #include "net/http/partial_data.h" namespace net { @@ -44,8 +47,9 @@ bool IsValidResponseForWriter(bool is_partial, // Return false if the response code sent by the server is garbled. // Both 200 and 304 are valid since concurrent writing is supported. - if (!is_partial && (response_info->headers->response_code() != 200 && - response_info->headers->response_code() != 304)) { + if (!is_partial && + (response_info->headers->response_code() != net::HTTP_OK && + response_info->headers->response_code() != net::HTTP_NOT_MODIFIED)) { return false; } @@ -65,7 +69,10 @@ HttpCache::Writers::TransactionInfo::TransactionInfo(const TransactionInfo&) = default; HttpCache::Writers::Writers(HttpCache* cache, HttpCache::ActiveEntry* entry) - : cache_(cache), entry_(entry) {} + : cache_(cache), entry_(entry) { + DCHECK(cache_); + DCHECK(entry_); +} HttpCache::Writers::~Writers() = default; @@ -161,12 +168,15 @@ void HttpCache::Writers::AddTransaction( void HttpCache::Writers::SetNetworkTransaction( Transaction* transaction, - std::unique_ptr<HttpTransaction> network_transaction) { + std::unique_ptr<HttpTransaction> network_transaction, + std::unique_ptr<crypto::SecureHash> checksum) { DCHECK_EQ(1u, all_writers_.count(transaction)); DCHECK(network_transaction); DCHECK(!network_transaction_); network_transaction_ = std::move(network_transaction); network_transaction_->SetPriority(priority_); + DCHECK(!checksum_); + checksum_ = std::move(checksum); } void HttpCache::Writers::ResetNetworkTransaction() { @@ -329,7 +339,6 @@ HttpCache::Writers::WaitingForRead::WaitingForRead( CompletionOnceCallback consumer_callback) : read_buf(std::move(buf)), read_buf_len(len), - write_len(0), callback(std::move(consumer_callback)) { DCHECK(read_buf); DCHECK_GT(len, 0); @@ -361,6 +370,14 @@ int HttpCache::Writers::DoLoop(int result) { case State::CACHE_WRITE_DATA_COMPLETE: rv = DoCacheWriteDataComplete(rv); break; + case State::MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE: + // `rv` is bytes here. + DCHECK_EQ(0, rv); + rv = DoMarkSingleKeyedCacheEntryUnusable(); + break; + case State::MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE_COMPLETE: + rv = DoMarkSingleKeyedCacheEntryUnusableComplete(rv); + break; case State::UNSET: NOTREACHED() << "bad state"; rv = ERR_FAILED; @@ -488,6 +505,20 @@ int HttpCache::Writers::DoCacheWriteData(int num_bytes) { int HttpCache::Writers::DoCacheWriteDataComplete(int result) { DCHECK(!all_writers_.empty()); next_state_ = State::NONE; + if (checksum_) { + if (write_len_ > 0) { + checksum_->Update(read_buf_->data(), write_len_); + } else { + // The write to the cache may have failed if result < 0, but even in that + // case we want to check whether the data we've read from the network is + // valid or not. + CHECK(active_transaction_); + if (!active_transaction_->ResponseChecksumMatches(std::move(checksum_))) { + next_state_ = State::MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE; + } + } + } + if (result != write_len_) { // Note that it is possible for cache write to fail if the size of the file // exceeds the per-file limit. @@ -501,6 +532,40 @@ int HttpCache::Writers::DoCacheWriteDataComplete(int result) { return result; } +int HttpCache::Writers::DoMarkSingleKeyedCacheEntryUnusable() { + // `response_info_truncation_` is not actually truncated. + // TODO(ricea): Maybe change the name of the member? + response_info_truncation_.single_keyed_cache_entry_unusable = true; + next_state_ = State::MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE_COMPLETE; + + // Update cache metadata. This is a subset of what + // HttpCache::Transaction::WriteResponseInfoToEntry does. + auto data = base::MakeRefCounted<PickledIOBuffer>(); + response_info_truncation_.Persist(data->pickle(), + /*skip_transient_headers=*/true, + /*response_truncated=*/false); + data->Done(); + io_buf_len_ = data->pickle()->size(); + CompletionOnceCallback io_callback = base::BindOnce( + &HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); + return entry_->disk_entry->WriteData(kResponseInfoIndex, 0, data.get(), + io_buf_len_, std::move(io_callback), + true); +} + +int HttpCache::Writers::DoMarkSingleKeyedCacheEntryUnusableComplete( + int result) { + next_state_ = State::NONE; + + if (result < 0) { + OnCacheWriteFailure(); + } + + // DoLoop() wants the size of the data write, not the size of the metadata + // write. + return write_len_; +} + void HttpCache::Writers::OnDataReceived(int result) { DCHECK(!all_writers_.empty()); diff --git a/chromium/net/http/http_cache_writers.h b/chromium/net/http/http_cache_writers.h index 4909ad652a1..7de8dde0c7c 100644 --- a/chromium/net/http/http_cache_writers.h +++ b/chromium/net/http/http_cache_writers.h @@ -14,6 +14,10 @@ #include "net/http/http_cache.h" #include "net/http/http_response_info.h" +namespace crypto { +class SecureHash; +} + namespace net { class HttpResponseInfo; @@ -138,10 +142,12 @@ class NET_EXPORT_PRIVATE HttpCache::Writers { LoadState GetLoadState() const; // Sets the network transaction argument to |network_transaction_|. Must be - // invoked before Read can be invoked. + // invoked before Read can be invoked. If |checksum| is set it will be + // validated and the cache entry will be marked unusable if it doesn't match. void SetNetworkTransaction( Transaction* transaction, - std::unique_ptr<HttpTransaction> network_transaction); + std::unique_ptr<HttpTransaction> network_transaction, + std::unique_ptr<crypto::SecureHash> checksum); // Resets the network transaction to nullptr. Required for range requests as // they might use the current network transaction only for part of the @@ -163,6 +169,8 @@ class NET_EXPORT_PRIVATE HttpCache::Writers { NETWORK_READ_COMPLETE, CACHE_WRITE_DATA, CACHE_WRITE_DATA_COMPLETE, + MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE, + MARK_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE_COMPLETE, }; // These transactions are waiting on Read. After the active transaction @@ -171,7 +179,7 @@ class NET_EXPORT_PRIVATE HttpCache::Writers { struct WaitingForRead { scoped_refptr<IOBuffer> read_buf; int read_buf_len; - int write_len; + int write_len = 0; CompletionOnceCallback callback; WaitingForRead(scoped_refptr<IOBuffer> read_buf, int len, @@ -192,6 +200,8 @@ class NET_EXPORT_PRIVATE HttpCache::Writers { int DoNetworkReadComplete(int result); int DoCacheWriteData(int num_bytes); int DoCacheWriteDataComplete(int result); + int DoMarkSingleKeyedCacheEntryUnusable(); + int DoMarkSingleKeyedCacheEntryUnusableComplete(int result); // Helper functions for callback. void OnNetworkReadFailure(int result); @@ -236,10 +246,10 @@ class NET_EXPORT_PRIVATE HttpCache::Writers { // True if only reading from network and not writing to cache. bool network_read_only_ = false; - raw_ptr<HttpCache> cache_ = nullptr; + raw_ptr<HttpCache> const cache_ = nullptr; // Owner of |this|. - raw_ptr<ActiveEntry> entry_ = nullptr; + raw_ptr<ActiveEntry> const entry_ = nullptr; std::unique_ptr<HttpTransaction> network_transaction_; @@ -285,6 +295,11 @@ class NET_EXPORT_PRIVATE HttpCache::Writers { // written. bool should_keep_entry_ = true; + // Set if we are currently calculating a checksum of the resource to validate + // it against the expected checksum for the single-keyed cache. Initialised + // with selected headers and accumulates the body of the response. + std::unique_ptr<crypto::SecureHash> checksum_; + CompletionOnceCallback callback_; // Callback for active_transaction_. // Since cache_ can destroy |this|, |cache_callback_| is only invoked at the diff --git a/chromium/net/http/http_cache_writers_unittest.cc b/chromium/net/http/http_cache_writers_unittest.cc index 9fe72473b29..275b6b7ad3d 100644 --- a/chromium/net/http/http_cache_writers_unittest.cc +++ b/chromium/net/http/http_cache_writers_unittest.cc @@ -12,6 +12,7 @@ #include "base/bind.h" #include "base/run_loop.h" +#include "crypto/secure_hash.h" #include "net/http/http_cache.h" #include "net/http/http_cache_transaction.h" #include "net/http/http_response_info.h" @@ -54,8 +55,7 @@ class TestHttpCache : public HttpCache { public: TestHttpCache(std::unique_ptr<HttpTransactionFactory> network_layer, std::unique_ptr<BackendFactory> backend_factory) - : HttpCache(std::move(network_layer), std::move(backend_factory), false) { - } + : HttpCache(std::move(network_layer), std::move(backend_factory)) {} void WritersDoneWritingToEntry(ActiveEntry* entry, bool success, @@ -83,7 +83,6 @@ class WritersTest : public TestWithTaskEnvironment { enum class DeleteTransactionType { NONE, ACTIVE, WAITING, IDLE }; WritersTest() : scoped_transaction_(kSimpleGET_Transaction), - disk_entry_(nullptr), test_cache_(std::make_unique<MockNetworkLayer>(), std::make_unique<MockBackendFactory>()), request_(kSimpleGET_Transaction) { @@ -143,7 +142,7 @@ class WritersTest : public TestWithTaskEnvironment { writers_->AddTransaction(transaction.get(), parallel_writing_pattern_, transaction->priority(), info); writers_->SetNetworkTransaction(transaction.get(), - std::move(network_transaction)); + std::move(network_transaction), nullptr); EXPECT_TRUE(writers_->HasTransaction(transaction.get())); transactions_.push_back(std::move(transaction)); } @@ -498,7 +497,7 @@ class WritersTest : public TestWithTaskEnvironment { ScopedMockTransaction scoped_transaction_; MockHttpCache cache_; std::unique_ptr<HttpCache::Writers> writers_; - disk_cache::Entry* disk_entry_; + disk_cache::Entry* disk_entry_ = nullptr; std::unique_ptr<HttpCache::ActiveEntry> entry_; TestHttpCache test_cache_; diff --git a/chromium/net/http/http_chunked_decoder.cc b/chromium/net/http/http_chunked_decoder.cc index fab94342adb..c6ae91fbd6e 100644 --- a/chromium/net/http/http_chunked_decoder.cc +++ b/chromium/net/http/http_chunked_decoder.cc @@ -58,13 +58,7 @@ namespace net { // extensions. const size_t HttpChunkedDecoder::kMaxLineBufLen = 16384; -HttpChunkedDecoder::HttpChunkedDecoder() - : chunk_remaining_(0), - chunk_terminator_remaining_(false), - reached_last_chunk_(false), - reached_eof_(false), - bytes_after_eof_(0) { -} +HttpChunkedDecoder::HttpChunkedDecoder() = default; int HttpChunkedDecoder::FilterBuf(char* buf, int buf_len) { int result = 0; diff --git a/chromium/net/http/http_chunked_decoder.h b/chromium/net/http/http_chunked_decoder.h index 2ff55f98291..99afb0b0d90 100644 --- a/chromium/net/http/http_chunked_decoder.h +++ b/chromium/net/http/http_chunked_decoder.h @@ -111,22 +111,22 @@ class NET_EXPORT_PRIVATE HttpChunkedDecoder { static bool ParseChunkSize(const char* start, int len, int64_t* out); // Indicates the number of bytes remaining for the current chunk. - int64_t chunk_remaining_; + int64_t chunk_remaining_ = 0; // A small buffer used to store a partial chunk marker. std::string line_buf_; // True if waiting for the terminal CRLF of a chunk's data. - bool chunk_terminator_remaining_; + bool chunk_terminator_remaining_ = false; // Set to true when FilterBuf encounters the last-chunk. - bool reached_last_chunk_; + bool reached_last_chunk_ = false; // Set to true when FilterBuf encounters the final CRLF. - bool reached_eof_; + bool reached_eof_ = false; // The number of extraneous unfiltered bytes after the final CRLF. - int bytes_after_eof_; + int bytes_after_eof_ = 0; }; } // namespace net diff --git a/chromium/net/http/http_content_disposition.cc b/chromium/net/http/http_content_disposition.cc index 17b0a6ffa02..c007c3caba9 100644 --- a/chromium/net/http/http_content_disposition.cc +++ b/chromium/net/http/http_content_disposition.cc @@ -6,12 +6,12 @@ #include "base/base64.h" #include "base/check_op.h" +#include "base/strings/escape.h" #include "base/strings/string_piece.h" #include "base/strings/string_tokenizer.h" #include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" -#include "net/base/escape.h" #include "net/base/net_string_util.h" #include "net/http/http_util.h" @@ -189,8 +189,8 @@ bool DecodeWord(base::StringPiece encoded_word, // web browser. // What IE6/7 does: %-escaped UTF-8. - decoded_word = - base::UnescapeBinaryURLComponent(encoded_word, UnescapeRule::NORMAL); + decoded_word = base::UnescapeBinaryURLComponent(encoded_word, + base::UnescapeRule::NORMAL); if (decoded_word != encoded_word) *parse_result_flags |= HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS; if (base::IsStringUTF8(decoded_word)) { @@ -326,7 +326,7 @@ bool DecodeExtValue(const std::string& param_value, std::string* decoded) { } std::string unescaped = - base::UnescapeBinaryURLComponent(value, UnescapeRule::NORMAL); + base::UnescapeBinaryURLComponent(value, base::UnescapeRule::NORMAL); return ConvertToUtf8AndNormalize(unescaped, charset.c_str(), decoded); } @@ -334,9 +334,8 @@ bool DecodeExtValue(const std::string& param_value, std::string* decoded) { } // namespace HttpContentDisposition::HttpContentDisposition( - const std::string& header, const std::string& referrer_charset) - : type_(INLINE), - parse_result_flags_(INVALID) { + const std::string& header, + const std::string& referrer_charset) { Parse(header, referrer_charset); } @@ -360,9 +359,9 @@ std::string::const_iterator HttpContentDisposition::ConsumeDispositionType( DCHECK(type.find('=') == base::StringPiece::npos); - if (base::LowerCaseEqualsASCII(type, "inline")) { + if (base::EqualsCaseInsensitiveASCII(type, "inline")) { type_ = INLINE; - } else if (base::LowerCaseEqualsASCII(type, "attachment")) { + } else if (base::EqualsCaseInsensitiveASCII(type, "attachment")) { type_ = ATTACHMENT; } else { parse_result_flags_ |= HAS_UNKNOWN_DISPOSITION_TYPE; @@ -404,7 +403,7 @@ void HttpContentDisposition::Parse(const std::string& header, HttpUtil::NameValuePairsIterator iter(pos, end, ';'); while (iter.GetNext()) { if (filename.empty() && - base::LowerCaseEqualsASCII(iter.name_piece(), "filename")) { + base::EqualsCaseInsensitiveASCII(iter.name_piece(), "filename")) { DecodeFilenameValue(iter.value(), referrer_charset, &filename, &parse_result_flags_); if (!filename.empty()) { @@ -412,8 +411,8 @@ void HttpContentDisposition::Parse(const std::string& header, if (filename[0] == '\'') parse_result_flags_ |= HAS_SINGLE_QUOTED_FILENAME; } - } else if (ext_filename.empty() && - base::LowerCaseEqualsASCII(iter.name_piece(), "filename*")) { + } else if (ext_filename.empty() && base::EqualsCaseInsensitiveASCII( + iter.name_piece(), "filename*")) { DecodeExtValue(iter.raw_value(), &ext_filename); if (!ext_filename.empty()) parse_result_flags_ |= HAS_EXT_FILENAME; diff --git a/chromium/net/http/http_content_disposition.h b/chromium/net/http/http_content_disposition.h index 82424018f67..476eebeabf7 100644 --- a/chromium/net/http/http_content_disposition.h +++ b/chromium/net/http/http_content_disposition.h @@ -72,9 +72,9 @@ class NET_EXPORT HttpContentDisposition { std::string::const_iterator ConsumeDispositionType( std::string::const_iterator begin, std::string::const_iterator end); - Type type_; + Type type_ = INLINE; std::string filename_; - int parse_result_flags_; + int parse_result_flags_ = INVALID; }; } // namespace net diff --git a/chromium/net/http/http_network_layer.cc b/chromium/net/http/http_network_layer.cc index a02911e5eaf..a9415436277 100644 --- a/chromium/net/http/http_network_layer.cc +++ b/chromium/net/http/http_network_layer.cc @@ -23,8 +23,7 @@ namespace net { HttpNetworkLayer::HttpNetworkLayer(HttpNetworkSession* session) - : session_(session), - suspended_(false) { + : session_(session) { DCHECK(session_); #if BUILDFLAG(IS_WIN) base::PowerMonitor::AddPowerSuspendObserver(this); diff --git a/chromium/net/http/http_network_layer.h b/chromium/net/http/http_network_layer.h index 0b8e361d2b0..4bbf32f1f2b 100644 --- a/chromium/net/http/http_network_layer.h +++ b/chromium/net/http/http_network_layer.h @@ -44,7 +44,7 @@ class NET_EXPORT HttpNetworkLayer : public HttpTransactionFactory, private: const raw_ptr<HttpNetworkSession> session_; - bool suspended_; + bool suspended_ = false; THREAD_CHECKER(thread_checker_); }; diff --git a/chromium/net/http/http_network_session.cc b/chromium/net/http/http_network_session.cc index fd3c01b7fc9..457bb677150 100644 --- a/chromium/net/http/http_network_session.cc +++ b/chromium/net/http/http_network_session.cc @@ -78,34 +78,9 @@ spdy::SettingsMap AddDefaultHttp2Settings(spdy::SettingsMap http2_settings) { } // unnamed namespace HttpNetworkSessionParams::HttpNetworkSessionParams() - : enable_server_push_cancellation(false), - ignore_certificate_errors(false), - testing_fixed_http_port(0), - testing_fixed_https_port(0), - enable_user_alternate_protocol_ports(false), - enable_spdy_ping_based_connection_checking(true), - enable_http2(true), - spdy_session_max_recv_window_size(kSpdySessionMaxRecvWindowSize), + : spdy_session_max_recv_window_size(kSpdySessionMaxRecvWindowSize), spdy_session_max_queued_capped_frames(kSpdySessionMaxQueuedCappedFrames), -// For OSs that terminate TCP connections upon relevant network changes, -// attempt to preserve active streams by marking all sessions as going -// away, rather than explicitly closing them. Streams may still fail due -// to a generated TCP reset. -#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS) - spdy_go_away_on_ip_change(true), -#else - spdy_go_away_on_ip_change(false), -#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS) - enable_http2_settings_grease(false), - http2_end_stream_with_data_frame(false), - time_func(&base::TimeTicks::Now), - enable_http2_alternative_service(false), - enable_quic(true), - enable_quic_proxies_for_https_urls(false), - disable_idle_sockets_close_on_memory_pressure(false), - key_auth_cache_server_entries_by_network_isolation_key(false), - enable_priority_update(false), - ignore_ip_address_changes(false) { + time_func(&base::TimeTicks::Now) { enable_early_data = base::FeatureList::IsEnabled(features::kEnableTLS13EarlyData); } @@ -167,7 +142,6 @@ HttpNetworkSession::HttpNetworkSession(const HttpNetworkSessionParams& params, context.ct_policy_enforcer, &ssl_client_session_cache_, context.sct_auditing_delegate), - push_delegate_(nullptr), quic_stream_factory_(context.net_log, context.host_resolver, context.ssl_config_service, @@ -274,7 +248,7 @@ ClientSocketPool* HttpNetworkSession::GetSocketPool( return GetSocketPoolManager(pool_type)->GetSocketPool(proxy_server); } -std::unique_ptr<base::Value> HttpNetworkSession::SocketPoolInfoToValue() const { +base::Value HttpNetworkSession::SocketPoolInfoToValue() const { // TODO(yutak): Should merge values from normal pools and WebSocket pools. return normal_socket_pool_manager_->SocketPoolInfoToValue(); } @@ -285,74 +259,74 @@ std::unique_ptr<base::Value> HttpNetworkSession::SpdySessionPoolInfoToValue() } base::Value HttpNetworkSession::QuicInfoToValue() const { - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetKey("sessions", - base::Value::FromUniquePtrValue( - quic_stream_factory_.QuicStreamFactoryInfoToValue())); - dict.SetBoolKey("quic_enabled", IsQuicEnabled()); + base::Value::Dict dict; + dict.Set("sessions", quic_stream_factory_.QuicStreamFactoryInfoToValue()); + dict.Set("quic_enabled", IsQuicEnabled()); const QuicParams* quic_params = context_.quic_context->params(); - base::Value connection_options(base::Value::Type::LIST); + base::Value::List connection_options; for (const auto& option : quic_params->connection_options) connection_options.Append(quic::QuicTagToString(option)); - dict.SetKey("connection_options", std::move(connection_options)); + dict.Set("connection_options", std::move(connection_options)); - base::Value supported_versions(base::Value::Type::LIST); + base::Value::List supported_versions; for (const auto& version : quic_params->supported_versions) supported_versions.Append(ParsedQuicVersionToString(version)); - dict.SetKey("supported_versions", std::move(supported_versions)); + dict.Set("supported_versions", std::move(supported_versions)); - base::Value origins_to_force_quic_on(base::Value::Type::LIST); + base::Value::List origins_to_force_quic_on; for (const auto& origin : quic_params->origins_to_force_quic_on) origins_to_force_quic_on.Append(origin.ToString()); - dict.SetKey("origins_to_force_quic_on", std::move(origins_to_force_quic_on)); - - dict.SetIntKey("max_packet_length", quic_params->max_packet_length); - dict.SetIntKey("max_server_configs_stored_in_properties", - quic_params->max_server_configs_stored_in_properties); - dict.SetIntKey("idle_connection_timeout_seconds", - quic_params->idle_connection_timeout.InSeconds()); - dict.SetIntKey("reduced_ping_timeout_seconds", - quic_params->reduced_ping_timeout.InSeconds()); - dict.SetBoolKey("retry_without_alt_svc_on_quic_errors", - quic_params->retry_without_alt_svc_on_quic_errors); - dict.SetBoolKey("disable_bidirectional_streams", - quic_params->disable_bidirectional_streams); - dict.SetBoolKey("close_sessions_on_ip_change", - quic_params->close_sessions_on_ip_change); - dict.SetBoolKey("goaway_sessions_on_ip_change", - quic_params->goaway_sessions_on_ip_change); - dict.SetBoolKey("migrate_sessions_on_network_change_v2", - quic_params->migrate_sessions_on_network_change_v2); - dict.SetBoolKey("migrate_sessions_early_v2", - quic_params->migrate_sessions_early_v2); - dict.SetIntKey("retransmittable_on_wire_timeout_milliseconds", - quic_params->retransmittable_on_wire_timeout.InMilliseconds()); - dict.SetBoolKey("retry_on_alternate_network_before_handshake", - quic_params->retry_on_alternate_network_before_handshake); - dict.SetBoolKey("migrate_idle_sessions", quic_params->migrate_idle_sessions); - dict.SetIntKey("idle_session_migration_period_seconds", - quic_params->idle_session_migration_period.InSeconds()); - dict.SetIntKey("max_time_on_non_default_network_seconds", - quic_params->max_time_on_non_default_network.InSeconds()); - dict.SetIntKey( - "max_num_migrations_to_non_default_network_on_write_error", - quic_params->max_migrations_to_non_default_network_on_write_error); - dict.SetIntKey( + dict.Set("origins_to_force_quic_on", std::move(origins_to_force_quic_on)); + + dict.Set("max_packet_length", + static_cast<int>(quic_params->max_packet_length)); + dict.Set( + "max_server_configs_stored_in_properties", + static_cast<int>(quic_params->max_server_configs_stored_in_properties)); + dict.Set("idle_connection_timeout_seconds", + static_cast<int>(quic_params->idle_connection_timeout.InSeconds())); + dict.Set("reduced_ping_timeout_seconds", + static_cast<int>(quic_params->reduced_ping_timeout.InSeconds())); + dict.Set("retry_without_alt_svc_on_quic_errors", + quic_params->retry_without_alt_svc_on_quic_errors); + dict.Set("disable_bidirectional_streams", + quic_params->disable_bidirectional_streams); + dict.Set("close_sessions_on_ip_change", + quic_params->close_sessions_on_ip_change); + dict.Set("goaway_sessions_on_ip_change", + quic_params->goaway_sessions_on_ip_change); + dict.Set("migrate_sessions_on_network_change_v2", + quic_params->migrate_sessions_on_network_change_v2); + dict.Set("migrate_sessions_early_v2", quic_params->migrate_sessions_early_v2); + dict.Set("retransmittable_on_wire_timeout_milliseconds", + static_cast<int>( + quic_params->retransmittable_on_wire_timeout.InMilliseconds())); + dict.Set("retry_on_alternate_network_before_handshake", + quic_params->retry_on_alternate_network_before_handshake); + dict.Set("migrate_idle_sessions", quic_params->migrate_idle_sessions); + dict.Set( + "idle_session_migration_period_seconds", + static_cast<int>(quic_params->idle_session_migration_period.InSeconds())); + dict.Set("max_time_on_non_default_network_seconds", + static_cast<int>( + quic_params->max_time_on_non_default_network.InSeconds())); + dict.Set("max_num_migrations_to_non_default_network_on_write_error", + quic_params->max_migrations_to_non_default_network_on_write_error); + dict.Set( "max_num_migrations_to_non_default_network_on_path_degrading", quic_params->max_migrations_to_non_default_network_on_path_degrading); - dict.SetBoolKey("allow_server_migration", - quic_params->allow_server_migration); - dict.SetBoolKey("race_stale_dns_on_connection", - quic_params->race_stale_dns_on_connection); - dict.SetBoolKey("estimate_initial_rtt", quic_params->estimate_initial_rtt); - dict.SetBoolKey("server_push_cancellation", - params_.enable_server_push_cancellation); - dict.SetIntKey("initial_rtt_for_handshake_milliseconds", - quic_params->initial_rtt_for_handshake.InMilliseconds()); - - return dict; + dict.Set("allow_server_migration", quic_params->allow_server_migration); + dict.Set("race_stale_dns_on_connection", + quic_params->race_stale_dns_on_connection); + dict.Set("estimate_initial_rtt", quic_params->estimate_initial_rtt); + dict.Set("server_push_cancellation", params_.enable_server_push_cancellation); + dict.Set("initial_rtt_for_handshake_milliseconds", + static_cast<int>( + quic_params->initial_rtt_for_handshake.InMilliseconds())); + + return base::Value(std::move(dict)); } void HttpNetworkSession::CloseAllConnections(int net_error, diff --git a/chromium/net/http/http_network_session.h b/chromium/net/http/http_network_session.h index 3b96c6c062f..e963d52b9dd 100644 --- a/chromium/net/http/http_network_session.h +++ b/chromium/net/http/http_network_session.h @@ -22,6 +22,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/threading/thread_checker.h" +#include "base/values.h" #include "build/buildflag.h" #include "net/base/host_mapping_rules.h" #include "net/base/host_port_pair.h" @@ -88,22 +89,30 @@ struct NET_EXPORT HttpNetworkSessionParams { HttpNetworkSessionParams(const HttpNetworkSessionParams& other); ~HttpNetworkSessionParams(); - bool enable_server_push_cancellation; + bool enable_server_push_cancellation = false; HostMappingRules host_mapping_rules; - bool ignore_certificate_errors; - uint16_t testing_fixed_http_port; - uint16_t testing_fixed_https_port; - bool enable_user_alternate_protocol_ports; + bool ignore_certificate_errors = false; + uint16_t testing_fixed_http_port = 0; + uint16_t testing_fixed_https_port = 0; + bool enable_user_alternate_protocol_ports = false; // Use SPDY ping frames to test for connection health after idle. - bool enable_spdy_ping_based_connection_checking; - bool enable_http2; + bool enable_spdy_ping_based_connection_checking = true; + bool enable_http2 = true; size_t spdy_session_max_recv_window_size; // Maximum number of capped frames that can be queued at any time. int spdy_session_max_queued_capped_frames; // Whether SPDY pools should mark sessions as going away upon relevant network // changes (instead of closing them). Default value is OS specific. - bool spdy_go_away_on_ip_change; + // For OSs that terminate TCP connections upon relevant network changes, + // attempt to preserve active streams by marking all sessions as going + // away, rather than explicitly closing them. Streams may still fail due + // to a generated TCP reset. +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS) + bool spdy_go_away_on_ip_change = true; +#else + bool spdy_go_away_on_ip_change = false; +#endif // HTTP/2 connection settings. // Unknown settings will still be sent to the server. // Might contain unknown setting identifiers from a predefined set that @@ -117,7 +126,7 @@ struct NET_EXPORT HttpNetworkSessionParams { // https://tools.ietf.org/html/draft-bishop-httpbis-grease-00. // The setting identifier and value will be drawn independently for each // connection to prevent tracking of the client. - bool enable_http2_settings_grease; + bool enable_http2_settings_grease = false; // If set, an HTTP/2 frame with a reserved frame type will be sent after // every HTTP/2 SETTINGS frame and before every HTTP/2 DATA frame. // https://tools.ietf.org/html/draft-bishop-httpbis-grease-00. @@ -131,35 +140,35 @@ struct NET_EXPORT HttpNetworkSessionParams { // If unset, the HEADERS frame will have the END_STREAM flag set on. // This is useful in conjunction with |greased_http2_frame| so that a frame // of reserved type can be sent out even on requests without a body. - bool http2_end_stream_with_data_frame; + bool http2_end_stream_with_data_frame = false; // Source of time for SPDY connections. SpdySessionPool::TimeFunc time_func; // Whether to enable HTTP/2 Alt-Svc entries. - bool enable_http2_alternative_service; + bool enable_http2_alternative_service = false; // Enables 0-RTT support. bool enable_early_data; // Enables QUIC support. - bool enable_quic; + bool enable_quic = true; // If true, HTTPS URLs can be sent to QUIC proxies. - bool enable_quic_proxies_for_https_urls; + bool enable_quic_proxies_for_https_urls = false; // If non-empty, QUIC will only be spoken to hosts in this list. base::flat_set<std::string> quic_host_allowlist; // If true, idle sockets won't be closed when memory pressure happens. - bool disable_idle_sockets_close_on_memory_pressure; + bool disable_idle_sockets_close_on_memory_pressure = false; - bool key_auth_cache_server_entries_by_network_isolation_key; + bool key_auth_cache_server_entries_by_network_isolation_key = false; // If true, enable sending PRIORITY_UPDATE frames until SETTINGS frame // arrives. After SETTINGS frame arrives, do not send PRIORITY_UPDATE // frames any longer if SETTINGS_DEPRECATE_HTTP2_PRIORITIES is missing or // has zero 0, but continue and also stop sending HTTP/2-style priority // information in HEADERS frames and PRIORITY frames if it has value 1. - bool enable_priority_update; + bool enable_priority_update = false; // If true, objects used by a HttpNetworkTransaction are asked not to perform // disruptive work after there has been an IP address change (which usually @@ -168,7 +177,7 @@ struct NET_EXPORT HttpNetworkSessionParams { // network: for these, the underlying network does never change, even if the // default network does (hence underlying objects should not drop their // state). - bool ignore_ip_address_changes; + bool ignore_ip_address_changes = false; }; // Structure with pointers to the dependencies of the HttpNetworkSession. @@ -261,7 +270,7 @@ class NET_EXPORT HttpNetworkSession { #endif // Creates a Value summary of the state of the socket pools. - std::unique_ptr<base::Value> SocketPoolInfoToValue() const; + base::Value SocketPoolInfoToValue() const; // Creates a Value summary of the state of the SPDY sessions. std::unique_ptr<base::Value> SpdySessionPoolInfoToValue() const; diff --git a/chromium/net/http/http_network_transaction.cc b/chromium/net/http/http_network_transaction.cc index 7ae9b23194e..5f530c81fcc 100644 --- a/chromium/net/http/http_network_transaction.cc +++ b/chromium/net/http/http_network_transaction.cc @@ -113,32 +113,10 @@ const int HttpNetworkTransaction::kDrainBodyBufferSize; HttpNetworkTransaction::HttpNetworkTransaction(RequestPriority priority, HttpNetworkSession* session) - : pending_auth_target_(HttpAuth::AUTH_NONE), - io_callback_(base::BindRepeating(&HttpNetworkTransaction::OnIOComplete, + : io_callback_(base::BindRepeating(&HttpNetworkTransaction::OnIOComplete, base::Unretained(this))), session_(session), - request_(nullptr), - priority_(priority), - headers_valid_(false), - can_send_early_data_(false), - configured_client_cert_for_server_(false), - request_headers_(), -#if BUILDFLAG(ENABLE_REPORTING) - network_error_logging_report_generated_(false), - request_reporting_upload_depth_(0), -#endif // BUILDFLAG(ENABLE_REPORTING) - read_buf_len_(0), - total_received_bytes_(0), - total_sent_bytes_(0), - next_state_(STATE_NONE), - establishing_tunnel_(false), - enable_ip_based_pooling_(true), - enable_alternative_services_(true), - websocket_handshake_stream_base_create_helper_(nullptr), - net_error_details_(), - retry_attempts_(0), - num_restarts_(0) { -} + priority_(priority) {} HttpNetworkTransaction::~HttpNetworkTransaction() { #if BUILDFLAG(ENABLE_REPORTING) @@ -908,9 +886,18 @@ int HttpNetworkTransaction::DoConnectedCallback() { // HttpStream::GetAcceptChViaAlps() needs the HttpRequestInfo to retrieve // the ACCEPT_CH frame payload. stream_->RegisterRequest(request_); - stream_->GetRemoteEndpoint(&remote_endpoint_); next_state_ = STATE_CONNECTED_CALLBACK_COMPLETE; + int result = stream_->GetRemoteEndpoint(&remote_endpoint_); + if (result != OK) { + // `GetRemoteEndpoint()` fails when the underlying socket is not connected + // anymore, even though the peer's address is known. This can happen when + // we picked a socket from socket pools while it was still connected, but + // the remote side closes it before we get a chance to send our request. + // See if we should retry the request based on the error code we got. + return HandleIOError(result); + } + if (connected_callback_.is_null()) { return OK; } @@ -1486,6 +1473,13 @@ void HttpNetworkTransaction::GenerateNetworkErrorLoggingReport(int rv) { details.user_agent = request_user_agent_; if (!remote_endpoint_.address().empty()) { details.server_ip = remote_endpoint_.address(); + } else if (!connection_attempts_.empty()) { + // When we failed to connect to the server, `remote_endpoint_` is not set. + // In such case, we use the last endpoint address of `connection_attempts_` + // for the NEL report. This address information is important for the + // downgrade step to protect against port scan attack. + // https://www.w3.org/TR/network-error-logging/#generate-a-network-error-report + details.server_ip = connection_attempts_.back().endpoint.address(); } else { details.server_ip = IPAddress(); } @@ -1574,9 +1568,10 @@ int HttpNetworkTransaction::HandleSSLClientAuthError(int error) { } // This method determines whether it is safe to resend the request after an -// IO error. It can only be called in response to request header or body -// write errors or response header read errors. It should not be used in -// other cases, such as a Connect error. +// IO error. It should only be called in response to errors received before +// final set of response headers have been successfully parsed, that the +// transaction may need to be retried on. +// It should not be used in other cases, such as a Connect error. int HttpNetworkTransaction::HandleIOError(int error) { // Because the peer may request renegotiation with client authentication at // any time, check and handle client authentication errors. @@ -1723,9 +1718,7 @@ bool HttpNetworkTransaction::ShouldResendRequest() const { // NOTE: we resend a request only if we reused a keep-alive connection. // This automatically prevents an infinite resend loop because we'll run // out of the cached keep-alive connections eventually. - if (connection_is_proven && !has_received_headers) - return true; - return false; + return connection_is_proven && !has_received_headers; } bool HttpNetworkTransaction::HasExceededMaxRetries() const { diff --git a/chromium/net/http/http_network_transaction.h b/chromium/net/http/http_network_transaction.h index 38120f07544..525a4b1f9de 100644 --- a/chromium/net/http/http_network_transaction.h +++ b/chromium/net/http/http_network_transaction.h @@ -318,7 +318,7 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction // Whether this transaction is waiting for proxy auth, server auth, or is // not waiting for any auth at all. |pending_auth_target_| is read and // cleared by RestartWithAuth(). - HttpAuth::Target pending_auth_target_; + HttpAuth::Target pending_auth_target_ = HttpAuth::AUTH_NONE; CompletionRepeatingCallback io_callback_; CompletionOnceCallback callback_; @@ -328,7 +328,7 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction NetLogWithSource net_log_; // Reset to null at the start of the Read state machine. - raw_ptr<const HttpRequestInfo> request_; + raw_ptr<const HttpRequestInfo> request_ = nullptr; // The requested URL. GURL url_; @@ -346,14 +346,14 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction std::unique_ptr<HttpStream> stream_; // True if we've validated the headers that the stream parser has returned. - bool headers_valid_; + bool headers_valid_ = false; // True if we can send the request over early data. - bool can_send_early_data_; + bool can_send_early_data_ = false; // True if the client certificate for the server (rather than the proxy) was // configured in this transaction. - bool configured_client_cert_for_server_; + bool configured_client_cert_for_server_ = false; // SSL configuration used for the server and proxy, respectively. Note // |server_ssl_config_| may be updated from the HttpStreamFactory, which will @@ -368,14 +368,14 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction HttpRequestHeaders request_headers_; #if BUILDFLAG(ENABLE_REPORTING) // Whether a NEL report has already been generated. Reset when restarting. - bool network_error_logging_report_generated_; + bool network_error_logging_report_generated_ = false; // Cache some fields from |request_| that we'll need to construct a NEL // report about the request. (NEL report construction happens after we've // cleared the |request_| pointer.) std::string request_method_; std::string request_referrer_; std::string request_user_agent_; - int request_reporting_upload_depth_; + int request_reporting_upload_depth_ = 0; base::TimeTicks start_timeticks_; #endif @@ -386,15 +386,15 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction // User buffer and length passed to the Read method. scoped_refptr<IOBuffer> read_buf_; - int read_buf_len_; + int read_buf_len_ = 0; // Total number of bytes received on all destroyed HttpStreams for this // transaction. - int64_t total_received_bytes_; + int64_t total_received_bytes_ = 0; // Total number of bytes sent on all destroyed HttpStreams for this // transaction. - int64_t total_sent_bytes_; + int64_t total_sent_bytes_ = 0; // When the transaction started / finished sending the request, including // the body, if present. |send_start_time_| is set to |base::TimeTicks()| @@ -403,18 +403,18 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction base::TimeTicks send_end_time_; // The next state in the state machine. - State next_state_; + State next_state_ = STATE_NONE; // True when the tunnel is in the process of being established - we can't // read from the socket until the tunnel is done. - bool establishing_tunnel_; + bool establishing_tunnel_ = false; // Enable pooling to a SpdySession with matching IP and certificate // even if the SpdySessionKey is different. - bool enable_ip_based_pooling_; + bool enable_ip_based_pooling_ = true; // Enable using alternative services for the request. - bool enable_alternative_services_; + bool enable_alternative_services_ = true; // When a request is retried because of errors with the alternative service, // this will store the alternative service used. @@ -423,7 +423,7 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction // The helper object to use to create WebSocketHandshakeStreamBase // objects. Only relevant when establishing a WebSocket connection. raw_ptr<WebSocketHandshakeStreamBase::CreateHelper> - websocket_handshake_stream_base_create_helper_; + websocket_handshake_stream_base_create_helper_ = nullptr; BeforeNetworkStartCallback before_network_start_callback_; ConnectedCallback connected_callback_; @@ -443,10 +443,10 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction // This count excludes retries on reused sockets since a well // behaved server may time those out and thus the number // of times we can retry a request on reused sockets is limited. - size_t retry_attempts_; + size_t retry_attempts_ = 0; // Number of times the transaction was restarted via a RestartWith* call. - size_t num_restarts_; + size_t num_restarts_ = 0; bool close_connection_on_destruction_ = false; }; diff --git a/chromium/net/http/http_network_transaction_unittest.cc b/chromium/net/http/http_network_transaction_unittest.cc index d9225864e3a..26120aade9c 100644 --- a/chromium/net/http/http_network_transaction_unittest.cc +++ b/chromium/net/http/http_network_transaction_unittest.cc @@ -9368,7 +9368,6 @@ TEST_F(HttpNetworkTransactionTest, NTLMOverHttp2WithWebsockets) { ssl1.next_protos_expected_in_ssl_config = NextProtoVector{kProtoHTTP11}; session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1); - session_deps_.enable_websocket_over_http2 = true; std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_)); HttpRequestInfo initial_request_info; @@ -15171,8 +15170,8 @@ TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { MockWrite write; MockRead read; int expected_rv; - const MockWrite* extra_write; - const MockRead* extra_read; + raw_ptr<const MockWrite> extra_write; + raw_ptr<const MockRead> extra_read; }; static const int kNoSSL = 500; @@ -20330,12 +20329,54 @@ TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, CreateReportSuccess) { } TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, + CreateReportDNSErrorAfterStartSync) { + std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + auto trans = + std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get()); + + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.host_resolver->rules()->AddRule(GURL(url_).host(), + ERR_NAME_NOT_RESOLVED); + TestCompletionCallback callback; + + int rv = trans->Start(&request_, callback.callback(), NetLogWithSource()); + EXPECT_THAT(callback.GetResult(rv), IsError(ERR_NAME_NOT_RESOLVED)); + + trans.reset(); + + ASSERT_EQ(1u, network_error_logging_service()->errors().size()); + CheckReport(0 /* index */, 0 /* status_code */, ERR_NAME_NOT_RESOLVED, + IPAddress() /* server_ip */); +} + +TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, + CreateReportDNSErrorAfterStartAsync) { + std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + auto trans = + std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get()); + + session_deps_.host_resolver->set_synchronous_mode(false); + session_deps_.host_resolver->rules()->AddRule(GURL(url_).host(), + ERR_NAME_NOT_RESOLVED); + TestCompletionCallback callback; + + int rv = trans->Start(&request_, callback.callback(), NetLogWithSource()); + EXPECT_THAT(callback.GetResult(rv), IsError(ERR_NAME_NOT_RESOLVED)); + + trans.reset(); + + ASSERT_EQ(1u, network_error_logging_service()->errors().size()); + CheckReport(0 /* index */, 0 /* status_code */, ERR_NAME_NOT_RESOLVED, + IPAddress() /* server_ip */); +} + +TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, CreateReportErrorAfterStart) { std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_)); auto trans = std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get()); - MockConnect mock_connect(SYNCHRONOUS, ERR_NAME_NOT_RESOLVED); + MockConnect mock_connect(SYNCHRONOUS, ERR_CONNECTION_REFUSED); StaticSocketDataProvider data; data.set_connect_data(mock_connect); session_deps_.socket_factory->AddSocketDataProvider(&data); @@ -20343,13 +20384,13 @@ TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, TestCompletionCallback callback; int rv = trans->Start(&request_, callback.callback(), NetLogWithSource()); - EXPECT_THAT(callback.GetResult(rv), IsError(ERR_NAME_NOT_RESOLVED)); + EXPECT_THAT(callback.GetResult(rv), IsError(ERR_CONNECTION_REFUSED)); trans.reset(); ASSERT_EQ(1u, network_error_logging_service()->errors().size()); - CheckReport(0 /* index */, 0 /* status_code */, ERR_NAME_NOT_RESOLVED, - IPAddress() /* server_ip */); + CheckReport(0 /* index */, 0 /* status_code */, ERR_CONNECTION_REFUSED, + IPAddress::IPv4Localhost() /* server_ip */); } // Same as above except the error is ASYNC @@ -20359,7 +20400,7 @@ TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, auto trans = std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get()); - MockConnect mock_connect(ASYNC, ERR_NAME_NOT_RESOLVED); + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); StaticSocketDataProvider data; data.set_connect_data(mock_connect); session_deps_.socket_factory->AddSocketDataProvider(&data); @@ -20367,13 +20408,13 @@ TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, TestCompletionCallback callback; int rv = trans->Start(&request_, callback.callback(), NetLogWithSource()); - EXPECT_THAT(callback.GetResult(rv), IsError(ERR_NAME_NOT_RESOLVED)); + EXPECT_THAT(callback.GetResult(rv), IsError(ERR_CONNECTION_REFUSED)); trans.reset(); ASSERT_EQ(1u, network_error_logging_service()->errors().size()); - CheckReport(0 /* index */, 0 /* status_code */, ERR_NAME_NOT_RESOLVED, - IPAddress() /* server_ip */); + CheckReport(0 /* index */, 0 /* status_code */, ERR_CONNECTION_REFUSED, + IPAddress::IPv4Localhost() /* server_ip */); } TEST_F(HttpNetworkTransactionNetworkErrorLoggingTest, diff --git a/chromium/net/http/http_proxy_client_socket.cc b/chromium/net/http/http_proxy_client_socket.cc index 195c1cf93a8..80623ee1e66 100644 --- a/chromium/net/http/http_proxy_client_socket.cc +++ b/chromium/net/http/http_proxy_client_socket.cc @@ -41,9 +41,7 @@ HttpProxyClientSocket::HttpProxyClientSocket( const NetworkTrafficAnnotationTag& traffic_annotation) : io_callback_(base::BindRepeating(&HttpProxyClientSocket::OnIOComplete, base::Unretained(this))), - next_state_(STATE_NONE), socket_(std::move(socket)), - is_reused_(false), endpoint_(endpoint), auth_(http_auth_controller), proxy_server_(proxy_server), @@ -151,11 +149,6 @@ bool HttpProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) { return false; } -void HttpProxyClientSocket::GetConnectionAttempts( - ConnectionAttempts* out) const { - out->clear(); -} - int64_t HttpProxyClientSocket::GetTotalReceivedBytes() const { return socket_->GetTotalReceivedBytes(); } diff --git a/chromium/net/http/http_proxy_client_socket.h b/chromium/net/http/http_proxy_client_socket.h index 369701fa771..4c82607381c 100644 --- a/chromium/net/http/http_proxy_client_socket.h +++ b/chromium/net/http/http_proxy_client_socket.h @@ -69,9 +69,6 @@ class NET_EXPORT_PRIVATE HttpProxyClientSocket : public ProxyClientSocket { bool WasAlpnNegotiated() const override; NextProto GetNegotiatedProtocol() const override; bool GetSSLInfo(SSLInfo* ssl_info) override; - void GetConnectionAttempts(ConnectionAttempts* out) const override; - void ClearConnectionAttempts() override {} - void AddConnectionAttempts(const ConnectionAttempts& attempts) override {} int64_t GetTotalReceivedBytes() const override; void ApplySocketTag(const SocketTag& tag) override; @@ -131,7 +128,7 @@ class NET_EXPORT_PRIVATE HttpProxyClientSocket : public ProxyClientSocket { bool CheckDone(); CompletionRepeatingCallback io_callback_; - State next_state_; + State next_state_ = STATE_NONE; // Stores the callback provided by the caller of async operations. CompletionOnceCallback user_callback_; @@ -147,7 +144,7 @@ class NET_EXPORT_PRIVATE HttpProxyClientSocket : public ProxyClientSocket { // Whether or not |socket_| has been previously used. Once auth credentials // are sent, set to true. - bool is_reused_; + bool is_reused_ = false; // The hostname and port of the endpoint. This is not necessarily the one // specified by the URL, due to Alternate-Protocol or fixed testing ports. diff --git a/chromium/net/http/http_proxy_connect_job.cc b/chromium/net/http/http_proxy_connect_job.cc index 5fadbafb011..9a95455672c 100644 --- a/chromium/net/http/http_proxy_connect_job.cc +++ b/chromium/net/http/http_proxy_connect_job.cc @@ -188,9 +188,6 @@ HttpProxyConnectJob::HttpProxyConnectJob( NetLogSourceType::HTTP_PROXY_CONNECT_JOB, NetLogEventType::HTTP_PROXY_CONNECT_JOB_CONNECT), params_(std::move(params)), - next_state_(STATE_NONE), - has_restarted_(false), - has_established_connection_(false), http_auth_controller_( params_->tunnel() ? base::MakeRefCounted<HttpAuthController>( @@ -441,9 +438,9 @@ int HttpProxyConnectJob::DoBeginConnect() { int HttpProxyConnectJob::DoTransportConnect() { ProxyServer::Scheme scheme = GetProxyServerScheme(); if (scheme == ProxyServer::SCHEME_HTTP) { - nested_connect_job_ = TransportConnectJob::CreateTransportConnectJob( - params_->transport_params(), priority(), socket_tag(), - common_connect_job_params(), this, &net_log()); + nested_connect_job_ = std::make_unique<TransportConnectJob>( + priority(), socket_tag(), common_connect_job_params(), + params_->transport_params(), this, &net_log()); } else { DCHECK_EQ(scheme, ProxyServer::SCHEME_HTTPS); DCHECK(params_->ssl_params()); @@ -660,7 +657,8 @@ int HttpProxyConnectJob::DoQuicProxyCreateSession() { quic_version, ssl_params->privacy_mode(), kH2QuicTunnelPriority, socket_tag(), params_->network_isolation_key(), ssl_params->GetDirectConnectionParams()->secure_dns_policy(), - /*use_dns_aliases=*/false, ssl_params->ssl_config().GetCertVerifyFlags(), + /*use_dns_aliases=*/false, /*require_dns_https_alpn=*/false, + ssl_params->ssl_config().GetCertVerifyFlags(), GURL("https://" + proxy_server.ToString()), net_log(), &quic_net_error_details_, /*failed_on_default_network_callback=*/CompletionOnceCallback(), diff --git a/chromium/net/http/http_proxy_connect_job.h b/chromium/net/http/http_proxy_connect_job.h index 3ef13066025..8587092cf1a 100644 --- a/chromium/net/http/http_proxy_connect_job.h +++ b/chromium/net/http/http_proxy_connect_job.h @@ -222,13 +222,13 @@ class NET_EXPORT_PRIVATE HttpProxyConnectJob : public ConnectJob, scoped_refptr<SSLCertRequestInfo> ssl_cert_request_info_; - State next_state_; + State next_state_ = STATE_NONE; - bool has_restarted_; + bool has_restarted_ = false; // Set to true once a connection has been successfully established. Remains // true even if a new socket is being connected to retry with auth. - bool has_established_connection_; + bool has_established_connection_ = false; ResolveErrorInfo resolve_error_info_; diff --git a/chromium/net/http/http_request_headers.cc b/chromium/net/http/http_request_headers.cc index e7855a169d6..ea36ce6f63d 100644 --- a/chromium/net/http/http_request_headers.cc +++ b/chromium/net/http/http_request_headers.cc @@ -8,12 +8,12 @@ #include "base/logging.h" #include "base/notreached.h" +#include "base/strings/escape.h" #include "base/strings/strcat.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/values.h" -#include "net/base/escape.h" #include "net/http/http_log_util.h" #include "net/http/http_util.h" #include "net/log/net_log_capture_mode.h" @@ -64,9 +64,7 @@ HttpRequestHeaders::HeaderKeyValuePair::HeaderKeyValuePair( : key(key.data(), key.size()), value(value.data(), value.size()) {} HttpRequestHeaders::Iterator::Iterator(const HttpRequestHeaders& headers) - : started_(false), - curr_(headers.headers_.begin()), - end_(headers.headers_.end()) {} + : curr_(headers.headers_.begin()), end_(headers.headers_.end()) {} HttpRequestHeaders::Iterator::~Iterator() = default; diff --git a/chromium/net/http/http_request_headers.h b/chromium/net/http/http_request_headers.h index dbc3124def7..c1a956e0a80 100644 --- a/chromium/net/http/http_request_headers.h +++ b/chromium/net/http/http_request_headers.h @@ -56,7 +56,7 @@ class NET_EXPORT HttpRequestHeaders { const std::string& value() const { return curr_->value; } private: - bool started_; + bool started_ = false; HttpRequestHeaders::HeaderVector::const_iterator curr_; const HttpRequestHeaders::HeaderVector::const_iterator end_; }; diff --git a/chromium/net/http/http_request_info.cc b/chromium/net/http/http_request_info.cc index faf31912abf..adac030ea8d 100644 --- a/chromium/net/http/http_request_info.cc +++ b/chromium/net/http/http_request_info.cc @@ -8,14 +8,7 @@ namespace net { -HttpRequestInfo::HttpRequestInfo() - : is_subframe_document_resource(false), - upload_data_stream(nullptr), - load_flags(0), - privacy_mode(PRIVACY_MODE_DISABLED), - secure_dns_policy(SecureDnsPolicy::kAllow), - reporting_upload_depth(0), - idempotency(net::DEFAULT_IDEMPOTENCY) {} +HttpRequestInfo::HttpRequestInfo() = default; HttpRequestInfo::HttpRequestInfo(const HttpRequestInfo& other) = default; diff --git a/chromium/net/http/http_request_info.h b/chromium/net/http/http_request_info.h index 4292be327a5..ff52954b981 100644 --- a/chromium/net/http/http_request_info.h +++ b/chromium/net/http/http_request_info.h @@ -40,23 +40,23 @@ struct NET_EXPORT HttpRequestInfo { NetworkIsolationKey network_isolation_key; // True if it is a subframe's document resource. - bool is_subframe_document_resource; + bool is_subframe_document_resource = false; // Any extra request headers (including User-Agent). HttpRequestHeaders extra_headers; // Any upload data. - raw_ptr<UploadDataStream> upload_data_stream; + raw_ptr<UploadDataStream> upload_data_stream = nullptr; // Any load flags (see load_flags.h). - int load_flags; + int load_flags = 0; // If enabled, then request must be sent over connection that cannot be // tracked by the server (e.g. without channel id). - PrivacyMode privacy_mode; + PrivacyMode privacy_mode = PRIVACY_MODE_DISABLED; // Secure DNS Tag for the request. - SecureDnsPolicy secure_dns_policy; + SecureDnsPolicy secure_dns_policy = SecureDnsPolicy::kAllow; // Tag applied to all sockets used to service request. SocketTag socket_tag; @@ -70,7 +70,7 @@ struct NET_EXPORT HttpRequestInfo { // // If the request is a Reporting upload, the depth is the max of the depth // of the requests reported within it plus 1. - int reporting_upload_depth; + int reporting_upload_depth = 0; // This may the top frame origin associated with a request, or it may be the // top frame site. Or it may be nullptr. Only used for histograms. @@ -87,7 +87,15 @@ struct NET_EXPORT HttpRequestInfo { // replay the request. If the request has any side effects, those effects can // happen multiple times. It is only safe to enable the 0-RTT if it is known // that the request is idempotent. - net::Idempotency idempotency; + net::Idempotency idempotency = net::DEFAULT_IDEMPOTENCY; + + // Index of the requested URL in Cache Transparency's pervasive payload list. + // Only used for logging purposes. + int pervasive_payloads_index_for_logging = -1; + + // Checksum of the request body and selected headers, in upper-case + // hexadecimal. Only non-empty if the USE_SINGLE_KEYED_CACHE load flag is set. + std::string checksum; }; } // namespace net diff --git a/chromium/net/http/http_response_body_drainer.cc b/chromium/net/http/http_response_body_drainer.cc index bca2f5fe3a1..9591c58c96f 100644 --- a/chromium/net/http/http_response_body_drainer.cc +++ b/chromium/net/http/http_response_body_drainer.cc @@ -20,10 +20,7 @@ const int HttpResponseBodyDrainer::kDrainBodyBufferSize; const int HttpResponseBodyDrainer::kTimeoutInSeconds; HttpResponseBodyDrainer::HttpResponseBodyDrainer(HttpStream* stream) - : stream_(stream), - next_state_(STATE_NONE), - total_read_(0), - session_(nullptr) {} + : stream_(stream) {} HttpResponseBodyDrainer::~HttpResponseBodyDrainer() = default; diff --git a/chromium/net/http/http_response_body_drainer.h b/chromium/net/http/http_response_body_drainer.h index 4ad3f5da318..5ba92fa0be1 100644 --- a/chromium/net/http/http_response_body_drainer.h +++ b/chromium/net/http/http_response_body_drainer.h @@ -55,10 +55,10 @@ class NET_EXPORT_PRIVATE HttpResponseBodyDrainer { scoped_refptr<IOBuffer> read_buf_; const std::unique_ptr<HttpStream> stream_; - State next_state_; - int total_read_; + State next_state_ = STATE_NONE; + int total_read_ = 0; base::OneShotTimer timer_; - raw_ptr<HttpNetworkSession> session_; + raw_ptr<HttpNetworkSession> session_ = nullptr; }; } // namespace net diff --git a/chromium/net/http/http_response_body_drainer_unittest.cc b/chromium/net/http/http_response_body_drainer_unittest.cc index ed70a0d0f83..13cdd3c74eb 100644 --- a/chromium/net/http/http_response_body_drainer_unittest.cc +++ b/chromium/net/http/http_response_body_drainer_unittest.cc @@ -48,10 +48,7 @@ static_assert((HttpResponseBodyDrainer::kDrainBodyBufferSize % class CloseResultWaiter { public: - CloseResultWaiter() - : result_(false), - have_result_(false), - waiting_for_result_(false) {} + CloseResultWaiter() = default; CloseResultWaiter(const CloseResultWaiter&) = delete; CloseResultWaiter& operator=(const CloseResultWaiter&) = delete; @@ -74,23 +71,15 @@ class CloseResultWaiter { } private: - int result_; - bool have_result_; - bool waiting_for_result_; + int result_ = false; + bool have_result_ = false; + bool waiting_for_result_ = false; }; class MockHttpStream : public HttpStream { public: - MockHttpStream(CloseResultWaiter* result_waiter) - : result_waiter_(result_waiter), - buf_len_(0), - closed_(false), - stall_reads_forever_(false), - num_chunks_(0), - is_sync_(false), - is_last_chunk_zero_size_(false), - is_complete_(false), - can_reuse_connection_(true) {} + explicit MockHttpStream(CloseResultWaiter* result_waiter) + : result_waiter_(result_waiter) {} MockHttpStream(const MockHttpStream&) = delete; MockHttpStream& operator=(const MockHttpStream&) = delete; @@ -125,7 +114,9 @@ class MockHttpStream : public HttpStream { } void GetSSLInfo(SSLInfo* ssl_info) override {} void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {} - bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } + int GetRemoteEndpoint(IPEndPoint* endpoint) override { + return ERR_UNEXPECTED; + } // Mocked API int ReadResponseBody(IOBuffer* buf, @@ -183,14 +174,14 @@ class MockHttpStream : public HttpStream { const raw_ptr<CloseResultWaiter> result_waiter_; scoped_refptr<IOBuffer> user_buf_; CompletionOnceCallback callback_; - int buf_len_; - bool closed_; - bool stall_reads_forever_; - int num_chunks_; - bool is_sync_; - bool is_last_chunk_zero_size_; - bool is_complete_; - bool can_reuse_connection_; + int buf_len_ = 0; + bool closed_ = false; + bool stall_reads_forever_ = false; + int num_chunks_ = 0; + bool is_sync_ = false; + bool is_last_chunk_zero_size_ = false; + bool is_complete_ = false; + bool can_reuse_connection_ = true; base::WeakPtrFactory<MockHttpStream> weak_factory_{this}; }; diff --git a/chromium/net/http/http_response_headers.cc b/chromium/net/http/http_response_headers.cc index 91536513f3d..727cebff27e 100644 --- a/chromium/net/http/http_response_headers.cc +++ b/chromium/net/http/http_response_headers.cc @@ -19,6 +19,7 @@ #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/pickle.h" +#include "base/strings/escape.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" @@ -27,10 +28,10 @@ #include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "base/values.h" -#include "net/base/escape.h" #include "net/base/parse_number.h" #include "net/http/http_byte_range.h" #include "net/http/http_log_util.h" +#include "net/http/http_status_code.h" #include "net/http/http_util.h" #include "net/log/net_log_capture_mode.h" #include "net/log/net_log_values.h" @@ -110,7 +111,7 @@ const char* const kNonUpdatedHeaderPrefixes[] = { bool ShouldUpdateHeader(base::StringPiece name) { for (size_t i = 0; i < std::size(kNonUpdatedHeaders); ++i) { - if (base::LowerCaseEqualsASCII(name, kNonUpdatedHeaders[i])) + if (base::EqualsCaseInsensitiveASCII(name, kNonUpdatedHeaders[i])) return false; } for (size_t i = 0; i < std::size(kNonUpdatedHeaderPrefixes); ++i) { @@ -263,8 +264,8 @@ void HttpResponseHeaders::Persist(base::Pickle* pickle, } void HttpResponseHeaders::Update(const HttpResponseHeaders& new_headers) { - DCHECK(new_headers.response_code() == 304 || - new_headers.response_code() == 206); + DCHECK(new_headers.response_code() == net::HTTP_NOT_MODIFIED || + new_headers.response_code() == net::HTTP_PARTIAL_CONTENT); // Copy up to the null byte. This just copies the status line. std::string new_raw_headers(raw_headers_.c_str()); @@ -707,7 +708,7 @@ void HttpResponseHeaders::ParseStatusLine( if (p == line_end) { DVLOG(1) << "missing response status; assuming 200 OK"; raw_headers_.append(" 200 OK"); - response_code_ = 200; + response_code_ = net::HTTP_OK; return; } @@ -722,7 +723,7 @@ void HttpResponseHeaders::ParseStatusLine( if (p == code) { DVLOG(1) << "missing response status number; assuming 200"; raw_headers_.append(" 200"); - response_code_ = 200; + response_code_ = net::HTTP_OK; return; } raw_headers_.push_back(' '); @@ -962,7 +963,7 @@ bool HttpResponseHeaders::IsRedirect(std::string* location) const { // valid UTF-8, so encoding errors turn into replacement characters before // escaping. Escaping here preserves the bytes as-is. See // https://crbug.com/942073#c14. - *location = EscapeNonASCII(location_strpiece); + *location = base::EscapeNonASCII(location_strpiece); } return true; @@ -972,8 +973,11 @@ bool HttpResponseHeaders::IsRedirect(std::string* location) const { bool HttpResponseHeaders::IsRedirectResponseCode(int response_code) { // Users probably want to see 300 (multiple choice) pages, so we don't count // them as redirects that need to be followed. - return (response_code == 301 || response_code == 302 || - response_code == 303 || response_code == 307 || response_code == 308); + return (response_code == net::HTTP_MOVED_PERMANENTLY || + response_code == net::HTTP_FOUND || + response_code == net::HTTP_SEE_OTHER || + response_code == net::HTTP_TEMPORARY_REDIRECT || + response_code == net::HTTP_PERMANENT_REDIRECT); } // From RFC 2616 section 13.2.4: @@ -1101,8 +1105,9 @@ HttpResponseHeaders::GetFreshnessLifetimes(const Time& response_time) const { // https://datatracker.ietf.org/doc/draft-reschke-http-status-308/ is an // experimental RFC that adds 308 permanent redirect as well, for which "any // future references ... SHOULD use one of the returned URIs." - if ((response_code_ == 200 || response_code_ == 203 || - response_code_ == 206) && + if ((response_code_ == net::HTTP_OK || + response_code_ == net::HTTP_NON_AUTHORITATIVE_INFORMATION || + response_code_ == net::HTTP_PARTIAL_CONTENT) && !must_revalidate) { // TODO(darin): Implement a smarter heuristic. Time last_modified_value; @@ -1116,8 +1121,10 @@ HttpResponseHeaders::GetFreshnessLifetimes(const Time& response_time) const { } // These responses are implicitly fresh (unless otherwise overruled): - if (response_code_ == 300 || response_code_ == 301 || response_code_ == 308 || - response_code_ == 410) { + if (response_code_ == net::HTTP_MULTIPLE_CHOICES || + response_code_ == net::HTTP_MOVED_PERMANENTLY || + response_code_ == net::HTTP_PERMANENT_REDIRECT || + response_code_ == net::HTTP_GONE) { lifetimes.freshness = base::TimeDelta::Max(); lifetimes.staleness = base::TimeDelta(); // It should never be stale. return lifetimes; @@ -1293,7 +1300,7 @@ bool HttpResponseHeaders::IsKeepAlive() const { std::string token; while (EnumerateHeader(&iterator, header, &token)) { for (const KeepAliveToken& keep_alive_token : kKeepAliveTokens) { - if (base::LowerCaseEqualsASCII(token, keep_alive_token.token)) + if (base::EqualsCaseInsensitiveASCII(token, keep_alive_token.token)) return keep_alive_token.keep_alive; } } diff --git a/chromium/net/http/http_response_info.cc b/chromium/net/http/http_response_info.cc index cc35dd83961..e96d6da97c1 100644 --- a/chromium/net/http/http_response_info.cc +++ b/chromium/net/http/http_response_info.cc @@ -117,6 +117,10 @@ enum { // This bit is set if the response has a nonempty `dns_aliases` entry. RESPONSE_INFO_HAS_DNS_ALIASES = 1 << 27, + // This bit is set for an entry in the single-keyed cache that has been marked + // unusable due to the checksum not matching. + RESPONSE_INFO_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE = 1 << 28, + // TODO(darin): Add other bits to indicate alternate request methods. // For now, we don't support storing those. }; @@ -183,18 +187,7 @@ HttpResponseInfo::ConnectionInfoCoarse HttpResponseInfo::ConnectionInfoToCoarse( return CONNECTION_INFO_COARSE_OTHER; } -HttpResponseInfo::HttpResponseInfo() - : was_cached(false), - cache_entry_status(CacheEntryStatus::ENTRY_UNDEFINED), - network_accessed(false), - was_fetched_via_spdy(false), - was_alpn_negotiated(false), - was_fetched_via_proxy(false), - did_use_http_auth(false), - unused_since_prefetch(false), - restricted_prefetch(false), - async_revalidation_requested(false), - connection_info(CONNECTION_INFO_UNKNOWN) {} +HttpResponseInfo::HttpResponseInfo() = default; HttpResponseInfo::HttpResponseInfo(const HttpResponseInfo& rhs) = default; @@ -357,6 +350,9 @@ bool HttpResponseInfo::InitFromPickle(const base::Pickle& pickle, restricted_prefetch = (flags & RESPONSE_INFO_RESTRICTED_PREFETCH) != 0; + single_keyed_cache_entry_unusable = + (flags & RESPONSE_INFO_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE) != 0; + ssl_info.pkp_bypassed = (flags & RESPONSE_INFO_PKP_BYPASSED) != 0; // Read peer_signature_algorithm. @@ -422,6 +418,8 @@ void HttpResponseInfo::Persist(base::Pickle* pickle, flags |= RESPONSE_INFO_UNUSED_SINCE_PREFETCH; if (restricted_prefetch) flags |= RESPONSE_INFO_RESTRICTED_PREFETCH; + if (single_keyed_cache_entry_unusable) + flags |= RESPONSE_INFO_SINGLE_KEYED_CACHE_ENTRY_UNUSABLE; if (ssl_info.pkp_bypassed) flags |= RESPONSE_INFO_PKP_BYPASSED; if (!stale_revalidate_timeout.is_null()) diff --git a/chromium/net/http/http_response_info.h b/chromium/net/http/http_response_info.h index a6d80e494ed..e09a96c4b5c 100644 --- a/chromium/net/http/http_response_info.h +++ b/chromium/net/http/http_response_info.h @@ -147,45 +147,60 @@ class NET_EXPORT HttpResponseInfo { // when reloading previously visited pages (without going over the network). // Note also that under normal circumstances, was_cached is set to the correct // value even if the request fails. - bool was_cached; + bool was_cached = false; // How this response was handled by the HTTP cache. - CacheEntryStatus cache_entry_status; + CacheEntryStatus cache_entry_status = CacheEntryStatus::ENTRY_UNDEFINED; // True if the request accessed the network in the process of retrieving // data. - bool network_accessed; + bool network_accessed = false; // True if the request was fetched over a SPDY channel. - bool was_fetched_via_spdy; + bool was_fetched_via_spdy = false; // True if ALPN was negotiated for this request. - bool was_alpn_negotiated; + bool was_alpn_negotiated = false; - // True if the request was fetched via an explicit proxy. The proxy could + // True if the response was fetched via an explicit proxy. The proxy could // be any type of proxy, HTTP or SOCKS. Note, we do not know if a - // transparent proxy may have been involved. If true, |proxy_server| contains - // the proxy server that was used. - // TODO(tbansal): crbug.com/653354. Remove |was_fetched_via_proxy|. - bool was_fetched_via_proxy; + // transparent proxy may have been involved. + // + // If true and this struct was not restored from pickled data, |proxy_server| + // contains the proxy server that was used. + // + // TODO(https://crbug.com/653354): Remove this in favor of |proxy_server|. + bool was_fetched_via_proxy = false; + + // Information about the proxy used to fetch this response, if any. + // + // This field is not persisted by |Persist()| and not restored by + // |InitFromPickle()|. + // + // TODO(https://crbug.com/653354): Support this field in |Persist()| and + // |InitFromPickle()| then use it to replace |was_fetched_via_proxy|. ProxyServer proxy_server; // Whether the request use http proxy or server authentication. - bool did_use_http_auth; + bool did_use_http_auth = false; // True if the resource was originally fetched for a prefetch and has not been // used since. - bool unused_since_prefetch; + bool unused_since_prefetch = false; // True if the response is a prefetch whose reuse is "restricted". This means // it can only be reused from the cache by requests that are marked as able to // use restricted prefetches. - bool restricted_prefetch; + bool restricted_prefetch = false; // True if this resource is stale and needs async revalidation. // This value is not persisted by Persist(); it is only ever set when the // response is retrieved from the cache. - bool async_revalidation_requested; + bool async_revalidation_requested = false; + + // True if this entry in the single-keyed cache is unusable due to a checksum + // mismatch. + bool single_keyed_cache_entry_unusable = false; // stale-while-revalidate, if any, will be honored until time given by // |stale_revalidate_timeout|. This value is latched the first time @@ -205,7 +220,7 @@ class NET_EXPORT HttpResponseInfo { std::string alpn_negotiated_protocol; // The type of connection used for this response. - ConnectionInfo connection_info; + ConnectionInfo connection_info = CONNECTION_INFO_UNKNOWN; // The time at which the request was made that resulted in this response. // For cached responses, this is the last time the cache entry was validated. diff --git a/chromium/net/http/http_security_headers.cc b/chromium/net/http/http_security_headers.cc index 39eb8191ebc..15defb9c4ff 100644 --- a/chromium/net/http/http_security_headers.cc +++ b/chromium/net/http/http_security_headers.cc @@ -102,10 +102,11 @@ bool ParseHSTSHeader(const std::string& value, case DIRECTIVE_END: if (base::IsAsciiWhitespace(token[0])) continue; - if (base::LowerCaseEqualsASCII(token, "max-age")) { + if (base::EqualsCaseInsensitiveASCII(token, "max-age")) { state = AFTER_MAX_AGE_LABEL; max_age_observed++; - } else if (base::LowerCaseEqualsASCII(token, "includesubdomains")) { + } else if (base::EqualsCaseInsensitiveASCII(token, + "includesubdomains")) { state = AFTER_INCLUDE_SUBDOMAINS; include_subdomains_observed++; include_subdomains_candidate = true; @@ -198,7 +199,7 @@ bool ParseExpectCTHeader(const std::string& value, while (name_value_pairs.GetNext()) { base::StringPiece name = name_value_pairs.name_piece(); - if (base::LowerCaseEqualsASCII(name, "max-age")) { + if (base::EqualsCaseInsensitiveASCII(name, "max-age")) { // "A given directive MUST NOT appear more than once in a given header // field." if (parsed_max_age) @@ -208,7 +209,7 @@ bool ParseExpectCTHeader(const std::string& value, return false; } parsed_max_age = true; - } else if (base::LowerCaseEqualsASCII(name, "enforce")) { + } else if (base::EqualsCaseInsensitiveASCII(name, "enforce")) { // "A given directive MUST NOT appear more than once in a given header // field." if (enforce_candidate) @@ -216,7 +217,7 @@ bool ParseExpectCTHeader(const std::string& value, if (!name_value_pairs.value_piece().empty()) return false; enforce_candidate = true; - } else if (base::LowerCaseEqualsASCII(name, "report-uri")) { + } else if (base::EqualsCaseInsensitiveASCII(name, "report-uri")) { // "A given directive MUST NOT appear more than once in a given header // field." if (has_report_uri) diff --git a/chromium/net/http/http_server_properties.cc b/chromium/net/http/http_server_properties.cc index 0ac003c574e..3adcc93fcbd 100644 --- a/chromium/net/http/http_server_properties.cc +++ b/chromium/net/http/http_server_properties.cc @@ -143,7 +143,6 @@ HttpServerProperties::HttpServerProperties( use_network_isolation_key_(base::FeatureList::IsEnabled( features::kPartitionHttpServerPropertiesByNetworkIsolationKey)), is_initialized_(pref_delegate.get() == nullptr), - queue_write_on_load_(false), properties_manager_( pref_delegate ? std::make_unique<HttpServerPropertiesManager>( diff --git a/chromium/net/http/http_server_properties.h b/chromium/net/http/http_server_properties.h index 2d782f38455..94c1573e620 100644 --- a/chromium/net/http/http_server_properties.h +++ b/chromium/net/http/http_server_properties.h @@ -610,7 +610,7 @@ class NET_EXPORT HttpServerProperties // Queue a write when resources finish loading. Set to true when // MaybeQueueWriteProperties() is invoked while still waiting on // initialization to complete. - bool queue_write_on_load_; + bool queue_write_on_load_ = false; // Used to load/save properties from/to preferences. May be nullptr. std::unique_ptr<HttpServerPropertiesManager> properties_manager_; diff --git a/chromium/net/http/http_server_properties_manager.cc b/chromium/net/http/http_server_properties_manager.cc index 3513f4fd685..7f7e696bea7 100644 --- a/chromium/net/http/http_server_properties_manager.cc +++ b/chromium/net/http/http_server_properties_manager.cc @@ -107,29 +107,26 @@ AlternativeServiceInfoVector GetAlternativeServiceToPersist( void AddAlternativeServiceFieldsToDictionaryValue( const AlternativeService& alternative_service, - base::Value* dict) { - DCHECK(dict->is_dict()); - dict->SetIntKey(kPortKey, alternative_service.port); + base::Value::Dict& dict) { + dict.Set(kPortKey, alternative_service.port); if (!alternative_service.host.empty()) { - dict->SetStringKey(kHostKey, alternative_service.host); + dict.Set(kHostKey, alternative_service.host); } - dict->SetStringKey(kProtocolKey, - NextProtoToString(alternative_service.protocol)); + dict.Set(kProtocolKey, NextProtoToString(alternative_service.protocol)); } // Fails in the case of NetworkIsolationKeys that can't be persisted to disk, // like unique origins. bool TryAddBrokenAlternativeServiceFieldsToDictionaryValue( const BrokenAlternativeService& broken_alt_service, - base::Value* dict) { - DCHECK(dict->is_dict()); + base::Value::Dict& dict) { base::Value network_isolation_key_value; if (!broken_alt_service.network_isolation_key.ToValue( &network_isolation_key_value)) { return false; } - dict->SetKey(kNetworkIsolationKey, std::move(network_isolation_key_value)); + dict.Set(kNetworkIsolationKey, std::move(network_isolation_key_value)); AddAlternativeServiceFieldsToDictionaryValue( broken_alt_service.alternative_service, dict); return true; @@ -153,20 +150,18 @@ std::string QuicServerIdToString(const quic::QuicServerId& server_id) { (server_id.privacy_mode_enabled() ? "/private" : ""); } -// Takes in a base::Value representing a dictionary, and whether -// NetworkIsolationKeys are enabled for HttpServerProperties, and extracts the -// NetworkIsolationKey stored with the |kNetworkIsolationKey| in the dictionary, -// and writes it to |out_network_isolation_key|. Returns false if unable to load -// a NetworkIsolationKey, or the NetworkIsolationKey is non-empty, but +// Takes in a base::Value::Dict, and whether NetworkIsolationKeys are enabled +// for HttpServerProperties, and extracts the NetworkIsolationKey stored with +// the |kNetworkIsolationKey| in the dictionary, and writes it to +// |out_network_isolation_key|. Returns false if unable to load a +// NetworkIsolationKey, or the NetworkIsolationKey is non-empty, but // |use_network_isolation_key| is false. bool GetNetworkIsolationKeyFromDict( - const base::Value& dict, + const base::Value::Dict& dict, bool use_network_isolation_key, NetworkIsolationKey* out_network_isolation_key) { - DCHECK(dict.is_dict()); - const base::Value* network_isolation_key_value = - dict.FindKey(kNetworkIsolationKey); + dict.Find(kNetworkIsolationKey); NetworkIsolationKey network_isolation_key; if (!network_isolation_key_value || !NetworkIsolationKey::FromValue(*network_isolation_key_value, @@ -230,16 +225,19 @@ void HttpServerPropertiesManager::ReadPrefs( net_log_.EndEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_INITIALIZATION); - const base::Value* http_server_properties_dict = + const base::Value* http_server_properties_value = pref_delegate_->GetServerProperties(); // If there are no preferences set, do nothing. - if (!http_server_properties_dict || !http_server_properties_dict->is_dict()) + if (!http_server_properties_value || !http_server_properties_value->is_dict()) return; + const base::Value::Dict& http_server_properties_dict = + http_server_properties_value->GetDict(); + net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_CACHE, - [&] { return http_server_properties_dict->Clone(); }); + [&] { return http_server_properties_value->Clone(); }); absl::optional<int> maybe_version_number = - http_server_properties_dict->FindIntKey(kVersionKey); + http_server_properties_dict.FindInt(kVersionKey); if (!maybe_version_number.has_value() || *maybe_version_number != kVersionNumber) { DVLOG(1) << "Missing or unsupported. Clearing all properties. " @@ -260,14 +258,14 @@ void HttpServerPropertiesManager::ReadPrefs( // ... // ], ... // }, - const base::Value* servers_list = - http_server_properties_dict->FindListKey(kServersKey); + const base::Value::List* servers_list = + http_server_properties_dict.FindList(kServersKey); if (!servers_list) { DVLOG(1) << "Malformed http_server_properties for servers list."; return; } - ReadLastLocalAddressWhenQuicWorked(*http_server_properties_dict, + ReadLastLocalAddressWhenQuicWorked(http_server_properties_dict, last_local_address_when_quic_worked); *server_info_map = std::make_unique<HttpServerProperties::ServerInfoMap>(); @@ -281,24 +279,23 @@ void HttpServerPropertiesManager::ReadPrefs( // Iterate servers list in reverse MRU order so that entries are inserted // into |spdy_servers_map|, |alternative_service_map|, and // |server_network_stats_map| from oldest to newest. - for (auto it = servers_list->GetListDeprecated().end(); - it != servers_list->GetListDeprecated().begin();) { + for (auto it = servers_list->end(); it != servers_list->begin();) { --it; if (!it->is_dict()) { DVLOG(1) << "Malformed http_server_properties for servers dictionary."; continue; } - AddServerData(*it, server_info_map->get(), use_network_isolation_key); + AddServerData(it->GetDict(), server_info_map->get(), + use_network_isolation_key); } - AddToQuicServerInfoMap(*http_server_properties_dict, - use_network_isolation_key, + AddToQuicServerInfoMap(http_server_properties_dict, use_network_isolation_key, quic_server_info_map->get()); // Read list containing broken and recently-broken alternative services, if // it exists. - const base::Value* broken_alt_svc_list = - http_server_properties_dict->FindListKey(kBrokenAlternativeServicesKey); + const base::Value::List* broken_alt_svc_list = + http_server_properties_dict.FindList(kBrokenAlternativeServicesKey); if (broken_alt_svc_list) { *broken_alternative_service_list = std::make_unique<BrokenAlternativeServiceList>(); @@ -307,15 +304,15 @@ void HttpServerPropertiesManager::ReadPrefs( kMaxRecentlyBrokenAlternativeServiceEntries); // Iterate list in reverse-MRU order - for (auto it = broken_alt_svc_list->GetListDeprecated().end(); - it != broken_alt_svc_list->GetListDeprecated().begin();) { + for (auto it = broken_alt_svc_list->end(); + it != broken_alt_svc_list->begin();) { --it; if (!it->is_dict()) { DVLOG(1) << "Malformed broken alterantive service entry."; continue; } AddToBrokenAlternativeServices( - *it, use_network_isolation_key, + it->GetDict(), use_network_isolation_key, broken_alternative_service_list->get(), recently_broken_alternative_services->get()); } @@ -341,7 +338,7 @@ void HttpServerPropertiesManager::ReadPrefs( } void HttpServerPropertiesManager::AddToBrokenAlternativeServices( - const base::Value& broken_alt_svc_entry_dict, + const base::Value::Dict& broken_alt_svc_entry_dict, bool use_network_isolation_key, BrokenAlternativeServiceList* broken_alternative_service_list, RecentlyBrokenAlternativeServices* recently_broken_alternative_services) { @@ -364,9 +361,9 @@ void HttpServerPropertiesManager::AddToBrokenAlternativeServices( // Read broken-count and add an entry for |alt_service| into // |recently_broken_alternative_services|. - if (broken_alt_svc_entry_dict.FindKey(kBrokenCountKey)) { + if (broken_alt_svc_entry_dict.Find(kBrokenCountKey)) { absl::optional<int> broken_count = - broken_alt_svc_entry_dict.FindIntKey(kBrokenCountKey); + broken_alt_svc_entry_dict.FindInt(kBrokenCountKey); if (!broken_count.has_value()) { DVLOG(1) << "Recently broken alternative service has malformed " << "broken-count."; @@ -385,9 +382,9 @@ void HttpServerPropertiesManager::AddToBrokenAlternativeServices( // Read broken-until and add an entry for |alt_service| in // |broken_alternative_service_list|. - if (broken_alt_svc_entry_dict.FindKey(kBrokenUntilKey)) { + if (broken_alt_svc_entry_dict.Find(kBrokenUntilKey)) { const std::string* expiration_string = - broken_alt_svc_entry_dict.FindStringKey(kBrokenUntilKey); + broken_alt_svc_entry_dict.FindString(kBrokenUntilKey); int64_t expiration_int64; if (!expiration_string || !base::StringToInt64(*expiration_string, &expiration_int64)) { @@ -415,11 +412,11 @@ void HttpServerPropertiesManager::AddToBrokenAlternativeServices( } void HttpServerPropertiesManager::AddServerData( - const base::Value& server_dict, + const base::Value::Dict& server_dict, HttpServerProperties::ServerInfoMap* server_info_map, bool use_network_isolation_key) { // Get server's scheme/host/pair. - const std::string* server_str = server_dict.FindStringKey(kServerKey); + const std::string* server_str = server_dict.FindString(kServerKey); NetworkIsolationKey network_isolation_key; // Can't load entry if server name missing, or if the network isolation key is // missing or invalid. @@ -437,7 +434,7 @@ void HttpServerPropertiesManager::AddServerData( HttpServerProperties::ServerInfo server_info; - server_info.supports_spdy = server_dict.FindBoolKey(kSupportsSpdyKey); + server_info.supports_spdy = server_dict.FindBool(kSupportsSpdyKey); if (ParseAlternativeServiceInfo(spdy_server, server_dict, &server_info)) ParseNetworkStats(spdy_server, server_dict, &server_info); @@ -451,12 +448,12 @@ void HttpServerPropertiesManager::AddServerData( } bool HttpServerPropertiesManager::ParseAlternativeServiceDict( - const base::Value& dict, + const base::Value::Dict& dict, bool host_optional, const std::string& parsing_under, AlternativeService* alternative_service) { // Protocol is mandatory. - const std::string* protocol_str = dict.FindStringKey(kProtocolKey); + const std::string* protocol_str = dict.FindString(kProtocolKey); if (!protocol_str) { DVLOG(1) << "Malformed alternative service protocol string under: " << parsing_under; @@ -473,8 +470,8 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceDict( // If host is optional, it defaults to "". std::string host = ""; const std::string* hostp = nullptr; - if (dict.FindKey(kHostKey)) { - hostp = dict.FindStringKey(kHostKey); + if (dict.Find(kHostKey)) { + hostp = dict.FindString(kHostKey); if (!hostp) { DVLOG(1) << "Malformed alternative service host string under: " << parsing_under; @@ -489,7 +486,7 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceDict( alternative_service->host = host; // Port is mandatory. - absl::optional<int> maybe_port = dict.FindIntKey(kPortKey); + absl::optional<int> maybe_port = dict.FindInt(kPortKey); if (!maybe_port.has_value() || !IsPortValid(maybe_port.value())) { DVLOG(1) << "Malformed alternative service port under: " << parsing_under; return false; @@ -500,7 +497,7 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceDict( } bool HttpServerPropertiesManager::ParseAlternativeServiceInfoDictOfServer( - const base::Value& dict, + const base::Value::Dict& dict, const std::string& server_str, AlternativeServiceInfo* alternative_service_info) { AlternativeService alternative_service; @@ -511,10 +508,10 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceInfoDictOfServer( alternative_service_info->set_alternative_service(alternative_service); // Expiration is optional, defaults to one day. - if (!dict.FindKey(kExpirationKey)) { + if (!dict.Find(kExpirationKey)) { alternative_service_info->set_expiration(base::Time::Now() + base::Days(1)); } else { - const std::string* expiration_string = dict.FindStringKey(kExpirationKey); + const std::string* expiration_string = dict.FindString(kExpirationKey); if (expiration_string) { int64_t expiration_int64 = 0; if (!base::StringToInt64(*expiration_string, &expiration_int64)) { @@ -532,15 +529,15 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceInfoDictOfServer( } // Advertised versions list is optional. - if (dict.FindKey(kAdvertisedAlpnsKey)) { - const base::Value* versions_list = dict.FindListKey(kAdvertisedAlpnsKey); + if (dict.Find(kAdvertisedAlpnsKey)) { + const base::Value::List* versions_list = dict.FindList(kAdvertisedAlpnsKey); if (!versions_list) { DVLOG(1) << "Malformed alternative service advertised versions list for " << "server: " << server_str; return false; } quic::ParsedQuicVersionVector advertised_versions; - for (const auto& value : versions_list->GetListDeprecated()) { + for (const auto& value : *versions_list) { const std::string* version_string = value.GetIfString(); if (!version_string) { DVLOG(1) << "Malformed alternative service version for server: " @@ -561,11 +558,11 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceInfoDictOfServer( bool HttpServerPropertiesManager::ParseAlternativeServiceInfo( const url::SchemeHostPort& server, - const base::Value& server_pref_dict, + const base::Value::Dict& server_pref_dict, HttpServerProperties::ServerInfo* server_info) { DCHECK(!server_info->alternative_services.has_value()); - const base::Value* alternative_service_list = - server_pref_dict.FindListKey(kAlternativeServiceKey); + const base::Value::List* alternative_service_list = + server_pref_dict.FindList(kAlternativeServiceKey); if (!alternative_service_list) { return true; } @@ -574,14 +571,13 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceInfo( } AlternativeServiceInfoVector alternative_service_info_vector; - for (const auto& alternative_service_list_item : - alternative_service_list->GetListDeprecated()) { + for (const auto& alternative_service_list_item : *alternative_service_list) { if (!alternative_service_list_item.is_dict()) return false; AlternativeServiceInfo alternative_service_info; - if (!ParseAlternativeServiceInfoDictOfServer(alternative_service_list_item, - server.Serialize(), - &alternative_service_info)) { + if (!ParseAlternativeServiceInfoDictOfServer( + alternative_service_list_item.GetDict(), server.Serialize(), + &alternative_service_info)) { return false; } if (base::Time::Now() < alternative_service_info.expiration()) { @@ -598,14 +594,14 @@ bool HttpServerPropertiesManager::ParseAlternativeServiceInfo( } void HttpServerPropertiesManager::ReadLastLocalAddressWhenQuicWorked( - const base::Value& http_server_properties_dict, + const base::Value::Dict& http_server_properties_dict, IPAddress* last_local_address_when_quic_worked) { - const base::Value* supports_quic_dict = - http_server_properties_dict.FindDictKey(kSupportsQuicKey); + const base::Value::Dict* supports_quic_dict = + http_server_properties_dict.FindDict(kSupportsQuicKey); if (!supports_quic_dict) { return; } - const base::Value* used_quic = supports_quic_dict->FindKey(kUsedQuicKey); + const base::Value* used_quic = supports_quic_dict->Find(kUsedQuicKey); if (!used_quic || !used_quic->is_bool()) { DVLOG(1) << "Malformed SupportsQuic"; return; @@ -613,7 +609,7 @@ void HttpServerPropertiesManager::ReadLastLocalAddressWhenQuicWorked( if (!used_quic->GetBool()) return; - const std::string* address = supports_quic_dict->FindStringKey(kAddressKey); + const std::string* address = supports_quic_dict->FindString(kAddressKey); if (!address || !last_local_address_when_quic_worked->AssignFromIPLiteral(*address)) { DVLOG(1) << "Malformed SupportsQuic"; @@ -622,16 +618,15 @@ void HttpServerPropertiesManager::ReadLastLocalAddressWhenQuicWorked( void HttpServerPropertiesManager::ParseNetworkStats( const url::SchemeHostPort& server, - const base::Value& server_pref_dict, + const base::Value::Dict& server_pref_dict, HttpServerProperties::ServerInfo* server_info) { DCHECK(!server_info->server_network_stats.has_value()); - const base::Value* server_network_stats_dict = - server_pref_dict.FindDictKey(kNetworkStatsKey); + const base::Value::Dict* server_network_stats_dict = + server_pref_dict.FindDict(kNetworkStatsKey); if (!server_network_stats_dict) { return; } - absl::optional<int> maybe_srtt = - server_network_stats_dict->FindIntKey(kSrttKey); + absl::optional<int> maybe_srtt = server_network_stats_dict->FindInt(kSrttKey); if (!maybe_srtt.has_value()) { DVLOG(1) << "Malformed ServerNetworkStats for server: " << server.Serialize(); @@ -645,23 +640,24 @@ void HttpServerPropertiesManager::ParseNetworkStats( } void HttpServerPropertiesManager::AddToQuicServerInfoMap( - const base::Value& http_server_properties_dict, + const base::Value::Dict& http_server_properties_dict, bool use_network_isolation_key, HttpServerProperties::QuicServerInfoMap* quic_server_info_map) { - const base::Value* quic_server_info_list = - http_server_properties_dict.FindListKey(kQuicServers); + const base::Value::List* quic_server_info_list = + http_server_properties_dict.FindList(kQuicServers); if (!quic_server_info_list) { DVLOG(1) << "Malformed http_server_properties for quic_servers."; return; } - for (const auto& quic_server_info_value : - quic_server_info_list->GetListDeprecated()) { - if (!quic_server_info_value.is_dict()) + for (const auto& quic_server_info_value : *quic_server_info_list) { + const base::Value::Dict* quic_server_info_dict = + quic_server_info_value.GetIfDict(); + if (!quic_server_info_dict) continue; const std::string* quic_server_id_str = - quic_server_info_value.FindStringKey(kQuicServerIdKey); + quic_server_info_dict->FindString(kQuicServerIdKey); if (!quic_server_id_str || quic_server_id_str->empty()) continue; @@ -674,7 +670,7 @@ void HttpServerPropertiesManager::AddToQuicServerInfoMap( } NetworkIsolationKey network_isolation_key; - if (!GetNetworkIsolationKeyFromDict(quic_server_info_value, + if (!GetNetworkIsolationKeyFromDict(*quic_server_info_dict, use_network_isolation_key, &network_isolation_key)) { DVLOG(1) << "Malformed http_server_properties quic server dict: " @@ -683,7 +679,7 @@ void HttpServerPropertiesManager::AddToQuicServerInfoMap( } const std::string* quic_server_info = - quic_server_info_value.FindStringKey(kServerInfoKey); + quic_server_info_dict->FindString(kServerInfoKey); if (!quic_server_info) { DVLOG(1) << "Malformed http_server_properties quic server info: " << *quic_server_id_str; @@ -714,11 +710,13 @@ void HttpServerPropertiesManager::WriteToPrefs( std::set<std::pair<std::string, NetworkIsolationKey>> persisted_canonical_suffix_set; const base::Time now = base::Time::Now(); - base::Value http_server_properties_dict(base::Value::Type::DICTIONARY); + base::Value http_server_properties_value(base::Value::Type::DICTIONARY); + base::Value::Dict& http_server_properties_dict = + http_server_properties_value.GetDict(); - // Convert |server_info_map| to a dictionary Value and add it to + // Convert |server_info_map| to a list Value and add it to // |http_server_properties_dict|. - base::Value servers_list(base::Value::Type::LIST); + base::Value::List servers_list; for (const auto& [key, server_info] : base::Reversed(server_info_map)) { // If can't convert the NetworkIsolationKey to a value, don't save to disk. // Generally happens because the key is for a unique origin. @@ -726,139 +724,137 @@ void HttpServerPropertiesManager::WriteToPrefs( if (!key.network_isolation_key.ToValue(&network_isolation_key_value)) continue; - base::Value server_dict(base::Value::Type::DICTIONARY); + base::Value::Dict server_dict; bool supports_spdy = server_info.supports_spdy.value_or(false); if (supports_spdy) - server_dict.SetBoolKey(kSupportsSpdyKey, supports_spdy); + server_dict.Set(kSupportsSpdyKey, supports_spdy); AlternativeServiceInfoVector alternative_services = GetAlternativeServiceToPersist(server_info.alternative_services, key, now, get_canonical_suffix, &persisted_canonical_suffix_set); if (!alternative_services.empty()) - SaveAlternativeServiceToServerPrefs(alternative_services, &server_dict); + SaveAlternativeServiceToServerPrefs(alternative_services, server_dict); if (server_info.server_network_stats) { SaveNetworkStatsToServerPrefs(*server_info.server_network_stats, - &server_dict); + server_dict); } // Don't add empty entries. This can happen if, for example, all alternative // services are empty, or |supports_spdy| is set to false, and all other // fields are not set. - if (server_dict.DictEmpty()) + if (server_dict.empty()) continue; - server_dict.SetStringKey(kServerKey, key.server.Serialize()); - server_dict.SetKey(kNetworkIsolationKey, - std::move(network_isolation_key_value)); + server_dict.Set(kServerKey, key.server.Serialize()); + server_dict.Set(kNetworkIsolationKey, + std::move(network_isolation_key_value)); servers_list.Append(std::move(server_dict)); } - http_server_properties_dict.SetKey(kServersKey, std::move(servers_list)); + http_server_properties_dict.Set(kServersKey, std::move(servers_list)); - http_server_properties_dict.SetIntKey(kVersionKey, kVersionNumber); + http_server_properties_dict.Set(kVersionKey, kVersionNumber); SaveLastLocalAddressWhenQuicWorkedToPrefs(last_local_address_when_quic_worked, - &http_server_properties_dict); + http_server_properties_dict); SaveQuicServerInfoMapToServerPrefs(quic_server_info_map, - &http_server_properties_dict); + http_server_properties_dict); SaveBrokenAlternativeServicesToPrefs( broken_alternative_service_list, kMaxBrokenAlternativeServicesToPersist, - recently_broken_alternative_services, &http_server_properties_dict); + recently_broken_alternative_services, http_server_properties_dict); - pref_delegate_->SetServerProperties(http_server_properties_dict, + pref_delegate_->SetServerProperties(http_server_properties_value, std::move(callback)); net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_PREFS, - [&] { return http_server_properties_dict.Clone(); }); + [&] { return http_server_properties_value.Clone(); }); } void HttpServerPropertiesManager::SaveAlternativeServiceToServerPrefs( const AlternativeServiceInfoVector& alternative_service_info_vector, - base::Value* server_pref_dict) { + base::Value::Dict& server_pref_dict) { if (alternative_service_info_vector.empty()) { return; } - base::Value alternative_service_list(base::Value::Type::LIST); + base::Value::List alternative_service_list; for (const AlternativeServiceInfo& alternative_service_info : alternative_service_info_vector) { const AlternativeService& alternative_service = alternative_service_info.alternative_service(); DCHECK(IsAlternateProtocolValid(alternative_service.protocol)); - base::Value alternative_service_dict(base::Value::Type::DICTIONARY); + base::Value::Dict alternative_service_dict; AddAlternativeServiceFieldsToDictionaryValue(alternative_service, - &alternative_service_dict); + alternative_service_dict); // JSON cannot store int64_t, so expiration is converted to a string. - alternative_service_dict.SetStringKey( + alternative_service_dict.Set( kExpirationKey, base::NumberToString( alternative_service_info.expiration().ToInternalValue())); - base::Value advertised_versions_list(base::Value::Type::LIST); + base::Value::List advertised_versions_list; for (const auto& version : alternative_service_info.advertised_versions()) { advertised_versions_list.Append(quic::AlpnForVersion(version)); } - alternative_service_dict.SetKey(kAdvertisedAlpnsKey, - std::move(advertised_versions_list)); + alternative_service_dict.Set(kAdvertisedAlpnsKey, + std::move(advertised_versions_list)); alternative_service_list.Append(std::move(alternative_service_dict)); } - if (alternative_service_list.GetListDeprecated().size() == 0) + if (alternative_service_list.size() == 0) return; - server_pref_dict->SetKey(kAlternativeServiceKey, - std::move(alternative_service_list)); + server_pref_dict.Set(kAlternativeServiceKey, + std::move(alternative_service_list)); } void HttpServerPropertiesManager::SaveLastLocalAddressWhenQuicWorkedToPrefs( const IPAddress& last_local_address_when_quic_worked, - base::Value* http_server_properties_dict) { + base::Value::Dict& http_server_properties_dict) { if (!last_local_address_when_quic_worked.IsValid()) return; - base::Value supports_quic_dict(base::Value::Type::DICTIONARY); - supports_quic_dict.SetBoolKey(kUsedQuicKey, true); - supports_quic_dict.SetStringKey( - kAddressKey, last_local_address_when_quic_worked.ToString()); - http_server_properties_dict->SetKey(kSupportsQuicKey, - std::move(supports_quic_dict)); + base::Value::Dict supports_quic_dict; + supports_quic_dict.Set(kUsedQuicKey, true); + supports_quic_dict.Set(kAddressKey, + last_local_address_when_quic_worked.ToString()); + http_server_properties_dict.Set(kSupportsQuicKey, + std::move(supports_quic_dict)); } void HttpServerPropertiesManager::SaveNetworkStatsToServerPrefs( const ServerNetworkStats& server_network_stats, - base::Value* server_pref_dict) { - base::Value server_network_stats_dict(base::Value::Type::DICTIONARY); - // Becasue JSON doesn't support int64_t, persist int64_t as a string. - server_network_stats_dict.SetIntKey( + base::Value::Dict& server_pref_dict) { + base::Value::Dict server_network_stats_dict; + // Because JSON doesn't support int64_t, persist int64_t as a string. + server_network_stats_dict.Set( kSrttKey, static_cast<int>(server_network_stats.srtt.InMicroseconds())); // TODO(rtenneti): When QUIC starts using bandwidth_estimate, then persist // bandwidth_estimate. - server_pref_dict->SetKey(kNetworkStatsKey, - std::move(server_network_stats_dict)); + server_pref_dict.Set(kNetworkStatsKey, std::move(server_network_stats_dict)); } void HttpServerPropertiesManager::SaveQuicServerInfoMapToServerPrefs( const HttpServerProperties::QuicServerInfoMap& quic_server_info_map, - base::Value* http_server_properties_dict) { + base::Value::Dict& http_server_properties_dict) { if (quic_server_info_map.empty()) return; - base::Value quic_servers_list(base::Value::Type::LIST); + base::Value::List quic_servers_list; for (const auto& [key, server_info] : base::Reversed(quic_server_info_map)) { base::Value network_isolation_key_value; // Don't save entries with ephemeral NIKs. if (!key.network_isolation_key.ToValue(&network_isolation_key_value)) continue; - base::Value quic_server_pref_dict(base::Value::Type::DICTIONARY); - quic_server_pref_dict.SetStringKey(kQuicServerIdKey, - QuicServerIdToString(key.server_id)); - quic_server_pref_dict.SetKey(kNetworkIsolationKey, - std::move(network_isolation_key_value)); - quic_server_pref_dict.SetStringKey(kServerInfoKey, server_info); + base::Value::Dict quic_server_pref_dict; + quic_server_pref_dict.Set(kQuicServerIdKey, + QuicServerIdToString(key.server_id)); + quic_server_pref_dict.Set(kNetworkIsolationKey, + std::move(network_isolation_key_value)); + quic_server_pref_dict.Set(kServerInfoKey, server_info); quic_servers_list.Append(std::move(quic_server_pref_dict)); } - http_server_properties_dict->SetKey(kQuicServers, - std::move(quic_servers_list)); + http_server_properties_dict.Set(kQuicServers, std::move(quic_servers_list)); } void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs( @@ -866,7 +862,7 @@ void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs( size_t max_broken_alternative_services, const RecentlyBrokenAlternativeServices& recently_broken_alternative_services, - base::Value* http_server_properties_dict) { + base::Value::Dict& http_server_properties_dict) { if (broken_alternative_service_list.empty() && recently_broken_alternative_services.empty()) { return; @@ -874,7 +870,7 @@ void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs( // JSON list will be in MRU order according to // |recently_broken_alternative_services|. - base::Value json_list(base::Value::Type::LIST); + base::Value::List json_list; // Maps recently-broken alternative services to the index where it's stored // in |json_list|. @@ -883,14 +879,13 @@ void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs( if (!recently_broken_alternative_services.empty()) { for (const auto& [broken_alt_service, broken_count] : base::Reversed(recently_broken_alternative_services)) { - base::Value entry_dict(base::Value::Type::DICTIONARY); + base::Value::Dict entry_dict; if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue( - broken_alt_service, &entry_dict)) { + broken_alt_service, entry_dict)) { continue; } - entry_dict.SetKey(kBrokenCountKey, base::Value(broken_count)); - json_list_index_map[broken_alt_service] = - json_list.GetListDeprecated().size(); + entry_dict.Set(kBrokenCountKey, broken_count); + json_list_index_map[broken_alt_service] = json_list.size(); json_list.Append(std::move(entry_dict)); } } @@ -914,20 +909,18 @@ void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs( auto index_map_it = json_list_index_map.find(broken_alt_service); if (index_map_it != json_list_index_map.end()) { size_t json_list_index = index_map_it->second; - base::Value& entry_dict = - json_list.GetListDeprecated()[json_list_index]; + base::Value& entry_dict = json_list[json_list_index]; DCHECK(entry_dict.is_dict()); - DCHECK(!entry_dict.FindKey(kBrokenUntilKey)); - entry_dict.SetKey(kBrokenUntilKey, - base::Value(base::NumberToString(expiration_int64))); + DCHECK(!entry_dict.GetDict().Find(kBrokenUntilKey)); + entry_dict.GetDict().Set(kBrokenUntilKey, + base::NumberToString(expiration_int64)); } else { - base::Value entry_dict(base::Value::Type::DICTIONARY); + base::Value::Dict entry_dict; if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue( - broken_alt_service, &entry_dict)) { + broken_alt_service, entry_dict)) { continue; } - entry_dict.SetKey(kBrokenUntilKey, - base::Value(base::NumberToString(expiration_int64))); + entry_dict.Set(kBrokenUntilKey, base::NumberToString(expiration_int64)); json_list.Append(std::move(entry_dict)); } } @@ -935,11 +928,11 @@ void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs( // This can happen if all the entries are for NetworkIsolationKeys for opaque // origins, which isn't exactly common, but can theoretically happen. - if (json_list.GetListDeprecated().empty()) + if (json_list.empty()) return; - http_server_properties_dict->SetKey(kBrokenAlternativeServicesKey, - std::move(json_list)); + http_server_properties_dict.Set(kBrokenAlternativeServicesKey, + std::move(json_list)); } void HttpServerPropertiesManager::OnHttpServerPropertiesLoaded() { diff --git a/chromium/net/http/http_server_properties_manager.h b/chromium/net/http/http_server_properties_manager.h index add11943a41..9be5265ae9d 100644 --- a/chromium/net/http/http_server_properties_manager.h +++ b/chromium/net/http/http_server_properties_manager.h @@ -132,7 +132,7 @@ class NET_EXPORT_PRIVATE HttpServerPropertiesManager { FRIEND_TEST_ALL_PREFIXES(HttpServerPropertiesManagerTest, AdvertisedVersionsRoundTrip); - void AddServerData(const base::Value& server_dict, + void AddServerData(const base::Value::Dict& server_dict, HttpServerProperties::ServerInfoMap* server_info_map, bool use_network_isolation_key); @@ -146,13 +146,13 @@ class NET_EXPORT_PRIVATE HttpServerPropertiesManager { // |alternative_service| is the output of parsing |dict|. // Return value is true if parsing is successful. static bool ParseAlternativeServiceDict( - const base::Value& dict, + const base::Value::Dict& dict, bool host_optional, const std::string& parsing_under, AlternativeService* alternative_service); static bool ParseAlternativeServiceInfoDictOfServer( - const base::Value& dict, + const base::Value::Dict& dict, const std::string& server_str, AlternativeServiceInfo* alternative_service_info); @@ -161,43 +161,43 @@ class NET_EXPORT_PRIVATE HttpServerPropertiesManager { // not considered corruption). static bool ParseAlternativeServiceInfo( const url::SchemeHostPort& server, - const base::Value& server_dict, + const base::Value::Dict& server_dict, HttpServerProperties::ServerInfo* server_info); void ReadLastLocalAddressWhenQuicWorked( - const base::Value& server_dict, + const base::Value::Dict& server_dict, IPAddress* last_local_address_when_quic_worked); void ParseNetworkStats(const url::SchemeHostPort& server, - const base::Value& server_dict, + const base::Value::Dict& server_dict, HttpServerProperties::ServerInfo* server_info); void AddToQuicServerInfoMap( - const base::Value& server_dict, + const base::Value::Dict& server_dict, bool use_network_isolation_key, HttpServerProperties::QuicServerInfoMap* quic_server_info_map); void AddToBrokenAlternativeServices( - const base::Value& broken_alt_svc_entry_dict, + const base::Value::Dict& broken_alt_svc_entry_dict, bool use_network_isolation_key, BrokenAlternativeServiceList* broken_alternative_service_list, RecentlyBrokenAlternativeServices* recently_broken_alternative_services); void SaveAlternativeServiceToServerPrefs( const AlternativeServiceInfoVector& alternative_service_info_vector, - base::Value* server_pref_dict); + base::Value::Dict& server_pref_dict); void SaveLastLocalAddressWhenQuicWorkedToPrefs( const IPAddress& last_local_address_when_quic_worked, - base::Value* http_server_properties_dict); + base::Value::Dict& http_server_properties_dict); void SaveNetworkStatsToServerPrefs( const ServerNetworkStats& server_network_stats, - base::Value* server_pref_dict); + base::Value::Dict& server_pref_dict); void SaveQuicServerInfoMapToServerPrefs( const HttpServerProperties::QuicServerInfoMap& quic_server_info_map, - base::Value* http_server_properties_dict); + base::Value::Dict& http_server_properties_dict); void SaveBrokenAlternativeServicesToPrefs( const BrokenAlternativeServiceList& broken_alternative_service_list, size_t max_broken_alternative_services, const RecentlyBrokenAlternativeServices& recently_broken_alternative_services, - base::Value* http_server_properties_dict); + base::Value::Dict& http_server_properties_dict); void OnHttpServerPropertiesLoaded(); diff --git a/chromium/net/http/http_server_properties_manager_unittest.cc b/chromium/net/http/http_server_properties_manager_unittest.cc index 598d9c934f7..df6ed2e114e 100644 --- a/chromium/net/http/http_server_properties_manager_unittest.cc +++ b/chromium/net/http/http_server_properties_manager_unittest.cc @@ -279,9 +279,9 @@ class HttpServerPropertiesManagerTest : public testing::Test, // Returns a dictionary with only the version field populated. static base::Value DictWithVersion() { - base::Value http_server_properties_dict(base::Value::Type::DICTIONARY); - http_server_properties_dict.SetIntKey("version", 5); - return http_server_properties_dict; + base::Value::Dict http_server_properties_dict; + http_server_properties_dict.Set("version", 5); + return base::Value(std::move(http_server_properties_dict)); } raw_ptr<MockPrefDelegate> @@ -292,43 +292,42 @@ class HttpServerPropertiesManagerTest : public testing::Test, }; TEST_F(HttpServerPropertiesManagerTest, BadCachedHostPortPair) { - base::Value server_pref_dict(base::Value::Type::DICTIONARY); + base::Value::Dict server_pref_dict; // Set supports_spdy for www.google.com:65536. - server_pref_dict.SetBoolKey("supports_spdy", true); + server_pref_dict.Set("supports_spdy", true); // Set up alternative_service for www.google.com:65536. - base::Value alternative_service_dict(base::Value::Type::DICTIONARY); - alternative_service_dict.SetStringKey("protocol_str", "h2"); - alternative_service_dict.SetIntKey("port", 80); - base::Value alternative_service_list(base::Value::Type::LIST); + base::Value::Dict alternative_service_dict; + alternative_service_dict.Set("protocol_str", "h2"); + alternative_service_dict.Set("port", 80); + base::Value::List alternative_service_list; alternative_service_list.Append(std::move(alternative_service_dict)); - server_pref_dict.SetKey("alternative_service", - std::move(alternative_service_list)); + server_pref_dict.Set("alternative_service", + std::move(alternative_service_list)); // Set up ServerNetworkStats for www.google.com:65536. - base::Value stats(base::Value::Type::DICTIONARY); - stats.SetIntKey("srtt", 10); - server_pref_dict.SetKey("network_stats", std::move(stats)); + base::Value::Dict stats; + stats.Set("srtt", 10); + server_pref_dict.Set("network_stats", std::move(stats)); // Set the server preference for www.google.com:65536. - base::Value servers_dict(base::Value::Type::DICTIONARY); - servers_dict.SetKey("www.google.com:65536", std::move(server_pref_dict)); - base::Value servers_list(base::Value::Type::LIST); + base::Value::Dict servers_dict; + servers_dict.Set("www.google.com:65536", std::move(server_pref_dict)); + base::Value::List servers_list; servers_list.Append(std::move(servers_dict)); base::Value http_server_properties_dict = DictWithVersion(); - http_server_properties_dict.SetKey("servers", std::move(servers_list)); + http_server_properties_dict.GetDict().Set("servers", std::move(servers_list)); // Set quic_server_info for www.google.com:65536. - base::Value quic_servers_dict(base::Value::Type::DICTIONARY); - base::Value quic_server_pref_dict1(base::Value::Type::DICTIONARY); - quic_server_pref_dict1.SetKey("server_info", - base::Value("quic_server_info1")); - quic_servers_dict.SetKey("http://mail.google.com:65536", - std::move(quic_server_pref_dict1)); + base::Value::Dict quic_servers_dict; + base::Value::Dict quic_server_pref_dict1; + quic_server_pref_dict1.Set("server_info", "quic_server_info1"); + quic_servers_dict.Set("http://mail.google.com:65536", + std::move(quic_server_pref_dict1)); - http_server_properties_dict.SetKey("quic_servers", - std::move(quic_servers_dict)); + http_server_properties_dict.GetDict().Set("quic_servers", + std::move(quic_servers_dict)); // Set up the pref. InitializePrefs(http_server_properties_dict); @@ -349,27 +348,27 @@ TEST_F(HttpServerPropertiesManagerTest, BadCachedHostPortPair) { } TEST_F(HttpServerPropertiesManagerTest, BadCachedAltProtocolPort) { - base::Value server_pref_dict(base::Value::Type::DICTIONARY); + base::Value::Dict server_pref_dict; // Set supports_spdy for www.google.com:80. - server_pref_dict.SetBoolKey("supports_spdy", true); + server_pref_dict.Set("supports_spdy", true); // Set up alternative_service for www.google.com:80. - base::Value alternative_service_dict(base::Value::Type::DICTIONARY); - alternative_service_dict.SetStringKey("protocol_str", "h2"); - alternative_service_dict.SetIntKey("port", 65536); - base::Value alternative_service_list(base::Value::Type::LIST); + base::Value::Dict alternative_service_dict; + alternative_service_dict.Set("protocol_str", "h2"); + alternative_service_dict.Set("port", 65536); + base::Value::List alternative_service_list; alternative_service_list.Append(std::move(alternative_service_dict)); - server_pref_dict.SetKey("alternative_service", - std::move(alternative_service_list)); + server_pref_dict.Set("alternative_service", + std::move(alternative_service_list)); // Set the server preference for www.google.com:80. - base::Value servers_dict(base::Value::Type::DICTIONARY); - servers_dict.SetKey("www.google.com:80", std::move(server_pref_dict)); - base::Value servers_list(base::Value::Type::LIST); + base::Value::Dict servers_dict; + servers_dict.Set("www.google.com:80", std::move(server_pref_dict)); + base::Value::List servers_list; servers_list.Append(std::move(servers_dict)); base::Value http_server_properties_dict = DictWithVersion(); - http_server_properties_dict.SetKey("servers", std::move(servers_list)); + http_server_properties_dict.GetDict().Set("servers", std::move(servers_list)); // Set up the pref. InitializePrefs(http_server_properties_dict); @@ -1028,38 +1027,37 @@ TEST_F(HttpServerPropertiesManagerTest, Clear) { // https://crbug.com/444956: Add 200 alternative_service servers followed by // supports_quic and verify we have read supports_quic from prefs. TEST_F(HttpServerPropertiesManagerTest, BadLastLocalAddressWhenQuicWorked) { - base::Value servers_list(base::Value::Type::LIST); + base::Value::List servers_list; for (int i = 1; i <= 200; ++i) { // Set up alternative_service for www.google.com:i. - base::Value server_dict(base::Value::Type::DICTIONARY); - base::Value alternative_service_dict(base::Value::Type::DICTIONARY); - alternative_service_dict.SetStringKey("protocol_str", "quic"); - alternative_service_dict.SetIntKey("port", i); - base::Value alternative_service_list(base::Value::Type::LIST); + base::Value::Dict server_dict; + base::Value::Dict alternative_service_dict; + alternative_service_dict.Set("protocol_str", "quic"); + alternative_service_dict.Set("port", i); + base::Value::List alternative_service_list; alternative_service_list.Append(std::move(alternative_service_dict)); - server_dict.SetKey("alternative_service", - std::move(alternative_service_list)); - server_dict.SetStringKey("server", - StringPrintf("https://www.google.com:%d", i)); - server_dict.SetKey("isolation", base::Value(base::Value::Type::LIST)); + server_dict.Set("alternative_service", std::move(alternative_service_list)); + server_dict.Set("server", StringPrintf("https://www.google.com:%d", i)); + server_dict.Set("isolation", base::Value(base::Value::Type::LIST)); servers_list.Append(std::move(server_dict)); } // Set the server preference for http://mail.google.com server. - base::Value server_dict2(base::Value::Type::DICTIONARY); - server_dict2.SetStringKey("server", "https://mail.google.com"); - server_dict2.SetKey("isolation", base::Value(base::Value::Type::LIST)); + base::Value::Dict server_dict2; + server_dict2.Set("server", "https://mail.google.com"); + server_dict2.Set("isolation", base::Value(base::Value::Type::LIST)); servers_list.Append(std::move(server_dict2)); base::Value http_server_properties_dict = DictWithVersion(); - http_server_properties_dict.SetKey("servers", std::move(servers_list)); + http_server_properties_dict.GetDict().Set("servers", std::move(servers_list)); // Set up SupportsQuic for 127.0.0.1 - base::Value supports_quic(base::Value::Type::DICTIONARY); - supports_quic.SetBoolKey("used_quic", true); - supports_quic.SetStringKey("address", "127.0.0.1"); - http_server_properties_dict.SetKey("supports_quic", std::move(supports_quic)); + base::Value::Dict supports_quic; + supports_quic.Set("used_quic", true); + supports_quic.Set("address", "127.0.0.1"); + http_server_properties_dict.GetDict().Set("supports_quic", + std::move(supports_quic)); // Set up the pref. InitializePrefs(http_server_properties_dict); @@ -1174,17 +1172,16 @@ TEST_F(HttpServerPropertiesManagerTest, UpdatePrefsWithCache) { ASSERT_TRUE(server_dict.is_dict()); // Extract and remove the "broken_until" string for "www.google.com:1234". - base::Value* broken_alt_svc_list = - server_dict.FindListKey("broken_alternative_services"); + base::Value::List* broken_alt_svc_list = + server_dict.GetDict().FindList("broken_alternative_services"); ASSERT_TRUE(broken_alt_svc_list); - ASSERT_EQ(2u, broken_alt_svc_list->GetListDeprecated().size()); - base::Value& broken_alt_svcs_list_entry = - broken_alt_svc_list->GetListDeprecated()[0]; + ASSERT_EQ(2u, broken_alt_svc_list->size()); + base::Value& broken_alt_svcs_list_entry = (*broken_alt_svc_list)[0]; const std::string* broken_until_str = - broken_alt_svcs_list_entry.FindStringKey("broken_until"); + broken_alt_svcs_list_entry.GetDict().FindString("broken_until"); ASSERT_TRUE(broken_until_str); const std::string expiration_string = *broken_until_str; - broken_alt_svcs_list_entry.RemoveKey("broken_until"); + broken_alt_svcs_list_entry.GetDict().Remove("broken_until"); // Expiration time of "www.google.com:1234" should be 5 minutes minus the // update-prefs-delay from when the prefs were written. @@ -1248,7 +1245,7 @@ TEST_F(HttpServerPropertiesManagerTest, ParseAlternativeServiceInfo) { const url::SchemeHostPort server("https", "example.com", 443); HttpServerProperties::ServerInfo server_info; EXPECT_TRUE(HttpServerPropertiesManager::ParseAlternativeServiceInfo( - server, *server_dict, &server_info)); + server, server_dict->GetDict(), &server_info)); ASSERT_TRUE(server_info.alternative_services.has_value()); AlternativeServiceInfoVector alternative_service_info_vector = @@ -1302,7 +1299,7 @@ TEST_F(HttpServerPropertiesManagerTest, DoNotLoadAltSvcForInsecureOrigins) { const url::SchemeHostPort server("http", "example.com", 80); HttpServerProperties::ServerInfo server_info; EXPECT_FALSE(HttpServerPropertiesManager::ParseAlternativeServiceInfo( - server, *server_dict, &server_info)); + server, server_dict->GetDict(), &server_info)); EXPECT_TRUE(server_info.empty()); } @@ -1352,38 +1349,40 @@ TEST_F(HttpServerPropertiesManagerTest, DoNotPersistExpiredAlternativeService) { const base::Value* pref_dict = pref_delegate_->GetServerProperties(); - const base::Value* servers_list = pref_dict->FindListKey("servers"); + const base::Value::List* servers_list = + pref_dict->GetDict().FindList("servers"); ASSERT_TRUE(servers_list); - auto it = servers_list->GetListDeprecated().begin(); + auto it = servers_list->begin(); const base::Value& server_pref_dict = *it; ASSERT_TRUE(server_pref_dict.is_dict()); - const std::string* server_str = server_pref_dict.FindStringKey("server"); + const std::string* server_str = + server_pref_dict.GetDict().FindString("server"); ASSERT_TRUE(server_str); EXPECT_EQ("https://www.example.com", *server_str); const base::Value* network_isolation_key_value = - server_pref_dict.FindKey("isolation"); + server_pref_dict.GetDict().Find("isolation"); ASSERT_TRUE(network_isolation_key_value); ASSERT_EQ(base::Value::Type::LIST, network_isolation_key_value->type()); - EXPECT_TRUE(network_isolation_key_value->GetListDeprecated().empty()); + EXPECT_TRUE(network_isolation_key_value->GetList().empty()); - const base::Value* altsvc_list = - server_pref_dict.FindListKey("alternative_service"); + const base::Value::List* altsvc_list = + server_pref_dict.GetDict().FindList("alternative_service"); ASSERT_TRUE(altsvc_list); - ASSERT_EQ(2u, altsvc_list->GetListDeprecated().size()); + ASSERT_EQ(2u, altsvc_list->size()); - const base::Value& altsvc_entry = altsvc_list->GetListDeprecated()[0]; + const base::Value& altsvc_entry = (*altsvc_list)[0]; ASSERT_TRUE(altsvc_entry.is_dict()); - const std::string* hostname = altsvc_entry.FindStringKey("host"); + const std::string* hostname = altsvc_entry.GetDict().FindString("host"); ASSERT_TRUE(hostname); EXPECT_EQ("broken.example.com", *hostname); - const base::Value& altsvc_entry2 = altsvc_list->GetListDeprecated()[1]; + const base::Value& altsvc_entry2 = (*altsvc_list)[1]; ASSERT_TRUE(altsvc_entry.is_dict()); - hostname = altsvc_entry2.FindStringKey("host"); + hostname = altsvc_entry2.GetDict().FindString("host"); ASSERT_TRUE(hostname); EXPECT_EQ("valid.example.com", *hostname); } @@ -1392,27 +1391,27 @@ TEST_F(HttpServerPropertiesManagerTest, DoNotPersistExpiredAlternativeService) { TEST_F(HttpServerPropertiesManagerTest, DoNotLoadExpiredAlternativeService) { InitializePrefs(); - base::Value alternative_service_list(base::Value::Type::LIST); - base::Value expired_dict(base::Value::Type::DICTIONARY); - expired_dict.SetStringKey("protocol_str", "h2"); - expired_dict.SetStringKey("host", "expired.example.com"); - expired_dict.SetIntKey("port", 443); + base::Value::List alternative_service_list; + base::Value::Dict expired_dict; + expired_dict.Set("protocol_str", "h2"); + expired_dict.Set("host", "expired.example.com"); + expired_dict.Set("port", 443); base::Time time_one_day_ago = base::Time::Now() - base::Days(1); - expired_dict.SetStringKey( - "expiration", base::NumberToString(time_one_day_ago.ToInternalValue())); + expired_dict.Set("expiration", + base::NumberToString(time_one_day_ago.ToInternalValue())); alternative_service_list.Append(std::move(expired_dict)); - base::Value valid_dict(base::Value::Type::DICTIONARY); - valid_dict.SetStringKey("protocol_str", "h2"); - valid_dict.SetStringKey("host", "valid.example.com"); - valid_dict.SetIntKey("port", 443); - valid_dict.SetStringKey( - "expiration", base::NumberToString(one_day_from_now_.ToInternalValue())); + base::Value::Dict valid_dict; + valid_dict.Set("protocol_str", "h2"); + valid_dict.Set("host", "valid.example.com"); + valid_dict.Set("port", 443); + valid_dict.Set("expiration", + base::NumberToString(one_day_from_now_.ToInternalValue())); alternative_service_list.Append(std::move(valid_dict)); - base::Value server_pref_dict(base::Value::Type::DICTIONARY); - server_pref_dict.SetKey("alternative_service", - std::move(alternative_service_list)); + base::Value::Dict server_pref_dict; + server_pref_dict.Set("alternative_service", + std::move(alternative_service_list)); const url::SchemeHostPort server("https", "example.com", 443); HttpServerProperties::ServerInfo server_info; @@ -1520,7 +1519,7 @@ TEST_F(HttpServerPropertiesManagerTest, PersistAdvertisedVersionsToPref) { "\"isolation\":[]," "\"server\":\"https://www.google.com:80\"}," "{\"alternative_service\":[{" - "\"advertised_alpns\":[\"h3\",\"h3-Q050\"]," + "\"advertised_alpns\":[\"h3\"]," "\"expiration\":\"9223372036854775807\"," "\"host\":\"foo.google.com\",\"port\":444,\"protocol_str\":\"quic\"}]," "\"isolation\":[]," @@ -1554,7 +1553,7 @@ TEST_F(HttpServerPropertiesManagerTest, ReadAdvertisedVersionsFromPref) { const url::SchemeHostPort server("https", "example.com", 443); HttpServerProperties::ServerInfo server_info; EXPECT_TRUE(HttpServerPropertiesManager::ParseAlternativeServiceInfo( - server, *server_dict, &server_info)); + server, server_dict->GetDict(), &server_info)); ASSERT_TRUE(server_info.alternative_services.has_value()); AlternativeServiceInfoVector alternative_service_info_vector = @@ -1633,7 +1632,7 @@ TEST_F(HttpServerPropertiesManagerTest, "\"server_info\":\"quic_server_info1\"}]," "\"servers\":[" "{\"alternative_service\":[{" - "\"advertised_alpns\":[\"h3\",\"h3-Q050\"]," + "\"advertised_alpns\":[\"h3\"]," "\"expiration\":\"13756212000000000\",\"port\":443," "\"protocol_str\":\"quic\"}]," "\"isolation\":[]," @@ -3030,14 +3029,14 @@ TEST_F(HttpServerPropertiesManagerTest, AdvertisedVersionsRoundTrip) { base::JSONReader::ReadDeprecated(preferences_json); ASSERT_TRUE(preferences_dict); ASSERT_TRUE(preferences_dict->is_dict()); - const base::Value* servers_list = preferences_dict->FindListKey("servers"); + const base::Value::List* servers_list = + preferences_dict->GetDict().FindList("servers"); ASSERT_TRUE(servers_list); - ASSERT_TRUE(servers_list->is_list()); - ASSERT_EQ(servers_list->GetListDeprecated().size(), 1u); - const base::Value& server_dict = servers_list->GetListDeprecated()[0]; + ASSERT_EQ(servers_list->size(), 1u); + const base::Value& server_dict = (*servers_list)[0]; HttpServerProperties::ServerInfo server_info; EXPECT_TRUE(HttpServerPropertiesManager::ParseAlternativeServiceInfo( - server, server_dict, &server_info)); + server, server_dict.GetDict(), &server_info)); ASSERT_TRUE(server_info.alternative_services.has_value()); AlternativeServiceInfoVector alternative_service_info_vector_out = server_info.alternative_services.value(); diff --git a/chromium/net/http/http_stream.h b/chromium/net/http/http_stream.h index 04d1507f8ce..80712a613ff 100644 --- a/chromium/net/http/http_stream.h +++ b/chromium/net/http/http_stream.h @@ -171,9 +171,9 @@ class NET_EXPORT_PRIVATE HttpStream { virtual void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) = 0; // Gets the remote endpoint of the socket that the HTTP stream is using, if - // any. Returns true and fills in |endpoint| if it is available; returns false - // and does not modify |endpoint| if it is unavailable. - virtual bool GetRemoteEndpoint(IPEndPoint* endpoint) = 0; + // any. Returns OK and fills in |endpoint| if it is available; returns an + // error and does not modify |endpoint| otherwise. + virtual int GetRemoteEndpoint(IPEndPoint* endpoint) = 0; // In the case of an HTTP error or redirect, flush the response body (usually // a simple error or "this page has moved") so that we can re-use the diff --git a/chromium/net/http/http_stream_factory.cc b/chromium/net/http/http_stream_factory.cc index 7812973922f..4b3a3463c5a 100644 --- a/chromium/net/http/http_stream_factory.cc +++ b/chromium/net/http/http_stream_factory.cc @@ -152,7 +152,11 @@ std::unique_ptr<HttpStreamRequest> HttpStreamFactory::RequestStreamInternal( auto job_controller = std::make_unique<JobController>( this, delegate, session_, job_factory_.get(), request_info, /* is_preconnect = */ false, is_websocket, enable_ip_based_pooling, - enable_alternative_services, server_ssl_config, proxy_ssl_config); + enable_alternative_services, + session_->context() + .quic_context->params() + ->delay_main_job_with_available_spdy_session, + server_ssl_config, proxy_ssl_config); JobController* job_controller_raw_ptr = job_controller.get(); job_controller_set_.insert(std::move(job_controller)); return job_controller_raw_ptr->Start(delegate, @@ -169,7 +173,11 @@ void HttpStreamFactory::PreconnectStreams(int num_streams, /*is_preconnect=*/true, /*is_websocket=*/false, /*enable_ip_based_pooling=*/true, - /*enable_alternative_services=*/true, /*server_ssl_config=*/SSLConfig(), + /*enable_alternative_services=*/true, + session_->context() + .quic_context->params() + ->delay_main_job_with_available_spdy_session, + /*server_ssl_config=*/SSLConfig(), /*proxy_ssl_config=*/SSLConfig()); JobController* job_controller_raw_ptr = job_controller.get(); job_controller_set_.insert(std::move(job_controller)); diff --git a/chromium/net/http/http_stream_factory_job.cc b/chromium/net/http/http_stream_factory_job.cc index b8583f068a9..f0a2edc34f1 100644 --- a/chromium/net/http/http_stream_factory_job.cc +++ b/chromium/net/http/http_stream_factory_job.cc @@ -124,7 +124,6 @@ HttpStreamFactory::Job::Job(Delegate* delegate, base::BindRepeating(&Job::OnIOComplete, base::Unretained(this))), connection_(new ClientSocketHandle), session_(session), - next_state_(STATE_NONE), destination_(std::move(destination)), origin_url_(origin_url), is_websocket_(is_websocket), @@ -147,15 +146,7 @@ HttpStreamFactory::Job::Job(Delegate* delegate, (ShouldForceQuic(session, destination_, proxy_info, using_ssl_))), quic_version_(quic_version), expect_spdy_(alternative_protocol == kProtoHTTP2 && !using_quic_), - using_spdy_(false), - should_reconsider_proxy_(false), quic_request_(session_->quic_stream_factory()), - expect_on_quic_host_resolution_(false), - using_existing_quic_session_(false), - establishing_tunnel_(false), - was_alpn_negotiated_(false), - negotiated_protocol_(kProtoUnknown), - num_streams_(0), pushed_stream_id_(kNoPushedStreamFound), spdy_session_key_( using_quic_ ? SpdySessionKey() @@ -164,12 +155,12 @@ HttpStreamFactory::Job::Job(Delegate* delegate, request_info_.privacy_mode, request_info_.socket_tag, request_info_.network_isolation_key, - request_info_.secure_dns_policy)), - stream_type_(HttpStreamRequest::BIDIRECTIONAL_STREAM), - init_connection_already_resumed_(false) { + request_info_.secure_dns_policy)) { // Websocket `destination` schemes should be converted to HTTP(S). - DCHECK(base::LowerCaseEqualsASCII(destination_.scheme(), url::kHttpScheme) || - base::LowerCaseEqualsASCII(destination_.scheme(), url::kHttpsScheme)); + DCHECK(base::EqualsCaseInsensitiveASCII(destination_.scheme(), + url::kHttpScheme) || + base::EqualsCaseInsensitiveASCII(destination_.scheme(), + url::kHttpsScheme)); // This class is specific to a single `ProxyServer`, so `proxy_info_` must be // non-empty. Entries beyond the first are ignored. It should simply take a @@ -308,6 +299,12 @@ void HttpStreamFactory::Job::SetPriority(RequestPriority priority) { // TODO(akalin): Maybe Propagate this to the preconnect state. } +bool HttpStreamFactory::Job::HasAvailableSpdySession() const { + return !using_quic_ && CanUseExistingSpdySession() && + session_->spdy_session_pool()->HasAvailableSession(spdy_session_key_, + is_websocket_); +} + bool HttpStreamFactory::Job::was_alpn_negotiated() const { return was_alpn_negotiated_; } @@ -356,7 +353,8 @@ bool HttpStreamFactory::Job::ShouldForceQuic( base::Contains(quic_params->origins_to_force_quic_on, HostPortPair::FromSchemeHostPort(destination))) && proxy_info.is_direct() && - base::LowerCaseEqualsASCII(destination.scheme(), url::kHttpsScheme); + base::EqualsCaseInsensitiveASCII(destination.scheme(), + url::kHttpsScheme); } // static @@ -409,7 +407,7 @@ void HttpStreamFactory::Job::OnStreamReadyCallback() { DCHECK_NE(job_type_, PRECONNECT); DCHECK(!is_websocket_ || try_websocket_over_http2_); - MaybeCopyConnectionAttemptsFromSocketOrHandle(); + MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnStreamReady(this, server_ssl_config_); // |this| may be deleted after this call. @@ -420,7 +418,7 @@ void HttpStreamFactory::Job::OnWebSocketHandshakeStreamReadyCallback() { DCHECK_NE(job_type_, PRECONNECT); DCHECK(is_websocket_); - MaybeCopyConnectionAttemptsFromSocketOrHandle(); + MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnWebSocketHandshakeStreamReady( this, server_ssl_config_, proxy_info_, std::move(websocket_stream_)); @@ -430,7 +428,7 @@ void HttpStreamFactory::Job::OnWebSocketHandshakeStreamReadyCallback() { void HttpStreamFactory::Job::OnBidirectionalStreamImplReadyCallback() { DCHECK(bidirectional_stream_impl_); - MaybeCopyConnectionAttemptsFromSocketOrHandle(); + MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnBidirectionalStreamImplReady(this, server_ssl_config_, proxy_info_); @@ -440,7 +438,7 @@ void HttpStreamFactory::Job::OnBidirectionalStreamImplReadyCallback() { void HttpStreamFactory::Job::OnStreamFailedCallback(int result) { DCHECK_NE(job_type_, PRECONNECT); - MaybeCopyConnectionAttemptsFromSocketOrHandle(); + MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnStreamFailed(this, result, server_ssl_config_); // |this| may be deleted after this call. @@ -452,7 +450,7 @@ void HttpStreamFactory::Job::OnCertificateErrorCallback( DCHECK_NE(job_type_, PRECONNECT); DCHECK(!spdy_session_request_); - MaybeCopyConnectionAttemptsFromSocketOrHandle(); + MaybeCopyConnectionAttemptsFromHandle(); delegate_->OnCertificateError(this, result, server_ssl_config_, ssl_info); // |this| may be deleted after this call. @@ -819,11 +817,18 @@ int HttpStreamFactory::Job::DoInitConnectionImpl() { DCHECK(!is_websocket_); DCHECK(request_info_.socket_tag == SocketTag()); + // The lifeime of the preconnect tasks is not controlled by |connection_|. + // It may outlives |this|. So we can't use |io_callback_| which holds + // base::Unretained(this). + auto callback = + base::BindOnce(&Job::OnIOComplete, ptr_factory_.GetWeakPtr()); + return PreconnectSocketsForHttpRequest( destination_, request_info_.load_flags, priority_, session_, proxy_info_, server_ssl_config_, proxy_ssl_config_, request_info_.privacy_mode, request_info_.network_isolation_key, - request_info_.secure_dns_policy, net_log_, num_streams_); + request_info_.secure_dns_policy, net_log_, num_streams_, + std::move(callback)); } ClientSocketPool::ProxyAuthCallback proxy_auth_callback = @@ -880,7 +885,8 @@ int HttpStreamFactory::Job::DoInitConnectionImplQuic() { std::move(destination), quic_version_, request_info_.privacy_mode, priority_, request_info_.socket_tag, request_info_.network_isolation_key, request_info_.secure_dns_policy, proxy_info_.is_direct(), - ssl_config->GetCertVerifyFlags(), url, net_log_, &net_error_details_, + /*require_dns_https_alpn=*/false, ssl_config->GetCertVerifyFlags(), url, + net_log_, &net_error_details_, base::BindOnce(&Job::OnFailedOnDefaultNetwork, ptr_factory_.GetWeakPtr()), io_callback_); if (rv == OK) { @@ -1239,21 +1245,12 @@ int HttpStreamFactory::Job::ReconsiderProxyAfterError(int error) { return error; } -// If the connection succeeds, failed connection attempts leading up to the -// success will be returned via the successfully connected socket. If the -// connection fails, failed connection attempts will be returned via the -// ClientSocketHandle. Check whether a socket was returned and copy the -// connection attempts from the proper place. -void HttpStreamFactory::Job::MaybeCopyConnectionAttemptsFromSocketOrHandle() { +void HttpStreamFactory::Job::MaybeCopyConnectionAttemptsFromHandle() { if (!connection_) return; - ConnectionAttempts socket_attempts = connection_->connection_attempts(); - if (connection_->socket()) { - connection_->socket()->GetConnectionAttempts(&socket_attempts); - } - - delegate_->AddConnectionAttemptsToRequest(this, socket_attempts); + delegate_->AddConnectionAttemptsToRequest(this, + connection_->connection_attempts()); } HttpStreamFactory::JobFactory::JobFactory() = default; diff --git a/chromium/net/http/http_stream_factory_job.h b/chromium/net/http/http_stream_factory_job.h index 6ce0c30b74c..1a176b71a8f 100644 --- a/chromium/net/http/http_stream_factory_job.h +++ b/chromium/net/http/http_stream_factory_job.h @@ -191,6 +191,10 @@ class HttpStreamFactory::Job void SetPriority(RequestPriority priority); + // Returns true if the current request can be immediately sent on a existing + // spdy session. + bool HasAvailableSpdySession() const; + const GURL& origin_url() const { return origin_url_; } RequestPriority priority() const { return priority_; } bool was_alpn_negotiated() const; @@ -334,7 +338,7 @@ class HttpStreamFactory::Job // code is simply returned. int ReconsiderProxyAfterError(int error); - void MaybeCopyConnectionAttemptsFromSocketOrHandle(); + void MaybeCopyConnectionAttemptsFromHandle(); // Returns true if the request should be throttled to allow for only one // connection attempt to be made to an H2 server at a time. @@ -351,7 +355,7 @@ class HttpStreamFactory::Job std::unique_ptr<ClientSocketHandle> connection_; const raw_ptr<HttpNetworkSession> session_; - State next_state_; + State next_state_ = STATE_NONE; // The server we are trying to reach, could be that of the origin or of the // alternative service (after applying host mapping rules). @@ -384,7 +388,7 @@ class HttpStreamFactory::Job // describe some proxy cases. const bool using_ssl_; - // True if Job actually uses HTTP/2. Note this describes both using QUIC + // True if Job actually uses QUIC. Note this describes both using QUIC // with an HTTPS origin, and proxying a cleartext HTTP request over an QUIC // proxy. This differs from `using_ssl_`, which only describes the origin. const bool using_quic_; @@ -401,37 +405,37 @@ class HttpStreamFactory::Job // True if Job actually uses HTTP/2. Note this describes both using HTTP/2 // with an HTTPS origin, and proxying a cleartext HTTP request over an HTTP/2 // proxy. This differs from `using_ssl_`, which only describes the origin. - bool using_spdy_; + bool using_spdy_ = false; // True if this job might succeed with a different proxy config. - bool should_reconsider_proxy_; + bool should_reconsider_proxy_ = false; QuicStreamRequest quic_request_; // Only valid for a QUIC job. Set when a QUIC connection is started. If true, // then OnQuicHostResolution() is expected to be called in the future. - bool expect_on_quic_host_resolution_; + bool expect_on_quic_host_resolution_ = false; // True if this job used an existing QUIC session. - bool using_existing_quic_session_; + bool using_existing_quic_session_ = false; // True when the tunnel is in the process of being established - we can't // read from the socket until the tunnel is done. - bool establishing_tunnel_; + bool establishing_tunnel_ = false; std::unique_ptr<HttpStream> stream_; std::unique_ptr<WebSocketHandshakeStreamBase> websocket_stream_; std::unique_ptr<BidirectionalStreamImpl> bidirectional_stream_impl_; // True if we negotiated ALPN. - bool was_alpn_negotiated_; + bool was_alpn_negotiated_ = false; // Protocol negotiated with the server. - NextProto negotiated_protocol_; + NextProto negotiated_protocol_ = kProtoUnknown; // 0 if we're not preconnecting. Otherwise, the number of streams to // preconnect. - int num_streams_; + int num_streams_ = 0; // Initialized when we have an existing SpdySession. base::WeakPtr<SpdySession> existing_spdy_session_; @@ -447,10 +451,11 @@ class HttpStreamFactory::Job const SpdySessionKey spdy_session_key_; // Type of stream that is requested. - HttpStreamRequest::StreamType stream_type_; + HttpStreamRequest::StreamType stream_type_ = + HttpStreamRequest::BIDIRECTIONAL_STREAM; // Whether Job has continued to DoInitConnection(). - bool init_connection_already_resumed_; + bool init_connection_already_resumed_ = false; base::OnceClosure restart_with_auth_callback_; diff --git a/chromium/net/http/http_stream_factory_job_controller.cc b/chromium/net/http/http_stream_factory_job_controller.cc index 98d2dfdc8f5..19dd80b8150 100644 --- a/chromium/net/http/http_stream_factory_job_controller.cc +++ b/chromium/net/http/http_stream_factory_job_controller.cc @@ -130,31 +130,22 @@ HttpStreamFactory::JobController::JobController( bool is_websocket, bool enable_ip_based_pooling, bool enable_alternative_services, + bool delay_main_job_with_available_spdy_session, const SSLConfig& server_ssl_config, const SSLConfig& proxy_ssl_config) : factory_(factory), session_(session), job_factory_(job_factory), - request_(nullptr), delegate_(delegate), is_preconnect_(is_preconnect), is_websocket_(is_websocket), enable_ip_based_pooling_(enable_ip_based_pooling), enable_alternative_services_(enable_alternative_services), - main_job_net_error_(OK), - alternative_job_net_error_(OK), - alternative_job_failed_on_default_network_(false), - job_bound_(false), - main_job_is_blocked_(false), - main_job_is_resumed_(false), - bound_job_(nullptr), - next_state_(STATE_RESOLVE_PROXY), - proxy_resolve_request_(nullptr), + delay_main_job_with_available_spdy_session_( + delay_main_job_with_available_spdy_session), request_info_(request_info), server_ssl_config_(server_ssl_config), proxy_ssl_config_(proxy_ssl_config), - num_streams_(0), - priority_(IDLE), net_log_(NetLogWithSource::Make( session->net_log(), NetLogSourceType::HTTP_STREAM_JOB_CONTROLLER)) { @@ -599,8 +590,23 @@ const NetLogWithSource* HttpStreamFactory::JobController::GetNetLog() const { void HttpStreamFactory::JobController::MaybeSetWaitTimeForMainJob( const base::TimeDelta& delay) { if (main_job_is_blocked_) { - main_job_wait_time_ = - std::min(delay, base::Seconds(kMaxDelayTimeForMainJobSecs)); + const bool has_available_spdy_session = + main_job_->HasAvailableSpdySession(); + if (!delay_main_job_with_available_spdy_session_ && + has_available_spdy_session) { + main_job_wait_time_ = base::TimeDelta(); + } else { + main_job_wait_time_ = + std::min(delay, base::Seconds(kMaxDelayTimeForMainJobSecs)); + } + if (has_available_spdy_session) { + UMA_HISTOGRAM_TIMES("Net.HttpJob.MainJobWaitTimeWithAvailableSpdySession", + main_job_wait_time_); + } else { + UMA_HISTOGRAM_TIMES( + "Net.HttpJob.MainJobWaitTimeWithoutAvailableSpdySession", + main_job_wait_time_); + } } } @@ -1107,7 +1113,7 @@ HttpStreamFactory::JobController::GetAlternativeServiceInfoInternal( QuicSessionKey session_key( HostPortPair::FromURL(mapped_origin), request_info.privacy_mode, request_info.socket_tag, request_info.network_isolation_key, - request_info.secure_dns_policy); + request_info.secure_dns_policy, /*require_dns_https_alpn=*/false); GURL destination = CreateAltSvcUrl( original_url, alternative_service_info.host_port_pair()); diff --git a/chromium/net/http/http_stream_factory_job_controller.h b/chromium/net/http/http_stream_factory_job_controller.h index 5b5cb2ad651..e6149e3f076 100644 --- a/chromium/net/http/http_stream_factory_job_controller.h +++ b/chromium/net/http/http_stream_factory_job_controller.h @@ -12,6 +12,7 @@ #include "base/memory/raw_ptr.h" #include "base/time/time.h" #include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" #include "net/base/privacy_mode.h" #include "net/http/http_stream_factory_job.h" #include "net/http/http_stream_request.h" @@ -42,6 +43,7 @@ class HttpStreamFactory::JobController bool is_websocket, bool enable_ip_based_pooling, bool enable_alternative_services, + bool delay_main_job_with_available_spdy_session, const SSLConfig& server_ssl_config, const SSLConfig& proxy_ssl_config); @@ -278,7 +280,7 @@ class HttpStreamFactory::JobController // reference and is safe as |request_| will notify |this| JobController // when it's destructed by calling OnRequestComplete(), which nulls // |request_|. - raw_ptr<HttpStreamRequest> request_; + raw_ptr<HttpStreamRequest> request_ = nullptr; const raw_ptr<HttpStreamRequest::Delegate> delegate_; @@ -306,40 +308,44 @@ class HttpStreamFactory::JobController // Error status used for alternative service brokenness reporting. // Net error code of the main job. Set to OK by default. - int main_job_net_error_; + int main_job_net_error_ = OK; // Net error code of the alternative job. Set to OK by default. - int alternative_job_net_error_; + int alternative_job_net_error_ = OK; // Set to true if the alternative job failed on the default network. - bool alternative_job_failed_on_default_network_; + bool alternative_job_failed_on_default_network_ = false; // True if a Job has ever been bound to the |request_|. - bool job_bound_; + bool job_bound_ = false; // True if the main job has to wait for the alternative job: i.e., the main // job must not create a connection until it is resumed. - bool main_job_is_blocked_; + bool main_job_is_blocked_ = false; // Handle for cancelling any posted delayed ResumeMainJob() task. base::CancelableOnceClosure resume_main_job_callback_; // True if the main job was blocked and has been resumed in ResumeMainJob(). - bool main_job_is_resumed_; + bool main_job_is_resumed_ = false; + + // If true, delay main job even the request can be sent immediately on an + // available SPDY session. + bool delay_main_job_with_available_spdy_session_; // Waiting time for the main job before it is resumed. base::TimeDelta main_job_wait_time_; // At the point where a Job is irrevocably tied to |request_|, we set this. // It will be nulled when the |request_| is finished. - raw_ptr<Job> bound_job_; + raw_ptr<Job> bound_job_ = nullptr; - State next_state_; + State next_state_ = STATE_RESOLVE_PROXY; std::unique_ptr<ProxyResolutionRequest> proxy_resolve_request_; const HttpRequestInfo request_info_; ProxyInfo proxy_info_; const SSLConfig server_ssl_config_; const SSLConfig proxy_ssl_config_; - int num_streams_; + int num_streams_ = 0; HttpStreamRequest::StreamType stream_type_; - RequestPriority priority_; + RequestPriority priority_ = IDLE; const NetLogWithSource net_log_; base::WeakPtrFactory<JobController> ptr_factory_{this}; diff --git a/chromium/net/http/http_stream_factory_job_controller_unittest.cc b/chromium/net/http/http_stream_factory_job_controller_unittest.cc index cd013d32fa1..c401c7b4167 100644 --- a/chromium/net/http/http_stream_factory_job_controller_unittest.cc +++ b/chromium/net/http/http_stream_factory_job_controller_unittest.cc @@ -55,6 +55,8 @@ #include "net/socket/socket_test_util.h" #include "net/spdy/spdy_session_key.h" #include "net/spdy/spdy_test_util_common.h" +#include "net/test/cert_test_util.h" +#include "net/test/test_data_directory.h" #include "net/test/test_with_task_environment.h" #include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" @@ -114,14 +116,6 @@ class MockPrefDelegate : public HttpServerProperties::PrefDelegate { class HttpStreamFactoryJobPeer { public: - static void Start(HttpStreamFactory::Job* job, - HttpStreamRequest::StreamType stream_type) { - // Start() is mocked for MockHttpStreamFactoryJob. - // This is the alternative method to invoke real Start() method on Job. - job->stream_type_ = stream_type; - job->StartInternal(); - } - // Returns |num_streams_| of |job|. It should be 0 for non-preconnect Jobs. static int GetNumStreams(const HttpStreamFactory::Job* job) { return job->num_streams_; @@ -215,6 +209,11 @@ class HttpStreamFactoryJobControllerTest : public TestWithTaskEnvironment { enable_ip_based_pooling_ = false; } + void SetNotDelayMainJobWithAvailableSpdySession() { + ASSERT_FALSE(test_proxy_delegate_); + delay_main_job_with_available_spdy_session_ = false; + } + void DisableAlternativeServices() { ASSERT_FALSE(test_proxy_delegate_); enable_alternative_services_ = false; @@ -252,7 +251,8 @@ class HttpStreamFactoryJobControllerTest : public TestWithTaskEnvironment { job_controller_ = new HttpStreamFactory::JobController( factory_, &request_delegate_, session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller_); } @@ -354,6 +354,7 @@ class HttpStreamFactoryJobControllerTest : public TestWithTaskEnvironment { bool is_preconnect_ = false; bool enable_ip_based_pooling_ = true; bool enable_alternative_services_ = true; + bool delay_main_job_with_available_spdy_session_ = true; private: std::unique_ptr<TestProxyDelegate> test_proxy_delegate_; @@ -483,7 +484,8 @@ class JobControllerReconsiderProxyAfterErrorTest new HttpStreamFactory::JobController( factory_, &request_delegate_, session_.get(), &default_job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); return job_controller->Start( @@ -1862,11 +1864,12 @@ void HttpStreamFactoryJobControllerTest:: // Run message loop to make the main job succeed. base::RunLoop().RunUntilIdle(); + request_.reset(); + // If alt job was retried on the alternate network, the alternative service // should be marked broken until the default network changes. VerifyBrokenAlternateProtocolMapping(request_info, alt_job_retried_on_non_default_network); - request_.reset(); EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); if (alt_job_retried_on_non_default_network) { // Verify the brokenness is cleared when the default network changes. @@ -1889,7 +1892,7 @@ TEST_F(HttpStreamFactoryJobControllerTest, // changes. TEST_F(HttpStreamFactoryJobControllerTest, MainJobSucceedsAfterAltJobSucceededOnAlternateNetwork) { - TestAltJobSucceedsAfterMainJobSucceeded(true); + TestMainJobSucceedsAfterAltJobSucceeded(true); } void HttpStreamFactoryJobControllerTest::TestMainJobFailsAfterAltJobSucceeded( @@ -2685,7 +2688,8 @@ TEST_F(HttpStreamFactoryJobControllerTest, new HttpStreamFactory::JobController( factory_, &request_delegate, session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); job_controller->Preconnect(/*num_streams=*/5); @@ -2702,6 +2706,58 @@ TEST_F(HttpStreamFactoryJobControllerTest, } } +TEST_F(HttpStreamFactoryJobControllerTest, + DonotDelayMainJobIfHasAvailableSpdySession) { + SetNotDelayMainJobWithAvailableSpdySession(); + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + // Put a SpdySession in the pool. + HostPortPair host_port_pair("www.google.com", 443); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + PRIVACY_MODE_DISABLED, + SpdySessionKey::IsProxySession::kFalse, SocketTag(), + NetworkIsolationKey(), SecureDnsPolicy::kAllow); + std::ignore = CreateFakeSpdySession(session_->spdy_session_pool(), key); + + // Handshake will fail asynchronously after mock data is unpaused. + MockQuicData quic_data(version_); + quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause + quic_data.AddRead(ASYNC, ERR_FAILED); + quic_data.AddWrite(ASYNC, ERR_FAILED); + quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_is_quic_known_to_work_on_current_network(true); + ServerNetworkStats stats1; + stats1.srtt = base::Milliseconds(100); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://www.google.com")), + NetworkIsolationKey(), stats1); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // This prevents handshake from immediately succeeding. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_, + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + // The main job shouldn't have any delay since the request can be sent on + // available SPDY session. + EXPECT_FALSE(job_controller_->ShouldWait( + const_cast<net::HttpStreamFactory::Job*>(job_controller_->main_job()))); +} + // Check the case that while a preconnect is waiting in the H2 request queue, // and a SPDY session appears, the job completes successfully. TEST_F(HttpStreamFactoryJobControllerTest, SpdySessionInterruptsPreconnect) { @@ -2737,7 +2793,8 @@ TEST_F(HttpStreamFactoryJobControllerTest, SpdySessionInterruptsPreconnect) { new HttpStreamFactory::JobController( factory_, &preconnect_request_delegate, session_.get(), &job_factory_, request_info, true /* is_preconnect */, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); job_controller->Preconnect(1); @@ -2768,6 +2825,152 @@ TEST_F(HttpStreamFactoryJobControllerTest, SpdySessionInterruptsPreconnect) { EXPECT_TRUE(spdy_session); } +// This test verifies that a preconnect job doesn't block subsequent requests +// which can use an existing IP based pooled SpdySession. +// This test uses "wildcard.pem" to support IpBasedPooling for *.example.org, +// and starts 3 requests: +// [1] Normal non-preconnect request to www.example.org. +// [2] Preconnect request to other.example.org. The connection is paused until +// OnConnectComplete() is called in the end of the test. +// [3] Normal non-preconnect request to other.example.org. This request must +// succeed even while the preconnect request [2] is paused. +TEST_F(HttpStreamFactoryJobControllerTest, + PreconnectJobDoesntBlockIpBasedPooling) { + // Make sure that both "www.example.org" and "other.example.org" are pointing + // to the same IP address. + std::vector<HostResolverEndpointResult> endpoints; + HostResolverEndpointResult endpoint_result; + endpoint_result.ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)}; + endpoints.push_back(endpoint_result); + session_deps_.host_resolver->rules()->AddRule("www.example.org", endpoints); + session_deps_.host_resolver->rules()->AddRule("other.example.org", endpoints); + // Make |host_resolver| asynchronous to simulate the issue of + // crbug.com/1320608. + session_deps_.host_resolver->set_synchronous_mode(false); + + // This is used for the non-preconnect requests [1] and [3]. + MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0)}; + MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1)}; + SequencedSocketData first_socket(reads, writes); + first_socket.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider(&first_socket); + + // This is used for the non-preconnect requests. + SSLSocketDataProvider ssl_data1(ASYNC, OK); + ssl_data1.next_proto = kProtoHTTP2; + // "wildcard.pem" supports "*.example.org". + ssl_data1.ssl_info.cert = + ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1); + + // This is used for the preconnect request. + SequencedSocketData second_socket; + // The connection is paused. And it will be completed with + // ERR_CONNECTION_FAILED. + second_socket.set_connect_data(MockConnect(ASYNC, ERR_IO_PENDING)); + session_deps_.socket_factory->AddSocketDataProvider(&second_socket); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.example.org"); + Initialize(request_info); + + // Start a non-preconnect request [1]. + { + std::unique_ptr<HttpStreamRequest> stream_request = job_controller_->Start( + &request_delegate_, + /*websocket_handshake_stream_create_helper=*/nullptr, + NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + base::RunLoop run_loop; + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)) + .WillOnce([&run_loop]() { run_loop.Quit(); }); + run_loop.Run(); + } + + // Sanity check - make sure the SpdySession was created. + { + base::WeakPtr<SpdySession> spdy_session = + session_->spdy_session_pool()->FindAvailableSession( + SpdySessionKey(HostPortPair::FromURL(request_info.url), + ProxyServer::Direct(), request_info.privacy_mode, + SpdySessionKey::IsProxySession::kFalse, + request_info.socket_tag, + request_info.network_isolation_key, + request_info.secure_dns_policy), + /*enable_ip_based_pooling=*/false, /*is_websocket=*/false, + NetLogWithSource()); + EXPECT_TRUE(spdy_session); + } + + HttpRequestInfo other_request_info; + other_request_info.method = "GET"; + other_request_info.url = GURL("https://other.example.org"); + + // Create and start a preconnect request [2]. + MockHttpStreamRequestDelegate preconnect_request_delegate; + HttpStreamFactory::JobController* preconnect_job_controller = + new HttpStreamFactory::JobController( + factory_, &preconnect_request_delegate, session_.get(), &job_factory_, + other_request_info, /*is_preconnect=*/true, + /*is_websocket=*/false, /*enable_ip_based_pooling=*/true, + enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, preconnect_job_controller); + preconnect_job_controller->Preconnect(1); + base::RunLoop().RunUntilIdle(); + + // The SpdySession is available for IP based pooling when the host resolution + // has finished. + { + const SpdySessionKey spdy_session_key = SpdySessionKey( + HostPortPair::FromURL(other_request_info.url), ProxyServer::Direct(), + other_request_info.privacy_mode, SpdySessionKey::IsProxySession::kFalse, + other_request_info.socket_tag, other_request_info.network_isolation_key, + other_request_info.secure_dns_policy); + EXPECT_FALSE(session_->spdy_session_pool()->FindAvailableSession( + spdy_session_key, /*enable_ip_based_pooling=*/false, + /*is_websocket=*/false, NetLogWithSource())); + EXPECT_TRUE(session_->spdy_session_pool()->FindAvailableSession( + spdy_session_key, /*enable_ip_based_pooling=*/true, + /*is_websocket=*/false, NetLogWithSource())); + } + + // Create and start a second non-preconnect request [3]. + { + MockHttpStreamRequestDelegate request_delegate; + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, &request_delegate, session_.get(), &job_factory_, + other_request_info, /*is_preconnect=*/false, + /*is_websocket=*/false, /*enable_ip_based_pooling=*/true, + enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + std::unique_ptr<HttpStreamRequest> second_stream_request = + job_controller->Start( + &request_delegate, + /*websocket_handshake_stream_create_helper=*/nullptr, + NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, + DEFAULT_PRIORITY); + + base::RunLoop run_loop; + EXPECT_CALL(request_delegate, OnStreamReadyImpl(_, _, _)) + .WillOnce([&run_loop]() { run_loop.Quit(); }); + run_loop.Run(); + second_stream_request.reset(); + } + + second_socket.socket()->OnConnectComplete( + MockConnect(SYNCHRONOUS, ERR_CONNECTION_FAILED)); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + EXPECT_TRUE(first_socket.AllReadDataConsumed()); + EXPECT_TRUE(first_socket.AllWriteDataConsumed()); +} + class JobControllerLimitMultipleH2Requests : public HttpStreamFactoryJobControllerTest { protected: @@ -2805,7 +3008,8 @@ TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequests) { new HttpStreamFactory::JobController( factory_, request_delegates[i].get(), session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); auto request = job_controller->Start( @@ -2892,7 +3096,9 @@ TEST_F(JobControllerLimitMultipleH2Requests, factory_, request_delegates[i].get(), session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, enable_ip_based_pooling_, - enable_alternative_services_, SSLConfig(), SSLConfig()); + enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), + SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); auto request = job_controller->Start( request_delegates[i].get(), nullptr, net_log_with_source_, @@ -2967,7 +3173,8 @@ TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequestsFirstRequestHang) { new HttpStreamFactory::JobController( factory_, request_delegates[i].get(), session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); auto request = job_controller->Start( @@ -3040,7 +3247,8 @@ TEST_F(JobControllerLimitMultipleH2Requests, new HttpStreamFactory::JobController( factory_, request_delegates[i].get(), session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); auto request = job_controller->Start( @@ -3095,7 +3303,8 @@ TEST_F(JobControllerLimitMultipleH2Requests, MultiplePreconnects) { new HttpStreamFactory::JobController( factory_, request_delegates[i].get(), session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); job_controller->Preconnect(1); @@ -3143,7 +3352,8 @@ TEST_F(JobControllerLimitMultipleH2Requests, H1NegotiatedForFirstRequest) { new HttpStreamFactory::JobController( factory_, request_delegates[i].get(), session_.get(), &job_factory_, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); auto request = job_controller->Start( @@ -3205,7 +3415,8 @@ TEST_F(JobControllerLimitMultipleH2Requests, QuicJobNotThrottled) { new HttpStreamFactory::JobController( factory_, &request_delegate_, session_.get(), &default_job_factory, request_info, is_preconnect_, false /* is_websocket */, - enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + enable_ip_based_pooling_, enable_alternative_services_, + delay_main_job_with_available_spdy_session_, SSLConfig(), SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller); request_ = @@ -3303,7 +3514,9 @@ class HttpStreamFactoryJobControllerPreconnectTest request_info_, /* is_preconnect = */ true, /* is_websocket = */ false, /* enable_ip_based_pooling = */ true, - /* enable_alternative_services = */ true, SSLConfig(), SSLConfig()); + /* enable_alternative_services = */ true, + /* delay_main_job_with_available_spdy_session = */ true, SSLConfig(), + SSLConfig()); HttpStreamFactoryPeer::AddJobController(factory_, job_controller_); } diff --git a/chromium/net/http/http_stream_factory_test_util.cc b/chromium/net/http/http_stream_factory_test_util.cc index 3da68eec0e6..5b7a376b708 100644 --- a/chromium/net/http/http_stream_factory_test_util.cc +++ b/chromium/net/http/http_stream_factory_test_util.cc @@ -52,10 +52,7 @@ MockHttpStreamFactoryJob::MockHttpStreamFactoryJob( MockHttpStreamFactoryJob::~MockHttpStreamFactoryJob() = default; -TestJobFactory::TestJobFactory() - : main_job_(nullptr), - alternative_job_(nullptr), - override_main_job_url_(false) {} +TestJobFactory::TestJobFactory() = default; TestJobFactory::~TestJobFactory() = default; diff --git a/chromium/net/http/http_stream_factory_test_util.h b/chromium/net/http/http_stream_factory_test_util.h index 163129859bb..ac89b431dba 100644 --- a/chromium/net/http/http_stream_factory_test_util.h +++ b/chromium/net/http/http_stream_factory_test_util.h @@ -174,9 +174,9 @@ class TestJobFactory : public HttpStreamFactory::JobFactory { } private: - raw_ptr<MockHttpStreamFactoryJob> main_job_; - raw_ptr<MockHttpStreamFactoryJob> alternative_job_; - bool override_main_job_url_; + raw_ptr<MockHttpStreamFactoryJob> main_job_ = nullptr; + raw_ptr<MockHttpStreamFactoryJob> alternative_job_ = nullptr; + bool override_main_job_url_ = false; GURL main_job_alternative_url_; }; diff --git a/chromium/net/http/http_stream_factory_unittest.cc b/chromium/net/http/http_stream_factory_unittest.cc index 7145365d4ac..e11997a6ae8 100644 --- a/chromium/net/http/http_stream_factory_unittest.cc +++ b/chromium/net/http/http_stream_factory_unittest.cc @@ -25,6 +25,7 @@ #include "build/build_config.h" #include "net/base/completion_once_callback.h" #include "net/base/features.h" +#include "net/base/net_errors.h" #include "net/base/network_isolation_key.h" #include "net/base/port_util.h" #include "net/base/privacy_mode.h" @@ -163,7 +164,9 @@ class MockWebSocketHandshakeStream : public WebSocketHandshakeStreamBase { } void GetSSLInfo(SSLInfo* ssl_info) override {} void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {} - bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } + int GetRemoteEndpoint(IPEndPoint* endpoint) override { + return ERR_UNEXPECTED; + } void Drain(HttpNetworkSession* session) override {} void PopulateNetErrorDetails(NetErrorDetails* details) override { return; } void SetPriority(RequestPriority priority) override {} @@ -189,11 +192,8 @@ class MockWebSocketHandshakeStream : public WebSocketHandshakeStreamBase { class MockHttpStreamFactoryForPreconnect : public HttpStreamFactory { public: explicit MockHttpStreamFactoryForPreconnect(HttpNetworkSession* session) - : HttpStreamFactory(session), - preconnect_done_(false), - waiting_for_preconnect_(false) {} - - ~MockHttpStreamFactoryForPreconnect() override {} + : HttpStreamFactory(session) {} + ~MockHttpStreamFactoryForPreconnect() override = default; void WaitForPreconnects() { while (!preconnect_done_) { @@ -211,14 +211,14 @@ class MockHttpStreamFactoryForPreconnect : public HttpStreamFactory { loop_.QuitWhenIdle(); } - bool preconnect_done_; - bool waiting_for_preconnect_; + bool preconnect_done_ = false; + bool waiting_for_preconnect_ = false; base::RunLoop loop_; }; class StreamRequestWaiter : public HttpStreamRequest::Delegate { public: - StreamRequestWaiter() : error_status_(OK) {} + StreamRequestWaiter() = default; StreamRequestWaiter(const StreamRequestWaiter&) = delete; StreamRequestWaiter& operator=(const StreamRequestWaiter&) = delete; @@ -319,7 +319,7 @@ class StreamRequestWaiter : public HttpStreamRequest::Delegate { std::unique_ptr<BidirectionalStreamImpl> bidirectional_stream_impl_; SSLConfig used_ssl_config_; ProxyInfo used_proxy_info_; - int error_status_; + int error_status_ = OK; }; class WebSocketBasicHandshakeStream : public MockWebSocketHandshakeStream { @@ -423,8 +423,7 @@ class CapturePreconnectsTransportSocketPool : public TransportClientSocketPool { base::TimeDelta(), ProxyServer::Direct(), false /* is_for_websockets */, - common_connect_job_params), - last_num_streams_(-1) {} + common_connect_job_params) {} int last_num_streams() const { return last_num_streams_; } const ClientSocketPool::GroupId& last_group_id() const { @@ -457,14 +456,16 @@ class CapturePreconnectsTransportSocketPool : public TransportClientSocketPool { return ERR_UNEXPECTED; } - void RequestSockets( + int RequestSockets( const ClientSocketPool::GroupId& group_id, scoped_refptr<ClientSocketPool::SocketParams> socket_params, const absl::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag, int num_sockets, + CompletionOnceCallback callback, const NetLogWithSource& net_log) override { last_num_streams_ = num_sockets; last_group_id_ = group_id; + return OK; } void CancelRequest(const ClientSocketPool::GroupId& group_id, @@ -496,7 +497,7 @@ class CapturePreconnectsTransportSocketPool : public TransportClientSocketPool { } private: - int last_num_streams_; + int last_num_streams_ = -1; ClientSocketPool::GroupId last_group_id_; }; @@ -946,8 +947,7 @@ class TestBidirectionalDelegate : public BidirectionalStreamImpl::Delegate { // Simplify ownership issues and the interaction with the MockSocketFactory. class MockQuicData { public: - explicit MockQuicData(quic::ParsedQuicVersion version) - : packet_number_(0), printer_(version) {} + explicit MockQuicData(quic::ParsedQuicVersion version) : printer_(version) {} ~MockQuicData() = default; @@ -977,7 +977,7 @@ class MockQuicData { std::vector<std::unique_ptr<quic::QuicEncryptedPacket>> packets_; std::vector<MockWrite> writes_; std::vector<MockRead> reads_; - size_t packet_number_; + size_t packet_number_ = 0; QuicPacketPrinter printer_; std::unique_ptr<SequencedSocketData> socket_data_; }; @@ -1065,10 +1065,10 @@ int GetHandedOutSocketCount(ClientSocketPool* pool) { // Return count of distinct QUIC sessions. int GetQuicSessionCount(HttpNetworkSession* session) { base::Value dict(session->QuicInfoToValue()); - base::Value* session_list = dict.FindListKey("sessions"); + base::Value::List* session_list = dict.GetDict().FindList("sessions"); if (!session_list) return -1; - return session_list->GetListDeprecated().size(); + return session_list->size(); } TEST_F(HttpStreamFactoryTest, PrivacyModeUsesDifferentSocketPoolGroup) { @@ -2086,7 +2086,7 @@ class HttpStreamFactoryBidirectionalQuicTest MockHostResolver* host_resolver() { return &host_resolver_; } private: - QuicFlagSaver saver_; + quic::test::QuicFlagSaver saver_; const quic::ParsedQuicVersion version_; const bool client_headers_include_h2_stream_dependency_; MockQuicContext quic_context_; diff --git a/chromium/net/http/http_stream_parser.cc b/chromium/net/http/http_stream_parser.cc index 0637a502b9a..7a0e8f0e4ca 100644 --- a/chromium/net/http/http_stream_parser.cc +++ b/chromium/net/http/http_stream_parser.cc @@ -13,6 +13,7 @@ #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/metrics/histogram_macros.h" +#include "base/numerics/clamped_math.h" #include "base/strings/string_util.h" #include "base/values.h" #include "net/base/io_buffer.h" @@ -24,6 +25,7 @@ #include "net/http/http_request_info.h" #include "net/http/http_response_headers.h" #include "net/http/http_response_info.h" +#include "net/http/http_status_code.h" #include "net/http/http_util.h" #include "net/log/net_log_event_type.h" #include "net/socket/ssl_client_socket.h" @@ -113,12 +115,7 @@ bool ShouldTryReadingOnUploadError(int error_code) { class HttpStreamParser::SeekableIOBuffer : public IOBuffer { public: explicit SeekableIOBuffer(int capacity) - : IOBuffer(capacity), - real_data_(data_), - capacity_(capacity), - size_(0), - used_(0) { - } + : IOBuffer(capacity), real_data_(data_), capacity_(capacity) {} // DidConsume() changes the |data_| pointer so that |data_| always points // to the first unconsumed byte. @@ -171,8 +168,8 @@ class HttpStreamParser::SeekableIOBuffer : public IOBuffer { raw_ptr<char> real_data_; const int capacity_; - int size_; - int used_; + int size_ = 0; + int used_ = 0; }; // 2 CRLFs + max of 8 hex chars. @@ -183,26 +180,12 @@ HttpStreamParser::HttpStreamParser(StreamSocket* stream_socket, const HttpRequestInfo* request, GrowableIOBuffer* read_buffer, const NetLogWithSource& net_log) - : io_state_(STATE_NONE), - request_(request), - request_headers_(nullptr), - request_headers_length_(0), + : request_(request), read_buf_(read_buffer), - read_buf_unused_offset_(0), response_header_start_offset_(std::string::npos), - received_bytes_(0), - sent_bytes_(0), - response_(nullptr), - response_body_length_(-1), - response_is_keep_alive_(false), - response_body_read_(0), - user_read_buf_(nullptr), - user_read_buf_len_(0), stream_socket_(stream_socket), connection_is_reused_(connection_is_reused), - net_log_(net_log), - sent_last_chunk_(false), - upload_error_(OK) { + net_log_(net_log) { io_callback_ = base::BindRepeating(&HttpStreamParser::OnIOComplete, weak_ptr_factory_.GetWeakPtr()); } @@ -912,7 +895,7 @@ int HttpStreamParser::HandleReadHeaderResult(int result) { response_body_length_ = -1; // Record the timing of the 103 Early Hints response for the experiment // (https://crbug.com/1093693). - if (response_->headers->response_code() == 103 && + if (response_->headers->response_code() == net::HTTP_EARLY_HINTS && first_early_hints_time_.is_null()) { first_early_hints_time_ = current_response_start_time_; } @@ -1018,7 +1001,7 @@ int HttpStreamParser::ParseResponseHeaders(int end_offset) { // See // https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/qS63pYso4P0 if (read_buf_->offset() < 3 || scheme != "http" || - !base::LowerCaseEqualsASCII( + !base::EqualsCaseInsensitiveASCII( base::StringPiece(read_buf_->StartOfBuffer(), 3), "icy")) { return ERR_INVALID_HTTP_RESPONSE; } @@ -1087,9 +1070,9 @@ void HttpStreamParser::CalculateResponseBodySize() { response_body_length_ = 0; } else { switch (response_->headers->response_code()) { - case 204: // No Content - case 205: // Reset Content - case 304: // Not Modified + case net::HTTP_NO_CONTENT: // No Content + case net::HTTP_RESET_CONTENT: // Reset Content + case net::HTTP_NOT_MODIFIED: // Not Modified response_body_length_ = 0; break; } diff --git a/chromium/net/http/http_stream_parser.h b/chromium/net/http/http_stream_parser.h index 16b5c49983b..73a4bc560ea 100644 --- a/chromium/net/http/http_stream_parser.h +++ b/chromium/net/http/http_stream_parser.h @@ -211,7 +211,7 @@ class NET_EXPORT_PRIVATE HttpStreamParser { bool SendRequestBuffersEmpty(); // Next state of the request, when the current one completes. - State io_state_; + State io_state_ = STATE_NONE; // Null when read state machine is invoked. raw_ptr<const HttpRequestInfo> request_; @@ -221,7 +221,7 @@ class NET_EXPORT_PRIVATE HttpStreamParser { // Size of just the request headers. May be less than the length of // |request_headers_| if the body was merged with the headers. - int request_headers_length_; + int request_headers_length_ = 0; // Temporary buffer for reading. scoped_refptr<GrowableIOBuffer> read_buf_; @@ -229,7 +229,7 @@ class NET_EXPORT_PRIVATE HttpStreamParser { // Offset of the first unused byte in |read_buf_|. May be nonzero due to // body data in the same packet as header data but is zero when reading // headers. - int read_buf_unused_offset_; + int read_buf_unused_offset_ = 0; // The amount beyond |read_buf_unused_offset_| where the status line starts; // std::string::npos if not found yet. @@ -237,16 +237,16 @@ class NET_EXPORT_PRIVATE HttpStreamParser { // The amount of received data. If connection is reused then intermediate // value may be bigger than final. - int64_t received_bytes_; + int64_t received_bytes_ = 0; // The amount of sent data. - int64_t sent_bytes_; + int64_t sent_bytes_ = 0; // The parsed response headers. Owned by the caller of SendRequest. This // cannot be safely accessed after reading the final set of headers, as the // caller of SendRequest may have been destroyed - this happens in the case an // HttpResponseBodyDrainer is used. - raw_ptr<HttpResponseInfo> response_; + raw_ptr<HttpResponseInfo> response_ = nullptr; // Time at which the first bytes of the first header response including // informational responses (1xx) are about to be parsed. This corresponds to @@ -271,10 +271,10 @@ class NET_EXPORT_PRIVATE HttpStreamParser { // Indicates the content length. If this value is less than zero // (and chunked_decoder_ is null), then we must read until the server // closes the connection. - int64_t response_body_length_; + int64_t response_body_length_ = -1; // True if reading a keep-alive response. False if not, or if don't yet know. - bool response_is_keep_alive_; + bool response_is_keep_alive_ = false; // True if we've seen a response that has an HTTP status line. This is // persistent across multiple response parsing. If we see a status line @@ -282,14 +282,14 @@ class NET_EXPORT_PRIVATE HttpStreamParser { bool has_seen_status_line_ = false; // Keep track of the number of response body bytes read so far. - int64_t response_body_read_; + int64_t response_body_read_ = 0; // Helper if the data is chunked. std::unique_ptr<HttpChunkedDecoder> chunked_decoder_; // Where the caller wants the body data. scoped_refptr<IOBuffer> user_read_buf_; - int user_read_buf_len_; + int user_read_buf_len_ = 0; // The callback to notify a user that the handshake has been confirmed. CompletionOnceCallback confirm_handshake_callback_; @@ -317,10 +317,10 @@ class NET_EXPORT_PRIVATE HttpStreamParser { // Buffer used to send the request body. This points the same buffer as // |request_body_read_buf_| unless the data is chunked. scoped_refptr<SeekableIOBuffer> request_body_send_buf_; - bool sent_last_chunk_; + bool sent_last_chunk_ = false; // Error received when uploading the body, if any. - int upload_error_; + int upload_error_ = OK; MutableNetworkTrafficAnnotationTag traffic_annotation_; diff --git a/chromium/net/http/http_stream_parser_unittest.cc b/chromium/net/http/http_stream_parser_unittest.cc index 62373540a51..d870cd1c398 100644 --- a/chromium/net/http/http_stream_parser_unittest.cc +++ b/chromium/net/http/http_stream_parser_unittest.cc @@ -1227,10 +1227,9 @@ class SimpleGetRunner { public: SimpleGetRunner() : url_("http://localhost"), - read_buffer_(base::MakeRefCounted<GrowableIOBuffer>()), - sequence_number_(0) { - writes_.push_back(MockWrite( - SYNCHRONOUS, sequence_number_++, "GET / HTTP/1.1\r\n\r\n")); + read_buffer_(base::MakeRefCounted<GrowableIOBuffer>()) { + writes_.emplace_back( + MockWrite(SYNCHRONOUS, sequence_number_++, "GET / HTTP/1.1\r\n\r\n")); } void set_url(const GURL& url) { url_ = url; } @@ -1313,7 +1312,7 @@ class SimpleGetRunner { std::unique_ptr<StreamSocket> stream_socket_; std::unique_ptr<SequencedSocketData> data_; std::unique_ptr<HttpStreamParser> parser_; - int sequence_number_; + int sequence_number_ = 0; }; // Test that HTTP/0.9 works as expected, only on ports where it should be diff --git a/chromium/net/http/http_stream_request.cc b/chromium/net/http/http_stream_request.cc index 980bc740455..c2446311e23 100644 --- a/chromium/net/http/http_stream_request.cc +++ b/chromium/net/http/http_stream_request.cc @@ -29,10 +29,6 @@ HttpStreamRequest::HttpStreamRequest( websocket_handshake_stream_create_helper_( websocket_handshake_stream_create_helper), net_log_(net_log), - completed_(false), - was_alpn_negotiated_(false), - negotiated_protocol_(kProtoUnknown), - using_spdy_(false), stream_type_(stream_type) { net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_REQUEST); } diff --git a/chromium/net/http/http_stream_request.h b/chromium/net/http/http_stream_request.h index a96da693891..8b8eb41dfdf 100644 --- a/chromium/net/http/http_stream_request.h +++ b/chromium/net/http/http_stream_request.h @@ -226,11 +226,11 @@ class NET_EXPORT_PRIVATE HttpStreamRequest { websocket_handshake_stream_create_helper_; const NetLogWithSource net_log_; - bool completed_; - bool was_alpn_negotiated_; + bool completed_ = false; + bool was_alpn_negotiated_ = false; // Protocol negotiated with the server. - NextProto negotiated_protocol_; - bool using_spdy_; + NextProto negotiated_protocol_ = kProtoUnknown; + bool using_spdy_ = false; ConnectionAttempts connection_attempts_; const StreamType stream_type_; }; diff --git a/chromium/net/http/http_stream_request_unittest.cc b/chromium/net/http/http_stream_request_unittest.cc index ce2667feefa..bd31793c593 100644 --- a/chromium/net/http/http_stream_request_unittest.cc +++ b/chromium/net/http/http_stream_request_unittest.cc @@ -44,7 +44,9 @@ TEST(HttpStreamRequestTest, SetPriority) { /* is_preconnect = */ false, /* is_websocket = */ false, /* enable_ip_based_pooling = */ true, - /* enable_alternative_services = */ true, SSLConfig(), SSLConfig()); + /* enable_alternative_services = */ true, + /* delay_main_job_with_available_spdy_session = */ true, SSLConfig(), + SSLConfig()); HttpStreamFactory::JobController* job_controller_raw_ptr = job_controller.get(); factory->job_controller_set_.insert(std::move(job_controller)); diff --git a/chromium/net/http/http_transaction_test_util.cc b/chromium/net/http/http_transaction_test_util.cc index 6c750250382..254263aba71 100644 --- a/chromium/net/http/http_transaction_test_util.cc +++ b/chromium/net/http/http_transaction_test_util.cc @@ -32,7 +32,6 @@ #include "net/http/http_transaction.h" #include "net/log/net_log.h" #include "net/log/net_log_source.h" -#include "net/log/net_log_with_source.h" #include "net/ssl/ssl_private_key.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -215,8 +214,7 @@ int TestTransactionConsumer::quit_counter_ = 0; TestTransactionConsumer::TestTransactionConsumer( RequestPriority priority, - HttpTransactionFactory* factory) - : state_(State::kIdle), error_(OK) { + HttpTransactionFactory* factory) { // Disregard the error code. factory->CreateTransaction(priority, &trans_); ++quit_counter_; @@ -286,18 +284,7 @@ void TestTransactionConsumer::OnIOComplete(int result) { MockNetworkTransaction::MockNetworkTransaction(RequestPriority priority, MockNetworkLayer* factory) - : request_(nullptr), - data_cursor_(0), - content_length_(0), - priority_(priority), - read_handler_(nullptr), - websocket_handshake_stream_create_helper_(nullptr), - transaction_factory_(factory->AsWeakPtr()), - received_bytes_(0), - sent_bytes_(0), - socket_log_id_(NetLogSource::kInvalidId), - done_reading_called_(false), - reading_(false) {} + : priority_(priority), transaction_factory_(factory->AsWeakPtr()) {} MockNetworkTransaction::~MockNetworkTransaction() { // Use request_ as in ~HttpNetworkTransaction to make sure its valid and not @@ -512,6 +499,8 @@ int MockNetworkTransaction::StartInternal(const HttpRequestInfo* request, response_.was_cached = false; response_.network_accessed = true; response_.remote_endpoint = t->transport_info.endpoint; + response_.was_fetched_via_proxy = + t->transport_info.type == TransportType::kProxied; response_.response_time = transaction_factory_->Now(); if (!t->response_time.is_null()) @@ -576,7 +565,7 @@ int MockNetworkTransaction::ResumeNetworkStart() { } ConnectionAttempts MockNetworkTransaction::GetConnectionAttempts() const { - NOTIMPLEMENTED(); + // TODO(ricea): Replace this with a proper implementation if needed. return {}; } @@ -597,13 +586,7 @@ void MockNetworkTransaction::RunCallback(CompletionOnceCallback callback, std::move(callback).Run(result); } -MockNetworkLayer::MockNetworkLayer() - : transaction_count_(0), - done_reading_called_(false), - stop_caching_called_(false), - last_create_transaction_priority_(DEFAULT_PRIORITY), - clock_(nullptr) { -} +MockNetworkLayer::MockNetworkLayer() = default; MockNetworkLayer::~MockNetworkLayer() = default; diff --git a/chromium/net/http/http_transaction_test_util.h b/chromium/net/http/http_transaction_test_util.h index 18e015452d9..71541b55510 100644 --- a/chromium/net/http/http_transaction_test_util.h +++ b/chromium/net/http/http_transaction_test_util.h @@ -33,6 +33,7 @@ #include "net/http/http_request_info.h" #include "net/http/http_response_headers.h" #include "net/http/http_response_info.h" +#include "net/log/net_log_source.h" #include "net/socket/connection_attempts.h" namespace net { @@ -170,11 +171,11 @@ class TestTransactionConsumer { void OnIOComplete(int result); - State state_; + State state_ = State::kIdle; std::unique_ptr<HttpTransaction> trans_; std::string content_; scoped_refptr<IOBuffer> read_buf_; - int error_; + int error_ = OK; static int quit_counter_; }; @@ -276,29 +277,29 @@ class MockNetworkTransaction void CallbackLater(CompletionOnceCallback callback, int result); void RunCallback(CompletionOnceCallback callback, int result); - raw_ptr<const HttpRequestInfo> request_; + raw_ptr<const HttpRequestInfo> request_ = nullptr; HttpResponseInfo response_; std::string data_; - int64_t data_cursor_; - int64_t content_length_; + int64_t data_cursor_ = 0; + int64_t content_length_ = 0; int test_mode_; RequestPriority priority_; - MockTransactionReadHandler read_handler_; - raw_ptr<CreateHelper> websocket_handshake_stream_create_helper_; + MockTransactionReadHandler read_handler_ = nullptr; + raw_ptr<CreateHelper> websocket_handshake_stream_create_helper_ = nullptr; BeforeNetworkStartCallback before_network_start_callback_; ConnectedCallback connected_callback_; base::WeakPtr<MockNetworkLayer> transaction_factory_; - int64_t received_bytes_; - int64_t sent_bytes_; + int64_t received_bytes_ = 0; + int64_t sent_bytes_ = 0; // NetLog ID of the fake / non-existent underlying socket used by the // connection. Requires Start() be passed a NetLogWithSource with a real // NetLog to // be initialized. - unsigned int socket_log_id_; + unsigned int socket_log_id_ = NetLogSource::kInvalidId; - bool done_reading_called_; - bool reading_; + bool done_reading_called_ = false; + bool reading_ = false; CompletionOnceCallback resume_start_callback_; // used for pause and restart. @@ -356,14 +357,14 @@ class MockNetworkLayer : public HttpTransactionFactory, base::Time Now(); private: - int transaction_count_; - bool done_reading_called_; - bool stop_caching_called_; - RequestPriority last_create_transaction_priority_; + int transaction_count_ = 0; + bool done_reading_called_ = false; + bool stop_caching_called_ = false; + RequestPriority last_create_transaction_priority_ = DEFAULT_PRIORITY; // By default clock_ is NULL but it can be set to a custom clock by test // frameworks using SetClock. - raw_ptr<base::Clock> clock_; + raw_ptr<base::Clock> clock_ = nullptr; base::WeakPtr<MockNetworkTransaction> last_transaction_; }; diff --git a/chromium/net/http/http_util.cc b/chromium/net/http/http_util.cc index 0978147da37..96a6e2943e8 100644 --- a/chromium/net/http/http_util.cc +++ b/chromium/net/http/http_util.cc @@ -114,14 +114,14 @@ void HttpUtil::ParseContentType(const std::string& content_type_str, // Trim LWS from param value, ParseMimeType() leaves WS for quoted-string. // TODO(mmenke): Check that name has only valid characters. if (!type_has_charset && - base::LowerCaseEqualsASCII(param.first, "charset")) { + base::EqualsCaseInsensitiveASCII(param.first, "charset")) { type_has_charset = true; charset_value = std::string(HttpUtil::TrimLWS(param.second)); continue; } if (boundary && !type_has_boundary && - base::LowerCaseEqualsASCII(param.first, "boundary")) { + base::EqualsCaseInsensitiveASCII(param.first, "boundary")) { type_has_boundary = true; *boundary = std::string(HttpUtil::TrimLWS(param.second)); continue; @@ -131,7 +131,7 @@ void HttpUtil::ParseContentType(const std::string& content_type_str, // If `mime_type_value` is the same as `mime_type`, then just update // `charset`. However, if `charset` is empty and `mime_type` hasn't changed, // then don't wipe-out an existing `charset`. - bool eq = base::LowerCaseEqualsASCII(mime_type->data(), mime_type_value); + bool eq = base::EqualsCaseInsensitiveASCII(mime_type_value, *mime_type); if (!eq) { *mime_type = base::ToLowerASCII(mime_type_value); } @@ -154,7 +154,7 @@ bool HttpUtil::ParseRangeHeader(const std::string& ranges_specifier, // "bytes" unit identifier is not found. bytes_unit = TrimLWS(bytes_unit); - if (!base::LowerCaseEqualsASCII(bytes_unit, "bytes")) { + if (!base::EqualsCaseInsensitiveASCII(bytes_unit, "bytes")) { return false; } @@ -227,7 +227,7 @@ bool HttpUtil::ParseContentRangeHeaderFor206( return false; // Invalid header if it doesn't contain "bytes-unit". - if (!base::LowerCaseEqualsASCII( + if (!base::EqualsCaseInsensitiveASCII( TrimLWS(content_range_spec.substr(0, space_position)), "bytes")) { return false; } @@ -332,7 +332,7 @@ bool HttpUtil::IsSafeHeader(base::StringPiece name) { return false; for (const char* field : kForbiddenHeaderFields) { - if (base::LowerCaseEqualsASCII(name, field)) + if (base::EqualsCaseInsensitiveASCII(name, field)) return false; } return true; @@ -375,7 +375,7 @@ bool HttpUtil::IsNonCoalescingHeader(base::StringPiece name) { }; for (const char* header : kNonCoalescingHeaders) { - if (base::LowerCaseEqualsASCII(name, header)) { + if (base::EqualsCaseInsensitiveASCII(name, header)) { return true; } } @@ -518,8 +518,8 @@ size_t HttpUtil::LocateStartOfStatusLine(const char* buf, size_t buf_len) { if (buf_len >= http_len) { size_t i_max = std::min(buf_len - http_len, slop); for (size_t i = 0; i <= i_max; ++i) { - if (base::LowerCaseEqualsASCII(base::StringPiece(buf + i, http_len), - "http")) + if (base::EqualsCaseInsensitiveASCII(base::StringPiece(buf + i, http_len), + "http")) return i; } } @@ -751,7 +751,7 @@ bool HttpUtil::HasStrongValidators(HttpVersion version, std::string::const_iterator i = etag_header.begin(); std::string::const_iterator j = etag_header.begin() + slash; TrimLWS(&i, &j); - if (!base::LowerCaseEqualsASCII(base::MakeStringPiece(i, j), "w")) + if (!base::EqualsCaseInsensitiveASCII(base::MakeStringPiece(i, j), "w")) return true; } @@ -870,7 +870,7 @@ bool HttpUtil::HeadersIterator::AdvanceTo(const char* name) { << "the header name must be in all lower case"; while (GetNext()) { - if (base::LowerCaseEqualsASCII( + if (base::EqualsCaseInsensitiveASCII( base::MakeStringPiece(name_begin_, name_end_), name)) { return true; } @@ -917,12 +917,10 @@ HttpUtil::NameValuePairsIterator::NameValuePairsIterator( Values optional_values, Quotes strict_quotes) : props_(begin, end, delimiter), - valid_(true), name_begin_(end), name_end_(end), value_begin_(end), value_end_(end), - value_is_quoted_(false), values_optional_(optional_values == Values::NOT_REQUIRED), strict_quotes_(strict_quotes == Quotes::STRICT_QUOTES) {} @@ -1049,7 +1047,7 @@ bool HttpUtil::ParseAcceptEncoding(const std::string& accept_encoding, return false; base::StringPiece param_name = params.substr(0, equals_pos); param_name = TrimLWS(param_name); - if (!base::LowerCaseEqualsASCII(param_name, "q")) + if (!base::EqualsCaseInsensitiveASCII(param_name, "q")) return false; base::StringPiece qvalue = params.substr(equals_pos + 1); qvalue = TrimLWS(qvalue); diff --git a/chromium/net/http/http_util.h b/chromium/net/http/http_util.h index 65f64279020..7733270cf20 100644 --- a/chromium/net/http/http_util.h +++ b/chromium/net/http/http_util.h @@ -450,7 +450,7 @@ class NET_EXPORT HttpUtil { private: HttpUtil::ValuesIterator props_; - bool valid_; + bool valid_ = true; std::string::const_iterator name_begin_; std::string::const_iterator name_end_; @@ -463,7 +463,7 @@ class NET_EXPORT HttpUtil { // into the original's unquoted_value_ member. std::string unquoted_value_; - bool value_is_quoted_; + bool value_is_quoted_ = false; // True if values are required for each name/value pair; false if a // name is permitted to appear without a corresponding value. diff --git a/chromium/net/http/http_util_unittest.cc b/chromium/net/http/http_util_unittest.cc index 5072a7cbfee..b2eb2332b54 100644 --- a/chromium/net/http/http_util_unittest.cc +++ b/chromium/net/http/http_util_unittest.cc @@ -987,6 +987,8 @@ TEST(HttpUtilTest, ParseContentType) { { "*/*", "", "", false, "" }, { "*/*; charset=utf-8", "*/*", "utf-8", true, "" }, { "*/* ", "*/*", "", false, "" }, + // Regression test for https://crbug.com/1326529 + { "teXT/html", "text/html", "", false, ""}, // TODO(abarth): Add more interesting test cases. }; // clang-format on @@ -1008,6 +1010,42 @@ TEST(HttpUtilTest, ParseContentType) { } } +TEST(HttpUtilTest, ParseContentResetCharset) { + std::string mime_type; + std::string charset; + bool had_charset = false; + std::string boundary; + + // Set mime (capitalization should be ignored), but not charset. + HttpUtil::ParseContentType("Text/Html", &mime_type, &charset, &had_charset, + &boundary); + EXPECT_EQ("text/html", mime_type); + EXPECT_EQ("", charset); + EXPECT_FALSE(had_charset); + + // The same mime, add charset. + HttpUtil::ParseContentType("tExt/hTml;charset=utf-8", &mime_type, &charset, + &had_charset, &boundary); + EXPECT_EQ("text/html", mime_type); + EXPECT_EQ("utf-8", charset); + EXPECT_TRUE(had_charset); + + // The same mime (different capitalization), but no charset - should not clear + // charset. + HttpUtil::ParseContentType("teXt/htMl", &mime_type, &charset, &had_charset, + &boundary); + EXPECT_EQ("text/html", mime_type); + EXPECT_EQ("utf-8", charset); + EXPECT_TRUE(had_charset); + + // A different mime will clear charset. + HttpUtil::ParseContentType("texT/plaiN", &mime_type, &charset, &had_charset, + &boundary); + EXPECT_EQ("text/plain", mime_type); + EXPECT_EQ("", charset); + EXPECT_TRUE(had_charset); +} + TEST(HttpUtilTest, ParseContentRangeHeader) { const struct { const char* const content_range_header_spec; diff --git a/chromium/net/http/http_vary_data.cc b/chromium/net/http/http_vary_data.cc index 221978e1db5..9b444b9b16c 100644 --- a/chromium/net/http/http_vary_data.cc +++ b/chromium/net/http/http_vary_data.cc @@ -15,8 +15,7 @@ namespace net { -HttpVaryData::HttpVaryData() : is_valid_(false) { -} +HttpVaryData::HttpVaryData() = default; bool HttpVaryData::Init(const HttpRequestInfo& request_info, const HttpResponseHeaders& response_headers) { diff --git a/chromium/net/http/http_vary_data.h b/chromium/net/http/http_vary_data.h index 0f4ceb1d005..c2512465554 100644 --- a/chromium/net/http/http_vary_data.h +++ b/chromium/net/http/http_vary_data.h @@ -79,7 +79,7 @@ class NET_EXPORT_PRIVATE HttpVaryData { base::MD5Digest request_digest_; // True when request_digest_ contains meaningful data. - bool is_valid_; + bool is_valid_ = false; }; } // namespace net diff --git a/chromium/net/http/mock_http_cache.cc b/chromium/net/http/mock_http_cache.cc index 7a4eea72f62..790d31af772 100644 --- a/chromium/net/http/mock_http_cache.cc +++ b/chromium/net/http/mock_http_cache.cc @@ -55,18 +55,7 @@ struct MockDiskEntry::CallbackInfo { }; MockDiskEntry::MockDiskEntry(const std::string& key) - : key_(key), - in_memory_data_(0), - max_file_size_(std::numeric_limits<int>::max()), - doomed_(false), - sparse_(false), - fail_requests_(0), - fail_sparse_requests_(false), - busy_(false), - delayed_(false), - cancel_(false), - defer_op_(DEFER_NONE), - resume_return_code_(0) { + : key_(key), max_file_size_(std::numeric_limits<int>::max()) { test_mode_ = GetTestModeForEntry(key); } @@ -393,19 +382,7 @@ bool MockDiskEntry::ignore_callbacks_ = false; //----------------------------------------------------------------------------- MockDiskCache::MockDiskCache() - : Backend(DISK_CACHE), - open_count_(0), - create_count_(0), - doomed_count_(0), - max_file_size_(std::numeric_limits<int>::max()), - fail_requests_(false), - soft_failures_(0), - soft_failures_one_instance_(0), - double_create_check_(true), - fail_sparse_requests_(false), - support_in_memory_entry_data_(true), - force_fail_callback_later_(false), - defer_op_(MockDiskEntry::DEFER_NONE) {} + : Backend(DISK_CACHE), max_file_size_(std::numeric_limits<int>::max()) {} MockDiskCache::~MockDiskCache() { ReleaseAll(); @@ -682,21 +659,13 @@ int MockBackendFactory::CreateBackend( //----------------------------------------------------------------------------- -MockHttpCache::MockHttpCache() : MockHttpCache(false) {} +MockHttpCache::MockHttpCache() + : MockHttpCache(std::make_unique<MockBackendFactory>()) {} MockHttpCache::MockHttpCache( std::unique_ptr<HttpCache::BackendFactory> disk_cache_factory) - : MockHttpCache(std::move(disk_cache_factory), false) {} - -MockHttpCache::MockHttpCache(bool is_main_cache) - : MockHttpCache(std::make_unique<MockBackendFactory>(), is_main_cache) {} - -MockHttpCache::MockHttpCache( - std::unique_ptr<HttpCache::BackendFactory> disk_cache_factory, - bool is_main_cache) : http_cache_(std::make_unique<MockNetworkLayer>(), - std::move(disk_cache_factory), - is_main_cache) {} + std::move(disk_cache_factory)) {} disk_cache::Backend* MockHttpCache::backend() { TestCompletionCallback cb; @@ -852,8 +821,7 @@ int MockBackendNoCbFactory::CreateBackend( //----------------------------------------------------------------------------- -MockBlockingBackendFactory::MockBlockingBackendFactory() - : backend_(nullptr), block_(true), fail_(false) {} +MockBlockingBackendFactory::MockBlockingBackendFactory() = default; MockBlockingBackendFactory::~MockBlockingBackendFactory() = default; diff --git a/chromium/net/http/mock_http_cache.h b/chromium/net/http/mock_http_cache.h index f60f7c4656c..9068a3d8456 100644 --- a/chromium/net/http/mock_http_cache.h +++ b/chromium/net/http/mock_http_cache.h @@ -137,21 +137,21 @@ class MockDiskEntry : public disk_cache::Entry, std::string key_; std::vector<char> data_[kNumCacheEntryDataIndices]; - uint8_t in_memory_data_; + uint8_t in_memory_data_ = 0; int test_mode_; int max_file_size_; - bool doomed_; - bool sparse_; - int fail_requests_; - bool fail_sparse_requests_; - bool busy_; - bool delayed_; - bool cancel_; + bool doomed_ = false; + bool sparse_ = false; + int fail_requests_ = 0; + bool fail_sparse_requests_ = false; + bool busy_ = false; + bool delayed_ = false; + bool cancel_ = false; // Used for pause and restart. - DeferOp defer_op_; + DeferOp defer_op_ = DEFER_NONE; CompletionOnceCallback resume_callback_; - int resume_return_code_; + int resume_return_code_ = 0; static bool ignore_callbacks_; }; @@ -261,20 +261,20 @@ class MockDiskCache : public disk_cache::Backend { EntryMap entries_; std::vector<std::string> external_cache_hits_; - int open_count_; - int create_count_; - int doomed_count_; + int open_count_ = 0; + int create_count_ = 0; + int doomed_count_ = 0; int max_file_size_; - bool fail_requests_; - int soft_failures_; - int soft_failures_one_instance_; - bool double_create_check_; - bool fail_sparse_requests_; - bool support_in_memory_entry_data_; - bool force_fail_callback_later_; + bool fail_requests_ = false; + int soft_failures_ = 0; + int soft_failures_one_instance_ = 0; + bool double_create_check_ = true; + bool fail_sparse_requests_ = false; + bool support_in_memory_entry_data_ = true; + bool force_fail_callback_later_ = false; // Used for pause and restart. - MockDiskEntry::DeferOp defer_op_; + MockDiskEntry::DeferOp defer_op_ = MockDiskEntry::DEFER_NONE; base::OnceClosure resume_callback_; }; @@ -290,11 +290,6 @@ class MockHttpCache { MockHttpCache(); explicit MockHttpCache( std::unique_ptr<HttpCache::BackendFactory> disk_cache_factory); - // |is_main_cache| if set, will set a quic server info factory. - explicit MockHttpCache(bool is_main_cache); - - MockHttpCache(std::unique_ptr<HttpCache::BackendFactory> disk_cache_factory, - bool is_main_cache); HttpCache* http_cache() { return &http_cache_; } @@ -390,10 +385,10 @@ class MockBlockingBackendFactory : public HttpCache::BackendFactory { private: int Result() { return fail_ ? ERR_FAILED : OK; } - raw_ptr<std::unique_ptr<disk_cache::Backend>> backend_; + raw_ptr<std::unique_ptr<disk_cache::Backend>> backend_ = nullptr; CompletionOnceCallback callback_; - bool block_; - bool fail_; + bool block_ = true; + bool fail_ = false; }; } // namespace net diff --git a/chromium/net/http/partial_data.cc b/chromium/net/http/partial_data.cc index 3f76d090e79..7c60c5114b6 100644 --- a/chromium/net/http/partial_data.cc +++ b/chromium/net/http/partial_data.cc @@ -17,6 +17,7 @@ #include "net/base/net_errors.h" #include "net/disk_cache/disk_cache.h" #include "net/http/http_response_headers.h" +#include "net/http/http_status_code.h" #include "net/http/http_util.h" namespace net { @@ -30,18 +31,7 @@ const int kDataStream = 1; } // namespace -PartialData::PartialData() - : current_range_start_(0), - current_range_end_(0), - cached_start_(0), - cached_min_len_(0), - resource_size_(0), - range_requested_(false), - range_present_(false), - final_range_(false), - sparse_entry_(true), - truncated_(false), - initial_validation_(false) {} +PartialData::PartialData() = default; PartialData::~PartialData() = default; @@ -226,7 +216,7 @@ bool PartialData::UpdateFromStoredHeaders(const HttpResponseHeaders* headers, return true; } - sparse_entry_ = (headers->response_code() == 206); + sparse_entry_ = (headers->response_code() == net::HTTP_PARTIAL_CONTENT); if (writing_in_progress || sparse_entry_) { // |writing_in_progress| means another Transaction is still fetching the @@ -292,7 +282,7 @@ bool PartialData::IsRequestedRangeOK() { } bool PartialData::ResponseHeadersOK(const HttpResponseHeaders* headers) { - if (headers->response_code() == 304) { + if (headers->response_code() == net::HTTP_NOT_MODIFIED) { if (!byte_range_.IsValid() || truncated_) return true; diff --git a/chromium/net/http/partial_data.h b/chromium/net/http/partial_data.h index 16510de5ebc..4f6fe440574 100644 --- a/chromium/net/http/partial_data.h +++ b/chromium/net/http/partial_data.h @@ -136,8 +136,8 @@ class PartialData { void GetAvailableRangeCompleted(const disk_cache::RangeResult& result); // The portion we're trying to get, either from cache or network. - int64_t current_range_start_; - int64_t current_range_end_; + int64_t current_range_start_ = 0; + int64_t current_range_end_ = 0; // Next portion available in the cache --- this may be what's currently being // read, or the next thing that will be read if the current network portion @@ -146,20 +146,20 @@ class PartialData { // |cached_start_| represents the beginning of the range, while // |cached_min_len_| the data not yet read (possibly overestimated). It may // also have an error code latched into it. - int64_t cached_start_; - int cached_min_len_; + int64_t cached_start_ = 0; + int cached_min_len_ = 0; // The size of the whole file. - int64_t resource_size_; + int64_t resource_size_ = 0; HttpByteRange byte_range_; // The range requested by the user. // The clean set of extra headers (no ranges). HttpRequestHeaders extra_headers_; - bool range_requested_; // ### - bool range_present_; // True if next range entry is already stored. - bool final_range_; - bool sparse_entry_; - bool truncated_; // We have an incomplete 200 stored. - bool initial_validation_; // Only used for truncated entries. + bool range_requested_ = false; // ### + bool range_present_ = false; // True if next range entry is already stored. + bool final_range_ = false; + bool sparse_entry_ = true; + bool truncated_ = false; // We have an incomplete 200 stored. + bool initial_validation_ = false; // Only used for truncated entries. CompletionOnceCallback callback_; base::WeakPtrFactory<PartialData> weak_factory_{this}; }; diff --git a/chromium/net/http/structured_headers.cc b/chromium/net/http/structured_headers.cc deleted file mode 100644 index 58953d7ee4a..00000000000 --- a/chromium/net/http/structured_headers.cc +++ /dev/null @@ -1,984 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/http/structured_headers.h" - -#include <cmath> -#include <string> -#include <utility> - -#include "base/base64.h" -#include "base/containers/flat_set.h" -#include "base/containers/span.h" -#include "base/logging.h" -#include "base/notreached.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_piece.h" -#include "base/strings/string_util.h" - -namespace net { -namespace structured_headers { - -namespace { - -#define DIGIT "0123456789" -#define LCALPHA "abcdefghijklmnopqrstuvwxyz" -#define UCALPHA "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -#define TCHAR DIGIT LCALPHA UCALPHA "!#$%&'*+-.^_`|~" -// https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-09#section-3.9 -constexpr char kTokenChars09[] = DIGIT UCALPHA LCALPHA "_-.:%*/"; -// https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3.4 -constexpr char kTokenChars[] = TCHAR ":/"; -// https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-09#section-3.1 -constexpr char kKeyChars09[] = DIGIT LCALPHA "_-"; -// https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1.2 -constexpr char kKeyChars[] = DIGIT LCALPHA "_-.*"; -constexpr char kSP[] = " "; -constexpr char kOWS[] = " \t"; -#undef DIGIT -#undef LCALPHA -#undef UCALPHA - -// https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3.1 -constexpr int64_t kMaxInteger = 999'999'999'999'999L; -constexpr int64_t kMinInteger = -999'999'999'999'999L; - -// Smallest value which is too large for an sh-decimal. This is the smallest -// double which will round up to 1e12 when serialized, which exceeds the range -// for sh-decimal. Any float less than this should round down. This behaviour is -// verified by unit tests. -constexpr double kTooLargeDecimal = 1e12 - 0.0005; - -// Parser for (a subset of) Structured Headers for HTTP defined in [SH09] and -// [RFC8941]. [SH09] compatibility is retained for use by Web Packaging, and can -// be removed once that spec is updated, and users have migrated to new headers. -// [SH09] https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-09 -// [RFC8941] https://www.rfc-editor.org/rfc/rfc8941.html -class StructuredHeaderParser { - public: - enum DraftVersion { - kDraft09, - kFinal, - }; - explicit StructuredHeaderParser(base::StringPiece str, DraftVersion version) - : input_(str), version_(version) { - // [SH09] 4.2 Step 1. - // Discard any leading OWS from input_string. - // [RFC8941] 4.2 Step 2. - // Discard any leading SP characters from input_string. - SkipWhitespaces(); - } - StructuredHeaderParser(const StructuredHeaderParser&) = delete; - StructuredHeaderParser& operator=(const StructuredHeaderParser&) = delete; - - // Callers should call this after ReadSomething(), to check if parser has - // consumed all the input successfully. - bool FinishParsing() { - // [SH09] 4.2 Step 7. - // Discard any leading OWS from input_string. - // [RFC8941] 4.2 Step 6. - // Discard any leading SP characters from input_string. - SkipWhitespaces(); - // [SH09] 4.2 Step 8. [RFC8941] 4.2 Step 7. - // If input_string is not empty, fail parsing. - return input_.empty(); - } - - // Parses a List of Lists ([SH09] 4.2.4). - absl::optional<ListOfLists> ReadListOfLists() { - DCHECK_EQ(version_, kDraft09); - ListOfLists result; - while (true) { - std::vector<Item> inner_list; - while (true) { - absl::optional<Item> item(ReadBareItem()); - if (!item) - return absl::nullopt; - inner_list.push_back(std::move(*item)); - SkipWhitespaces(); - if (!ConsumeChar(';')) - break; - SkipWhitespaces(); - } - result.push_back(std::move(inner_list)); - SkipWhitespaces(); - if (!ConsumeChar(',')) - break; - SkipWhitespaces(); - } - return result; - } - - // Parses a List ([RFC8941] 4.2.1). - absl::optional<List> ReadList() { - DCHECK_EQ(version_, kFinal); - List members; - while (!input_.empty()) { - absl::optional<ParameterizedMember> member(ReadItemOrInnerList()); - if (!member) - return absl::nullopt; - members.push_back(std::move(*member)); - SkipOWS(); - if (input_.empty()) - break; - if (!ConsumeChar(',')) - return absl::nullopt; - SkipOWS(); - if (input_.empty()) - return absl::nullopt; - } - return members; - } - - // Parses an Item ([RFC8941] 4.2.3). - absl::optional<ParameterizedItem> ReadItem() { - absl::optional<Item> item = ReadBareItem(); - if (!item) - return absl::nullopt; - absl::optional<Parameters> parameters = ReadParameters(); - if (!parameters) - return absl::nullopt; - return ParameterizedItem(std::move(*item), std::move(*parameters)); - } - - // Parses a bare Item ([RFC8941] 4.2.3.1, though this is also the algorithm - // for parsing an Item from [SH09] 4.2.7). - absl::optional<Item> ReadBareItem() { - if (input_.empty()) { - DVLOG(1) << "ReadBareItem: unexpected EOF"; - return absl::nullopt; - } - switch (input_.front()) { - case '"': - return ReadString(); - case '*': - if (version_ == kDraft09) - return ReadByteSequence(); - return ReadToken(); - case ':': - if (version_ == kFinal) - return ReadByteSequence(); - return absl::nullopt; - case '?': - return ReadBoolean(); - default: - if (input_.front() == '-' || base::IsAsciiDigit(input_.front())) - return ReadNumber(); - if (base::IsAsciiAlpha(input_.front())) - return ReadToken(); - return absl::nullopt; - } - } - - // Parses a Dictionary ([RFC8941] 4.2.2). - absl::optional<Dictionary> ReadDictionary() { - DCHECK_EQ(version_, kFinal); - Dictionary members; - while (!input_.empty()) { - absl::optional<std::string> key(ReadKey()); - if (!key) - return absl::nullopt; - absl::optional<ParameterizedMember> member; - if (ConsumeChar('=')) { - member = ReadItemOrInnerList(); - if (!member) - return absl::nullopt; - } else { - absl::optional<Parameters> parameters; - parameters = ReadParameters(); - if (!parameters) - return absl::nullopt; - member = ParameterizedMember{Item(true), std::move(*parameters)}; - } - members[*key] = std::move(*member); - SkipOWS(); - if (input_.empty()) - break; - if (!ConsumeChar(',')) - return absl::nullopt; - SkipOWS(); - if (input_.empty()) - return absl::nullopt; - } - return members; - } - - // Parses a Parameterised List ([SH09] 4.2.5). - absl::optional<ParameterisedList> ReadParameterisedList() { - DCHECK_EQ(version_, kDraft09); - ParameterisedList items; - while (true) { - absl::optional<ParameterisedIdentifier> item = - ReadParameterisedIdentifier(); - if (!item) - return absl::nullopt; - items.push_back(std::move(*item)); - SkipWhitespaces(); - if (!ConsumeChar(',')) - return items; - SkipWhitespaces(); - } - } - - private: - // Parses a Parameterised Identifier ([SH09] 4.2.6). - absl::optional<ParameterisedIdentifier> ReadParameterisedIdentifier() { - DCHECK_EQ(version_, kDraft09); - absl::optional<Item> primary_identifier = ReadToken(); - if (!primary_identifier) - return absl::nullopt; - - ParameterisedIdentifier::Parameters parameters; - - SkipWhitespaces(); - while (ConsumeChar(';')) { - SkipWhitespaces(); - - absl::optional<std::string> name = ReadKey(); - if (!name) - return absl::nullopt; - - Item value; - if (ConsumeChar('=')) { - auto item = ReadBareItem(); - if (!item) - return absl::nullopt; - value = std::move(*item); - } - if (!parameters.emplace(*name, value).second) { - DVLOG(1) << "ReadParameterisedIdentifier: duplicated parameter: " - << *name; - return absl::nullopt; - } - SkipWhitespaces(); - } - return ParameterisedIdentifier(std::move(*primary_identifier), - std::move(parameters)); - } - - // Parses an Item or Inner List ([RFC8941] 4.2.1.1). - absl::optional<ParameterizedMember> ReadItemOrInnerList() { - DCHECK_EQ(version_, kFinal); - std::vector<Item> member; - bool member_is_inner_list = (!input_.empty() && input_.front() == '('); - if (member_is_inner_list) { - return ReadInnerList(); - } else { - auto item = ReadItem(); - if (!item) - return absl::nullopt; - return ParameterizedMember(std::move(item->item), - std::move(item->params)); - } - } - - // Parses Parameters ([RFC8941] 4.2.3.2) - absl::optional<Parameters> ReadParameters() { - Parameters parameters; - base::flat_set<std::string> keys; - - while (ConsumeChar(';')) { - SkipWhitespaces(); - - absl::optional<std::string> name = ReadKey(); - if (!name) - return absl::nullopt; - bool is_duplicate_key = !keys.insert(*name).second; - - Item value{true}; - if (ConsumeChar('=')) { - auto item = ReadBareItem(); - if (!item) - return absl::nullopt; - value = std::move(*item); - } - if (is_duplicate_key) { - for (auto& param : parameters) { - if (param.first == name) { - param.second = std::move(value); - break; - } - } - } else { - parameters.emplace_back(std::move(*name), std::move(value)); - } - } - return parameters; - } - - // Parses an Inner List ([RFC8941] 4.2.1.2). - absl::optional<ParameterizedMember> ReadInnerList() { - DCHECK_EQ(version_, kFinal); - if (!ConsumeChar('(')) - return absl::nullopt; - std::vector<ParameterizedItem> inner_list; - while (true) { - SkipWhitespaces(); - if (ConsumeChar(')')) { - absl::optional<Parameters> parameters; - parameters = ReadParameters(); - if (!parameters) - return absl::nullopt; - return ParameterizedMember(std::move(inner_list), true, - std::move(*parameters)); - } - auto item = ReadItem(); - if (!item) - return absl::nullopt; - inner_list.push_back(std::move(*item)); - if (input_.empty() || (input_.front() != ' ' && input_.front() != ')')) - return absl::nullopt; - } - NOTREACHED(); - return absl::nullopt; - } - - // Parses a Key ([SH09] 4.2.2, [RFC8941] 4.2.3.3). - absl::optional<std::string> ReadKey() { - if (version_ == kDraft09) { - if (input_.empty() || !base::IsAsciiLower(input_.front())) { - LogParseError("ReadKey", "lcalpha"); - return absl::nullopt; - } - } else { - if (input_.empty() || - (!base::IsAsciiLower(input_.front()) && input_.front() != '*')) { - LogParseError("ReadKey", "lcalpha | *"); - return absl::nullopt; - } - } - const char* allowed_chars = - (version_ == kDraft09 ? kKeyChars09 : kKeyChars); - size_t len = input_.find_first_not_of(allowed_chars); - if (len == base::StringPiece::npos) - len = input_.size(); - std::string key(input_.substr(0, len)); - input_.remove_prefix(len); - return key; - } - - // Parses a Token ([SH09] 4.2.10, [RFC8941] 4.2.6). - absl::optional<Item> ReadToken() { - if (input_.empty() || - !(base::IsAsciiAlpha(input_.front()) || input_.front() == '*')) { - LogParseError("ReadToken", "ALPHA"); - return absl::nullopt; - } - size_t len = input_.find_first_not_of(version_ == kDraft09 ? kTokenChars09 - : kTokenChars); - if (len == base::StringPiece::npos) - len = input_.size(); - std::string token(input_.substr(0, len)); - input_.remove_prefix(len); - return Item(std::move(token), Item::kTokenType); - } - - // Parses a Number ([SH09] 4.2.8, [RFC8941] 4.2.4). - absl::optional<Item> ReadNumber() { - bool is_negative = ConsumeChar('-'); - bool is_decimal = false; - size_t decimal_position = 0; - size_t i = 0; - for (; i < input_.size(); ++i) { - if (i > 0 && input_[i] == '.' && !is_decimal) { - is_decimal = true; - decimal_position = i; - continue; - } - if (!base::IsAsciiDigit(input_[i])) - break; - } - if (i == 0) { - LogParseError("ReadNumber", "DIGIT"); - return absl::nullopt; - } - if (!is_decimal) { - // [RFC8941] restricts the range of integers further. - if (version_ == kFinal && i > 15) { - LogParseError("ReadNumber", "integer too long"); - return absl::nullopt; - } - } else { - if (version_ != kFinal && i > 16) { - LogParseError("ReadNumber", "float too long"); - return absl::nullopt; - } - if (version_ == kFinal && decimal_position > 12) { - LogParseError("ReadNumber", "decimal too long"); - return absl::nullopt; - } - if (i - decimal_position > (version_ == kFinal ? 4 : 7)) { - LogParseError("ReadNumber", "too many digits after decimal"); - return absl::nullopt; - } - if (i == decimal_position) { - LogParseError("ReadNumber", "no digits after decimal"); - return absl::nullopt; - } - } - std::string output_number_string(input_.substr(0, i)); - input_.remove_prefix(i); - - if (is_decimal) { - // Convert to a 64-bit double, and return if the conversion is - // successful. - double f; - if (!base::StringToDouble(output_number_string, &f)) - return absl::nullopt; - return Item(is_negative ? -f : f); - } else { - // Convert to a 64-bit signed integer, and return if the conversion is - // successful. - int64_t n; - if (!base::StringToInt64(output_number_string, &n)) - return absl::nullopt; - DCHECK(version_ != kFinal || (n <= kMaxInteger && n >= kMinInteger)); - return Item(is_negative ? -n : n); - } - } - - // Parses a String ([SH09] 4.2.9, [RFC8941] 4.2.5). - absl::optional<Item> ReadString() { - std::string s; - if (!ConsumeChar('"')) { - LogParseError("ReadString", "'\"'"); - return absl::nullopt; - } - while (!ConsumeChar('"')) { - size_t i = 0; - for (; i < input_.size(); ++i) { - if (!base::IsAsciiPrintable(input_[i])) { - DVLOG(1) << "ReadString: non printable-ASCII character"; - return absl::nullopt; - } - if (input_[i] == '"' || input_[i] == '\\') - break; - } - if (i == input_.size()) { - DVLOG(1) << "ReadString: missing closing '\"'"; - return absl::nullopt; - } - s.append(std::string(input_.substr(0, i))); - input_.remove_prefix(i); - if (ConsumeChar('\\')) { - if (input_.empty()) { - DVLOG(1) << "ReadString: backslash at string end"; - return absl::nullopt; - } - if (input_[0] != '"' && input_[0] != '\\') { - DVLOG(1) << "ReadString: invalid escape"; - return absl::nullopt; - } - s.push_back(input_.front()); - input_.remove_prefix(1); - } - } - return s; - } - - // Parses a Byte Sequence ([SH09] 4.2.11, [RFC8941] 4.2.7). - absl::optional<Item> ReadByteSequence() { - char delimiter = (version_ == kDraft09 ? '*' : ':'); - if (!ConsumeChar(delimiter)) { - LogParseError("ReadByteSequence", "delimiter"); - return absl::nullopt; - } - size_t len = input_.find(delimiter); - if (len == base::StringPiece::npos) { - DVLOG(1) << "ReadByteSequence: missing closing delimiter"; - return absl::nullopt; - } - std::string base64(input_.substr(0, len)); - // Append the necessary padding characters. - base64.resize((base64.size() + 3) / 4 * 4, '='); - - std::string binary; - if (!base::Base64Decode(base64, &binary)) { - DVLOG(1) << "ReadByteSequence: failed to decode base64: " << base64; - return absl::nullopt; - } - input_.remove_prefix(len); - ConsumeChar(delimiter); - return Item(std::move(binary), Item::kByteSequenceType); - } - - // Parses a Boolean ([RFC8941] 4.2.8). - // Note that this only parses ?0 and ?1 forms from SH version 10+, not the - // previous ?F and ?T, which were not needed by any consumers of SH version 9. - absl::optional<Item> ReadBoolean() { - if (!ConsumeChar('?')) { - LogParseError("ReadBoolean", "'?'"); - return absl::nullopt; - } - if (ConsumeChar('1')) { - return Item(true); - } - if (ConsumeChar('0')) { - return Item(false); - } - return absl::nullopt; - } - - // There are several points in the specs where the handling of whitespace - // differs between Draft 9 and the final RFC. In those cases, Draft 9 allows - // any OWS character, while the RFC allows only a U+0020 SPACE. - void SkipWhitespaces() { - if (version_ == kDraft09) { - input_ = - base::TrimString(input_, base::StringPiece(kOWS), base::TRIM_LEADING); - } else { - input_ = - base::TrimString(input_, base::StringPiece(kSP), base::TRIM_LEADING); - } - } - - void SkipOWS() { - input_ = - base::TrimString(input_, base::StringPiece(kOWS), base::TRIM_LEADING); - } - - bool ConsumeChar(char expected) { - if (!input_.empty() && input_.front() == expected) { - input_.remove_prefix(1); - return true; - } - return false; - } - - void LogParseError(const char* func, const char* expected) { - DVLOG(1) << func << ": " << expected << " expected, got " - << (input_.empty() ? "EOS" - : "'" + std::string(input_.substr(0, 1)) + "'"); - } - - base::StringPiece input_; - DraftVersion version_; -}; - -// Serializer for (a subset of) Structured Field Values for HTTP defined in -// [RFC8941]. Note that this serializer does not attempt to support [SH09]. -class StructuredHeaderSerializer { - public: - StructuredHeaderSerializer() = default; - ~StructuredHeaderSerializer() = default; - StructuredHeaderSerializer(const StructuredHeaderSerializer&) = delete; - StructuredHeaderSerializer& operator=(const StructuredHeaderSerializer&) = - delete; - - std::string Output() { return output_.str(); } - - // Serializes a List ([RFC8941] 4.1.1). - bool WriteList(const List& value) { - bool first = true; - for (const auto& member : value) { - if (!first) - output_ << ", "; - if (!WriteParameterizedMember(member)) - return false; - first = false; - } - return true; - } - - // Serializes an Item ([RFC8941] 4.1.3). - bool WriteItem(const ParameterizedItem& value) { - if (!WriteBareItem(value.item)) - return false; - return WriteParameters(value.params); - } - - // Serializes an Item ([RFC8941] 4.1.3). - bool WriteBareItem(const Item& value) { - if (value.is_string()) { - // Serializes a String ([RFC8941] 4.1.6). - output_ << "\""; - for (const char& c : value.GetString()) { - if (!base::IsAsciiPrintable(c)) - return false; - if (c == '\\' || c == '\"') - output_ << "\\"; - output_ << c; - } - output_ << "\""; - return true; - } - if (value.is_token()) { - // Serializes a Token ([RFC8941] 4.1.7). - if (!value.GetString().size() || - !(base::IsAsciiAlpha(value.GetString().front()) || - value.GetString().front() == '*')) - return false; - if (value.GetString().find_first_not_of(kTokenChars) != std::string::npos) - return false; - output_ << value.GetString(); - return true; - } - if (value.is_byte_sequence()) { - // Serializes a Byte Sequence ([RFC8941] 4.1.8). - output_ << ":"; - output_ << base::Base64Encode( - base::as_bytes(base::make_span(value.GetString()))); - output_ << ":"; - return true; - } - if (value.is_integer()) { - // Serializes an Integer ([RFC8941] 4.1.4). - if (value.GetInteger() > kMaxInteger || value.GetInteger() < kMinInteger) - return false; - output_ << value.GetInteger(); - return true; - } - if (value.is_decimal()) { - // Serializes a Decimal ([RFC8941] 4.1.5). - double decimal_value = value.GetDecimal(); - if (!std::isfinite(decimal_value) || - fabs(decimal_value) >= kTooLargeDecimal) - return false; - - // Handle sign separately to simplify the rest of the formatting. - if (decimal_value < 0) - output_ << "-"; - // Unconditionally take absolute value to ensure that -0 is serialized as - // "0.0", with no negative sign, as required by spec. (4.1.5, step 2). - decimal_value = fabs(decimal_value); - double remainder = fmod(decimal_value, 0.002); - if (remainder == 0.0005) { - // Value ended in exactly 0.0005, 0.0025, 0.0045, etc. Round down. - decimal_value -= 0.0005; - } else if (remainder == 0.0015) { - // Value ended in exactly 0.0015, 0.0035, 0,0055, etc. Round up. - decimal_value += 0.0005; - } else { - // Standard rounding will work in all other cases. - decimal_value = round(decimal_value * 1000.0) / 1000.0; - } - - // Use standard library functions to write the decimal, and then truncate - // if necessary to conform to spec. - - // Maximum is 12 integer digits, one decimal point, three fractional - // digits, and a null terminator. - char buffer[17]; - base::snprintf(buffer, std::size(buffer), "%#.3f", decimal_value); - - // Strip any trailing 0s after the decimal point, but leave at least one - // digit after it in all cases. (So 1.230 becomes 1.23, but 1.000 becomes - // 1.0.) - base::StringPiece formatted_number(buffer); - auto truncate_index = formatted_number.find_last_not_of('0'); - if (formatted_number[truncate_index] == '.') - truncate_index++; - output_ << formatted_number.substr(0, truncate_index + 1); - return true; - } - if (value.is_boolean()) { - // Serializes a Boolean ([RFC8941] 4.1.9). - output_ << (value.GetBoolean() ? "?1" : "?0"); - return true; - } - return false; - } - - // Serializes a Dictionary ([RFC8941] 4.1.2). - bool WriteDictionary(const Dictionary& value) { - bool first = true; - for (const auto& dict : value) { - const auto& dict_member = dict.second; - if (!first) - output_ << ", "; - if (!WriteKey(dict.first)) - return false; - first = false; - if (!dict_member.member_is_inner_list && - dict_member.member.front().item.is_boolean() && - dict_member.member.front().item.GetBoolean()) { - if (!WriteParameters(dict_member.params)) - return false; - } else { - output_ << "="; - if (!WriteParameterizedMember(dict_member)) - return false; - } - } - return true; - } - - private: - bool WriteParameterizedMember(const ParameterizedMember& value) { - // Serializes a parameterized member ([RFC8941] 4.1.1). - if (value.member_is_inner_list) { - if (!WriteInnerList(value.member)) - return false; - } else { - DCHECK_EQ(value.member.size(), 1UL); - if (!WriteItem(value.member[0])) - return false; - } - return WriteParameters(value.params); - } - - bool WriteInnerList(const std::vector<ParameterizedItem>& value) { - // Serializes an inner list ([RFC8941] 4.1.1.1). - output_ << "("; - bool first = true; - for (const ParameterizedItem& member : value) { - if (!first) - output_ << " "; - if (!WriteItem(member)) - return false; - first = false; - } - output_ << ")"; - return true; - } - - bool WriteParameters(const Parameters& value) { - // Serializes a parameter list ([RFC8941] 4.1.1.2). - for (const auto& param_name_and_value : value) { - const std::string& param_name = param_name_and_value.first; - const Item& param_value = param_name_and_value.second; - output_ << ";"; - if (!WriteKey(param_name)) - return false; - if (!param_value.is_null()) { - if (param_value.is_boolean() && param_value.GetBoolean()) - continue; - output_ << "="; - if (!WriteBareItem(param_value)) - return false; - } - } - return true; - } - - bool WriteKey(const std::string& value) { - // Serializes a Key ([RFC8941] 4.1.1.3). - if (!value.size()) - return false; - if (value.find_first_not_of(kKeyChars) != std::string::npos) - return false; - if (!base::IsAsciiLower(value[0]) && value[0] != '*') - return false; - output_ << value; - return true; - } - - std::ostringstream output_; -}; - -} // namespace - -Item::Item() {} -Item::Item(const std::string& value, Item::ItemType type) - : type_(type), string_value_(value) {} -Item::Item(std::string&& value, Item::ItemType type) - : type_(type), string_value_(std::move(value)) { - DCHECK(type_ == kStringType || type_ == kTokenType || - type_ == kByteSequenceType); -} -Item::Item(const char* value, Item::ItemType type) - : Item(std::string(value), type) {} -Item::Item(int64_t value) : type_(kIntegerType), integer_value_(value) {} -Item::Item(double value) : type_(kDecimalType), decimal_value_(value) {} -Item::Item(bool value) : type_(kBooleanType), boolean_value_(value) {} - -bool operator==(const Item& lhs, const Item& rhs) { - if (lhs.type_ != rhs.type_) - return false; - switch (lhs.type_) { - case Item::kNullType: - return true; - case Item::kStringType: - case Item::kTokenType: - case Item::kByteSequenceType: - return lhs.string_value_ == rhs.string_value_; - case Item::kIntegerType: - return lhs.integer_value_ == rhs.integer_value_; - case Item::kDecimalType: - return lhs.decimal_value_ == rhs.decimal_value_; - case Item::kBooleanType: - return lhs.boolean_value_ == rhs.boolean_value_; - } - NOTREACHED(); - return false; -} - -ParameterizedItem::ParameterizedItem(const ParameterizedItem&) = default; -ParameterizedItem& ParameterizedItem::operator=(const ParameterizedItem&) = - default; -ParameterizedItem::ParameterizedItem(Item id, const Parameters& ps) - : item(std::move(id)), params(ps) {} -ParameterizedItem::~ParameterizedItem() = default; - -ParameterizedMember::ParameterizedMember() = default; -ParameterizedMember::ParameterizedMember(const ParameterizedMember&) = default; -ParameterizedMember& ParameterizedMember::operator=( - const ParameterizedMember&) = default; -ParameterizedMember::ParameterizedMember(std::vector<ParameterizedItem> id, - bool member_is_inner_list, - const Parameters& ps) - : member(std::move(id)), - member_is_inner_list(member_is_inner_list), - params(ps) {} -ParameterizedMember::ParameterizedMember(std::vector<ParameterizedItem> id, - const Parameters& ps) - : member(std::move(id)), member_is_inner_list(true), params(ps) {} -ParameterizedMember::ParameterizedMember(Item id, const Parameters& ps) - : member({{std::move(id), {}}}), member_is_inner_list(false), params(ps) {} -ParameterizedMember::~ParameterizedMember() = default; - -ParameterisedIdentifier::ParameterisedIdentifier( - const ParameterisedIdentifier&) = default; -ParameterisedIdentifier& ParameterisedIdentifier::operator=( - const ParameterisedIdentifier&) = default; -ParameterisedIdentifier::ParameterisedIdentifier(Item id, const Parameters& ps) - : identifier(std::move(id)), params(ps) {} -ParameterisedIdentifier::~ParameterisedIdentifier() = default; - -Dictionary::Dictionary() = default; -Dictionary::Dictionary(const Dictionary&) = default; -Dictionary::Dictionary(std::vector<DictionaryMember> members) - : members_(std::move(members)) {} -Dictionary::~Dictionary() = default; -std::vector<DictionaryMember>::iterator Dictionary::begin() { - return members_.begin(); -} -std::vector<DictionaryMember>::const_iterator Dictionary::begin() const { - return members_.begin(); -} -std::vector<DictionaryMember>::iterator Dictionary::end() { - return members_.end(); -} -std::vector<DictionaryMember>::const_iterator Dictionary::end() const { - return members_.end(); -} -ParameterizedMember& Dictionary::operator[](std::size_t idx) { - return members_[idx].second; -} -const ParameterizedMember& Dictionary::operator[](std::size_t idx) const { - return members_[idx].second; -} -ParameterizedMember& Dictionary::at(std::size_t idx) { - return (*this)[idx]; -} -const ParameterizedMember& Dictionary::at(std::size_t idx) const { - return (*this)[idx]; -} -ParameterizedMember& Dictionary::operator[](base::StringPiece key) { - auto it = - std::find_if(members_.begin(), members_.end(), - [key](const auto& member) { return member.first == key; }); - if (it != members_.end()) - return it->second; - return (*(members_.insert(members_.end(), make_pair(std::string(key), - ParameterizedMember())))) - .second; -} -ParameterizedMember& Dictionary::at(base::StringPiece key) { - auto it = - std::find_if(members_.begin(), members_.end(), - [key](const auto& member) { return member.first == key; }); - DCHECK(it != members_.end()) << "Provided key not found in dictionary"; - return it->second; -} -const ParameterizedMember& Dictionary::at(base::StringPiece key) const { - auto it = - std::find_if(members_.begin(), members_.end(), - [key](const auto& member) { return member.first == key; }); - DCHECK(it != members_.end()) << "Provided key not found in dictionary"; - return it->second; -} -bool Dictionary::empty() const { - return members_.empty(); -} -std::size_t Dictionary::size() const { - return members_.size(); -} -bool Dictionary::contains(base::StringPiece key) const { - for (auto& member : members_) { - if (member.first == key) - return true; - } - return false; -} - -absl::optional<ParameterizedItem> ParseItem(base::StringPiece str) { - StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); - absl::optional<ParameterizedItem> item = parser.ReadItem(); - if (item && parser.FinishParsing()) - return item; - return absl::nullopt; -} - -absl::optional<Item> ParseBareItem(base::StringPiece str) { - StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); - absl::optional<Item> item = parser.ReadBareItem(); - if (item && parser.FinishParsing()) - return item; - return absl::nullopt; -} - -absl::optional<ParameterisedList> ParseParameterisedList( - base::StringPiece str) { - StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09); - absl::optional<ParameterisedList> param_list = parser.ReadParameterisedList(); - if (param_list && parser.FinishParsing()) - return param_list; - return absl::nullopt; -} - -absl::optional<ListOfLists> ParseListOfLists(base::StringPiece str) { - StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09); - absl::optional<ListOfLists> list_of_lists = parser.ReadListOfLists(); - if (list_of_lists && parser.FinishParsing()) - return list_of_lists; - return absl::nullopt; -} - -absl::optional<List> ParseList(base::StringPiece str) { - StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); - absl::optional<List> list = parser.ReadList(); - if (list && parser.FinishParsing()) - return list; - return absl::nullopt; -} - -absl::optional<Dictionary> ParseDictionary(const base::StringPiece& str) { - StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); - absl::optional<Dictionary> dictionary = parser.ReadDictionary(); - if (dictionary && parser.FinishParsing()) - return dictionary; - return absl::nullopt; -} - -absl::optional<std::string> SerializeItem(const Item& value) { - StructuredHeaderSerializer s; - if (s.WriteItem(ParameterizedItem(value, {}))) - return s.Output(); - return absl::nullopt; -} - -absl::optional<std::string> SerializeItem(const ParameterizedItem& value) { - StructuredHeaderSerializer s; - if (s.WriteItem(value)) - return s.Output(); - return absl::nullopt; -} - -absl::optional<std::string> SerializeList(const List& value) { - StructuredHeaderSerializer s; - if (s.WriteList(value)) - return s.Output(); - return absl::nullopt; -} - -absl::optional<std::string> SerializeDictionary(const Dictionary& value) { - StructuredHeaderSerializer s; - if (s.WriteDictionary(value)) - return s.Output(); - return absl::nullopt; -} - -} // namespace structured_headers -} // namespace net diff --git a/chromium/net/http/structured_headers.h b/chromium/net/http/structured_headers.h index 262724694a9..97a1da5cf01 100644 --- a/chromium/net/http/structured_headers.h +++ b/chromium/net/http/structured_headers.h @@ -5,296 +5,69 @@ #ifndef NET_HTTP_STRUCTURED_HEADERS_H_ #define NET_HTTP_STRUCTURED_HEADERS_H_ -#include <algorithm> -#include <map> #include <string> -#include <tuple> -#include <vector> +#include "base/strings/abseil_string_conversions.h" #include "base/strings/string_piece.h" -#include "net/base/net_export.h" +#include "net/third_party/quiche/src/quiche/common/structured_headers.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace net { namespace structured_headers { -// This file implements parsing of HTTP structured headers, as defined in -// RFC8941 (https://www.rfc-editor.org/rfc/rfc8941.html). For compatibility with -// the shipped implementation of Web Packaging, this file also supports a -// previous revision of the standard, referred to here as "Draft 9". -// (https://datatracker.ietf.org/doc/draft-ietf-httpbis-header-structure/09/) -// -// The major difference between the two revisions is in the various list -// formats: Draft 9 describes "parameterised lists" and "lists-of-lists", while -// the final RFC uses a single "list" syntax, whose members may be inner lists. -// There should be no ambiguity, however, as the code which calls this parser -// should be expecting only a single type for a given header. -// -// References within the code are tagged with either [SH09] or [RFC8941], -// depending on which revision they refer to. -// -// Currently supported data types are: -// Item: -// integer: 123 -// string: "abc" -// token: abc -// byte sequence: *YWJj* -// Parameterised list: abc_123;a=1;b=2; cdef_456, ghi;q="9";r="w" -// List-of-lists: "foo";"bar", "baz", "bat"; "one" -// List: "foo", "bar", "It was the best of times." -// ("foo" "bar"), ("baz"), ("bat" "one"), () -// abc;a=1;b=2; cde_456, (ghi jkl);q="9";r=w -// Dictionary: a=(1 2), b=3, c=4;aa=bb, d=(5 6);valid=?0 -// -// Functions are provided to parse each of these, which are intended to be -// called with the complete value of an HTTP header (that is, any -// sub-structure will be handled internally by the parser; the exported -// functions are not intended to be called on partial header strings.) Input -// values should be ASCII byte strings (non-ASCII characters should not be -// present in Structured Header values, and will cause the entire header to fail -// to parse.) - -class NET_EXPORT Item { - public: - enum ItemType { - kNullType, - kIntegerType, - kDecimalType, - kStringType, - kTokenType, - kByteSequenceType, - kBooleanType - }; - Item(); - explicit Item(int64_t value); - explicit Item(double value); - explicit Item(bool value); - - // Constructors for string-like items: Strings, Tokens and Byte Sequences. - Item(const char* value, Item::ItemType type = kStringType); - // Item(StringPiece value, Item::ItemType type = kStringType); - Item(const std::string& value, Item::ItemType type = kStringType); - Item(std::string&& value, Item::ItemType type = kStringType); - - NET_EXPORT friend bool operator==(const Item& lhs, const Item& rhs); - inline friend bool operator!=(const Item& lhs, const Item& rhs) { - return !(lhs == rhs); - } - - bool is_null() const { return type_ == kNullType; } - bool is_integer() const { return type_ == kIntegerType; } - bool is_decimal() const { return type_ == kDecimalType; } - bool is_string() const { return type_ == kStringType; } - bool is_token() const { return type_ == kTokenType; } - bool is_byte_sequence() const { return type_ == kByteSequenceType; } - bool is_boolean() const { return type_ == kBooleanType; } - - int64_t GetInteger() const { - DCHECK_EQ(type_, kIntegerType); - return integer_value_; - } - double GetDecimal() const { - DCHECK_EQ(type_, kDecimalType); - return decimal_value_; - } - bool GetBoolean() const { - DCHECK_EQ(type_, kBooleanType); - return boolean_value_; - } - // TODO(iclelland): Split up accessors for String, Token and Byte Sequence. - const std::string& GetString() const { - DCHECK(type_ == kStringType || type_ == kTokenType || - type_ == kByteSequenceType); - return string_value_; - } - - ItemType Type() const { return type_; } - - private: - ItemType type_ = kNullType; - // TODO(iclelland): Make this class more memory-efficient, replacing the - // values here with a union or std::variant (when available). - int64_t integer_value_ = 0; - std::string string_value_; - double decimal_value_; - bool boolean_value_; -}; - -// Holds a ParameterizedIdentifier (draft 9 only). The contained Item must be a -// Token, and there may be any number of parameters. Parameter ordering is not -// significant. -struct NET_EXPORT ParameterisedIdentifier { - using Parameters = std::map<std::string, Item>; - - Item identifier; - Parameters params; - - ParameterisedIdentifier(const ParameterisedIdentifier&); - ParameterisedIdentifier& operator=(const ParameterisedIdentifier&); - ParameterisedIdentifier(Item, const Parameters&); - ~ParameterisedIdentifier(); -}; - -inline bool operator==(const ParameterisedIdentifier& lhs, - const ParameterisedIdentifier& rhs) { - return std::tie(lhs.identifier, lhs.params) == - std::tie(rhs.identifier, rhs.params); +using Item = quiche::structured_headers::Item; +using ParameterisedIdentifier = + quiche::structured_headers::ParameterisedIdentifier; +using ParameterizedItem = quiche::structured_headers::ParameterizedItem; +using ParameterizedMember = quiche::structured_headers::ParameterizedMember; +using DictionaryMember = quiche::structured_headers::DictionaryMember; +using Dictionary = quiche::structured_headers::Dictionary; +using ParameterisedList = quiche::structured_headers::ParameterisedList; +using ListOfLists = quiche::structured_headers::ListOfLists; +using List = quiche::structured_headers::List; +using Parameters = quiche::structured_headers::Parameters; + +inline absl::optional<ParameterizedItem> ParseItem(base::StringPiece str) { + return quiche::structured_headers::ParseItem( + base::StringPieceToStringView(str)); } - -using Parameters = std::vector<std::pair<std::string, Item>>; - -struct NET_EXPORT ParameterizedItem { - Item item; - Parameters params; - - ParameterizedItem(const ParameterizedItem&); - ParameterizedItem& operator=(const ParameterizedItem&); - ParameterizedItem(Item, const Parameters&); - ~ParameterizedItem(); -}; - -inline bool operator==(const ParameterizedItem& lhs, - const ParameterizedItem& rhs) { - return std::tie(lhs.item, lhs.params) == std::tie(rhs.item, rhs.params); +inline absl::optional<Item> ParseBareItem(base::StringPiece str) { + return quiche::structured_headers::ParseBareItem( + base::StringPieceToStringView(str)); } - -inline bool operator!=(const ParameterizedItem& lhs, - const ParameterizedItem& rhs) { - return !(lhs == rhs); +inline absl::optional<ParameterisedList> ParseParameterisedList( + base::StringPiece str) { + return quiche::structured_headers::ParseParameterisedList( + base::StringPieceToStringView(str)); } - -// Holds a ParameterizedMember, which may be either an single Item, or an Inner -// List of ParameterizedItems, along with any number of parameters. Parameter -// ordering is significant. -struct NET_EXPORT ParameterizedMember { - std::vector<ParameterizedItem> member; - // If false, then |member| should only hold one Item. - bool member_is_inner_list = false; - - Parameters params; - - ParameterizedMember(); - ParameterizedMember(const ParameterizedMember&); - ParameterizedMember& operator=(const ParameterizedMember&); - ParameterizedMember(std::vector<ParameterizedItem>, - bool member_is_inner_list, - const Parameters&); - // Shorthand constructor for a member which is an inner list. - ParameterizedMember(std::vector<ParameterizedItem>, const Parameters&); - // Shorthand constructor for a member which is a single Item. - ParameterizedMember(Item, const Parameters&); - ~ParameterizedMember(); -}; - -inline bool operator==(const ParameterizedMember& lhs, - const ParameterizedMember& rhs) { - return std::tie(lhs.member, lhs.member_is_inner_list, lhs.params) == - std::tie(rhs.member, rhs.member_is_inner_list, rhs.params); +inline absl::optional<ListOfLists> ParseListOfLists(base::StringPiece str) { + return quiche::structured_headers::ParseListOfLists( + base::StringPieceToStringView(str)); } - -using DictionaryMember = std::pair<std::string, ParameterizedMember>; - -// Structured Headers Draft 15 Dictionary. -class NET_EXPORT Dictionary { - public: - using iterator = std::vector<DictionaryMember>::iterator; - using const_iterator = std::vector<DictionaryMember>::const_iterator; - - Dictionary(); - Dictionary(const Dictionary&); - explicit Dictionary(std::vector<DictionaryMember> members); - ~Dictionary(); - Dictionary& operator=(const Dictionary&) = default; - iterator begin(); - const_iterator begin() const; - iterator end(); - const_iterator end() const; - - // operator[](size_t) and at(size_t) will both abort the program in case of - // out of bounds access. - ParameterizedMember& operator[](std::size_t idx); - const ParameterizedMember& operator[](std::size_t idx) const; - ParameterizedMember& at(std::size_t idx); - const ParameterizedMember& at(std::size_t idx) const; - - // Consistent with std::map, if |key| does not exist in the Dictionary, then - // operator[](base::StringPiece) will create an entry for it, but at() will - // abort the entire program. - ParameterizedMember& operator[](base::StringPiece key); - ParameterizedMember& at(base::StringPiece key); - const ParameterizedMember& at(base::StringPiece key) const; - - bool empty() const; - std::size_t size() const; - bool contains(base::StringPiece key) const; - friend bool operator==(const Dictionary& lhs, const Dictionary& rhs); - friend bool operator!=(const Dictionary& lhs, const Dictionary& rhs); - - private: - // Uses a vector to hold pairs of key and dictionary member. This makes - // look up by index and serialization much easier. - std::vector<DictionaryMember> members_; -}; - -inline bool operator==(const Dictionary& lhs, const Dictionary& rhs) { - return lhs.members_ == rhs.members_; +inline absl::optional<List> ParseList(base::StringPiece str) { + return quiche::structured_headers::ParseList( + base::StringPieceToStringView(str)); } - -inline bool operator!=(const Dictionary& lhs, const Dictionary& rhs) { - return !(lhs == rhs); +inline absl::optional<Dictionary> ParseDictionary( + const base::StringPiece& str) { + return quiche::structured_headers::ParseDictionary( + base::StringPieceToStringView(str)); } -// Structured Headers Draft 09 Parameterised List. -using ParameterisedList = std::vector<ParameterisedIdentifier>; -// Structured Headers Draft 09 List of Lists. -using ListOfLists = std::vector<std::vector<Item>>; -// Structured Headers Draft 15 List. -using List = std::vector<ParameterizedMember>; - -// Returns the result of parsing the header value as an Item, if it can be -// parsed as one, or nullopt if it cannot. Note that this uses the Draft 15 -// parsing rules, and so applies tighter range limits to integers. -NET_EXPORT absl::optional<ParameterizedItem> ParseItem(base::StringPiece str); - -// Returns the result of parsing the header value as an Item with no parameters, -// or nullopt if it cannot. Note that this uses the Draft 15 parsing rules, and -// so applies tighter range limits to integers. -NET_EXPORT absl::optional<Item> ParseBareItem(base::StringPiece str); - -// Returns the result of parsing the header value as a Parameterised List, if it -// can be parsed as one, or nullopt if it cannot. Note that parameter keys will -// be returned as strings, which are guaranteed to be ASCII-encoded. List items, -// as well as parameter values, will be returned as Items. This method uses the -// Draft 09 parsing rules for Items, so integers have the 64-bit int range. -// Structured-Headers Draft 09 only. -NET_EXPORT absl::optional<ParameterisedList> ParseParameterisedList( - base::StringPiece str); - -// Returns the result of parsing the header value as a List of Lists, if it can -// be parsed as one, or nullopt if it cannot. Inner list items will be returned -// as Items. This method uses the Draft 09 parsing rules for Items, so integers -// have the 64-bit int range. -// Structured-Headers Draft 09 only. -NET_EXPORT absl::optional<ListOfLists> ParseListOfLists(base::StringPiece str); - -// Returns the result of parsing the header value as a general List, if it can -// be parsed as one, or nullopt if it cannot. -// Structured-Headers Draft 15 only. -NET_EXPORT absl::optional<List> ParseList(base::StringPiece str); - -// Returns the result of parsing the header value as a general Dictionary, if it -// can be parsed as one, or nullopt if it cannot. Structured-Headers Draft 15 -// only. -NET_EXPORT absl::optional<Dictionary> ParseDictionary( - const base::StringPiece& str); - -// Serialization is implemented for Structured-Headers Draft 15 only. -NET_EXPORT absl::optional<std::string> SerializeItem(const Item& value); -NET_EXPORT absl::optional<std::string> SerializeItem( - const ParameterizedItem& value); -NET_EXPORT absl::optional<std::string> SerializeList(const List& value); -NET_EXPORT absl::optional<std::string> SerializeDictionary( - const Dictionary& value); +inline absl::optional<std::string> SerializeItem(const Item& value) { + return quiche::structured_headers::SerializeItem(value); +} +inline absl::optional<std::string> SerializeItem( + const ParameterizedItem& value) { + return quiche::structured_headers::SerializeItem(value); +} +inline absl::optional<std::string> SerializeList(const List& value) { + return quiche::structured_headers::SerializeList(value); +} +inline absl::optional<std::string> SerializeDictionary( + const Dictionary& value) { + return quiche::structured_headers::SerializeDictionary(value); +} } // namespace structured_headers } // namespace net diff --git a/chromium/net/http/structured_headers_generated_unittest.cc b/chromium/net/http/structured_headers_generated_unittest.cc deleted file mode 100644 index 82d1e01205f..00000000000 --- a/chromium/net/http/structured_headers_generated_unittest.cc +++ /dev/null @@ -1,3178 +0,0 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/http/structured_headers.h" - -#include <math.h> - -#include <limits> -#include <string> - -#include "testing/gtest/include/gtest/gtest.h" - -// This file contains tests cases for the Structured Header parser and -// serializer, taken from the public test case repository at -// https://github.com/httpwg/structured-field-tests. All of the tests are named, -// so a given test case can be found in the JSON files in that repository by -// searching for the test name. This file is generated, with the test cases -// being automatically translated from the JSON source to C++ unit tests. Please -// do not modify, as the contents will be overwritten when this is re-generated. - -// Generated on 2022-03-05 from structured-field-tests.git @ -// 4d33b9c2f4e0a7d7d1d733ccf48783aaead8ca4d - -namespace net { -namespace structured_headers { -namespace { - -// Helpers to make test cases clearer - -Item Integer(int64_t value) { - return Item(value); -} - -std::pair<std::string, Item> BooleanParam(std::string key, bool value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> DoubleParam(std::string key, double value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> Param(std::string key, int64_t value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> Param(std::string key, std::string value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> TokenParam(std::string key, std::string value) { - return std::make_pair(key, Item(value, Item::kTokenType)); -} - -const struct ParameterizedItemTestCase { - const char* name; - const char* raw; - size_t raw_len; - const absl::optional<ParameterizedItem> - expected; // nullopt if parse error is expected. - const char* canonical; // nullptr if parse error is expected, or if canonical - // format is identical to raw. -} parameterized_item_test_cases[] = { - - // binary.json - {"basic binary", - ":aGVsbG8=:", - 10, - {{Item("hello", Item::kByteSequenceType), {}}}}, - {"empty binary", "::", 2, {{Item("", Item::kByteSequenceType), {}}}}, - {"bad paddding", - ":aGVsbG8:", - 9, - {{Item("hello", Item::kByteSequenceType), {}}}, - ":aGVsbG8=:"}, - {"bad end delimiter", ":aGVsbG8=", 9, absl::nullopt}, - {"extra whitespace", ":aGVsb G8=:", 11, absl::nullopt}, - {"extra chars", ":aGVsbG!8=:", 11, absl::nullopt}, - {"suffix chars", ":aGVsbG8=!:", 11, absl::nullopt}, - {"non-zero pad bits", - ":iZ==:", - 6, - {{Item("\211", Item::kByteSequenceType), {}}}, - ":iQ==:"}, - {"non-ASCII binary", - ":/+Ah:", - 6, - {{Item("\377\340!", Item::kByteSequenceType), {}}}}, - {"base64url binary", ":_-Ah:", 6, absl::nullopt}, - // boolean.json - {"basic true boolean", "?1", 2, {{Item(true), {}}}}, - {"basic false boolean", "?0", 2, {{Item(false), {}}}}, - {"unknown boolean", "?Q", 2, absl::nullopt}, - {"whitespace boolean", "? 1", 3, absl::nullopt}, - {"negative zero boolean", "?-0", 3, absl::nullopt}, - {"T boolean", "?T", 2, absl::nullopt}, - {"F boolean", "?F", 2, absl::nullopt}, - {"t boolean", "?t", 2, absl::nullopt}, - {"f boolean", "?f", 2, absl::nullopt}, - {"spelled-out True boolean", "?True", 5, absl::nullopt}, - {"spelled-out False boolean", "?False", 6, absl::nullopt}, - // examples.json - {"Foo-Example", - "2; foourl=\"https://foo.example.com/\"", - 36, - {{Integer(2), {Param("foourl", "https://foo.example.com/")}}}, - "2;foourl=\"https://foo.example.com/\""}, - {"Example-IntHeader", - "1; a; b=?0", - 10, - {{Integer(1), {BooleanParam("a", true), BooleanParam("b", false)}}}, - "1;a;b=?0"}, - {"Example-IntItemHeader", "5", 1, {{Integer(5), {}}}}, - {"Example-IntItemHeader (params)", - "5; foo=bar", - 10, - {{Integer(5), {TokenParam("foo", "bar")}}}, - "5;foo=bar"}, - {"Example-IntegerHeader", "42", 2, {{Integer(42), {}}}}, - {"Example-FloatHeader", "4.5", 3, {{Item(4.500000), {}}}}, - {"Example-StringHeader", - "\"hello world\"", - 13, - {{Item("hello world"), {}}}}, - {"Example-BinaryHdr", - ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:", - 46, - {{Item("pretend this is binary content.", Item::kByteSequenceType), {}}}}, - {"Example-BoolHdr", "?1", 2, {{Item(true), {}}}}, - // item.json - {"empty item", "", 0, absl::nullopt}, - {"leading space", " \t 1", 4, absl::nullopt}, - {"trailing space", "1 \t ", 4, absl::nullopt}, - {"leading and trailing space", " 1 ", 5, {{Integer(1), {}}}, "1"}, - {"leading and trailing whitespace", " 1 ", 8, {{Integer(1), {}}}, "1"}, - // number-generated.json - {"1 digits of zero", "0", 1, {{Integer(0), {}}}, "0"}, - {"1 digit small integer", "1", 1, {{Integer(1), {}}}}, - {"1 digit large integer", "9", 1, {{Integer(9), {}}}}, - {"2 digits of zero", "00", 2, {{Integer(0), {}}}, "0"}, - {"2 digit small integer", "11", 2, {{Integer(11), {}}}}, - {"2 digit large integer", "99", 2, {{Integer(99), {}}}}, - {"3 digits of zero", "000", 3, {{Integer(0), {}}}, "0"}, - {"3 digit small integer", "111", 3, {{Integer(111), {}}}}, - {"3 digit large integer", "999", 3, {{Integer(999), {}}}}, - {"4 digits of zero", "0000", 4, {{Integer(0), {}}}, "0"}, - {"4 digit small integer", "1111", 4, {{Integer(1111), {}}}}, - {"4 digit large integer", "9999", 4, {{Integer(9999), {}}}}, - {"5 digits of zero", "00000", 5, {{Integer(0), {}}}, "0"}, - {"5 digit small integer", "11111", 5, {{Integer(11111), {}}}}, - {"5 digit large integer", "99999", 5, {{Integer(99999), {}}}}, - {"6 digits of zero", "000000", 6, {{Integer(0), {}}}, "0"}, - {"6 digit small integer", "111111", 6, {{Integer(111111), {}}}}, - {"6 digit large integer", "999999", 6, {{Integer(999999), {}}}}, - {"7 digits of zero", "0000000", 7, {{Integer(0), {}}}, "0"}, - {"7 digit small integer", "1111111", 7, {{Integer(1111111), {}}}}, - {"7 digit large integer", "9999999", 7, {{Integer(9999999), {}}}}, - {"8 digits of zero", "00000000", 8, {{Integer(0), {}}}, "0"}, - {"8 digit small integer", "11111111", 8, {{Integer(11111111), {}}}}, - {"8 digit large integer", "99999999", 8, {{Integer(99999999), {}}}}, - {"9 digits of zero", "000000000", 9, {{Integer(0), {}}}, "0"}, - {"9 digit small integer", "111111111", 9, {{Integer(111111111), {}}}}, - {"9 digit large integer", "999999999", 9, {{Integer(999999999), {}}}}, - {"10 digits of zero", "0000000000", 10, {{Integer(0), {}}}, "0"}, - {"10 digit small integer", "1111111111", 10, {{Integer(1111111111), {}}}}, - {"10 digit large integer", "9999999999", 10, {{Integer(9999999999), {}}}}, - {"11 digits of zero", "00000000000", 11, {{Integer(0), {}}}, "0"}, - {"11 digit small integer", "11111111111", 11, {{Integer(11111111111), {}}}}, - {"11 digit large integer", "99999999999", 11, {{Integer(99999999999), {}}}}, - {"12 digits of zero", "000000000000", 12, {{Integer(0), {}}}, "0"}, - {"12 digit small integer", - "111111111111", - 12, - {{Integer(111111111111), {}}}}, - {"12 digit large integer", - "999999999999", - 12, - {{Integer(999999999999), {}}}}, - {"13 digits of zero", "0000000000000", 13, {{Integer(0), {}}}, "0"}, - {"13 digit small integer", - "1111111111111", - 13, - {{Integer(1111111111111), {}}}}, - {"13 digit large integer", - "9999999999999", - 13, - {{Integer(9999999999999), {}}}}, - {"14 digits of zero", "00000000000000", 14, {{Integer(0), {}}}, "0"}, - {"14 digit small integer", - "11111111111111", - 14, - {{Integer(11111111111111), {}}}}, - {"14 digit large integer", - "99999999999999", - 14, - {{Integer(99999999999999), {}}}}, - {"15 digits of zero", "000000000000000", 15, {{Integer(0), {}}}, "0"}, - {"15 digit small integer", - "111111111111111", - 15, - {{Integer(111111111111111), {}}}}, - {"15 digit large integer", - "999999999999999", - 15, - {{Integer(999999999999999), {}}}}, - {"2 digit 0, 1 fractional small decimal", - "0.1", - 3, - {{Item(0.100000), {}}}, - "0.1"}, - {"2 digit, 1 fractional 0 decimal", - "1.0", - 3, - {{Item(1.000000), {}}}, - "1.0"}, - {"2 digit, 1 fractional small decimal", "1.1", 3, {{Item(1.100000), {}}}}, - {"2 digit, 1 fractional large decimal", "9.9", 3, {{Item(9.900000), {}}}}, - {"3 digit 0, 2 fractional small decimal", - "0.11", - 4, - {{Item(0.110000), {}}}, - "0.11"}, - {"3 digit, 2 fractional 0 decimal", - "1.00", - 4, - {{Item(1.000000), {}}}, - "1.0"}, - {"3 digit, 2 fractional small decimal", "1.11", 4, {{Item(1.110000), {}}}}, - {"3 digit, 2 fractional large decimal", "9.99", 4, {{Item(9.990000), {}}}}, - {"4 digit 0, 3 fractional small decimal", - "0.111", - 5, - {{Item(0.111000), {}}}, - "0.111"}, - {"4 digit, 3 fractional 0 decimal", - "1.000", - 5, - {{Item(1.000000), {}}}, - "1.0"}, - {"4 digit, 3 fractional small decimal", "1.111", 5, {{Item(1.111000), {}}}}, - {"4 digit, 3 fractional large decimal", "9.999", 5, {{Item(9.999000), {}}}}, - {"3 digit 0, 1 fractional small decimal", - "00.1", - 4, - {{Item(0.100000), {}}}, - "0.1"}, - {"3 digit, 1 fractional 0 decimal", - "11.0", - 4, - {{Item(11.000000), {}}}, - "11.0"}, - {"3 digit, 1 fractional small decimal", "11.1", 4, {{Item(11.100000), {}}}}, - {"3 digit, 1 fractional large decimal", "99.9", 4, {{Item(99.900000), {}}}}, - {"4 digit 0, 2 fractional small decimal", - "00.11", - 5, - {{Item(0.110000), {}}}, - "0.11"}, - {"4 digit, 2 fractional 0 decimal", - "11.00", - 5, - {{Item(11.000000), {}}}, - "11.0"}, - {"4 digit, 2 fractional small decimal", - "11.11", - 5, - {{Item(11.110000), {}}}}, - {"4 digit, 2 fractional large decimal", - "99.99", - 5, - {{Item(99.990000), {}}}}, - {"5 digit 0, 3 fractional small decimal", - "00.111", - 6, - {{Item(0.111000), {}}}, - "0.111"}, - {"5 digit, 3 fractional 0 decimal", - "11.000", - 6, - {{Item(11.000000), {}}}, - "11.0"}, - {"5 digit, 3 fractional small decimal", - "11.111", - 6, - {{Item(11.111000), {}}}}, - {"5 digit, 3 fractional large decimal", - "99.999", - 6, - {{Item(99.999000), {}}}}, - {"4 digit 0, 1 fractional small decimal", - "000.1", - 5, - {{Item(0.100000), {}}}, - "0.1"}, - {"4 digit, 1 fractional 0 decimal", - "111.0", - 5, - {{Item(111.000000), {}}}, - "111.0"}, - {"4 digit, 1 fractional small decimal", - "111.1", - 5, - {{Item(111.100000), {}}}}, - {"4 digit, 1 fractional large decimal", - "999.9", - 5, - {{Item(999.900000), {}}}}, - {"5 digit 0, 2 fractional small decimal", - "000.11", - 6, - {{Item(0.110000), {}}}, - "0.11"}, - {"5 digit, 2 fractional 0 decimal", - "111.00", - 6, - {{Item(111.000000), {}}}, - "111.0"}, - {"5 digit, 2 fractional small decimal", - "111.11", - 6, - {{Item(111.110000), {}}}}, - {"5 digit, 2 fractional large decimal", - "999.99", - 6, - {{Item(999.990000), {}}}}, - {"6 digit 0, 3 fractional small decimal", - "000.111", - 7, - {{Item(0.111000), {}}}, - "0.111"}, - {"6 digit, 3 fractional 0 decimal", - "111.000", - 7, - {{Item(111.000000), {}}}, - "111.0"}, - {"6 digit, 3 fractional small decimal", - "111.111", - 7, - {{Item(111.111000), {}}}}, - {"6 digit, 3 fractional large decimal", - "999.999", - 7, - {{Item(999.999000), {}}}}, - {"5 digit 0, 1 fractional small decimal", - "0000.1", - 6, - {{Item(0.100000), {}}}, - "0.1"}, - {"5 digit, 1 fractional 0 decimal", - "1111.0", - 6, - {{Item(1111.000000), {}}}, - "1111.0"}, - {"5 digit, 1 fractional small decimal", - "1111.1", - 6, - {{Item(1111.100000), {}}}}, - {"5 digit, 1 fractional large decimal", - "9999.9", - 6, - {{Item(9999.900000), {}}}}, - {"6 digit 0, 2 fractional small decimal", - "0000.11", - 7, - {{Item(0.110000), {}}}, - "0.11"}, - {"6 digit, 2 fractional 0 decimal", - "1111.00", - 7, - {{Item(1111.000000), {}}}, - "1111.0"}, - {"6 digit, 2 fractional small decimal", - "1111.11", - 7, - {{Item(1111.110000), {}}}}, - {"6 digit, 2 fractional large decimal", - "9999.99", - 7, - {{Item(9999.990000), {}}}}, - {"7 digit 0, 3 fractional small decimal", - "0000.111", - 8, - {{Item(0.111000), {}}}, - "0.111"}, - {"7 digit, 3 fractional 0 decimal", - "1111.000", - 8, - {{Item(1111.000000), {}}}, - "1111.0"}, - {"7 digit, 3 fractional small decimal", - "1111.111", - 8, - {{Item(1111.111000), {}}}}, - {"7 digit, 3 fractional large decimal", - "9999.999", - 8, - {{Item(9999.999000), {}}}}, - {"6 digit 0, 1 fractional small decimal", - "00000.1", - 7, - {{Item(0.100000), {}}}, - "0.1"}, - {"6 digit, 1 fractional 0 decimal", - "11111.0", - 7, - {{Item(11111.000000), {}}}, - "11111.0"}, - {"6 digit, 1 fractional small decimal", - "11111.1", - 7, - {{Item(11111.100000), {}}}}, - {"6 digit, 1 fractional large decimal", - "99999.9", - 7, - {{Item(99999.900000), {}}}}, - {"7 digit 0, 2 fractional small decimal", - "00000.11", - 8, - {{Item(0.110000), {}}}, - "0.11"}, - {"7 digit, 2 fractional 0 decimal", - "11111.00", - 8, - {{Item(11111.000000), {}}}, - "11111.0"}, - {"7 digit, 2 fractional small decimal", - "11111.11", - 8, - {{Item(11111.110000), {}}}}, - {"7 digit, 2 fractional large decimal", - "99999.99", - 8, - {{Item(99999.990000), {}}}}, - {"8 digit 0, 3 fractional small decimal", - "00000.111", - 9, - {{Item(0.111000), {}}}, - "0.111"}, - {"8 digit, 3 fractional 0 decimal", - "11111.000", - 9, - {{Item(11111.000000), {}}}, - "11111.0"}, - {"8 digit, 3 fractional small decimal", - "11111.111", - 9, - {{Item(11111.111000), {}}}}, - {"8 digit, 3 fractional large decimal", - "99999.999", - 9, - {{Item(99999.999000), {}}}}, - {"7 digit 0, 1 fractional small decimal", - "000000.1", - 8, - {{Item(0.100000), {}}}, - "0.1"}, - {"7 digit, 1 fractional 0 decimal", - "111111.0", - 8, - {{Item(111111.000000), {}}}, - "111111.0"}, - {"7 digit, 1 fractional small decimal", - "111111.1", - 8, - {{Item(111111.100000), {}}}}, - {"7 digit, 1 fractional large decimal", - "999999.9", - 8, - {{Item(999999.900000), {}}}}, - {"8 digit 0, 2 fractional small decimal", - "000000.11", - 9, - {{Item(0.110000), {}}}, - "0.11"}, - {"8 digit, 2 fractional 0 decimal", - "111111.00", - 9, - {{Item(111111.000000), {}}}, - "111111.0"}, - {"8 digit, 2 fractional small decimal", - "111111.11", - 9, - {{Item(111111.110000), {}}}}, - {"8 digit, 2 fractional large decimal", - "999999.99", - 9, - {{Item(999999.990000), {}}}}, - {"9 digit 0, 3 fractional small decimal", - "000000.111", - 10, - {{Item(0.111000), {}}}, - "0.111"}, - {"9 digit, 3 fractional 0 decimal", - "111111.000", - 10, - {{Item(111111.000000), {}}}, - "111111.0"}, - {"9 digit, 3 fractional small decimal", - "111111.111", - 10, - {{Item(111111.111000), {}}}}, - {"9 digit, 3 fractional large decimal", - "999999.999", - 10, - {{Item(999999.999000), {}}}}, - {"8 digit 0, 1 fractional small decimal", - "0000000.1", - 9, - {{Item(0.100000), {}}}, - "0.1"}, - {"8 digit, 1 fractional 0 decimal", - "1111111.0", - 9, - {{Item(1111111.000000), {}}}, - "1111111.0"}, - {"8 digit, 1 fractional small decimal", - "1111111.1", - 9, - {{Item(1111111.100000), {}}}}, - {"8 digit, 1 fractional large decimal", - "9999999.9", - 9, - {{Item(9999999.900000), {}}}}, - {"9 digit 0, 2 fractional small decimal", - "0000000.11", - 10, - {{Item(0.110000), {}}}, - "0.11"}, - {"9 digit, 2 fractional 0 decimal", - "1111111.00", - 10, - {{Item(1111111.000000), {}}}, - "1111111.0"}, - {"9 digit, 2 fractional small decimal", - "1111111.11", - 10, - {{Item(1111111.110000), {}}}}, - {"9 digit, 2 fractional large decimal", - "9999999.99", - 10, - {{Item(9999999.990000), {}}}}, - {"10 digit 0, 3 fractional small decimal", - "0000000.111", - 11, - {{Item(0.111000), {}}}, - "0.111"}, - {"10 digit, 3 fractional 0 decimal", - "1111111.000", - 11, - {{Item(1111111.000000), {}}}, - "1111111.0"}, - {"10 digit, 3 fractional small decimal", - "1111111.111", - 11, - {{Item(1111111.111000), {}}}}, - {"10 digit, 3 fractional large decimal", - "9999999.999", - 11, - {{Item(9999999.999000), {}}}}, - {"9 digit 0, 1 fractional small decimal", - "00000000.1", - 10, - {{Item(0.100000), {}}}, - "0.1"}, - {"9 digit, 1 fractional 0 decimal", - "11111111.0", - 10, - {{Item(11111111.000000), {}}}, - "11111111.0"}, - {"9 digit, 1 fractional small decimal", - "11111111.1", - 10, - {{Item(11111111.100000), {}}}}, - {"9 digit, 1 fractional large decimal", - "99999999.9", - 10, - {{Item(99999999.900000), {}}}}, - {"10 digit 0, 2 fractional small decimal", - "00000000.11", - 11, - {{Item(0.110000), {}}}, - "0.11"}, - {"10 digit, 2 fractional 0 decimal", - "11111111.00", - 11, - {{Item(11111111.000000), {}}}, - "11111111.0"}, - {"10 digit, 2 fractional small decimal", - "11111111.11", - 11, - {{Item(11111111.110000), {}}}}, - {"10 digit, 2 fractional large decimal", - "99999999.99", - 11, - {{Item(99999999.990000), {}}}}, - {"11 digit 0, 3 fractional small decimal", - "00000000.111", - 12, - {{Item(0.111000), {}}}, - "0.111"}, - {"11 digit, 3 fractional 0 decimal", - "11111111.000", - 12, - {{Item(11111111.000000), {}}}, - "11111111.0"}, - {"11 digit, 3 fractional small decimal", - "11111111.111", - 12, - {{Item(11111111.111000), {}}}}, - {"11 digit, 3 fractional large decimal", - "99999999.999", - 12, - {{Item(99999999.999000), {}}}}, - {"10 digit 0, 1 fractional small decimal", - "000000000.1", - 11, - {{Item(0.100000), {}}}, - "0.1"}, - {"10 digit, 1 fractional 0 decimal", - "111111111.0", - 11, - {{Item(111111111.000000), {}}}, - "111111111.0"}, - {"10 digit, 1 fractional small decimal", - "111111111.1", - 11, - {{Item(111111111.100000), {}}}}, - {"10 digit, 1 fractional large decimal", - "999999999.9", - 11, - {{Item(999999999.900000), {}}}}, - {"11 digit 0, 2 fractional small decimal", - "000000000.11", - 12, - {{Item(0.110000), {}}}, - "0.11"}, - {"11 digit, 2 fractional 0 decimal", - "111111111.00", - 12, - {{Item(111111111.000000), {}}}, - "111111111.0"}, - {"11 digit, 2 fractional small decimal", - "111111111.11", - 12, - {{Item(111111111.110000), {}}}}, - {"11 digit, 2 fractional large decimal", - "999999999.99", - 12, - {{Item(999999999.990000), {}}}}, - {"12 digit 0, 3 fractional small decimal", - "000000000.111", - 13, - {{Item(0.111000), {}}}, - "0.111"}, - {"12 digit, 3 fractional 0 decimal", - "111111111.000", - 13, - {{Item(111111111.000000), {}}}, - "111111111.0"}, - {"12 digit, 3 fractional small decimal", - "111111111.111", - 13, - {{Item(111111111.111000), {}}}}, - {"12 digit, 3 fractional large decimal", - "999999999.999", - 13, - {{Item(999999999.999000), {}}}}, - {"11 digit 0, 1 fractional small decimal", - "0000000000.1", - 12, - {{Item(0.100000), {}}}, - "0.1"}, - {"11 digit, 1 fractional 0 decimal", - "1111111111.0", - 12, - {{Item(1111111111.000000), {}}}, - "1111111111.0"}, - {"11 digit, 1 fractional small decimal", - "1111111111.1", - 12, - {{Item(1111111111.100000), {}}}}, - {"11 digit, 1 fractional large decimal", - "9999999999.9", - 12, - {{Item(9999999999.900000), {}}}}, - {"12 digit 0, 2 fractional small decimal", - "0000000000.11", - 13, - {{Item(0.110000), {}}}, - "0.11"}, - {"12 digit, 2 fractional 0 decimal", - "1111111111.00", - 13, - {{Item(1111111111.000000), {}}}, - "1111111111.0"}, - {"12 digit, 2 fractional small decimal", - "1111111111.11", - 13, - {{Item(1111111111.110000), {}}}}, - {"12 digit, 2 fractional large decimal", - "9999999999.99", - 13, - {{Item(9999999999.990000), {}}}}, - {"13 digit 0, 3 fractional small decimal", - "0000000000.111", - 14, - {{Item(0.111000), {}}}, - "0.111"}, - {"13 digit, 3 fractional 0 decimal", - "1111111111.000", - 14, - {{Item(1111111111.000000), {}}}, - "1111111111.0"}, - {"13 digit, 3 fractional small decimal", - "1111111111.111", - 14, - {{Item(1111111111.111000), {}}}}, - {"13 digit, 3 fractional large decimal", - "9999999999.999", - 14, - {{Item(9999999999.999001), {}}}}, - {"12 digit 0, 1 fractional small decimal", - "00000000000.1", - 13, - {{Item(0.100000), {}}}, - "0.1"}, - {"12 digit, 1 fractional 0 decimal", - "11111111111.0", - 13, - {{Item(11111111111.000000), {}}}, - "11111111111.0"}, - {"12 digit, 1 fractional small decimal", - "11111111111.1", - 13, - {{Item(11111111111.100000), {}}}}, - {"12 digit, 1 fractional large decimal", - "99999999999.9", - 13, - {{Item(99999999999.899994), {}}}}, - {"13 digit 0, 2 fractional small decimal", - "00000000000.11", - 14, - {{Item(0.110000), {}}}, - "0.11"}, - {"13 digit, 2 fractional 0 decimal", - "11111111111.00", - 14, - {{Item(11111111111.000000), {}}}, - "11111111111.0"}, - {"13 digit, 2 fractional small decimal", - "11111111111.11", - 14, - {{Item(11111111111.110001), {}}}}, - {"13 digit, 2 fractional large decimal", - "99999999999.99", - 14, - {{Item(99999999999.990005), {}}}}, - {"14 digit 0, 3 fractional small decimal", - "00000000000.111", - 15, - {{Item(0.111000), {}}}, - "0.111"}, - {"14 digit, 3 fractional 0 decimal", - "11111111111.000", - 15, - {{Item(11111111111.000000), {}}}, - "11111111111.0"}, - {"14 digit, 3 fractional small decimal", - "11111111111.111", - 15, - {{Item(11111111111.111000), {}}}}, - {"14 digit, 3 fractional large decimal", - "99999999999.999", - 15, - {{Item(99999999999.998993), {}}}}, - {"13 digit 0, 1 fractional small decimal", - "000000000000.1", - 14, - {{Item(0.100000), {}}}, - "0.1"}, - {"13 digit, 1 fractional 0 decimal", - "111111111111.0", - 14, - {{Item(111111111111.000000), {}}}, - "111111111111.0"}, - {"13 digit, 1 fractional small decimal", - "111111111111.1", - 14, - {{Item(111111111111.100006), {}}}}, - {"13 digit, 1 fractional large decimal", - "999999999999.9", - 14, - {{Item(999999999999.900024), {}}}}, - {"14 digit 0, 2 fractional small decimal", - "000000000000.11", - 15, - {{Item(0.110000), {}}}, - "0.11"}, - {"14 digit, 2 fractional 0 decimal", - "111111111111.00", - 15, - {{Item(111111111111.000000), {}}}, - "111111111111.0"}, - {"14 digit, 2 fractional small decimal", - "111111111111.11", - 15, - {{Item(111111111111.110001), {}}}}, - {"14 digit, 2 fractional large decimal", - "999999999999.99", - 15, - {{Item(999999999999.989990), {}}}}, - {"15 digit 0, 3 fractional small decimal", - "000000000000.111", - 16, - {{Item(0.111000), {}}}, - "0.111"}, - {"15 digit, 3 fractional 0 decimal", - "111111111111.000", - 16, - {{Item(111111111111.000000), {}}}, - "111111111111.0"}, - {"15 digit, 3 fractional small decimal", - "111111111111.111", - 16, - {{Item(111111111111.110992), {}}}}, - {"15 digit, 3 fractional large decimal", - "999999999999.999", - 16, - {{Item(999999999999.999023), {}}}}, - {"too many digit 0 decimal", "000000000000000.0", 17, absl::nullopt}, - {"too many fractional digits 0 decimal", "000000000000.0000", 17, - absl::nullopt}, - {"too many digit 9 decimal", "999999999999999.9", 17, absl::nullopt}, - {"too many fractional digits 9 decimal", "999999999999.9999", 17, - absl::nullopt}, - // number.json - {"basic integer", "42", 2, {{Integer(42), {}}}}, - {"zero integer", "0", 1, {{Integer(0), {}}}}, - {"negative zero", "-0", 2, {{Integer(0), {}}}, "0"}, - {"double negative zero", "--0", 3, absl::nullopt}, - {"negative integer", "-42", 3, {{Integer(-42), {}}}}, - {"leading 0 integer", "042", 3, {{Integer(42), {}}}, "42"}, - {"leading 0 negative integer", "-042", 4, {{Integer(-42), {}}}, "-42"}, - {"leading 0 zero", "00", 2, {{Integer(0), {}}}, "0"}, - {"comma", "2,3", 3, absl::nullopt}, - {"negative non-DIGIT first character", "-a23", 4, absl::nullopt}, - {"sign out of place", "4-2", 3, absl::nullopt}, - {"whitespace after sign", "- 42", 4, absl::nullopt}, - {"long integer", "123456789012345", 15, {{Integer(123456789012345), {}}}}, - {"long negative integer", - "-123456789012345", - 16, - {{Integer(-123456789012345), {}}}}, - {"too long integer", "1234567890123456", 16, absl::nullopt}, - {"negative too long integer", "-1234567890123456", 17, absl::nullopt}, - {"simple decimal", "1.23", 4, {{Item(1.230000), {}}}}, - {"negative decimal", "-1.23", 5, {{Item(-1.230000), {}}}}, - {"decimal, whitespace after decimal", "1. 23", 5, absl::nullopt}, - {"decimal, whitespace before decimal", "1 .23", 5, absl::nullopt}, - {"negative decimal, whitespace after sign", "- 1.23", 6, absl::nullopt}, - {"tricky precision decimal", - "123456789012.1", - 14, - {{Item(123456789012.100006), {}}}}, - {"double decimal decimal", "1.5.4", 5, absl::nullopt}, - {"adjacent double decimal decimal", "1..4", 4, absl::nullopt}, - {"decimal with three fractional digits", - "1.123", - 5, - {{Item(1.123000), {}}}}, - {"negative decimal with three fractional digits", - "-1.123", - 6, - {{Item(-1.123000), {}}}}, - {"decimal with four fractional digits", "1.1234", 6, absl::nullopt}, - {"negative decimal with four fractional digits", "-1.1234", 7, - absl::nullopt}, - {"decimal with thirteen integer digits", "1234567890123.0", 15, - absl::nullopt}, - {"negative decimal with thirteen integer digits", "-1234567890123.0", 16, - absl::nullopt}, - // string-generated.json - {"0x00 in string", "\" \000 \"", 5, absl::nullopt}, - {"0x01 in string", "\" \001 \"", 5, absl::nullopt}, - {"0x02 in string", "\" \002 \"", 5, absl::nullopt}, - {"0x03 in string", "\" \003 \"", 5, absl::nullopt}, - {"0x04 in string", "\" \004 \"", 5, absl::nullopt}, - {"0x05 in string", "\" \005 \"", 5, absl::nullopt}, - {"0x06 in string", "\" \006 \"", 5, absl::nullopt}, - {"0x07 in string", "\" \a \"", 5, absl::nullopt}, - {"0x08 in string", "\" \b \"", 5, absl::nullopt}, - {"0x09 in string", "\" \t \"", 5, absl::nullopt}, - {"0x0a in string", "\" \n \"", 5, absl::nullopt}, - {"0x0b in string", "\" \v \"", 5, absl::nullopt}, - {"0x0c in string", "\" \f \"", 5, absl::nullopt}, - {"0x0d in string", "\" \r \"", 5, absl::nullopt}, - {"0x0e in string", "\" \016 \"", 5, absl::nullopt}, - {"0x0f in string", "\" \017 \"", 5, absl::nullopt}, - {"0x10 in string", "\" \020 \"", 5, absl::nullopt}, - {"0x11 in string", "\" \021 \"", 5, absl::nullopt}, - {"0x12 in string", "\" \022 \"", 5, absl::nullopt}, - {"0x13 in string", "\" \023 \"", 5, absl::nullopt}, - {"0x14 in string", "\" \024 \"", 5, absl::nullopt}, - {"0x15 in string", "\" \025 \"", 5, absl::nullopt}, - {"0x16 in string", "\" \026 \"", 5, absl::nullopt}, - {"0x17 in string", "\" \027 \"", 5, absl::nullopt}, - {"0x18 in string", "\" \030 \"", 5, absl::nullopt}, - {"0x19 in string", "\" \031 \"", 5, absl::nullopt}, - {"0x1a in string", "\" \032 \"", 5, absl::nullopt}, - {"0x1b in string", "\" \033 \"", 5, absl::nullopt}, - {"0x1c in string", "\" \034 \"", 5, absl::nullopt}, - {"0x1d in string", "\" \035 \"", 5, absl::nullopt}, - {"0x1e in string", "\" \036 \"", 5, absl::nullopt}, - {"0x1f in string", "\" \037 \"", 5, absl::nullopt}, - {"0x20 in string", "\" \"", 5, {{Item(" "), {}}}}, - {"0x21 in string", "\" ! \"", 5, {{Item(" ! "), {}}}}, - {"0x22 in string", "\" \" \"", 5, absl::nullopt}, - {"0x23 in string", "\" # \"", 5, {{Item(" # "), {}}}}, - {"0x24 in string", "\" $ \"", 5, {{Item(" $ "), {}}}}, - {"0x25 in string", "\" % \"", 5, {{Item(" % "), {}}}}, - {"0x26 in string", "\" & \"", 5, {{Item(" & "), {}}}}, - {"0x27 in string", "\" ' \"", 5, {{Item(" ' "), {}}}}, - {"0x28 in string", "\" ( \"", 5, {{Item(" ( "), {}}}}, - {"0x29 in string", "\" ) \"", 5, {{Item(" ) "), {}}}}, - {"0x2a in string", "\" * \"", 5, {{Item(" * "), {}}}}, - {"0x2b in string", "\" + \"", 5, {{Item(" + "), {}}}}, - {"0x2c in string", "\" , \"", 5, {{Item(" , "), {}}}}, - {"0x2d in string", "\" - \"", 5, {{Item(" - "), {}}}}, - {"0x2e in string", "\" . \"", 5, {{Item(" . "), {}}}}, - {"0x2f in string", "\" / \"", 5, {{Item(" / "), {}}}}, - {"0x30 in string", "\" 0 \"", 5, {{Item(" 0 "), {}}}}, - {"0x31 in string", "\" 1 \"", 5, {{Item(" 1 "), {}}}}, - {"0x32 in string", "\" 2 \"", 5, {{Item(" 2 "), {}}}}, - {"0x33 in string", "\" 3 \"", 5, {{Item(" 3 "), {}}}}, - {"0x34 in string", "\" 4 \"", 5, {{Item(" 4 "), {}}}}, - {"0x35 in string", "\" 5 \"", 5, {{Item(" 5 "), {}}}}, - {"0x36 in string", "\" 6 \"", 5, {{Item(" 6 "), {}}}}, - {"0x37 in string", "\" 7 \"", 5, {{Item(" 7 "), {}}}}, - {"0x38 in string", "\" 8 \"", 5, {{Item(" 8 "), {}}}}, - {"0x39 in string", "\" 9 \"", 5, {{Item(" 9 "), {}}}}, - {"0x3a in string", "\" : \"", 5, {{Item(" : "), {}}}}, - {"0x3b in string", "\" ; \"", 5, {{Item(" ; "), {}}}}, - {"0x3c in string", "\" < \"", 5, {{Item(" < "), {}}}}, - {"0x3d in string", "\" = \"", 5, {{Item(" = "), {}}}}, - {"0x3e in string", "\" > \"", 5, {{Item(" > "), {}}}}, - {"0x3f in string", "\" ? \"", 5, {{Item(" ? "), {}}}}, - {"0x40 in string", "\" @ \"", 5, {{Item(" @ "), {}}}}, - {"0x41 in string", "\" A \"", 5, {{Item(" A "), {}}}}, - {"0x42 in string", "\" B \"", 5, {{Item(" B "), {}}}}, - {"0x43 in string", "\" C \"", 5, {{Item(" C "), {}}}}, - {"0x44 in string", "\" D \"", 5, {{Item(" D "), {}}}}, - {"0x45 in string", "\" E \"", 5, {{Item(" E "), {}}}}, - {"0x46 in string", "\" F \"", 5, {{Item(" F "), {}}}}, - {"0x47 in string", "\" G \"", 5, {{Item(" G "), {}}}}, - {"0x48 in string", "\" H \"", 5, {{Item(" H "), {}}}}, - {"0x49 in string", "\" I \"", 5, {{Item(" I "), {}}}}, - {"0x4a in string", "\" J \"", 5, {{Item(" J "), {}}}}, - {"0x4b in string", "\" K \"", 5, {{Item(" K "), {}}}}, - {"0x4c in string", "\" L \"", 5, {{Item(" L "), {}}}}, - {"0x4d in string", "\" M \"", 5, {{Item(" M "), {}}}}, - {"0x4e in string", "\" N \"", 5, {{Item(" N "), {}}}}, - {"0x4f in string", "\" O \"", 5, {{Item(" O "), {}}}}, - {"0x50 in string", "\" P \"", 5, {{Item(" P "), {}}}}, - {"0x51 in string", "\" Q \"", 5, {{Item(" Q "), {}}}}, - {"0x52 in string", "\" R \"", 5, {{Item(" R "), {}}}}, - {"0x53 in string", "\" S \"", 5, {{Item(" S "), {}}}}, - {"0x54 in string", "\" T \"", 5, {{Item(" T "), {}}}}, - {"0x55 in string", "\" U \"", 5, {{Item(" U "), {}}}}, - {"0x56 in string", "\" V \"", 5, {{Item(" V "), {}}}}, - {"0x57 in string", "\" W \"", 5, {{Item(" W "), {}}}}, - {"0x58 in string", "\" X \"", 5, {{Item(" X "), {}}}}, - {"0x59 in string", "\" Y \"", 5, {{Item(" Y "), {}}}}, - {"0x5a in string", "\" Z \"", 5, {{Item(" Z "), {}}}}, - {"0x5b in string", "\" [ \"", 5, {{Item(" [ "), {}}}}, - {"0x5c in string", "\" \\ \"", 5, absl::nullopt}, - {"0x5d in string", "\" ] \"", 5, {{Item(" ] "), {}}}}, - {"0x5e in string", "\" ^ \"", 5, {{Item(" ^ "), {}}}}, - {"0x5f in string", "\" _ \"", 5, {{Item(" _ "), {}}}}, - {"0x60 in string", "\" ` \"", 5, {{Item(" ` "), {}}}}, - {"0x61 in string", "\" a \"", 5, {{Item(" a "), {}}}}, - {"0x62 in string", "\" b \"", 5, {{Item(" b "), {}}}}, - {"0x63 in string", "\" c \"", 5, {{Item(" c "), {}}}}, - {"0x64 in string", "\" d \"", 5, {{Item(" d "), {}}}}, - {"0x65 in string", "\" e \"", 5, {{Item(" e "), {}}}}, - {"0x66 in string", "\" f \"", 5, {{Item(" f "), {}}}}, - {"0x67 in string", "\" g \"", 5, {{Item(" g "), {}}}}, - {"0x68 in string", "\" h \"", 5, {{Item(" h "), {}}}}, - {"0x69 in string", "\" i \"", 5, {{Item(" i "), {}}}}, - {"0x6a in string", "\" j \"", 5, {{Item(" j "), {}}}}, - {"0x6b in string", "\" k \"", 5, {{Item(" k "), {}}}}, - {"0x6c in string", "\" l \"", 5, {{Item(" l "), {}}}}, - {"0x6d in string", "\" m \"", 5, {{Item(" m "), {}}}}, - {"0x6e in string", "\" n \"", 5, {{Item(" n "), {}}}}, - {"0x6f in string", "\" o \"", 5, {{Item(" o "), {}}}}, - {"0x70 in string", "\" p \"", 5, {{Item(" p "), {}}}}, - {"0x71 in string", "\" q \"", 5, {{Item(" q "), {}}}}, - {"0x72 in string", "\" r \"", 5, {{Item(" r "), {}}}}, - {"0x73 in string", "\" s \"", 5, {{Item(" s "), {}}}}, - {"0x74 in string", "\" t \"", 5, {{Item(" t "), {}}}}, - {"0x75 in string", "\" u \"", 5, {{Item(" u "), {}}}}, - {"0x76 in string", "\" v \"", 5, {{Item(" v "), {}}}}, - {"0x77 in string", "\" w \"", 5, {{Item(" w "), {}}}}, - {"0x78 in string", "\" x \"", 5, {{Item(" x "), {}}}}, - {"0x79 in string", "\" y \"", 5, {{Item(" y "), {}}}}, - {"0x7a in string", "\" z \"", 5, {{Item(" z "), {}}}}, - {"0x7b in string", "\" { \"", 5, {{Item(" { "), {}}}}, - {"0x7c in string", "\" | \"", 5, {{Item(" | "), {}}}}, - {"0x7d in string", "\" } \"", 5, {{Item(" } "), {}}}}, - {"0x7e in string", "\" ~ \"", 5, {{Item(" ~ "), {}}}}, - {"0x7f in string", "\" \177 \"", 5, absl::nullopt}, - {"Escaped 0x00 in string", "\"\\\000\"", 4, absl::nullopt}, - {"Escaped 0x01 in string", "\"\\\001\"", 4, absl::nullopt}, - {"Escaped 0x02 in string", "\"\\\002\"", 4, absl::nullopt}, - {"Escaped 0x03 in string", "\"\\\003\"", 4, absl::nullopt}, - {"Escaped 0x04 in string", "\"\\\004\"", 4, absl::nullopt}, - {"Escaped 0x05 in string", "\"\\\005\"", 4, absl::nullopt}, - {"Escaped 0x06 in string", "\"\\\006\"", 4, absl::nullopt}, - {"Escaped 0x07 in string", "\"\\\a\"", 4, absl::nullopt}, - {"Escaped 0x08 in string", "\"\\\b\"", 4, absl::nullopt}, - {"Escaped 0x09 in string", "\"\\\t\"", 4, absl::nullopt}, - {"Escaped 0x0a in string", "\"\\\n\"", 4, absl::nullopt}, - {"Escaped 0x0b in string", "\"\\\v\"", 4, absl::nullopt}, - {"Escaped 0x0c in string", "\"\\\f\"", 4, absl::nullopt}, - {"Escaped 0x0d in string", "\"\\\r\"", 4, absl::nullopt}, - {"Escaped 0x0e in string", "\"\\\016\"", 4, absl::nullopt}, - {"Escaped 0x0f in string", "\"\\\017\"", 4, absl::nullopt}, - {"Escaped 0x10 in string", "\"\\\020\"", 4, absl::nullopt}, - {"Escaped 0x11 in string", "\"\\\021\"", 4, absl::nullopt}, - {"Escaped 0x12 in string", "\"\\\022\"", 4, absl::nullopt}, - {"Escaped 0x13 in string", "\"\\\023\"", 4, absl::nullopt}, - {"Escaped 0x14 in string", "\"\\\024\"", 4, absl::nullopt}, - {"Escaped 0x15 in string", "\"\\\025\"", 4, absl::nullopt}, - {"Escaped 0x16 in string", "\"\\\026\"", 4, absl::nullopt}, - {"Escaped 0x17 in string", "\"\\\027\"", 4, absl::nullopt}, - {"Escaped 0x18 in string", "\"\\\030\"", 4, absl::nullopt}, - {"Escaped 0x19 in string", "\"\\\031\"", 4, absl::nullopt}, - {"Escaped 0x1a in string", "\"\\\032\"", 4, absl::nullopt}, - {"Escaped 0x1b in string", "\"\\\033\"", 4, absl::nullopt}, - {"Escaped 0x1c in string", "\"\\\034\"", 4, absl::nullopt}, - {"Escaped 0x1d in string", "\"\\\035\"", 4, absl::nullopt}, - {"Escaped 0x1e in string", "\"\\\036\"", 4, absl::nullopt}, - {"Escaped 0x1f in string", "\"\\\037\"", 4, absl::nullopt}, - {"Escaped 0x20 in string", "\"\\ \"", 4, absl::nullopt}, - {"Escaped 0x21 in string", "\"\\!\"", 4, absl::nullopt}, - {"Escaped 0x22 in string", "\"\\\"\"", 4, {{Item("\""), {}}}}, - {"Escaped 0x23 in string", "\"\\#\"", 4, absl::nullopt}, - {"Escaped 0x24 in string", "\"\\$\"", 4, absl::nullopt}, - {"Escaped 0x25 in string", "\"\\%\"", 4, absl::nullopt}, - {"Escaped 0x26 in string", "\"\\&\"", 4, absl::nullopt}, - {"Escaped 0x27 in string", "\"\\'\"", 4, absl::nullopt}, - {"Escaped 0x28 in string", "\"\\(\"", 4, absl::nullopt}, - {"Escaped 0x29 in string", "\"\\)\"", 4, absl::nullopt}, - {"Escaped 0x2a in string", "\"\\*\"", 4, absl::nullopt}, - {"Escaped 0x2b in string", "\"\\+\"", 4, absl::nullopt}, - {"Escaped 0x2c in string", "\"\\,\"", 4, absl::nullopt}, - {"Escaped 0x2d in string", "\"\\-\"", 4, absl::nullopt}, - {"Escaped 0x2e in string", "\"\\.\"", 4, absl::nullopt}, - {"Escaped 0x2f in string", "\"\\/\"", 4, absl::nullopt}, - {"Escaped 0x30 in string", "\"\\0\"", 4, absl::nullopt}, - {"Escaped 0x31 in string", "\"\\1\"", 4, absl::nullopt}, - {"Escaped 0x32 in string", "\"\\2\"", 4, absl::nullopt}, - {"Escaped 0x33 in string", "\"\\3\"", 4, absl::nullopt}, - {"Escaped 0x34 in string", "\"\\4\"", 4, absl::nullopt}, - {"Escaped 0x35 in string", "\"\\5\"", 4, absl::nullopt}, - {"Escaped 0x36 in string", "\"\\6\"", 4, absl::nullopt}, - {"Escaped 0x37 in string", "\"\\7\"", 4, absl::nullopt}, - {"Escaped 0x38 in string", "\"\\8\"", 4, absl::nullopt}, - {"Escaped 0x39 in string", "\"\\9\"", 4, absl::nullopt}, - {"Escaped 0x3a in string", "\"\\:\"", 4, absl::nullopt}, - {"Escaped 0x3b in string", "\"\\;\"", 4, absl::nullopt}, - {"Escaped 0x3c in string", "\"\\<\"", 4, absl::nullopt}, - {"Escaped 0x3d in string", "\"\\=\"", 4, absl::nullopt}, - {"Escaped 0x3e in string", "\"\\>\"", 4, absl::nullopt}, - {"Escaped 0x3f in string", "\"\\?\"", 4, absl::nullopt}, - {"Escaped 0x40 in string", "\"\\@\"", 4, absl::nullopt}, - {"Escaped 0x41 in string", "\"\\A\"", 4, absl::nullopt}, - {"Escaped 0x42 in string", "\"\\B\"", 4, absl::nullopt}, - {"Escaped 0x43 in string", "\"\\C\"", 4, absl::nullopt}, - {"Escaped 0x44 in string", "\"\\D\"", 4, absl::nullopt}, - {"Escaped 0x45 in string", "\"\\E\"", 4, absl::nullopt}, - {"Escaped 0x46 in string", "\"\\F\"", 4, absl::nullopt}, - {"Escaped 0x47 in string", "\"\\G\"", 4, absl::nullopt}, - {"Escaped 0x48 in string", "\"\\H\"", 4, absl::nullopt}, - {"Escaped 0x49 in string", "\"\\I\"", 4, absl::nullopt}, - {"Escaped 0x4a in string", "\"\\J\"", 4, absl::nullopt}, - {"Escaped 0x4b in string", "\"\\K\"", 4, absl::nullopt}, - {"Escaped 0x4c in string", "\"\\L\"", 4, absl::nullopt}, - {"Escaped 0x4d in string", "\"\\M\"", 4, absl::nullopt}, - {"Escaped 0x4e in string", "\"\\N\"", 4, absl::nullopt}, - {"Escaped 0x4f in string", "\"\\O\"", 4, absl::nullopt}, - {"Escaped 0x50 in string", "\"\\P\"", 4, absl::nullopt}, - {"Escaped 0x51 in string", "\"\\Q\"", 4, absl::nullopt}, - {"Escaped 0x52 in string", "\"\\R\"", 4, absl::nullopt}, - {"Escaped 0x53 in string", "\"\\S\"", 4, absl::nullopt}, - {"Escaped 0x54 in string", "\"\\T\"", 4, absl::nullopt}, - {"Escaped 0x55 in string", "\"\\U\"", 4, absl::nullopt}, - {"Escaped 0x56 in string", "\"\\V\"", 4, absl::nullopt}, - {"Escaped 0x57 in string", "\"\\W\"", 4, absl::nullopt}, - {"Escaped 0x58 in string", "\"\\X\"", 4, absl::nullopt}, - {"Escaped 0x59 in string", "\"\\Y\"", 4, absl::nullopt}, - {"Escaped 0x5a in string", "\"\\Z\"", 4, absl::nullopt}, - {"Escaped 0x5b in string", "\"\\[\"", 4, absl::nullopt}, - {"Escaped 0x5c in string", "\"\\\\\"", 4, {{Item("\\"), {}}}}, - {"Escaped 0x5d in string", "\"\\]\"", 4, absl::nullopt}, - {"Escaped 0x5e in string", "\"\\^\"", 4, absl::nullopt}, - {"Escaped 0x5f in string", "\"\\_\"", 4, absl::nullopt}, - {"Escaped 0x60 in string", "\"\\`\"", 4, absl::nullopt}, - {"Escaped 0x61 in string", "\"\\a\"", 4, absl::nullopt}, - {"Escaped 0x62 in string", "\"\\b\"", 4, absl::nullopt}, - {"Escaped 0x63 in string", "\"\\c\"", 4, absl::nullopt}, - {"Escaped 0x64 in string", "\"\\d\"", 4, absl::nullopt}, - {"Escaped 0x65 in string", "\"\\e\"", 4, absl::nullopt}, - {"Escaped 0x66 in string", "\"\\f\"", 4, absl::nullopt}, - {"Escaped 0x67 in string", "\"\\g\"", 4, absl::nullopt}, - {"Escaped 0x68 in string", "\"\\h\"", 4, absl::nullopt}, - {"Escaped 0x69 in string", "\"\\i\"", 4, absl::nullopt}, - {"Escaped 0x6a in string", "\"\\j\"", 4, absl::nullopt}, - {"Escaped 0x6b in string", "\"\\k\"", 4, absl::nullopt}, - {"Escaped 0x6c in string", "\"\\l\"", 4, absl::nullopt}, - {"Escaped 0x6d in string", "\"\\m\"", 4, absl::nullopt}, - {"Escaped 0x6e in string", "\"\\n\"", 4, absl::nullopt}, - {"Escaped 0x6f in string", "\"\\o\"", 4, absl::nullopt}, - {"Escaped 0x70 in string", "\"\\p\"", 4, absl::nullopt}, - {"Escaped 0x71 in string", "\"\\q\"", 4, absl::nullopt}, - {"Escaped 0x72 in string", "\"\\r\"", 4, absl::nullopt}, - {"Escaped 0x73 in string", "\"\\s\"", 4, absl::nullopt}, - {"Escaped 0x74 in string", "\"\\t\"", 4, absl::nullopt}, - {"Escaped 0x75 in string", "\"\\u\"", 4, absl::nullopt}, - {"Escaped 0x76 in string", "\"\\v\"", 4, absl::nullopt}, - {"Escaped 0x77 in string", "\"\\w\"", 4, absl::nullopt}, - {"Escaped 0x78 in string", "\"\\x\"", 4, absl::nullopt}, - {"Escaped 0x79 in string", "\"\\y\"", 4, absl::nullopt}, - {"Escaped 0x7a in string", "\"\\z\"", 4, absl::nullopt}, - {"Escaped 0x7b in string", "\"\\{\"", 4, absl::nullopt}, - {"Escaped 0x7c in string", "\"\\|\"", 4, absl::nullopt}, - {"Escaped 0x7d in string", "\"\\}\"", 4, absl::nullopt}, - {"Escaped 0x7e in string", "\"\\~\"", 4, absl::nullopt}, - {"Escaped 0x7f in string", "\"\\\177\"", 4, absl::nullopt}, - // string.json - {"basic string", "\"foo bar\"", 9, {{Item("foo bar"), {}}}}, - {"empty string", "\"\"", 2, {{Item(""), {}}}}, - {"long string", - "\"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo " - "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo " - "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo " - "foo foo foo foo foo foo foo foo foo foo foo foo \"", - 262, - {{Item("foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo " - "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo " - "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo " - "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo " - "foo "), - {}}}}, - {"whitespace string", "\" \"", 5, {{Item(" "), {}}}}, - {"non-ascii string", "\"f\374\374\"", 5, absl::nullopt}, - {"tab in string", "\"\\t\"", 4, absl::nullopt}, - {"newline in string", "\" \\n \"", 6, absl::nullopt}, - {"single quoted string", "'foo'", 5, absl::nullopt}, - {"unbalanced string", "\"foo", 4, absl::nullopt}, - {"string quoting", - "\"foo \\\"bar\\\" \\\\ baz\"", - 20, - {{Item("foo \"bar\" \\ baz"), {}}}}, - {"bad string quoting", "\"foo \\,\"", 8, absl::nullopt}, - {"ending string quote", "\"foo \\\"", 7, absl::nullopt}, - {"abruptly ending string quote", "\"foo \\", 6, absl::nullopt}, - // token-generated.json - {"0x00 in token", "a\000a", 3, absl::nullopt}, - {"0x01 in token", "a\001a", 3, absl::nullopt}, - {"0x02 in token", "a\002a", 3, absl::nullopt}, - {"0x03 in token", "a\003a", 3, absl::nullopt}, - {"0x04 in token", "a\004a", 3, absl::nullopt}, - {"0x05 in token", "a\005a", 3, absl::nullopt}, - {"0x06 in token", "a\006a", 3, absl::nullopt}, - {"0x07 in token", "a\aa", 3, absl::nullopt}, - {"0x08 in token", "a\ba", 3, absl::nullopt}, - {"0x09 in token", "a\ta", 3, absl::nullopt}, - {"0x0a in token", "a\na", 3, absl::nullopt}, - {"0x0b in token", "a\va", 3, absl::nullopt}, - {"0x0c in token", "a\fa", 3, absl::nullopt}, - {"0x0d in token", "a\ra", 3, absl::nullopt}, - {"0x0e in token", "a\016a", 3, absl::nullopt}, - {"0x0f in token", "a\017a", 3, absl::nullopt}, - {"0x10 in token", "a\020a", 3, absl::nullopt}, - {"0x11 in token", "a\021a", 3, absl::nullopt}, - {"0x12 in token", "a\022a", 3, absl::nullopt}, - {"0x13 in token", "a\023a", 3, absl::nullopt}, - {"0x14 in token", "a\024a", 3, absl::nullopt}, - {"0x15 in token", "a\025a", 3, absl::nullopt}, - {"0x16 in token", "a\026a", 3, absl::nullopt}, - {"0x17 in token", "a\027a", 3, absl::nullopt}, - {"0x18 in token", "a\030a", 3, absl::nullopt}, - {"0x19 in token", "a\031a", 3, absl::nullopt}, - {"0x1a in token", "a\032a", 3, absl::nullopt}, - {"0x1b in token", "a\033a", 3, absl::nullopt}, - {"0x1c in token", "a\034a", 3, absl::nullopt}, - {"0x1d in token", "a\035a", 3, absl::nullopt}, - {"0x1e in token", "a\036a", 3, absl::nullopt}, - {"0x1f in token", "a\037a", 3, absl::nullopt}, - {"0x20 in token", "a a", 3, absl::nullopt}, - {"0x21 in token", "a!a", 3, {{Item("a!a", Item::kTokenType), {}}}}, - {"0x22 in token", "a\"a", 3, absl::nullopt}, - {"0x23 in token", "a#a", 3, {{Item("a#a", Item::kTokenType), {}}}}, - {"0x24 in token", "a$a", 3, {{Item("a$a", Item::kTokenType), {}}}}, - {"0x25 in token", "a%a", 3, {{Item("a%a", Item::kTokenType), {}}}}, - {"0x26 in token", "a&a", 3, {{Item("a&a", Item::kTokenType), {}}}}, - {"0x27 in token", "a'a", 3, {{Item("a'a", Item::kTokenType), {}}}}, - {"0x28 in token", "a(a", 3, absl::nullopt}, - {"0x29 in token", "a)a", 3, absl::nullopt}, - {"0x2a in token", "a*a", 3, {{Item("a*a", Item::kTokenType), {}}}}, - {"0x2b in token", "a+a", 3, {{Item("a+a", Item::kTokenType), {}}}}, - {"0x2c in token", "a,a", 3, absl::nullopt}, - {"0x2d in token", "a-a", 3, {{Item("a-a", Item::kTokenType), {}}}}, - {"0x2e in token", "a.a", 3, {{Item("a.a", Item::kTokenType), {}}}}, - {"0x2f in token", "a/a", 3, {{Item("a/a", Item::kTokenType), {}}}}, - {"0x30 in token", "a0a", 3, {{Item("a0a", Item::kTokenType), {}}}}, - {"0x31 in token", "a1a", 3, {{Item("a1a", Item::kTokenType), {}}}}, - {"0x32 in token", "a2a", 3, {{Item("a2a", Item::kTokenType), {}}}}, - {"0x33 in token", "a3a", 3, {{Item("a3a", Item::kTokenType), {}}}}, - {"0x34 in token", "a4a", 3, {{Item("a4a", Item::kTokenType), {}}}}, - {"0x35 in token", "a5a", 3, {{Item("a5a", Item::kTokenType), {}}}}, - {"0x36 in token", "a6a", 3, {{Item("a6a", Item::kTokenType), {}}}}, - {"0x37 in token", "a7a", 3, {{Item("a7a", Item::kTokenType), {}}}}, - {"0x38 in token", "a8a", 3, {{Item("a8a", Item::kTokenType), {}}}}, - {"0x39 in token", "a9a", 3, {{Item("a9a", Item::kTokenType), {}}}}, - {"0x3a in token", "a:a", 3, {{Item("a:a", Item::kTokenType), {}}}}, - {"0x3b in token", - "a;a", - 3, - {{Item("a", Item::kTokenType), {BooleanParam("a", true)}}}}, - {"0x3c in token", "a<a", 3, absl::nullopt}, - {"0x3d in token", "a=a", 3, absl::nullopt}, - {"0x3e in token", "a>a", 3, absl::nullopt}, - {"0x3f in token", "a?a", 3, absl::nullopt}, - {"0x40 in token", "a@a", 3, absl::nullopt}, - {"0x41 in token", "aAa", 3, {{Item("aAa", Item::kTokenType), {}}}}, - {"0x42 in token", "aBa", 3, {{Item("aBa", Item::kTokenType), {}}}}, - {"0x43 in token", "aCa", 3, {{Item("aCa", Item::kTokenType), {}}}}, - {"0x44 in token", "aDa", 3, {{Item("aDa", Item::kTokenType), {}}}}, - {"0x45 in token", "aEa", 3, {{Item("aEa", Item::kTokenType), {}}}}, - {"0x46 in token", "aFa", 3, {{Item("aFa", Item::kTokenType), {}}}}, - {"0x47 in token", "aGa", 3, {{Item("aGa", Item::kTokenType), {}}}}, - {"0x48 in token", "aHa", 3, {{Item("aHa", Item::kTokenType), {}}}}, - {"0x49 in token", "aIa", 3, {{Item("aIa", Item::kTokenType), {}}}}, - {"0x4a in token", "aJa", 3, {{Item("aJa", Item::kTokenType), {}}}}, - {"0x4b in token", "aKa", 3, {{Item("aKa", Item::kTokenType), {}}}}, - {"0x4c in token", "aLa", 3, {{Item("aLa", Item::kTokenType), {}}}}, - {"0x4d in token", "aMa", 3, {{Item("aMa", Item::kTokenType), {}}}}, - {"0x4e in token", "aNa", 3, {{Item("aNa", Item::kTokenType), {}}}}, - {"0x4f in token", "aOa", 3, {{Item("aOa", Item::kTokenType), {}}}}, - {"0x50 in token", "aPa", 3, {{Item("aPa", Item::kTokenType), {}}}}, - {"0x51 in token", "aQa", 3, {{Item("aQa", Item::kTokenType), {}}}}, - {"0x52 in token", "aRa", 3, {{Item("aRa", Item::kTokenType), {}}}}, - {"0x53 in token", "aSa", 3, {{Item("aSa", Item::kTokenType), {}}}}, - {"0x54 in token", "aTa", 3, {{Item("aTa", Item::kTokenType), {}}}}, - {"0x55 in token", "aUa", 3, {{Item("aUa", Item::kTokenType), {}}}}, - {"0x56 in token", "aVa", 3, {{Item("aVa", Item::kTokenType), {}}}}, - {"0x57 in token", "aWa", 3, {{Item("aWa", Item::kTokenType), {}}}}, - {"0x58 in token", "aXa", 3, {{Item("aXa", Item::kTokenType), {}}}}, - {"0x59 in token", "aYa", 3, {{Item("aYa", Item::kTokenType), {}}}}, - {"0x5a in token", "aZa", 3, {{Item("aZa", Item::kTokenType), {}}}}, - {"0x5b in token", "a[a", 3, absl::nullopt}, - {"0x5c in token", "a\\a", 3, absl::nullopt}, - {"0x5d in token", "a]a", 3, absl::nullopt}, - {"0x5e in token", "a^a", 3, {{Item("a^a", Item::kTokenType), {}}}}, - {"0x5f in token", "a_a", 3, {{Item("a_a", Item::kTokenType), {}}}}, - {"0x60 in token", "a`a", 3, {{Item("a`a", Item::kTokenType), {}}}}, - {"0x61 in token", "aaa", 3, {{Item("aaa", Item::kTokenType), {}}}}, - {"0x62 in token", "aba", 3, {{Item("aba", Item::kTokenType), {}}}}, - {"0x63 in token", "aca", 3, {{Item("aca", Item::kTokenType), {}}}}, - {"0x64 in token", "ada", 3, {{Item("ada", Item::kTokenType), {}}}}, - {"0x65 in token", "aea", 3, {{Item("aea", Item::kTokenType), {}}}}, - {"0x66 in token", "afa", 3, {{Item("afa", Item::kTokenType), {}}}}, - {"0x67 in token", "aga", 3, {{Item("aga", Item::kTokenType), {}}}}, - {"0x68 in token", "aha", 3, {{Item("aha", Item::kTokenType), {}}}}, - {"0x69 in token", "aia", 3, {{Item("aia", Item::kTokenType), {}}}}, - {"0x6a in token", "aja", 3, {{Item("aja", Item::kTokenType), {}}}}, - {"0x6b in token", "aka", 3, {{Item("aka", Item::kTokenType), {}}}}, - {"0x6c in token", "ala", 3, {{Item("ala", Item::kTokenType), {}}}}, - {"0x6d in token", "ama", 3, {{Item("ama", Item::kTokenType), {}}}}, - {"0x6e in token", "ana", 3, {{Item("ana", Item::kTokenType), {}}}}, - {"0x6f in token", "aoa", 3, {{Item("aoa", Item::kTokenType), {}}}}, - {"0x70 in token", "apa", 3, {{Item("apa", Item::kTokenType), {}}}}, - {"0x71 in token", "aqa", 3, {{Item("aqa", Item::kTokenType), {}}}}, - {"0x72 in token", "ara", 3, {{Item("ara", Item::kTokenType), {}}}}, - {"0x73 in token", "asa", 3, {{Item("asa", Item::kTokenType), {}}}}, - {"0x74 in token", "ata", 3, {{Item("ata", Item::kTokenType), {}}}}, - {"0x75 in token", "aua", 3, {{Item("aua", Item::kTokenType), {}}}}, - {"0x76 in token", "ava", 3, {{Item("ava", Item::kTokenType), {}}}}, - {"0x77 in token", "awa", 3, {{Item("awa", Item::kTokenType), {}}}}, - {"0x78 in token", "axa", 3, {{Item("axa", Item::kTokenType), {}}}}, - {"0x79 in token", "aya", 3, {{Item("aya", Item::kTokenType), {}}}}, - {"0x7a in token", "aza", 3, {{Item("aza", Item::kTokenType), {}}}}, - {"0x7b in token", "a{a", 3, absl::nullopt}, - {"0x7c in token", "a|a", 3, {{Item("a|a", Item::kTokenType), {}}}}, - {"0x7d in token", "a}a", 3, absl::nullopt}, - {"0x7e in token", "a~a", 3, {{Item("a~a", Item::kTokenType), {}}}}, - {"0x7f in token", "a\177a", 3, absl::nullopt}, - {"0x00 starting an token", "\000a", 2, absl::nullopt}, - {"0x01 starting an token", "\001a", 2, absl::nullopt}, - {"0x02 starting an token", "\002a", 2, absl::nullopt}, - {"0x03 starting an token", "\003a", 2, absl::nullopt}, - {"0x04 starting an token", "\004a", 2, absl::nullopt}, - {"0x05 starting an token", "\005a", 2, absl::nullopt}, - {"0x06 starting an token", "\006a", 2, absl::nullopt}, - {"0x07 starting an token", "\aa", 2, absl::nullopt}, - {"0x08 starting an token", "\ba", 2, absl::nullopt}, - {"0x09 starting an token", "\ta", 2, absl::nullopt}, - {"0x0a starting an token", "\na", 2, absl::nullopt}, - {"0x0b starting an token", "\va", 2, absl::nullopt}, - {"0x0c starting an token", "\fa", 2, absl::nullopt}, - {"0x0d starting an token", "\ra", 2, absl::nullopt}, - {"0x0e starting an token", "\016a", 2, absl::nullopt}, - {"0x0f starting an token", "\017a", 2, absl::nullopt}, - {"0x10 starting an token", "\020a", 2, absl::nullopt}, - {"0x11 starting an token", "\021a", 2, absl::nullopt}, - {"0x12 starting an token", "\022a", 2, absl::nullopt}, - {"0x13 starting an token", "\023a", 2, absl::nullopt}, - {"0x14 starting an token", "\024a", 2, absl::nullopt}, - {"0x15 starting an token", "\025a", 2, absl::nullopt}, - {"0x16 starting an token", "\026a", 2, absl::nullopt}, - {"0x17 starting an token", "\027a", 2, absl::nullopt}, - {"0x18 starting an token", "\030a", 2, absl::nullopt}, - {"0x19 starting an token", "\031a", 2, absl::nullopt}, - {"0x1a starting an token", "\032a", 2, absl::nullopt}, - {"0x1b starting an token", "\033a", 2, absl::nullopt}, - {"0x1c starting an token", "\034a", 2, absl::nullopt}, - {"0x1d starting an token", "\035a", 2, absl::nullopt}, - {"0x1e starting an token", "\036a", 2, absl::nullopt}, - {"0x1f starting an token", "\037a", 2, absl::nullopt}, - {"0x20 starting an token", - " a", - 2, - {{Item("a", Item::kTokenType), {}}}, - "a"}, - {"0x21 starting an token", "!a", 2, absl::nullopt}, - {"0x22 starting an token", "\"a", 2, absl::nullopt}, - {"0x23 starting an token", "#a", 2, absl::nullopt}, - {"0x24 starting an token", "$a", 2, absl::nullopt}, - {"0x25 starting an token", "%a", 2, absl::nullopt}, - {"0x26 starting an token", "&a", 2, absl::nullopt}, - {"0x27 starting an token", "'a", 2, absl::nullopt}, - {"0x28 starting an token", "(a", 2, absl::nullopt}, - {"0x29 starting an token", ")a", 2, absl::nullopt}, - {"0x2a starting an token", "*a", 2, {{Item("*a", Item::kTokenType), {}}}}, - {"0x2b starting an token", "+a", 2, absl::nullopt}, - {"0x2c starting an token", ",a", 2, absl::nullopt}, - {"0x2d starting an token", "-a", 2, absl::nullopt}, - {"0x2e starting an token", ".a", 2, absl::nullopt}, - {"0x2f starting an token", "/a", 2, absl::nullopt}, - {"0x30 starting an token", "0a", 2, absl::nullopt}, - {"0x31 starting an token", "1a", 2, absl::nullopt}, - {"0x32 starting an token", "2a", 2, absl::nullopt}, - {"0x33 starting an token", "3a", 2, absl::nullopt}, - {"0x34 starting an token", "4a", 2, absl::nullopt}, - {"0x35 starting an token", "5a", 2, absl::nullopt}, - {"0x36 starting an token", "6a", 2, absl::nullopt}, - {"0x37 starting an token", "7a", 2, absl::nullopt}, - {"0x38 starting an token", "8a", 2, absl::nullopt}, - {"0x39 starting an token", "9a", 2, absl::nullopt}, - {"0x3a starting an token", ":a", 2, absl::nullopt}, - {"0x3b starting an token", ";a", 2, absl::nullopt}, - {"0x3c starting an token", "<a", 2, absl::nullopt}, - {"0x3d starting an token", "=a", 2, absl::nullopt}, - {"0x3e starting an token", ">a", 2, absl::nullopt}, - {"0x3f starting an token", "?a", 2, absl::nullopt}, - {"0x40 starting an token", "@a", 2, absl::nullopt}, - {"0x41 starting an token", "Aa", 2, {{Item("Aa", Item::kTokenType), {}}}}, - {"0x42 starting an token", "Ba", 2, {{Item("Ba", Item::kTokenType), {}}}}, - {"0x43 starting an token", "Ca", 2, {{Item("Ca", Item::kTokenType), {}}}}, - {"0x44 starting an token", "Da", 2, {{Item("Da", Item::kTokenType), {}}}}, - {"0x45 starting an token", "Ea", 2, {{Item("Ea", Item::kTokenType), {}}}}, - {"0x46 starting an token", "Fa", 2, {{Item("Fa", Item::kTokenType), {}}}}, - {"0x47 starting an token", "Ga", 2, {{Item("Ga", Item::kTokenType), {}}}}, - {"0x48 starting an token", "Ha", 2, {{Item("Ha", Item::kTokenType), {}}}}, - {"0x49 starting an token", "Ia", 2, {{Item("Ia", Item::kTokenType), {}}}}, - {"0x4a starting an token", "Ja", 2, {{Item("Ja", Item::kTokenType), {}}}}, - {"0x4b starting an token", "Ka", 2, {{Item("Ka", Item::kTokenType), {}}}}, - {"0x4c starting an token", "La", 2, {{Item("La", Item::kTokenType), {}}}}, - {"0x4d starting an token", "Ma", 2, {{Item("Ma", Item::kTokenType), {}}}}, - {"0x4e starting an token", "Na", 2, {{Item("Na", Item::kTokenType), {}}}}, - {"0x4f starting an token", "Oa", 2, {{Item("Oa", Item::kTokenType), {}}}}, - {"0x50 starting an token", "Pa", 2, {{Item("Pa", Item::kTokenType), {}}}}, - {"0x51 starting an token", "Qa", 2, {{Item("Qa", Item::kTokenType), {}}}}, - {"0x52 starting an token", "Ra", 2, {{Item("Ra", Item::kTokenType), {}}}}, - {"0x53 starting an token", "Sa", 2, {{Item("Sa", Item::kTokenType), {}}}}, - {"0x54 starting an token", "Ta", 2, {{Item("Ta", Item::kTokenType), {}}}}, - {"0x55 starting an token", "Ua", 2, {{Item("Ua", Item::kTokenType), {}}}}, - {"0x56 starting an token", "Va", 2, {{Item("Va", Item::kTokenType), {}}}}, - {"0x57 starting an token", "Wa", 2, {{Item("Wa", Item::kTokenType), {}}}}, - {"0x58 starting an token", "Xa", 2, {{Item("Xa", Item::kTokenType), {}}}}, - {"0x59 starting an token", "Ya", 2, {{Item("Ya", Item::kTokenType), {}}}}, - {"0x5a starting an token", "Za", 2, {{Item("Za", Item::kTokenType), {}}}}, - {"0x5b starting an token", "[a", 2, absl::nullopt}, - {"0x5c starting an token", "\\a", 2, absl::nullopt}, - {"0x5d starting an token", "]a", 2, absl::nullopt}, - {"0x5e starting an token", "^a", 2, absl::nullopt}, - {"0x5f starting an token", "_a", 2, absl::nullopt}, - {"0x60 starting an token", "`a", 2, absl::nullopt}, - {"0x61 starting an token", "aa", 2, {{Item("aa", Item::kTokenType), {}}}}, - {"0x62 starting an token", "ba", 2, {{Item("ba", Item::kTokenType), {}}}}, - {"0x63 starting an token", "ca", 2, {{Item("ca", Item::kTokenType), {}}}}, - {"0x64 starting an token", "da", 2, {{Item("da", Item::kTokenType), {}}}}, - {"0x65 starting an token", "ea", 2, {{Item("ea", Item::kTokenType), {}}}}, - {"0x66 starting an token", "fa", 2, {{Item("fa", Item::kTokenType), {}}}}, - {"0x67 starting an token", "ga", 2, {{Item("ga", Item::kTokenType), {}}}}, - {"0x68 starting an token", "ha", 2, {{Item("ha", Item::kTokenType), {}}}}, - {"0x69 starting an token", "ia", 2, {{Item("ia", Item::kTokenType), {}}}}, - {"0x6a starting an token", "ja", 2, {{Item("ja", Item::kTokenType), {}}}}, - {"0x6b starting an token", "ka", 2, {{Item("ka", Item::kTokenType), {}}}}, - {"0x6c starting an token", "la", 2, {{Item("la", Item::kTokenType), {}}}}, - {"0x6d starting an token", "ma", 2, {{Item("ma", Item::kTokenType), {}}}}, - {"0x6e starting an token", "na", 2, {{Item("na", Item::kTokenType), {}}}}, - {"0x6f starting an token", "oa", 2, {{Item("oa", Item::kTokenType), {}}}}, - {"0x70 starting an token", "pa", 2, {{Item("pa", Item::kTokenType), {}}}}, - {"0x71 starting an token", "qa", 2, {{Item("qa", Item::kTokenType), {}}}}, - {"0x72 starting an token", "ra", 2, {{Item("ra", Item::kTokenType), {}}}}, - {"0x73 starting an token", "sa", 2, {{Item("sa", Item::kTokenType), {}}}}, - {"0x74 starting an token", "ta", 2, {{Item("ta", Item::kTokenType), {}}}}, - {"0x75 starting an token", "ua", 2, {{Item("ua", Item::kTokenType), {}}}}, - {"0x76 starting an token", "va", 2, {{Item("va", Item::kTokenType), {}}}}, - {"0x77 starting an token", "wa", 2, {{Item("wa", Item::kTokenType), {}}}}, - {"0x78 starting an token", "xa", 2, {{Item("xa", Item::kTokenType), {}}}}, - {"0x79 starting an token", "ya", 2, {{Item("ya", Item::kTokenType), {}}}}, - {"0x7a starting an token", "za", 2, {{Item("za", Item::kTokenType), {}}}}, - {"0x7b starting an token", "{a", 2, absl::nullopt}, - {"0x7c starting an token", "|a", 2, absl::nullopt}, - {"0x7d starting an token", "}a", 2, absl::nullopt}, - {"0x7e starting an token", "~a", 2, absl::nullopt}, - {"0x7f starting an token", "\177a", 2, absl::nullopt}, - // token.json - {"basic token - item", - "a_b-c.d3:f%00/*", - 15, - {{Item("a_b-c.d3:f%00/*", Item::kTokenType), {}}}}, - {"token with capitals - item", - "fooBar", - 6, - {{Item("fooBar", Item::kTokenType), {}}}}, - {"token starting with capitals - item", - "FooBar", - 6, - {{Item("FooBar", Item::kTokenType), {}}}}, -}; - -const struct ListTestCase { - const char* name; - const char* raw; - size_t raw_len; - const absl::optional<List> expected; // nullopt if parse error is expected. - const char* canonical; // nullptr if parse error is expected, or if canonical - // format is identical to raw. -} list_test_cases[] = { - - // examples.json - {"Example-StrListHeader", - "\"foo\", \"bar\", \"It was the best of times.\"", - 41, - {{{Item("foo"), {}}, - {Item("bar"), {}}, - {Item("It was the best of times."), {}}}}}, - {"Example-Hdr (list on one line)", - "foo, bar", - 8, - {{{Item("foo", Item::kTokenType), {}}, - {Item("bar", Item::kTokenType), {}}}}}, - {"Example-Hdr (list on two lines)", - "foo, bar", - 8, - {{{Item("foo", Item::kTokenType), {}}, - {Item("bar", Item::kTokenType), {}}}}, - "foo, bar"}, - {"Example-StrListListHeader", - "(\"foo\" \"bar\"), (\"baz\"), (\"bat\" \"one\"), ()", - 41, - {{{{{Item("foo"), {}}, {Item("bar"), {}}}, {}}, - {{{Item("baz"), {}}}, {}}, - {{{Item("bat"), {}}, {Item("one"), {}}}, {}}, - {std::vector<ParameterizedItem>(), {}}}}}, - {"Example-ListListParam", - "(\"foo\"; a=1;b=2);lvl=5, (\"bar\" \"baz\");lvl=1", - 43, - {{{{{Item("foo"), {Param("a", 1), Param("b", 2)}}}, {Param("lvl", 5)}}, - {{{Item("bar"), {}}, {Item("baz"), {}}}, {Param("lvl", 1)}}}}, - "(\"foo\";a=1;b=2);lvl=5, (\"bar\" \"baz\");lvl=1"}, - {"Example-ParamListHeader", - "abc;a=1;b=2; cde_456, (ghi;jk=4 l);q=\"9\";r=w", - 44, - {{{Item("abc", Item::kTokenType), - {Param("a", 1), Param("b", 2), BooleanParam("cde_456", true)}}, - {{{Item("ghi", Item::kTokenType), {Param("jk", 4)}}, - {Item("l", Item::kTokenType), {}}}, - {Param("q", "9"), TokenParam("r", "w")}}}}, - "abc;a=1;b=2;cde_456, (ghi;jk=4 l);q=\"9\";r=w"}, - // key-generated.json - {"0x00 in parameterised list key", "foo; a\000a=1", 10, absl::nullopt}, - {"0x01 in parameterised list key", "foo; a\001a=1", 10, absl::nullopt}, - {"0x02 in parameterised list key", "foo; a\002a=1", 10, absl::nullopt}, - {"0x03 in parameterised list key", "foo; a\003a=1", 10, absl::nullopt}, - {"0x04 in parameterised list key", "foo; a\004a=1", 10, absl::nullopt}, - {"0x05 in parameterised list key", "foo; a\005a=1", 10, absl::nullopt}, - {"0x06 in parameterised list key", "foo; a\006a=1", 10, absl::nullopt}, - {"0x07 in parameterised list key", "foo; a\aa=1", 10, absl::nullopt}, - {"0x08 in parameterised list key", "foo; a\ba=1", 10, absl::nullopt}, - {"0x09 in parameterised list key", "foo; a\ta=1", 10, absl::nullopt}, - {"0x0a in parameterised list key", "foo; a\na=1", 10, absl::nullopt}, - {"0x0b in parameterised list key", "foo; a\va=1", 10, absl::nullopt}, - {"0x0c in parameterised list key", "foo; a\fa=1", 10, absl::nullopt}, - {"0x0d in parameterised list key", "foo; a\ra=1", 10, absl::nullopt}, - {"0x0e in parameterised list key", "foo; a\016a=1", 10, absl::nullopt}, - {"0x0f in parameterised list key", "foo; a\017a=1", 10, absl::nullopt}, - {"0x10 in parameterised list key", "foo; a\020a=1", 10, absl::nullopt}, - {"0x11 in parameterised list key", "foo; a\021a=1", 10, absl::nullopt}, - {"0x12 in parameterised list key", "foo; a\022a=1", 10, absl::nullopt}, - {"0x13 in parameterised list key", "foo; a\023a=1", 10, absl::nullopt}, - {"0x14 in parameterised list key", "foo; a\024a=1", 10, absl::nullopt}, - {"0x15 in parameterised list key", "foo; a\025a=1", 10, absl::nullopt}, - {"0x16 in parameterised list key", "foo; a\026a=1", 10, absl::nullopt}, - {"0x17 in parameterised list key", "foo; a\027a=1", 10, absl::nullopt}, - {"0x18 in parameterised list key", "foo; a\030a=1", 10, absl::nullopt}, - {"0x19 in parameterised list key", "foo; a\031a=1", 10, absl::nullopt}, - {"0x1a in parameterised list key", "foo; a\032a=1", 10, absl::nullopt}, - {"0x1b in parameterised list key", "foo; a\033a=1", 10, absl::nullopt}, - {"0x1c in parameterised list key", "foo; a\034a=1", 10, absl::nullopt}, - {"0x1d in parameterised list key", "foo; a\035a=1", 10, absl::nullopt}, - {"0x1e in parameterised list key", "foo; a\036a=1", 10, absl::nullopt}, - {"0x1f in parameterised list key", "foo; a\037a=1", 10, absl::nullopt}, - {"0x20 in parameterised list key", "foo; a a=1", 10, absl::nullopt}, - {"0x21 in parameterised list key", "foo; a!a=1", 10, absl::nullopt}, - {"0x22 in parameterised list key", "foo; a\"a=1", 10, absl::nullopt}, - {"0x23 in parameterised list key", "foo; a#a=1", 10, absl::nullopt}, - {"0x24 in parameterised list key", "foo; a$a=1", 10, absl::nullopt}, - {"0x25 in parameterised list key", "foo; a%a=1", 10, absl::nullopt}, - {"0x26 in parameterised list key", "foo; a&a=1", 10, absl::nullopt}, - {"0x27 in parameterised list key", "foo; a'a=1", 10, absl::nullopt}, - {"0x28 in parameterised list key", "foo; a(a=1", 10, absl::nullopt}, - {"0x29 in parameterised list key", "foo; a)a=1", 10, absl::nullopt}, - {"0x2a in parameterised list key", - "foo; a*a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a*a", 1)}}}}, - "foo;a*a=1"}, - {"0x2b in parameterised list key", "foo; a+a=1", 10, absl::nullopt}, - {"0x2c in parameterised list key", "foo; a,a=1", 10, absl::nullopt}, - {"0x2d in parameterised list key", - "foo; a-a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a-a", 1)}}}}, - "foo;a-a=1"}, - {"0x2e in parameterised list key", - "foo; a.a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a.a", 1)}}}}, - "foo;a.a=1"}, - {"0x2f in parameterised list key", "foo; a/a=1", 10, absl::nullopt}, - {"0x30 in parameterised list key", - "foo; a0a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a0a", 1)}}}}, - "foo;a0a=1"}, - {"0x31 in parameterised list key", - "foo; a1a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a1a", 1)}}}}, - "foo;a1a=1"}, - {"0x32 in parameterised list key", - "foo; a2a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a2a", 1)}}}}, - "foo;a2a=1"}, - {"0x33 in parameterised list key", - "foo; a3a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a3a", 1)}}}}, - "foo;a3a=1"}, - {"0x34 in parameterised list key", - "foo; a4a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a4a", 1)}}}}, - "foo;a4a=1"}, - {"0x35 in parameterised list key", - "foo; a5a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a5a", 1)}}}}, - "foo;a5a=1"}, - {"0x36 in parameterised list key", - "foo; a6a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a6a", 1)}}}}, - "foo;a6a=1"}, - {"0x37 in parameterised list key", - "foo; a7a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a7a", 1)}}}}, - "foo;a7a=1"}, - {"0x38 in parameterised list key", - "foo; a8a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a8a", 1)}}}}, - "foo;a8a=1"}, - {"0x39 in parameterised list key", - "foo; a9a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a9a", 1)}}}}, - "foo;a9a=1"}, - {"0x3a in parameterised list key", "foo; a:a=1", 10, absl::nullopt}, - {"0x3b in parameterised list key", - "foo; a;a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a", 1)}}}}, - "foo;a=1"}, - {"0x3c in parameterised list key", "foo; a<a=1", 10, absl::nullopt}, - {"0x3d in parameterised list key", "foo; a=a=1", 10, absl::nullopt}, - {"0x3e in parameterised list key", "foo; a>a=1", 10, absl::nullopt}, - {"0x3f in parameterised list key", "foo; a?a=1", 10, absl::nullopt}, - {"0x40 in parameterised list key", "foo; a@a=1", 10, absl::nullopt}, - {"0x41 in parameterised list key", "foo; aAa=1", 10, absl::nullopt}, - {"0x42 in parameterised list key", "foo; aBa=1", 10, absl::nullopt}, - {"0x43 in parameterised list key", "foo; aCa=1", 10, absl::nullopt}, - {"0x44 in parameterised list key", "foo; aDa=1", 10, absl::nullopt}, - {"0x45 in parameterised list key", "foo; aEa=1", 10, absl::nullopt}, - {"0x46 in parameterised list key", "foo; aFa=1", 10, absl::nullopt}, - {"0x47 in parameterised list key", "foo; aGa=1", 10, absl::nullopt}, - {"0x48 in parameterised list key", "foo; aHa=1", 10, absl::nullopt}, - {"0x49 in parameterised list key", "foo; aIa=1", 10, absl::nullopt}, - {"0x4a in parameterised list key", "foo; aJa=1", 10, absl::nullopt}, - {"0x4b in parameterised list key", "foo; aKa=1", 10, absl::nullopt}, - {"0x4c in parameterised list key", "foo; aLa=1", 10, absl::nullopt}, - {"0x4d in parameterised list key", "foo; aMa=1", 10, absl::nullopt}, - {"0x4e in parameterised list key", "foo; aNa=1", 10, absl::nullopt}, - {"0x4f in parameterised list key", "foo; aOa=1", 10, absl::nullopt}, - {"0x50 in parameterised list key", "foo; aPa=1", 10, absl::nullopt}, - {"0x51 in parameterised list key", "foo; aQa=1", 10, absl::nullopt}, - {"0x52 in parameterised list key", "foo; aRa=1", 10, absl::nullopt}, - {"0x53 in parameterised list key", "foo; aSa=1", 10, absl::nullopt}, - {"0x54 in parameterised list key", "foo; aTa=1", 10, absl::nullopt}, - {"0x55 in parameterised list key", "foo; aUa=1", 10, absl::nullopt}, - {"0x56 in parameterised list key", "foo; aVa=1", 10, absl::nullopt}, - {"0x57 in parameterised list key", "foo; aWa=1", 10, absl::nullopt}, - {"0x58 in parameterised list key", "foo; aXa=1", 10, absl::nullopt}, - {"0x59 in parameterised list key", "foo; aYa=1", 10, absl::nullopt}, - {"0x5a in parameterised list key", "foo; aZa=1", 10, absl::nullopt}, - {"0x5b in parameterised list key", "foo; a[a=1", 10, absl::nullopt}, - {"0x5c in parameterised list key", "foo; a\\a=1", 10, absl::nullopt}, - {"0x5d in parameterised list key", "foo; a]a=1", 10, absl::nullopt}, - {"0x5e in parameterised list key", "foo; a^a=1", 10, absl::nullopt}, - {"0x5f in parameterised list key", - "foo; a_a=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("a_a", 1)}}}}, - "foo;a_a=1"}, - {"0x60 in parameterised list key", "foo; a`a=1", 10, absl::nullopt}, - {"0x61 in parameterised list key", - "foo; aaa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aaa", 1)}}}}, - "foo;aaa=1"}, - {"0x62 in parameterised list key", - "foo; aba=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aba", 1)}}}}, - "foo;aba=1"}, - {"0x63 in parameterised list key", - "foo; aca=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aca", 1)}}}}, - "foo;aca=1"}, - {"0x64 in parameterised list key", - "foo; ada=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("ada", 1)}}}}, - "foo;ada=1"}, - {"0x65 in parameterised list key", - "foo; aea=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aea", 1)}}}}, - "foo;aea=1"}, - {"0x66 in parameterised list key", - "foo; afa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("afa", 1)}}}}, - "foo;afa=1"}, - {"0x67 in parameterised list key", - "foo; aga=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aga", 1)}}}}, - "foo;aga=1"}, - {"0x68 in parameterised list key", - "foo; aha=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aha", 1)}}}}, - "foo;aha=1"}, - {"0x69 in parameterised list key", - "foo; aia=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aia", 1)}}}}, - "foo;aia=1"}, - {"0x6a in parameterised list key", - "foo; aja=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aja", 1)}}}}, - "foo;aja=1"}, - {"0x6b in parameterised list key", - "foo; aka=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aka", 1)}}}}, - "foo;aka=1"}, - {"0x6c in parameterised list key", - "foo; ala=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("ala", 1)}}}}, - "foo;ala=1"}, - {"0x6d in parameterised list key", - "foo; ama=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("ama", 1)}}}}, - "foo;ama=1"}, - {"0x6e in parameterised list key", - "foo; ana=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("ana", 1)}}}}, - "foo;ana=1"}, - {"0x6f in parameterised list key", - "foo; aoa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aoa", 1)}}}}, - "foo;aoa=1"}, - {"0x70 in parameterised list key", - "foo; apa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("apa", 1)}}}}, - "foo;apa=1"}, - {"0x71 in parameterised list key", - "foo; aqa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aqa", 1)}}}}, - "foo;aqa=1"}, - {"0x72 in parameterised list key", - "foo; ara=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("ara", 1)}}}}, - "foo;ara=1"}, - {"0x73 in parameterised list key", - "foo; asa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("asa", 1)}}}}, - "foo;asa=1"}, - {"0x74 in parameterised list key", - "foo; ata=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("ata", 1)}}}}, - "foo;ata=1"}, - {"0x75 in parameterised list key", - "foo; aua=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aua", 1)}}}}, - "foo;aua=1"}, - {"0x76 in parameterised list key", - "foo; ava=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("ava", 1)}}}}, - "foo;ava=1"}, - {"0x77 in parameterised list key", - "foo; awa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("awa", 1)}}}}, - "foo;awa=1"}, - {"0x78 in parameterised list key", - "foo; axa=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("axa", 1)}}}}, - "foo;axa=1"}, - {"0x79 in parameterised list key", - "foo; aya=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aya", 1)}}}}, - "foo;aya=1"}, - {"0x7a in parameterised list key", - "foo; aza=1", - 10, - {{{Item("foo", Item::kTokenType), {Param("aza", 1)}}}}, - "foo;aza=1"}, - {"0x7b in parameterised list key", "foo; a{a=1", 10, absl::nullopt}, - {"0x7c in parameterised list key", "foo; a|a=1", 10, absl::nullopt}, - {"0x7d in parameterised list key", "foo; a}a=1", 10, absl::nullopt}, - {"0x7e in parameterised list key", "foo; a~a=1", 10, absl::nullopt}, - {"0x7f in parameterised list key", "foo; a\177a=1", 10, absl::nullopt}, - {"0x00 starting a parameterised list key", "foo; \000a=1", 9, - absl::nullopt}, - {"0x01 starting a parameterised list key", "foo; \001a=1", 9, - absl::nullopt}, - {"0x02 starting a parameterised list key", "foo; \002a=1", 9, - absl::nullopt}, - {"0x03 starting a parameterised list key", "foo; \003a=1", 9, - absl::nullopt}, - {"0x04 starting a parameterised list key", "foo; \004a=1", 9, - absl::nullopt}, - {"0x05 starting a parameterised list key", "foo; \005a=1", 9, - absl::nullopt}, - {"0x06 starting a parameterised list key", "foo; \006a=1", 9, - absl::nullopt}, - {"0x07 starting a parameterised list key", "foo; \aa=1", 9, absl::nullopt}, - {"0x08 starting a parameterised list key", "foo; \ba=1", 9, absl::nullopt}, - {"0x09 starting a parameterised list key", "foo; \ta=1", 9, absl::nullopt}, - {"0x0a starting a parameterised list key", "foo; \na=1", 9, absl::nullopt}, - {"0x0b starting a parameterised list key", "foo; \va=1", 9, absl::nullopt}, - {"0x0c starting a parameterised list key", "foo; \fa=1", 9, absl::nullopt}, - {"0x0d starting a parameterised list key", "foo; \ra=1", 9, absl::nullopt}, - {"0x0e starting a parameterised list key", "foo; \016a=1", 9, - absl::nullopt}, - {"0x0f starting a parameterised list key", "foo; \017a=1", 9, - absl::nullopt}, - {"0x10 starting a parameterised list key", "foo; \020a=1", 9, - absl::nullopt}, - {"0x11 starting a parameterised list key", "foo; \021a=1", 9, - absl::nullopt}, - {"0x12 starting a parameterised list key", "foo; \022a=1", 9, - absl::nullopt}, - {"0x13 starting a parameterised list key", "foo; \023a=1", 9, - absl::nullopt}, - {"0x14 starting a parameterised list key", "foo; \024a=1", 9, - absl::nullopt}, - {"0x15 starting a parameterised list key", "foo; \025a=1", 9, - absl::nullopt}, - {"0x16 starting a parameterised list key", "foo; \026a=1", 9, - absl::nullopt}, - {"0x17 starting a parameterised list key", "foo; \027a=1", 9, - absl::nullopt}, - {"0x18 starting a parameterised list key", "foo; \030a=1", 9, - absl::nullopt}, - {"0x19 starting a parameterised list key", "foo; \031a=1", 9, - absl::nullopt}, - {"0x1a starting a parameterised list key", "foo; \032a=1", 9, - absl::nullopt}, - {"0x1b starting a parameterised list key", "foo; \033a=1", 9, - absl::nullopt}, - {"0x1c starting a parameterised list key", "foo; \034a=1", 9, - absl::nullopt}, - {"0x1d starting a parameterised list key", "foo; \035a=1", 9, - absl::nullopt}, - {"0x1e starting a parameterised list key", "foo; \036a=1", 9, - absl::nullopt}, - {"0x1f starting a parameterised list key", "foo; \037a=1", 9, - absl::nullopt}, - {"0x20 starting a parameterised list key", - "foo; a=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("a", 1)}}}}, - "foo;a=1"}, - {"0x21 starting a parameterised list key", "foo; !a=1", 9, absl::nullopt}, - {"0x22 starting a parameterised list key", "foo; \"a=1", 9, absl::nullopt}, - {"0x23 starting a parameterised list key", "foo; #a=1", 9, absl::nullopt}, - {"0x24 starting a parameterised list key", "foo; $a=1", 9, absl::nullopt}, - {"0x25 starting a parameterised list key", "foo; %a=1", 9, absl::nullopt}, - {"0x26 starting a parameterised list key", "foo; &a=1", 9, absl::nullopt}, - {"0x27 starting a parameterised list key", "foo; 'a=1", 9, absl::nullopt}, - {"0x28 starting a parameterised list key", "foo; (a=1", 9, absl::nullopt}, - {"0x29 starting a parameterised list key", "foo; )a=1", 9, absl::nullopt}, - {"0x2a starting a parameterised list key", - "foo; *a=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("*a", 1)}}}}, - "foo;*a=1"}, - {"0x2b starting a parameterised list key", "foo; +a=1", 9, absl::nullopt}, - {"0x2c starting a parameterised list key", "foo; ,a=1", 9, absl::nullopt}, - {"0x2d starting a parameterised list key", "foo; -a=1", 9, absl::nullopt}, - {"0x2e starting a parameterised list key", "foo; .a=1", 9, absl::nullopt}, - {"0x2f starting a parameterised list key", "foo; /a=1", 9, absl::nullopt}, - {"0x30 starting a parameterised list key", "foo; 0a=1", 9, absl::nullopt}, - {"0x31 starting a parameterised list key", "foo; 1a=1", 9, absl::nullopt}, - {"0x32 starting a parameterised list key", "foo; 2a=1", 9, absl::nullopt}, - {"0x33 starting a parameterised list key", "foo; 3a=1", 9, absl::nullopt}, - {"0x34 starting a parameterised list key", "foo; 4a=1", 9, absl::nullopt}, - {"0x35 starting a parameterised list key", "foo; 5a=1", 9, absl::nullopt}, - {"0x36 starting a parameterised list key", "foo; 6a=1", 9, absl::nullopt}, - {"0x37 starting a parameterised list key", "foo; 7a=1", 9, absl::nullopt}, - {"0x38 starting a parameterised list key", "foo; 8a=1", 9, absl::nullopt}, - {"0x39 starting a parameterised list key", "foo; 9a=1", 9, absl::nullopt}, - {"0x3a starting a parameterised list key", "foo; :a=1", 9, absl::nullopt}, - {"0x3b starting a parameterised list key", "foo; ;a=1", 9, absl::nullopt}, - {"0x3c starting a parameterised list key", "foo; <a=1", 9, absl::nullopt}, - {"0x3d starting a parameterised list key", "foo; =a=1", 9, absl::nullopt}, - {"0x3e starting a parameterised list key", "foo; >a=1", 9, absl::nullopt}, - {"0x3f starting a parameterised list key", "foo; ?a=1", 9, absl::nullopt}, - {"0x40 starting a parameterised list key", "foo; @a=1", 9, absl::nullopt}, - {"0x41 starting a parameterised list key", "foo; Aa=1", 9, absl::nullopt}, - {"0x42 starting a parameterised list key", "foo; Ba=1", 9, absl::nullopt}, - {"0x43 starting a parameterised list key", "foo; Ca=1", 9, absl::nullopt}, - {"0x44 starting a parameterised list key", "foo; Da=1", 9, absl::nullopt}, - {"0x45 starting a parameterised list key", "foo; Ea=1", 9, absl::nullopt}, - {"0x46 starting a parameterised list key", "foo; Fa=1", 9, absl::nullopt}, - {"0x47 starting a parameterised list key", "foo; Ga=1", 9, absl::nullopt}, - {"0x48 starting a parameterised list key", "foo; Ha=1", 9, absl::nullopt}, - {"0x49 starting a parameterised list key", "foo; Ia=1", 9, absl::nullopt}, - {"0x4a starting a parameterised list key", "foo; Ja=1", 9, absl::nullopt}, - {"0x4b starting a parameterised list key", "foo; Ka=1", 9, absl::nullopt}, - {"0x4c starting a parameterised list key", "foo; La=1", 9, absl::nullopt}, - {"0x4d starting a parameterised list key", "foo; Ma=1", 9, absl::nullopt}, - {"0x4e starting a parameterised list key", "foo; Na=1", 9, absl::nullopt}, - {"0x4f starting a parameterised list key", "foo; Oa=1", 9, absl::nullopt}, - {"0x50 starting a parameterised list key", "foo; Pa=1", 9, absl::nullopt}, - {"0x51 starting a parameterised list key", "foo; Qa=1", 9, absl::nullopt}, - {"0x52 starting a parameterised list key", "foo; Ra=1", 9, absl::nullopt}, - {"0x53 starting a parameterised list key", "foo; Sa=1", 9, absl::nullopt}, - {"0x54 starting a parameterised list key", "foo; Ta=1", 9, absl::nullopt}, - {"0x55 starting a parameterised list key", "foo; Ua=1", 9, absl::nullopt}, - {"0x56 starting a parameterised list key", "foo; Va=1", 9, absl::nullopt}, - {"0x57 starting a parameterised list key", "foo; Wa=1", 9, absl::nullopt}, - {"0x58 starting a parameterised list key", "foo; Xa=1", 9, absl::nullopt}, - {"0x59 starting a parameterised list key", "foo; Ya=1", 9, absl::nullopt}, - {"0x5a starting a parameterised list key", "foo; Za=1", 9, absl::nullopt}, - {"0x5b starting a parameterised list key", "foo; [a=1", 9, absl::nullopt}, - {"0x5c starting a parameterised list key", "foo; \\a=1", 9, absl::nullopt}, - {"0x5d starting a parameterised list key", "foo; ]a=1", 9, absl::nullopt}, - {"0x5e starting a parameterised list key", "foo; ^a=1", 9, absl::nullopt}, - {"0x5f starting a parameterised list key", "foo; _a=1", 9, absl::nullopt}, - {"0x60 starting a parameterised list key", "foo; `a=1", 9, absl::nullopt}, - {"0x61 starting a parameterised list key", - "foo; aa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("aa", 1)}}}}, - "foo;aa=1"}, - {"0x62 starting a parameterised list key", - "foo; ba=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ba", 1)}}}}, - "foo;ba=1"}, - {"0x63 starting a parameterised list key", - "foo; ca=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ca", 1)}}}}, - "foo;ca=1"}, - {"0x64 starting a parameterised list key", - "foo; da=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("da", 1)}}}}, - "foo;da=1"}, - {"0x65 starting a parameterised list key", - "foo; ea=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ea", 1)}}}}, - "foo;ea=1"}, - {"0x66 starting a parameterised list key", - "foo; fa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("fa", 1)}}}}, - "foo;fa=1"}, - {"0x67 starting a parameterised list key", - "foo; ga=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ga", 1)}}}}, - "foo;ga=1"}, - {"0x68 starting a parameterised list key", - "foo; ha=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ha", 1)}}}}, - "foo;ha=1"}, - {"0x69 starting a parameterised list key", - "foo; ia=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ia", 1)}}}}, - "foo;ia=1"}, - {"0x6a starting a parameterised list key", - "foo; ja=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ja", 1)}}}}, - "foo;ja=1"}, - {"0x6b starting a parameterised list key", - "foo; ka=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ka", 1)}}}}, - "foo;ka=1"}, - {"0x6c starting a parameterised list key", - "foo; la=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("la", 1)}}}}, - "foo;la=1"}, - {"0x6d starting a parameterised list key", - "foo; ma=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ma", 1)}}}}, - "foo;ma=1"}, - {"0x6e starting a parameterised list key", - "foo; na=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("na", 1)}}}}, - "foo;na=1"}, - {"0x6f starting a parameterised list key", - "foo; oa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("oa", 1)}}}}, - "foo;oa=1"}, - {"0x70 starting a parameterised list key", - "foo; pa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("pa", 1)}}}}, - "foo;pa=1"}, - {"0x71 starting a parameterised list key", - "foo; qa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("qa", 1)}}}}, - "foo;qa=1"}, - {"0x72 starting a parameterised list key", - "foo; ra=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ra", 1)}}}}, - "foo;ra=1"}, - {"0x73 starting a parameterised list key", - "foo; sa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("sa", 1)}}}}, - "foo;sa=1"}, - {"0x74 starting a parameterised list key", - "foo; ta=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ta", 1)}}}}, - "foo;ta=1"}, - {"0x75 starting a parameterised list key", - "foo; ua=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ua", 1)}}}}, - "foo;ua=1"}, - {"0x76 starting a parameterised list key", - "foo; va=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("va", 1)}}}}, - "foo;va=1"}, - {"0x77 starting a parameterised list key", - "foo; wa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("wa", 1)}}}}, - "foo;wa=1"}, - {"0x78 starting a parameterised list key", - "foo; xa=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("xa", 1)}}}}, - "foo;xa=1"}, - {"0x79 starting a parameterised list key", - "foo; ya=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("ya", 1)}}}}, - "foo;ya=1"}, - {"0x7a starting a parameterised list key", - "foo; za=1", - 9, - {{{Item("foo", Item::kTokenType), {Param("za", 1)}}}}, - "foo;za=1"}, - {"0x7b starting a parameterised list key", "foo; {a=1", 9, absl::nullopt}, - {"0x7c starting a parameterised list key", "foo; |a=1", 9, absl::nullopt}, - {"0x7d starting a parameterised list key", "foo; }a=1", 9, absl::nullopt}, - {"0x7e starting a parameterised list key", "foo; ~a=1", 9, absl::nullopt}, - {"0x7f starting a parameterised list key", "foo; \177a=1", 9, - absl::nullopt}, - // list.json - {"basic list", "1, 42", 5, {{{Integer(1), {}}, {Integer(42), {}}}}}, - {"empty list", "", 0, {List()}}, - {"leading SP list", - " 42, 43", - 8, - {{{Integer(42), {}}, {Integer(43), {}}}}, - "42, 43"}, - {"single item list", "42", 2, {{{Integer(42), {}}}}}, - {"no whitespace list", - "1,42", - 4, - {{{Integer(1), {}}, {Integer(42), {}}}}, - "1, 42"}, - {"extra whitespace list", - "1 , 42", - 6, - {{{Integer(1), {}}, {Integer(42), {}}}}, - "1, 42"}, - {"tab separated list", - "1\t,\t42", - 6, - {{{Integer(1), {}}, {Integer(42), {}}}}, - "1, 42"}, - {"two line list", - "1, 42", - 5, - {{{Integer(1), {}}, {Integer(42), {}}}}, - "1, 42"}, - {"trailing comma list", "1, 42,", 6, absl::nullopt}, - {"empty item list", "1,,42", 5, absl::nullopt}, - {"empty item list (multiple field lines)", "1, , 42", 7, absl::nullopt}, - // listlist.json - {"basic list of lists", - "(1 2), (42 43)", - 14, - {{{{{Integer(1), {}}, {Integer(2), {}}}, {}}, - {{{Integer(42), {}}, {Integer(43), {}}}, {}}}}}, - {"single item list of lists", "(42)", 4, {{{{{Integer(42), {}}}, {}}}}}, - {"empty item list of lists", - "()", - 2, - {{{std::vector<ParameterizedItem>(), {}}}}}, - {"empty middle item list of lists", - "(1),(),(42)", - 11, - {{{{{Integer(1), {}}}, {}}, - {std::vector<ParameterizedItem>(), {}}, - {{{Integer(42), {}}}, {}}}}, - "(1), (), (42)"}, - {"extra whitespace list of lists", - "( 1 42 )", - 11, - {{{{{Integer(1), {}}, {Integer(42), {}}}, {}}}}, - "(1 42)"}, - {"wrong whitespace list of lists", "(1\t 42)", 7, absl::nullopt}, - {"no trailing parenthesis list of lists", "(1 42", 5, absl::nullopt}, - {"no trailing parenthesis middle list of lists", "(1 2, (42 43)", 13, - absl::nullopt}, - {"no spaces in inner-list", "(abc\"def\"?0123*dXZ3*xyz)", 24, - absl::nullopt}, - {"no closing parenthesis", "(", 1, absl::nullopt}, - // param-list.json - {"basic parameterised list", - "abc_123;a=1;b=2; cdef_456, ghi;q=9;r=\"+w\"", - 41, - {{{Item("abc_123", Item::kTokenType), - {Param("a", 1), Param("b", 2), BooleanParam("cdef_456", true)}}, - {Item("ghi", Item::kTokenType), {Param("q", 9), Param("r", "+w")}}}}, - "abc_123;a=1;b=2;cdef_456, ghi;q=9;r=\"+w\""}, - {"single item parameterised list", - "text/html;q=1.0", - 15, - {{{Item("text/html", Item::kTokenType), {DoubleParam("q", 1.000000)}}}}}, - {"missing parameter value parameterised list", - "text/html;a;q=1.0", - 17, - {{{Item("text/html", Item::kTokenType), - {BooleanParam("a", true), DoubleParam("q", 1.000000)}}}}}, - {"missing terminal parameter value parameterised list", - "text/html;q=1.0;a", - 17, - {{{Item("text/html", Item::kTokenType), - {DoubleParam("q", 1.000000), BooleanParam("a", true)}}}}}, - {"no whitespace parameterised list", - "text/html,text/plain;q=0.5", - 26, - {{{Item("text/html", Item::kTokenType), {}}, - {Item("text/plain", Item::kTokenType), {DoubleParam("q", 0.500000)}}}}, - "text/html, text/plain;q=0.5"}, - {"whitespace before = parameterised list", "text/html, text/plain;q =0.5", - 28, absl::nullopt}, - {"whitespace after = parameterised list", "text/html, text/plain;q= 0.5", - 28, absl::nullopt}, - {"whitespace before ; parameterised list", "text/html, text/plain ;q=0.5", - 28, absl::nullopt}, - {"whitespace after ; parameterised list", - "text/html, text/plain; q=0.5", - 28, - {{{Item("text/html", Item::kTokenType), {}}, - {Item("text/plain", Item::kTokenType), {DoubleParam("q", 0.500000)}}}}, - "text/html, text/plain;q=0.5"}, - {"extra whitespace parameterised list", - "text/html , text/plain; q=0.5; charset=utf-8", - 48, - {{{Item("text/html", Item::kTokenType), {}}, - {Item("text/plain", Item::kTokenType), - {DoubleParam("q", 0.500000), TokenParam("charset", "utf-8")}}}}, - "text/html, text/plain;q=0.5;charset=utf-8"}, - {"two lines parameterised list", - "text/html, text/plain;q=0.5", - 27, - {{{Item("text/html", Item::kTokenType), {}}, - {Item("text/plain", Item::kTokenType), {DoubleParam("q", 0.500000)}}}}, - "text/html, text/plain;q=0.5"}, - {"trailing comma parameterised list", "text/html,text/plain;q=0.5,", 27, - absl::nullopt}, - {"empty item parameterised list", "text/html,,text/plain;q=0.5,", 28, - absl::nullopt}, - // param-listlist.json - {"parameterised inner list", - "(abc_123);a=1;b=2, cdef_456", - 27, - {{{{{Item("abc_123", Item::kTokenType), {}}}, - {Param("a", 1), Param("b", 2)}}, - {Item("cdef_456", Item::kTokenType), {}}}}}, - {"parameterised inner list item", - "(abc_123;a=1;b=2;cdef_456)", - 26, - {{{{{Item("abc_123", Item::kTokenType), - {Param("a", 1), Param("b", 2), BooleanParam("cdef_456", true)}}}, - {}}}}}, - {"parameterised inner list with parameterised item", - "(abc_123;a=1;b=2);cdef_456", - 26, - {{{{{Item("abc_123", Item::kTokenType), {Param("a", 1), Param("b", 2)}}}, - {BooleanParam("cdef_456", true)}}}}}, - // token.json - {"basic token - list", - "a_b-c3/*", - 8, - {{{Item("a_b-c3/*", Item::kTokenType), {}}}}}, - {"token with capitals - list", - "fooBar", - 6, - {{{Item("fooBar", Item::kTokenType), {}}}}}, - {"token starting with capitals - list", - "FooBar", - 6, - {{{Item("FooBar", Item::kTokenType), {}}}}}, -}; - -const struct DictionaryTestCase { - const char* name; - const char* raw; - size_t raw_len; - const absl::optional<Dictionary> - expected; // nullopt if parse error is expected. - const char* canonical; // nullptr if parse error is expected, or if canonical - // format is identical to raw. -} dictionary_test_cases[] = { - - // dictionary.json - {"basic dictionary", - "en=\"Applepie\", da=:w4ZibGV0w6ZydGUK:", - 36, - {Dictionary{{{"en", {Item("Applepie"), {}}}, - {"da", - {Item("\303\206blet\303\246rte\n", Item::kByteSequenceType), - {}}}}}}}, - {"empty dictionary", "", 0, {Dictionary{{}}}}, - {"single item dictionary", - "a=1", - 3, - {Dictionary{{{"a", {Integer(1), {}}}}}}}, - {"list item dictionary", - "a=(1 2)", - 7, - {Dictionary{{{"a", {{{Integer(1), {}}, {Integer(2), {}}}, {}}}}}}}, - {"single list item dictionary", - "a=(1)", - 5, - {Dictionary{{{"a", {{{Integer(1), {}}}, {}}}}}}}, - {"empty list item dictionary", - "a=()", - 4, - {Dictionary{{{"a", {std::vector<ParameterizedItem>(), {}}}}}}}, - {"no whitespace dictionary", - "a=1,b=2", - 7, - {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}}, - "a=1, b=2"}, - {"extra whitespace dictionary", - "a=1 , b=2", - 10, - {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}}, - "a=1, b=2"}, - {"tab separated dictionary", - "a=1\t,\tb=2", - 9, - {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}}, - "a=1, b=2"}, - {"leading whitespace dictionary", - " a=1 , b=2", - 15, - {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}}, - "a=1, b=2"}, - {"whitespace before = dictionary", "a =1, b=2", 9, absl::nullopt}, - {"whitespace after = dictionary", "a=1, b= 2", 9, absl::nullopt}, - {"two lines dictionary", - "a=1, b=2", - 8, - {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}}, - "a=1, b=2"}, - {"missing value dictionary", - "a=1, b, c=3", - 11, - {Dictionary{{{"a", {Integer(1), {}}}, - {"b", {Item(true), {}}}, - {"c", {Integer(3), {}}}}}}}, - {"all missing value dictionary", - "a, b, c", - 7, - {Dictionary{{{"a", {Item(true), {}}}, - {"b", {Item(true), {}}}, - {"c", {Item(true), {}}}}}}}, - {"start missing value dictionary", - "a, b=2", - 6, - {Dictionary{{{"a", {Item(true), {}}}, {"b", {Integer(2), {}}}}}}}, - {"end missing value dictionary", - "a=1, b", - 6, - {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Item(true), {}}}}}}}, - {"missing value with params dictionary", - "a=1, b;foo=9, c=3", - 17, - {Dictionary{{{"a", {Integer(1), {}}}, - {"b", {Item(true), {Param("foo", 9)}}}, - {"c", {Integer(3), {}}}}}}}, - {"explicit true value with params dictionary", - "a=1, b=?1;foo=9, c=3", - 20, - {Dictionary{{{"a", {Integer(1), {}}}, - {"b", {Item(true), {Param("foo", 9)}}}, - {"c", {Integer(3), {}}}}}}, - "a=1, b;foo=9, c=3"}, - {"trailing comma dictionary", "a=1, b=2,", 9, absl::nullopt}, - {"empty item dictionary", "a=1,,b=2,", 9, absl::nullopt}, - {"duplicate key dictionary", - "a=1,b=2,a=3", - 11, - {Dictionary{{{"a", {Integer(3), {}}}, {"b", {Integer(2), {}}}}}}, - "a=3, b=2"}, - {"numeric key dictionary", "a=1,1b=2,a=1", 12, absl::nullopt}, - {"uppercase key dictionary", "a=1,B=2,a=1", 11, absl::nullopt}, - {"bad key dictionary", "a=1,b!=2,a=1", 12, absl::nullopt}, - // examples.json - {"Example-DictHeader", - "en=\"Applepie\", da=:w4ZibGV0w6ZydGU=:", - 36, - {Dictionary{ - {{"en", {Item("Applepie"), {}}}, - {"da", - {Item("\303\206blet\303\246rte", Item::kByteSequenceType), {}}}}}}}, - {"Example-DictHeader", - "a=?0, b, c; foo=bar", - 19, - {Dictionary{{{"a", {Item(false), {}}}, - {"b", {Item(true), {}}}, - {"c", {Item(true), {TokenParam("foo", "bar")}}}}}}, - "a=?0, b, c;foo=bar"}, - {"Example-DictListHeader", - "rating=1.5, feelings=(joy sadness)", - 34, - {Dictionary{{{"rating", {Item(1.500000), {}}}, - {"feelings", - {{{Item("joy", Item::kTokenType), {}}, - {Item("sadness", Item::kTokenType), {}}}, - {}}}}}}}, - {"Example-MixDict", - "a=(1 2), b=3, c=4;aa=bb, d=(5 6);valid", - 38, - {Dictionary{{{"a", {{{Integer(1), {}}, {Integer(2), {}}}, {}}}, - {"b", {Integer(3), {}}}, - {"c", {Integer(4), {TokenParam("aa", "bb")}}}, - {"d", - {{{Integer(5), {}}, {Integer(6), {}}}, - {BooleanParam("valid", true)}}}}}}, - "a=(1 2), b=3, c=4;aa=bb, d=(5 6);valid"}, - {"Example-Hdr (dictionary on one line)", - "foo=1, bar=2", - 12, - {Dictionary{{{"foo", {Integer(1), {}}}, {"bar", {Integer(2), {}}}}}}}, - {"Example-Hdr (dictionary on two lines)", - "foo=1, bar=2", - 12, - {Dictionary{{{"foo", {Integer(1), {}}}, {"bar", {Integer(2), {}}}}}}, - "foo=1, bar=2"}, - // key-generated.json - {"0x00 as a single-character dictionary key", "\000=1", 3, absl::nullopt}, - {"0x01 as a single-character dictionary key", "\001=1", 3, absl::nullopt}, - {"0x02 as a single-character dictionary key", "\002=1", 3, absl::nullopt}, - {"0x03 as a single-character dictionary key", "\003=1", 3, absl::nullopt}, - {"0x04 as a single-character dictionary key", "\004=1", 3, absl::nullopt}, - {"0x05 as a single-character dictionary key", "\005=1", 3, absl::nullopt}, - {"0x06 as a single-character dictionary key", "\006=1", 3, absl::nullopt}, - {"0x07 as a single-character dictionary key", "\a=1", 3, absl::nullopt}, - {"0x08 as a single-character dictionary key", "\b=1", 3, absl::nullopt}, - {"0x09 as a single-character dictionary key", "\t=1", 3, absl::nullopt}, - {"0x0a as a single-character dictionary key", "\n=1", 3, absl::nullopt}, - {"0x0b as a single-character dictionary key", "\v=1", 3, absl::nullopt}, - {"0x0c as a single-character dictionary key", "\f=1", 3, absl::nullopt}, - {"0x0d as a single-character dictionary key", "\r=1", 3, absl::nullopt}, - {"0x0e as a single-character dictionary key", "\016=1", 3, absl::nullopt}, - {"0x0f as a single-character dictionary key", "\017=1", 3, absl::nullopt}, - {"0x10 as a single-character dictionary key", "\020=1", 3, absl::nullopt}, - {"0x11 as a single-character dictionary key", "\021=1", 3, absl::nullopt}, - {"0x12 as a single-character dictionary key", "\022=1", 3, absl::nullopt}, - {"0x13 as a single-character dictionary key", "\023=1", 3, absl::nullopt}, - {"0x14 as a single-character dictionary key", "\024=1", 3, absl::nullopt}, - {"0x15 as a single-character dictionary key", "\025=1", 3, absl::nullopt}, - {"0x16 as a single-character dictionary key", "\026=1", 3, absl::nullopt}, - {"0x17 as a single-character dictionary key", "\027=1", 3, absl::nullopt}, - {"0x18 as a single-character dictionary key", "\030=1", 3, absl::nullopt}, - {"0x19 as a single-character dictionary key", "\031=1", 3, absl::nullopt}, - {"0x1a as a single-character dictionary key", "\032=1", 3, absl::nullopt}, - {"0x1b as a single-character dictionary key", "\033=1", 3, absl::nullopt}, - {"0x1c as a single-character dictionary key", "\034=1", 3, absl::nullopt}, - {"0x1d as a single-character dictionary key", "\035=1", 3, absl::nullopt}, - {"0x1e as a single-character dictionary key", "\036=1", 3, absl::nullopt}, - {"0x1f as a single-character dictionary key", "\037=1", 3, absl::nullopt}, - {"0x20 as a single-character dictionary key", "=1", 2, absl::nullopt}, - {"0x21 as a single-character dictionary key", "!=1", 3, absl::nullopt}, - {"0x22 as a single-character dictionary key", "\"=1", 3, absl::nullopt}, - {"0x23 as a single-character dictionary key", "#=1", 3, absl::nullopt}, - {"0x24 as a single-character dictionary key", "$=1", 3, absl::nullopt}, - {"0x25 as a single-character dictionary key", "%=1", 3, absl::nullopt}, - {"0x26 as a single-character dictionary key", "&=1", 3, absl::nullopt}, - {"0x27 as a single-character dictionary key", "'=1", 3, absl::nullopt}, - {"0x28 as a single-character dictionary key", "(=1", 3, absl::nullopt}, - {"0x29 as a single-character dictionary key", ")=1", 3, absl::nullopt}, - {"0x2a as a single-character dictionary key", - "*=1", - 3, - {Dictionary{{{"*", {Integer(1), {}}}}}}}, - {"0x2b as a single-character dictionary key", "+=1", 3, absl::nullopt}, - {"0x2c as a single-character dictionary key", ",=1", 3, absl::nullopt}, - {"0x2d as a single-character dictionary key", "-=1", 3, absl::nullopt}, - {"0x2e as a single-character dictionary key", ".=1", 3, absl::nullopt}, - {"0x2f as a single-character dictionary key", "/=1", 3, absl::nullopt}, - {"0x30 as a single-character dictionary key", "0=1", 3, absl::nullopt}, - {"0x31 as a single-character dictionary key", "1=1", 3, absl::nullopt}, - {"0x32 as a single-character dictionary key", "2=1", 3, absl::nullopt}, - {"0x33 as a single-character dictionary key", "3=1", 3, absl::nullopt}, - {"0x34 as a single-character dictionary key", "4=1", 3, absl::nullopt}, - {"0x35 as a single-character dictionary key", "5=1", 3, absl::nullopt}, - {"0x36 as a single-character dictionary key", "6=1", 3, absl::nullopt}, - {"0x37 as a single-character dictionary key", "7=1", 3, absl::nullopt}, - {"0x38 as a single-character dictionary key", "8=1", 3, absl::nullopt}, - {"0x39 as a single-character dictionary key", "9=1", 3, absl::nullopt}, - {"0x3a as a single-character dictionary key", ":=1", 3, absl::nullopt}, - {"0x3b as a single-character dictionary key", ";=1", 3, absl::nullopt}, - {"0x3c as a single-character dictionary key", "<=1", 3, absl::nullopt}, - {"0x3d as a single-character dictionary key", "==1", 3, absl::nullopt}, - {"0x3e as a single-character dictionary key", ">=1", 3, absl::nullopt}, - {"0x3f as a single-character dictionary key", "?=1", 3, absl::nullopt}, - {"0x40 as a single-character dictionary key", "@=1", 3, absl::nullopt}, - {"0x41 as a single-character dictionary key", "A=1", 3, absl::nullopt}, - {"0x42 as a single-character dictionary key", "B=1", 3, absl::nullopt}, - {"0x43 as a single-character dictionary key", "C=1", 3, absl::nullopt}, - {"0x44 as a single-character dictionary key", "D=1", 3, absl::nullopt}, - {"0x45 as a single-character dictionary key", "E=1", 3, absl::nullopt}, - {"0x46 as a single-character dictionary key", "F=1", 3, absl::nullopt}, - {"0x47 as a single-character dictionary key", "G=1", 3, absl::nullopt}, - {"0x48 as a single-character dictionary key", "H=1", 3, absl::nullopt}, - {"0x49 as a single-character dictionary key", "I=1", 3, absl::nullopt}, - {"0x4a as a single-character dictionary key", "J=1", 3, absl::nullopt}, - {"0x4b as a single-character dictionary key", "K=1", 3, absl::nullopt}, - {"0x4c as a single-character dictionary key", "L=1", 3, absl::nullopt}, - {"0x4d as a single-character dictionary key", "M=1", 3, absl::nullopt}, - {"0x4e as a single-character dictionary key", "N=1", 3, absl::nullopt}, - {"0x4f as a single-character dictionary key", "O=1", 3, absl::nullopt}, - {"0x50 as a single-character dictionary key", "P=1", 3, absl::nullopt}, - {"0x51 as a single-character dictionary key", "Q=1", 3, absl::nullopt}, - {"0x52 as a single-character dictionary key", "R=1", 3, absl::nullopt}, - {"0x53 as a single-character dictionary key", "S=1", 3, absl::nullopt}, - {"0x54 as a single-character dictionary key", "T=1", 3, absl::nullopt}, - {"0x55 as a single-character dictionary key", "U=1", 3, absl::nullopt}, - {"0x56 as a single-character dictionary key", "V=1", 3, absl::nullopt}, - {"0x57 as a single-character dictionary key", "W=1", 3, absl::nullopt}, - {"0x58 as a single-character dictionary key", "X=1", 3, absl::nullopt}, - {"0x59 as a single-character dictionary key", "Y=1", 3, absl::nullopt}, - {"0x5a as a single-character dictionary key", "Z=1", 3, absl::nullopt}, - {"0x5b as a single-character dictionary key", "[=1", 3, absl::nullopt}, - {"0x5c as a single-character dictionary key", "\\=1", 3, absl::nullopt}, - {"0x5d as a single-character dictionary key", "]=1", 3, absl::nullopt}, - {"0x5e as a single-character dictionary key", "^=1", 3, absl::nullopt}, - {"0x5f as a single-character dictionary key", "_=1", 3, absl::nullopt}, - {"0x60 as a single-character dictionary key", "`=1", 3, absl::nullopt}, - {"0x61 as a single-character dictionary key", - "a=1", - 3, - {Dictionary{{{"a", {Integer(1), {}}}}}}}, - {"0x62 as a single-character dictionary key", - "b=1", - 3, - {Dictionary{{{"b", {Integer(1), {}}}}}}}, - {"0x63 as a single-character dictionary key", - "c=1", - 3, - {Dictionary{{{"c", {Integer(1), {}}}}}}}, - {"0x64 as a single-character dictionary key", - "d=1", - 3, - {Dictionary{{{"d", {Integer(1), {}}}}}}}, - {"0x65 as a single-character dictionary key", - "e=1", - 3, - {Dictionary{{{"e", {Integer(1), {}}}}}}}, - {"0x66 as a single-character dictionary key", - "f=1", - 3, - {Dictionary{{{"f", {Integer(1), {}}}}}}}, - {"0x67 as a single-character dictionary key", - "g=1", - 3, - {Dictionary{{{"g", {Integer(1), {}}}}}}}, - {"0x68 as a single-character dictionary key", - "h=1", - 3, - {Dictionary{{{"h", {Integer(1), {}}}}}}}, - {"0x69 as a single-character dictionary key", - "i=1", - 3, - {Dictionary{{{"i", {Integer(1), {}}}}}}}, - {"0x6a as a single-character dictionary key", - "j=1", - 3, - {Dictionary{{{"j", {Integer(1), {}}}}}}}, - {"0x6b as a single-character dictionary key", - "k=1", - 3, - {Dictionary{{{"k", {Integer(1), {}}}}}}}, - {"0x6c as a single-character dictionary key", - "l=1", - 3, - {Dictionary{{{"l", {Integer(1), {}}}}}}}, - {"0x6d as a single-character dictionary key", - "m=1", - 3, - {Dictionary{{{"m", {Integer(1), {}}}}}}}, - {"0x6e as a single-character dictionary key", - "n=1", - 3, - {Dictionary{{{"n", {Integer(1), {}}}}}}}, - {"0x6f as a single-character dictionary key", - "o=1", - 3, - {Dictionary{{{"o", {Integer(1), {}}}}}}}, - {"0x70 as a single-character dictionary key", - "p=1", - 3, - {Dictionary{{{"p", {Integer(1), {}}}}}}}, - {"0x71 as a single-character dictionary key", - "q=1", - 3, - {Dictionary{{{"q", {Integer(1), {}}}}}}}, - {"0x72 as a single-character dictionary key", - "r=1", - 3, - {Dictionary{{{"r", {Integer(1), {}}}}}}}, - {"0x73 as a single-character dictionary key", - "s=1", - 3, - {Dictionary{{{"s", {Integer(1), {}}}}}}}, - {"0x74 as a single-character dictionary key", - "t=1", - 3, - {Dictionary{{{"t", {Integer(1), {}}}}}}}, - {"0x75 as a single-character dictionary key", - "u=1", - 3, - {Dictionary{{{"u", {Integer(1), {}}}}}}}, - {"0x76 as a single-character dictionary key", - "v=1", - 3, - {Dictionary{{{"v", {Integer(1), {}}}}}}}, - {"0x77 as a single-character dictionary key", - "w=1", - 3, - {Dictionary{{{"w", {Integer(1), {}}}}}}}, - {"0x78 as a single-character dictionary key", - "x=1", - 3, - {Dictionary{{{"x", {Integer(1), {}}}}}}}, - {"0x79 as a single-character dictionary key", - "y=1", - 3, - {Dictionary{{{"y", {Integer(1), {}}}}}}}, - {"0x7a as a single-character dictionary key", - "z=1", - 3, - {Dictionary{{{"z", {Integer(1), {}}}}}}}, - {"0x7b as a single-character dictionary key", "{=1", 3, absl::nullopt}, - {"0x7c as a single-character dictionary key", "|=1", 3, absl::nullopt}, - {"0x7d as a single-character dictionary key", "}=1", 3, absl::nullopt}, - {"0x7e as a single-character dictionary key", "~=1", 3, absl::nullopt}, - {"0x7f as a single-character dictionary key", "\177=1", 3, absl::nullopt}, - {"0x00 in dictionary key", "a\000a=1", 5, absl::nullopt}, - {"0x01 in dictionary key", "a\001a=1", 5, absl::nullopt}, - {"0x02 in dictionary key", "a\002a=1", 5, absl::nullopt}, - {"0x03 in dictionary key", "a\003a=1", 5, absl::nullopt}, - {"0x04 in dictionary key", "a\004a=1", 5, absl::nullopt}, - {"0x05 in dictionary key", "a\005a=1", 5, absl::nullopt}, - {"0x06 in dictionary key", "a\006a=1", 5, absl::nullopt}, - {"0x07 in dictionary key", "a\aa=1", 5, absl::nullopt}, - {"0x08 in dictionary key", "a\ba=1", 5, absl::nullopt}, - {"0x09 in dictionary key", "a\ta=1", 5, absl::nullopt}, - {"0x0a in dictionary key", "a\na=1", 5, absl::nullopt}, - {"0x0b in dictionary key", "a\va=1", 5, absl::nullopt}, - {"0x0c in dictionary key", "a\fa=1", 5, absl::nullopt}, - {"0x0d in dictionary key", "a\ra=1", 5, absl::nullopt}, - {"0x0e in dictionary key", "a\016a=1", 5, absl::nullopt}, - {"0x0f in dictionary key", "a\017a=1", 5, absl::nullopt}, - {"0x10 in dictionary key", "a\020a=1", 5, absl::nullopt}, - {"0x11 in dictionary key", "a\021a=1", 5, absl::nullopt}, - {"0x12 in dictionary key", "a\022a=1", 5, absl::nullopt}, - {"0x13 in dictionary key", "a\023a=1", 5, absl::nullopt}, - {"0x14 in dictionary key", "a\024a=1", 5, absl::nullopt}, - {"0x15 in dictionary key", "a\025a=1", 5, absl::nullopt}, - {"0x16 in dictionary key", "a\026a=1", 5, absl::nullopt}, - {"0x17 in dictionary key", "a\027a=1", 5, absl::nullopt}, - {"0x18 in dictionary key", "a\030a=1", 5, absl::nullopt}, - {"0x19 in dictionary key", "a\031a=1", 5, absl::nullopt}, - {"0x1a in dictionary key", "a\032a=1", 5, absl::nullopt}, - {"0x1b in dictionary key", "a\033a=1", 5, absl::nullopt}, - {"0x1c in dictionary key", "a\034a=1", 5, absl::nullopt}, - {"0x1d in dictionary key", "a\035a=1", 5, absl::nullopt}, - {"0x1e in dictionary key", "a\036a=1", 5, absl::nullopt}, - {"0x1f in dictionary key", "a\037a=1", 5, absl::nullopt}, - {"0x20 in dictionary key", "a a=1", 5, absl::nullopt}, - {"0x21 in dictionary key", "a!a=1", 5, absl::nullopt}, - {"0x22 in dictionary key", "a\"a=1", 5, absl::nullopt}, - {"0x23 in dictionary key", "a#a=1", 5, absl::nullopt}, - {"0x24 in dictionary key", "a$a=1", 5, absl::nullopt}, - {"0x25 in dictionary key", "a%a=1", 5, absl::nullopt}, - {"0x26 in dictionary key", "a&a=1", 5, absl::nullopt}, - {"0x27 in dictionary key", "a'a=1", 5, absl::nullopt}, - {"0x28 in dictionary key", "a(a=1", 5, absl::nullopt}, - {"0x29 in dictionary key", "a)a=1", 5, absl::nullopt}, - {"0x2a in dictionary key", - "a*a=1", - 5, - {Dictionary{{{"a*a", {Integer(1), {}}}}}}}, - {"0x2b in dictionary key", "a+a=1", 5, absl::nullopt}, - {"0x2c in dictionary key", - "a,a=1", - 5, - {Dictionary{{{"a", {Integer(1), {}}}}}}, - "a=1"}, - {"0x2d in dictionary key", - "a-a=1", - 5, - {Dictionary{{{"a-a", {Integer(1), {}}}}}}}, - {"0x2e in dictionary key", - "a.a=1", - 5, - {Dictionary{{{"a.a", {Integer(1), {}}}}}}}, - {"0x2f in dictionary key", "a/a=1", 5, absl::nullopt}, - {"0x30 in dictionary key", - "a0a=1", - 5, - {Dictionary{{{"a0a", {Integer(1), {}}}}}}}, - {"0x31 in dictionary key", - "a1a=1", - 5, - {Dictionary{{{"a1a", {Integer(1), {}}}}}}}, - {"0x32 in dictionary key", - "a2a=1", - 5, - {Dictionary{{{"a2a", {Integer(1), {}}}}}}}, - {"0x33 in dictionary key", - "a3a=1", - 5, - {Dictionary{{{"a3a", {Integer(1), {}}}}}}}, - {"0x34 in dictionary key", - "a4a=1", - 5, - {Dictionary{{{"a4a", {Integer(1), {}}}}}}}, - {"0x35 in dictionary key", - "a5a=1", - 5, - {Dictionary{{{"a5a", {Integer(1), {}}}}}}}, - {"0x36 in dictionary key", - "a6a=1", - 5, - {Dictionary{{{"a6a", {Integer(1), {}}}}}}}, - {"0x37 in dictionary key", - "a7a=1", - 5, - {Dictionary{{{"a7a", {Integer(1), {}}}}}}}, - {"0x38 in dictionary key", - "a8a=1", - 5, - {Dictionary{{{"a8a", {Integer(1), {}}}}}}}, - {"0x39 in dictionary key", - "a9a=1", - 5, - {Dictionary{{{"a9a", {Integer(1), {}}}}}}}, - {"0x3a in dictionary key", "a:a=1", 5, absl::nullopt}, - {"0x3b in dictionary key", - "a;a=1", - 5, - {Dictionary{{{"a", {Item(true), {Param("a", 1)}}}}}}}, - {"0x3c in dictionary key", "a<a=1", 5, absl::nullopt}, - {"0x3d in dictionary key", "a=a=1", 5, absl::nullopt}, - {"0x3e in dictionary key", "a>a=1", 5, absl::nullopt}, - {"0x3f in dictionary key", "a?a=1", 5, absl::nullopt}, - {"0x40 in dictionary key", "a@a=1", 5, absl::nullopt}, - {"0x41 in dictionary key", "aAa=1", 5, absl::nullopt}, - {"0x42 in dictionary key", "aBa=1", 5, absl::nullopt}, - {"0x43 in dictionary key", "aCa=1", 5, absl::nullopt}, - {"0x44 in dictionary key", "aDa=1", 5, absl::nullopt}, - {"0x45 in dictionary key", "aEa=1", 5, absl::nullopt}, - {"0x46 in dictionary key", "aFa=1", 5, absl::nullopt}, - {"0x47 in dictionary key", "aGa=1", 5, absl::nullopt}, - {"0x48 in dictionary key", "aHa=1", 5, absl::nullopt}, - {"0x49 in dictionary key", "aIa=1", 5, absl::nullopt}, - {"0x4a in dictionary key", "aJa=1", 5, absl::nullopt}, - {"0x4b in dictionary key", "aKa=1", 5, absl::nullopt}, - {"0x4c in dictionary key", "aLa=1", 5, absl::nullopt}, - {"0x4d in dictionary key", "aMa=1", 5, absl::nullopt}, - {"0x4e in dictionary key", "aNa=1", 5, absl::nullopt}, - {"0x4f in dictionary key", "aOa=1", 5, absl::nullopt}, - {"0x50 in dictionary key", "aPa=1", 5, absl::nullopt}, - {"0x51 in dictionary key", "aQa=1", 5, absl::nullopt}, - {"0x52 in dictionary key", "aRa=1", 5, absl::nullopt}, - {"0x53 in dictionary key", "aSa=1", 5, absl::nullopt}, - {"0x54 in dictionary key", "aTa=1", 5, absl::nullopt}, - {"0x55 in dictionary key", "aUa=1", 5, absl::nullopt}, - {"0x56 in dictionary key", "aVa=1", 5, absl::nullopt}, - {"0x57 in dictionary key", "aWa=1", 5, absl::nullopt}, - {"0x58 in dictionary key", "aXa=1", 5, absl::nullopt}, - {"0x59 in dictionary key", "aYa=1", 5, absl::nullopt}, - {"0x5a in dictionary key", "aZa=1", 5, absl::nullopt}, - {"0x5b in dictionary key", "a[a=1", 5, absl::nullopt}, - {"0x5c in dictionary key", "a\\a=1", 5, absl::nullopt}, - {"0x5d in dictionary key", "a]a=1", 5, absl::nullopt}, - {"0x5e in dictionary key", "a^a=1", 5, absl::nullopt}, - {"0x5f in dictionary key", - "a_a=1", - 5, - {Dictionary{{{"a_a", {Integer(1), {}}}}}}}, - {"0x60 in dictionary key", "a`a=1", 5, absl::nullopt}, - {"0x61 in dictionary key", - "aaa=1", - 5, - {Dictionary{{{"aaa", {Integer(1), {}}}}}}}, - {"0x62 in dictionary key", - "aba=1", - 5, - {Dictionary{{{"aba", {Integer(1), {}}}}}}}, - {"0x63 in dictionary key", - "aca=1", - 5, - {Dictionary{{{"aca", {Integer(1), {}}}}}}}, - {"0x64 in dictionary key", - "ada=1", - 5, - {Dictionary{{{"ada", {Integer(1), {}}}}}}}, - {"0x65 in dictionary key", - "aea=1", - 5, - {Dictionary{{{"aea", {Integer(1), {}}}}}}}, - {"0x66 in dictionary key", - "afa=1", - 5, - {Dictionary{{{"afa", {Integer(1), {}}}}}}}, - {"0x67 in dictionary key", - "aga=1", - 5, - {Dictionary{{{"aga", {Integer(1), {}}}}}}}, - {"0x68 in dictionary key", - "aha=1", - 5, - {Dictionary{{{"aha", {Integer(1), {}}}}}}}, - {"0x69 in dictionary key", - "aia=1", - 5, - {Dictionary{{{"aia", {Integer(1), {}}}}}}}, - {"0x6a in dictionary key", - "aja=1", - 5, - {Dictionary{{{"aja", {Integer(1), {}}}}}}}, - {"0x6b in dictionary key", - "aka=1", - 5, - {Dictionary{{{"aka", {Integer(1), {}}}}}}}, - {"0x6c in dictionary key", - "ala=1", - 5, - {Dictionary{{{"ala", {Integer(1), {}}}}}}}, - {"0x6d in dictionary key", - "ama=1", - 5, - {Dictionary{{{"ama", {Integer(1), {}}}}}}}, - {"0x6e in dictionary key", - "ana=1", - 5, - {Dictionary{{{"ana", {Integer(1), {}}}}}}}, - {"0x6f in dictionary key", - "aoa=1", - 5, - {Dictionary{{{"aoa", {Integer(1), {}}}}}}}, - {"0x70 in dictionary key", - "apa=1", - 5, - {Dictionary{{{"apa", {Integer(1), {}}}}}}}, - {"0x71 in dictionary key", - "aqa=1", - 5, - {Dictionary{{{"aqa", {Integer(1), {}}}}}}}, - {"0x72 in dictionary key", - "ara=1", - 5, - {Dictionary{{{"ara", {Integer(1), {}}}}}}}, - {"0x73 in dictionary key", - "asa=1", - 5, - {Dictionary{{{"asa", {Integer(1), {}}}}}}}, - {"0x74 in dictionary key", - "ata=1", - 5, - {Dictionary{{{"ata", {Integer(1), {}}}}}}}, - {"0x75 in dictionary key", - "aua=1", - 5, - {Dictionary{{{"aua", {Integer(1), {}}}}}}}, - {"0x76 in dictionary key", - "ava=1", - 5, - {Dictionary{{{"ava", {Integer(1), {}}}}}}}, - {"0x77 in dictionary key", - "awa=1", - 5, - {Dictionary{{{"awa", {Integer(1), {}}}}}}}, - {"0x78 in dictionary key", - "axa=1", - 5, - {Dictionary{{{"axa", {Integer(1), {}}}}}}}, - {"0x79 in dictionary key", - "aya=1", - 5, - {Dictionary{{{"aya", {Integer(1), {}}}}}}}, - {"0x7a in dictionary key", - "aza=1", - 5, - {Dictionary{{{"aza", {Integer(1), {}}}}}}}, - {"0x7b in dictionary key", "a{a=1", 5, absl::nullopt}, - {"0x7c in dictionary key", "a|a=1", 5, absl::nullopt}, - {"0x7d in dictionary key", "a}a=1", 5, absl::nullopt}, - {"0x7e in dictionary key", "a~a=1", 5, absl::nullopt}, - {"0x7f in dictionary key", "a\177a=1", 5, absl::nullopt}, - {"0x00 starting an dictionary key", "\000a=1", 4, absl::nullopt}, - {"0x01 starting an dictionary key", "\001a=1", 4, absl::nullopt}, - {"0x02 starting an dictionary key", "\002a=1", 4, absl::nullopt}, - {"0x03 starting an dictionary key", "\003a=1", 4, absl::nullopt}, - {"0x04 starting an dictionary key", "\004a=1", 4, absl::nullopt}, - {"0x05 starting an dictionary key", "\005a=1", 4, absl::nullopt}, - {"0x06 starting an dictionary key", "\006a=1", 4, absl::nullopt}, - {"0x07 starting an dictionary key", "\aa=1", 4, absl::nullopt}, - {"0x08 starting an dictionary key", "\ba=1", 4, absl::nullopt}, - {"0x09 starting an dictionary key", "\ta=1", 4, absl::nullopt}, - {"0x0a starting an dictionary key", "\na=1", 4, absl::nullopt}, - {"0x0b starting an dictionary key", "\va=1", 4, absl::nullopt}, - {"0x0c starting an dictionary key", "\fa=1", 4, absl::nullopt}, - {"0x0d starting an dictionary key", "\ra=1", 4, absl::nullopt}, - {"0x0e starting an dictionary key", "\016a=1", 4, absl::nullopt}, - {"0x0f starting an dictionary key", "\017a=1", 4, absl::nullopt}, - {"0x10 starting an dictionary key", "\020a=1", 4, absl::nullopt}, - {"0x11 starting an dictionary key", "\021a=1", 4, absl::nullopt}, - {"0x12 starting an dictionary key", "\022a=1", 4, absl::nullopt}, - {"0x13 starting an dictionary key", "\023a=1", 4, absl::nullopt}, - {"0x14 starting an dictionary key", "\024a=1", 4, absl::nullopt}, - {"0x15 starting an dictionary key", "\025a=1", 4, absl::nullopt}, - {"0x16 starting an dictionary key", "\026a=1", 4, absl::nullopt}, - {"0x17 starting an dictionary key", "\027a=1", 4, absl::nullopt}, - {"0x18 starting an dictionary key", "\030a=1", 4, absl::nullopt}, - {"0x19 starting an dictionary key", "\031a=1", 4, absl::nullopt}, - {"0x1a starting an dictionary key", "\032a=1", 4, absl::nullopt}, - {"0x1b starting an dictionary key", "\033a=1", 4, absl::nullopt}, - {"0x1c starting an dictionary key", "\034a=1", 4, absl::nullopt}, - {"0x1d starting an dictionary key", "\035a=1", 4, absl::nullopt}, - {"0x1e starting an dictionary key", "\036a=1", 4, absl::nullopt}, - {"0x1f starting an dictionary key", "\037a=1", 4, absl::nullopt}, - {"0x20 starting an dictionary key", - " a=1", - 4, - {Dictionary{{{"a", {Integer(1), {}}}}}}, - "a=1"}, - {"0x21 starting an dictionary key", "!a=1", 4, absl::nullopt}, - {"0x22 starting an dictionary key", "\"a=1", 4, absl::nullopt}, - {"0x23 starting an dictionary key", "#a=1", 4, absl::nullopt}, - {"0x24 starting an dictionary key", "$a=1", 4, absl::nullopt}, - {"0x25 starting an dictionary key", "%a=1", 4, absl::nullopt}, - {"0x26 starting an dictionary key", "&a=1", 4, absl::nullopt}, - {"0x27 starting an dictionary key", "'a=1", 4, absl::nullopt}, - {"0x28 starting an dictionary key", "(a=1", 4, absl::nullopt}, - {"0x29 starting an dictionary key", ")a=1", 4, absl::nullopt}, - {"0x2a starting an dictionary key", - "*a=1", - 4, - {Dictionary{{{"*a", {Integer(1), {}}}}}}}, - {"0x2b starting an dictionary key", "+a=1", 4, absl::nullopt}, - {"0x2c starting an dictionary key", ",a=1", 4, absl::nullopt}, - {"0x2d starting an dictionary key", "-a=1", 4, absl::nullopt}, - {"0x2e starting an dictionary key", ".a=1", 4, absl::nullopt}, - {"0x2f starting an dictionary key", "/a=1", 4, absl::nullopt}, - {"0x30 starting an dictionary key", "0a=1", 4, absl::nullopt}, - {"0x31 starting an dictionary key", "1a=1", 4, absl::nullopt}, - {"0x32 starting an dictionary key", "2a=1", 4, absl::nullopt}, - {"0x33 starting an dictionary key", "3a=1", 4, absl::nullopt}, - {"0x34 starting an dictionary key", "4a=1", 4, absl::nullopt}, - {"0x35 starting an dictionary key", "5a=1", 4, absl::nullopt}, - {"0x36 starting an dictionary key", "6a=1", 4, absl::nullopt}, - {"0x37 starting an dictionary key", "7a=1", 4, absl::nullopt}, - {"0x38 starting an dictionary key", "8a=1", 4, absl::nullopt}, - {"0x39 starting an dictionary key", "9a=1", 4, absl::nullopt}, - {"0x3a starting an dictionary key", ":a=1", 4, absl::nullopt}, - {"0x3b starting an dictionary key", ";a=1", 4, absl::nullopt}, - {"0x3c starting an dictionary key", "<a=1", 4, absl::nullopt}, - {"0x3d starting an dictionary key", "=a=1", 4, absl::nullopt}, - {"0x3e starting an dictionary key", ">a=1", 4, absl::nullopt}, - {"0x3f starting an dictionary key", "?a=1", 4, absl::nullopt}, - {"0x40 starting an dictionary key", "@a=1", 4, absl::nullopt}, - {"0x41 starting an dictionary key", "Aa=1", 4, absl::nullopt}, - {"0x42 starting an dictionary key", "Ba=1", 4, absl::nullopt}, - {"0x43 starting an dictionary key", "Ca=1", 4, absl::nullopt}, - {"0x44 starting an dictionary key", "Da=1", 4, absl::nullopt}, - {"0x45 starting an dictionary key", "Ea=1", 4, absl::nullopt}, - {"0x46 starting an dictionary key", "Fa=1", 4, absl::nullopt}, - {"0x47 starting an dictionary key", "Ga=1", 4, absl::nullopt}, - {"0x48 starting an dictionary key", "Ha=1", 4, absl::nullopt}, - {"0x49 starting an dictionary key", "Ia=1", 4, absl::nullopt}, - {"0x4a starting an dictionary key", "Ja=1", 4, absl::nullopt}, - {"0x4b starting an dictionary key", "Ka=1", 4, absl::nullopt}, - {"0x4c starting an dictionary key", "La=1", 4, absl::nullopt}, - {"0x4d starting an dictionary key", "Ma=1", 4, absl::nullopt}, - {"0x4e starting an dictionary key", "Na=1", 4, absl::nullopt}, - {"0x4f starting an dictionary key", "Oa=1", 4, absl::nullopt}, - {"0x50 starting an dictionary key", "Pa=1", 4, absl::nullopt}, - {"0x51 starting an dictionary key", "Qa=1", 4, absl::nullopt}, - {"0x52 starting an dictionary key", "Ra=1", 4, absl::nullopt}, - {"0x53 starting an dictionary key", "Sa=1", 4, absl::nullopt}, - {"0x54 starting an dictionary key", "Ta=1", 4, absl::nullopt}, - {"0x55 starting an dictionary key", "Ua=1", 4, absl::nullopt}, - {"0x56 starting an dictionary key", "Va=1", 4, absl::nullopt}, - {"0x57 starting an dictionary key", "Wa=1", 4, absl::nullopt}, - {"0x58 starting an dictionary key", "Xa=1", 4, absl::nullopt}, - {"0x59 starting an dictionary key", "Ya=1", 4, absl::nullopt}, - {"0x5a starting an dictionary key", "Za=1", 4, absl::nullopt}, - {"0x5b starting an dictionary key", "[a=1", 4, absl::nullopt}, - {"0x5c starting an dictionary key", "\\a=1", 4, absl::nullopt}, - {"0x5d starting an dictionary key", "]a=1", 4, absl::nullopt}, - {"0x5e starting an dictionary key", "^a=1", 4, absl::nullopt}, - {"0x5f starting an dictionary key", "_a=1", 4, absl::nullopt}, - {"0x60 starting an dictionary key", "`a=1", 4, absl::nullopt}, - {"0x61 starting an dictionary key", - "aa=1", - 4, - {Dictionary{{{"aa", {Integer(1), {}}}}}}}, - {"0x62 starting an dictionary key", - "ba=1", - 4, - {Dictionary{{{"ba", {Integer(1), {}}}}}}}, - {"0x63 starting an dictionary key", - "ca=1", - 4, - {Dictionary{{{"ca", {Integer(1), {}}}}}}}, - {"0x64 starting an dictionary key", - "da=1", - 4, - {Dictionary{{{"da", {Integer(1), {}}}}}}}, - {"0x65 starting an dictionary key", - "ea=1", - 4, - {Dictionary{{{"ea", {Integer(1), {}}}}}}}, - {"0x66 starting an dictionary key", - "fa=1", - 4, - {Dictionary{{{"fa", {Integer(1), {}}}}}}}, - {"0x67 starting an dictionary key", - "ga=1", - 4, - {Dictionary{{{"ga", {Integer(1), {}}}}}}}, - {"0x68 starting an dictionary key", - "ha=1", - 4, - {Dictionary{{{"ha", {Integer(1), {}}}}}}}, - {"0x69 starting an dictionary key", - "ia=1", - 4, - {Dictionary{{{"ia", {Integer(1), {}}}}}}}, - {"0x6a starting an dictionary key", - "ja=1", - 4, - {Dictionary{{{"ja", {Integer(1), {}}}}}}}, - {"0x6b starting an dictionary key", - "ka=1", - 4, - {Dictionary{{{"ka", {Integer(1), {}}}}}}}, - {"0x6c starting an dictionary key", - "la=1", - 4, - {Dictionary{{{"la", {Integer(1), {}}}}}}}, - {"0x6d starting an dictionary key", - "ma=1", - 4, - {Dictionary{{{"ma", {Integer(1), {}}}}}}}, - {"0x6e starting an dictionary key", - "na=1", - 4, - {Dictionary{{{"na", {Integer(1), {}}}}}}}, - {"0x6f starting an dictionary key", - "oa=1", - 4, - {Dictionary{{{"oa", {Integer(1), {}}}}}}}, - {"0x70 starting an dictionary key", - "pa=1", - 4, - {Dictionary{{{"pa", {Integer(1), {}}}}}}}, - {"0x71 starting an dictionary key", - "qa=1", - 4, - {Dictionary{{{"qa", {Integer(1), {}}}}}}}, - {"0x72 starting an dictionary key", - "ra=1", - 4, - {Dictionary{{{"ra", {Integer(1), {}}}}}}}, - {"0x73 starting an dictionary key", - "sa=1", - 4, - {Dictionary{{{"sa", {Integer(1), {}}}}}}}, - {"0x74 starting an dictionary key", - "ta=1", - 4, - {Dictionary{{{"ta", {Integer(1), {}}}}}}}, - {"0x75 starting an dictionary key", - "ua=1", - 4, - {Dictionary{{{"ua", {Integer(1), {}}}}}}}, - {"0x76 starting an dictionary key", - "va=1", - 4, - {Dictionary{{{"va", {Integer(1), {}}}}}}}, - {"0x77 starting an dictionary key", - "wa=1", - 4, - {Dictionary{{{"wa", {Integer(1), {}}}}}}}, - {"0x78 starting an dictionary key", - "xa=1", - 4, - {Dictionary{{{"xa", {Integer(1), {}}}}}}}, - {"0x79 starting an dictionary key", - "ya=1", - 4, - {Dictionary{{{"ya", {Integer(1), {}}}}}}}, - {"0x7a starting an dictionary key", - "za=1", - 4, - {Dictionary{{{"za", {Integer(1), {}}}}}}}, - {"0x7b starting an dictionary key", "{a=1", 4, absl::nullopt}, - {"0x7c starting an dictionary key", "|a=1", 4, absl::nullopt}, - {"0x7d starting an dictionary key", "}a=1", 4, absl::nullopt}, - {"0x7e starting an dictionary key", "~a=1", 4, absl::nullopt}, - {"0x7f starting an dictionary key", "\177a=1", 4, absl::nullopt}, - // param-dict.json - {"basic parameterised dict", - "abc=123;a=1;b=2, def=456, ghi=789;q=9;r=\"+w\"", - 44, - {Dictionary{ - {{"abc", {Integer(123), {Param("a", 1), Param("b", 2)}}}, - {"def", {Integer(456), {}}}, - {"ghi", {Integer(789), {Param("q", 9), Param("r", "+w")}}}}}}}, - {"single item parameterised dict", - "a=b; q=1.0", - 10, - {Dictionary{ - {{"a", {Item("b", Item::kTokenType), {DoubleParam("q", 1.000000)}}}}}}, - "a=b;q=1.0"}, - {"list item parameterised dictionary", - "a=(1 2); q=1.0", - 14, - {Dictionary{{{"a", - {{{Integer(1), {}}, {Integer(2), {}}}, - {DoubleParam("q", 1.000000)}}}}}}, - "a=(1 2);q=1.0"}, - {"missing parameter value parameterised dict", - "a=3;c;d=5", - 9, - {Dictionary{ - {{"a", {Integer(3), {BooleanParam("c", true), Param("d", 5)}}}}}}}, - {"terminal missing parameter value parameterised dict", - "a=3;c=5;d", - 9, - {Dictionary{ - {{"a", {Integer(3), {Param("c", 5), BooleanParam("d", true)}}}}}}}, - {"no whitespace parameterised dict", - "a=b;c=1,d=e;f=2", - 15, - {Dictionary{{{"a", {Item("b", Item::kTokenType), {Param("c", 1)}}}, - {"d", {Item("e", Item::kTokenType), {Param("f", 2)}}}}}}, - "a=b;c=1, d=e;f=2"}, - {"whitespace before = parameterised dict", "a=b;q =0.5", 10, absl::nullopt}, - {"whitespace after = parameterised dict", "a=b;q= 0.5", 10, absl::nullopt}, - {"whitespace before ; parameterised dict", "a=b ;q=0.5", 10, absl::nullopt}, - {"whitespace after ; parameterised dict", - "a=b; q=0.5", - 10, - {Dictionary{ - {{"a", {Item("b", Item::kTokenType), {DoubleParam("q", 0.500000)}}}}}}, - "a=b;q=0.5"}, - {"extra whitespace parameterised dict", - "a=b; c=1 , d=e; f=2; g=3", - 27, - {Dictionary{ - {{"a", {Item("b", Item::kTokenType), {Param("c", 1)}}}, - {"d", - {Item("e", Item::kTokenType), {Param("f", 2), Param("g", 3)}}}}}}, - "a=b;c=1, d=e;f=2;g=3"}, - {"two lines parameterised list", - "a=b;c=1, d=e;f=2", - 16, - {Dictionary{{{"a", {Item("b", Item::kTokenType), {Param("c", 1)}}}, - {"d", {Item("e", Item::kTokenType), {Param("f", 2)}}}}}}, - "a=b;c=1, d=e;f=2"}, - {"trailing comma parameterised list", "a=b; q=1.0,", 11, absl::nullopt}, - {"empty item parameterised list", "a=b; q=1.0,,c=d", 15, absl::nullopt}, -}; -} // namespace - -TEST(StructuredHeaderGeneratedTest, ParseItem) { - for (const auto& c : parameterized_item_test_cases) { - if (c.raw) { - SCOPED_TRACE(c.name); - std::string raw{c.raw, c.raw_len}; - absl::optional<ParameterizedItem> result = ParseItem(raw); - EXPECT_EQ(result, c.expected); - } - } -} - -TEST(StructuredHeaderGeneratedTest, ParseList) { - for (const auto& c : list_test_cases) { - if (c.raw) { - SCOPED_TRACE(c.name); - std::string raw{c.raw, c.raw_len}; - absl::optional<List> result = ParseList(raw); - EXPECT_EQ(result, c.expected); - } - } -} - -TEST(StructuredHeaderGeneratedTest, ParseDictionary) { - for (const auto& c : dictionary_test_cases) { - if (c.raw) { - SCOPED_TRACE(c.name); - std::string raw{c.raw, c.raw_len}; - absl::optional<Dictionary> result = ParseDictionary(raw); - EXPECT_EQ(result, c.expected); - } - } -} - -TEST(StructuredHeaderGeneratedTest, SerializeItem) { - for (const auto& c : parameterized_item_test_cases) { - SCOPED_TRACE(c.name); - if (c.expected) { - absl::optional<std::string> result = SerializeItem(*c.expected); - if (c.raw || c.canonical) { - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), - std::string(c.canonical ? c.canonical : c.raw)); - } else { - EXPECT_FALSE(result.has_value()); - } - } - } -} - -TEST(StructuredHeaderGeneratedTest, SerializeList) { - for (const auto& c : list_test_cases) { - SCOPED_TRACE(c.name); - if (c.expected) { - absl::optional<std::string> result = SerializeList(*c.expected); - if (c.raw || c.canonical) { - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), - std::string(c.canonical ? c.canonical : c.raw)); - } else { - EXPECT_FALSE(result.has_value()); - } - } - } -} - -TEST(StructuredHeaderGeneratedTest, SerializeDictionary) { - for (const auto& c : dictionary_test_cases) { - SCOPED_TRACE(c.name); - if (c.expected) { - absl::optional<std::string> result = SerializeDictionary(*c.expected); - if (c.raw || c.canonical) { - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), - std::string(c.canonical ? c.canonical : c.raw)); - } else { - EXPECT_FALSE(result.has_value()); - } - } - } -} - -} // namespace structured_headers -} // namespace net diff --git a/chromium/net/http/structured_headers_unittest.cc b/chromium/net/http/structured_headers_unittest.cc deleted file mode 100644 index d2848b2b523..00000000000 --- a/chromium/net/http/structured_headers_unittest.cc +++ /dev/null @@ -1,748 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/http/structured_headers.h" - -#include <math.h> - -#include <limits> -#include <string> - -#include "testing/gtest/include/gtest/gtest.h" - -namespace net { -namespace structured_headers { -namespace { - -// Helpers to make test cases clearer - -Item Token(std::string value) { - return Item(value, Item::kTokenType); -} - -Item Integer(int64_t value) { - return Item(value); -} - -// Parameter with null value, only used in Structured Headers Draft 09 -std::pair<std::string, Item> NullParam(std::string key) { - return std::make_pair(key, Item()); -} - -std::pair<std::string, Item> BooleanParam(std::string key, bool value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> DoubleParam(std::string key, double value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> Param(std::string key, int64_t value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> Param(std::string key, std::string value) { - return std::make_pair(key, Item(value)); -} - -std::pair<std::string, Item> ByteSequenceParam(std::string key, - std::string value) { - return std::make_pair(key, Item(value, Item::kByteSequenceType)); -} - -std::pair<std::string, Item> TokenParam(std::string key, std::string value) { - return std::make_pair(key, Token(value)); -} - -// Test cases taken from https://github.com/httpwg/structured-header-tests can -// be found in structured_headers_generated_unittest.cc - -const struct ItemTestCase { - const char* name; - const char* raw; - const absl::optional<Item> expected; // nullopt if parse error is expected. - const char* canonical; // nullptr if parse error is expected, or if canonical - // format is identical to raw. -} item_test_cases[] = { - // Token - {"bad token - item", "abc$@%!", absl::nullopt}, - {"leading whitespace", " foo", Token("foo"), "foo"}, - {"trailing whitespace", "foo ", Token("foo"), "foo"}, - {"leading asterisk", "*foo", Token("*foo")}, - // Number - {"long integer", "999999999999999", Integer(999999999999999L)}, - {"long negative integer", "-999999999999999", Integer(-999999999999999L)}, - {"too long integer", "1000000000000000", absl::nullopt}, - {"negative too long integer", "-1000000000000000", absl::nullopt}, - {"integral decimal", "1.0", Item(1.0)}, - // String - {"basic string", "\"foo\"", Item("foo")}, - {"non-ascii string", "\"f\xC3\xBC\xC3\xBC\"", absl::nullopt}, - // Additional tests - {"valid quoting containing \\n", "\"\\\\n\"", Item("\\n")}, - {"valid quoting containing \\t", "\"\\\\t\"", Item("\\t")}, - {"valid quoting containing \\x", "\"\\\\x61\"", Item("\\x61")}, - {"c-style hex escape in string", "\"\\x61\"", absl::nullopt}, - {"valid quoting containing \\u", "\"\\\\u0061\"", Item("\\u0061")}, - {"c-style unicode escape in string", "\"\\u0061\"", absl::nullopt}, -}; - -const ItemTestCase sh09_item_test_cases[] = { - // Integer - {"large integer", "9223372036854775807", Integer(9223372036854775807L)}, - {"large negative integer", "-9223372036854775807", - Integer(-9223372036854775807L)}, - {"too large integer", "9223372036854775808", absl::nullopt}, - {"too large negative integer", "-9223372036854775808", absl::nullopt}, - // Byte Sequence - {"basic binary", "*aGVsbG8=*", Item("hello", Item::kByteSequenceType)}, - {"empty binary", "**", Item("", Item::kByteSequenceType)}, - {"bad paddding", "*aGVsbG8*", Item("hello", Item::kByteSequenceType), - "*aGVsbG8=*"}, - {"bad end delimiter", "*aGVsbG8=", absl::nullopt}, - {"extra whitespace", "*aGVsb G8=*", absl::nullopt}, - {"extra chars", "*aGVsbG!8=*", absl::nullopt}, - {"suffix chars", "*aGVsbG8=!*", absl::nullopt}, - {"non-zero pad bits", "*iZ==*", Item("\x89", Item::kByteSequenceType), - "*iQ==*"}, - {"non-ASCII binary", "*/+Ah*", Item("\xFF\xE0!", Item::kByteSequenceType)}, - {"base64url binary", "*_-Ah*", absl::nullopt}, - {"token with leading asterisk", "*foo", absl::nullopt}, -}; - -// For Structured Headers Draft 15 -const struct ParameterizedItemTestCase { - const char* name; - const char* raw; - const absl::optional<ParameterizedItem> - expected; // nullopt if parse error is expected. - const char* canonical; // nullptr if parse error is expected, or if canonical - // format is identical to raw. -} parameterized_item_test_cases[] = { - {"single parameter item", - "text/html;q=1.0", - {{Token("text/html"), {DoubleParam("q", 1)}}}}, - {"missing parameter value item", - "text/html;a;q=1.0", - {{Token("text/html"), {BooleanParam("a", true), DoubleParam("q", 1)}}}}, - {"missing terminal parameter value item", - "text/html;q=1.0;a", - {{Token("text/html"), {DoubleParam("q", 1), BooleanParam("a", true)}}}}, - {"duplicate parameter keys with different value", - "text/html;a=1;b=2;a=3.0", - {{Token("text/html"), {DoubleParam("a", 3), Param("b", 2L)}}}, - "text/html;a=3.0;b=2"}, - {"multiple duplicate parameter keys at different position", - "text/html;c=1;a=2;b;b=3.0;a", - {{Token("text/html"), - {Param("c", 1L), BooleanParam("a", true), DoubleParam("b", 3)}}}, - "text/html;c=1;a;b=3.0"}, - {"duplicate parameter keys with missing value", - "text/html;a;a=1", - {{Token("text/html"), {Param("a", 1L)}}}, - "text/html;a=1"}, - {"whitespace before = parameterised item", "text/html, text/plain;q =0.5", - absl::nullopt}, - {"whitespace after = parameterised item", "text/html, text/plain;q= 0.5", - absl::nullopt}, - {"whitespace before ; parameterised item", "text/html, text/plain ;q=0.5", - absl::nullopt}, - {"whitespace after ; parameterised item", - "text/plain; q=0.5", - {{Token("text/plain"), {DoubleParam("q", 0.5)}}}, - "text/plain;q=0.5"}, - {"extra whitespace parameterised item", - "text/plain; q=0.5; charset=utf-8", - {{Token("text/plain"), - {DoubleParam("q", 0.5), TokenParam("charset", "utf-8")}}}, - "text/plain;q=0.5;charset=utf-8"}, -}; - -// For Structured Headers Draft 15 -const struct ListTestCase { - const char* name; - const char* raw; - const absl::optional<List> expected; // nullopt if parse error is expected. - const char* canonical; // nullptr if parse error is expected, or if canonical - // format is identical to raw. -} list_test_cases[] = { - // Lists of lists - {"extra whitespace list of lists", - "(1 42)", - {{{{{Integer(1L), {}}, {Integer(42L), {}}}, {}}}}, - "(1 42)"}, - // Parameterized Lists - {"basic parameterised list", - "abc_123;a=1;b=2; cdef_456, ghi;q=\"9\";r=\"+w\"", - {{{Token("abc_123"), - {Param("a", 1), Param("b", 2), BooleanParam("cdef_456", true)}}, - {Token("ghi"), {Param("q", "9"), Param("r", "+w")}}}}, - "abc_123;a=1;b=2;cdef_456, ghi;q=\"9\";r=\"+w\""}, - // Parameterized inner lists - {"parameterised basic list of lists", - "(1;a=1.0 2), (42 43)", - {{{{{Integer(1L), {DoubleParam("a", 1.0)}}, {Integer(2L), {}}}, {}}, - {{{Integer(42L), {}}, {Integer(43L), {}}}, {}}}}}, - {"parameters on inner members", - "(1;a=1.0 2;b=c), (42;d=?0 43;e=:Zmdo:)", - {{{{{Integer(1L), {DoubleParam("a", 1.0)}}, - {Integer(2L), {TokenParam("b", "c")}}}, - {}}, - {{{Integer(42L), {BooleanParam("d", false)}}, - {Integer(43L), {ByteSequenceParam("e", "fgh")}}}, - {}}}}}, - {"parameters on inner lists", - "(1 2);a=1.0, (42 43);b=?0", - {{{{{Integer(1L), {}}, {Integer(2L), {}}}, {DoubleParam("a", 1.0)}}, - {{{Integer(42L), {}}, {Integer(43L), {}}}, - {BooleanParam("b", false)}}}}}, - {"default true values for parameters on inner list members", - "(1;a 2), (42 43;b)", - {{{{{Integer(1L), {BooleanParam("a", true)}}, {Integer(2L), {}}}, {}}, - {{{Integer(42L), {}}, {Integer(43L), {BooleanParam("b", true)}}}, {}}}}}, - {"default true values for parameters on inner lists", - "(1 2);a, (42 43);b", - {{{{{Integer(1L), {}}, {Integer(2L), {}}}, {BooleanParam("a", true)}}, - {{{Integer(42L), {}}, {Integer(43L), {}}}, {BooleanParam("b", true)}}}}}, - {"extra whitespace before semicolon in parameters on inner list member", - "(a;b ;c b)", absl::nullopt}, - {"extra whitespace between parameters on inner list member", - "(a;b; c b)", - {{{{{Token("a"), {BooleanParam("b", true), BooleanParam("c", true)}}, - {Token("b"), {}}}, - {}}}}, - "(a;b;c b)"}, - {"extra whitespace before semicolon in parameters on inner list", - "(a b);c ;d, (e)", absl::nullopt}, - {"extra whitespace between parameters on inner list", - "(a b);c; d, (e)", - {{{{{Token("a"), {}}, {Token("b"), {}}}, - {BooleanParam("c", true), BooleanParam("d", true)}}, - {{{Token("e"), {}}}, {}}}}, - "(a b);c;d, (e)"}, -}; - -// For Structured Headers Draft 15 -const struct DictionaryTestCase { - const char* name; - const char* raw; - const absl::optional<Dictionary> - expected; // nullopt if parse error is expected. - const char* canonical; // nullptr if parse error is expected, or if canonical - // format is identical to raw. -} dictionary_test_cases[] = { - {"basic dictionary", - "en=\"Applepie\", da=:aGVsbG8=:", - {Dictionary{{{"en", {Item("Applepie"), {}}}, - {"da", {Item("hello", Item::kByteSequenceType), {}}}}}}}, - {"tab separated dictionary", - "a=1\t,\tb=2", - {Dictionary{{{"a", {Integer(1L), {}}}, {"b", {Integer(2L), {}}}}}}, - "a=1, b=2"}, - {"missing value with params dictionary", - "a=1, b;foo=9, c=3", - {Dictionary{{{"a", {Integer(1L), {}}}, - {"b", {Item(true), {Param("foo", 9)}}}, - {"c", {Integer(3L), {}}}}}}}, - // Parameterised dictionary tests - {"parameterised inner list member dict", - "a=(\"1\";b=1;c=?0 \"2\");d=\"e\"", - {Dictionary{{{"a", - {{{Item("1"), {Param("b", 1), BooleanParam("c", false)}}, - {Item("2"), {}}}, - {Param("d", "e")}}}}}}}, - {"explicit true value with parameter", - "a=?1;b=1", - {Dictionary{{{"a", {Item(true), {Param("b", 1)}}}}}}, - "a;b=1"}, - {"implicit true value with parameter", - "a;b=1", - {Dictionary{{{"a", {Item(true), {Param("b", 1)}}}}}}}, - {"implicit true value with implicitly-valued parameter", - "a;b", - {Dictionary{{{"a", {Item(true), {BooleanParam("b", true)}}}}}}}, -}; -} // namespace - -TEST(StructuredHeaderTest, ParseBareItem) { - for (const auto& c : item_test_cases) { - SCOPED_TRACE(c.name); - absl::optional<Item> result = ParseBareItem(c.raw); - EXPECT_EQ(result, c.expected); - } -} - -// For Structured Headers Draft 15, these tests include parameters on Items. -TEST(StructuredHeaderTest, ParseItem) { - for (const auto& c : parameterized_item_test_cases) { - SCOPED_TRACE(c.name); - absl::optional<ParameterizedItem> result = ParseItem(c.raw); - EXPECT_EQ(result, c.expected); - } -} - -// Structured Headers Draft 9 parsing rules are different than Draft 15, and -// some strings which are considered invalid in SH15 should parse in SH09. -// The SH09 Item parser is not directly exposed, but can be used indirectly by -// calling the parser for SH09-specific lists. -TEST(StructuredHeaderTest, ParseSH09Item) { - for (const auto& c : sh09_item_test_cases) { - SCOPED_TRACE(c.name); - absl::optional<ListOfLists> result = ParseListOfLists(c.raw); - if (c.expected.has_value()) { - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result->size(), 1UL); - EXPECT_EQ((*result)[0].size(), 1UL); - EXPECT_EQ((*result)[0][0], c.expected); - } else { - EXPECT_FALSE(result.has_value()); - } - } -} - -// In Structured Headers Draft 9, floats can have more than three fractional -// digits, and can be larger than 1e12. This behaviour is exposed in the parser -// for SH09-specific lists, so test it through that interface. -TEST(StructuredHeaderTest, SH09HighPrecisionFloats) { - // These values are exactly representable in binary floating point, so no - // accuracy issues are expected in this test. - absl::optional<ListOfLists> result = - ParseListOfLists("1.03125;-1.03125;12345678901234.5;-12345678901234.5"); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, - (ListOfLists{{Item(1.03125), Item(-1.03125), Item(12345678901234.5), - Item(-12345678901234.5)}})); - - result = ParseListOfLists("123456789012345.0"); - EXPECT_FALSE(result.has_value()); - - result = ParseListOfLists("-123456789012345.0"); - EXPECT_FALSE(result.has_value()); -} - -// For Structured Headers Draft 9 -TEST(StructuredHeaderTest, ParseListOfLists) { - static const struct TestCase { - const char* name; - const char* raw; - ListOfLists expected; // empty if parse error is expected - } cases[] = { - {"basic list of lists", - "1;2, 42;43", - {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}}, - {"empty list of lists", "", {}}, - {"single item list of lists", "42", {{Integer(42L)}}}, - {"no whitespace list of lists", "1,42", {{Integer(1L)}, {Integer(42L)}}}, - {"no inner whitespace list of lists", - "1;2, 42;43", - {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}}, - {"extra whitespace list of lists", - "1 , 42", - {{Integer(1L)}, {Integer(42L)}}}, - {"extra inner whitespace list of lists", - "1 ; 2,42 ; 43", - {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}}, - {"trailing comma list of lists", "1;2, 42,", {}}, - {"trailing semicolon list of lists", "1;2, 42;43;", {}}, - {"leading comma list of lists", ",1;2, 42", {}}, - {"leading semicolon list of lists", ";1;2, 42;43", {}}, - {"empty item list of lists", "1,,42", {}}, - {"empty inner item list of lists", "1;;2,42", {}}, - }; - for (const auto& c : cases) { - SCOPED_TRACE(c.name); - absl::optional<ListOfLists> result = ParseListOfLists(c.raw); - if (!c.expected.empty()) { - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(*result, c.expected); - } else { - EXPECT_FALSE(result.has_value()); - } - } -} - -// For Structured Headers Draft 9 -TEST(StructuredHeaderTest, ParseParameterisedList) { - static const struct TestCase { - const char* name; - const char* raw; - ParameterisedList expected; // empty if parse error is expected - } cases[] = { - {"basic param-list", - "abc_123;a=1;b=2; cdef_456, ghi;q=\"9\";r=\"w\"", - { - {Token("abc_123"), - {Param("a", 1), Param("b", 2), NullParam("cdef_456")}}, - {Token("ghi"), {Param("q", "9"), Param("r", "w")}}, - }}, - {"empty param-list", "", {}}, - {"single item param-list", - "text/html;q=1", - {{Token("text/html"), {Param("q", 1)}}}}, - {"empty param-list", "", {}}, - {"no whitespace param-list", - "text/html,text/plain;q=1", - {{Token("text/html"), {}}, {Token("text/plain"), {Param("q", 1)}}}}, - {"whitespace before = param-list", "text/html, text/plain;q =1", {}}, - {"whitespace after = param-list", "text/html, text/plain;q= 1", {}}, - {"extra whitespace param-list", - "text/html , text/plain ; q=1", - {{Token("text/html"), {}}, {Token("text/plain"), {Param("q", 1)}}}}, - {"duplicate key", "abc;a=1;b=2;a=1", {}}, - {"numeric key", "abc;a=1;1b=2;c=1", {}}, - {"uppercase key", "abc;a=1;B=2;c=1", {}}, - {"bad key", "abc;a=1;b!=2;c=1", {}}, - {"another bad key", "abc;a=1;b==2;c=1", {}}, - {"empty key name", "abc;a=1;=2;c=1", {}}, - {"empty parameter", "abc;a=1;;c=1", {}}, - {"empty list item", "abc;a=1,,def;b=1", {}}, - {"extra semicolon", "abc;a=1;b=1;", {}}, - {"extra comma", "abc;a=1,def;b=1,", {}}, - {"leading semicolon", ";abc;a=1", {}}, - {"leading comma", ",abc;a=1", {}}, - }; - for (const auto& c : cases) { - SCOPED_TRACE(c.name); - absl::optional<ParameterisedList> result = ParseParameterisedList(c.raw); - if (c.expected.empty()) { - EXPECT_FALSE(result.has_value()); - continue; - } - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result->size(), c.expected.size()); - if (result->size() == c.expected.size()) { - for (size_t i = 0; i < c.expected.size(); ++i) - EXPECT_EQ((*result)[i], c.expected[i]); - } - } -} - -// For Structured Headers Draft 15 -TEST(StructuredHeaderTest, ParseList) { - for (const auto& c : list_test_cases) { - SCOPED_TRACE(c.name); - absl::optional<List> result = ParseList(c.raw); - EXPECT_EQ(result, c.expected); - } -} - -// For Structured Headers Draft 15 -TEST(StructuredHeaderTest, ParseDictionary) { - for (const auto& c : dictionary_test_cases) { - SCOPED_TRACE(c.name); - absl::optional<Dictionary> result = ParseDictionary(c.raw); - EXPECT_EQ(result, c.expected); - } -} - -// Serializer tests are all exclusively for Structured Headers Draft 15 - -TEST(StructuredHeaderTest, SerializeItem) { - for (const auto& c : item_test_cases) { - SCOPED_TRACE(c.name); - if (c.expected) { - absl::optional<std::string> result = SerializeItem(*c.expected); - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw)); - } - } -} - -TEST(StructuredHeaderTest, SerializeParameterizedItem) { - for (const auto& c : parameterized_item_test_cases) { - SCOPED_TRACE(c.name); - if (c.expected) { - absl::optional<std::string> result = SerializeItem(*c.expected); - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw)); - } - } -} - -TEST(StructuredHeaderTest, UnserializableItems) { - // Test that items with unknown type are not serialized. - EXPECT_FALSE(SerializeItem(Item()).has_value()); -} - -TEST(StructuredHeaderTest, UnserializableTokens) { - static const struct UnserializableString { - const char* name; - const char* value; - } bad_tokens[] = { - {"empty token", ""}, - {"contains high ascii", "a\xff"}, - {"contains nonprintable character", "a\x7f"}, - {"contains C0", "a\x01"}, - {"UTF-8 encoded", "a\xc3\xa9"}, - {"contains TAB", "a\t"}, - {"contains LF", "a\n"}, - {"contains CR", "a\r"}, - {"contains SP", "a "}, - {"begins with digit", "9token"}, - {"begins with hyphen", "-token"}, - {"begins with LF", "\ntoken"}, - {"begins with SP", " token"}, - {"begins with colon", ":token"}, - {"begins with percent", "%token"}, - {"begins with period", ".token"}, - {"begins with slash", "/token"}, - }; - for (const auto& bad_token : bad_tokens) { - SCOPED_TRACE(bad_token.name); - absl::optional<std::string> serialization = - SerializeItem(Token(bad_token.value)); - EXPECT_FALSE(serialization.has_value()) << *serialization; - } -} - -TEST(StructuredHeaderTest, UnserializableKeys) { - static const struct UnserializableString { - const char* name; - const char* value; - } bad_keys[] = { - {"empty key", ""}, - {"contains high ascii", "a\xff"}, - {"contains nonprintable character", "a\x7f"}, - {"contains C0", "a\x01"}, - {"UTF-8 encoded", "a\xc3\xa9"}, - {"contains TAB", "a\t"}, - {"contains LF", "a\n"}, - {"contains CR", "a\r"}, - {"contains SP", "a "}, - {"begins with uppercase", "Atoken"}, - {"begins with digit", "9token"}, - {"begins with hyphen", "-token"}, - {"begins with LF", "\ntoken"}, - {"begins with SP", " token"}, - {"begins with colon", ":token"}, - {"begins with percent", "%token"}, - {"begins with period", ".token"}, - {"begins with slash", "/token"}, - }; - for (const auto& bad_key : bad_keys) { - SCOPED_TRACE(bad_key.name); - absl::optional<std::string> serialization = - SerializeItem(ParameterizedItem("a", {{bad_key.value, "a"}})); - EXPECT_FALSE(serialization.has_value()) << *serialization; - } -} - -TEST(StructuredHeaderTest, UnserializableStrings) { - static const struct UnserializableString { - const char* name; - const char* value; - } bad_strings[] = { - {"contains high ascii", "a\xff"}, - {"contains nonprintable character", "a\x7f"}, - {"UTF-8 encoded", "a\xc3\xa9"}, - {"contains TAB", "a\t"}, - {"contains LF", "a\n"}, - {"contains CR", "a\r"}, - {"contains C0", "a\x01"}, - }; - for (const auto& bad_string : bad_strings) { - SCOPED_TRACE(bad_string.name); - absl::optional<std::string> serialization = - SerializeItem(Item(bad_string.value)); - EXPECT_FALSE(serialization.has_value()) << *serialization; - } -} - -TEST(StructuredHeaderTest, UnserializableIntegers) { - EXPECT_FALSE(SerializeItem(Integer(1e15L)).has_value()); - EXPECT_FALSE(SerializeItem(Integer(-1e15L)).has_value()); -} - -TEST(StructuredHeaderTest, UnserializableDecimals) { - for (double value : - {std::numeric_limits<double>::quiet_NaN(), - std::numeric_limits<double>::infinity(), - -std::numeric_limits<double>::infinity(), 1e12, 1e12 - 0.0001, - 1e12 - 0.0005, -1e12, -1e12 + 0.0001, -1e12 + 0.0005}) { - auto x = SerializeItem(Item(value)); - EXPECT_FALSE(SerializeItem(Item(value)).has_value()); - } -} - -// These values cannot be directly parsed from headers, but are valid doubles -// which can be serialized as sh-floats (though rounding is expected.) -TEST(StructuredHeaderTest, SerializeUnparseableDecimals) { - struct UnparseableDecimal { - const char* name; - double value; - const char* canonical; - } float_test_cases[] = { - {"negative 0", -0.0, "0.0"}, - {"0.0001", 0.0001, "0.0"}, - {"0.0000001", 0.0000001, "0.0"}, - {"1.0001", 1.0001, "1.0"}, - {"1.0009", 1.0009, "1.001"}, - {"round positive odd decimal", 0.0015, "0.002"}, - {"round positive even decimal", 0.0025, "0.002"}, - {"round negative odd decimal", -0.0015, "-0.002"}, - {"round negative even decimal", -0.0025, "-0.002"}, - {"round decimal up to integer part", 9.9995, "10.0"}, - {"subnormal numbers", std::numeric_limits<double>::denorm_min(), "0.0"}, - {"round up to 10 digits", 1e9 - 0.0000001, "1000000000.0"}, - {"round up to 11 digits", 1e10 - 0.000001, "10000000000.0"}, - {"round up to 12 digits", 1e11 - 0.00001, "100000000000.0"}, - {"largest serializable float", nextafter(1e12 - 0.0005, 0), - "999999999999.999"}, - {"largest serializable negative float", -nextafter(1e12 - 0.0005, 0), - "-999999999999.999"}, - // This will fail if we simply truncate the fractional portion. - {"float rounds up to next int", 3.9999999, "4.0"}, - // This will fail if we first round to >3 digits, and then round again to - // 3 digits. - {"don't double round", 3.99949, "3.999"}, - // This will fail if we first round to 3 digits, and then round again to - // max_avail_digits. - {"don't double round", 123456789.99949, "123456789.999"}, - }; - for (const auto& test_case : float_test_cases) { - SCOPED_TRACE(test_case.name); - absl::optional<std::string> serialization = - SerializeItem(Item(test_case.value)); - EXPECT_TRUE(serialization.has_value()); - EXPECT_EQ(*serialization, test_case.canonical); - } -} - -TEST(StructuredHeaderTest, SerializeList) { - for (const auto& c : list_test_cases) { - SCOPED_TRACE(c.name); - if (c.expected) { - absl::optional<std::string> result = SerializeList(*c.expected); - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw)); - } - } -} - -TEST(StructuredHeaderTest, UnserializableLists) { - static const struct UnserializableList { - const char* name; - const List value; - } bad_lists[] = { - {"Null item as member", {{Item(), {}}}}, - {"Unserializable item as member", {{Token("\n"), {}}}}, - {"Key is empty", {{Token("abc"), {Param("", 1)}}}}, - {"Key contains whitespace", {{Token("abc"), {Param("a\n", 1)}}}}, - {"Key contains UTF8", {{Token("abc"), {Param("a\xc3\xa9", 1)}}}}, - {"Key contains unprintable characters", - {{Token("abc"), {Param("a\x7f", 1)}}}}, - {"Key contains disallowed characters", - {{Token("abc"), {Param("a:", 1)}}}}, - {"Param value is unserializable", {{Token("abc"), {{"a", Token("\n")}}}}}, - {"Inner list contains unserializable item", - {{std::vector<ParameterizedItem>{{Token("\n"), {}}}, {}}}}, - }; - for (const auto& bad_list : bad_lists) { - SCOPED_TRACE(bad_list.name); - absl::optional<std::string> serialization = SerializeList(bad_list.value); - EXPECT_FALSE(serialization.has_value()) << *serialization; - } -} - -TEST(StructuredHeaderTest, SerializeDictionary) { - for (const auto& c : dictionary_test_cases) { - SCOPED_TRACE(c.name); - if (c.expected) { - absl::optional<std::string> result = SerializeDictionary(*c.expected); - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw)); - } - } -} - -TEST(StructuredHeaderTest, DictionaryConstructors) { - const std::string key0 = "key0"; - const std::string key1 = "key1"; - const ParameterizedMember member0{Item("Applepie"), {}}; - const ParameterizedMember member1{Item("hello", Item::kByteSequenceType), {}}; - - Dictionary dict; - EXPECT_TRUE(dict.empty()); - EXPECT_EQ(0U, dict.size()); - dict[key0] = member0; - EXPECT_FALSE(dict.empty()); - EXPECT_EQ(1U, dict.size()); - - const Dictionary dict_copy = dict; - EXPECT_FALSE(dict_copy.empty()); - EXPECT_EQ(1U, dict_copy.size()); - EXPECT_EQ(dict, dict_copy); - - const Dictionary dict_init{{{key0, member0}, {key1, member1}}}; - EXPECT_FALSE(dict_init.empty()); - EXPECT_EQ(2U, dict_init.size()); - EXPECT_EQ(member0, dict_init.at(key0)); - EXPECT_EQ(member1, dict_init.at(key1)); -} - -TEST(StructuredHeaderTest, DictionaryAccessors) { - const std::string key0 = "key0"; - const std::string key1 = "key1"; - - const ParameterizedMember nonempty_member0{Item("Applepie"), {}}; - const ParameterizedMember nonempty_member1{ - Item("hello", Item::kByteSequenceType), {}}; - const ParameterizedMember empty_member; - - Dictionary dict{{{key0, nonempty_member0}}}; - EXPECT_TRUE(dict.contains(key0)); - EXPECT_EQ(nonempty_member0, dict[key0]); - EXPECT_EQ(&dict[key0], &dict.at(key0)); - EXPECT_EQ(&dict[key0], &dict[0]); - EXPECT_EQ(&dict[key0], &dict.at(0)); - - // Even if the key does not yet exist in |dict|, operator[]() should - // automatically create an empty entry. - ASSERT_FALSE(dict.contains(key1)); - ParameterizedMember& member1 = dict[key1]; - EXPECT_TRUE(dict.contains(key1)); - EXPECT_EQ(empty_member, member1); - EXPECT_EQ(&member1, &dict[key1]); - EXPECT_EQ(&member1, &dict.at(key1)); - EXPECT_EQ(&member1, &dict[1]); - EXPECT_EQ(&member1, &dict.at(1)); - - member1 = nonempty_member1; - EXPECT_EQ(nonempty_member1, dict[key1]); - EXPECT_EQ(&dict[key1], &dict.at(key1)); - EXPECT_EQ(&dict[key1], &dict[1]); - EXPECT_EQ(&dict[key1], &dict.at(1)); - - // at(StringPiece) and indexed accessors have const overloads. - const Dictionary& dict_ref = dict; - EXPECT_EQ(&member1, &dict_ref.at(key1)); - EXPECT_EQ(&member1, &dict_ref[1]); - EXPECT_EQ(&member1, &dict_ref.at(1)); -} - -TEST(StructuredHeaderTest, UnserializableDictionary) { - static const struct UnserializableDictionary { - const char* name; - const Dictionary value; - } bad_dictionaries[] = { - {"Unserializable dict key", Dictionary{{{"ABC", {Token("abc"), {}}}}}}, - {"Dictionary item is unserializable", - Dictionary{{{"abc", {Token("abc="), {}}}}}}, - {"Param value is unserializable", - Dictionary{{{"abc", {Token("abc"), {{"a", Token("\n")}}}}}}}, - {"Dictionary inner-list contains unserializable item", - Dictionary{ - {{"abc", - {std::vector<ParameterizedItem>{{Token("abc="), {}}}, {}}}}}}, - }; - for (const auto& bad_dictionary : bad_dictionaries) { - SCOPED_TRACE(bad_dictionary.name); - absl::optional<std::string> serialization = - SerializeDictionary(bad_dictionary.value); - EXPECT_FALSE(serialization.has_value()) << *serialization; - } -} - -} // namespace structured_headers -} // namespace net diff --git a/chromium/net/http/transport_security_state.cc b/chromium/net/http/transport_security_state.cc index 340b2e49df2..25c3764df23 100644 --- a/chromium/net/http/transport_security_state.cc +++ b/chromium/net/http/transport_security_state.cc @@ -404,17 +404,13 @@ TransportSecurityState::TransportSecurityState() TransportSecurityState::TransportSecurityState( std::vector<std::string> hsts_host_bypass_list) - : enable_static_pins_(true), - enable_static_expect_ct_(true), - enable_pkp_bypass_for_local_trust_anchors_(true), - sent_hpkp_reports_cache_(kMaxReportCacheEntries), + : sent_hpkp_reports_cache_(kMaxReportCacheEntries), sent_expect_ct_reports_cache_(kMaxReportCacheEntries), key_expect_ct_by_nik_(base::FeatureList::IsEnabled( features::kPartitionExpectCTStateByNetworkIsolationKey)) { // Static pinning is only enabled for official builds to make sure that // others don't end up with pins that cannot be easily updated. -#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || BUILDFLAG(IS_ANDROID) || \ - BUILDFLAG(IS_IOS) +#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || BUILDFLAG(IS_IOS) enable_static_pins_ = false; enable_static_expect_ct_ = false; #endif @@ -1179,8 +1175,10 @@ bool TransportSecurityState::GetStaticPKPState(const std::string& host, PKPState* pkp_result) const { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - if (!enable_static_pins_ || !IsStaticPKPListTimely()) + if (!enable_static_pins_ || !IsStaticPKPListTimely() || + !base::FeatureList::IsEnabled(features::kStaticKeyPinningEnforcement)) { return false; + } PreloadResult result; if (host_pins_.has_value()) { @@ -1387,9 +1385,7 @@ void TransportSecurityState::AddOrUpdateEnabledExpectCTHosts( hashed_host, network_isolation_key)] = state; } -TransportSecurityState::STSState::STSState() - : upgrade_mode(MODE_DEFAULT), include_subdomains(false) { -} +TransportSecurityState::STSState::STSState() = default; TransportSecurityState::STSState::~STSState() = default; @@ -1405,14 +1401,13 @@ TransportSecurityState::STSStateIterator::STSStateIterator( TransportSecurityState::STSStateIterator::~STSStateIterator() = default; -TransportSecurityState::PKPState::PKPState() : include_subdomains(false) { -} +TransportSecurityState::PKPState::PKPState() = default; TransportSecurityState::PKPState::PKPState(const PKPState& other) = default; TransportSecurityState::PKPState::~PKPState() = default; -TransportSecurityState::ExpectCTState::ExpectCTState() : enforce(false) {} +TransportSecurityState::ExpectCTState::ExpectCTState() = default; TransportSecurityState::ExpectCTState::~ExpectCTState() = default; @@ -1641,10 +1636,18 @@ bool TransportSecurityState::IsCTLogListTimely() const { } bool TransportSecurityState::IsStaticPKPListTimely() const { + if (pins_list_always_timely_for_testing_) { + return true; + } + // If the list has not been updated via component updater, freshness depends - // on build freshness. + // on the compiled-in list freshness. if (!host_pins_.has_value()) { - return IsBuildTimely(); +#if BUILDFLAG(INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST) + return (base::Time::Now() - kPinsListTimestamp).InDays() < 70; +#else + return false; +#endif } DCHECK(!key_pins_list_last_update_time_.is_null()); // Else, we use the last update time. diff --git a/chromium/net/http/transport_security_state.h b/chromium/net/http/transport_security_state.h index e8d50dbf23a..f8b68821c48 100644 --- a/chromium/net/http/transport_security_state.h +++ b/chromium/net/http/transport_security_state.h @@ -125,10 +125,10 @@ class NET_EXPORT TransportSecurityState { // expires. base::Time expiry; - UpgradeMode upgrade_mode; + UpgradeMode upgrade_mode = MODE_DEFAULT; // Are subdomains subject to this policy state? - bool include_subdomains; + bool include_subdomains = false; // The domain which matched during a search for this STSState entry. // Updated by |GetDynamicSTSState| and |GetStaticDomainState|. @@ -191,7 +191,7 @@ class NET_EXPORT TransportSecurityState { HashValueVector bad_spki_hashes; // Are subdomains subject to this policy state? - bool include_subdomains; + bool include_subdomains = false; // The domain which matched during a search for this DomainState entry. // Updated by |GetDynamicPKPState| and |GetStaticDomainState|. @@ -239,7 +239,7 @@ class NET_EXPORT TransportSecurityState { // True if connections should be closed if they do not comply with the CT // policy. If false, noncompliant connections will be allowed but reports // will be sent about the violation. - bool enforce; + bool enforce = false; // The absolute time (UTC) when the Expect-CT state was last observed. base::Time last_observed; // The absolute time (UTC) when the Expect-CT state expires. @@ -642,6 +642,12 @@ class NET_EXPORT TransportSecurityState { // The number of cached ExpectCTState entries. size_t num_expect_ct_entries_for_testing() const; + // Sets whether pinning list timestamp freshness should be ignored for + // testing. + void SetPinningListAlwaysTimelyForTesting(bool always_timely) { + pins_list_always_timely_for_testing_ = always_timely; + } + // The number of cached STSState entries. size_t num_sts_entries() const; @@ -773,13 +779,13 @@ class NET_EXPORT TransportSecurityState { raw_ptr<ReportSenderInterface> report_sender_ = nullptr; // True if static pins should be used. - bool enable_static_pins_; + bool enable_static_pins_ = true; // True if static expect-CT state should be used. - bool enable_static_expect_ct_; + bool enable_static_expect_ct_ = true; // True if public key pinning bypass is enabled for local trust anchors. - bool enable_pkp_bypass_for_local_trust_anchors_; + bool enable_pkp_bypass_for_local_trust_anchors_ = true; raw_ptr<ExpectCTReporter> expect_ct_reporter_ = nullptr; @@ -813,6 +819,8 @@ class NET_EXPORT TransportSecurityState { base::Time key_pins_list_last_update_time_; std::vector<PinSet> pinsets_; + bool pins_list_always_timely_for_testing_ = false; + THREAD_CHECKER(thread_checker_); }; diff --git a/chromium/net/http/transport_security_state_static.json.gz b/chromium/net/http/transport_security_state_static.json.gz Binary files differindex 4aadbc25d28..67961946223 100644 --- a/chromium/net/http/transport_security_state_static.json.gz +++ b/chromium/net/http/transport_security_state_static.json.gz diff --git a/chromium/net/http/transport_security_state_static.pins b/chromium/net/http/transport_security_state_static.pins index 27610e1a6a2..42af336fbf1 100644 --- a/chromium/net/http/transport_security_state_static.pins +++ b/chromium/net/http/transport_security_state_static.pins @@ -43,6 +43,10 @@ # hash function for preloaded entries again (we have already done so once). # +# Last updated: 2022-08-31 12:54 UTC +PinsListTimestamp +1661950496 + TestSPKI sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= @@ -1571,23 +1575,6 @@ aFE/tQpQ/YVB8aQ9+2vGoBDv9bFF+bxMmQyFtST3SUvKLhgeRqLraEd8OPfEizYl PwIDAQAB -----END PUBLIC KEY----- -YahooBackup1 ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1vZXDjNejqvtVvyYcP0 -7ubiG0RW1sar002td1qI7Zo9Lz8kZtrAUJrBXGh6bbpovsIwWkUjirUQk2b3M7Ob -bgQWVGGeQlrVUUjTMth4Cg5p2Xj8b5Z5Nt7H4G9yS1boxQyh23yxpS6vB4E8LNhY -xUBdL20j24UeRjMC+KcgKLdz0cnXpu/icfLNs9NRuYGFQZzdM7hlCKJKFLZfpNOw -xW38ArbbJfvCZ7KhenRphlnCTCxUglp3hgGMc9pBb2xKhIh1bABn12/DB/ZuOP4d -hYeg5UhYKhS7Z5q3Yg+mBLgQQrdat82yTU677iY38IXNzud9kvxRYb5mZrc5va6N -SQIDAQAB ------END PUBLIC KEY----- - -YahooBackup2 ------BEGIN PUBLIC KEY----- -MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEY26fFxATbUDuPe7tEeZx7uGLIycMrm20 -/opCiPXC5oAcyOu0pLmiGWBKD8CRK+Po/pYsyIsHotll0aZ6V3BbQg== ------END PUBLIC KEY----- - # From https://letsencrypt.org/certificates/ # The key is shared by Let's Encrypt Authority X1 and X3. LetsEncryptAuthorityPrimary_X1_X3 diff --git a/chromium/net/http/transport_security_state_static.template b/chromium/net/http/transport_security_state_static.template index ba40b5bfefc..58d1f8af822 100644 --- a/chromium/net/http/transport_security_state_static.template +++ b/chromium/net/http/transport_security_state_static.template @@ -11,8 +11,12 @@ #include <iterator> +#include "base/time/time.h" #include "net/http/transport_security_state_source.h" +// This is the time at which the key pins list was last updated. +const base::Time kPinsListTimestamp = base::Time::FromTimeT([[PINS_LIST_TIMESTAMP]]); + // These are SubjectPublicKeyInfo hashes for public key pinning. The // hashes are SHA256 digests. [[SPKI_HASHES]] diff --git a/chromium/net/http/transport_security_state_static_unittest.pins b/chromium/net/http/transport_security_state_static_unittest.pins index 36ef2f10e7f..fc583a79637 100644 --- a/chromium/net/http/transport_security_state_static_unittest.pins +++ b/chromium/net/http/transport_security_state_static_unittest.pins @@ -5,6 +5,12 @@ # This is a HSTS pins file used by the unittests. For more information on the # content and format see the comments in transport_security_state_static.pins. +# Having a timestamp is required by the parser, but this timestamp is not +# otherwise used by tests (since they override the behavior by setting +# pins_list_always_timely_for_testing_ +PinsListTimestamp +0 + TestSPKI1 sha256/AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE= diff --git a/chromium/net/http/transport_security_state_static_unittest_default.pins b/chromium/net/http/transport_security_state_static_unittest_default.pins index 900b7fd7333..486dc8bb9ce 100644 --- a/chromium/net/http/transport_security_state_static_unittest_default.pins +++ b/chromium/net/http/transport_security_state_static_unittest_default.pins @@ -5,6 +5,12 @@ # For use with transport_security_state_static_unittest_default.json. # The format of this file is identical to transport_security_state_static.pins. +# Having a timestamp is required by the parser, but this timestamp is not +# otherwise used by tests (since they override the behavior by setting +# pins_list_always_timely_for_testing_ +PinsListTimestamp +0 + TestSPKI1 sha256/w3y7Yg3RzkAyhCeBoLHm71YRnuuUW87AAR/DVpLMTw4= diff --git a/chromium/net/http/transport_security_state_unittest.cc b/chromium/net/http/transport_security_state_unittest.cc index ba3b4e3c3b2..72ea47ef65b 100644 --- a/chromium/net/http/transport_security_state_unittest.cc +++ b/chromium/net/http/transport_security_state_unittest.cc @@ -164,7 +164,7 @@ class MockCertificateReportSender class MockFailingCertificateReportSender : public TransportSecurityState::ReportSenderInterface { public: - MockFailingCertificateReportSender() : net_error_(ERR_CONNECTION_FAILED) {} + MockFailingCertificateReportSender() = default; ~MockFailingCertificateReportSender() override = default; int net_error() { return net_error_; } @@ -182,14 +182,14 @@ class MockFailingCertificateReportSender } private: - const int net_error_; + const int net_error_ = ERR_CONNECTION_FAILED; }; // A mock ExpectCTReporter that remembers the latest violation that was // reported and the number of violations reported. class MockExpectCTReporter : public TransportSecurityState::ExpectCTReporter { public: - MockExpectCTReporter() : num_failures_(0) {} + MockExpectCTReporter() = default; ~MockExpectCTReporter() override = default; void OnExpectCTFailed( @@ -233,7 +233,7 @@ class MockExpectCTReporter : public TransportSecurityState::ExpectCTReporter { HostPortPair host_port_pair_; GURL report_uri_; base::Time expiration_; - uint32_t num_failures_; + uint32_t num_failures_ = 0; raw_ptr<const X509Certificate> served_certificate_chain_; raw_ptr<const X509Certificate> validated_certificate_chain_; SignedCertificateTimestampAndStatusList signed_certificate_timestamps_; @@ -381,6 +381,7 @@ class TransportSecurityStateTest : public ::testing::Test, static void EnableStaticPins(TransportSecurityState* state) { state->enable_static_pins_ = true; + state->SetPinningListAlwaysTimelyForTesting(true); } static void EnableStaticExpectCT(TransportSecurityState* state) { @@ -884,6 +885,7 @@ TEST_F(TransportSecurityStateTest, DeleteDynamicDataForHost) { TEST_F(TransportSecurityStateTest, LongNames) { TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); const char kLongName[] = "lookupByWaveIdHashAndWaveIdIdAndWaveIdDomainAndWaveletIdIdAnd" "WaveletIdDomainAndBlipBlipid"; @@ -906,6 +908,9 @@ static bool AddHash(const std::string& type_and_base64, HashValueVector* out) { } TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); HashValueVector good_hashes, bad_hashes; for (size_t i = 0; kGoodPath[i]; i++) { @@ -916,6 +921,7 @@ TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { } TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); EnableStaticPins(&state); TransportSecurityState::PKPState pkp_state; @@ -931,12 +937,16 @@ TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { // Tests that pinning violations on preloaded pins trigger reports when // the preloaded pin contains a report URI. TEST_F(TransportSecurityStateTest, PreloadedPKPReportUri) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); const char kPreloadedPinDomain[] = "with-report-uri-pkp.preloaded.test"; HostPortPair host_port_pair(kPreloadedPinDomain, kPort); net::NetworkIsolationKey network_isolation_key = NetworkIsolationKey::CreateTransient(); TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); MockCertificateReportSender mock_report_sender; state.SetReportSender(&mock_report_sender); @@ -1372,6 +1382,9 @@ TEST_F(TransportSecurityStateTest, RepeatedExpectCTReportsForStaticExpectCT) { // the lookup methods can find the entry and correctly decode the different // preloaded states (HSTS, HPKP, and Expect-CT). TEST_F(TransportSecurityStateTest, DecodePreloadedSingle) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); SetTransportSecurityStateSourceForTesting(&test1::kHSTSSource); TransportSecurityState state; @@ -1402,6 +1415,9 @@ TEST_F(TransportSecurityStateTest, DecodePreloadedSingle) { // entries and correctly decode the different preloaded states (HSTS, HPKP, // and Expect-CT) for each entry. TEST_F(TransportSecurityStateTest, DecodePreloadedMultiplePrefix) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); SetTransportSecurityStateSourceForTesting(&test2::kHSTSSource); TransportSecurityState state; @@ -1471,6 +1487,9 @@ TEST_F(TransportSecurityStateTest, DecodePreloadedMultiplePrefix) { // all entries and correctly decode the different preloaded states (HSTS, HPKP, // and Expect-CT) for each entry. TEST_F(TransportSecurityStateTest, DecodePreloadedMultipleMix) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); SetTransportSecurityStateSourceForTesting(&test3::kHSTSSource); TransportSecurityState state; @@ -2703,6 +2722,7 @@ static bool StaticShouldRedirect(const char* hostname) { static bool HasStaticState(const char* hostname) { TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); TransportSecurityState::STSState sts_state; TransportSecurityState::PKPState pkp_state; return state.GetStaticSTSState(hostname, &sts_state) || @@ -2711,6 +2731,7 @@ static bool HasStaticState(const char* hostname) { static bool HasStaticPublicKeyPins(const char* hostname) { TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); TransportSecurityStateTest::EnableStaticPins(&state); TransportSecurityState::PKPState pkp_state; if (!state.GetStaticPKPState(hostname, &pkp_state)) @@ -2728,7 +2749,11 @@ static bool OnlyPinningInStaticState(const char* hostname) { } TEST_F(TransportSecurityStateStaticTest, EnableStaticPins) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); TransportSecurityState::PKPState pkp_state; EnableStaticPins(&state); @@ -2739,6 +2764,7 @@ TEST_F(TransportSecurityStateStaticTest, EnableStaticPins) { TEST_F(TransportSecurityStateStaticTest, DisableStaticPins) { TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); TransportSecurityState::PKPState pkp_state; DisableStaticPins(&state); @@ -2783,6 +2809,9 @@ TEST_F(TransportSecurityStateStaticTest, IsPreloaded) { } TEST_F(TransportSecurityStateStaticTest, PreloadedDomainSet) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); TransportSecurityState state; EnableStaticPins(&state); TransportSecurityState::STSState sts_state; @@ -2801,6 +2830,9 @@ TEST_F(TransportSecurityStateStaticTest, PreloadedDomainSet) { } TEST_F(TransportSecurityStateStaticTest, Preloaded) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); TransportSecurityState state; EnableStaticPins(&state); TransportSecurityState::STSState sts_state; @@ -3017,6 +3049,9 @@ TEST_F(TransportSecurityStateStaticTest, Preloaded) { } TEST_F(TransportSecurityStateStaticTest, PreloadedPins) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); TransportSecurityState state; EnableStaticPins(&state); TransportSecurityState::STSState sts_state; @@ -3083,6 +3118,9 @@ TEST_F(TransportSecurityStateStaticTest, PreloadedPins) { } TEST_F(TransportSecurityStateStaticTest, BuiltinCertPins) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); TransportSecurityState state; EnableStaticPins(&state); TransportSecurityState::PKPState pkp_state; @@ -3139,6 +3177,9 @@ TEST_F(TransportSecurityStateStaticTest, BuiltinCertPins) { } TEST_F(TransportSecurityStateStaticTest, OptionalHSTSCertPins) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); TransportSecurityState state; EnableStaticPins(&state); @@ -3162,11 +3203,16 @@ TEST_F(TransportSecurityStateStaticTest, OptionalHSTSCertPins) { } TEST_F(TransportSecurityStateStaticTest, OverrideBuiltins) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); EXPECT_TRUE(HasStaticPublicKeyPins("google.com")); EXPECT_FALSE(StaticShouldRedirect("google.com")); EXPECT_FALSE(StaticShouldRedirect("www.google.com")); TransportSecurityState state; + state.SetPinningListAlwaysTimelyForTesting(true); + const base::Time current_time(base::Time::Now()); const base::Time expiry = current_time + base::Seconds(1000); state.AddHSTS("www.google.com", expiry, true); @@ -3176,6 +3222,9 @@ TEST_F(TransportSecurityStateStaticTest, OverrideBuiltins) { // Tests that redundant reports are rate-limited. TEST_F(TransportSecurityStateStaticTest, HPKPReportRateLimiting) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); HostPortPair host_port_pair(kHost, kPort); HostPortPair subdomain_host_port_pair(kSubdomain, kPort); GURL report_uri(kReportUri); @@ -3238,6 +3287,9 @@ TEST_F(TransportSecurityStateStaticTest, HPKPReportRateLimiting) { } TEST_F(TransportSecurityStateStaticTest, HPKPReporting) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); HostPortPair host_port_pair(kHost, kPort); HostPortPair subdomain_host_port_pair(kSubdomain, kPort); GURL report_uri(kReportUri); @@ -3882,6 +3934,9 @@ TEST_F(TransportSecurityStateTest, PruneExpectCTNetworkIsolationKeyLimit) { } TEST_F(TransportSecurityStateTest, UpdateKeyPinsListValidPin) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); HostPortPair host_port_pair(kHost, kPort); GURL report_uri(kReportUri); NetworkIsolationKey network_isolation_key = @@ -3938,6 +3993,9 @@ TEST_F(TransportSecurityStateTest, UpdateKeyPinsListValidPin) { } TEST_F(TransportSecurityStateTest, UpdateKeyPinsListNotValidPin) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); HostPortPair host_port_pair(kHost, kPort); GURL report_uri(kReportUri); NetworkIsolationKey network_isolation_key = @@ -3994,6 +4052,9 @@ TEST_F(TransportSecurityStateTest, UpdateKeyPinsListNotValidPin) { } TEST_F(TransportSecurityStateTest, UpdateKeyPinsEmptyList) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); HostPortPair host_port_pair(kHost, kPort); GURL report_uri(kReportUri); NetworkIsolationKey network_isolation_key = @@ -4035,6 +4096,9 @@ TEST_F(TransportSecurityStateTest, UpdateKeyPinsEmptyList) { } TEST_F(TransportSecurityStateTest, UpdateKeyPinsListTimestamp) { + base::test::ScopedFeatureList scoped_feature_list_; + scoped_feature_list_.InitAndEnableFeature( + net::features::kStaticKeyPinningEnforcement); HostPortPair host_port_pair(kHost, kPort); GURL report_uri(kReportUri); NetworkIsolationKey network_isolation_key = @@ -4064,6 +4128,12 @@ TEST_F(TransportSecurityStateTest, UpdateKeyPinsListTimestamp) { TransportSecurityState::ENABLE_PIN_REPORTS, network_isolation_key, &unused_failure_log)); + // TransportSecurityStateTest sets a flag when EnableStaticPins is called that + // results in TransportSecurityState considering the pins list as always + // timely. We need to disable it so we can test that the timestamp has the + // required effect. + state.SetPinningListAlwaysTimelyForTesting(false); + // Update the pins list, with bad hashes as rejected, but a timestamp >70 days // old. std::vector<std::vector<uint8_t>> rejected_hashes; @@ -4102,4 +4172,48 @@ TEST_F(TransportSecurityStateTest, UpdateKeyPinsListTimestamp) { network_isolation_key, &unused_failure_log)); } +class TransportSecurityStatePinningKillswitchTest + : public TransportSecurityStateTest { + public: + void SetUp() override { + scoped_feature_list_.InitAndDisableFeature( + features::kStaticKeyPinningEnforcement); + TransportSecurityStateTest::SetUp(); + } + + protected: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(TransportSecurityStatePinningKillswitchTest, PinningKillswitchSet) { + HostPortPair host_port_pair(kHost, kPort); + GURL report_uri(kReportUri); + NetworkIsolationKey network_isolation_key = + NetworkIsolationKey::CreateTransient(); + // Two dummy certs to use as the server-sent and validated chains. The + // contents don't matter. + scoped_refptr<X509Certificate> cert1 = + ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); + ASSERT_TRUE(cert1); + scoped_refptr<X509Certificate> cert2 = + ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem"); + ASSERT_TRUE(cert2); + + HashValueVector bad_hashes; + + for (size_t i = 0; kBadPath[i]; i++) + EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); + + TransportSecurityState state; + EnableStaticPins(&state); + std::string unused_failure_log; + + // Hashes should be accepted since pinning enforcement is disabled. + EXPECT_EQ(TransportSecurityState::PKPStatus::OK, + state.CheckPublicKeyPins( + host_port_pair, true, bad_hashes, cert1.get(), cert2.get(), + TransportSecurityState::ENABLE_PIN_REPORTS, + network_isolation_key, &unused_failure_log)); +} + } // namespace net |