Commit 0f0e8b3b authored by Yutaka Hirano's avatar Yutaka Hirano Committed by Commit Bot

[CORS] Be strict on request's Content-Type

Following the corresponding spec change:
https://github.com/whatwg/fetch/pull/829

Bug: 902681
Change-Id: If0da598ada489417c34926935acdd3cfff519aa7
Reviewed-on: https://chromium-review.googlesource.com/c/1329815Reviewed-by: default avatarTakashi Toyoshima <toyoshim@chromium.org>
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#607847}
parent 24f56e3a
......@@ -88,11 +88,21 @@ bool IsSimilarToIntABNF(const std::string& header_value) {
return true;
}
// |lower_case_media_type| should be lower case.
bool IsCORSSafelistedLowerCaseContentType(
const std::string& lower_case_media_type) {
DCHECK_EQ(lower_case_media_type, base::ToLowerASCII(lower_case_media_type));
std::string mime_type = ExtractMIMETypeFromMediaType(lower_case_media_type);
// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte
bool IsCorsUnsafeRequestHeaderByte(char c) {
return (c < 0x20 && c != 0x09) || c == 0x22 || c == 0x28 || c == 0x29 ||
c == 0x3a || c == 0x3c || c == 0x3e || c == 0x3f || c == 0x40 ||
c == 0x5b || c == 0x5c || c == 0x5d || c == 0x7b || c == 0x7d ||
c >= 0x7f;
}
// |value| should be lower case.
bool IsCORSSafelistedLowerCaseContentType(const std::string& value) {
DCHECK_EQ(value, base::ToLowerASCII(value));
if (std::any_of(value.begin(), value.end(), IsCorsUnsafeRequestHeaderByte))
return false;
std::string mime_type = ExtractMIMETypeFromMediaType(value);
return mime_type == "application/x-www-form-urlencoded" ||
mime_type == "multipart/form-data" || mime_type == "text/plain";
}
......@@ -388,12 +398,8 @@ bool IsCORSSafelistedHeader(const std::string& name, const std::string& value) {
return lower_value == "on";
if (lower_name == "accept") {
return (value.end() == std::find_if(value.begin(), value.end(), [](char c) {
return (c < 0x20 && c != 0x09) || c == 0x22 || c == 0x28 ||
c == 0x29 || c == 0x3a || c == 0x3c || c == 0x3e ||
c == 0x3f || c == 0x40 || c == 0x5b || c == 0x5c ||
c == 0x5d || c == 0x7b || c == 0x7d || c >= 0x7f;
}));
return !std::any_of(value.begin(), value.end(),
IsCorsUnsafeRequestHeaderByte);
}
if (lower_name == "accept-language" || lower_name == "content-language") {
......
......@@ -470,6 +470,21 @@ TEST_F(CORSTest, SafelistedContentLanguage) {
}
TEST_F(CORSTest, SafelistedContentType) {
constexpr char kAllowed[] =
"\t !#$%&'*+,-./0123456789;="
"ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
for (int i = CHAR_MIN; i <= CHAR_MAX; ++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));
}
EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "text/plain"));
EXPECT_TRUE(IsCORSSafelistedHeader("CoNtEnt-TyPE", "text/plain"));
EXPECT_TRUE(
......
......@@ -3,16 +3,16 @@ FAIL Preflight for {"content-type":"text /plain"} assert_unreached: Should have
FAIL Preflight for {"content-type":"text\t/\tplain"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS No preflight for {"content-type":"text/plain;"}
PASS No preflight for {"content-type":"text/plain;garbage"}
FAIL Preflight for {"content-type":"text/plain;garbage\u0001\u0002"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"text/plain;garbage\u0001\u0002"}
PASS Preflight for {"content-type":"text/plain,"}
PASS Preflight for {"content-type":",text/plain"}
PASS Preflight for {"content-type":"text/plain,text/plain"}
PASS Preflight for {"content-type":"text/plain,x/x"}
FAIL Preflight for {"content-type":"text/plain\u000b"} assert_unreached: Should have rejected: undefined Reached unreachable code
FAIL Preflight for {"content-type":"text/plain\f"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"text/plain\u000b"}
PASS Preflight for {"content-type":"text/plain\f"}
PASS Preflight for {"content-type":"application/www-form-urlencoded"}
FAIL Preflight for {"content-type":"application/x-www-form-urlencoded;"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"application/x-www-form-urlencoded;"}
PASS No preflight for {"content-type":"multipart/form-data"}
FAIL Preflight for {"content-type":"multipart/form-data;\""} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"multipart/form-data;\""}
Harness: the test ran to completion.
......@@ -3,16 +3,16 @@ FAIL Preflight for {"content-type":"text /plain"} assert_unreached: Should have
FAIL Preflight for {"content-type":"text\t/\tplain"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS No preflight for {"content-type":"text/plain;"}
PASS No preflight for {"content-type":"text/plain;garbage"}
FAIL Preflight for {"content-type":"text/plain;garbage\u0001\u0002"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"text/plain;garbage\u0001\u0002"}
PASS Preflight for {"content-type":"text/plain,"}
PASS Preflight for {"content-type":",text/plain"}
PASS Preflight for {"content-type":"text/plain,text/plain"}
PASS Preflight for {"content-type":"text/plain,x/x"}
FAIL Preflight for {"content-type":"text/plain\u000b"} assert_unreached: Should have rejected: undefined Reached unreachable code
FAIL Preflight for {"content-type":"text/plain\f"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"text/plain\u000b"}
PASS Preflight for {"content-type":"text/plain\f"}
PASS Preflight for {"content-type":"application/www-form-urlencoded"}
FAIL Preflight for {"content-type":"application/x-www-form-urlencoded;"} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"application/x-www-form-urlencoded;"}
PASS No preflight for {"content-type":"multipart/form-data"}
FAIL Preflight for {"content-type":"multipart/form-data;\""} assert_unreached: Should have rejected: undefined Reached unreachable code
PASS Preflight for {"content-type":"multipart/form-data;\""}
Harness: the test ran to completion.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment