From 75417f5e3d32645859d94cec82255dc130ec4a2e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:55:34 +0200 Subject: Adding upstream version 2020.10.7. Signed-off-by: Daniel Baumann --- src/tests/tests/utils.js | 550 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 550 insertions(+) create mode 100644 src/tests/tests/utils.js (limited to 'src/tests/tests/utils.js') diff --git a/src/tests/tests/utils.js b/src/tests/tests/utils.js new file mode 100644 index 0000000..c884374 --- /dev/null +++ b/src/tests/tests/utils.js @@ -0,0 +1,550 @@ +/* globals badger:false */ + +(function() { + +QUnit.module("Utils"); + +var utils = require('utils'); +var getSurrogateURI = require('surrogates').getSurrogateURI; + +QUnit.test("explodeSubdomains", function (assert) { + var fqdn = "test.what.yea.eff.org"; + var subs = utils.explodeSubdomains(fqdn); + assert.equal(subs.length, 4); + assert.equal(subs[0], fqdn); + assert.equal(subs[3], "eff.org"); +}); + +QUnit.test("xhrRequest", function (assert) { + // set up fake server to simulate XMLHttpRequests + let server = sinon.fakeServer.create({ + respondImmediately: true + }); + server.respondWith("GET", "https://www.eff.org/files/badgertest.txt", + [200, {}, "test passed\n"]); + + let done = assert.async(); + assert.expect(4); + + utils.xhrRequest("https://www.eff.org/files/badgertest.txt", function (err1, resp) { + assert.strictEqual(err1, null, "there was no error"); + assert.equal(resp, "test passed\n", "got expected response text"); + + utils.xhrRequest("https://www.eff.org/nonexistent-page", function(err2/*, resp*/) { + assert.ok(err2, "there was an error"); + assert.equal(err2.status, 404, "error was 404"); + + server.restore(); + done(); + }); + }); +}); + +QUnit.test("isPrivacyBadgerEnabled basic tests", function (assert) { + assert.ok(badger.isPrivacyBadgerEnabled("example.com"), + "Domain starts out as enabled."); + + badger.disablePrivacyBadgerForOrigin("example.com"); + assert.notOk(badger.isPrivacyBadgerEnabled("example.com"), + "Disabling the domain works."); + + badger.enablePrivacyBadgerForOrigin("example.com"); + assert.ok(badger.isPrivacyBadgerEnabled("example.com"), + "Re-enabling the domain works."); +}); + +QUnit.test("isPrivacyBadgerEnabled wildcard tests", function (assert) { + badger.disablePrivacyBadgerForOrigin('*.mail.example.com'); + assert.ok( + badger.isPrivacyBadgerEnabled('www.example.com'), + "Ignores cases without as many subdomains as the wildcard." + ); + assert.ok( + badger.isPrivacyBadgerEnabled('web.stuff.example.com'), + "Ignores cases where subdomains do not match the wildcard." + ); + assert.notOk( + badger.isPrivacyBadgerEnabled('web.mail.example.com'), + "Website matches wildcard pattern." + ); + assert.notOk( + badger.isPrivacyBadgerEnabled('stuff.fakedomain.web.mail.example.com'), + "Wildcard catches all prefacing subdomains." + ); + assert.ok( + badger.isPrivacyBadgerEnabled('mail.example.com'), + "Checks against URLs that lack a starting dot." + ); + + const PSL_TLD = "example.googlecode.com"; + assert.equal(window.getBaseDomain(PSL_TLD), PSL_TLD, + PSL_TLD + " is a PSL TLD"); + badger.disablePrivacyBadgerForOrigin('*.googlecode.com'); + assert.notOk(badger.isPrivacyBadgerEnabled(PSL_TLD), + "PSL TLDs work with wildcards as expected."); +}); + +QUnit.test("disable/enable privacy badger for origin", function (assert) { + function parsed() { + return badger.storage.getStore('settings_map').getItem('disabledSites'); + } + + let origLength = parsed() && parsed().length || 0; + + badger.disablePrivacyBadgerForOrigin('foo.com'); + assert.ok(parsed().length == (origLength + 1), "one more disabled site"); + + badger.enablePrivacyBadgerForOrigin('foo.com'); + assert.ok(parsed().length == origLength, "one less disabled site"); +}); + +QUnit.test("surrogate script URL lookups", function (assert) { + const NOOP = function () {}; + const surrogatedb = require('surrogatedb'); + const SURROGATE_PREFIX = 'data:application/javascript;base64,'; + const GA_JS_TESTS = [ + { + url: 'http://www.google-analytics.com/ga.js', + msg: "Google Analytics ga.js http URL should match" + }, + { + url: 'https://www.google-analytics.com/ga.js', + msg: "Google Analytics ga.js https URL should match" + }, + { + url: 'https://www.google-analytics.com/ga.js?foo=bar', + msg: "Google Analytics ga.js querystring URL should match" + }, + ]; + const NYT_SCRIPT_PATH = '/assets/homepage/20160920-111441/js/foundation/lib/framework.js'; + const NYT_URL = 'https://a1.nyt.com' + NYT_SCRIPT_PATH; + + let ga_js_surrogate; + + for (let i = 0; i < GA_JS_TESTS.length; i++) { + ga_js_surrogate = getSurrogateURI( + GA_JS_TESTS[i].url, + 'www.google-analytics.com' + ); + assert.ok(ga_js_surrogate, GA_JS_TESTS[i].msg); + } + + assert.ok( + ga_js_surrogate.startsWith(SURROGATE_PREFIX), + "The returned ga.js surrogate is a base64-encoded JavaScript data URI" + ); + + // test negative match + assert.notOk( + getSurrogateURI(NYT_URL, window.extractHostFromURL(NYT_URL)), + "New York Times script URL should not match any surrogates" + ); + + // test surrogate suffix token response contents + surrogatedb.hostnames[window.extractHostFromURL(NYT_URL)] = [ + NYT_SCRIPT_PATH + ]; + surrogatedb.surrogates[NYT_SCRIPT_PATH] = NOOP; + assert.equal( + getSurrogateURI(NYT_URL, window.extractHostFromURL(NYT_URL)), + SURROGATE_PREFIX + btoa(NOOP), + "New York Times script URL should now match the noop surrogate" + ); + + // set up test data for wildcard token tests + surrogatedb.hostnames['cdn.example.com'] = 'noop'; + surrogatedb.surrogates.noop = NOOP; + + // test wildcard tokens + for (let i = 0; i < 25; i++) { + let url = 'http://cdn.example.com/' + _.sample( + 'abcdefghijklmnopqrstuvwxyz0123456789'.split(''), + _.random(5, 15) + ).join(''); + + assert.equal( + getSurrogateURI(url, window.extractHostFromURL(url)), + SURROGATE_PREFIX + btoa(NOOP), + "A wildcard token should match all URLs for the hostname: " + url + ); + } +}); + +QUnit.test("rateLimit", (assert) => { + const INTERVAL = 100, + NUM_TESTS = 5; + + let clock = sinon.useFakeTimers(+new Date()); + + let callback = sinon.spy(function (password, i) { + // check args + assert.equal(password, "qwerty", + "rateLimit should preserve args"); + assert.equal(i + 1, callback.callCount, + "rateLimit should preserve args and call order"); + + // check context + assert.ok(this.foo == "bar", "rateLimit should preserve context"); + }); + + let fn = utils.rateLimit(callback, INTERVAL, {foo:"bar"}); + + for (let i = 0; i < NUM_TESTS; i++) { + fn("qwerty", i); + } + + for (let i = 0; i < NUM_TESTS; i++) { + // check rate limiting + assert.equal(callback.callCount, i + 1, + "rateLimit should allow only one call per interval"); + + // advance the clock + clock.tick(INTERVAL); + } + + clock.restore(); +}); + +// the following cookie parsing tests are derived from +// https://github.com/jshttp/cookie/blob/81bd3c77db6a8dcb23567de94b3beaef6c03e97a/test/parse.js +QUnit.test("cookie parsing", function (assert) { + + assert.deepEqual(utils.parseCookie('foo=bar'), { foo: 'bar' }, + "simple cookie"); + + assert.deepEqual( + utils.parseCookie('foo=bar;bar=123'), + { + foo: 'bar', + bar: '123' + }, + "simple cookie with two values" + ); + + assert.deepEqual( + utils.parseCookie('FOO = bar; baz = raz'), + { + FOO: 'bar', + baz: 'raz' + }, + "ignore spaces" + ); + + assert.deepEqual( + utils.parseCookie('foo="bar=123456789&name=Magic+Mouse"'), + { foo: 'bar=123456789&name=Magic+Mouse' }, + "escaped value" + ); + + assert.deepEqual( + utils.parseCookie('email=%20%22%2c%3b%2f'), + { email: ' ",;/' }, + "encoded value" + ); + + assert.deepEqual( + utils.parseCookie('foo=%1;bar=bar'), + { + foo: '%1', + bar: 'bar' + }, + "ignore escaping error and return original value" + ); + + assert.deepEqual( + utils.parseCookie('foo=%1;bar=bar;HttpOnly;Secure', { skipNonValues: true }), + { + foo: '%1', + bar: 'bar' + }, + "ignore non values" + ); + + assert.deepEqual( + utils.parseCookie('priority=true; expires=Wed, 29 Jan 2014 17:43:25 GMT; Path=/'), + { + priority: 'true', + expires: 'Wed, 29 Jan 2014 17:43:25 GMT', + Path: '/' + }, + "dates" + ); + + assert.deepEqual( + utils.parseCookie('foo=%1;bar=bar;foo=boo', { noOverwrite: true}), + { + foo: '%1', + bar: 'bar' + }, + "duplicate names #1" + ); + + assert.deepEqual( + utils.parseCookie('foo=false;bar=bar;foo=true', { noOverwrite: true}), + { + foo: 'false', + bar: 'bar' + }, + "duplicate names #2" + ); + + assert.deepEqual( + utils.parseCookie('foo=;bar=bar;foo=boo', { noOverwrite: true}), + { + foo: '', + bar: 'bar' + }, + "duplicate names #3" + ); + + // SameSite attribute + let SAMESITE_COOKIE = 'abc=123; path=/; domain=.githack.com; HttpOnly; SameSite=Lax'; + assert.deepEqual( + utils.parseCookie(SAMESITE_COOKIE), + { + abc: '123', + SameSite: 'Lax', + path: '/', + domain: '.githack.com', + HttpOnly: '', + }, + "SameSite is parsed" + ); + assert.deepEqual( + utils.parseCookie(SAMESITE_COOKIE, { skipAttributes: true }), + { abc: '123' }, + "SameSite is ignored when ignoring attributes" + ); + +}); + +QUnit.test("cookie parsing (legacy Firefox add-on)", function (assert) { + // raw cookies (input) + let optimizelyCookie = 'optimizelyEndUserId=oeu1394241144653r0.538161732205'+ + '5392; optimizelySegments=%7B%22237061344%22%3A%22none%22%2C%22237321400%'+ + '22%3A%22ff%22%2C%22237335298%22%3A%22search%22%2C%22237485170%22%3A%22fa'+ + 'lse%22%7D; optimizelyBuckets=%7B%7D'; + let googleCookie = 'PREF=ID=d93d4e842d10e12a:U=3838eaea5cd40d37:FF=0:TM=139'+ + '4232126:LM=1394235924:S=rKP367ac3aAdDzAS; NID=67=VwhHOGQunRmNsm9WwJyK571'+ + 'OGqb3RtvUmH987K5DXFgKFAxFwafA_5VPF5_bsjhrCoM0BjyQdxyL2b-qs9b-fmYCQ_1Uqjt'+ + 'qTeidAJBnc2ecjewJia6saHrcJ6yOVVgv'; + let hackpadCookie = 'acctIds=%5B%22mIqZhIPMu7j%22%2C%221394477194%22%2C%22u'+ + 'T/ayZECO0g/+hHtQnjrdEZivWA%3D%22%5D; expires=Wed, 01-Jan-3000 08:00:01 G'+ + 'MT; domain=.hackpad.com; path=/; secure; httponly\nacctIds=%5B%22mIqZhIP'+ + 'Mu7j%22%2C%221394477194%22%2C%22uT/ayZECO0g/+hHtQnjrdEZivWA%3D%22%5D; ex'+ + 'pires=Wed, 01-Jan-3000 08:00:00 GMT; domain=.hackpad.com; path=/; secure'+ + '; httponly\n1ASIE=T; expires=Wed, 01-Jan-3000 08:00:00 GMT; domain=hackp'+ + 'ad.com; path=/\nPUAS3=3186efa7f8bca99c; expires=Wed, 01-Jan-3000 08:00:0'+ + '0 GMT; path=/; secure; httponly'; + let emptyCookie = ''; + let testCookie = ' notacookiestring; abc=123 '; + + // parsed cookies (expected output) + let COOKIES = {}; + COOKIES[optimizelyCookie] = { + optimizelyEndUserId: 'oeu1394241144653r0.5381617322055392', + optimizelySegments: '%7B%22237061344%22%3A%22none%22%2C%22237321400%2' + + '2%3A%22ff%22%2C%22237335298%22%3A%22search%22%2C%22237485170%22%3A%2' + + '2false%22%7D', + optimizelyBuckets: '%7B%7D' + }; + COOKIES[emptyCookie] = {}; + COOKIES[testCookie] = {abc: '123'}; + COOKIES[googleCookie] = { + PREF: 'ID=d93d4e842d10e12a:U=3838eaea5cd40d37:FF=0:TM=1394232126:LM=1'+ + '394235924:S=rKP367ac3aAdDzAS', + NID: '67=VwhHOGQunRmNsm9WwJyK571OGqb3RtvUmH987K5DXFgKFAxFwafA_5VPF5_b'+ + 'sjhrCoM0BjyQdxyL2b-qs9b-fmYCQ_1UqjtqTeidAJBnc2ecjewJia6saHrcJ6yOVVgv' + }; + COOKIES[hackpadCookie] = { + acctIds: '%5B%22mIqZhIPMu7j%22%2C%221394477194%22%2C%22uT/ayZECO0g/+h'+ + 'HtQnjrdEZivWA%3D%22%5D', + PUAS3: '3186efa7f8bca99c', + '1ASIE': 'T' + }; + + // compare actual to expected + let test_number = 0; + for (let cookieString in COOKIES) { + if (COOKIES.hasOwnProperty(cookieString)) { + test_number++; + + let expected = COOKIES[cookieString]; + + let actual = utils.parseCookie( + cookieString, { + noDecode: true, + skipAttributes: true, + skipNonValues: true + } + ); + + assert.deepEqual(actual, expected, "cookie test #" + test_number); + } + } +}); + +// the following cookie parsing tests are derived from +// https://github.com/yui/yui3/blob/25264e3629b1c07fb779d203c4a25c0879ec862c/src/cookie/tests/cookie-tests.js +QUnit.test("cookie parsing (YUI3)", function (assert) { + + let cookieString = "a=b"; + let cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty("a"), "Cookie 'a' is present."); + assert.equal(cookies.a, "b", "Cookie 'a' should have value 'b'."); + + cookieString = "12345=b"; + cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty("12345"), "Cookie '12345' is present."); + assert.equal(cookies["12345"], "b", "Cookie '12345' should have value 'b'."); + + cookieString = "a=b; c=d; e=f; g=h"; + cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty("a"), "Cookie 'a' is present."); + assert.ok(cookies.hasOwnProperty("c"), "Cookie 'c' is present."); + assert.ok(cookies.hasOwnProperty("e"), "Cookie 'e' is present."); + assert.ok(cookies.hasOwnProperty("g"), "Cookie 'g' is present."); + assert.equal(cookies.a, "b", "Cookie 'a' should have value 'b'."); + assert.equal(cookies.c, "d", "Cookie 'c' should have value 'd'."); + assert.equal(cookies.e, "f", "Cookie 'e' should have value 'f'."); + assert.equal(cookies.g, "h", "Cookie 'g' should have value 'h'."); + + cookieString = "name=Nicholas%20Zakas; title=front%20end%20engineer"; + cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty("name"), "Cookie 'name' is present."); + assert.ok(cookies.hasOwnProperty("title"), "Cookie 'title' is present."); + assert.equal(cookies.name, "Nicholas Zakas", "Cookie 'name' should have value 'Nicholas Zakas'."); + assert.equal(cookies.title, "front end engineer", "Cookie 'title' should have value 'front end engineer'."); + + cookieString = "B=2nk0a3t3lj7cr&b=3&s=13; LYC=l_v=2&l_lv=10&l_l=94ddoa70d&l_s=qz54t4qwrsqquyv51w0z4xxwtx31x1t0&l_lid=146p1u6&l_r=4q&l_lc=0_0_0_0_0&l_mpr=50_0_0&l_um=0_0_1_0_0;YMRAD=1215072198*0_0_7318647_1_0_40123839_1; l%5FPD3=840"; + cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty("B"), "Cookie 'B' is present."); + assert.ok(cookies.hasOwnProperty("LYC"), "Cookie 'LYC' is present."); + assert.ok(cookies.hasOwnProperty("l_PD3"), "Cookie 'l_PD3' is present."); + + let cookieName = "something[1]"; + let cookieValue = "123"; + cookieString = encodeURIComponent(cookieName) + "=" + encodeURIComponent(cookieValue); + cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty(cookieName), "Cookie '" + cookieName + "' is present."); + assert.equal(cookies[cookieName], cookieValue, "Cookie value for '" + cookieName + "' is " + cookieValue + "."); + + cookieString = "SESSION=27bedbdf3d35252d0db07f34d81dcca6; STATS=OK; SCREEN=1280x1024; undefined; ys-bottom-preview=o%3Aheight%3Dn%253A389"; + cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty("SCREEN"), "Cookie 'SCREEN' is present."); + assert.ok(cookies.hasOwnProperty("STATS"), "Cookie 'STATS' is present."); + assert.ok(cookies.hasOwnProperty("SESSION"), "Cookie 'SESSION' is present."); + assert.ok(cookies.hasOwnProperty("ys-bottom-preview"), "Cookie 'ys-bottom-preview' is present."); + assert.ok(cookies.hasOwnProperty("undefined"), "Cookie 'undefined' is present."); + + // Tests that cookie parsing deals with cookies that contain an invalid + // encoding. It shouldn't error, but should treat the cookie as if it + // doesn't exist (return null). + cookieString = "DetailInfoList=CPN03022194=@|@=CPN03#|#%B4%EB%C3%B5%C7%D8%BC%F6%BF%E5%C0%E5#|#1016026000#|#%BD%C5%C8%E6%B5%BF#|##|#"; + cookies = utils.parseCookie(cookieString, { skipInvalid: true }); + assert.equal(cookies.DetailInfoList, null, "Cookie 'DetailInfoList' should not have a value."); + + // Tests that a Boolean cookie, one without an equals sign of value, + // is represented as an empty string. + cookieString = "info"; + cookies = utils.parseCookie(cookieString); + assert.equal(cookies.info, "", "Cookie 'info' should be an empty string."); + + cookieString = "name=Nicholas%20Zakas; hash=a=b&c=d&e=f&g=h; title=front%20end%20engineer"; + cookies = utils.parseCookie(cookieString); + assert.ok(cookies.hasOwnProperty("name"), "Cookie 'name' is present."); + assert.ok(cookies.hasOwnProperty("hash"), "Cookie 'hash' is present."); + assert.ok(cookies.hasOwnProperty("title"), "Cookie 'title' is present."); + assert.equal(cookies.name, "Nicholas Zakas", "Cookie 'name' should have value 'Nicholas Zakas'."); + assert.equal(cookies.hash, "a=b&c=d&e=f&g=h", "Cookie 'hash' should have value 'a=b&c=d&e=f&g=h'."); + assert.equal(cookies.title, "front end engineer", "Cookie 'title' should have value 'front end engineer'."); + +}); + +QUnit.test("getHostFromDomainInput", assert => { + assert.equal( + utils.getHostFromDomainInput("www.spiegel.de"), + "www.spiegel.de", + "Valid domains are accepted" + ); + + assert.equal( + utils.getHostFromDomainInput("http://www.spiegel.de/"), + "www.spiegel.de", + "URLs get transformed into domains" + ); + + assert.equal( + utils.getHostFromDomainInput("http://www.spiegel.de"), + "www.spiegel.de", + "Trailing slashes are not required" + ); + + assert.equal( + utils.getHostFromDomainInput("@"), + false, + "Valid URIs with empty hosts are rejected." + ); +}); + +// used in pixel tracking heuristic, given a string the estimateMaxEntropy function +// will return the estimated entropy value from it, based on logic parsing the string's length, +// and classes of character complication included in the string +QUnit.test("estimateMaxEntropy", assert => { + assert.equal( + utils.estimateMaxEntropy("google.com/analytics.google/analytics.google/google.com/analytics.google/analytics.google/google.com/analytics.google/analytics.google/google.com/analytics.google/analytics.google/google.com/analytics.google/analytics.google/google.com/analytics.google/anal"), + 257, + "returns length of string if it's above 256 (MAX_LS_LEN_FOR_ENTROPY_EST)" + ); + + assert.equal( + utils.estimateMaxEntropy("googlecomanalytics"), + utils.estimateMaxEntropy("GOOGLECOMANALYTICS"), + "if the same string is all lower case or all upper case, the returned estimated entropy value is the same" + ); + + assert.notEqual( + utils.estimateMaxEntropy('analytics.GOOGLE1234_'), + utils.estimateMaxEntropy('ANALYTICS.google1234'), + "two nearly identical strings of mixed character classes and different cases will return different values" + ); + + assert.notEqual( + utils.estimateMaxEntropy('google.com/analytics'), + utils.estimateMaxEntropy('0191/_-goo~le9x+xzxo'), + "strings of the same length but from different character classes will estimate different entropy values" + ); + + assert.equal( + utils.estimateMaxEntropy("google.com/0191/_-google/analytics.fizz?buzz=foobar"), + 320.55551316197466, + "entropy for complex string of varying character classes is correctly estimated" + ); + + assert.equal( + utils.estimateMaxEntropy("03899029.01_293"), + 49.82892142331044, + "entropy for string from the common classes of characters is correctly estimated" + ); + + assert.equal( + utils.estimateMaxEntropy("fizzBUZZ012345"), + 84, + "entropy for string from the case-insensitive class of characters is correctly estimated" + ); + + assert.equal( + utils.estimateMaxEntropy("fizz/buzz+fizzy~buzzy%"), + 142.82076811925285, + "entropy for string from the case-sensitive class of characters is correctly estimated" + ); + + assert.equal( + utils.estimateMaxEntropy("1280x720") < 32, + true, + "resolution strings with 'x' char from SEPS class are correctly estimated as low entropy" + ); + +}); + +})(); -- cgit v1.2.3