Commit 63231ede authored by Takashi Toyoshima's avatar Takashi Toyoshima Committed by Commit Bot

OOR-CORS: introduce cors::PreflightController

This patch introduces cors::PreflightController that will handle
URLLoader interfaces to make CORS-preflight request and response for
CORS-preflight enabled request from CORSURLLoader in the near future.

This will also own a unified CORS-preflight cache.

The first patch implements a function that takes an original
ResourceRequest and creates a ResourceRequest for a CORS-preflight
request.

Bug: 803766
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_mojo
Change-Id: Iad50a2d297e445bacca8c3fdb364b942d237b5aa
Reviewed-on: https://chromium-review.googlesource.com/923907
Commit-Queue: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: default avatarTakeshi Yoshino <tyoshino@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#539089}
parent d980aea4
...@@ -12,6 +12,8 @@ component("network_service") { ...@@ -12,6 +12,8 @@ component("network_service") {
sources = [ sources = [
"cookie_manager.cc", "cookie_manager.cc",
"cookie_manager.h", "cookie_manager.h",
"cors/preflight_controller.cc",
"cors/preflight_controller.h",
"data_pipe_element_reader.cc", "data_pipe_element_reader.cc",
"data_pipe_element_reader.h", "data_pipe_element_reader.h",
"http_server_properties_pref_delegate.cc", "http_server_properties_pref_delegate.cc",
...@@ -114,7 +116,8 @@ source_set("tests") { ...@@ -114,7 +116,8 @@ source_set("tests") {
sources = [ sources = [
"cookie_manager_unittest.cc", "cookie_manager_unittest.cc",
"cors_url_loader_unittest.cc", "cors/cors_url_loader_unittest.cc",
"cors/preflight_controller_unittest.cc",
"data_pipe_element_reader_unittest.cc", "data_pipe_element_reader_unittest.cc",
"ignore_errors_cert_verifier_unittest.cc", "ignore_errors_cert_verifier_unittest.cc",
"keepalive_statistics_recorder_unittest.cc", "keepalive_statistics_recorder_unittest.cc",
......
// 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/cors/preflight_controller.h"
#include <algorithm>
#include "base/strings/string_util.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/cors/cors.h"
#include "url/gurl.h"
namespace network {
namespace cors {
namespace {
// Algorithm step 3 of the CORS-preflight fetch,
// https://fetch.spec.whatwg.org/#cors-preflight-fetch-0, that requires
// - CORS-safelisted request-headers excluded
// - duplicates excluded
// - sorted lexicographically
// - byte-lowercased
std::string CreateAccessControlRequestHeadersHeader(
const net::HttpRequestHeaders& headers) {
std::vector<std::string> filtered_headers;
for (const auto& header : headers.GetHeaderVector()) {
// Exclude CORS-safelisted headers.
if (cors::IsCORSSafelistedHeader(header.key, header.value))
continue;
// Exclude the forbidden headers because they may be added by the user
// agent. They must be checked separately and rejected for
// JavaScript-initiated requests.
if (cors::IsForbiddenHeader(header.key))
continue;
filtered_headers.push_back(base::ToLowerASCII(header.key));
}
if (filtered_headers.empty())
return std::string();
// Sort header names lexicographically.
std::sort(filtered_headers.begin(), filtered_headers.end());
return base::JoinString(filtered_headers, ",");
}
} // namespace
// static
std::unique_ptr<ResourceRequest> PreflightController::CreatePreflightRequest(
const ResourceRequest& request) {
DCHECK(!request.url.has_username());
DCHECK(!request.url.has_password());
std::unique_ptr<ResourceRequest> preflight_request =
std::make_unique<ResourceRequest>();
// Algorithm step 1 through 4 of the CORS-preflight fetch,
// https://fetch.spec.whatwg.org/#cors-preflight-fetch-0.
preflight_request->url = request.url;
preflight_request->method = "OPTIONS";
preflight_request->priority = request.priority;
preflight_request->request_context = request.request_context;
preflight_request->referrer = request.referrer;
preflight_request->referrer_policy = request.referrer_policy;
preflight_request->fetch_credentials_mode =
mojom::FetchCredentialsMode::kOmit;
preflight_request->headers.SetHeader(
cors::header_names::kAccessControlRequestMethod, request.method);
std::string request_headers =
CreateAccessControlRequestHeadersHeader(request.headers);
if (!request_headers.empty()) {
preflight_request->headers.SetHeader(
cors::header_names::kAccessControlRequestHeaders, request_headers);
}
if (request.is_external_request) {
preflight_request->headers.SetHeader(
cors::header_names::kAccessControlRequestExternal, "true");
}
// TODO(toyoshim): Remove the following line once the network service is
// enabled by default.
preflight_request->skip_service_worker = true;
return preflight_request;
}
} // namespace cors
} // namespace network
// 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.
#ifndef SERVICES_NETWORK_CORS_PREFLIGHT_CONTROLLER_H_
#define SERVICES_NETWORK_CORS_PREFLIGHT_CONTROLLER_H_
#include <memory>
#include "base/component_export.h"
#include "services/network/public/cpp/resource_request.h"
namespace network {
namespace cors {
// A class to manage CORS-preflight, making a CORS-preflight request, checking
// its result, and owning a CORS-preflight cache.
// TODO(toyoshim): Features explained above not fully implemented yet.
// See also https://crbug.com/803766 to check a design doc.
class COMPONENT_EXPORT(NETWORK_SERVICE) PreflightController {
public:
// Creates a CORS-preflight ResourceRequest for a specified |request| for a
// URL that is originally requested.
// Note: This function is exposed for testing only purpose, and production
// code outside this class should not call this function directly.
static std::unique_ptr<ResourceRequest> CreatePreflightRequest(
const ResourceRequest& request);
// TODO(toyoshim): Implements an asynchronous interface to consult about
// CORS-preflight check, that manages a preflight cache, and may make a
// preflight request internally.
};
} // namespace cors
} // namespace network
#endif // SERVICES_NETWORK_CORS_PREFLIGHT_CONTROLLER_H_
// 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/cors/preflight_controller.h"
#include "services/network/public/cpp/cors/cors.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace cors {
namespace {
TEST(PreflightControllerCreatePreflightRequestTest, LexicographicalOrder) {
ResourceRequest request;
request.headers.SetHeader("Orange", "Orange");
request.headers.SetHeader("Apple", "Red");
request.headers.SetHeader("Kiwifruit", "Green");
request.headers.SetHeader("Content-Type", "application/octet-stream");
request.headers.SetHeader("Strawberry", "Red");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequest(request);
std::string header;
EXPECT_TRUE(preflight->headers.GetHeader(
cors::header_names::kAccessControlRequestHeaders, &header));
EXPECT_EQ("apple,content-type,kiwifruit,orange,strawberry", header);
}
TEST(PreflightControllerCreatePreflightRequestTest, ExcludeSimpleHeaders) {
ResourceRequest request;
request.headers.SetHeader("Accept", "everything");
request.headers.SetHeader("Accept-Language", "everything");
request.headers.SetHeader("Content-Language", "everything");
request.headers.SetHeader("Save-Data", "on");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequest(request);
// Do not emit empty-valued headers; an empty list of non-"CORS safelisted"
// request headers should cause "Access-Control-Request-Headers:" to be
// left out in the preflight request.
std::string header;
EXPECT_FALSE(preflight->headers.GetHeader(
cors::header_names::kAccessControlRequestHeaders, &header));
}
TEST(PreflightControllerCreatePreflightRequestTest,
ExcludeSimpleContentTypeHeader) {
ResourceRequest request;
request.headers.SetHeader("Content-Type", "text/plain");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequest(request);
// Empty list also; see comment in test above.
std::string header;
EXPECT_FALSE(preflight->headers.GetHeader(
cors::header_names::kAccessControlRequestHeaders, &header));
}
TEST(PreflightControllerCreatePreflightRequestTest, IncludeNonSimpleHeader) {
ResourceRequest request;
request.headers.SetHeader("X-Custom-Header", "foobar");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequest(request);
std::string header;
EXPECT_TRUE(preflight->headers.GetHeader(
cors::header_names::kAccessControlRequestHeaders, &header));
EXPECT_EQ("x-custom-header", header);
}
TEST(PreflightControllerCreatePreflightRequestTest,
IncludeNonSimpleContentTypeHeader) {
ResourceRequest request;
request.headers.SetHeader("Content-Type", "application/octet-stream");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequest(request);
std::string header;
EXPECT_TRUE(preflight->headers.GetHeader(
cors::header_names::kAccessControlRequestHeaders, &header));
EXPECT_EQ("content-type", header);
}
TEST(PreflightControllerCreatePreflightRequestTest, ExcludeForbiddenHeaders) {
ResourceRequest request;
request.headers.SetHeader("referer", "https://www.google.com/");
std::unique_ptr<ResourceRequest> preflight =
PreflightController::CreatePreflightRequest(request);
std::string header;
EXPECT_FALSE(preflight->headers.GetHeader(
cors::header_names::kAccessControlRequestHeaders, &header));
}
} // namespace
} // namespace cors
} // namespace network
...@@ -51,6 +51,9 @@ namespace header_names { ...@@ -51,6 +51,9 @@ namespace header_names {
const char kAccessControlAllowCredentials[] = const char kAccessControlAllowCredentials[] =
"Access-Control-Allow-Credentials"; "Access-Control-Allow-Credentials";
const char kAccessControlAllowOrigin[] = "Access-Control-Allow-Origin"; const char kAccessControlAllowOrigin[] = "Access-Control-Allow-Origin";
const char kAccessControlRequestExternal[] = "Access-Control-Request-External";
const char kAccessControlRequestHeaders[] = "Access-Control-Request-Headers";
const char kAccessControlRequestMethod[] = "Access-Control-Request-Method";
} // namespace header_names } // namespace header_names
......
...@@ -27,6 +27,12 @@ COMPONENT_EXPORT(NETWORK_CPP) ...@@ -27,6 +27,12 @@ COMPONENT_EXPORT(NETWORK_CPP)
extern const char kAccessControlAllowCredentials[]; extern const char kAccessControlAllowCredentials[];
COMPONENT_EXPORT(NETWORK_CPP) COMPONENT_EXPORT(NETWORK_CPP)
extern const char kAccessControlAllowOrigin[]; extern const char kAccessControlAllowOrigin[];
COMPONENT_EXPORT(NETWORK_CPP)
extern const char kAccessControlRequestExternal[];
COMPONENT_EXPORT(NETWORK_CPP)
extern const char kAccessControlRequestHeaders[];
COMPONENT_EXPORT(NETWORK_CPP)
extern const char kAccessControlRequestMethod[];
} // namespace header_names } // namespace header_names
......
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