summaryrefslogtreecommitdiff
path: root/chromium/net
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net')
-rw-r--r--chromium/net/base/features.cc9
-rw-r--r--chromium/net/base/features.h9
-rw-r--r--chromium/net/cookies/canonical_cookie.cc48
-rw-r--r--chromium/net/cookies/canonical_cookie.h5
-rw-r--r--chromium/net/cookies/canonical_cookie_unittest.cc125
-rw-r--r--chromium/net/http/transport_security_state_static.json.gzbin1353766 -> 1353766 bytes
-rw-r--r--chromium/net/spdy/alps_decoder.cc57
-rw-r--r--chromium/net/spdy/alps_decoder_test.cc86
8 files changed, 320 insertions, 19 deletions
diff --git a/chromium/net/base/features.cc b/chromium/net/base/features.cc
index 20b3b04f50d..e2a91634275 100644
--- a/chromium/net/base/features.cc
+++ b/chromium/net/base/features.cc
@@ -293,5 +293,14 @@ const base::Feature kClampCookieExpiryTo400Days(
"ClampCookieExpiryTo400Days",
base::FEATURE_DISABLED_BY_DEFAULT);
+const base::Feature kAlpsParsing{"AlpsParsing",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kAlpsClientHintParsing{"AlpsClientHintParsing",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kShouldKillSessionOnAcceptChMalformed{
+ "ShouldKillSessionOnAcceptChMalformed", base::FEATURE_DISABLED_BY_DEFAULT};
+
} // namespace features
} // namespace net
diff --git a/chromium/net/base/features.h b/chromium/net/base/features.h
index ca9fe8f6922..469ffec6760 100644
--- a/chromium/net/base/features.h
+++ b/chromium/net/base/features.h
@@ -430,6 +430,15 @@ NET_EXPORT extern const base::Feature kSandboxHttpCache;
// future.
NET_EXPORT extern const base::Feature kClampCookieExpiryTo400Days;
+// Whether ALPS parsing is on for any type of frame.
+NET_EXPORT extern const base::Feature kAlpsParsing;
+
+// Whether ALPS parsing is on for client hint parsing specifically.
+NET_EXPORT extern const base::Feature kAlpsClientHintParsing;
+
+// Whether to kill the session on Error::kAcceptChMalformed.
+NET_EXPORT extern const base::Feature kShouldKillSessionOnAcceptChMalformed;
+
} // namespace features
} // namespace net
diff --git a/chromium/net/cookies/canonical_cookie.cc b/chromium/net/cookies/canonical_cookie.cc
index 053f760ed76..d0120b13c5c 100644
--- a/chromium/net/cookies/canonical_cookie.cc
+++ b/chromium/net/cookies/canonical_cookie.cc
@@ -56,6 +56,7 @@
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "net/base/features.h"
@@ -558,6 +559,11 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::Create(
CookiePrefix prefix = GetCookiePrefix(parsed_cookie.Name());
bool is_cookie_prefix_valid = IsCookiePrefixValid(prefix, url, parsed_cookie);
RecordCookiePrefixMetrics(prefix, is_cookie_prefix_valid);
+
+ if (parsed_cookie.Name() == "") {
+ is_cookie_prefix_valid = !HasHiddenPrefixName(parsed_cookie.Value());
+ }
+
if (!is_cookie_prefix_valid) {
DVLOG(net::cookie_util::kVlogSetCookies)
<< "Create() failed because the cookie violated prefix rules.";
@@ -768,6 +774,11 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie(
net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
}
+ if (name == "" && HasHiddenPrefixName(value)) {
+ status->AddExclusionReason(
+ net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
+ }
+
if (!IsCookieSamePartyValid(same_party, secure, same_site)) {
status->AddExclusionReason(
net::CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY);
@@ -1472,6 +1483,9 @@ bool CanonicalCookie::IsCanonicalForFromStorage() const {
return false;
}
+ if (name_ == "" && HasHiddenPrefixName(value_))
+ return false;
+
if (!IsCookieSamePartyValid(same_party_, secure_, same_site_))
return false;
@@ -1594,6 +1608,40 @@ CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSite(
}
}
+// static
+bool CanonicalCookie::HasHiddenPrefixName(
+ const base::StringPiece cookie_value) {
+ // Skip BWS as defined by HTTPSEM as SP or HTAB (0x20 or 0x9).
+ base::StringPiece value_without_BWS =
+ base::TrimString(cookie_value, " \t", base::TRIM_LEADING);
+
+ const base::StringPiece host_prefix = "__Host-";
+
+ // Compare the value to the host_prefix.
+ if (base::StartsWith(value_without_BWS, host_prefix)) {
+ // The prefix matches, now check if the value string contains a subsequent
+ // '='.
+ if (value_without_BWS.find_first_of('=', host_prefix.size()) !=
+ base::StringPiece::npos) {
+ // This value contains a hidden prefix name.
+ return true;
+ }
+ return false;
+ }
+
+ // Do a similar check for the secure prefix
+ const base::StringPiece secure_prefix = "__Secure-";
+
+ if (base::StartsWith(value_without_BWS, secure_prefix)) {
+ if (value_without_BWS.find_first_of('=', secure_prefix.size()) !=
+ base::StringPiece::npos) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
bool CanonicalCookie::IsRecentlyCreated(base::TimeDelta age_threshold) const {
return (base::Time::Now() - creation_date_) <= age_threshold;
}
diff --git a/chromium/net/cookies/canonical_cookie.h b/chromium/net/cookies/canonical_cookie.h
index 19acf0a8821..0283553acf8 100644
--- a/chromium/net/cookies/canonical_cookie.h
+++ b/chromium/net/cookies/canonical_cookie.h
@@ -11,6 +11,7 @@
#include <vector>
#include "base/gtest_prod_util.h"
+#include "base/strings/string_piece.h"
#include "base/time/time.h"
#include "net/base/net_export.h"
#include "net/cookies/cookie_access_result.h"
@@ -421,6 +422,7 @@ class NET_EXPORT CanonicalCookie {
private:
FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestPrefixHistograms);
+ FRIEND_TEST_ALL_PREFIXES(CanonicalCookieTest, TestHasHiddenPrefixName);
// This constructor does not validate or canonicalize their inputs;
// the resulting CanonicalCookies should not be relied on to be canonical
@@ -484,6 +486,9 @@ class NET_EXPORT CanonicalCookie {
CookieEffectiveSameSite GetEffectiveSameSite(
CookieAccessSemantics access_semantics) const;
+ // Checks for values that could be misinterpreted as a cookie name prefix.
+ static bool HasHiddenPrefixName(const base::StringPiece cookie_value);
+
// Returns whether the cookie was created at most |age_threshold| ago.
bool IsRecentlyCreated(base::TimeDelta age_threshold) const;
diff --git a/chromium/net/cookies/canonical_cookie_unittest.cc b/chromium/net/cookies/canonical_cookie_unittest.cc
index 7d8a0151548..da01a7888dc 100644
--- a/chromium/net/cookies/canonical_cookie_unittest.cc
+++ b/chromium/net/cookies/canonical_cookie_unittest.cc
@@ -2348,6 +2348,18 @@ TEST(CanonicalCookieTest, SecureCookiePrefix) {
absl::nullopt /* cookie_partition_key */, &status));
EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
{CookieInclusionStatus::EXCLUDE_INVALID_PREFIX}));
+
+ // Hidden __Secure- prefixes should be rejected.
+ EXPECT_FALSE(CanonicalCookie::Create(
+ https_url, "=__Secure-A=B; Secure", creation_time, server_time,
+ absl::nullopt /* cookie_partition_key */, &status));
+ EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
+ {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX}));
+
+ // While tricky, this isn't considered hidden and is fine.
+ EXPECT_TRUE(CanonicalCookie::Create(
+ https_url, "A=__Secure-A=B; Secure", creation_time, server_time,
+ absl::nullopt /* cookie_partition_key */));
}
TEST(CanonicalCookieTest, HostCookiePrefix) {
@@ -2432,6 +2444,18 @@ TEST(CanonicalCookieTest, HostCookiePrefix) {
EXPECT_TRUE(CanonicalCookie::Create(
https_url, "__HostA=B; Domain=" + domain + "; Secure;", creation_time,
server_time, absl::nullopt /* cookie_partition_key */));
+
+ // Hidden __Host- prefixes should be rejected.
+ EXPECT_FALSE(CanonicalCookie::Create(
+ https_url, "=__Host-A=B; Path=/; Secure;", creation_time, server_time,
+ absl::nullopt /* cookie_partition_key */, &status));
+ EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
+ {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX}));
+
+ // While tricky, this isn't considered hidden and is fine.
+ EXPECT_TRUE(CanonicalCookie::Create(
+ https_url, "A=__Host-A=B; Path=/; Secure;", creation_time, server_time,
+ absl::nullopt /* cookie_partition_key */));
}
TEST(CanonicalCookieTest, CanCreateSecureCookiesFromAnyScheme) {
@@ -2874,6 +2898,31 @@ TEST(CanonicalCookieTest, IsCanonical) {
CookiePartitionKey::FromURLForTesting(
GURL("https://toplevelsite.com")))
->IsCanonical());
+
+ // Hidden cookie prefixes.
+ EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting(
+ "", "__Secure-a=b", "x.y", "/", base::Time(), base::Time(),
+ base::Time(), true, false, CookieSameSite::NO_RESTRICTION,
+ COOKIE_PRIORITY_LOW, false)
+ ->IsCanonical());
+
+ EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting(
+ "", "__Host-a=b", "x.y", "/", base::Time(), base::Time(),
+ base::Time(), true, false, CookieSameSite::NO_RESTRICTION,
+ COOKIE_PRIORITY_LOW, false)
+ ->IsCanonical());
+
+ EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting(
+ "a", "__Secure-a=b", "x.y", "/", base::Time(), base::Time(),
+ base::Time(), true, false, CookieSameSite::NO_RESTRICTION,
+ COOKIE_PRIORITY_LOW, false)
+ ->IsCanonical());
+
+ EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting(
+ "a", "__Host-a=b", "x.y", "/", base::Time(), base::Time(),
+ base::Time(), true, false, CookieSameSite::NO_RESTRICTION,
+ COOKIE_PRIORITY_LOW, false)
+ ->IsCanonical());
}
TEST(CanonicalCookieTest, TestSetCreationDate) {
@@ -3525,6 +3574,39 @@ TEST(CanonicalCookieTest, CreateSanitizedCookie_Logic) {
false /*same_party*/, absl::nullopt /*partition_key*/, &status));
EXPECT_TRUE(status.IsInclude());
+ // Cookies with hidden prefixes should be rejected.
+
+ EXPECT_FALSE(CanonicalCookie::CreateSanitizedCookie(
+ GURL("https://www.foo.com"), "", "__Host-A=B", "", "/", two_hours_ago,
+ one_hour_from_now, one_hour_ago, true, false,
+ CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT,
+ false /*same_party*/, absl::nullopt /*partition_key*/, &status));
+ EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
+ {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX}));
+
+ EXPECT_FALSE(CanonicalCookie::CreateSanitizedCookie(
+ GURL("https://www.foo.com"), "", "__Secure-A=B", "", "/", two_hours_ago,
+ one_hour_from_now, one_hour_ago, true, false,
+ CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT,
+ false /*same_party*/, absl::nullopt /*partition_key*/, &status));
+ EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
+ {CookieInclusionStatus::EXCLUDE_INVALID_PREFIX}));
+
+ // While tricky, this aren't considered hidden prefixes and should succeed.
+ EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie(
+ GURL("https://www.foo.com"), "A", "__Host-A=B", "", "/", two_hours_ago,
+ one_hour_from_now, one_hour_ago, true, false,
+ CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT,
+ false /*same_party*/, absl::nullopt /*partition_key*/, &status));
+ EXPECT_TRUE(status.IsInclude());
+
+ EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie(
+ GURL("https://www.foo.com"), "A", "__Secure-A=B", "", "/", two_hours_ago,
+ one_hour_from_now, one_hour_ago, true, false,
+ CookieSameSite::NO_RESTRICTION, CookiePriority::COOKIE_PRIORITY_DEFAULT,
+ false /*same_party*/, absl::nullopt /*partition_key*/, &status));
+ EXPECT_TRUE(status.IsInclude());
+
// SameParty attribute requires Secure and forbids SameSite=Strict.
EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie(
GURL("https://www.foo.com"), "A", "B", ".www.foo.com", "/", two_hours_ago,
@@ -5114,4 +5196,47 @@ TEST(CanonicalCookieTest, TestIsCanonicalWithInvalidSizeHistograms) {
histograms.ExpectBucketCount(kFromStorageWithValidLengthHistogram, kValid, 1);
}
+TEST(CanonicalCookieTest, TestHasHiddenPrefixName) {
+ const struct {
+ const char* value;
+ bool result;
+ } kTestCases[] = {
+ {"", false},
+ {" ", false},
+ {"foobar=", false},
+ {"foo=bar", false},
+ {" \t ", false},
+ {"\t", false},
+ {"__Secure-abc", false},
+ {"__Secur=e-abc", false},
+ {"__Secureabc", false},
+ {"__Host-abc", false},
+ {"__Hos=t-abc", false},
+ {"_Host", false},
+ {" __Secure-abc", false},
+ {"\t__Host-", false},
+ {"a__Host-abc=123", false},
+ {"a__Secure-abc=123", false},
+ {"__Host-abc=", true},
+ {"__Host-abc=123", true},
+ {" __Host-abc=123", true},
+ {" __Host-abc=", true},
+ {"\t\t\t\t\t__Host-abc=123", true},
+ {"\t __Host-abc=", true},
+ {"__Secure-abc=", true},
+ {"__Secure-abc=123", true},
+ {" __Secure-abc=123", true},
+ {" __Secure-abc=", true},
+ {"\t\t\t\t\t__Secure-abc=123", true},
+ {"\t __Secure-abc=", true},
+ {"__Secure-abc=123=d=4=fg=", true},
+ };
+
+ for (auto test_case : kTestCases) {
+ EXPECT_EQ(CanonicalCookie::HasHiddenPrefixName(test_case.value),
+ test_case.result)
+ << test_case.value << " failed check";
+ }
+}
+
} // namespace net
diff --git a/chromium/net/http/transport_security_state_static.json.gz b/chromium/net/http/transport_security_state_static.json.gz
index df8112dc1ce..4aadbc25d28 100644
--- a/chromium/net/http/transport_security_state_static.json.gz
+++ b/chromium/net/http/transport_security_state_static.json.gz
Binary files differ
diff --git a/chromium/net/spdy/alps_decoder.cc b/chromium/net/spdy/alps_decoder.cc
index 69c8b710387..fb43d876a23 100644
--- a/chromium/net/spdy/alps_decoder.cc
+++ b/chromium/net/spdy/alps_decoder.cc
@@ -4,6 +4,10 @@
#include "net/spdy/alps_decoder.h"
+#include "base/feature_list.h"
+#include "base/metrics/histogram_functions.h"
+#include "net/base/features.h"
+
namespace net {
namespace {
@@ -101,23 +105,34 @@ bool AlpsDecoder::AcceptChParser::OnFrameHeader(spdy::SpdyStreamId stream_id,
size_t length,
uint8_t type,
uint8_t flags) {
- if (type != static_cast<uint8_t>(spdy::SpdyFrameType::ACCEPT_CH) ||
- error_ != Error::kNoError) {
- // Ignore every frame except for ACCEPT_CH.
- // Ignore data after an error has occurred.
- // Returning false causes Http2DecoderAdapter not to call OnFramePayload().
- return false;
- }
- if (stream_id != 0) {
- error_ = Error::kAcceptChInvalidStream;
+ // Ignore data after an error has occurred.
+ if (error_ != Error::kNoError)
return false;
- }
- if (flags != 0) {
- error_ = Error::kAcceptChWithFlags;
+ // Stop all alps parsing if it's disabled.
+ if (!base::FeatureList::IsEnabled(features::kAlpsParsing))
return false;
+ // Handle per-type parsing.
+ switch (type) {
+ case static_cast<uint8_t>(spdy::SpdyFrameType::ACCEPT_CH): {
+ // Stop alps client hint parsing if it's disabled.
+ if (!base::FeatureList::IsEnabled(features::kAlpsClientHintParsing))
+ return false;
+ // Check for issues with the frame.
+ if (stream_id != 0) {
+ error_ = Error::kAcceptChInvalidStream;
+ return false;
+ }
+ if (flags != 0) {
+ error_ = Error::kAcceptChWithFlags;
+ return false;
+ }
+ // This frame can be parsed in OnFramePayload.
+ return true;
+ }
+ default:
+ // Ignore all other types.
+ return false;
}
-
- return true;
}
void AlpsDecoder::AcceptChParser::OnFramePayload(const char* data, size_t len) {
@@ -130,8 +145,18 @@ void AlpsDecoder::AcceptChParser::OnFramePayload(const char* data, size_t len) {
base::StringPiece value;
if (!ReadUint16PrefixedStringPiece(&payload, &origin) ||
!ReadUint16PrefixedStringPiece(&payload, &value)) {
- error_ = Error::kAcceptChMalformed;
- return;
+ if (base::FeatureList::IsEnabled(
+ features::kShouldKillSessionOnAcceptChMalformed)) {
+ // This causes a session termination.
+ error_ = Error::kAcceptChMalformed;
+ return;
+ } else {
+ // This logs that a session termination was bypassed.
+ base::UmaHistogramEnumeration(
+ "Net.SpdySession.AlpsDecoderStatus.Bypassed",
+ Error::kAcceptChMalformed);
+ return;
+ }
}
accept_ch_.push_back(
spdy::AcceptChOriginValuePair{std::string(origin), std::string(value)});
diff --git a/chromium/net/spdy/alps_decoder_test.cc b/chromium/net/spdy/alps_decoder_test.cc
index 80c98c3a424..5f155e5c97d 100644
--- a/chromium/net/spdy/alps_decoder_test.cc
+++ b/chromium/net/spdy/alps_decoder_test.cc
@@ -4,6 +4,9 @@
#include "net/spdy/alps_decoder.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "net/base/features.h"
#include "net/base/hex_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -123,6 +126,58 @@ TEST(AlpsDecoderTest, ParseLargeAcceptChFrame) {
accept_ch_tokens}));
}
+TEST(AlpsDecoderTest, DisableAlpsParsing) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndDisableFeature(features::kAlpsParsing);
+ AlpsDecoder decoder;
+ AlpsDecoder::Error error = decoder.Decode(HexDecode(
+ // ACCEPT_CH frame
+ "00003d" // length
+ "89" // type ACCEPT_CH
+ "00" // flags
+ "00000000" // stream ID
+ "0017" // origin length
+ "68747470733a2f2f7777772e" //
+ "6578616d706c652e636f6d" // origin "https://www.example.com"
+ "0003" // value length
+ "666f6f" // value "foo"
+ "0018" // origin length
+ "68747470733a2f2f6d61696c" //
+ "2e6578616d706c652e636f6d" // origin "https://mail.example.com"
+ "0003" // value length
+ "626172" // value "bar"
+ ));
+
+ EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
+ EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
+}
+
+TEST(AlpsDecoderTest, DisableAlpsClientHintParsing) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndDisableFeature(features::kAlpsClientHintParsing);
+ AlpsDecoder decoder;
+ AlpsDecoder::Error error = decoder.Decode(HexDecode(
+ // ACCEPT_CH frame
+ "00003d" // length
+ "89" // type ACCEPT_CH
+ "00" // flags
+ "00000000" // stream ID
+ "0017" // origin length
+ "68747470733a2f2f7777772e" //
+ "6578616d706c652e636f6d" // origin "https://www.example.com"
+ "0003" // value length
+ "666f6f" // value "foo"
+ "0018" // origin length
+ "68747470733a2f2f6d61696c" //
+ "2e6578616d706c652e636f6d" // origin "https://mail.example.com"
+ "0003" // value length
+ "626172" // value "bar"
+ ));
+
+ EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
+ EXPECT_THAT(decoder.GetAcceptCh(), IsEmpty());
+}
+
TEST(AlpsDecoderTest, IncompleteFrame) {
AlpsDecoder decoder;
AlpsDecoder::Error error =
@@ -262,7 +317,23 @@ TEST(AlpsDecoderTest, UnknownFrame) {
EXPECT_EQ(0, decoder.settings_frame_count());
}
-TEST(AlpsDecoderTest, MalformedAcceptChFrame) {
+class AlpsDecoderTestWithFeature : public ::testing::TestWithParam<bool> {
+ public:
+ bool ShouldKillSessionOnAcceptChMalformed() { return GetParam(); }
+
+ private:
+ void SetUp() override {
+ feature_list_.InitWithFeatureState(
+ features::kShouldKillSessionOnAcceptChMalformed,
+ ShouldKillSessionOnAcceptChMalformed());
+ }
+
+ base::test::ScopedFeatureList feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All, AlpsDecoderTestWithFeature, testing::Bool());
+
+TEST_P(AlpsDecoderTestWithFeature, MalformedAcceptChFrame) {
// Correct, complete payload.
std::string payload = HexDecode(
"0017" // origin length
@@ -273,6 +344,7 @@ TEST(AlpsDecoderTest, MalformedAcceptChFrame) {
for (uint8_t payload_length = 1; payload_length < payload.length();
payload_length++) {
+ base::HistogramTester histogram_tester;
// First two bytes of length.
std::string frame = HexDecode("0000");
// Last byte of length.
@@ -287,8 +359,16 @@ TEST(AlpsDecoderTest, MalformedAcceptChFrame) {
AlpsDecoder decoder;
AlpsDecoder::Error error = decoder.Decode(frame);
-
- EXPECT_EQ(AlpsDecoder::Error::kAcceptChMalformed, error);
+ if (ShouldKillSessionOnAcceptChMalformed()) {
+ EXPECT_EQ(AlpsDecoder::Error::kAcceptChMalformed, error);
+ histogram_tester.ExpectTotalCount(
+ "Net.SpdySession.AlpsDecoderStatus.Bypassed", 0);
+ } else {
+ EXPECT_EQ(AlpsDecoder::Error::kNoError, error);
+ histogram_tester.ExpectUniqueSample(
+ "Net.SpdySession.AlpsDecoderStatus.Bypassed",
+ static_cast<int>(AlpsDecoder::Error::kAcceptChMalformed), 1);
+ }
}
}