Skip to content

Commit

Permalink
Testing request cloning and header sanitisation
Browse files Browse the repository at this point in the history
  • Loading branch information
jakearchibald committed Apr 6, 2018
1 parent 782344a commit 5310b7d
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 64 deletions.
Binary file removed fetch/api/resources/header.wav
Binary file not shown.
64 changes: 0 additions & 64 deletions fetch/api/resources/long-wav.py

This file was deleted.

1 change: 1 addition & 0 deletions fetch/privileged-headers/resources/basic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html>
104 changes: 104 additions & 0 deletions fetch/privileged-headers/resources/long-wav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""
This generates a 30 minute silent wav, and is capable of
responding to Range requests.
"""
import time
import re
import struct


def create_wav_header(sample_rate, bit_depth, channels, duration):
bytes_per_sample = bit_depth / 8
block_align = bytes_per_sample * channels
byte_rate = sample_rate * block_align
sub_chunk_2_size = duration * byte_rate

data = b''
# ChunkID
data += b'RIFF'
# ChunkSize
data += struct.pack('<L', 36 + sub_chunk_2_size)
# Format
data += b'WAVE'
# Subchunk1ID
data += b'fmt '
# Subchunk1Size
data += struct.pack('<L', 16)
# AudioFormat
data += struct.pack('<H', 1)
# NumChannels
data += struct.pack('<H', channels)
# SampleRate
data += struct.pack('<L', sample_rate)
# ByteRate
data += struct.pack('<L', byte_rate)
# BlockAlign
data += struct.pack('<H', block_align)
# BitsPerSample
data += struct.pack('<H', bit_depth)
# Subchunk2ID
data += b'data'
# Subchunk2Size
data += struct.pack('<L', sub_chunk_2_size)

return data


def main(request, response):
response.headers.set("Content-Type", "audio/wav")
response.headers.set("Accept-Ranges", "bytes")
response.headers.set("Cache-Control", "no-cache")

range_header = request.headers.get('Range', '')

sample_rate = 8000
bit_depth = 8
channels = 1
duration = 60 * 5

total_length = (sample_rate * bit_depth * channels * duration) / 8
bytes_to_send = total_length
initial_write = ''

if range_header:
response.status = 206
start, end = re.search(r'^bytes=(\d*)-(\d*)$', range_header).groups()

start = int(start)
end = int(end) if end else 0

if end:
bytes_to_send = (end + 1) - start
else:
bytes_to_send = total_length - start

wav_header = create_wav_header(sample_rate, bit_depth, channels, duration)

if start < len(wav_header):
initial_write = wav_header[start:]

if bytes_to_send < len(initial_write):
initial_write = initial_write[0:bytes_to_send]

content_range = "bytes {}-{}/{}".format(start, end or total_length - 1, total_length)

response.headers.set("Content-Range", content_range)
else:
initial_write = create_wav_header(sample_rate, bit_depth, channels, duration)

response.headers.set("Content-Length", bytes_to_send)

response.write_status_headers()
response.writer.write(initial_write)

bytes_to_send -= len(initial_write)

while bytes_to_send > 0:
if not response.writer.flush():
break

to_send = b'\x00' * min(bytes_to_send, sample_rate)
bytes_to_send -= len(to_send)

response.writer.write(to_send)
time.sleep(0.5)
File renamed without changes.
77 changes: 77 additions & 0 deletions fetch/privileged-headers/resources/range-sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
importScripts('/resources/testharness.js');

setup({
explicit_done: true
});

function assert_range_request(request, expectedRangeHeader, name) {
assert_equals(request.headers.get('Range'), expectedRangeHeader, name);
}

let gotRangeResponse = false;

addEventListener('fetch', event => {
const { request } = event;

if (!request.headers.has('Range') || gotRangeResponse) return;
gotRangeResponse = true;

const rangeValue = request.headers.get('Range');

test(() => {
assert_range_request(new Request(request), rangeValue, `Untampered`);
assert_range_request(new Request(request, {}), rangeValue, `Untampered (no init props set)`);
assert_range_request(new Request(request, { __foo: 'bar' }), rangeValue, `Untampered (only invalid props set)`);
assert_range_request(new Request(request, { move: 'cors' }), rangeValue, `More permissive mode`);
assert_range_request(request.clone(), rangeValue, `Clone`);
}, "Range headers correctly preserved");

test(() => {
assert_range_request(new Request(request, { headers: { Range: 'foo' } }), null, `Tampered - range header set`);
assert_range_request(new Request(request, { headers: {} }), null, `Tampered - empty headers set`);
assert_range_request(new Request(request, { mode: 'no-cors' }), null, `Tampered – mode set`);
assert_range_request(new Request(request, { cache: 'no-cache' }), null, `Tampered – cache mode set`);
}, "Range headers correctly removed");

test(() => {
let headers;

headers = new Request(request).headers;
headers.delete('does-not-exist');
assert_equals(header.get('Range'), rangeValue, `Preserved if no header removed`);

headers = new Request(request).headers;
headers.append('foo', 'bar');
assert_equals(header.get('Range'), rangeValue, `Preserved if silent-failure on append (due to request-no-cors guard)`);

headers = new Request(request).headers;
headers.set('foo', 'bar');
assert_equals(header.get('Range'), rangeValue, `Preserved if silent-failure on set (due to request-no-cors guard)`);

headers = new Request(request).headers;
headers.append('Range', 'foo');
assert_equals(header.get('Range'), rangeValue, `Preserved if silent-failure on append (due to request-no-cors guard)`);

headers = new Request(request).headers;
headers.set('Range', 'foo');
assert_equals(header.get('Range'), rangeValue, `Preserved if silent-failure on set (due to request-no-cors guard)`);

headers = new Request(request).headers;
headers.append('Accept', 'whatever');
assert_equals(header.get('Range'), null, `Stripped if header successfully appended`);

headers = new Request(request).headers;
headers.set('Accept', 'whatever');
assert_equals(header.get('Range'), null, `Stripped if header successfully set`);

headers = new Request(request).headers;
headers.delete('Accept');
assert_equals(header.get('Range'), null, `Stripped if header successfully deleted`);

headers = new Request(request).headers;
headers.delete('Range');
assert_equals(header.get('Range'), null, `Stripped if range header successfully deleted`);
}, "Headers correctly filtered");

done();
});
34 changes: 34 additions & 0 deletions fetch/privileged-headers/sw.https.window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// META: script=../../../service-workers/service-worker/resources/test-helpers.sub.js

const SCOPE = 'resources/basic.html';

async function cleanup() {
for (const iframe of document.querySelectorAll('.test-iframe')) {
iframe.parentNode.removeChild(iframe);
}

const reg = await navigator.serviceWorker.getRegistration(SCOPE);
if (reg) await reg.unregister();
}

async function setupRegistration(t) {
await cleanup();
const reg = await navigator.serviceWorker.register('resources/range-sw.js', { scope: SCOPE });
await wait_for_state(t, reg.installing, 'activated');
return reg;
}

promise_test(async t => {
const reg = await setupRegistration(t);
const iframe = await with_iframe(SCOPE);
const w = iframe.contentWindow;

// Trigger a range request
const audio = w.document.createElement('audio');
audio.muted = true;
audio.src = 'long-wav.py';
audio.preload = true;
w.document.body.appendChild(audio);

fetch_tests_from_worker(reg.active);
}, "Defer tests to service worker");

0 comments on commit 5310b7d

Please sign in to comment.