This source file includes following definitions.
- isOnAccessControlSimpleRequestMethodWhitelist
- isOnAccessControlSimpleRequestHeaderWhitelist
- isSimpleCrossOriginAccessRequest
- createAllowedCrossOriginResponseHeadersSet
- isOnAccessControlResponseHeaderWhitelist
- updateRequestForAccessControl
- createAccessControlPreflightRequest
- isOriginSeparator
- passesAccessControlCheck
- passesPreflightStatusCheck
- parseAccessControlExposeHeadersAllowList
- isLegalRedirectLocation
- handleRedirect
#include "config.h"
#include "core/fetch/CrossOriginAccessControl.h"
#include "core/fetch/Resource.h"
#include "core/fetch/ResourceLoaderOptions.h"
#include "platform/network/HTTPParsers.h"
#include "platform/network/ResourceRequest.h"
#include "platform/network/ResourceResponse.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "wtf/Threading.h"
#include "wtf/text/AtomicString.h"
#include "wtf/text/StringBuilder.h"
namespace WebCore {
bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method)
{
return method == "GET" || method == "HEAD" || method == "POST";
}
bool isOnAccessControlSimpleRequestHeaderWhitelist(const AtomicString& name, const AtomicString& value)
{
if (equalIgnoringCase(name, "accept")
|| equalIgnoringCase(name, "accept-language")
|| equalIgnoringCase(name, "content-language")
|| equalIgnoringCase(name, "origin")
|| equalIgnoringCase(name, "referer"))
return true;
if (equalIgnoringCase(name, "content-type")) {
AtomicString mimeType = extractMIMETypeFromMediaType(value);
return equalIgnoringCase(mimeType, "application/x-www-form-urlencoded")
|| equalIgnoringCase(mimeType, "multipart/form-data")
|| equalIgnoringCase(mimeType, "text/plain");
}
return false;
}
bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap)
{
if (!isOnAccessControlSimpleRequestMethodWhitelist(method))
return false;
HTTPHeaderMap::const_iterator end = headerMap.end();
for (HTTPHeaderMap::const_iterator it = headerMap.begin(); it != end; ++it) {
if (!isOnAccessControlSimpleRequestHeaderWhitelist(it->key, it->value))
return false;
}
return true;
}
static PassOwnPtr<HTTPHeaderSet> createAllowedCrossOriginResponseHeadersSet()
{
OwnPtr<HTTPHeaderSet> headerSet = adoptPtr(new HashSet<String, CaseFoldingHash>);
headerSet->add("cache-control");
headerSet->add("content-language");
headerSet->add("content-type");
headerSet->add("expires");
headerSet->add("last-modified");
headerSet->add("pragma");
return headerSet.release();
}
bool isOnAccessControlResponseHeaderWhitelist(const String& name)
{
AtomicallyInitializedStatic(HTTPHeaderSet*, allowedCrossOriginResponseHeaders = createAllowedCrossOriginResponseHeadersSet().leakPtr());
return allowedCrossOriginResponseHeaders->contains(name);
}
void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* securityOrigin, StoredCredentials allowCredentials)
{
request.removeCredentials();
request.setAllowStoredCredentials(allowCredentials == AllowStoredCredentials);
if (securityOrigin)
request.setHTTPOrigin(securityOrigin->toAtomicString());
}
ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin)
{
ResourceRequest preflightRequest(request.url());
updateRequestForAccessControl(preflightRequest, securityOrigin, DoNotAllowStoredCredentials);
preflightRequest.setHTTPMethod("OPTIONS");
preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod());
preflightRequest.setPriority(request.priority());
const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
if (requestHeaderFields.size() > 0) {
StringBuilder headerBuffer;
HTTPHeaderMap::const_iterator it = requestHeaderFields.begin();
headerBuffer.append(it->key);
++it;
HTTPHeaderMap::const_iterator end = requestHeaderFields.end();
for (; it != end; ++it) {
headerBuffer.appendLiteral(", ");
headerBuffer.append(it->key);
}
preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", AtomicString(headerBuffer.toString().lower()));
}
return preflightRequest;
}
static bool isOriginSeparator(UChar ch)
{
return isASCIISpace(ch) || ch == ',';
}
bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription)
{
AtomicallyInitializedStatic(AtomicString&, accessControlAllowOrigin = *new AtomicString("access-control-allow-origin", AtomicString::ConstructFromLiteral));
AtomicallyInitializedStatic(AtomicString&, accessControlAllowCredentials = *new AtomicString("access-control-allow-credentials", AtomicString::ConstructFromLiteral));
if (!response.httpStatusCode()) {
errorDescription = "Received an invalid response. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
return false;
}
const AtomicString& accessControlOriginString = response.httpHeaderField(accessControlAllowOrigin);
if (accessControlOriginString == starAtom) {
if (includeCredentials == DoNotAllowStoredCredentials)
return true;
if (response.isHTTP()) {
errorDescription = "A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
return false;
}
} else if (accessControlOriginString != securityOrigin->toAtomicString()) {
if (accessControlOriginString.isEmpty()) {
errorDescription = "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
} else if (accessControlOriginString.string().find(isOriginSeparator, 0) != kNotFound) {
errorDescription = "The 'Access-Control-Allow-Origin' header contains multiple values '" + accessControlOriginString + "', but only one is allowed. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
} else {
KURL headerOrigin(KURL(), accessControlOriginString);
if (!headerOrigin.isValid())
errorDescription = "The 'Access-Control-Allow-Origin' header contains the invalid value '" + accessControlOriginString + "'. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
else
errorDescription = "The 'Access-Control-Allow-Origin' header has a value '" + accessControlOriginString + "' that is not equal to the supplied origin. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
}
return false;
}
if (includeCredentials == AllowStoredCredentials) {
const AtomicString& accessControlCredentialsString = response.httpHeaderField(accessControlAllowCredentials);
if (accessControlCredentialsString != "true") {
errorDescription = "Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is '" + accessControlCredentialsString + "'. It must be 'true' to allow credentials.";
return false;
}
}
return true;
}
bool passesPreflightStatusCheck(const ResourceResponse& response, String& errorDescription)
{
if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 400) {
errorDescription = "Invalid HTTP status code " + String::number(response.httpStatusCode());
return false;
}
return true;
}
void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
{
Vector<String> headers;
headerValue.split(',', false, headers);
for (unsigned headerCount = 0; headerCount < headers.size(); headerCount++) {
String strippedHeader = headers[headerCount].stripWhiteSpace();
if (!strippedHeader.isEmpty())
headerSet.add(strippedHeader);
}
}
bool CrossOriginAccessControl::isLegalRedirectLocation(const KURL& requestURL, String& errorDescription)
{
if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(requestURL.protocol())) {
errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') which has a disallowed scheme for cross-origin requests.";
return false;
}
if (!(requestURL.user().isEmpty() && requestURL.pass().isEmpty())) {
errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') containing userinfo, which is disallowed for cross-origin requests.";
return false;
}
return true;
}
bool CrossOriginAccessControl::handleRedirect(Resource* resource, SecurityOrigin* securityOrigin, ResourceRequest& request, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options, String& errorMessage)
{
const KURL& originalURL = redirectResponse.url();
const KURL& requestURL = request.url();
bool redirectCrossOrigin = !securityOrigin->canRequest(requestURL);
if (!securityOrigin->canRequest(originalURL)) {
String errorDescription;
bool allowRedirect = isLegalRedirectLocation(requestURL, errorDescription);
if (allowRedirect) {
StoredCredentials withCredentials = resource->lastResourceRequest().allowStoredCredentials() ? AllowStoredCredentials : DoNotAllowStoredCredentials;
allowRedirect = passesAccessControlCheck(redirectResponse, withCredentials, securityOrigin, errorDescription);
if (allowRedirect) {
RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(originalURL);
if (!originalOrigin->canRequest(requestURL)) {
options.securityOrigin = SecurityOrigin::createUnique();
securityOrigin = options.securityOrigin.get();
}
}
}
if (!allowRedirect) {
const String& originalOrigin = SecurityOrigin::create(originalURL)->toString();
errorMessage = "Redirect at origin '" + originalOrigin + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription;
return false;
}
}
if (redirectCrossOrigin) {
request.clearHTTPOrigin();
request.setHTTPOrigin(securityOrigin->toAtomicString());
if (options.credentialsRequested == ClientDidNotRequestCredentials)
options.allowCredentials = DoNotAllowStoredCredentials;
}
return true;
}
}