blob: 111bd3432b4c1f0aa22f1b847c18ebb83e7a51f5 [file] [log] [blame]
// Copyright 2018 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 "services/network/public/cpp/cors/cors.h"
#include <limits.h>
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace network {
namespace cors {
namespace {
using CorsTest = testing::Test;
TEST_F(CorsTest, CheckAccessDetectsInvalidResponse) {
base::Optional<CorsErrorStatus> error_status =
CheckAccess(GURL(), 0 /* response_status_code */,
base::nullopt /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, url::Origin());
ASSERT_TRUE(error_status);
EXPECT_EQ(mojom::CorsError::kInvalidResponse, error_status->cors_error);
}
// Tests if CheckAccess detects kWildcardOriginNotAllowed error correctly.
TEST_F(CorsTest, CheckAccessDetectsWildcardOriginNotAllowed) {
const GURL response_url("http://example.com/data");
const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
const int response_status_code = 200;
const std::string allow_all_header("*");
// Access-Control-Allow-Origin '*' works.
base::Optional<CorsErrorStatus> error1 =
CheckAccess(response_url, response_status_code,
allow_all_header /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, origin);
EXPECT_FALSE(error1);
// Access-Control-Allow-Origin '*' should not be allowed if credentials mode
// is kInclude.
base::Optional<CorsErrorStatus> error2 =
CheckAccess(response_url, response_status_code,
allow_all_header /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kInclude, origin);
ASSERT_TRUE(error2);
EXPECT_EQ(mojom::CorsError::kWildcardOriginNotAllowed, error2->cors_error);
}
// Tests if CheckAccess detects kMissingAllowOriginHeader error correctly.
TEST_F(CorsTest, CheckAccessDetectsMissingAllowOriginHeader) {
const GURL response_url("http://example.com/data");
const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
const int response_status_code = 200;
// Access-Control-Allow-Origin is missed.
base::Optional<CorsErrorStatus> error =
CheckAccess(response_url, response_status_code,
base::nullopt /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, origin);
ASSERT_TRUE(error);
EXPECT_EQ(mojom::CorsError::kMissingAllowOriginHeader, error->cors_error);
}
// Tests if CheckAccess detects kMultipleAllowOriginValues error
// correctly.
TEST_F(CorsTest, CheckAccessDetectsMultipleAllowOriginValues) {
const GURL response_url("http://example.com/data");
const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
const int response_status_code = 200;
const std::string space_separated_multiple_origins(
"http://example.com http://another.example.com");
base::Optional<CorsErrorStatus> error1 =
CheckAccess(response_url, response_status_code,
space_separated_multiple_origins /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, origin);
ASSERT_TRUE(error1);
EXPECT_EQ(mojom::CorsError::kMultipleAllowOriginValues, error1->cors_error);
const std::string comma_separated_multiple_origins(
"http://example.com,http://another.example.com");
base::Optional<CorsErrorStatus> error2 =
CheckAccess(response_url, response_status_code,
comma_separated_multiple_origins /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, origin);
ASSERT_TRUE(error2);
EXPECT_EQ(mojom::CorsError::kMultipleAllowOriginValues, error2->cors_error);
}
// Tests if CheckAccess detects kInvalidAllowOriginValue error correctly.
TEST_F(CorsTest, CheckAccessDetectsInvalidAllowOriginValue) {
const GURL response_url("http://example.com/data");
const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
const int response_status_code = 200;
base::Optional<CorsErrorStatus> error =
CheckAccess(response_url, response_status_code,
std::string("invalid.origin") /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, origin);
ASSERT_TRUE(error);
EXPECT_EQ(mojom::CorsError::kInvalidAllowOriginValue, error->cors_error);
EXPECT_EQ("invalid.origin", error->failed_parameter);
}
// Tests if CheckAccess detects kAllowOriginMismatch error correctly.
TEST_F(CorsTest, CheckAccessDetectsAllowOriginMismatch) {
const GURL response_url("http://example.com/data");
const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
const int response_status_code = 200;
base::Optional<CorsErrorStatus> error1 =
CheckAccess(response_url, response_status_code,
origin.Serialize() /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, origin);
ASSERT_FALSE(error1);
base::Optional<CorsErrorStatus> error2 = CheckAccess(
response_url, response_status_code,
std::string("http://not.google.com") /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, origin);
ASSERT_TRUE(error2);
EXPECT_EQ(mojom::CorsError::kAllowOriginMismatch, error2->cors_error);
EXPECT_EQ("http://not.google.com", error2->failed_parameter);
// Allow "null" value to match serialized unique origins.
const std::string null_string("null");
const url::Origin null_origin;
EXPECT_EQ(null_string, null_origin.Serialize());
base::Optional<CorsErrorStatus> error3 = CheckAccess(
response_url, response_status_code, null_string /* allow_origin_header */,
base::nullopt /* allow_credentials_header */,
network::mojom::CredentialsMode::kOmit, null_origin);
EXPECT_FALSE(error3);
}
// Tests if CheckAccess detects kInvalidAllowCredentials error correctly.
TEST_F(CorsTest, CheckAccessDetectsInvalidAllowCredential) {
const GURL response_url("http://example.com/data");
const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
const int response_status_code = 200;
base::Optional<CorsErrorStatus> error1 =
CheckAccess(response_url, response_status_code,
origin.Serialize() /* allow_origin_header */,
std::string("true") /* allow_credentials_header */,
network::mojom::CredentialsMode::kInclude, origin);
ASSERT_FALSE(error1);
base::Optional<CorsErrorStatus> error2 =
CheckAccess(response_url, response_status_code,
origin.Serialize() /* allow_origin_header */,
std::string("fuga") /* allow_credentials_header */,
network::mojom::CredentialsMode::kInclude, origin);
ASSERT_TRUE(error2);
EXPECT_EQ(mojom::CorsError::kInvalidAllowCredentials, error2->cors_error);
EXPECT_EQ("fuga", error2->failed_parameter);
}
// Tests if CheckRedirectLocation detects kCorsDisabledScheme and
// kRedirectContainsCredentials errors correctly.
TEST_F(CorsTest, CheckRedirectLocation) {
struct TestCase {
GURL url;
mojom::RequestMode request_mode;
bool cors_flag;
bool tainted;
base::Optional<CorsErrorStatus> expectation;
};
const auto kCors = mojom::RequestMode::kCors;
const auto kCorsWithForcedPreflight =
mojom::RequestMode::kCorsWithForcedPreflight;
const auto kNoCors = mojom::RequestMode::kNoCors;
const url::Origin origin = url::Origin::Create(GURL("http://example.com/"));
const GURL same_origin_url("http://example.com/");
const GURL cross_origin_url("http://example2.com/");
const GURL data_url("data:,Hello");
const GURL same_origin_url_with_user("http://yukari@example.com/");
const GURL same_origin_url_with_pass("http://:tamura@example.com/");
const GURL cross_origin_url_with_user("http://yukari@example2.com/");
const GURL cross_origin_url_with_pass("http://:tamura@example2.com/");
const auto ok = base::nullopt;
const CorsErrorStatus kCorsDisabledScheme(
mojom::CorsError::kCorsDisabledScheme);
const CorsErrorStatus kRedirectContainsCredentials(
mojom::CorsError::kRedirectContainsCredentials);
TestCase cases[] = {
// "cors", no credentials information
{same_origin_url, kCors, false, false, ok},
{cross_origin_url, kCors, false, false, ok},
{data_url, kCors, false, false, ok},
{same_origin_url, kCors, true, false, ok},
{cross_origin_url, kCors, true, false, ok},
{data_url, kCors, true, false, ok},
{same_origin_url, kCors, false, true, ok},
{cross_origin_url, kCors, false, true, ok},
{data_url, kCors, false, true, ok},
{same_origin_url, kCors, true, true, ok},
{cross_origin_url, kCors, true, true, ok},
{data_url, kCors, true, true, ok},
// "cors" with forced preflight, no credentials information
{same_origin_url, kCorsWithForcedPreflight, false, false, ok},
{cross_origin_url, kCorsWithForcedPreflight, false, false, ok},
{data_url, kCorsWithForcedPreflight, false, false, ok},
{same_origin_url, kCorsWithForcedPreflight, true, false, ok},
{cross_origin_url, kCorsWithForcedPreflight, true, false, ok},
{data_url, kCorsWithForcedPreflight, true, false, ok},
{same_origin_url, kCorsWithForcedPreflight, false, true, ok},
{cross_origin_url, kCorsWithForcedPreflight, false, true, ok},
{data_url, kCorsWithForcedPreflight, false, true, ok},
{same_origin_url, kCorsWithForcedPreflight, true, true, ok},
{cross_origin_url, kCorsWithForcedPreflight, true, true, ok},
{data_url, kCorsWithForcedPreflight, true, true, ok},
// "no-cors", no credentials information
{same_origin_url, kNoCors, false, false, ok},
{cross_origin_url, kNoCors, false, false, ok},
{data_url, kNoCors, false, false, ok},
{same_origin_url, kNoCors, false, true, ok},
{cross_origin_url, kNoCors, false, true, ok},
{data_url, kNoCors, false, true, ok},
// with credentials information (same origin)
{same_origin_url_with_user, kCors, false, false, ok},
{same_origin_url_with_user, kCors, true, false,
kRedirectContainsCredentials},
{same_origin_url_with_user, kCors, true, true,
kRedirectContainsCredentials},
{same_origin_url_with_user, kNoCors, false, false, ok},
{same_origin_url_with_user, kNoCors, false, true, ok},
{same_origin_url_with_pass, kCors, false, false, ok},
{same_origin_url_with_pass, kCors, true, false,
kRedirectContainsCredentials},
{same_origin_url_with_pass, kCors, true, true,
kRedirectContainsCredentials},
{same_origin_url_with_pass, kNoCors, false, false, ok},
{same_origin_url_with_pass, kNoCors, false, true, ok},
// with credentials information (cross origin)
{cross_origin_url_with_user, kCors, false, false,
kRedirectContainsCredentials},
{cross_origin_url_with_user, kCors, true, false,
kRedirectContainsCredentials},
{cross_origin_url_with_user, kCors, true, true,
kRedirectContainsCredentials},
{cross_origin_url_with_user, kNoCors, false, true, ok},
{cross_origin_url_with_user, kNoCors, false, false, ok},
{cross_origin_url_with_pass, kCors, false, false,
kRedirectContainsCredentials},
{cross_origin_url_with_pass, kCors, true, false,
kRedirectContainsCredentials},
{cross_origin_url_with_pass, kCors, true, true,
kRedirectContainsCredentials},
{cross_origin_url_with_pass, kNoCors, false, true, ok},
{cross_origin_url_with_pass, kNoCors, false, false, ok},
};
for (const auto& test : cases) {
SCOPED_TRACE(testing::Message()
<< "url: " << test.url
<< ", request mode: " << test.request_mode
<< ", origin: " << origin << ", cors_flag: " << test.cors_flag
<< ", tainted: " << test.tainted);
EXPECT_EQ(test.expectation,
CheckRedirectLocation(test.url, test.request_mode, origin,
test.cors_flag, test.tainted));
}
}
TEST_F(CorsTest, CheckPreflightDetectsErrors) {
EXPECT_FALSE(CheckPreflight(200));
EXPECT_FALSE(CheckPreflight(299));
base::Optional<mojom::CorsError> error1 = CheckPreflight(300);
ASSERT_TRUE(error1);
EXPECT_EQ(mojom::CorsError::kPreflightInvalidStatus, *error1);
EXPECT_FALSE(CheckExternalPreflight(std::string("true")));
base::Optional<CorsErrorStatus> error2 =
CheckExternalPreflight(base::nullopt);
ASSERT_TRUE(error2);
EXPECT_EQ(mojom::CorsError::kPreflightMissingAllowExternal,
error2->cors_error);
EXPECT_EQ("", error2->failed_parameter);
base::Optional<CorsErrorStatus> error3 =
CheckExternalPreflight(std::string("TRUE"));
ASSERT_TRUE(error3);
EXPECT_EQ(mojom::CorsError::kPreflightInvalidAllowExternal,
error3->cors_error);
EXPECT_EQ("TRUE", error3->failed_parameter);
}
TEST_F(CorsTest, SafelistedMethod) {
// Method check should be case-insensitive.
EXPECT_TRUE(IsCorsSafelistedMethod("get"));
EXPECT_TRUE(IsCorsSafelistedMethod("Get"));
EXPECT_TRUE(IsCorsSafelistedMethod("GET"));
EXPECT_TRUE(IsCorsSafelistedMethod("HEAD"));
EXPECT_TRUE(IsCorsSafelistedMethod("POST"));
EXPECT_FALSE(IsCorsSafelistedMethod("OPTIONS"));
}
TEST_F(CorsTest, SafelistedHeader) {
// See SafelistedAccept/AcceptLanguage/ContentLanguage/ContentType also.
EXPECT_TRUE(IsCorsSafelistedHeader("accept", "foo"));
EXPECT_FALSE(IsCorsSafelistedHeader("foo", "bar"));
EXPECT_FALSE(IsCorsSafelistedHeader("user-agent", "foo"));
}
TEST_F(CorsTest, SafelistedAccept) {
EXPECT_TRUE(IsCorsSafelistedHeader("accept", "text/html"));
EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", "text/html"));
constexpr char kAllowed[] =
"\t !#$%&'*+,-./0123456789;="
"ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
for (int i = 0; i < 128; ++i) {
SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
char c = static_cast<char>(i);
// 1 for the trailing null character.
auto* end = kAllowed + base::size(kAllowed) - 1;
EXPECT_EQ(std::find(kAllowed, end, c) != end,
IsCorsSafelistedHeader("accept", std::string(1, c)));
EXPECT_EQ(std::find(kAllowed, end, c) != end,
IsCorsSafelistedHeader("AccepT", std::string(1, c)));
}
for (int i = 128; i <= 255; ++i) {
SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
char c = static_cast<char>(static_cast<unsigned char>(i));
EXPECT_TRUE(IsCorsSafelistedHeader("accept", std::string(1, c)));
EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", std::string(1, c)));
}
EXPECT_TRUE(IsCorsSafelistedHeader("accept", std::string(128, 'a')));
EXPECT_FALSE(IsCorsSafelistedHeader("accept", std::string(129, 'a')));
EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", std::string(128, 'a')));
EXPECT_FALSE(IsCorsSafelistedHeader("AccepT", std::string(129, 'a')));
}
TEST_F(CorsTest, SafelistedAcceptLanguage) {
EXPECT_TRUE(IsCorsSafelistedHeader("accept-language", "en,ja"));
EXPECT_TRUE(IsCorsSafelistedHeader("aCcEPT-lAngUAge", "en,ja"));
constexpr char kAllowed[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz *,-.;=";
for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) {
SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
char c = static_cast<char>(i);
// 1 for the trailing null character.
auto* end = kAllowed + base::size(kAllowed) - 1;
EXPECT_EQ(std::find(kAllowed, end, c) != end,
IsCorsSafelistedHeader("aCcEPT-lAngUAge", std::string(1, c)));
}
EXPECT_TRUE(IsCorsSafelistedHeader("accept-language", std::string(128, 'a')));
EXPECT_FALSE(
IsCorsSafelistedHeader("accept-language", std::string(129, 'a')));
EXPECT_TRUE(IsCorsSafelistedHeader("aCcEPT-lAngUAge", std::string(128, 'a')));
EXPECT_FALSE(
IsCorsSafelistedHeader("aCcEPT-lAngUAge", std::string(129, 'a')));
}
TEST_F(CorsTest, SafelistedSecCHLang) {
EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-Lang", "\"en\", \"de\""));
// TODO(mkwst): Validate that `Sec-CH-Lang` is a structured header.
// https://crbug.com/924969
}
TEST_F(CorsTest, SafelistedSecCHUA) {
EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA", "\"User Agent!\""));
EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA-Platform", "\"Platform!\""));
EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA-Arch", "\"Architecture!\""));
EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA-Model", "\"Model!\""));
// TODO(mkwst): Validate that `Sec-CH-UA-*` is a structured header.
// https://crbug.com/924969
}
TEST_F(CorsTest, SafelistedContentLanguage) {
EXPECT_TRUE(IsCorsSafelistedHeader("content-language", "en,ja"));
EXPECT_TRUE(IsCorsSafelistedHeader("cONTent-LANguaGe", "en,ja"));
constexpr char kAllowed[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz *,-.;=";
for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) {
SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
char c = static_cast<char>(i);
// 1 for the trailing null character.
auto* end = kAllowed + base::size(kAllowed) - 1;
EXPECT_EQ(std::find(kAllowed, end, c) != end,
IsCorsSafelistedHeader("content-language", std::string(1, c)));
EXPECT_EQ(std::find(kAllowed, end, c) != end,
IsCorsSafelistedHeader("cONTent-LANguaGe", std::string(1, c)));
}
EXPECT_TRUE(
IsCorsSafelistedHeader("content-language", std::string(128, 'a')));
EXPECT_FALSE(
IsCorsSafelistedHeader("content-language", std::string(129, 'a')));
EXPECT_TRUE(
IsCorsSafelistedHeader("cONTent-LANguaGe", std::string(128, 'a')));
EXPECT_FALSE(
IsCorsSafelistedHeader("cONTent-LANguaGe", std::string(129, 'a')));
}
TEST_F(CorsTest, SafelistedContentType) {
constexpr char kAllowed[] =
"\t !#$%&'*+,-./0123456789;="
"ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
for (int i = 0; i < 128; ++i) {
SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
const char c = static_cast<char>(i);
// 1 for the trailing null character.
const auto* const end = kAllowed + base::size(kAllowed) - 1;
const bool is_allowed = std::find(kAllowed, end, c) != end;
const std::string value = std::string("text/plain; charset=") + c;
EXPECT_EQ(is_allowed, IsCorsSafelistedHeader("content-type", value));
EXPECT_EQ(is_allowed, IsCorsSafelistedHeader("cONtent-tYPe", value));
}
for (int i = 128; i <= 255; ++i) {
SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
char c = static_cast<char>(static_cast<unsigned char>(i));
const std::string value = std::string("text/plain; charset=") + c;
EXPECT_TRUE(IsCorsSafelistedHeader("content-type", value));
EXPECT_TRUE(IsCorsSafelistedHeader("ConTEnt-Type", value));
}
EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "text/plain"));
EXPECT_TRUE(IsCorsSafelistedHeader("CoNtEnt-TyPE", "text/plain"));
EXPECT_TRUE(
IsCorsSafelistedHeader("content-type", "text/plain; charset=utf-8"));
EXPECT_TRUE(
IsCorsSafelistedHeader("content-type", " text/plain ; charset=UTF-8"));
EXPECT_TRUE(
IsCorsSafelistedHeader("content-type", "text/plain; param=BOGUS"));
EXPECT_TRUE(IsCorsSafelistedHeader("content-type",
"application/x-www-form-urlencoded"));
EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "multipart/form-data"));
EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "Text/plain"));
EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "tEXT/PLAIN"));
EXPECT_FALSE(IsCorsSafelistedHeader("content-type", "text/html"));
EXPECT_FALSE(IsCorsSafelistedHeader("CoNtEnt-TyPE", "text/html"));
EXPECT_FALSE(IsCorsSafelistedHeader("content-type", "image/png"));
EXPECT_FALSE(IsCorsSafelistedHeader("CoNtEnt-TyPE", "image/png"));
EXPECT_TRUE(IsCorsSafelistedHeader(
"content-type", "text/plain; charset=" + std::string(108, 'a')));
EXPECT_TRUE(IsCorsSafelistedHeader(
"cONTent-tYPE", "text/plain; charset=" + std::string(108, 'a')));
EXPECT_FALSE(IsCorsSafelistedHeader(
"content-type", "text/plain; charset=" + std::string(109, 'a')));
EXPECT_FALSE(IsCorsSafelistedHeader(
"cONTent-tYPE", "text/plain; charset=" + std::string(109, 'a')));
}
TEST_F(CorsTest, CheckCorsClientHintsSafelist) {
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", ""));
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "abc"));
EXPECT_TRUE(IsCorsSafelistedHeader("device-memory", "1.25"));
EXPECT_TRUE(IsCorsSafelistedHeader("DEVICE-memory", "1.25"));
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "1.25-2.5"));
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "-1.25"));
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "1e2"));
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "inf"));
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "-2.3"));
EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "NaN"));
EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", "1.25.3"));
EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", "1."));
EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", ".1"));
EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", "."));
EXPECT_TRUE(IsCorsSafelistedHeader("DEVICE-memory", "1"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", ""));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "abc"));
EXPECT_TRUE(IsCorsSafelistedHeader("dpr", "1.25"));
EXPECT_TRUE(IsCorsSafelistedHeader("Dpr", "1.25"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1.25-2.5"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "-1.25"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1e2"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "inf"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "-2.3"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "NaN"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1.25.3"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1."));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", ".1"));
EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "."));
EXPECT_TRUE(IsCorsSafelistedHeader("dpr", "1"));
EXPECT_FALSE(IsCorsSafelistedHeader("width", ""));
EXPECT_FALSE(IsCorsSafelistedHeader("width", "abc"));
EXPECT_TRUE(IsCorsSafelistedHeader("width", "125"));
EXPECT_TRUE(IsCorsSafelistedHeader("width", "1"));
EXPECT_TRUE(IsCorsSafelistedHeader("WIDTH", "125"));
EXPECT_FALSE(IsCorsSafelistedHeader("width", "125.2"));
EXPECT_FALSE(IsCorsSafelistedHeader("width", "-125"));
EXPECT_TRUE(IsCorsSafelistedHeader("width", "2147483648"));
EXPECT_FALSE(IsCorsSafelistedHeader("viewport-width", ""));
EXPECT_FALSE(IsCorsSafelistedHeader("viewport-width", "abc"));
EXPECT_TRUE(IsCorsSafelistedHeader("viewport-width", "125"));
EXPECT_TRUE(IsCorsSafelistedHeader("viewport-width", "1"));
EXPECT_TRUE(IsCorsSafelistedHeader("viewport-Width", "125"));
EXPECT_FALSE(IsCorsSafelistedHeader("viewport-width", "125.2"));
EXPECT_TRUE(IsCorsSafelistedHeader("viewport-width", "2147483648"));
}
TEST_F(CorsTest, CorsUnsafeRequestHeaderNames) {
// Needed because initializer list is not allowed for a macro argument.
using List = std::vector<std::string>;
// Empty => Empty
EXPECT_EQ(CorsUnsafeRequestHeaderNames({}), List({}));
// Some headers are safelisted.
EXPECT_EQ(CorsUnsafeRequestHeaderNames({{"content-type", "text/plain"},
{"dpr", "12345"},
{"aCCept", "en,ja"},
{"accept-charset", "utf-8"},
{"uSer-Agent", "foo"},
{"hogE", "fuga"}}),
List({"accept-charset", "user-agent", "hoge"}));
// All headers are not safelisted.
EXPECT_EQ(
CorsUnsafeRequestHeaderNames({{"content-type", "text/html"},
{"dpr", "123-45"},
{"aCCept", "en,ja"},
{"accept-charset", "utf-8"},
{"uSer-Agent", "foo"},
{"hogE", "fuga"}}),
List({"content-type", "dpr", "accept-charset", "user-agent", "hoge"}));
// |safelistValueSize| is 1024.
EXPECT_EQ(
CorsUnsafeRequestHeaderNames(
{{"content-type", "text/plain; charset=" + std::string(108, '1')},
{"accept", std::string(128, '1')},
{"accept-language", std::string(128, '1')},
{"content-language", std::string(128, '1')},
{"dpr", std::string(128, '1')},
{"device-memory", std::string(128, '1')},
{"save-data", "on"},
{"viewport-width", std::string(128, '1')},
{"width", std::string(126, '1')},
{"hogE", "fuga"}}),
List({"hoge"}));
// |safelistValueSize| is 1025.
EXPECT_EQ(
CorsUnsafeRequestHeaderNames(
{{"content-type", "text/plain; charset=" + std::string(108, '1')},
{"accept", std::string(128, '1')},
{"accept-language", std::string(128, '1')},
{"content-language", std::string(128, '1')},
{"dpr", std::string(128, '1')},
{"device-memory", std::string(128, '1')},
{"save-data", "on"},
{"viewport-width", std::string(128, '1')},
{"width", std::string(127, '1')},
{"hogE", "fuga"}}),
List({"hoge", "content-type", "accept", "accept-language",
"content-language", "dpr", "device-memory", "save-data",
"viewport-width", "width"}));
// |safelistValueSize| is 897 because "content-type" is not safelisted.
EXPECT_EQ(
CorsUnsafeRequestHeaderNames(
{{"content-type", "text/plain; charset=" + std::string(128, '1')},
{"accept", std::string(128, '1')},
{"accept-language", std::string(128, '1')},
{"content-language", std::string(128, '1')},
{"dpr", std::string(128, '1')},
{"device-memory", std::string(128, '1')},
{"save-data", "on"},
{"viewport-width", std::string(128, '1')},
{"width", std::string(127, '1')},
{"hogE", "fuga"}}),
List({"content-type", "hoge"}));
}
TEST_F(CorsTest, CorsUnsafeNotForbiddenRequestHeaderNames) {
// Needed because initializer list is not allowed for a macro argument.
using List = std::vector<std::string>;
// Empty => Empty
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames({}, false /* is_revalidating */),
List({}));
// "user-agent" is NOT forbidden per spec, but forbidden in Chromium.
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/plain"},
{"dpr", "12345"},
{"aCCept", "en,ja"},
{"accept-charset", "utf-8"},
{"uSer-Agent", "foo"},
{"hogE", "fuga"}},
false /* is_revalidating */),
List({"hoge"}));
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/html"},
{"dpr", "123-45"},
{"aCCept", "en,ja"},
{"accept-charset", "utf-8"},
{"hogE", "fuga"}},
false /* is_revalidating */),
List({"content-type", "dpr", "hoge"}));
// |safelistValueSize| is 1024.
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames(
{{"content-type", "text/plain; charset=" + std::string(108, '1')},
{"accept", std::string(128, '1')},
{"accept-language", std::string(128, '1')},
{"content-language", std::string(128, '1')},
{"dpr", std::string(128, '1')},
{"device-memory", std::string(128, '1')},
{"save-data", "on"},
{"viewport-width", std::string(128, '1')},
{"width", std::string(126, '1')},
{"accept-charset", "utf-8"},
{"hogE", "fuga"}},
false /* is_revalidating */),
List({"hoge"}));
// |safelistValueSize| is 1025.
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames(
{{"content-type", "text/plain; charset=" + std::string(108, '1')},
{"accept", std::string(128, '1')},
{"accept-language", std::string(128, '1')},
{"content-language", std::string(128, '1')},
{"dpr", std::string(128, '1')},
{"device-memory", std::string(128, '1')},
{"save-data", "on"},
{"viewport-width", std::string(128, '1')},
{"width", std::string(127, '1')},
{"accept-charset", "utf-8"},
{"hogE", "fuga"}},
false /* is_revalidating */),
List({"hoge", "content-type", "accept", "accept-language",
"content-language", "dpr", "device-memory", "save-data",
"viewport-width", "width"}));
// |safelistValueSize| is 897 because "content-type" is not safelisted.
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames(
{{"content-type", "text/plain; charset=" + std::string(128, '1')},
{"accept", std::string(128, '1')},
{"accept-language", std::string(128, '1')},
{"content-language", std::string(128, '1')},
{"dpr", std::string(128, '1')},
{"device-memory", std::string(128, '1')},
{"save-data", "on"},
{"viewport-width", std::string(128, '1')},
{"width", std::string(127, '1')},
{"accept-charset", "utf-8"},
{"hogE", "fuga"}},
false /* is_revalidating */),
List({"content-type", "hoge"}));
}
TEST_F(CorsTest, CorsUnsafeNotForbiddenRequestHeaderNamesWithRevalidating) {
// Needed because initializer list is not allowed for a macro argument.
using List = std::vector<std::string>;
// Empty => Empty
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames({}, true /* is_revalidating */),
List({}));
// These three headers will be ignored.
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames({{"If-MODifIED-since", "x"},
{"iF-nONE-MATCh", "y"},
{"CACHE-ContrOl", "z"}},
true /* is_revalidating */),
List({}));
// Without is_revalidating set, these three headers will not be safelisted.
EXPECT_EQ(
CorsUnsafeNotForbiddenRequestHeaderNames({{"If-MODifIED-since", "x"},
{"iF-nONE-MATCh", "y"},
{"CACHE-ContrOl", "z"}},
false /* is_revalidating */),
List({"if-modified-since", "if-none-match", "cache-control"}));
}
TEST_F(CorsTest, NoCorsSafelistedHeaderName) {
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("accept"));
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("AcCePT"));
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("accept-language"));
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("acCEPt-lAnguage"));
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("content-language"));
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("coNTENt-lAnguage"));
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("content-type"));
EXPECT_TRUE(IsNoCorsSafelistedHeaderName("CONTENT-TYPE"));
EXPECT_FALSE(IsNoCorsSafelistedHeaderName("range"));
EXPECT_FALSE(IsNoCorsSafelistedHeaderName("cookie"));
EXPECT_FALSE(IsNoCorsSafelistedHeaderName("foobar"));
}
TEST_F(CorsTest, PrivilegedNoCorsHeaderName) {
EXPECT_TRUE(IsPrivilegedNoCorsHeaderName("range"));
EXPECT_TRUE(IsPrivilegedNoCorsHeaderName("RanGe"));
EXPECT_FALSE(IsPrivilegedNoCorsHeaderName("content-type"));
EXPECT_FALSE(IsPrivilegedNoCorsHeaderName("foobar"));
EXPECT_FALSE(IsPrivilegedNoCorsHeaderName("cookie"));
}
TEST_F(CorsTest, IsForbiddenMethod) {
EXPECT_TRUE(IsForbiddenMethod("connect"));
EXPECT_TRUE(IsForbiddenMethod("CONNECT"));
EXPECT_TRUE(IsForbiddenMethod("Connect"));
EXPECT_TRUE(IsForbiddenMethod("CoNnEcT"));
EXPECT_FALSE(IsForbiddenMethod("C0NNECT"));
EXPECT_TRUE(IsForbiddenMethod("trace"));
EXPECT_TRUE(IsForbiddenMethod("track"));
EXPECT_FALSE(IsForbiddenMethod("trac"));
EXPECT_FALSE(IsForbiddenMethod("tracz"));
}
} // namespace
} // namespace cors
} // namespace network