diff --git a/README.md b/README.md index f709c92..e2f1695 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,32 @@ Backloader ========== -A Chrome extension to redirect HTTP requests to other URLs +A browser extension to redirect HTTP requests to other URLs +**Firefox WebExtension Compatible!** Some CDNs are extremely fast in one place and extremely slow or outright blocked in another. When a webpage makes a request for a file from a CDN (Content Delivery Network), Backloader can redirect the request to another, hopefully faster, CDN. This decreases page load time dramatically. In some cases, Backloader will outright block requests for files that aren't needed and are slow in loading (for example, fonts, or scripts that simply allow for compatibility with another browser). **Of course, this can also cause problems, so if you ever experience problems, you should disable Backloader and try again.** +Browser Compatibility +============ +| Tested Browsers | Compatibility | Notes | +|------------------------------|---------------|--------------------| +| Google Chrome | 100% | +| Chromium | 100% | +| Firefox Nightly Builds (44) | 90% | Requires work-arounds in-code. Popup doesn't auto-update with the latest information. Tabs appear to be loading infinitely when navigating to a blocked url. +| Opera | 100% | +| Vivaldi (1.0.252.3-snapshot+)| 100% | +| Maxathon | 99% | Requires unsupported manual installation method. + Installation ============ 1. Download the latest release from https://github.com/Nateowami/Backloader/releases/latest. -2. Open chrome://extensions/ +2. Open chrome://extensions/ or equivalent, according to your browser. 3. Open your downloads folder, or wherever you saved Backloader when you downloaded it. -4. Drag the file (Backloader.crx) from its folder to Chrome on the extensions page. -5. Chrome will ask you to confirm that you want to install the file. Click "Add." +4. Drag the file (Backloader.crx) from its folder to your browser's extensions page. +5. The browser will ask you to confirm that you want to install the file. Click "Add." -That's it! If you experience any issues try disabling Backloader by clicking on the extension's icon and deselecting "Enable redirects and blocking." +That's it! If you experience any issues try disabling Backloader by clicking on the extension's icon and clicking "Disable." Future Plans ============ diff --git a/build/Backloader.pem b/build/Backloader.pem new file mode 100644 index 0000000..51ffc22 --- /dev/null +++ b/build/Backloader.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAysK+NQ2HVUhOb/6FEotVcsqAI6K1Wvfd9xjcD9GWqonqzYwV +ioaTiHiH8d5a0twhIGmswFfmUvFEwB+/Tctw5pk1jteM8r7HyQBUcsC6J77CFMp5 +wbjKPkvxkDMVmYBJunXIBVQheW2aUTkfyRoSdY9wPM+MAFhsVvuENwR2QdartIYV +rrOi9yEdCEEBNAnSQbdNnrZvtlyFWJT8ytItE5m5Arbq8a8Mr8MGeJryKQkfxGGO +4oBu4EDABAZwDmh5jmmnp2iwOZV5wXc2ugX7MwVMr5CwNn9jkMX2INe0F024kM7A +mHFW+CcMSMHAyTdk3ZQBVI4AD44k8JTNKpUQBwIDAQABAoIBAG6FZn6uqCUha7f6 +SXkJQAN5RA2opCg40eHdyB5LRVAiKz3tbNR8dLC/2oJJjMJBQUFUuNiDUUXGw43i +ADoceacIvjvlXKv4uBflOSLv0MjXXx1MHMAuxPbeGIdcgRqR5h5gEy5sZG63ApXh +iGTq9ELmc+yByTNq/2sOYExI/1j0/RePAzJXmWB9Cgc4XyKLV/ccr1XzN7CCsjRN +uaPH+T5UQXxqhaTMbcyc1Fy5NS6bBehAR1Iz7sCkPB9eDv1r3k53ar+okb0TKvNB +vRbFnZhn/K8VTuk3fJqe+xrzH2r+O+XjFCjUC2bI6cSa0F3Yu/HhwfSwZ8vuCcws +9126ukECgYEA8ITp1FhBlRjymQGUzjKxDvKTf9jbqKJPdwVxPJ3KrOQenX8lrXdO +uKYImhRJ/VlB2TCrtOOOdIrluXwjQuVtw4QfTT7EyfsEiy0mJ5idsGSu5FOy3Brz +xhwOzILcqY4DDMMYkkjWIrxeUQMvkAFEEIdrPqb5Y7VMuYW+oOEPNicCgYEA18+s +zclX72kjtmVSVs0hRzU41zh5jkJSGtZIVbXfI+V1BHoOw4cTCTUsNZZrKCM+BiLX +L1IsGLMrR0m0XSf795ZmWaQ0Lo3u3B210j/hYoiG4v6Rs5VY6G+F3i2w5YASOwGC +bQfZYHUrUE35D/KeHfY4Nkm5Lw7CoQp1mpeiYyECgYBOJCCD6JiGBDDtHOJni3iS +zqS1KJNU2kbO+Gmts+Yg/YwkxyZKkTR2MRsiU+0l7lXCZlmizzt9CZNRC8+48CkV +GFvDEN2GcZMsVGLNAnxYzp0zeS3m7Kq+dmcocmuWepjrEIdvH1LSvjg2Qb62HnMq +m5RwKndjuYdPzu/7/KKgiQKBgQCle0RJQ8Oe++L7gHQDxSVoYcIlHdPvpqEonPk4 +Z84jy1XQSfu6KIALRm+Q47dlPqjXsa2JRpwzkGd2MWkRKZDGx9fRktzEeecXd9ys +zHj5wjCULJnyt2lGSgW/WQ/U20CfaEfnbOZdVVsSJ8f8V5Cw7tUpo5xRHiNVsxx8 +nxtQoQKBgQCu2XDfpfpaErJxC2iHCNcq6Tvabb9xpixmsAJW5a9kd1GTrXJEHPqj +sIkxf7scyWR7QVJjezmzsRajxWLPRy/gQlYuqa7QhmX6w6IjBK8xkwSU8Wp0BrFU +rvgAP1xX7U4Do6b30nDqBlvk2NIvcQJvC5wNe80ifVLNX54XBq/uzw== +-----END RSA PRIVATE KEY----- diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..6f5e25e --- /dev/null +++ b/build/README.md @@ -0,0 +1,5 @@ +### Build Instructions +To build the development versions of the extensions, run `./build-dist.sh` in this directory. + +### Information +The private key provided is for development builds only and will not be used in published versions of this extension. diff --git a/build/build-dist.sh b/build/build-dist.sh new file mode 100755 index 0000000..6976aa4 --- /dev/null +++ b/build/build-dist.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Quick'n'dirty bash script for building installable Backloader extension distributions. +# Usage: ./build-dist.sh + +rm -rf ../dist +mkdir ../dist + +# Build chrome/opera/vivaldi extension +echo "Building signed CRX distributable file." +./crxmake.sh ../src ./Backloader.pem ../dist/Backloader.crx +echo "Built signed CRX distributable file." + +echo "" + +# Build firefox .xpi +echo "Building unsigned XPI (Mozilla) distributable file." +cd ../src; +zip -r ../dist/Backloader.xpi * +echo "Built unsigned XPI (Mozilla) distributable file." + +echo "" + +echo "Build complete." diff --git a/build/crxmake.sh b/build/crxmake.sh new file mode 100755 index 0000000..4595207 --- /dev/null +++ b/build/crxmake.sh @@ -0,0 +1,42 @@ +#!/bin/bash -e +# +# Purpose: Pack a Chromium extension directory into crx format + +if test $# -ne 3; then + echo "Usage: crxmake.sh " + exit 1 +fi + +dir=$1 +key=$2 +name=$(basename "$dir") +crx=$3 +pub="$name.pub" +sig="$name.sig" +zip="$name.zip" +trap 'rm -f "$pub" "$sig" "$zip"' EXIT + +# zip up the crx dir +cwd=$(pwd -P) +(cd "$dir" && zip -qr -9 -X "$cwd/$zip" .) + +# signature +openssl sha1 -sha1 -binary -sign "$key" < "$zip" > "$sig" + +# public key +openssl rsa -pubout -outform DER < "$key" > "$pub" 2>/dev/null + +byte_swap () { + # Take "abcdefgh" and return it as "ghefcdab" + echo "${1:6:2}${1:4:2}${1:2:2}${1:0:2}" +} + +crmagic_hex="4372 3234" # Cr24 +version_hex="0200 0000" # 2 +pub_len_hex=$(byte_swap $(printf '%08xn' $(ls -l "$pub" | awk '{print $5}'))) +sig_len_hex=$(byte_swap $(printf '%08xn' $(ls -l "$sig" | awk '{print $5}'))) +( + echo "$crmagic_hex $version_hex $pub_len_hex $sig_len_hex" | xxd -r -p + cat "$pub" "$sig" "$zip" +) > "$crx" +echo "Wrote $crx" diff --git a/dist/Backloader.crx b/dist/Backloader.crx new file mode 100644 index 0000000..e376b9e Binary files /dev/null and b/dist/Backloader.crx differ diff --git a/dist/Backloader.xpi b/dist/Backloader.xpi new file mode 100644 index 0000000..f492ab3 Binary files /dev/null and b/dist/Backloader.xpi differ diff --git a/img/icon.png b/src/img/icon.png similarity index 100% rename from img/icon.png rename to src/img/icon.png diff --git a/img/icon.svg b/src/img/icon.svg similarity index 100% rename from img/icon.svg rename to src/img/icon.svg diff --git a/lib/array-move.js b/src/lib/array-move.js similarity index 100% rename from lib/array-move.js rename to src/lib/array-move.js diff --git a/lib/assets/FontAwesome.otf b/src/lib/assets/FontAwesome.otf similarity index 100% rename from lib/assets/FontAwesome.otf rename to src/lib/assets/FontAwesome.otf diff --git a/lib/assets/fontawesome-webfont.eot b/src/lib/assets/fontawesome-webfont.eot similarity index 100% rename from lib/assets/fontawesome-webfont.eot rename to src/lib/assets/fontawesome-webfont.eot diff --git a/lib/assets/fontawesome-webfont.svg b/src/lib/assets/fontawesome-webfont.svg similarity index 100% rename from lib/assets/fontawesome-webfont.svg rename to src/lib/assets/fontawesome-webfont.svg diff --git a/lib/assets/fontawesome-webfont.ttf b/src/lib/assets/fontawesome-webfont.ttf similarity index 100% rename from lib/assets/fontawesome-webfont.ttf rename to src/lib/assets/fontawesome-webfont.ttf diff --git a/lib/assets/fontawesome-webfont.woff b/src/lib/assets/fontawesome-webfont.woff similarity index 100% rename from lib/assets/fontawesome-webfont.woff rename to src/lib/assets/fontawesome-webfont.woff diff --git a/lib/assets/fontawesome-webfont.woff2 b/src/lib/assets/fontawesome-webfont.woff2 similarity index 100% rename from lib/assets/fontawesome-webfont.woff2 rename to src/lib/assets/fontawesome-webfont.woff2 diff --git a/lib/jquery.min.js b/src/lib/jquery.min.js similarity index 100% rename from lib/jquery.min.js rename to src/lib/jquery.min.js diff --git a/lib/photonui-base.css b/src/lib/photonui-base.css similarity index 100% rename from lib/photonui-base.css rename to src/lib/photonui-base.css diff --git a/lib/photonui-theme-particle.css b/src/lib/photonui-theme-particle.css similarity index 100% rename from lib/photonui-theme-particle.css rename to src/lib/photonui-theme-particle.css diff --git a/lib/photonui.min.js b/src/lib/photonui.min.js similarity index 100% rename from lib/photonui.min.js rename to src/lib/photonui.min.js diff --git a/manifest.json b/src/manifest.json similarity index 80% rename from manifest.json rename to src/manifest.json index 80e3d72..3154a3d 100644 --- a/manifest.json +++ b/src/manifest.json @@ -3,10 +3,11 @@ "name": "Backloader", "version": "0.2.0", - "description": "A Chrome extension to redirect HTTP requests to other URLs", + "description": "A browser extension to redirect HTTP requests to other URLs", "permissions": [ "storage", + "unlimitedStorage", "tabs", "webRequest", "webRequestBlocking", @@ -24,6 +25,12 @@ "default_title": "Backloader" }, + "applications": { + "gecko": { + "id": "backloader@nateowami.github.io" + } + }, + "content_scripts": [ { "matches": [ diff --git a/pages/options/options.css b/src/pages/options/options.css similarity index 100% rename from pages/options/options.css rename to src/pages/options/options.css diff --git a/pages/options/options.html b/src/pages/options/options.html similarity index 100% rename from pages/options/options.html rename to src/pages/options/options.html diff --git a/pages/options/options.js b/src/pages/options/options.js similarity index 99% rename from pages/options/options.js rename to src/pages/options/options.js index 34b7f36..2eefff8 100644 --- a/pages/options/options.js +++ b/src/pages/options/options.js @@ -1,3 +1,5 @@ +var browser = browser || chrome; + var p = photonui; var options = { @@ -10,7 +12,8 @@ var ruleSelection = null; $(document).ready(function() { prepareLayout(); - chrome.storage.local.get('filter-list', init); + // Arg 1: ['filter-list'] regressed to null for Firefox support. + browser.storage.local.get(null, init); }); function init(storageResults) { @@ -556,7 +559,7 @@ function addEditList(isNew) { }); // Apply logic - p.getWidget("list-edit-apply").registerCallback("apply", "click", function() { + p.getWidget("list-edit-apply").registerCallback("apply", "click", function() { var filterObject = { name: nameField.value, isEnabled: true, @@ -681,7 +684,7 @@ function addEditList(isNew) { } function saveList() { - chrome.storage.local.set({ + browser.storage.local.set({ "filter-list": filterList, }, function() { }); diff --git a/pages/popup/popup.css b/src/pages/popup/popup.css similarity index 100% rename from pages/popup/popup.css rename to src/pages/popup/popup.css diff --git a/pages/popup/popup.html b/src/pages/popup/popup.html similarity index 100% rename from pages/popup/popup.html rename to src/pages/popup/popup.html diff --git a/pages/popup/popup.js b/src/pages/popup/popup.js similarity index 79% rename from pages/popup/popup.js rename to src/pages/popup/popup.js index 65a1b07..981484a 100644 --- a/pages/popup/popup.js +++ b/src/pages/popup/popup.js @@ -1,6 +1,9 @@ +var browser = browser || chrome; + var p = photonui; $(document).ready(function() { - chrome.storage.local.get(["enabled", "redirected", "blocked"], initPopup); + // Arg 1: ['enabled', 'redirected', 'blocked'] regressed to null for Firefox support. + browser.storage.local.get(null, initPopup); }); @@ -22,7 +25,7 @@ function initPopup(items) { enableButton.registerCallback("toggled", "click", function(widget) { widget.text = widget.value ? "Disable" : "Enable"; - chrome.storage.local.set({enabled: widget.value}); + browser.storage.local.set({enabled: widget.value}); }); var optionsButton = new p.Button({ @@ -30,7 +33,7 @@ function initPopup(items) { leftIcon: new p.FAIcon("fa-cog"), }); optionsButton.registerCallback("open-options", "click", function(widget) { - chrome.tabs.create({url: './pages/options/options.html'}); + browser.tabs.create({url: './pages/options/options.html'}); }); controlBox.addChild(enableButton); diff --git a/scripts/background/background.js b/src/scripts/background/background.js similarity index 64% rename from scripts/background/background.js rename to src/scripts/background/background.js index 5f8a568..35e6a4d 100644 --- a/scripts/background/background.js +++ b/src/scripts/background/background.js @@ -1,3 +1,6 @@ +var browser = browser || chrome; + +console.log('Starting BackLoader background page.'); var filterList = []; var splitTabPage = { @@ -5,78 +8,87 @@ var splitTabPage = { path: null, }; -// Set "enabled" to true on first run -chrome.runtime.onInstalled.addListener(function(details){ - if(details.reason === "install"){ - chrome.storage.local.set({ - "enabled": true, - "redirected": 0, - "blocked": 0, - "filter-list": defaultFilterList, - }); - } -}); +// Set 'enabled' to true on first run +function prepareExtension(callback) { + // Arg 1: ['installed'] regressed to null for Firefox support. + browser.storage.local.get(null, function(storageResults) { + if(!storageResults['installed']) { + var extData = { + 'installed': true, + 'enabled': true, + 'redirected': 0, + 'blocked': 0, + 'filter-list': defaultFilterList, + }; + browser.storage.local.set(extData, callback); + } else { + callback ? callback() : null; + } + }); +} // Listen for updates to local storage -chrome.storage.onChanged.addListener(updateEnabled); +browser.storage.onChanged.addListener(updateEnabled); // Update backloader.enabled when it get's changed in local storage function updateEnabled(changes, namespace) { - for (key in changes) { + for (var key in changes) { var change = changes[key]; - // Only do something if it's "enabled" that gets updated - if(namespace == "local") { - if(key == "enabled") { + // Only do something if it's 'enabled' that gets updated + if(namespace == 'local') { + if(key == 'enabled') { backloader.enabled = change.newValue; - console.log("Updated local-storage-enable-status from " + change.oldValue + " to " + change.newValue); - - } else if(key == "filter-list") { + console.log('Updated local-storage-enable-status from ' + change.oldValue + ' to ' + change.newValue); + + } else if(key == 'filter-list') { loadFilterList(function(list) { filterList = list; - }); + }) } } } } var backloader = { - "redirected": 0, - "blocked": 0, - "enabled": true, - + 'redirected': 0, + 'blocked': 0, + 'enabled': true, + /** - * Increments the value indicated by key (may be "redirected" - * or "blocked"). + * Increments the value indicated by key (may be 'redirected' + * or 'blocked'). * @param {String} key - The variable to increment. */ increment: function(key){ var obj = {}; obj[key] = ++this[key]; - chrome.storage.local.set(obj); - console.log("Updated " + key + " to " + this[key] + "."); + browser.storage.local.set(obj); + console.log('Updated ' + key + ' to ' + this[key] + '.'); } }; -// Initialize after loading the filter list -loadFilterList(function(list) { - filterList = list; - init(); -}); +prepareExtension(function() { + // Initialize after loading the filter list + loadFilterList(function(list) { + filterList = list; + init(); + }); -loadEnabled(function(enabled) { - backloader.enabled = enabled; + loadEnabled(function(enabled) { + backloader.enabled = enabled; + }); }); function init() { // Update the current tab URL every time the tab changes. - chrome.tabs.onActivated.addListener(function(data) { - chrome.tabs.get(data.tabId, function(ret) { + browser.tabs.onActivated.addListener(function(data) { + browser.tabs.get(data.tabId, function(ret) { splitTabPage = splitByProtocol(ret.url); }); }); // Register an event listener for all web requests. - chrome.webRequest.onBeforeRequest.addListener(manageRequest, {urls: [""]}, ["blocking"]); + browser.webRequest.onBeforeRequest.addListener(manageRequest, {urls: ['']}, ['blocking']); } /** @@ -87,13 +99,13 @@ function init() { function manageRequest(request) { // Only run if filtering is enabled if(!backloader.enabled) return; - + for(var r = 0; r < filterList.length; r++) { if(!filterList[r].isEnabled) continue; - + var list = filterList[r]; // Only run the rule checks if this list is meant to be active on this page. - if(list.activePage == null || list.activePage == "" || list.activePage == " ") { + if(list.activePage == null || list.activePage === '' || list.activePage === ' ') { // Using || instead of && for speed. } else { var splitActivePage = splitByProtocol(list.activePage); @@ -101,30 +113,30 @@ function manageRequest(request) { if(!match(splitActivePage.path, splitTabPage.path)) continue; } - + for(var i = 0; i < list.rules.length; i++) { var rule = list.rules[i]; var requestUrl = request.url; var testUrl = rule.src; - + // Just in-case someone leaves a field entirely whitespace. - if(testUrl == null || testUrl == "" || testUrl == " ") continue; - + if(testUrl == null || testUrl === '' || testUrl === ' ') continue; + // Split up the URLs into protocols and paths var splitRequestUrl = splitByProtocol(requestUrl); var splitTestUrl = splitByProtocol(testUrl); - + if(match(splitTestUrl.path, splitRequestUrl.path)) { - if(rule.dest != null && rule.dest != "" && match(rule.dest, requestUrl)) + if(rule.dest != null && rule.dest !== '' && match(rule.dest, requestUrl)) continue; - + var splitTargetUrl = splitByProtocol(rule.dest); var protocol = splitRequestUrl.protocol; - + if(splitTargetUrl.protocol != null) { protocol = splitTargetUrl.protocol; } - + var redirect = redirectUrl( splitTestUrl.path, splitTargetUrl.path, @@ -133,37 +145,39 @@ function manageRequest(request) { ); if(redirect) { - console.log("Redirecting "+request.url+" to " + redirect); - backloader.increment("redirected"); + console.log('Redirecting '+request.url+' to ' + redirect); + backloader.increment('redirected'); return {redirectUrl: redirect}; - + } else { //block the request - console.log("Blocking " + request.url); - backloader.increment("blocked"); + console.log('Blocking ' + request.url); + backloader.increment('blocked'); return {cancel: true}; } - }; + } } } } function removeUrlObjProtocol(urlObj) { - return urlObj.href.split(urlObj.protocol).join(""); + return urlObj.href.split(urlObj.protocol).join(''); } /** * Returns a complete list of URLs to filter. */ function loadFilterList(cb) { - chrome.storage.local.get("filter-list", function(storageResults) { - cb ? cb(storageResults["filter-list"]) : null; + // Arg 1: ['filterList'] Regressed to null for Firefox Support + browser.storage.local.get(null, function(storageResults) { + cb ? cb(storageResults['filter-list']) : null; }); } function loadEnabled(cb) { - chrome.storage.local.get("enabled", function(storageResults) { - cb ? cb(storageResults["enabled"]) : null; + // Arg 1: ['enabled'] Regressed to null for Firefox Support + browser.storage.local.get(null, function(storageResults) { + cb ? cb(storageResults['enabled']) : null; }); } @@ -184,8 +198,8 @@ function match(source, target) { * Calculates a redirect URL given source and target (optionally * containing wildcards), and the URL requested, by replaceing wildcards * in target with their respective matches in url. For example, -* redirectUrl("*nowhere*", "*somewhere*", "http://nowhere.nil") returns -* "http://somewhere.nil". +* redirectUrl('*nowhere*', '*somewhere*', 'http://nowhere.nil') returns +* 'http://somewhere.nil'. * @param {String} source - A string optionally with wildcards, matching * url. * @param {String} target - A string optionally with wildcards matching @@ -196,16 +210,16 @@ function match(source, target) { * in target. Or, if no target is provided, false. */ function redirectUrl(source, target, url, protocol){ - var protocol = protocol ? protocol : ""; - + protocol = protocol ? protocol : ''; + if(!target) return false; //a list of concrete values for the wildcards var matches = url.match(new RegExp(escapeUrl(source))); //a list of the non-wildcard parts of target - var targetSplit = target.split("*"); + var targetSplit = target.split('*'); //now we join the two, taking every other, starting with targetSplit var joined = targetSplit[0]; - + //i = 1 because targetSplit[0] has been appended above, and because //matches[0] is the same as url if(matches) { @@ -213,11 +227,11 @@ function redirectUrl(source, target, url, protocol){ joined += matches[i] + targetSplit[i]; } // Strip out any additional wildcards, just in case and add the protocol - return protocol+joined.split("*").join(""); + return protocol+joined.split('*').join(''); } - + // In the rare but possible case that there are no matches, return the joined target. - return protocol+targetSplit.join(""); + return protocol+targetSplit.join(''); } /** @@ -238,9 +252,9 @@ function escapeUrl(url) { * @return {Object} {protocol: null or the URL's protocol, path: The rest of the URL.} **/ function splitByProtocol(url) { - var splitUrl = url.split("://"); + var splitUrl = url.split('://'); if(splitUrl.length > 1) { - return {protocol: splitUrl[0]+"://", path: splitUrl[1]}; + return {protocol: splitUrl[0]+'://', path: splitUrl[1]}; } else { return {protocol: null, path: splitUrl[0]}; } diff --git a/scripts/defaultFilterList.js b/src/scripts/defaultFilterList.js similarity index 100% rename from scripts/defaultFilterList.js rename to src/scripts/defaultFilterList.js diff --git a/styles/remove_routerware.css b/src/styles/remove_routerware.css similarity index 100% rename from styles/remove_routerware.css rename to src/styles/remove_routerware.css