Skip to content

Commit

Permalink
Merge pull request YahooArchive#43 from SOASTA/restiming-noscript
Browse files Browse the repository at this point in the history
Change optimized trie to avoid string that trigger a XSS warning from NoScript
  • Loading branch information
nicjansma committed Apr 22, 2015
2 parents 7415270 + a61cfd7 commit 4b0c1e1
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 11 deletions.
4 changes: 2 additions & 2 deletions Gruntfile.js
Expand Up @@ -214,7 +214,7 @@ module.exports = function (grunt) {
"tests/vendor/mocha/mocha.css",
"tests/vendor/mocha/mocha.js",
"tests/vendor/node-assert/assert.js",
"tests/vendor/assertive-chai/assertive-chai.js",
"tests/vendor/assertive-chai/dist/assertive-chai.js",
"tests/unit/*.js",
"tests/build/*.js"
]
Expand Down Expand Up @@ -264,7 +264,7 @@ module.exports = function (grunt) {
},
connect: {
options: {
port: 3000,
port: 4002,
hostname: "localhost",
middleware: function(connect, options, middlewares) {
middlewares.push(["/delay", require("./tests/server/route-delay")]);
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Expand Up @@ -26,6 +26,6 @@
"devDependencies": {
"mocha": "~1.21.5",
"lodash": "~3.0.0",
"assertive-chai": "~1.0.0"
"assertive-chai": "~2.0.0"
}
}
45 changes: 40 additions & 5 deletions plugins/restiming.js
Expand Up @@ -7,6 +7,8 @@ see: http://www.w3.org/TR/resource-timing/

(function() {

var impl;

BOOMR = BOOMR || {};
BOOMR.plugins = BOOMR.plugins || {};
if (BOOMR.plugins.ResourceTiming) {
Expand All @@ -22,6 +24,16 @@ var initiatorTypes = {
"xmlhttprequest": 5
};

// Words that will be broken (by ensuring the optimized trie doesn't contain
// the whole string) in URLs, to ensure NoScript doesn't think this is an XSS attack
var defaultXssBreakWords = [
/(h)(ref)/gi,
/(s)(rc)/gi,
/(a)(ction)/gi
];

var xssBreakDelim = "\n";

/**
* Converts entries to a Trie:
* http://en.wikipedia.org/wiki/Trie
Expand All @@ -39,15 +51,24 @@ var initiatorTypes = {
* @return A trie
*/
function convertToTrie(entries) {
var trie = {}, url, i, value, letters, letter, cur, node;
var trie = {}, url, urlFixed, i, value, letters, letter, cur, node;

for(url in entries) {
urlFixed = url;

// find any strings to break
for(i = 0; i < impl.xssBreakWords.length; i++) {
// Add a xssBreakDelim character after the first letter. optimizeTrie will
// ensure this sequence doesn't get combined.
urlFixed = urlFixed.replace(impl.xssBreakWords[i], "$1" + xssBreakDelim + "$2");
}

if(!entries.hasOwnProperty(url)) {
continue;
}

value = entries[url];
letters = url.split("");
letters = urlFixed.split("");
cur = trie;

for(i = 0; i < letters.length; i++) {
Expand Down Expand Up @@ -92,7 +113,18 @@ function optimizeTrie(cur, top) {
if(ret) {
// swap the current leaf with compressed one
delete cur[node];
node = node + ret.name;

if(node === xssBreakDelim) {
// If this node is a newline, which can't be in a regular URL,
// it's due to the XSS patch. Remove the placeholder character,
// and make sure this node isn't compressed by incrementing
// num to be greater than one.
node = ret.name;
num++;
}
else {
node = node + ret.name;
}
cur[node] = ret.value;
}
}
Expand Down Expand Up @@ -377,10 +409,11 @@ function getResourceTiming() {
return optimizeTrie(convertToTrie(results), true);
}

var impl = {
impl = {
complete: false,
initialized: false,
supported: false,
xssBreakWords: defaultXssBreakWords,
done: function() {
var r;
if(this.complete) {
Expand All @@ -406,9 +439,11 @@ var impl = {
};

BOOMR.plugins.ResourceTiming = {
init: function() {
init: function(config) {
var p = BOOMR.window.performance;

BOOMR.utils.pluginConfig(impl, config, "ResourceTiming", ["xssBreakWords"]);

if(impl.initialized) {
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/page-template-snippets/header.tpl
Expand Up @@ -7,7 +7,7 @@
<link rel="stylesheet" href="../../vendor/mocha/mocha.css" />
<script src="../../vendor/mocha/mocha.js"></script>
<script src="../../vendor/node-assert/assert.js"></script>
<script src="../../vendor/assertive-chai/assertive-chai.js"></script>
<script src="../../vendor/assertive-chai/dist/assertive-chai.js"></script>
<script src="../../vendor/lodash/lodash.js"></script></head>
<script src="../../boomerang-test-framework.js" type="text/javascript"></script>
<body>
Expand Down
126 changes: 125 additions & 1 deletion tests/unit/04-plugins-restiming.js
Expand Up @@ -127,6 +127,86 @@ describe("BOOMR.plugins.ResourceTiming", function() {
};
assert.deepEqual(BOOMR.plugins.ResourceTiming.convertToTrie(data), expected);
});

it("should should break 'href' for NoScript", function() {
var data = {"href": "abc"};
var expected = {
"h": {
"\n": {
"r": {
"e": {
"f": "abc"
}
}
}
}
};

assert.deepEqual(BOOMR.plugins.ResourceTiming.convertToTrie(data), expected);
});

it("should should break 'src' for NoScript", function() {
var data = {"src": "abc"};
var expected = {
"s": {
"\n": {
"r": {
"c": "abc"
}
}
}
};

assert.deepEqual(BOOMR.plugins.ResourceTiming.convertToTrie(data), expected);
});

it("should should break 'action' for NoScript", function() {
var data = {"action": "abc"};
var expected = {
"a": {
"\n": {
"c": {
"t": {
"i": {
"o": {
"n": "abc"
}
}
}
}
}
}
};

assert.deepEqual(BOOMR.plugins.ResourceTiming.convertToTrie(data), expected);
});

it("should update XSS words from config.js", function() {
BOOMR.init({
ResourceTiming: {
enabled: true,
xssBreakWords: [
/(h)(ref)/gi,
/(s)(rc)/gi,
/(a)(ction)/gi,
/(m)(oo)/gi
]
}
});

var data = {"moo": "abc"};
var expected = {
"m": {
"\n": {
"o": {
"o": "abc"
}
}
}
};

assert.deepEqual(BOOMR.plugins.ResourceTiming.convertToTrie(data), expected);
});
});

//
Expand Down Expand Up @@ -173,6 +253,50 @@ describe("BOOMR.plugins.ResourceTiming", function() {

assert.deepEqual(BOOMR.plugins.ResourceTiming.optimizeTrie(trie, true), expected);
});

it("should should break 'href' for NoScript", function() {
var data = {"href": "abc" };
var expected = {
"h": {
"ref": "abc"
}
};

var trie = BOOMR.plugins.ResourceTiming.convertToTrie(data);

var optTrie = BOOMR.plugins.ResourceTiming.optimizeTrie(trie, true);
var optTrieJson = JSON.stringify(optTrie);

assert.deepEqual(optTrie, expected);

assert.equal(optTrieJson.indexOf("href"), -1);
});

it("should should break 'href', 'action' and 'src' for NoScript", function() {
var data = {"href": "abc", "123action123": "abc", "_src_abc_action123": "abc" };
var expected = {
"_s": {
"rc_abc_a": {
"ction123": "abc"
}
},
"123a": {
"ction123": "abc"
},
"h": {
"ref": "abc"
}
};

var trie = BOOMR.plugins.ResourceTiming.convertToTrie(data);

var optTrie = BOOMR.plugins.ResourceTiming.optimizeTrie(trie, true);
var optTrieJson = JSON.stringify(optTrie);

assert.deepEqual(optTrie, expected);

assert.equal(optTrieJson.indexOf("href"), -1);
});
});

describe("findPerformanceEntriesForFrame()", function() {
Expand All @@ -198,7 +322,7 @@ describe("BOOMR.plugins.ResourceTiming", function() {
var entriesToFind = [
{ url: "/tests/vendor/mocha/mocha.css", initiatorType: "link" },
{ url: "/tests/vendor/mocha/mocha.js", initiatorType: "script" },
{ url: "/tests/vendor/assertive-chai/assertive-chai.js", initiatorType: "script" }
{ url: "/tests/vendor/assertive-chai/dist/assertive-chai.js", initiatorType: "script" }
];

// we don't know what order these will come in, so grep thru the list
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/index.html
Expand Up @@ -12,7 +12,7 @@
<!-- NOTE: If these change, update 04-plugins-restiming.js -->
<script src="../vendor/mocha/mocha.js"></script>
<script src="../../vendor/node-assert/assert.js"></script>
<script src="../../vendor/assertive-chai/assertive-chai.js"></script>
<script src="../../vendor/assertive-chai/dist/assertive-chai.js"></script>
<script src="../vendor/lodash/lodash.js"></script>
<script>
mocha.setup('bdd');
Expand Down

0 comments on commit 4b0c1e1

Please sign in to comment.