Skip to content

Commit

Permalink
Restore ability to redirect xhr to image resources
Browse files Browse the repository at this point in the history
The ability to redirect xmlhttprequest to binary
resources was lost when redirectable/injectable
resources became immutable in commit
152cea2.

This commit restores the ability to redirect a
xmlhttprequest to a binary resource by making
it possible to derive a data: URI from the
content of binary resources such as images.

Addtionally a redirect to a data: URI can be
forced by prefixing the resource token with `%`.
This is a non-official feature at this point,
i.e. it could be removed at any time.
  • Loading branch information
gorhill committed Aug 6, 2019
1 parent a1b9995 commit 7ac7b02
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 163 deletions.
135 changes: 73 additions & 62 deletions src/js/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,8 @@ const fireNotification = function(topic, details) {

/******************************************************************************/

api.fetchText = function(url, onLoad, onError) {
const isExternal = reIsExternalPath.test(url);
let actualUrl = isExternal ? url : vAPI.getURL(url);

// https://github.com/gorhill/uBlock/issues/2592
// Force browser cache to be bypassed, but only for resources which have
// been fetched more than one hour ago.
//
// https://github.com/uBlockOrigin/uBlock-issues/issues/682#issuecomment-515197130
// Provide filter list authors a way to completely bypass
// the browser cache.
if ( isExternal ) {
const cacheBypassToken =
µBlock.hiddenSettings.updateAssetBypassBrowserCache
? Math.floor(Date.now() / 1000) % 86400
: Math.floor(Date.now() / 3600000) % 12;
const queryValue = `_=${cacheBypassToken}`;
if ( actualUrl.indexOf('?') === -1 ) {
actualUrl += '?';
} else {
actualUrl += '&';
}
actualUrl += queryValue;
}

if ( typeof onError !== 'function' ) {
onError = onLoad;
}

return new Promise(resolve => {
api.fetch = function(url, options = {}) {
return new Promise((resolve, reject) => {
// Start of executor

const timeoutAfter = µBlock.hiddenSettings.assetFetchTimeout * 1000 || 30000;
Expand All @@ -110,20 +82,6 @@ api.fetchText = function(url, onLoad, onError) {
}
};

const onResolve = function(details) {
if ( onLoad instanceof Function ) {
return onLoad(details);
}
resolve(details);
};

const onReject = function(details) {
if ( onError instanceof Function ) {
return onError(details);
}
resolve(details);
};

// https://github.com/gorhill/uMatrix/issues/15
const onLoadEvent = function() {
cleanup();
Expand All @@ -135,31 +93,20 @@ api.fetchText = function(url, onLoad, onError) {
statusText: this.statusText || ''
};
if ( details.statusCode < 200 || details.statusCode >= 300 ) {
return onReject(details);
}
// consider an empty result to be an error
if ( stringIsNotEmpty(this.responseText) === false ) {
return onReject(details);
}
// we never download anything else than plain text: discard if response
// appears to be a HTML document: could happen when server serves
// some kind of error page I suppose
const text = this.responseText.trim();
if ( text.startsWith('<') && text.endsWith('>') ) {
return onReject(details);
return reject(details);
}
details.content = this.responseText;
onResolve(details);
details.content = this.response;
resolve(details);
};

const onErrorEvent = function() {
cleanup();
µBlock.logger.writeOne({
realm: 'message',
type: 'error',
text: errorCantConnectTo.replace('{{msg}}', actualUrl)
text: errorCantConnectTo.replace('{{msg}}', url)
});
onReject({ url, content: '' });
reject({ url, content: '' });
};

const onTimeout = function() {
Expand All @@ -181,12 +128,12 @@ api.fetchText = function(url, onLoad, onError) {
// I am pretty sure it used to work, but now using a URL such as
// `file:///` on Chromium 40 results in an exception being thrown.
try {
xhr.open('get', actualUrl, true);
xhr.open('get', url, true);
xhr.addEventListener('load', onLoadEvent);
xhr.addEventListener('error', onErrorEvent);
xhr.addEventListener('abort', onErrorEvent);
xhr.addEventListener('progress', onProgressEvent);
xhr.responseType = 'text';
xhr.responseType = options.responseType || 'text';
xhr.send();
timeoutTimer = vAPI.setTimeout(onTimeout, timeoutAfter);
} catch (e) {
Expand All @@ -199,6 +146,70 @@ api.fetchText = function(url, onLoad, onError) {

/******************************************************************************/

api.fetchText = function(url, onLoad, onError) {
const isExternal = reIsExternalPath.test(url);
let actualUrl = isExternal ? url : vAPI.getURL(url);

// https://github.com/gorhill/uBlock/issues/2592
// Force browser cache to be bypassed, but only for resources which have
// been fetched more than one hour ago.
//
// https://github.com/uBlockOrigin/uBlock-issues/issues/682#issuecomment-515197130
// Provide filter list authors a way to completely bypass
// the browser cache.
if ( isExternal ) {
const cacheBypassToken =
µBlock.hiddenSettings.updateAssetBypassBrowserCache
? Math.floor(Date.now() / 1000) % 86400
: Math.floor(Date.now() / 3600000) % 12;
const queryValue = `_=${cacheBypassToken}`;
if ( actualUrl.indexOf('?') === -1 ) {
actualUrl += '?';
} else {
actualUrl += '&';
}
actualUrl += queryValue;
}

if ( typeof onError !== 'function' ) {
onError = onLoad;
}

const onResolve = function(details) {
if ( onLoad instanceof Function ) {
return onLoad(details);
}
return details;
};

const onReject = function(details) {
details.content = '';
if ( onError instanceof Function ) {
return onError(details);
}
return details;
};

return api.fetch(url).then(details => {
// consider an empty result to be an error
if ( stringIsNotEmpty(details.content) === false ) {
return onReject(details);
}
// we never download anything else than plain text: discard if response
// appears to be a HTML document: could happen when server serves
// some kind of error page I suppose
const text = details.content.trim();
if ( text.startsWith('<') && text.endsWith('>') ) {
return onReject(details);
}
return onResolve(details);
}).catch(details => {
return onReject(details);
});
};

/******************************************************************************/

// https://github.com/gorhill/uBlock/issues/3331
// Support the seamless loading of sublists.

Expand Down
Loading

1 comment on commit 7ac7b02

@gorhill
Copy link
Owner Author

@gorhill gorhill commented on 7ac7b02 Aug 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have noted that I found only one instance of such xhr redirection to an image, which I removed when I refactored resource management:

uBlockOrigin/uAssets@1e0b6bb

I verified the filter was obsolete but it does show that the case for such filter can arise.

Please sign in to comment.