Require preflight for non-standard CORS-safelisted request headers Accept, Accept...
authorwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 2 Dec 2016 21:33:51 +0000 (21:33 +0000)
committerwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 2 Dec 2016 21:33:51 +0000 (21:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=165178
<rdar://problem/18792250>

Reviewed by Youenn Fablet.

Fetch currently only restricts the header Content-Type for simple requests:
https://fetch.spec.whatwg.org/#cors-safelisted-request-header

This means simple CORS requests can send unexpected characters in Accept,
Accept-Language, and Content-Language header values.

RFC 7231 implies restrictions on these header values:
- Accept https://tools.ietf.org/html/rfc7231#section-5.3.2
- Accept-Language https://tools.ietf.org/html/rfc7231#section-5.3.5
- Content-Language https://tools.ietf.org/html/rfc7231#section-3.1.3.2

As per discussions in the W3C WebAppSec group we should try to restrict
these header values to help protect servers that do not expect simple CORS
requests.

Non-standard, safelisted header values should trigger a preflight and require
the headers to be whitelisted in the response's Access-Control-Allow-Headers.
For Fetch in no-cors mode this change means non-standard header values are not
allowed to be set.

Source/WebCore:

Test: http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html

* loader/CrossOriginAccessControl.cpp:
(WebCore::isSimpleCrossOriginAccessRequest):
    Now calls WebCore::isCrossOriginSafeRequestHeader() instead of
    WebCore::isOnAccessControlSimpleRequestHeaderWhitelist().
(WebCore::isOnAccessControlSimpleRequestHeaderWhitelist): Deleted.
    It was a duplicate of WebCore::isCrossOriginSafeRequestHeader().
* loader/CrossOriginAccessControl.h:
* loader/CrossOriginPreflightResultCache.cpp:
(WebCore::CrossOriginPreflightResultCacheItem::allowsCrossOriginHeaders):
    Now calls WebCore::isCrossOriginSafeRequestHeader() instead of
    WebCore::isOnAccessControlSimpleRequestHeaderWhitelist().
* platform/network/HTTPParsers.cpp:
(WebCore::isValidAcceptHeaderValue):
    Basic check that the characters are all ASCII alphanumeric, ' ', '*', '.',
    '/', ';', or '='.
(WebCore::isValidLanguageHeaderValue):
    Basic check that the characters are all ASCII alphanumeric, ' ', '*', '-',
    '.', ';', or '='.
(WebCore::isSimpleHeader):
    Removed duplicate code. Now calls WebCore::isCrossOriginSafeRequestHeader().
(WebCore::isCrossOriginSafeRequestHeader):
    Now makes a call to WebCore::isValidAcceptHeaderValue() for Accept
    headers and WebCore::isValidLanguageHeaderValue() for Accept-Language
    and Content-Language headers.
* platform/network/HTTPParsers.h:

LayoutTests:

* http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight-expected.txt: Added.
* http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html: Added.
    Tests that:
    - Normal Accept, Accept-Language, and Content-Language headers don't trigger
        a preflight.
    - Abnormal Accept, Accept-Language, and Content-Language headers do trigger
        a preflight.
    - Abnormal Accept, Accept-Language, and Content-Language headers are
        accepted if the server whitelists them.
* http/tests/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@209261 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/loader/CrossOriginAccessControl.cpp
Source/WebCore/loader/CrossOriginAccessControl.h
Source/WebCore/loader/CrossOriginPreflightResultCache.cpp
Source/WebCore/platform/network/HTTPParsers.cpp
Source/WebCore/platform/network/HTTPParsers.h

index 2148dfa..1df41bb 100644 (file)
@@ -1,3 +1,42 @@
+2016-12-02  John Wilander  <wilander@apple.com>
+
+        Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language
+        https://bugs.webkit.org/show_bug.cgi?id=165178
+        <rdar://problem/18792250>
+
+        Reviewed by Youenn Fablet.
+
+        Fetch currently only restricts the header Content-Type for simple requests:
+        https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+
+        This means simple CORS requests can send unexpected characters in Accept, 
+        Accept-Language, and Content-Language header values.
+
+        RFC 7231 implies restrictions on these header values:
+        - Accept https://tools.ietf.org/html/rfc7231#section-5.3.2
+        - Accept-Language https://tools.ietf.org/html/rfc7231#section-5.3.5
+        - Content-Language https://tools.ietf.org/html/rfc7231#section-3.1.3.2
+
+        As per discussions in the W3C WebAppSec group we should try to restrict 
+        these header values to help protect servers that do not expect simple CORS 
+        requests.
+
+        Non-standard, safelisted header values should trigger a preflight and require
+        the headers to be whitelisted in the response's Access-Control-Allow-Headers.
+        For Fetch in no-cors mode this change means non-standard header values are not
+        allowed to be set.
+
+        * http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight-expected.txt: Added.
+        * http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html: Added.
+            Tests that:
+            - Normal Accept, Accept-Language, and Content-Language headers don't trigger
+                a preflight.
+            - Abnormal Accept, Accept-Language, and Content-Language headers do trigger 
+                a preflight.
+            - Abnormal Accept, Accept-Language, and Content-Language headers are 
+                accepted if the server whitelists them.
+        * http/tests/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php: Added.
+
 2016-12-02  Zalan Bujtas  <zalan@apple.com>
 
         ASSERTION FAILED: flowThread->regionInRange(region, startRegion, endRegion) in WebCore::RenderBox::borderBoxRectInRegion
diff --git a/LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight-expected.txt b/LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight-expected.txt
new file mode 100644 (file)
index 0000000..24107fe
--- /dev/null
@@ -0,0 +1,21 @@
+CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Accept is not allowed by Access-Control-Allow-Headers.
+CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Accept-Language is not allowed by Access-Control-Allow-Headers.
+CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Content-Language is not allowed by Access-Control-Allow-Headers.
+CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Content-Language is not allowed by Access-Control-Allow-Headers.
+CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Accept is not allowed by Access-Control-Allow-Headers.
+PASS Accept header with normal value SHOULD NOT cause a preflight
+PASS Accept header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight
+PASS Accept-Language header with normal value SHOULD NOT cause a preflight
+PASS Accept-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight
+PASS Content-Language header with normal value SHOULD NOT cause a preflight
+PASS Content-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight
+PASS Accept header with abnormal value SHOULD cause a preflight
+PASS Accept-Language header with abnormal value SHOULD cause a preflight
+PASS Content-Language header with abnormal value SHOULD cause a preflight
+PASS Accept header with normal value, Accept-Language header with normal value, and Content-Language header with abnormal value SHOULD cause a preflight
+PASS Accept header with normal value and then another Accept header with abnormal value SHOULD cause a preflight
+PASS Accept header with abnormal value and explicitly allowed headers SHOULD be allowed
+PASS Content-Language header with abnormal value and explicitly allowed headers SHOULD be allowed
+PASS Accept header with normal value, Accept-Language header with normal value, Content-Language header with abnormal value, and explicitly allowed headers SHOULD be allowed
+PASS Accept header with normal value, then another Accept header with abnormal value, and explicitly allowed headers SHOULD be allowed
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html b/LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html
new file mode 100644 (file)
index 0000000..0fc675a
--- /dev/null
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Non-Standard Safelisted Headers SHOULD Trigger a Preflight</title>
+    <script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<!-- https://fetch.spec.whatwg.org/#cors-safelisted-request-header -->
+<script>
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+
+    var xhr;
+    var url = 'http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php';
+
+    function createReadyStateHandler (description, testNumber) {
+        return function handler (e) {
+            if (xhr.readyState === XMLHttpRequest.DONE) {
+                testPassed(description);
+                nextStep(testNumber);
+            }
+        }
+    }
+
+    function createOnErrorHandler (description, testNumber) {
+        return function handler (e) {
+            e.preventDefault();
+            testPassed(description);
+            nextStep(testNumber);
+        }
+    }
+
+    var abnormalSimpleCorsHeaderValue = "() { :;};"
+    var allAllowedNonAlphanumericCharactersForAcceptHeader = " *./;="
+    var allAllowedNonAlphanumericCharactersForAcceptAndContentLanguageHeader = " *-.;="
+    var testCases = [
+        // Positive test cases with normal headers
+        {
+            headersToAdd: [{ name : "Accept", value: "text/*" }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: false,
+            description: "Accept header with normal value SHOULD NOT cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept", value: allAllowedNonAlphanumericCharactersForAcceptHeader }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: false,
+            description: "Accept header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept-Language", value: "en" }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: false,
+            description: "Accept-Language header with normal value SHOULD NOT cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept-Language", value: allAllowedNonAlphanumericCharactersForAcceptAndContentLanguageHeader }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: false,
+            description: "Accept-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Content-Language", value: "en" }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: false,
+            description: "Content-Language header with normal value SHOULD NOT cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Content-Language", value: allAllowedNonAlphanumericCharactersForAcceptAndContentLanguageHeader }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: false,
+            description: "Content-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight"
+        }
+        // Negative test cases with abnormal headers
+        ,{
+            headersToAdd: [{ name : "Accept", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: true,
+            description: "Accept header with abnormal value SHOULD cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept-Language", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: true,
+            description: "Accept-Language header with abnormal value SHOULD cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: true,
+            description: "Content-Language header with abnormal value SHOULD cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept-Language", value: "en" }, { name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: true,
+            description: "Accept header with normal value, Accept-Language header with normal value, and Content-Language header with abnormal value SHOULD cause a preflight"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: false,
+            shouldCausePreflight: true,
+            description: "Accept header with normal value and then another Accept header with abnormal value SHOULD cause a preflight"
+        }
+        // Positive test cases with abnormal headers
+        ,{
+            headersToAdd: [{ name : "Accept", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: true,
+            shouldCausePreflight: true,
+            description: "Accept header with abnormal value and explicitly allowed headers SHOULD be allowed"
+        }
+        ,{
+            headersToAdd: [{ name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: true,
+            shouldCausePreflight: true,
+            description: "Content-Language header with abnormal value and explicitly allowed headers SHOULD be allowed"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept-Language", value: "en" }, { name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: true,
+            shouldCausePreflight: true,
+            description: "Accept header with normal value, Accept-Language header with normal value, Content-Language header with abnormal value, and explicitly allowed headers SHOULD be allowed"
+        }
+        ,{
+            headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept", value: abnormalSimpleCorsHeaderValue }],
+            explicitlyAllowHeaders: true,
+            shouldCausePreflight: true,
+            description: "Accept header with normal value, then another Accept header with abnormal value, and explicitly allowed headers SHOULD be allowed"
+        }
+    ];
+
+    function runTestCase(testNumber) {
+        var testCase = testCases[testNumber];
+        xhr = new XMLHttpRequest();
+        xhr.open('GET', url + (testCase.explicitlyAllowHeaders ? "/?explicitlyAllowHeaders=true" : ""), true);
+        for (var i = 0; i < testCase.headersToAdd.length; i++) {
+            xhr.setRequestHeader(testCase.headersToAdd[i].name, testCase.headersToAdd[i].value);
+        }
+        if (testCase.shouldCausePreflight && !testCase.explicitlyAllowHeaders)
+            xhr.onerror = createOnErrorHandler(testCase.description, testNumber);
+        else
+            xhr.onreadystatechange = createReadyStateHandler(testCase.description, testNumber);
+        xhr.send();
+    }
+
+    function nextStep (testNumber) {
+        if (testNumber === (testCases.length - 1)) {
+            if (window.testRunner)
+                testRunner.notifyDone();
+        } else
+            runTestCase(testNumber + 1);
+    }
+
+    runTestCase(0);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/http/tests/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php b/LayoutTests/http/tests/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php
new file mode 100644 (file)
index 0000000..284da9c
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+header('Access-Control-Allow-Origin: http://127.0.0.1:8000');
+
+if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && isset($_GET['explicitlyAllowHeaders'])) {
+    header('Access-Control-Allow-Methods: GET, OPTIONS');
+    header('Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language');
+}
+?>
\ No newline at end of file
index 6db5d29..6778ec2 100644 (file)
@@ -1,3 +1,59 @@
+2016-12-02  John Wilander  <wilander@apple.com>
+
+        Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language
+        https://bugs.webkit.org/show_bug.cgi?id=165178
+        <rdar://problem/18792250>
+
+        Reviewed by Youenn Fablet.
+
+        Fetch currently only restricts the header Content-Type for simple requests:
+        https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+
+        This means simple CORS requests can send unexpected characters in Accept, 
+        Accept-Language, and Content-Language header values.
+
+        RFC 7231 implies restrictions on these header values:
+        - Accept https://tools.ietf.org/html/rfc7231#section-5.3.2
+        - Accept-Language https://tools.ietf.org/html/rfc7231#section-5.3.5
+        - Content-Language https://tools.ietf.org/html/rfc7231#section-3.1.3.2
+
+        As per discussions in the W3C WebAppSec group we should try to restrict 
+        these header values to help protect servers that do not expect simple CORS 
+        requests.
+
+        Non-standard, safelisted header values should trigger a preflight and require
+        the headers to be whitelisted in the response's Access-Control-Allow-Headers.
+        For Fetch in no-cors mode this change means non-standard header values are not
+        allowed to be set.
+
+        Test: http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html
+
+        * loader/CrossOriginAccessControl.cpp:
+        (WebCore::isSimpleCrossOriginAccessRequest):
+            Now calls WebCore::isCrossOriginSafeRequestHeader() instead of
+            WebCore::isOnAccessControlSimpleRequestHeaderWhitelist().
+        (WebCore::isOnAccessControlSimpleRequestHeaderWhitelist): Deleted.
+            It was a duplicate of WebCore::isCrossOriginSafeRequestHeader().
+        * loader/CrossOriginAccessControl.h:
+        * loader/CrossOriginPreflightResultCache.cpp:
+        (WebCore::CrossOriginPreflightResultCacheItem::allowsCrossOriginHeaders):
+            Now calls WebCore::isCrossOriginSafeRequestHeader() instead of
+            WebCore::isOnAccessControlSimpleRequestHeaderWhitelist().
+        * platform/network/HTTPParsers.cpp:
+        (WebCore::isValidAcceptHeaderValue):
+            Basic check that the characters are all ASCII alphanumeric, ' ', '*', '.',
+            '/', ';', or '='.
+        (WebCore::isValidLanguageHeaderValue):
+            Basic check that the characters are all ASCII alphanumeric, ' ', '*', '-',
+            '.', ';', or '='.
+        (WebCore::isSimpleHeader):
+            Removed duplicate code. Now calls WebCore::isCrossOriginSafeRequestHeader().
+        (WebCore::isCrossOriginSafeRequestHeader):
+            Now makes a call to WebCore::isValidAcceptHeaderValue() for Accept
+            headers and WebCore::isValidLanguageHeaderValue() for Accept-Language
+            and Content-Language headers.
+        * platform/network/HTTPParsers.h:
+
 2016-12-02  Zalan Bujtas  <zalan@apple.com>
 
         ASSERTION FAILED: flowThread->regionInRange(region, startRegion, endRegion) in WebCore::RenderBox::borderBoxRectInRegion
index f72916f..ee0649c 100644 (file)
@@ -45,32 +45,13 @@ bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method)
     return method == "GET" || method == "HEAD" || method == "POST";
 }
 
-bool isOnAccessControlSimpleRequestHeaderWhitelist(HTTPHeaderName name, const String& value)
-{
-    switch (name) {
-    case HTTPHeaderName::Accept:
-    case HTTPHeaderName::AcceptLanguage:
-    case HTTPHeaderName::ContentLanguage:
-        return true;
-    case HTTPHeaderName::ContentType: {
-        // Preflight is required for MIME types that can not be sent via form submission.
-        String mimeType = extractMIMETypeFromMediaType(value);
-        return equalIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded")
-            || equalIgnoringASCIICase(mimeType, "multipart/form-data")
-            || equalIgnoringASCIICase(mimeType, "text/plain");
-    }
-    default:
-        return false;
-    }
-}
-
 bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap)
 {
     if (!isOnAccessControlSimpleRequestMethodWhitelist(method))
         return false;
 
     for (const auto& header : headerMap) {
-        if (!header.keyAsHTTPHeaderName || !isOnAccessControlSimpleRequestHeaderWhitelist(header.keyAsHTTPHeaderName.value(), header.value))
+        if (!header.keyAsHTTPHeaderName || !isCrossOriginSafeRequestHeader(header.keyAsHTTPHeaderName.value(), header.value))
             return false;
     }
 
index 43402c5..8a71043 100644 (file)
@@ -40,7 +40,6 @@ class URL;
 
 bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap&);
 bool isOnAccessControlSimpleRequestMethodWhitelist(const String&);
-bool isOnAccessControlSimpleRequestHeaderWhitelist(HTTPHeaderName, const String& value);
 bool isOnAccessControlResponseHeaderWhitelist(const String&);
 
 void updateRequestForAccessControl(ResourceRequest&, SecurityOrigin&, StoredCredentials);
index a8f46a2..47cf736 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "CrossOriginAccessControl.h"
 #include "HTTPHeaderNames.h"
+#include "HTTPParsers.h"
 #include "ResourceResponse.h"
 #include <wtf/MainThread.h>
 #include <wtf/NeverDestroyed.h>
@@ -127,7 +128,7 @@ bool CrossOriginPreflightResultCacheItem::allowsCrossOriginMethod(const String&
 bool CrossOriginPreflightResultCacheItem::allowsCrossOriginHeaders(const HTTPHeaderMap& requestHeaders, String& errorDescription) const
 {
     for (const auto& header : requestHeaders) {
-        if (header.keyAsHTTPHeaderName && isOnAccessControlSimpleRequestHeaderWhitelist(header.keyAsHTTPHeaderName.value(), header.value))
+        if (header.keyAsHTTPHeaderName && isCrossOriginSafeRequestHeader(header.keyAsHTTPHeaderName.value(), header.value))
             continue;
         if (!m_headers.contains(header.key)) {
             errorDescription = "Request header field " + header.key + " is not allowed by Access-Control-Allow-Headers.";
index 4928a35..f7b8846 100644 (file)
@@ -34,6 +34,7 @@
 #include "HTTPParsers.h"
 
 #include "HTTPHeaderNames.h"
+#include "Language.h"
 #include <wtf/DateMath.h>
 #include <wtf/NeverDestroyed.h>
 #include <wtf/text/CString.h>
@@ -126,6 +127,36 @@ bool isValidHTTPHeaderValue(const String& value)
     return true;
 }
 
+// See RFC 7231, Section 5.3.2
+bool isValidAcceptHeaderValue(const String& value)
+{
+    for (unsigned i = 0; i < value.length(); ++i) {
+        UChar c = value[i];
+        if (isASCIIAlphanumeric(c) || c == ' ' || c == '*' || c == '.' || c == '/' || c == ';' || c == '=')
+            continue;
+        return false;
+    }
+    
+    return true;
+}
+
+// See RFC 7231, Section 5.3.5 and 3.1.3.2
+bool isValidLanguageHeaderValue(const String& value)
+{
+    for (unsigned i = 0; i < value.length(); ++i) {
+        UChar c = value[i];
+        if (isASCIIAlphanumeric(c) || c == ' ' || c == '*' || c == '-' || c == '.' || c == ';' || c == '=')
+            continue;
+        return false;
+    }
+    
+    // FIXME: Validate further by splitting into language tags and optional quality
+    // values (q=) and then check each language tag.
+    // Language tags https://tools.ietf.org/html/rfc7231#section-3.1.3.1
+    // Language tag syntax https://tools.ietf.org/html/bcp47#section-2.1
+    return true;
+}
+
 // See RFC 7230, Section 3.2.6.
 bool isValidHTTPToken(const String& value)
 {
@@ -732,7 +763,7 @@ void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHea
     }
 }
 
-// Implememtnation of https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
+// Implementation of https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
 bool isForbiddenHeaderName(const String& name)
 {
     HTTPHeaderName headerName;
@@ -776,18 +807,7 @@ bool isSimpleHeader(const String& name, const String& value)
     HTTPHeaderName headerName;
     if (!findHTTPHeaderName(name, headerName))
         return false;
-    switch (headerName) {
-    case HTTPHeaderName::Accept:
-    case HTTPHeaderName::AcceptLanguage:
-    case HTTPHeaderName::ContentLanguage:
-        return true;
-    case HTTPHeaderName::ContentType: {
-        String mimeType = extractMIMETypeFromMediaType(value);
-        return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
-    }
-    default:
-        return false;
-    }
+    return isCrossOriginSafeRequestHeader(headerName, value);
 }
 
 bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessControlExposeHeaderSet)
@@ -824,10 +844,12 @@ bool isCrossOriginSafeRequestHeader(HTTPHeaderName name, const String& value)
 {
     switch (name) {
     case HTTPHeaderName::Accept:
+        return isValidAcceptHeaderValue(value);
     case HTTPHeaderName::AcceptLanguage:
     case HTTPHeaderName::ContentLanguage:
-        return true;
+        return isValidLanguageHeaderValue(value);
     case HTTPHeaderName::ContentType: {
+        // Preflight is required for MIME types that can not be sent via form submission.
         String mimeType = extractMIMETypeFromMediaType(value);
         return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
     }
index decc1b0..0c04240 100644 (file)
@@ -69,6 +69,8 @@ enum XFrameOptionsDisposition {
 
 bool isValidReasonPhrase(const String&);
 bool isValidHTTPHeaderValue(const String&);
+bool isValidAcceptHeaderValue(const String&);
+bool isValidLanguageHeaderValue(const String&);
 bool isValidHTTPToken(const String&);
 bool parseHTTPRefresh(const String& refresh, double& delay, String& url);
 std::optional<std::chrono::system_clock::time_point> parseHTTPDate(const String&);