diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index 6f7ea259f..f26fcdd92 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -29,78 +29,44 @@ /******************************************************************************/ var warResolve = (function() { - var timer; - var toResolve = new Set(); - var toProcess = new Set(); - var reMimeParser = /^[^/]+\/([^\s;]+)/; + var warPairs = []; - var replacer = function(s) { - if ( s === '+' ) { return '-'; } - if ( s === '/' ) { return '_'; } - if ( s === '=' ) { return ''; } - return s; - }; - - var filenameFromToken = function(token, mime) { - var name = btoa(token).replace(/[+/=]/g, replacer); - var match = reMimeParser.exec(mime); - if ( match !== null ) { - name += '.' + match[1]; - } - return name; - }; - - var onResolved = function(success, token, url) { + var onPairsReady = function() { var reng = µBlock.redirectEngine; - this.onload = this.onerror = null; - var resource = reng.resources.get(token); - if ( resource !== undefined && success ) { - resource.warURL = url; - } - toProcess.delete(token); - if ( toResolve.size === 0 && toProcess.size === 0 ) { - reng.selfieFromResources(); - } - }; - - var resolvePending = function() { - timer = undefined; - var reng = µBlock.redirectEngine, - resources = reng.resources, - n = 8; // max number of xhr at once - for ( var token of toResolve ) { - var resource = resources.get(token); - toResolve.delete(token); + for ( var i = 0; i < warPairs.length; i += 2 ) { + var resource = reng.resources.get(warPairs[i+0]); if ( resource === undefined ) { continue; } - toProcess.add(token); - var url = vAPI.getURL( - '/web_accessible_resources/' + - filenameFromToken(token, resource.mime) + resource.warURL = vAPI.getURL( + '/web_accessible_resources/' + warPairs[i+1] ); - var xhr = new XMLHttpRequest(); - xhr.timeout = 1000; - xhr.open('head', url + '?secret=' + vAPI.warSecret); - xhr.onload = onResolved.bind(this, true, token, url); - xhr.onerror = onResolved.bind(this, false, token, url); - xhr.responseType = 'text'; - xhr.send(); - n -= 1; - if ( n === 0 ) { break; } - } - if ( toResolve.size !== 0 ) { - timer = vAPI.setTimeout(resolvePending, 5); - } else if ( toProcess.size === 0 ) { - reng.selfieFromResources(); } + reng.selfieFromResources(); }; - return function(token) { - if ( vAPI.warSecret !== undefined ) { - toResolve.add(token); - } - if ( timer === undefined ) { - timer = vAPI.setTimeout(resolvePending, 1); + return function() { + if ( vAPI.warSecret === undefined || warPairs.length !== 0 ) { + return onPairsReady(); } + + var onPairsLoaded = function(details) { + var marker = '>>>>>'; + var pos = details.content.indexOf(marker); + if ( pos === -1 ) { return; } + var pairs = details.content.slice(pos + marker.length) + .trim() + .split('\n'); + if ( (pairs.length & 1) !== 0 ) { return; } + for ( var i = 0; i < pairs.length; i++ ) { + pairs[i] = pairs[i].trim(); + } + warPairs = pairs; + onPairsReady(); + }; + + µBlock.assets.fetchText( + '/web_accessible_resources/imported.txt?secret=' + vAPI.warSecret, + onPairsLoaded + ); }; })(); @@ -500,7 +466,6 @@ RedirectEngine.prototype.resourcesFromString = function(text) { // No more data, add the resource. this.resources.set(fields[0], RedirectEntry.fromFields(fields[1], fields.slice(2))); - warResolve(fields[0]); fields = undefined; } @@ -508,13 +473,14 @@ RedirectEngine.prototype.resourcesFromString = function(text) { // Process pending resource data. if ( fields !== undefined ) { this.resources.set(fields[0], RedirectEntry.fromFields(fields[1], fields.slice(2))); - warResolve(fields[0]); } + + warResolve(); }; /******************************************************************************/ -var resourcesSelfieVersion = 1; +var resourcesSelfieVersion = 2; RedirectEngine.prototype.selfieFromResources = function() { vAPI.cacheStorage.set({ diff --git a/src/web_accessible_resources/README.txt b/src/web_accessible_resources/README.txt new file mode 100644 index 000000000..673d50e3f --- /dev/null +++ b/src/web_accessible_resources/README.txt @@ -0,0 +1,11 @@ +IMPORTANT + +Content of this folder cannot be accessed without the internal secret token +created each time uBlock Origin launched. + +Any fetch operation made without uBlock Origin's internal secret will result +in failure. This means that despite the content of the folder here declared as +"web accessible resources", it still cannot be seen by the outside world. + +Only uBlock Origin knows the secret token at runtime and hence only +uBlock Origin can see the content of this folder. diff --git a/src/web_accessible_resources/imported.txt b/src/web_accessible_resources/imported.txt new file mode 100644 index 000000000..2dad1d719 --- /dev/null +++ b/src/web_accessible_resources/imported.txt @@ -0,0 +1,7 @@ +# List of resources imported as "web accessible resources". +# +# To ensure valid filename characters on any platform OS, the filenames are +# constructed using the md5 hash of the respective tokens. +# +# DO NOT REMOVE THIS LINE >>>>> + diff --git a/src/web_accessible_resources/to-import.txt b/src/web_accessible_resources/to-import.txt index cbb74df59..969b2efdf 100644 --- a/src/web_accessible_resources/to-import.txt +++ b/src/web_accessible_resources/to-import.txt @@ -11,9 +11,8 @@ # page to use one of these "web accessible resource" to directly # detect the presence of uBO. # -# To ensure valid filename on any platform OS, the resource tokens are -# converted to base64 (https://tools.ietf.org/html/rfc7515#appendix-C), -# and the result is used as filename. +# To ensure valid filename characters on any platform OS, the filenames are +# constructed using the md5 hash of the respective tokens. # # In case uBO redirects to a resource which has not been converted into # a "web accessible resource", the redirection code will fall back to diff --git a/tools/import-war.py b/tools/import-war.py index baf8339e6..6c296f3ab 100755 --- a/tools/import-war.py +++ b/tools/import-war.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import base64 +import hashlib import os import re import sys @@ -19,6 +20,8 @@ with open('./src/web_accessible_resources/to-import.txt', 'r') as f: if len(line) != 0 and line[0] != '#': to_import.add(line) +imported = [] + # scan the file until a resource to import is found def find_next_resource(f): for line in f: @@ -31,7 +34,9 @@ def find_next_resource(f): return ('', '') def safe_filename_from_token(token, mime): - name = str(base64.b64encode(bytes(token, 'utf-8'), b'-_'), 'utf-8').strip('=') + h = hashlib.md5() + h.update(bytes(token, 'utf-8')) + name = h.hexdigest() # extract file extension from mime match = re.search('^[^/]+/([^\s;]+)', mime) if match: @@ -58,6 +63,7 @@ def import_resource(f, token, mime): filedata = bytes(filedata, 'utf-8') with open(filepath, 'wb') as fo: fo.write(filedata) + imported.append(token + '\n\t' + filename) # Read content of the resources to convert # - At this point, it is assumed resources.txt has been imported into the @@ -70,3 +76,11 @@ with open(resources_filename, 'r') as f: break import_resource(f, token, mime) +# Output associations +content = '' +with open('./src/web_accessible_resources/imported.txt', 'r') as f: + content = f.read() + '\n'.join(imported) + filename = os.path.join(build_dir, 'web_accessible_resources/imported.txt') + with open(filename, 'w') as f: + f.write(content) +