Skip to content

Commit

Permalink
Add 304 tests for CORS
Browse files Browse the repository at this point in the history
Per Fetch and HTTP a 304 should be under the same CORS policy as the original 200 (unless it changes it).

This is testing to make sure that this is the case, since many servers (including intermediaries) do NOT add CORS headers (and some can't -- e.g., intermediary caches).
  • Loading branch information
mnot authored and annevk committed Feb 24, 2017
1 parent 771007a commit 8b967bb
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 0 deletions.
97 changes: 97 additions & 0 deletions cors/304.htm
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>CORS - 304 Responses</title>
<meta name=author title="Mark Nottingham" href="mailto:mnot@mnot.net">

<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=support.js?pipe=sub></script>

<h1>CORS - 304 Responses</h1>
<div id=log></div>
<script>


/*
* 304 Responses
*/

// A header used to correlate requests and responses
var state_header = "content-language"

/* Make a request; call ready(client) when done */
function req(url, id, t, ready) {
var client = new XMLHttpRequest()
client.open('GET', url, true)
client.setRequestHeader(state_header, id)
client.send()
client.onreadystatechange = function() {
if (client.readyState == client.DONE) {
t.step(function() {
assert_true(client.status != 299, "req " + id + " server says: " + client.responseText)
})
ready(client)
}
}
return client
}

/*
* Make two requests to test cache behaviour.
* The second is made after the first is done and a delay, to make sure it gets into cache.
*/
function two_reqs(id1, id2, should_have_same_body, t, done) {
var rand = Date.now()
var url = CROSSDOMAIN + 'resources/304.py?id=' + id1 + '&r=%s' + rand

var client1 = req(url, id1, t, function(client1) {
t.step(function() {
assert_equals(client1.response, "Success", "didn't get successful 1st response;")
assert_equals(client1.getResponseHeader(state_header), id1, "1st response didn't come from server;")
})

t.step_timeout(function() {
req(url, id2, t, function(client2) {
t.step(function() {
if (should_have_same_body) {
assert_equals(client1.response, client2.response, "response bodies were different;")
// var res_id2 = client2.getResponseHeader(state_header)
// assert_not_equals(res_id2, id1, "2nd response doesn't appear to have updated cached headers;")
// assert_not_equals(res_id2, null, "2nd response didn't expose request identifier;")
// assert_equals(res_id2, id2, "2nd response is associated with a different request (!);")
}
done(client1, client2)
})
t.done()
})
}, 5000)
})
}

async_test(function(t) {
two_reqs('1', '2', true, t, function(client1, client2) {
assert_equals(client1.getResponseHeader("A"), null, "'A' header exposed without permission;")
})
}, "A 304 response with no CORS headers inherits from the stored response")

async_test(function(t) {
two_reqs('3', '4', true, t, function(client1, client2) {
assert_equals(client2.getResponseHeader("A"), "4", "304 didn't expose 'A' header, even though allowed;")
assert_equals(client2.getResponseHeader("B"), "4", "304 didn't expose 'B' header even though allowed;")
})
}, "A 304 can expand Access-Control-Expose-Headers")

async_test(function(t) {
two_reqs('5', '6', true, t, function(client1, client2) {
assert_equals(client2.getResponseHeader("B"), null, "2nd 304 exposed 'B' header;")
})
}, "A 304 can contract Access-Control-Expose-Headers")

async_test(function(t) {
two_reqs('7', '8', false, t, function(client1, client2) {
assert_not_equals(client1.response, client2.response, "Access granted even though 304 updated it to disallow;")
})
}, "A 304 can change Access-Control-Allow-Origin")


</script>
62 changes: 62 additions & 0 deletions cors/resources/304.py
@@ -0,0 +1,62 @@
#!/usr/bin/env python

# A header used to correlate requests and responses
state_header = "content-language"

# Static ETag to use (and expect)
etag = "abcdef"

def error(msg):
return (299, "Client Error"), [
('content-type', 'text/plain'),
('access-control-allow-origin', "*"),
('access-control-expose-headers', state_header),
('cache-control', 'no-store')
], msg

def main(request, response):
headers = []

inm = request.headers.get('if-none-match', None)
raw_req_num = request.headers.get(state_header, None)
if raw_req_num == None:
return error("no req_num header in request")
else:
req_num = int(raw_req_num)
if req_num > 8:
return error("req_num %s out of range" % req_num)

headers.append(("Access-Control-Expose-Headers", state_header))
headers.append((state_header, req_num))
headers.append(("A", req_num))
headers.append(("B", req_num))

if req_num % 2: # odd requests are the first in a test pair
if inm:
# what are you doing here? This should be a fresh request.
return error("If-None-Match on first request")
else:
status = 200, "OK"
headers.append(("Access-Control-Allow-Origin", "*"))
headers.append(("Content-Type", "text/plain"))
headers.append(("Cache-Control", "private, max-age=3, must-revalidate"))
headers.append(("ETag", etag))
return status, headers, "Success"
else: # even requests are the second in a pair, and should have a good INM.
if inm != etag:
# Bad browser.
if inm == None:
return error("If-None-Match missing")
else:
return error("If-None-Match '%s' mismatches")
else:
if req_num == 2:
pass # basic, vanilla check
elif req_num == 4:
headers.append(("Access-Control-Expose-Headers", "a, b"))
elif req_num == 6:
headers.append(("Access-Control-Expose-Headers", "a"))
elif req_num == 8:
headers.append(("Access-Control-Allow-Origin", "other.origin.example:80"))
status = 304, "Not Modified"
return status, headers, ""

0 comments on commit 8b967bb

Please sign in to comment.