blob: 1b678264818534bdf40f9d7484fa528b8934d51c [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 "third_party/blink/renderer/platform/loader/cors/cors_error_string.h"
#include <initializer_list>
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/ascii_ctype.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/string_view.h"
namespace blink {
namespace CORS {
namespace {
void Append(StringBuilder& builder, std::initializer_list<StringView> views) {
for (const StringView& view : views) {
builder.Append(view);
}
}
bool IsPreflightError(network::mojom::CORSError error_code) {
switch (error_code) {
case network::mojom::CORSError::kPreflightWildcardOriginNotAllowed:
case network::mojom::CORSError::kPreflightMissingAllowOriginHeader:
case network::mojom::CORSError::kPreflightMultipleAllowOriginValues:
case network::mojom::CORSError::kPreflightInvalidAllowOriginValue:
case network::mojom::CORSError::kPreflightAllowOriginMismatch:
case network::mojom::CORSError::kPreflightInvalidAllowCredentials:
case network::mojom::CORSError::kPreflightInvalidStatus:
case network::mojom::CORSError::kPreflightDisallowedRedirect:
case network::mojom::CORSError::kPreflightMissingAllowExternal:
case network::mojom::CORSError::kPreflightInvalidAllowExternal:
return true;
default:
return false;
}
}
} // namespace
String GetErrorString(const network::CORSErrorStatus& status,
const KURL& initial_request_url,
const KURL& last_request_url,
const SecurityOrigin& origin,
Resource::Type resource_type,
const AtomicString& initiator_name) {
StringBuilder builder;
static constexpr char kNoCorsInformation[] =
" Have the server send the header with a valid value, or, if an opaque "
"response serves your needs, set the request's mode to 'no-cors' to "
"fetch the resource with CORS disabled.";
using CORSError = network::mojom::CORSError;
const StringView hint(status.failed_parameter.data(),
status.failed_parameter.size());
const char* resource_kind_raw =
Resource::ResourceTypeToString(resource_type, initiator_name);
String resource_kind(resource_kind_raw);
if (strlen(resource_kind_raw) >= 2 && IsASCIILower(resource_kind_raw[1]))
resource_kind = resource_kind.LowerASCII();
Append(builder, {"Access to ", resource_kind, " at '",
last_request_url.GetString(), "' "});
if (initial_request_url != last_request_url) {
Append(builder,
{"(redirected from '", initial_request_url.GetString(), "') "});
}
Append(builder, {"from origin '", origin.ToString(),
"' has been blocked by CORS policy: "});
if (IsPreflightError(status.cors_error)) {
builder.Append(
"Response to preflight request doesn't pass access control check: ");
}
switch (status.cors_error) {
case CORSError::kDisallowedByMode:
builder.Append("Cross origin requests are not allowed by request mode.");
break;
case CORSError::kInvalidResponse:
builder.Append("The response is invalid.");
break;
case CORSError::kWildcardOriginNotAllowed:
case CORSError::kPreflightWildcardOriginNotAllowed:
builder.Append(
"The value of the 'Access-Control-Allow-Origin' header in the "
"response must not be the wildcard '*' when the request's "
"credentials mode is 'include'.");
if (initiator_name == FetchInitiatorTypeNames::xmlhttprequest) {
builder.Append(
" The credentials mode of requests initiated by the "
"XMLHttpRequest is controlled by the withCredentials attribute.");
}
break;
case CORSError::kMissingAllowOriginHeader:
case CORSError::kPreflightMissingAllowOriginHeader:
builder.Append(
"No 'Access-Control-Allow-Origin' header is present on the "
"requested resource.");
if (initiator_name == FetchInitiatorTypeNames::fetch) {
builder.Append(
" If an opaque response serves your needs, set the request's "
"mode to 'no-cors' to fetch the resource with CORS disabled.");
}
break;
case CORSError::kMultipleAllowOriginValues:
case CORSError::kPreflightMultipleAllowOriginValues:
Append(builder,
{"The 'Access-Control-Allow-Origin' header contains multiple "
"values '",
hint, "', but only one is allowed."});
if (initiator_name == FetchInitiatorTypeNames::fetch)
builder.Append(kNoCorsInformation);
break;
case CORSError::kInvalidAllowOriginValue:
case CORSError::kPreflightInvalidAllowOriginValue:
Append(builder, {"The 'Access-Control-Allow-Origin' header contains the "
"invalid value '",
hint, "'."});
if (initiator_name == FetchInitiatorTypeNames::fetch)
builder.Append(kNoCorsInformation);
break;
case CORSError::kAllowOriginMismatch:
case CORSError::kPreflightAllowOriginMismatch:
Append(builder, {"The 'Access-Control-Allow-Origin' header has a value '",
hint, "' that is not equal to the supplied origin."});
if (initiator_name == FetchInitiatorTypeNames::fetch)
builder.Append(kNoCorsInformation);
break;
case CORSError::kInvalidAllowCredentials:
case CORSError::kPreflightInvalidAllowCredentials:
Append(builder,
{"The value of the 'Access-Control-Allow-Credentials' header in "
"the response is '",
hint,
"' which must be 'true' when the request's credentials mode is "
"'include'."});
if (initiator_name == FetchInitiatorTypeNames::xmlhttprequest) {
builder.Append(
" The credentials mode of requests initiated by the "
"XMLHttpRequest is controlled by the withCredentials "
"attribute.");
}
break;
case CORSError::kCORSDisabledScheme:
Append(builder,
{"Cross origin requests are only supported for protocol schemes: ",
SchemeRegistry::ListOfCORSEnabledURLSchemes(), "."});
break;
case CORSError::kPreflightInvalidStatus:
builder.Append("It does not have HTTP ok status.");
break;
case CORSError::kPreflightDisallowedRedirect:
builder.Append("Redirect is not allowed for a preflight request.");
break;
case CORSError::kPreflightMissingAllowExternal:
builder.Append(
"No 'Access-Control-Allow-External' header was present in the "
"preflight response for this external request (This is an "
"experimental header which is defined in "
"'https://wicg.github.io/cors-rfc1918/').");
break;
case CORSError::kPreflightInvalidAllowExternal:
Append(builder,
{"The 'Access-Control-Allow-External' header in the preflight "
"response for this external request had a value of '",
hint,
"', not 'true' (This is an experimental header which is defined "
"in 'https://wicg.github.io/cors-rfc1918/')."});
break;
case CORSError::kInvalidAllowMethodsPreflightResponse:
builder.Append(
"Cannot parse Access-Control-Allow-Methods response header field in "
"preflight response.");
break;
case CORSError::kInvalidAllowHeadersPreflightResponse:
builder.Append(
"Cannot parse Access-Control-Allow-Headers response header field in "
"preflight response.");
break;
case CORSError::kMethodDisallowedByPreflightResponse:
Append(builder, {"Method ", hint,
" is not allowed by Access-Control-Allow-Methods in "
"preflight response."});
break;
case CORSError::kHeaderDisallowedByPreflightResponse:
Append(builder, {"Request header field ", hint,
" is not allowed by "
"Access-Control-Allow-Headers in preflight response."});
break;
case CORSError::kRedirectDisallowedScheme:
Append(builder, {"Redirect location '", hint,
"' has a disallowed scheme for cross-origin "
"requests."});
break;
case CORSError::kRedirectContainsCredentials:
Append(builder, {"Redirect location '", hint,
"' contains a username and password, which is "
"disallowed for cross-origin requests."});
break;
}
return builder.ToString();
}
} // namespace CORS
} // namespace blink