Skip to content
This repository has been archived by the owner on Apr 5, 2024. It is now read-only.

Commit

Permalink
Merge e2c5867 into a428100
Browse files Browse the repository at this point in the history
  • Loading branch information
Francois Marier committed May 26, 2015
2 parents a428100 + e2c5867 commit 04ac49a
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 67 deletions.
63 changes: 37 additions & 26 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ var upgradeToHttps = function (urlString) {
urlObject.protocol = 'https:';
}

// If after all of this we don't end up with a supported URL, bail
if (urlObject.protocol !== 'https:' && urlObject.protocol !== 'http:') {
return false;
}

return url.format(urlObject);
};

Expand All @@ -49,18 +54,11 @@ var upgradeToHttps = function (urlString) {
* @return {Array} list of potential issues
*/
var eligibility = function (request) {
var badHeaders = [
'www-authenticate',
'refresh'
];
var issues = [];

var issues = badHeaders.filter(function (header) {
return request.getResponseHeader(header);
});

var cc = request.getResponseHeader('cache-control');
if (cc && (cc.indexOf('no-store') > -1 || cc.indexOf('private') > -1)) {
issues.push('no-cache');
var acao = request.getResponseHeader('access-control-allow-origin');
if (!acao) {
issues.push('non-cors');
}

return issues;
Expand All @@ -83,12 +81,14 @@ var processResource = function (resourceUrl, cb) {

if (request.status !== 200) {
return cb({
'status': false,
'response': request.status
'failed': true,
'status': request.status
});
}

return cb({
'failed': false,
'status': request.status,
'url': resourceUrl,
'eligibility': eligibility(request),
'data': request.responseText,
Expand All @@ -106,17 +106,16 @@ var processResource = function (resourceUrl, cb) {
*/
var fetchResource = function (resourceUrl, cb) {
resourceUrl = upgradeToHttps(resourceUrl);
if (!resourceUrl) {
cb(false);
return;
}

var request = new XMLHttpRequest();
request.onreadystatechange = processResource.bind(request, resourceUrl, cb);
request.onerror = function () {
return cb(false);
};

request.open('GET', resourceUrl);
request.send();

return request;
};


Expand Down Expand Up @@ -153,28 +152,31 @@ var guessResourceType = function (resource) {
*
* @param {Array} options.algorithms - List of desired hash algorithms
* @param {string} options.url - Resource URL
* @return {Object.XMLHttpRequest} fetchResource request
*/
var generate = function (options, cb) {
if (typeof options.algorithms === 'string') {
options.algorithms = [options.algorithms];
}

return fetchResource(options.url, function (resource) {
fetchResource(options.url, function (resource) {
if (!resource) {
return cb({
'status': false,
'response': 0
'failed': true,
'status': 0
});
}
if (resource.status !== 200) {
return cb(resource);
}

var sri = sriToolbox.generate({
'algorithms': options.algorithms,
'full': true
}, resource.data);

return cb({
'status': true,
'failed': false,
'status': resource.status,
'url': resource.url,
'type': guessResourceType({
'url': resource.url,
Expand All @@ -194,16 +196,25 @@ var generate = function (options, cb) {
* @param {string} resourceUrl
* @param {Array} algorithms
* @param {Function} cb - callback
* @return {Function.cb}
*/
var generateElement = function (resourceUrl, algorithms, cb) {
var options = {
'url': resourceUrl,
'algorithms': algorithms
};

return generate(options, function (resource) {
generate(options, function (resource) {
var element;
if (resource.failed) {
if (resource.status > 0) {
return cb('Error: fetching the resource returned a ' + resource.status + ' error code.');
} else {
return cb('Error: fetching the resource returned an unexpected error.');
}
}
if (resource.eligibility.length !== 0) {
return cb('Error: this resource is not eligible for integrity checks. See http://enable-cors.org/server.html');
}

switch (resource.type) {
case 'js':
Expand All @@ -227,7 +238,7 @@ var generateElement = function (resourceUrl, algorithms, cb) {
break;
}

cb(element);
return cb(element);
});
};

Expand Down
154 changes: 113 additions & 41 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,54 +53,34 @@ describe('upgradeToHttps()', function () {
assert.equal(relativeKnownUrl, 'https://code.jquery.com/script.js');
});
});
});

describe('eligibility()', function () {
describe('Eligible', function () {
it('Irrelevant header', function () {
var allGood = new FauxXHR({ headers: { 'dnt': '1' } });
var result = helpers.eligibility(allGood);
assert.deepEqual(result, []);
describe('Invalid URLs', function () {
it('Invalid scheme', function () {
var ftpScheme = helpers.upgradeToHttps('ftp://example.com/script.js');
assert.strictEqual(ftpScheme, false);
});
});

describe('Bad headers', function () {
it('refresh', function () {
var badRefresh = new FauxXHR({ headers: { 'refresh': '1' } });
var result = helpers.eligibility(badRefresh);
assert.deepEqual(result, ['refresh']);
});

it('www-authenticate', function () {
var badAuthenticate = new FauxXHR({ headers: { 'www-authenticate': '1' } });
var result = helpers.eligibility(badAuthenticate);
assert.deepEqual(result, ['www-authenticate']);
it('Bare hostname', function () {
var bareHostname = helpers.upgradeToHttps('foobar');
assert.equal(bareHostname, 'http://foobar/');
});
});
});

describe('Caching', function () {
it('Explicit public cache', function () {
var publicCache1 = new FauxXHR({ headers: { 'cache-control': 'public' } });
var result = helpers.eligibility(publicCache1);
assert.deepEqual(result, []);
});

it('Implicit public cache', function () {
var publicCache2 = new FauxXHR({ headers: { 'cache-control': 'bogus' } });
var result = helpers.eligibility(publicCache2);
describe('eligibility()', function () {
describe('Eligible', function () {
it('CORS', function () {
var allGood = new FauxXHR({ headers: { 'access-control-allow-origin': '*' } });
var result = helpers.eligibility(allGood);
assert.deepEqual(result, []);
});
});

it('private', function () {
var badCache1 = new FauxXHR({ headers: { 'cache-control': 'max-age=1000,private' } });
var result = helpers.eligibility(badCache1);
assert.deepEqual(result, ['no-cache']);
});

it('no-store', function () {
var badCache2 = new FauxXHR({ headers: { 'cache-control': 'public,no-store' } });
var result = helpers.eligibility(badCache2);
assert.deepEqual(result, ['no-cache']);
describe('Non-eligible', function () {
it('non-CORS', function () {
var nonCORS = new FauxXHR({ headers: { 'dnt': '1' } });
var result = helpers.eligibility(nonCORS);
assert.deepEqual(result, ['non-cors']);
});
});
});
Expand Down Expand Up @@ -196,7 +176,8 @@ describe ("generate()", function () {
algorithms: ['sha256']
};
var expect = {
status: true,
failed: false,
status: 200,
url: 'https://code.jquery.com/jquery-1.11.2.min.js',
type: 'js',
integrity: 'sha256-Ls0pXSlb7AYs7evhd+VLnWsZ/AqEHcXBeMZUycz/CcA=',
Expand All @@ -221,7 +202,8 @@ describe ("generate()", function () {
algorithms: ['sha256']
};
var expect = {
status: true,
failed: false,
status: 200,
url: 'https://code.jquery.com/ui/1.11.3/themes/black-tie/jquery-ui.css',
type: 'css',
integrity: 'sha256-DW9MX1sLpQ9seN/7+gouAyFj8+xc+lQD6Q9DKWqQDy0=',
Expand All @@ -239,4 +221,94 @@ describe ("generate()", function () {
assert.deepEqual(result, expect);
});
})();

(function (result) {
var resource = {
url: 'https://code.jquery.com/jquery-1.11.2.min.jss',
algorithms: ['sha256']
};
var expect = {
failed: true,
status: 404,
};

before(function (done) {
helpers.generate(resource, function (data) {
result = data;
done();
});
});

it ("404", function () {
assert.deepEqual(result, expect);
});
})();
});


// Ideally we should find a way to test this without using the network
describe ("generateElement()", function () {
(function (result) {
var url = 'https://www.google-analytics.com/ga.js';
var expect = 'Error: this resource is not eligible for integrity checks. See http://enable-cors.org/server.html';

before(function (done) {
helpers.generateElement(url, 'sha256', function (data) {
result = data;
done();
});
});

it ("non-CORS", function () {
assert.deepEqual(result, expect);
});
})();

(function (result) {
var url = 'code.jquery.com/jquery-1.11.2.min.js';
var expect = '<script src="https://code.jquery.com/jquery-1.11.2.min.js" integrity="sha256-Ls0pXSlb7AYs7evhd+VLnWsZ/AqEHcXBeMZUycz/CcA=" crossorigin="anonymous"></script>';

before(function (done) {
helpers.generateElement(url, 'sha256', function (data) {
result = data;
done();
});
});

it ("js", function () {
assert.deepEqual(result, expect);
});
})();

(function (result) {
var url = 'https://code.jquery.com/jquery-1.11.2.min.jss';
var expect = 'Error: fetching the resource returned a 404 error code.';

before(function (done) {
helpers.generateElement(url, 'sha256', function (data) {
result = data;
done();
});
});

it ("404", function () {
assert.deepEqual(result, expect);
});
})();

(function (result) {
var url = 'ftp://code.jquery.com/jquery-1.11.2.min.jss';
var expect = 'Error: fetching the resource returned an unexpected error.';

before(function (done) {
helpers.generateElement(url, 'sha256', function (data) {
result = data;
done();
});
});

it ("bad scheme", function () {
assert.deepEqual(result, expect);
});
})();
});

0 comments on commit 04ac49a

Please sign in to comment.