import * as ava from 'ava'; import { XMLHttpRequest } from '../xml-http-request'; import { HttpServer } from './helpers/server'; function contextualize(getContext: () => T): ava.RegisterContextual { ava.test.beforeEach(t => { Object.assign(t.context, getContext()); }); return ava.test; } const test = contextualize(() => ({ xhr: new XMLHttpRequest() })); test.before(async () => { await HttpServer.serverStarted; XMLHttpRequest.nodejsSet({ baseUrl: HttpServer.testUrl().replace('https://', 'http://') }); }); test.beforeEach(t => { t.context.xhr = new XMLHttpRequest(); }); test('#setRequestHeader with allowed headers should send the headers', async t => { const xhr = t.context.xhr; xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`); xhr.responseType = 'text'; xhr.setRequestHeader('Authorization', 'lol'); xhr.setRequestHeader('X-Answer', '42'); xhr.setRequestHeader('X-Header-Name', 'value'); await new Promise(resolve => { xhr.onload = () => { t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON'); const headers = JSON.parse(xhr.responseText); t.true(headers.hasOwnProperty('authorization'), 'headers have authorization header'); t.is(headers.authorization, 'lol', 'authorization header is correct'); t.true(headers.hasOwnProperty('x-answer'), 'headers have x-answer header'); t.is(headers['x-answer'], '42', 'x-answer header is correct'); t.true(headers.hasOwnProperty('x-header-name'), 'headers have x-header-name header'); t.is(headers['x-header-name'], 'value', 'x-header-name header is correct'); resolve(); }; xhr.send(''); }); }); test('#setRequestHeader with a mix of allowed and forbidden headers should only send the allowed headers', async t => { const xhr = t.context.xhr; xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`); xhr.responseType = 'text'; xhr.setRequestHeader('Authorization', 'lol'); xhr.setRequestHeader('Proxy-Authorization', 'evil:kitten'); xhr.setRequestHeader('Sec-Breach', 'yes please'); xhr.setRequestHeader('Host', 'www.google.com'); xhr.setRequestHeader('Origin', 'https://www.google.com'); xhr.setRequestHeader('X-Answer', '42'); await new Promise(resolve => { xhr.onload = () => { t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON'); const headers = JSON.parse(xhr.responseText); t.true(headers.hasOwnProperty('authorization'), 'headers have authorization header'); t.is(headers['authorization'], 'lol', 'authorization header is correct'); t.false(headers.hasOwnProperty('proxy-authorization'), 'headers do not have proxy-authorization header'); t.false(headers.hasOwnProperty('sec-breach'), 'headers do not have sec-breach header'); t.notRegex(headers['origin'] || '', /www\.google\.com/, 'header "origin" should not contain www.google.com'); t.notRegex(headers['host'] || '', /www\.google\.com/, 'header "host" should not contain www.google.com'); t.true(headers.hasOwnProperty('x-answer'), 'headers have x-answer header'); t.is(headers['x-answer'], '42', 'x-answer header is correct'); resolve(); }; xhr.send(''); }); }); test('#setRequestHeader with repeated headers should send all headers', async t => { const xhr = t.context.xhr; xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`); xhr.responseType = 'text'; xhr.setRequestHeader('Authorization', 'troll'); xhr.setRequestHeader('Authorization', 'lol'); xhr.setRequestHeader('Authorization', 'lol'); xhr.setRequestHeader('X-Answer', '42'); await new Promise(resolve => { xhr.onload = () => { t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON'); const headers = JSON.parse(xhr.responseText); t.true(headers.hasOwnProperty('authorization'), 'headers have authorization header'); t.is(headers['authorization'], 'troll, lol, lol', 'authorization header is correct'); t.true(headers.hasOwnProperty('x-answer'), 'headers have x-answer header'); t.is(headers['x-answer'], '42', 'x-answer header is correct'); resolve(); }; xhr.send(''); }); }); test('#setRequestHeader with no headers should set the protected headers correctly', async t => { const xhr = t.context.xhr; xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`); xhr.responseType = 'text'; xhr.setRequestHeader('Authorization', 'troll'); xhr.setRequestHeader('Authorization', 'lol'); xhr.setRequestHeader('Authorization', 'lol'); xhr.setRequestHeader('X-Answer', '42'); await new Promise(resolve => { xhr.onload = () => { t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON'); const headers = JSON.parse(xhr.responseText); t.true(headers.hasOwnProperty('connection'), 'headers have connection header'); t.is(headers['connection'], 'keep-alive', 'connection header is correct'); t.true(headers.hasOwnProperty('host'), 'headers have host header'); t.is(headers['host'], `localhost:${HttpServer.port}`, 'host header is correct'); t.true(headers.hasOwnProperty('user-agent'), 'headers have user-agent header'); t.regex(headers['user-agent'], /^Mozilla\//, 'user-agent header is correct'); resolve(); }; xhr.send(''); }); }); test('#getResponseHeader returns accessible headers, returns null for private headers, has headers on HEADERS_RECEIVED readyState', async t => { const xhr = t.context.xhr; xhr.open('POST', `http://localhost:${HttpServer.port}/_/get-headers`); const headerJson = `{ "Accept-Ranges": "bytes", "Content-Type": "application/xhr2; charset=utf-1337", "Set-Cookie": "UserID=JohnDoe; Max-Age=3600; Version=1", "X-Header": "one, more, value" }`; await new Promise(resolve => { xhr.onloadend = () => { t.is(xhr.getResponseHeader('AccEPt-RANgeS'), 'bytes', 'AccEPt-RANgeS works correctly'); t.is(xhr.getResponseHeader('content-Type'), 'application/xhr2; charset=utf-1337', 'content-Type works correctly'); t.is(xhr.getResponseHeader('X-Header'), 'one, more, value', 'X-Header works correctly'); t.is(xhr.getResponseHeader('set-cookie'), null, 'set-cookie works correctly'); resolve(); }; xhr.onreadystatechange = () => { if (xhr.readyState !== XMLHttpRequest.HEADERS_RECEIVED) { return; } t.is(xhr.getResponseHeader('AccEPt-RANgeS'), 'bytes', 'AccEPt-RANgeS works correctly when HEADERS_RECEIVED ready state'); }; xhr.send(headerJson); }); }); test('#getAllResponseHeaders contains accessible headers, does not contain private headers, has headers on HEADERS_RECEIVED readyState', async t => { const xhr = t.context.xhr; xhr.open('POST', `http://localhost:${HttpServer.port}/_/get-headers`); const headerJson = `{ "Accept-Ranges": "bytes", "Content-Type": "application/xhr2; charset=utf-1337", "Set-Cookie": "UserID=JohnDoe; Max-Age=3600; Version=1", "X-Header": "one, more, value" }`; await new Promise(resolve => { xhr.onloadend = () => { const headers = xhr.getAllResponseHeaders(); t.regex(headers, /(\A|\r\n)accept-ranges: bytes(\r\n|\Z)/mi); t.regex(headers, /(\A|\r\n)content-type: application\/xhr2; charset=utf-1337(\r\n|\Z)/mi); t.regex(headers, /(\A|\r\n)X-Header: one, more, value(\r\n|\Z)/mi); t.notRegex(headers, /(\A|\r\n)set-cookie:/mi); resolve(); }; xhr.onreadystatechange = () => { if (xhr.readyState !== XMLHttpRequest.HEADERS_RECEIVED) { return; } const headers = xhr.getAllResponseHeaders(); t.regex(headers, /(\A|\r\n)accept-ranges: bytes(\r\n|\Z)/mi); }; xhr.send(headerJson); }); }); // TODO: // * set request header after request opened should throw InvalidStateError // *