<!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.py';

    function createOnLoadHandler (description, testNumber, isExpected) {
        return function handler (e) {
            if (isExpected)
                testPassed(description);
            else
                testFailed(description);
            e.preventDefault();
            nextStep(testNumber);
        }
    }

    function createOnErrorHandler (description, testNumber, isExpected) {
        return function handler (e) {
            if (isExpected)
                testPassed(description);
            else
                testFailed(description);
            e.preventDefault();
            nextStep(testNumber);
        }
    }

    var abnormalSimpleCorsHeaderValue = "() { :;};"
    var allAllowedDelimiterCharactersForAcceptHeader = ",/;="
    var allDisallowedDelimiterCharactersForAcceptHeader = ['"', '(', ')', ':', '<', '>', '?', '@', '[', '\\', ']', '{', '}'];
    var allDisallowedCharactersForAcceptHeader = ['\x19', '\x0B', '\x08', '\x7F'];
    var allAllowedNonAlphanumericCharactersForAcceptAndContentLanguageHeader = "\x20 *,-.;= ";
    var allAllowedNonAlphanumericCharactersForAcceptHeader = "\x20 *,-.;= \x7E\x80";
    var testCases = [
        // Positive test cases with normal headers
        {
            headersToAdd: [{ name : "Accept", value: "application/json,text/*,*/*" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Accept header value 'application/json,text/*,*/*' SHOULD NOT cause a preflight"
        }
        ,{
            headersToAdd: [{ name : "Accept", value: "application/vnd.api+json" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Accept header with normal value 'application/vnd.api+json' SHOULD NOT cause a preflight"
        }
        ,{
            headersToAdd: [{ name : "Accept", value: "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Accept header with normal value 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c' SHOULD NOT cause a preflight"
        }
        ,{
            headersToAdd: [{ name : "Accept", value: "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Accept header with normal value 'text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5' SHOULD NOT cause a preflight"
        }
        ,{
            headersToAdd: [{ name : "Accept", value: allAllowedDelimiterCharactersForAcceptHeader }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Accept header value with all allowed delimiter characters SHOULD NOT cause a preflight"
        }
        ,{
            headersToAdd: [{ name : "Accept-Language", value: "en-US,en;q=0.8" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Accept-Language header value 'en-US,en;q=0.8' SHOULD NOT cause a preflight"
        }
        ,{
            headersToAdd: [{ name : "Accept-Language", value: "zh-Latn-CN-variant1-a-extend1-x-wadegile-private1" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Accept-Language header value 'zh-Latn-CN-variant1-a-extend1-x-wadegile-private1' 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: 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-US,en;q=0.8" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Content-Language header value 'en-US,en;q=0.8' SHOULD NOT cause a preflight"
        }
        ,{
            headersToAdd: [{ name : "Content-Language", value: "zh-Latn-CN-variant1-a-extend1-x-wadegile-private1" }],
            explicitlyAllowHeaders: false,
            shouldCausePreflight: false,
            description: "Content-Language header value 'zh-Latn-CN-variant1-a-extend1-x-wadegile-private1' 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"
        }
    ];

    // Individual negative test cases for each disallowed delimiter character in Accept header values.
    for (var i = 0; i < allDisallowedDelimiterCharactersForAcceptHeader.length; i++) {
        var disallowedDelimiter = allDisallowedDelimiterCharactersForAcceptHeader[i];
        testCases.push(
            {
                headersToAdd: [{ name : "Accept", value: disallowedDelimiter }],
                explicitlyAllowHeaders: false,
                shouldCausePreflight: true,
                description: "Accept header with disallowed delimiter '" + disallowedDelimiter + "' SHOULD cause a preflight"
            }
        );
    }
    for (var i = 0; i < allDisallowedCharactersForAcceptHeader.length; i++) {
        var disallowedCharacter = allDisallowedCharactersForAcceptHeader[i];
        testCases.push(
            {
                headersToAdd: [{ name : "Accept", value: disallowedCharacter }],
                explicitlyAllowHeaders: false,
                shouldCausePreflight: true,
                description: "Accept header with disallowed character '" + disallowedCharacter + "' SHOULD cause a preflight"
            }
        );
        testCases.push(
            {
                headersToAdd: [{ name : "Accept-Language", value: disallowedCharacter }],
                explicitlyAllowHeaders: false,
                shouldCausePreflight: true,
                description: "Accept-Language header with disallowed character '" + disallowedCharacter + "' SHOULD cause a preflight"
            }
        );
        testCases.push(
            {
                headersToAdd: [{ name : "Content-Language", value: disallowedCharacter }],
                explicitlyAllowHeaders: false,
                shouldCausePreflight: true,
                description: "Content-Language header with disallowed character '" + disallowedCharacter + "' SHOULD cause a preflight"
            }
        );
    }

    function runTestCase(testNumber) {
        var testCase = testCases[testNumber];
        xhr = new XMLHttpRequest();
        let query = "/?";
        if (testCase.explicitlyAllowHeaders)
            query += "explicitlyAllowHeaders&";
        if (testCase.shouldCausePreflight)
            query += "shouldPreflight";
        xhr.open('GET', url + query, true);
        for (var i = 0; i < testCase.headersToAdd.length; i++) {
            xhr.setRequestHeader(testCase.headersToAdd[i].name, testCase.headersToAdd[i].value);
        }
        let shouldFail = testCase.shouldCausePreflight && !testCase.explicitlyAllowHeaders;
        xhr.onerror = createOnErrorHandler(testCase.description, testNumber, shouldFail);
        xhr.onload = createOnLoadHandler(testCase.description, testNumber, !shouldFail);
        xhr.send();
    }

    function nextStep (testNumber) {
        if (testNumber === (testCases.length - 1)) {
            if (window.testRunner)
                testRunner.notifyDone();
        } else
            runTestCase(testNumber + 1);
    }

    runTestCase(0);
</script>
</body>
</html>
