blob: db52ea19bf2308c0912b45aa8764b651868cf7e8 [file] [log] [blame]
// |base_host| and |other_host| are custom hostnames for the base/other origin.
// They default to '127.0.0.1' and 'localhost' respectively.
// See fetch-test-options.js.
function get_thorough_test_options(base_host, other_host) {
var options = get_fetch_test_options(base_host, other_host);
var BASE_URL = options['BASE_ORIGIN'] +
'/serviceworker/resources/fetch-access-control.php?';
var OTHER_BASE_URL = options['OTHER_ORIGIN'] +
'/serviceworker/resources/fetch-access-control.php?';
var SCOPE = options['BASE_ORIGIN'] +
'/fetch/resources/thorough-iframe.html?' + options['TEST_OPTIONS'];
return Object.assign({
BASE_URL: BASE_URL,
OTHER_BASE_URL: OTHER_BASE_URL,
SCOPE: SCOPE,
IFRAME_ORIGIN: options['BASE_ORIGIN'],
BASE_URL_WITH_USERNAME: BASE_URL.replace('://', '://user@'),
OTHER_BASE_URL_WITH_USERNAME: OTHER_BASE_URL.replace('://', '://user@'),
BASE_URL_WITH_PASSWORD: BASE_URL.replace('://', '://user:pass@'),
OTHER_BASE_URL_WITH_PASSWORD: OTHER_BASE_URL.replace('://', '://user:pass@'),
REDIRECT_URL: options['BASE_ORIGIN'] +
'/serviceworker/resources/redirect.php?Redirect=',
OTHER_REDIRECT_URL: options['OTHER_ORIGIN'] +
'/serviceworker/resources/redirect.php?Redirect=',
REDIRECT_LOOP_URL: options['BASE_ORIGIN'] +
'/fetch/resources/redirect-loop.php?Redirect=',
OTHER_REDIRECT_LOOP_URL: options['OTHER_ORIGIN'] +
'/fetch/resources/redirect-loop.php?Redirect=',
IFRAME_URL: SCOPE,
WORKER_URL: options['BASE_ORIGIN'] +
'/fetch/resources/thorough-worker.js?' + options['TEST_OPTIONS']
}, options);
}
function onlyOnServiceWorkerProxiedTest(checkFuncs) {
return [];
}
// Functions to check the result from the ServiceWorker.
var checkFetchResult = function(expected, url, data) {
assert_equals(data.fetchResult, expected, url + ' should be ' + expected);
};
var checkFetchResponseBody = function(hasBody, url, data) {
assert_equals(data.fetchResult,
'resolved',
'fetchResult must be resolved. url: ' + url);
if (hasBody) {
assert_not_equals(data.body, '',
'response must have body. url: ' + url);
} else {
assert_equals(data.body, '',
'response must not have body. url: ' + url);
}
};
var checkFetchResponseHeader = function(name, expected, url, data) {
assert_equals(data.fetchResult,
'resolved',
'fetchResult must be resolved. url: ' + url);
var exist = false;
for (var i = 0; i < data.headers.length; ++i) {
if (data.headers[i][0] === name) {
exist = true;
}
}
assert_equals(exist,
expected,
'header check failed url: ' + url + ' name: ' + name);
};
var checkFetchResponseType = function(type, url, data) {
assert_equals(data.fetchResult,
'resolved',
'fetchResult must be resolved. url = ' + url);
assert_equals(data.type,
type,
'type must match. url: ' + url);
};
var checkFetchResponseRedirected = function(expected, url, data) {
assert_equals(data.fetchResult,
'resolved',
'fetchResult must be resolved. url = ' + url);
assert_equals(data.redirected,
expected,
url + ' redirected flag should match');
};
var checkURLList = function(redirectedURLList, url, data) {
if (!self.internals)
return;
var expectedURLList = [url].concat(redirectedURLList);
assert_equals(data.fetchResult,
'resolved',
'fetchResult must be resolved. url = ' + url);
assert_array_equals(data.urlList,
expectedURLList,
url + ' URL list should match');
};
var showComment = function(url, data) {
assert_true(!data.comment, 'Show comment: ' + data.comment + ' url: ' + url);
}
var fetchIgnored = checkFetchResult.bind(this, 'ignored');
var fetchResolved = checkFetchResult.bind(this, 'resolved');
var fetchRejected = checkFetchResult.bind(this, 'rejected');
var fetchError = checkFetchResult.bind(this, 'error');
var hasBody = checkFetchResponseBody.bind(this, true);
var noBody = checkFetchResponseBody.bind(this, false);
var hasContentLength =
checkFetchResponseHeader.bind(this, 'content-length', true);
var noContentLength =
checkFetchResponseHeader.bind(this, 'content-length', false);
var hasContentType =
checkFetchResponseHeader.bind(this, 'content-type', true);
var noContentType =
checkFetchResponseHeader.bind(this, 'content-type', false);
var hasServerHeader =
checkFetchResponseHeader.bind(this, 'x-serviceworker-serverheader', true);
var noServerHeader =
checkFetchResponseHeader.bind(this, 'x-serviceworker-serverheader', false);
var typeBasic = checkFetchResponseType.bind(this, 'basic');
var typeCors = checkFetchResponseType.bind(this, 'cors');
var typeOpaque = checkFetchResponseType.bind(this, 'opaque');
var typeOpaqueredirect = checkFetchResponseType.bind(this, 'opaqueredirect');
var responseRedirected = checkFetchResponseRedirected.bind(this, true);
var responseNotRedirected = checkFetchResponseRedirected.bind(this, false);
// Functions to check the result of JSONP which is evaluated in
// thorough-iframe.html by appending <script> element.
var checkJsonpResult = function(expected, url, data) {
assert_equals(data.jsonpResult,
expected,
url + ' jsonpResult should match');
};
var checkJsonpHeader = function(name, value, url, data) {
assert_equals(data.jsonpResult,
'success',
url + ' jsonpResult must be success');
assert_equals(data.headers[name],
value,
'Request header check failed url:' + url + ' name:' + name);
};
var checkJsonpMethod = function(method, url, data) {
assert_equals(data.jsonpResult,
'success',
url + ' jsonpResult must be success');
assert_equals(data.method,
method,
'Method must match url:' + url);
};
var checkJsonpAuth = function(username, password, cookie, url, data) {
assert_equals(data.jsonpResult,
'success',
url + ' jsonpResult must be success');
assert_equals(data.username,
username,
'Username must match. url: ' + url);
assert_equals(data.password,
password,
'Password must match. url: ' + url);
assert_equals(data.cookie,
cookie,
'Cookie must match. url: ' + url);
};
var checkJsonpCookie = function(cookie, url, data) {
assert_equals(data.jsonpResult,
'success',
url + ' jsonpResult must be success');
assert_equals(data.cookie,
cookie,
'Cookie must match. url: ' + url);
};
var checkJsonpError = checkJsonpResult.bind(this, 'error');
var checkJsonpSuccess = checkJsonpResult.bind(this, 'success');
var checkJsonpNoRedirect = checkJsonpResult.bind(this, 'noredirect');
var hasCustomHeader =
checkJsonpHeader.bind(this, 'X-ServiceWorker-Test', 'test');
var hasCustomHeader2 = function(url, data) {
checkJsonpHeader('X-ServiceWorker-s', 'test1', url, data);
checkJsonpHeader('X-ServiceWorker-Test', 'test2, test3', url, data);
checkJsonpHeader('X-ServiceWorker-ua', 'test4', url, data);
checkJsonpHeader('X-ServiceWorker-U', 'test5', url, data);
checkJsonpHeader('X-ServiceWorker-V', 'test6', url, data);
};
var noCustomHeader =
checkJsonpHeader.bind(this, 'X-ServiceWorker-Test', undefined);
var methodIsGET = checkJsonpMethod.bind(this, 'GET');
var methodIsPOST = checkJsonpMethod.bind(this, 'POST');
var methodIsPUT = checkJsonpMethod.bind(this, 'PUT');
var methodIsXXX = checkJsonpMethod.bind(this, 'XXX');
var authCheckNone =
checkJsonpAuth.bind(this, 'undefined', 'undefined', 'undefined');
var authCheck1 = checkJsonpAuth.bind(this, 'username1', 'password1', 'cookie1');
var authCheck2 = checkJsonpAuth.bind(this, 'username2', 'password2', 'cookie2');
var cookieCheck1 = checkJsonpCookie.bind(this, 'cookie1');
var cookieCheck2 = checkJsonpCookie.bind(this, 'cookie2');
var cookieCheckA = checkJsonpCookie.bind(this, 'cookieA');
var cookieCheckB = checkJsonpCookie.bind(this, 'cookieB');
var cookieCheckC = checkJsonpCookie.bind(this, 'cookieC');
var cookieCheckNone = checkJsonpCookie.bind(this, 'undefined');
if (location.href.indexOf('base-https') >= 0)
authCheck1 = checkJsonpAuth.bind(this, 'username1s', 'password1s', 'cookie1');
if (location.href.indexOf('other-https') >= 0)
authCheck2 = checkJsonpAuth.bind(this, 'username2s', 'password2s', 'cookie2');
function executeServiceWorkerProxiedTests(test_targets, thorough_options) {
var test = async_test('Verify access control of fetch() in a Service Worker');
var {WORKER_URL, IFRAME_ORIGIN, SCOPE} = thorough_options;
test.step(function() {
var worker = undefined;
var frameWindow = {};
var counter = 0;
window.addEventListener('message', test.step_func(onMessage), false);
Promise.resolve()
.then(function() {
return service_worker_unregister_and_register(test,
WORKER_URL,
SCOPE);
})
.then(function(registration) {
worker = registration.installing;
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = test.step_func(onWorkerMessage);
worker.postMessage(
{port: messageChannel.port2}, [messageChannel.port2]);
return wait_for_state(test, worker, 'activated');
})
.then(function() {
return with_iframe(SCOPE);
})
.then(function(frame) {
frameWindow = frame.contentWindow;
// Start tests.
loadNext();
})
.catch(unreached_rejection(test));
var readyFromWorkerReceived = undefined;
var resultFromWorkerReceived = undefined;
var resultFromIframeReceived = undefined;
function onMessage(e) {
// The message is sent from thorough-iframe.html in report()
// which is called by appending <script> element which source code is
// generated by fetch-access-control.php.
if (test_targets[counter][2]) {
test_targets[counter][2].forEach(function(checkFunc) {
checkFunc.call(this, test_targets[counter][0], e.data);
});
}
resultFromIframeReceived();
}
function onWorkerMessage(e) {
// The message is sent from the ServiceWorker.
var message = e.data;
if (message.msg === 'READY') {
readyFromWorkerReceived();
return;
}
var checks = test_targets[counter][1].concat(showComment);
checks.forEach(function(checkFunc) {
checkFunc.call(this, test_targets[counter][0], message);
});
resultFromWorkerReceived();
}
function loadNext() {
var workerPromise = new Promise(function(resolve, reject) {
resultFromWorkerReceived = resolve;
});
var iframePromise = new Promise(function(resolve, reject) {
resultFromIframeReceived = resolve;
});
Promise.all([workerPromise, iframePromise])
.then(test.step_func(function() {
++counter;
if (counter === test_targets.length) {
service_worker_unregister_and_done(test, SCOPE);
} else {
loadNext();
}
}));
(new Promise(function(resolve, reject) {
readyFromWorkerReceived = resolve;
worker.postMessage({msg: 'START TEST CASE'});
}))
.then(test.step_func(function() {
frameWindow.postMessage(
{url: test_targets[counter][0]},
IFRAME_ORIGIN);
}));
}
});
}
function getQueryParams(url) {
var search = (new URL(url)).search;
if (!search) {
return {};
}
var ret = {};
var params = search.substring(1).split('&');
params.forEach(function(param) {
var element = param.split('=');
ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]);
});
return ret;
}
function getRequestInit(params) {
var init = {};
if (params['method']) {
init['method'] = params['method'];
}
if (params['mode']) {
init['mode'] = params['mode'];
}
if (params['redirectmode']) {
init['redirect'] = params['redirectmode'];
}
if (params['credentials']) {
init['credentials'] = params['credentials'];
}
if (params['headers'] === 'CUSTOM') {
init['headers'] = {'X-ServiceWorker-Test': 'test'};
} else if (params['headers'] === 'CUSTOM2') {
init['headers'] = [['X-ServiceWorker-Test', 'test2'],
['X-ServiceWorker-ua', 'test4'],
['X-ServiceWorker-V', 'test6'],
['X-ServiceWorker-s', 'test1'],
['X-ServiceWorker-Test', 'test3'],
['X-ServiceWorker-U', 'test5']];
} else if (params['headers'] === 'SAFE') {
init['headers'] = [['Accept', '*/*'],
['Accept-Language', 'en-us,de'],
['Content-Language', 'en-us'],
['Content-Type', 'text/plain'],
['Save-data', 'on']];
} else if (params['headers'] === '{}') {
init['headers'] = {};
}
return init;
}
function headersToArray(headers) {
var ret = [];
// Workaround for Firefox. iterable is not implemented yet.
// This is used only by checkFetchResponseHeader, and
// checkFetchResponseHeader is used only for the header names listed below.
// FIXME: Replace it with the original code below when Firefox supports
// iterable.
['content-length', 'content-type', 'x-serviceworker-serverheader'].forEach(
function(name) {
for (var header of headers){
ret.push(header);
}
});
return ret;
}
function doFetch(request) {
var originalURL = request.url;
var params = getQueryParams(originalURL);
var init = getRequestInit(params);
var url = params['url'];
try {
if (url) {
request = new Request(url, init);
} else {
request = new Request(request, init);
}
var response;
return fetch(request)
.then(function(res) {
response = res;
return res.clone().text()
.then(function(body) {
return Promise.resolve({
// Setting a comment will fail the test and show the
// comment in the result. Use this for debugging
// tests.
comment: undefined,
fetchResult: 'resolved',
body: body,
status: response.status,
headers: headersToArray(response.headers),
type: response.type,
redirected: response.redirected,
urlList: self.internals ?
self.internals.getInternalResponseURLList(response) :
[],
response: response,
originalURL: originalURL
});
})
.catch(function(e) {
return Promise.resolve({fetchResult: 'error'});
});
})
.catch(function(e) {
return Promise.resolve({fetchResult: 'rejected'});
});
} catch (e) {
return Promise.resolve({fetchResult: 'error'});
}
}
var report_data = {};
function report(data) {
report_data = data;
}
// |test_target| is an array. The first element of |test_target| is the URL to
// be fetched. The second element of |test_target| is an array of test functions
// which will be called with the result of doFetch(). The third element of
// |test_target| is an array of test functions which will be called with
// |report_data| set by report() which is called while executing
// "eval(message.body)".
function executeTest(test_target) {
if (test_target.length == 0) {
return Promise.resolve();
}
return doFetch(new Request(test_target[0],
{credentials: 'same-origin', mode: 'no-cors'}))
.then(function(message) {
var checks = test_target[1].concat(showComment);
checks.forEach(function(checkFunc) {
checkFunc.call(this, test_target[0], message);
});
if (test_target[2]) {
report_data = {};
if (message.fetchResult !== 'resolved' ||
message.body === '' ||
400 <= message.status) {
report({jsonpResult:'error'});
} else {
eval(message.body);
}
assert_not_equals(report_data, {}, 'data should be set');
test_target[2].forEach(function(checkFunc) {
checkFunc.call(this, test_target[0], report_data);
});
}
});
}
function executeTests(test_targets) {
for (var i = 0; i < test_targets.length; ++i) {
promise_test(
function(counter, t) {
return executeTest(test_targets[counter]);
}.bind(this, i),
"executeTest-" + i);
}
}