diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README | 11 | ||||
| -rwxr-xr-x | build.sh | 15 | ||||
| -rw-r--r-- | common/Test.js | 55 | ||||
| -rw-r--r-- | html/common.css | 12 | ||||
| -rw-r--r-- | html/display_panel/content/display-panel.html | 2 | ||||
| -rw-r--r-- | html/display_panel/content/main_panel.js | 12 | ||||
| -rw-r--r-- | html/display_panel/content/panel-styles.css | 3 | ||||
| -rw-r--r-- | main_background.js | 15 | ||||
| -rw-r--r-- | test/SpecRunner.html | 43 | ||||
| -rw-r--r-- | test/resources/app-trilicensed.js | 1 | ||||
| -rw-r--r-- | test/resources/index.html | 38 | ||||
| -rw-r--r-- | test/resources/jquery.js | 1 | ||||
| -rw-r--r-- | test/resources/jslicense.html | 25 | ||||
| -rw-r--r-- | test/resources/proprietary.js | 1 | ||||
| -rw-r--r-- | test/resources/tracker.js | 1 | ||||
| -rw-r--r-- | test/spec/LibreJSSpec.js | 208 | 
17 files changed, 442 insertions, 2 deletions
| @@ -6,6 +6,7 @@ SDK LibreJS source/  # dependencies  node_modules/ +test/lib/  # artifacts  librejs.xpi @@ -15,6 +15,11 @@ To build the extension run:  To build the extension plus create a .xpi package run:    $ ./build.sh +To build the extension including the automated test suite (see TEST below) run: +  $ ./build.sh -t +  or +  $ ./build.sh --test +  Note: this build.sh script relies on no new source files being created. @@ -24,6 +29,12 @@ To debug this add-on on IceCat and other Firefox derivatives, browse to the spec  LibreJS should work with other WebExtensions-compliant browsers; but currently, none of them meet the freedom standards of GNU, so no help will be provided for their usage. +TEST: + +An automated test suite runs automatically in its own tab whenever the extension +is loaded as a "temporary add-on" from about:debugging. +Otherwise (if included in the xpi, see BUILD above) it can be launched from the +UI by clicking the [Automated self test...] button.  CONTACT: @@ -1,6 +1,16 @@  PATH=$PATH:./node_modules/.bin  which browserify > /dev/null || (echo "can not find browserify" && false) || exit +# prepare / update testing environment if needed +JASMINE_VER=3.2.1 +JASMINE_LIB="test/lib/jasmine-$JASMINE_VER" +if ! [ -d "$JASMINE_LIB" ]; then +  mkdir -p test/lib +  rm -rf test/lib/jasmine-* +  JASMINE_URL="https://github.com/jasmine/jasmine/releases/download/v$JASMINE_VER/jasmine-standalone-$JASMINE_VER.zip" +  curl -L -o "$JASMINE_LIB.zip" "$JASMINE_URL" && unzip -d test/ $JASMINE_LIB.zip lib/** +  rm -f "$JASMINE_LIB.zip" +fi  # Build the main file  browserify main_background.js -o bundle.js @@ -9,7 +19,10 @@ browserify main_background.js -o bundle.js  mkdir ./build_temp  # Move source files to temp directory -cp -r icons ./build_temp +if [ "$1" == "-t" -o "$1" == "--test" ]; then +  cp -r ./test ./build_temp +fi +cp -r ./icons ./build_temp  cp -r ./html ./build_temp  cp -r ./content ./build_temp  cp -r ./common ./build_temp diff --git a/common/Test.js b/common/Test.js new file mode 100644 index 0000000..e272d1e --- /dev/null +++ b/common/Test.js @@ -0,0 +1,55 @@ +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net> +* +* This file is part of GNU LibreJS. +* +* GNU LibreJS is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* GNU LibreJS is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with GNU LibreJS.  If not, see <http://www.gnu.org/licenses/>. +*/ + +"use strict"; +var Test = (() => { +  const RUNNER_URL = browser.extension.getURL("/test/SpecRunner.html"); +  return { +    /* +      returns RUNNER_URL if it's a test-enabled build or an about:debugging +      temporary extension session, null otherwise +    */ +    async getURL() { +      let url = RUNNER_URL; +      try { +        await fetch(url); +      } catch (e) { +        url = null; +      } +      this.getURL = () => url; +      return url; +    }, + +    async getTab(activate = false) { +      let url = await this.getURL(); +      let tab = url ? (await browser.tabs.query({url}))[0] || +                    (await browser.tabs.create({url})) +        : null; +      if (tab && activate) { +        await browser.tabs.update(tab.id, {active: true}); +      } +      return tab; +    } +  }; +})(); +if (typeof module === "object") { +  module.exports = Test; +} diff --git a/html/common.css b/html/common.css index cf2c5d1..14931fa 100644 --- a/html/common.css +++ b/html/common.css @@ -3,6 +3,7 @@ html {    margin:0px;    color:#000 !important;    background:url('background-panel.png') !important; +  font-family: sans-serif;  }  body {    padding:0; @@ -26,4 +27,15 @@ h1.libre {      font-weight:bold;      background:url('librejs-title.png') no-repeat top left;      text-indent:-1000px; +    position: relative; +} +h1.libre span { +  font-family: sans-serif; +  position: absolute; +  bottom: 32px; +  padding: 0 16px; +  left: 230px; +  display: block; +  text-indent: 0; +  vertical-align: bottom;  } diff --git a/html/display_panel/content/display-panel.html b/html/display_panel/content/display-panel.html index df153b3..80f4cb9 100644 --- a/html/display_panel/content/display-panel.html +++ b/html/display_panel/content/display-panel.html @@ -53,6 +53,7 @@            <button id="complain">Complain to site owner</button>           	<button id="report-tab">Show this report in a new tab</button>            <button id="open-options">Settings...</button> +          <button id="autotest">Automated self test...</button>          </div>      </div>      <div id="info"> @@ -100,5 +101,6 @@          </div>      </div>  </body> +<script src="/common/Test.js"></script>  <script src="main_panel.js"></script>  </html> diff --git a/html/display_panel/content/main_panel.js b/html/display_panel/content/main_panel.js index c55b167..2509545 100644 --- a/html/display_panel/content/main_panel.js +++ b/html/display_panel/content/main_panel.js @@ -38,6 +38,18 @@ myPort.postMessage({"update": true, tabId: parseInt(currentReport && currentRepo  // Display the actual extension version Number  document.querySelector("#version").textContent = browser.runtime.getManifest().version; +// Enable autotest button if this is a test-enabled build / session +(async () => { +  if (await Test.getURL()) { +    let button = document.querySelector("#autotest"); +    button.style.display = "block"; +    button.onclick = async () => { +      await Test.getTab(true); +      close(); +    } +  } +})(); +  var liTemplate = document.querySelector("#li-template");  liTemplate.remove(); diff --git a/html/display_panel/content/panel-styles.css b/html/display_panel/content/panel-styles.css index 0d849e4..940a8ff 100644 --- a/html/display_panel/content/panel-styles.css +++ b/html/display_panel/content/panel-styles.css @@ -141,7 +141,8 @@ span.blocked {    display: none;  } -.tab #must-reload, .tab #buttons, .empty #buttons { +.tab #must-reload, .tab #buttons, +.empty #complain, .empty #report-tab, #autotest {    display: none;  } diff --git a/main_background.js b/main_background.js index cdc987e..5c2e8df 100644 --- a/main_background.js +++ b/main_background.js @@ -1175,6 +1175,21 @@ async function init_addon() {  	ResponseProcessor.install(ResponseHandler);  	legacy_license_lib.init(); + + +	let Test = require("./common/Test"); +	if (Test.getURL()) { +		// export testable functions to the global scope +		this.LibreJS = { +			editHtml, +			handle_script, +			ExternalLicenses, +		}; +		// create or focus the autotest tab if it's a debugging session +		if ((await browser.management.getSelf()).installType === "development") { +			Test.getTab(true); +		} +	}  } diff --git a/test/SpecRunner.html b/test/SpecRunner.html new file mode 100644 index 0000000..42a372f --- /dev/null +++ b/test/SpecRunner.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<!-- +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net> +* +* This file is part of GNU LibreJS. +* +* GNU LibreJS is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* GNU LibreJS is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with GNU LibreJS.  If not, see <http://www.gnu.org/licenses/>. +*/ +--> +<head> +  <meta charset="utf-8"> +  <title>LibreJS Tests</title> + +  <link rel="shortcut icon" type="image/png" href="/icons/librejs.png"> +  <link rel="stylesheet" href="lib/jasmine-3.2.1/jasmine.css"> +  <link rel="stylesheet" href="/html/common.css"> +  <script src="lib/jasmine-3.2.1/jasmine.js"></script> +  <script src="lib/jasmine-3.2.1/jasmine-html.js"></script> +  <script src="lib/jasmine-3.2.1/boot.js"></script> + +  <script src="spec/LibreJSSpec.js"></script> + +</head> + +<body> +  <h1 class="libre">LibreJS <span>Autotest</span></h1> +</body> +</html> diff --git a/test/resources/app-trilicensed.js b/test/resources/app-trilicensed.js new file mode 100644 index 0000000..7b2c2ea --- /dev/null +++ b/test/resources/app-trilicensed.js @@ -0,0 +1 @@ +document.write(`<p>Executing ${document.currentScript.src}</p>`); diff --git a/test/resources/index.html b/test/resources/index.html new file mode 100644 index 0000000..c5f364c --- /dev/null +++ b/test/resources/index.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<!-- +/** +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net> +* +* This file is part of GNU LibreJS. +* +* GNU LibreJS is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* GNU LibreJS is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with GNU LibreJS.  If not, see <http://www.gnu.org/licenses/>. +*/ +--> +<head> +  <meta charset="utf-8"> +  <title>LibreJS test document</title> +  <link rel="shortcut icon" type="image/png" href="/icons/librejs.png"> +</head> +<body> +<h1>LibreJS test document</h1> +<a href="jslicense.html" rel="jslicense">JavaScript license information</a> +<script src="jquery.js"></script> +<script src="app-trilicensed.js"></script> +<script src="proprietary.js"></script> +<script src="tracker.js"></script> +</body> +</html> diff --git a/test/resources/jquery.js b/test/resources/jquery.js new file mode 100644 index 0000000..7b2c2ea --- /dev/null +++ b/test/resources/jquery.js @@ -0,0 +1 @@ +document.write(`<p>Executing ${document.currentScript.src}</p>`); diff --git a/test/resources/jslicense.html b/test/resources/jslicense.html new file mode 100644 index 0000000..0cd0449 --- /dev/null +++ b/test/resources/jslicense.html @@ -0,0 +1,25 @@ +<table id="jslicense-labels1"> +<tr> +<td><a href="jquery.js">Fake jQuery</a></td> +<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a> +<td><a href="jquery-1.7.tar.gz">jquery-1.7.tar.gz</a></td> +</tr> +<tr> +<td><a href="app-trilicensed.js">App specific script, tri-licensed (2 free and 1 proprietary license)</a></td> +<td> +<a href="magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt">GNU V3</a> +<br> +<a href="http://www.jclark.com/xml/copying.txt">Expat</a> +<br> +<a href="https://evil.com/someproprietarylicense">Proprietary, optional</a></td> +</td> +<td><a href="app-trilicensed.js">app-trilicensed.js</a></td> +</tr> +<tr> +<td><a href="proprietary.js">App specific script, proprietary only</a></td> +<td> +<a href="https://evil.com/someproprietarylicense">Proprietary</a></td> +</td> +<td><a href="proprietary.js">proprietary.js</a></td> +</tr> +</table> diff --git a/test/resources/proprietary.js b/test/resources/proprietary.js new file mode 100644 index 0000000..7b2c2ea --- /dev/null +++ b/test/resources/proprietary.js @@ -0,0 +1 @@ +document.write(`<p>Executing ${document.currentScript.src}</p>`); diff --git a/test/resources/tracker.js b/test/resources/tracker.js new file mode 100644 index 0000000..7b2c2ea --- /dev/null +++ b/test/resources/tracker.js @@ -0,0 +1 @@ +document.write(`<p>Executing ${document.currentScript.src}</p>`); diff --git a/test/spec/LibreJSSpec.js b/test/spec/LibreJSSpec.js new file mode 100644 index 0000000..3d61973 --- /dev/null +++ b/test/spec/LibreJSSpec.js @@ -0,0 +1,208 @@ +/* +* GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript. +* +* Copyright (C) 2018 Giorgio Maone <giorgio@maone.net> +* +* This file is part of GNU LibreJS. +* +* GNU LibreJS is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* GNU LibreJS is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with GNU LibreJS.  If not, see <http://www.gnu.org/licenses/>. +*/ +"use strict"; + +describe("LibreJS' components", () => { +  let LibreJS = browser.extension.getBackgroundPage().LibreJS; +  let license = { +    id: 'GPL-3.0', +    url: 'http://www.gnu.org/licenses/gpl-3.0.html', +    magnet: 'magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt', +  }; +  let unknownLicense = { +    id: 'Acme-proprietary-1.5', +    url: 'http://www.acme.com/license-1.5.html', +    magnet: 'magnet:?xt=urn:btih:2f739d935676111cfff4b4693e3816e664797050&dn=acme-1.5.txt' +  }; + +  let trivial = "1+1"; +  let nontrivial = `function nt() { document.documentElement.innerHTML=""; nt(); }`; +  let licensed = `// @license ${license.magnet} ${license.id}\n${nontrivial}\n// @license-end`; +  let unknownLicensed = `// @license ${unknownLicense.magnet} ${unknownLicense.id}\n${nontrivial}\n// @license-end`; +  let malformedLicensed = `// @license\n${nontrivial}`; + +  let tab, documentUrl; + +  beforeAll(async () => { +    let url = browser.extension.getURL("/test/resources/index.html"); +    tab = (await browser.tabs.query({url}))[0] || (await browser.tabs.create({url})); +    documentUrl = url; +  }); + +  describe("The external script source processor", () => { +    let url = "https://www.gnu.org/mock-script.js"; + +    let processScript = async (source, whitelisted = false) => +      await LibreJS.handle_script({ +        text: source, +        request: {url, tabId: tab.id, documentUrl, frameId: 0}, +      }, whitelisted); + +    it("should accept whitelisted scripts", async () => { +      expect(await processScript(nontrivial, true) || nontrivial).toContain(nontrivial); +    }); + +    it("should block trivial scripts too", async () => { +      let processed = await processScript(trivial); +      expect(processed || trivial).not.toContain(trivial); +    }); + +    it("should block non-trivial scripts", async () => { +      let processed = await processScript(nontrivial); +      expect(processed || nontrivial).not.toContain(nontrivial); +    }); + +    it("should accept scripts with known free license tags", async () => { +      let processed = await processScript(licensed); +      expect(processed || licensed).toContain(nontrivial); +    }); + +    it("should block scripts with unknown license tags", async () => { +      let processed = await processScript(unknownLicensed); +      expect(processed).not.toContain(nontrivial); +    }); + +    it("should block scripts with malformed license tags", async () => { +      let processed = await processScript(malformedLicensed); +      expect(processed).not.toContain(nontrivial); +    }); +  }); + +  describe("The HTML processor", () => { +    let processHtml = +      async (html, whitelisted = false) => +        LibreJS.editHtml(html, tab.url, tab.id, 0, whitelisted); + +    let addScript = (html, script, before = "</head>") => +      html.replace(before, `<script>${script}</script>${before}`); + +    function extractScripts(html, def = "") { +      let matches = html && html.match(/<script>[^]*?<\/script>/g); +      return matches && matches.join("") || def; +    } + +    let html, nontrivialInHtml; +    beforeAll(async () => { +      html = (await browser.tabs.executeScript(tab.id, { +        runAt: "document_start", +        code: "document.documentElement.outerHTML" +      }))[0]; +      nontrivialInHtml = addScript(html, nontrivial); +    }); + +    it("should not modify scriptless documents", async () => { +      expect(await processHtml(html)).toBeNull(); +    }); + +    it("should not modify whitelisted documents", async () => { +      expect(await processHtml(nontrivialInHtml, true)).toBeNull(); +    }); + +    it("should accept trivial scripts", async () => { +      let trivialInHtml = addScript(html, trivial); +      let processed = await processHtml(trivialInHtml); +      expect(extractScripts(processed, trivial)).toContain(trivial); +    }); + +    it("should block non-trivial scripts", async () => { +      let processed = await processHtml(nontrivialInHtml); +      expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial); +    }); + +    it("should accept scripts with known free license tags", async () => { +      let licensedInHtml = addScript(html, licensed); +      let processed = await processHtml(licensedInHtml); +      expect(extractScripts(processed, licensed)).toContain(nontrivial); +    }); + +    it("should block scripts with unknown license tags", async () => { +      let unknownInHtml = addScript(html, unknownLicensed); +      let processed = await processHtml(unknownInHtml); +      expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial); +    }); + +    it("should block scripts with malformed license tags", async () => { +      let malformedInHtml = addScript(html, malformedLicensed); +      let processed = await processHtml(malformedInHtml); +      expect(extractScripts(processed, nontrivial)).not.toContain(nontrivial); +    }); + +    it("should accept scripts on globally licensed pages", async () => { +      let globalLicense = `/* @licstart The following is the entire license notice +        for the JavaScript code in this page. +        -- Some free license -- +        @licend The above is the entire license notice for the JavaScript code in this page. */`; + +      let licensed = addScript(nontrivialInHtml, globalLicense, "<script>"); +      let processed = await processHtml(html); +      expect(extractScripts(processed, licensed)).toContain(nontrivial); +    }); + +    it("should discriminate trivial, non-trivial and licensed mixed on the same page", async () => { +      let mixedPage = addScript(addScript(nontrivialInHtml, trivial), licensed); +      let processed = await processHtml(mixedPage); +      expect(processed).not.toBeNull(); +      let scripts = extractScripts(processed, nontrivial); +      expect(scripts).toContain(trivial); +      expect(scripts).toContain(licensed); +      expect(scripts.replace(licensed, "")).not.toContain(nontrivial); +    }); +  }); + +  describe("The external (Web Labels) license checker", () => { +    let {ExternalLicenses} = LibreJS; +    let check; + +    beforeAll(async () => { +      let args = {tabId: tab.id, frameId: 0, documentUrl}; +      let resolve = url => new URL(url, documentUrl).href; +      check = async url => await ExternalLicenses.check(Object.assign({url: resolve(url)}, args)); +      await browser.tabs.executeScript(tab.id, { +        file: "/content/externalLicenseChecker.js" +      }); +    }); + +    it("should recognize free licenses", async () => { +      let scriptInfo = await check("jquery.js"); +      console.debug(scriptInfo); +      expect(scriptInfo.free).toBeTruthy(); +    }); +    it("should accept scripts if any of multiple licenses is free", async () => { +      let scriptInfo = await check("app-trilicensed.js"); +      console.debug(scriptInfo); +      expect(scriptInfo.free).toBeTruthy(); +    }); +    it("should block scripts declaring only proprietary license(s)", async () => { +      let scriptInfo = await check("proprietary.js"); +      console.debug(scriptInfo); +      expect(scriptInfo.free).toBeFalsy(); +    }); +    it("should block scripts not declaring any license", async () => { +      let scriptInfo = await check("tracker.js"); +      console.debug(scriptInfo); +      expect(scriptInfo).toBeNull(); +    }); +  }); +  afterAll(async () => { +    await browser.tabs.remove(tab.id); +    browser.tabs.update((await browser.tabs.getCurrent()).id, {active: true}); +  }); +}); | 
