Commit 59a2bc8a authored by Yutaka Hirano's avatar Yutaka Hirano Committed by Commit Bot

[Fetch] Support wildcard for access-control-expose-headers

Bug: 615313
Change-Id: Ie608f427083303af5afd98e8939dcfc43a266942
Reviewed-on: https://chromium-review.googlesource.com/805395Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarTakeshi Yoshino <tyoshino@chromium.org>
Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#522082}
parent 26635467
This is a testharness.js-based test.
FAIL Basic Access-Control-Expose-Headers: * support assert_equals: expected (string) "X" but got (object) null
PASS * for credentialed fetches only matches literally
FAIL * can be one of several values assert_equals: expected (string) "X" but got (object) null
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL Basic Access-Control-Expose-Headers: * support assert_equals: expected (string) "X" but got (object) null
PASS * for credentialed fetches only matches literally
FAIL * can be one of several values assert_equals: expected (string) "X" but got (object) null
Harness: the test ran to completion.
...@@ -31,7 +31,7 @@ promise_test(() => { ...@@ -31,7 +31,7 @@ promise_test(() => {
}, "* for credentialed fetches only matches literally") }, "* for credentialed fetches only matches literally")
promise_test(() => { promise_test(() => {
const headers = "header(Access-Control-Allow-Origin,*)|header(Access-Control-Expose-Headers,set-cookie)" const headers = "header(Access-Control-Allow-Origin,*)|header(Access-Control-Expose-Headers,set-cookie\\,*)"
return fetch(url + sharedHeaders + headers).then(resp => { return fetch(url + sharedHeaders + headers).then(resp => {
assert_equals(resp.status, 200) assert_equals(resp.status, 200)
assert_equals(resp.type , "cors") assert_equals(resp.type , "cors")
......
if (self.importScripts) { if (self.importScripts) {
importScripts('/resources/testharness.js'); importScripts('/resources/testharness.js');
importScripts('../resources/test-helpers.js'); importScripts('../resources/test-helpers.js');
importScripts('/common/get-host-info.sub.js');
} }
prepopulated_cache_test(simple_entries, function(cache, entries) { prepopulated_cache_test(simple_entries, function(cache, entries) {
...@@ -318,4 +319,22 @@ cache_test(function(cache) { ...@@ -318,4 +319,22 @@ cache_test(function(cache) {
}); });
}, 'Cache produces large Responses that can be cloned and read correctly.'); }, 'Cache produces large Responses that can be cloned and read correctly.');
cache_test(async (cache) => {
const url = get_host_info().HTTPS_REMOTE_ORIGIN +
'/service-workers/cache-storage/resources/simple.txt?pipe=' +
'header(access-control-allow-origin,*)|' +
'header(access-control-expose-headers,*)|' +
'header(foo,bar)|' +
'header(set-cookie,X)';
const response = await fetch(url);
await cache.put(new Request(url), response);
const cached_response = await cache.match(url);
const headers = cached_response.headers;
assert_equals(headers.get('access-control-expose-headers'), '*');
assert_equals(headers.get('foo'), 'bar');
assert_equals(headers.get('set-cookie'), null);
}, 'cors-exposed header should be stored correctly.');
done(); done();
...@@ -4,5 +4,6 @@ ...@@ -4,5 +4,6 @@
<meta name="timeout" content="long"> <meta name="timeout" content="long">
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="../resources/test-helpers.js"></script> <script src="../resources/test-helpers.js"></script>
<script src="../script-tests/cache-match.js"></script> <script src="../script-tests/cache-match.js"></script>
<!DOCTYPE html>
<title>Service Worker: CORS-exposed header names should be transferred correctly</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script>
promise_test(async function(t) {
const SCOPE = 'resources/simple.html';
const SCRIPT = 'resources/fetch-cors-exposed-header-names-worker.js';
const host_info = get_host_info();
const URL = get_host_info().HTTPS_REMOTE_ORIGIN +
'/service-workers/service-worker/resources/simple.txt?pipe=' +
'header(access-control-allow-origin,*)|' +
'header(access-control-expose-headers,*)|' +
'header(foo,bar)|' +
'header(set-cookie,X)';
const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
await wait_for_state(t, reg.installing, 'activated');
const frame = await with_iframe(SCOPE);
const response = await frame.contentWindow.fetch(URL);
const headers = response.headers;
assert_equals(headers.get('foo'), 'bar');
assert_equals(headers.get('set-cookie'), null);
assert_equals(headers.get('access-control-expose-headers'), '*');
}, 'CORS-exposed header names for a response from sw');
</script>
...@@ -99,6 +99,7 @@ class WebAssociatedURLLoaderImpl::ClientAdapter final ...@@ -99,6 +99,7 @@ class WebAssociatedURLLoaderImpl::ClientAdapter final
WebAssociatedURLLoaderClient*, WebAssociatedURLLoaderClient*,
const WebAssociatedURLLoaderOptions&, const WebAssociatedURLLoaderOptions&,
network::mojom::FetchRequestMode, network::mojom::FetchRequestMode,
network::mojom::FetchCredentialsMode,
scoped_refptr<WebTaskRunner>); scoped_refptr<WebTaskRunner>);
// ThreadableLoaderClient // ThreadableLoaderClient
...@@ -142,6 +143,7 @@ class WebAssociatedURLLoaderImpl::ClientAdapter final ...@@ -142,6 +143,7 @@ class WebAssociatedURLLoaderImpl::ClientAdapter final
WebAssociatedURLLoaderClient*, WebAssociatedURLLoaderClient*,
const WebAssociatedURLLoaderOptions&, const WebAssociatedURLLoaderOptions&,
network::mojom::FetchRequestMode, network::mojom::FetchRequestMode,
network::mojom::FetchCredentialsMode,
scoped_refptr<WebTaskRunner>); scoped_refptr<WebTaskRunner>);
void NotifyError(TimerBase*); void NotifyError(TimerBase*);
...@@ -150,6 +152,7 @@ class WebAssociatedURLLoaderImpl::ClientAdapter final ...@@ -150,6 +152,7 @@ class WebAssociatedURLLoaderImpl::ClientAdapter final
WebAssociatedURLLoaderClient* client_; WebAssociatedURLLoaderClient* client_;
WebAssociatedURLLoaderOptions options_; WebAssociatedURLLoaderOptions options_;
network::mojom::FetchRequestMode fetch_request_mode_; network::mojom::FetchRequestMode fetch_request_mode_;
network::mojom::FetchCredentialsMode credentials_mode_;
Optional<WebURLError> error_; Optional<WebURLError> error_;
TaskRunnerTimer<ClientAdapter> error_timer_; TaskRunnerTimer<ClientAdapter> error_timer_;
...@@ -165,9 +168,11 @@ WebAssociatedURLLoaderImpl::ClientAdapter::Create( ...@@ -165,9 +168,11 @@ WebAssociatedURLLoaderImpl::ClientAdapter::Create(
WebAssociatedURLLoaderClient* client, WebAssociatedURLLoaderClient* client,
const WebAssociatedURLLoaderOptions& options, const WebAssociatedURLLoaderOptions& options,
network::mojom::FetchRequestMode fetch_request_mode, network::mojom::FetchRequestMode fetch_request_mode,
network::mojom::FetchCredentialsMode credentials_mode,
scoped_refptr<WebTaskRunner> task_runner) { scoped_refptr<WebTaskRunner> task_runner) {
return WTF::WrapUnique(new ClientAdapter(loader, client, options, return WTF::WrapUnique(new ClientAdapter(loader, client, options,
fetch_request_mode, task_runner)); fetch_request_mode, credentials_mode,
task_runner));
} }
WebAssociatedURLLoaderImpl::ClientAdapter::ClientAdapter( WebAssociatedURLLoaderImpl::ClientAdapter::ClientAdapter(
...@@ -175,11 +180,13 @@ WebAssociatedURLLoaderImpl::ClientAdapter::ClientAdapter( ...@@ -175,11 +180,13 @@ WebAssociatedURLLoaderImpl::ClientAdapter::ClientAdapter(
WebAssociatedURLLoaderClient* client, WebAssociatedURLLoaderClient* client,
const WebAssociatedURLLoaderOptions& options, const WebAssociatedURLLoaderOptions& options,
network::mojom::FetchRequestMode fetch_request_mode, network::mojom::FetchRequestMode fetch_request_mode,
network::mojom::FetchCredentialsMode credentials_mode,
scoped_refptr<WebTaskRunner> task_runner) scoped_refptr<WebTaskRunner> task_runner)
: loader_(loader), : loader_(loader),
client_(client), client_(client),
options_(options), options_(options),
fetch_request_mode_(fetch_request_mode), fetch_request_mode_(fetch_request_mode),
credentials_mode_(credentials_mode),
error_timer_(std::move(task_runner), this, &ClientAdapter::NotifyError), error_timer_(std::move(task_runner), this, &ClientAdapter::NotifyError),
enable_error_notifications_(false), enable_error_notifications_(false),
did_fail_(false) { did_fail_(false) {
...@@ -226,9 +233,8 @@ void WebAssociatedURLLoaderImpl::ClientAdapter::DidReceiveResponse( ...@@ -226,9 +233,8 @@ void WebAssociatedURLLoaderImpl::ClientAdapter::DidReceiveResponse(
return; return;
} }
WebHTTPHeaderSet exposed_headers; WebHTTPHeaderSet exposed_headers = WebCORS::ExtractCorsExposedHeaderNamesList(
WebCORS::ExtractCorsExposedHeaderNamesList(WrappedResourceResponse(response), credentials_mode_, WrappedResourceResponse(response));
exposed_headers);
WebHTTPHeaderSet blocked_headers; WebHTTPHeaderSet blocked_headers;
for (const auto& header : response.HttpHeaderFields()) { for (const auto& header : response.HttpHeaderFields()) {
if (FetchUtils::IsForbiddenResponseHeaderName(header.key) || if (FetchUtils::IsForbiddenResponseHeaderName(header.key) ||
...@@ -397,9 +403,9 @@ void WebAssociatedURLLoaderImpl::LoadAsynchronously( ...@@ -397,9 +403,9 @@ void WebAssociatedURLLoaderImpl::LoadAsynchronously(
task_runner = Platform::Current()->CurrentThread()->GetWebTaskRunner(); task_runner = Platform::Current()->CurrentThread()->GetWebTaskRunner();
} }
client_ = client; client_ = client;
client_adapter_ = ClientAdapter::Create(this, client, options_, client_adapter_ = ClientAdapter::Create(
request.GetFetchRequestMode(), this, client, options_, request.GetFetchRequestMode(),
std::move(task_runner)); request.GetFetchCredentialsMode(), std::move(task_runner));
if (allow_load) { if (allow_load) {
ThreadableLoaderOptions options; ThreadableLoaderOptions options;
......
...@@ -1456,9 +1456,11 @@ String XMLHttpRequest::getAllResponseHeaders() const { ...@@ -1456,9 +1456,11 @@ String XMLHttpRequest::getAllResponseHeaders() const {
StringBuilder string_builder; StringBuilder string_builder;
WebHTTPHeaderSet access_control_expose_header_set; WebHTTPHeaderSet access_control_expose_header_set =
WebCORS::ExtractCorsExposedHeaderNamesList(WrappedResourceResponse(response_), WebCORS::ExtractCorsExposedHeaderNamesList(
access_control_expose_header_set); with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude
: network::mojom::FetchCredentialsMode::kSameOrigin,
WrappedResourceResponse(response_));
HTTPHeaderMap::const_iterator end = response_.HttpHeaderFields().end(); HTTPHeaderMap::const_iterator end = response_.HttpHeaderFields().end();
for (HTTPHeaderMap::const_iterator it = response_.HttpHeaderFields().begin(); for (HTTPHeaderMap::const_iterator it = response_.HttpHeaderFields().begin();
...@@ -1502,9 +1504,11 @@ const AtomicString& XMLHttpRequest::getResponseHeader( ...@@ -1502,9 +1504,11 @@ const AtomicString& XMLHttpRequest::getResponseHeader(
return g_null_atom; return g_null_atom;
} }
WebHTTPHeaderSet access_control_expose_header_set; WebHTTPHeaderSet access_control_expose_header_set =
WebCORS::ExtractCorsExposedHeaderNamesList(WrappedResourceResponse(response_), WebCORS::ExtractCorsExposedHeaderNamesList(
access_control_expose_header_set); with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude
: network::mojom::FetchCredentialsMode::kSameOrigin,
WrappedResourceResponse(response_));
if (!same_origin_request_ && if (!same_origin_request_ &&
!WebCORS::IsOnAccessControlResponseHeaderWhitelist(name) && !WebCORS::IsOnAccessControlResponseHeaderWhitelist(name) &&
......
...@@ -474,9 +474,9 @@ void FetchManager::Loader::DidReceiveResponse( ...@@ -474,9 +474,9 @@ void FetchManager::Loader::DidReceiveResponse(
tainted_response = response_data->CreateBasicFilteredResponse(); tainted_response = response_data->CreateBasicFilteredResponse();
break; break;
case FetchRequestData::kCORSTainting: { case FetchRequestData::kCORSTainting: {
WebHTTPHeaderSet header_names; WebHTTPHeaderSet header_names =
WebCORS::ExtractCorsExposedHeaderNamesList( WebCORS::ExtractCorsExposedHeaderNamesList(
WrappedResourceResponse(response), header_names); request_->Credentials(), WrappedResourceResponse(response));
tainted_response = tainted_response =
response_data->CreateCORSFilteredResponse(header_names); response_data->CreateCORSFilteredResponse(header_names);
break; break;
......
...@@ -70,18 +70,6 @@ FetchResponseData* FetchResponseData::CreateBasicFilteredResponse() const { ...@@ -70,18 +70,6 @@ FetchResponseData* FetchResponseData::CreateBasicFilteredResponse() const {
return response; return response;
} }
FetchResponseData* FetchResponseData::CreateCORSFilteredResponse() const {
DCHECK_EQ(type_, Type::kDefault);
WebHTTPHeaderSet access_control_expose_header_set;
String access_control_expose_headers;
if (header_list_->Get(HTTPNames::Access_Control_Expose_Headers,
access_control_expose_headers)) {
WebCORS::ParseAccessControlExposeHeadersAllowList(
access_control_expose_headers, access_control_expose_header_set);
}
return CreateCORSFilteredResponse(access_control_expose_header_set);
}
FetchResponseData* FetchResponseData::CreateCORSFilteredResponse( FetchResponseData* FetchResponseData::CreateCORSFilteredResponse(
const WebHTTPHeaderSet& exposed_headers) const { const WebHTTPHeaderSet& exposed_headers) const {
DCHECK_EQ(type_, Type::kDefault); DCHECK_EQ(type_, Type::kDefault);
...@@ -97,18 +85,13 @@ FetchResponseData* FetchResponseData::CreateCORSFilteredResponse( ...@@ -97,18 +85,13 @@ FetchResponseData* FetchResponseData::CreateCORSFilteredResponse(
response->SetURLList(url_list_); response->SetURLList(url_list_);
for (const auto& header : header_list_->List()) { for (const auto& header : header_list_->List()) {
const String& name = header.first; const String& name = header.first;
const bool explicitly_exposed =
exposed_headers.find(name.Ascii().data()) != exposed_headers.end();
if (WebCORS::IsOnAccessControlResponseHeaderWhitelist(name) || if (WebCORS::IsOnAccessControlResponseHeaderWhitelist(name) ||
(explicitly_exposed && (exposed_headers.find(name.Ascii().data()) != exposed_headers.end() &&
!FetchUtils::IsForbiddenResponseHeaderName(name))) { !FetchUtils::IsForbiddenResponseHeaderName(name))) {
if (explicitly_exposed) {
response->cors_exposed_header_names_.emplace(name.Ascii().data(),
name.Ascii().length());
}
response->header_list_->Append(name, header.second); response->header_list_->Append(name, header.second);
} }
} }
response->cors_exposed_header_names_ = exposed_headers;
response->buffer_ = buffer_; response->buffer_ = buffer_;
response->mime_type_ = mime_type_; response->mime_type_ = mime_type_;
response->internal_response_ = const_cast<FetchResponseData*>(this); response->internal_response_ = const_cast<FetchResponseData*>(this);
......
...@@ -43,12 +43,6 @@ class MODULES_EXPORT FetchResponseData final ...@@ -43,12 +43,6 @@ class MODULES_EXPORT FetchResponseData final
static FetchResponseData* CreateWithBuffer(BodyStreamBuffer*); static FetchResponseData* CreateWithBuffer(BodyStreamBuffer*);
FetchResponseData* CreateBasicFilteredResponse() const; FetchResponseData* CreateBasicFilteredResponse() const;
// Creates a CORS filtered response, settings the response's cors exposed
// header names list to the result of parsing the
// Access-Control-Expose-Headers header.
FetchResponseData* CreateCORSFilteredResponse() const;
// Creates a CORS filtered response with an explicit set of exposed header
// names.
FetchResponseData* CreateCORSFilteredResponse( FetchResponseData* CreateCORSFilteredResponse(
const WebHTTPHeaderSet& exposed_headers) const; const WebHTTPHeaderSet& exposed_headers) const;
FetchResponseData* CreateOpaqueFilteredResponse() const; FetchResponseData* CreateOpaqueFilteredResponse() const;
......
...@@ -100,7 +100,7 @@ TEST_F(FetchResponseDataTest, ToWebServiceWorkerBasicType) { ...@@ -100,7 +100,7 @@ TEST_F(FetchResponseDataTest, ToWebServiceWorkerBasicType) {
TEST_F(FetchResponseDataTest, CORSFilter) { TEST_F(FetchResponseDataTest, CORSFilter) {
FetchResponseData* internal_response = CreateInternalResponse(); FetchResponseData* internal_response = CreateInternalResponse();
FetchResponseData* cors_response_data = FetchResponseData* cors_response_data =
internal_response->CreateCORSFilteredResponse(); internal_response->CreateCORSFilteredResponse(WebHTTPHeaderSet());
EXPECT_EQ(internal_response, cors_response_data->InternalResponse()); EXPECT_EQ(internal_response, cors_response_data->InternalResponse());
...@@ -122,7 +122,7 @@ TEST_F(FetchResponseDataTest, ...@@ -122,7 +122,7 @@ TEST_F(FetchResponseDataTest,
"set-cookie, bar"); "set-cookie, bar");
FetchResponseData* cors_response_data = FetchResponseData* cors_response_data =
internal_response->CreateCORSFilteredResponse(); internal_response->CreateCORSFilteredResponse({"set-cookie", "bar"});
EXPECT_EQ(internal_response, cors_response_data->InternalResponse()); EXPECT_EQ(internal_response, cors_response_data->InternalResponse());
...@@ -197,7 +197,7 @@ TEST_F(FetchResponseDataTest, ToWebServiceWorkerCORSType) { ...@@ -197,7 +197,7 @@ TEST_F(FetchResponseDataTest, ToWebServiceWorkerCORSType) {
WebServiceWorkerResponse web_response; WebServiceWorkerResponse web_response;
FetchResponseData* internal_response = CreateInternalResponse(); FetchResponseData* internal_response = CreateInternalResponse();
FetchResponseData* cors_response_data = FetchResponseData* cors_response_data =
internal_response->CreateCORSFilteredResponse(); internal_response->CreateCORSFilteredResponse(WebHTTPHeaderSet());
cors_response_data->PopulateWebServiceWorkerResponse(web_response); cors_response_data->PopulateWebServiceWorkerResponse(web_response);
EXPECT_EQ(network::mojom::FetchResponseType::kCORS, EXPECT_EQ(network::mojom::FetchResponseType::kCORS,
......
...@@ -263,7 +263,7 @@ TEST(ServiceWorkerResponseTest, BodyStreamBufferCloneCORS) { ...@@ -263,7 +263,7 @@ TEST(ServiceWorkerResponseTest, BodyStreamBufferCloneCORS) {
Vector<KURL> url_list; Vector<KURL> url_list;
url_list.push_back(KURL("http://www.response.com")); url_list.push_back(KURL("http://www.response.com"));
fetch_response_data->SetURLList(url_list); fetch_response_data->SetURLList(url_list);
fetch_response_data = fetch_response_data->CreateCORSFilteredResponse(); fetch_response_data = fetch_response_data->CreateCORSFilteredResponse({});
Response* response = Response* response =
Response::Create(scope.GetExecutionContext(), fetch_response_data); Response::Create(scope.GetExecutionContext(), fetch_response_data);
EXPECT_EQ(response->InternalBodyBuffer(), buffer); EXPECT_EQ(response->InternalBodyBuffer(), buffer);
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "platform/loader/fetch/FetchUtils.h" #include "platform/loader/fetch/FetchUtils.h"
#include "platform/loader/fetch/ResourceLoaderOptions.h" #include "platform/loader/fetch/ResourceLoaderOptions.h"
#include "platform/loader/fetch/ResourceRequest.h" #include "platform/loader/fetch/ResourceRequest.h"
#include "platform/loader/fetch/ResourceResponse.h"
#include "platform/network/http_names.h" #include "platform/network/http_names.h"
#include "platform/weborigin/KURL.h" #include "platform/weborigin/KURL.h"
#include "platform/weborigin/SchemeRegistry.h" #include "platform/weborigin/SchemeRegistry.h"
...@@ -513,27 +514,35 @@ WebString GetErrorString(const CORSError error, ...@@ -513,27 +514,35 @@ WebString GetErrorString(const CORSError error,
return WebString(); return WebString();
} }
void ExtractCorsExposedHeaderNamesList(const WebURLResponse& response, WebHTTPHeaderSet ExtractCorsExposedHeaderNamesList(
WebHTTPHeaderSet& header_set) { network::mojom::FetchCredentialsMode credentials_mode,
const WebURLResponse& response) {
// If a response was fetched via a service worker, it will always have // If a response was fetched via a service worker, it will always have
// CorsExposedHeaderNames set from the Access-Control-Expose-Headers header. // CorsExposedHeaderNames set from the Access-Control-Expose-Headers header.
// For requests that didn't come from a service worker, just parse the CORS // For requests that didn't come from a service worker, just parse the CORS
// header. // header.
if (response.WasFetchedViaServiceWorker()) { if (response.WasFetchedViaServiceWorker()) {
WebHTTPHeaderSet header_set;
for (const auto& header : response.CorsExposedHeaderNames()) for (const auto& header : response.CorsExposedHeaderNames())
header_set.emplace(header.Ascii().data(), header.Ascii().length()); header_set.emplace(header.Ascii().data(), header.Ascii().length());
return; return header_set;
} }
ParseAccessControlExposeHeadersAllowList(
response.HttpHeaderField(
WebString(HTTPNames::Access_Control_Expose_Headers)),
header_set);
}
void ParseAccessControlExposeHeadersAllowList(const WebString& header_value, WebHTTPHeaderSet header_set;
WebHTTPHeaderSet& header_set) { HTTPHeaderNameListParser parser(response.HttpHeaderField(
HTTPHeaderNameListParser parser(header_value); WebString(HTTPNames::Access_Control_Expose_Headers)));
parser.Parse(header_set); parser.Parse(header_set);
if (credentials_mode != network::mojom::FetchCredentialsMode::kInclude &&
header_set.find("*") != header_set.end()) {
header_set.clear();
for (const auto& header :
response.ToResourceResponse().HttpHeaderFields()) {
CString name = header.key.Ascii();
header_set.emplace(name.data(), name.length());
}
}
return header_set;
} }
bool IsOnAccessControlResponseHeaderWhitelist(const WebString& name) { bool IsOnAccessControlResponseHeaderWhitelist(const WebString& name) {
......
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
#include "public/platform/WebCORS.h" #include "public/platform/WebCORS.h"
#include "platform/exported/WrappedResourceResponse.h"
#include "platform/loader/fetch/ResourceRequest.h" #include "platform/loader/fetch/ResourceRequest.h"
#include "platform/loader/fetch/ResourceResponse.h"
#include "platform/weborigin/SecurityOrigin.h" #include "platform/weborigin/SecurityOrigin.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -12,6 +14,20 @@ namespace blink { ...@@ -12,6 +14,20 @@ namespace blink {
namespace { namespace {
class CORSExposedHeadersTest : public ::testing::Test {
public:
using CredentialsMode = network::mojom::FetchCredentialsMode;
WebHTTPHeaderSet Parse(CredentialsMode credentials_mode,
const AtomicString& header) const {
ResourceResponse response;
response.AddHTTPHeaderField("access-control-expose-headers", header);
return WebCORS::ExtractCorsExposedHeaderNamesList(
credentials_mode, WrappedResourceResponse(response));
}
};
TEST(CreateAccessControlPreflightRequestTest, LexicographicalOrder) { TEST(CreateAccessControlPreflightRequestTest, LexicographicalOrder) {
WebURLRequest request; WebURLRequest request;
request.AddHTTPHeaderField("Orange", "Orange"); request.AddHTTPHeaderField("Orange", "Orange");
...@@ -79,85 +95,81 @@ TEST(CreateAccessControlPreflightRequestTest, ...@@ -79,85 +95,81 @@ TEST(CreateAccessControlPreflightRequestTest,
preflight.HttpHeaderField("Access-Control-Request-Headers")); preflight.HttpHeaderField("Access-Control-Request-Headers"));
} }
TEST(ParseAccessControlExposeHeadersAllowListTest, ValidInput) { TEST_F(CORSExposedHeadersTest, ValidInput) {
WebHTTPHeaderSet set; EXPECT_EQ(Parse(CredentialsMode::kOmit, "valid"),
WebCORS::ParseAccessControlExposeHeadersAllowList("valid", set); WebHTTPHeaderSet({"valid"}));
EXPECT_EQ(1U, set.size());
EXPECT_TRUE(set.find("valid") != set.end()); EXPECT_EQ(Parse(CredentialsMode::kOmit, "a,b"), WebHTTPHeaderSet({"a", "b"}));
set.clear(); EXPECT_EQ(Parse(CredentialsMode::kOmit, " a , b "),
WebCORS::ParseAccessControlExposeHeadersAllowList("a,b", set); WebHTTPHeaderSet({"a", "b"}));
EXPECT_EQ(2U, set.size());
EXPECT_TRUE(set.find("a") != set.end()); EXPECT_EQ(Parse(CredentialsMode::kOmit, " \t \t\t a"),
EXPECT_TRUE(set.find("b") != set.end()); WebHTTPHeaderSet({"a"}));
set.clear();
WebCORS::ParseAccessControlExposeHeadersAllowList(" a , b ", set);
EXPECT_EQ(2U, set.size());
EXPECT_TRUE(set.find("a") != set.end());
EXPECT_TRUE(set.find("b") != set.end());
set.clear();
WebCORS::ParseAccessControlExposeHeadersAllowList(" \t \t\t a", set);
EXPECT_EQ(1U, set.size());
EXPECT_TRUE(set.find("a") != set.end());
} }
TEST(ParseAccessControlExposeHeadersAllowListTest, DuplicatedEntries) { TEST_F(CORSExposedHeadersTest, DuplicatedEntries) {
WebHTTPHeaderSet set; EXPECT_EQ(Parse(CredentialsMode::kOmit, "a, a"), WebHTTPHeaderSet{"a"});
WebCORS::ParseAccessControlExposeHeadersAllowList("a, a", set);
EXPECT_EQ(1U, set.size()); EXPECT_EQ(Parse(CredentialsMode::kOmit, "a, a, b"),
EXPECT_TRUE(set.find("a") != set.end()); WebHTTPHeaderSet({"a", "b"}));
set.clear();
WebCORS::ParseAccessControlExposeHeadersAllowList("a, a, b", set);
EXPECT_EQ(2U, set.size());
EXPECT_TRUE(set.find("a") != set.end());
EXPECT_TRUE(set.find("b") != set.end());
} }
TEST(ParseAccessControlExposeHeadersAllowListTest, InvalidInput) { TEST_F(CORSExposedHeadersTest, InvalidInput) {
WebHTTPHeaderSet set; EXPECT_TRUE(Parse(CredentialsMode::kOmit, "not valid").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList("not valid", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, "///").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList("///", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, "/a/").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList("/a/", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, ",").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList(",", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, " , ").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList(" , ", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, " , a").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList(" , a", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, "a , ").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList("a , ", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, "").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList("", set);
EXPECT_TRUE(set.empty());
set.clear(); EXPECT_TRUE(Parse(CredentialsMode::kOmit, " ").empty());
WebCORS::ParseAccessControlExposeHeadersAllowList(" ", set);
EXPECT_TRUE(set.empty());
set.clear();
// U+0141 which is 'A' (0x41) + 0x100. // U+0141 which is 'A' (0x41) + 0x100.
WebCORS::ParseAccessControlExposeHeadersAllowList( EXPECT_TRUE(
String::FromUTF8("\xC5\x81"), set); Parse(CredentialsMode::kOmit, AtomicString(String::FromUTF8("\xC5\x81")))
EXPECT_TRUE(set.empty()); .empty());
}
TEST_F(CORSExposedHeadersTest, Wildcard) {
ResourceResponse response;
response.AddHTTPHeaderField("access-control-expose-headers", "a, b, *");
response.AddHTTPHeaderField("b", "-");
response.AddHTTPHeaderField("c", "-");
response.AddHTTPHeaderField("d", "-");
response.AddHTTPHeaderField("*", "-");
EXPECT_EQ(
WebCORS::ExtractCorsExposedHeaderNamesList(
CredentialsMode::kOmit, WrappedResourceResponse(response)),
WebHTTPHeaderSet({"access-control-expose-headers", "b", "c", "d", "*"}));
EXPECT_EQ(
WebCORS::ExtractCorsExposedHeaderNamesList(
CredentialsMode::kSameOrigin, WrappedResourceResponse(response)),
WebHTTPHeaderSet({"access-control-expose-headers", "b", "c", "d", "*"}));
}
TEST_F(CORSExposedHeadersTest, Asterisk) {
ResourceResponse response;
response.AddHTTPHeaderField("access-control-expose-headers", "a, b, *");
response.AddHTTPHeaderField("b", "-");
response.AddHTTPHeaderField("c", "-");
response.AddHTTPHeaderField("d", "-");
response.AddHTTPHeaderField("*", "-");
EXPECT_EQ(WebCORS::ExtractCorsExposedHeaderNamesList(
CredentialsMode::kInclude, WrappedResourceResponse(response)),
WebHTTPHeaderSet({"a", "b", "*"}));
} }
} // namespace } // namespace
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include "public/platform/WebURL.h" #include "public/platform/WebURL.h"
#include "public/platform/WebURLRequest.h" #include "public/platform/WebURLRequest.h"
#include "services/network/public/interfaces/cors.mojom-shared.h" #include "services/network/public/interfaces/cors.mojom-shared.h"
#include "services/network/public/interfaces/fetch_api.mojom-shared.h"
namespace blink { namespace blink {
...@@ -105,13 +106,9 @@ GetErrorString(const network::mojom::CORSError, ...@@ -105,13 +106,9 @@ GetErrorString(const network::mojom::CORSError,
const WebSecurityOrigin&, const WebSecurityOrigin&,
const WebURLRequest::RequestContext); const WebURLRequest::RequestContext);
BLINK_PLATFORM_EXPORT void ParseAccessControlExposeHeadersAllowList( BLINK_PLATFORM_EXPORT WebHTTPHeaderSet
const WebString&, ExtractCorsExposedHeaderNamesList(network::mojom::FetchCredentialsMode,
WebHTTPHeaderSet&); const WebURLResponse&);
BLINK_PLATFORM_EXPORT void ExtractCorsExposedHeaderNamesList(
const WebURLResponse&,
WebHTTPHeaderSet&);
BLINK_PLATFORM_EXPORT bool IsOnAccessControlResponseHeaderWhitelist( BLINK_PLATFORM_EXPORT bool IsOnAccessControlResponseHeaderWhitelist(
const WebString&); const WebString&);
......
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