spoof USO referrer for their style search API

fixes #413
This commit is contained in:
tophf 2018-07-04 15:03:54 +03:00
parent 5b2dba4cdf
commit 7c9fd5e611
3 changed files with 109 additions and 14 deletions

View File

@ -330,12 +330,13 @@
} }
})(); })();
document.documentElement.appendChild(document.createElement('script')).text = '(' + // run in page context
function () { document.documentElement.appendChild(document.createElement('script')).text = `(${
EXTENSION_ORIGIN => {
document.currentScript.remove(); document.currentScript.remove();
// spoof Stylish extension presence in Chrome // spoof Stylish extension presence in Chrome
if (chrome.app) { if (window.chrome && chrome.app) {
const realImage = window.Image; const realImage = window.Image;
window.Image = function Image(...args) { window.Image = function Image(...args) {
return new Proxy(new realImage(...args), { return new Proxy(new realImage(...args), {
@ -354,6 +355,29 @@ document.documentElement.appendChild(document.createElement('script')).text = '(
}; };
} }
// spoof USO referrer for style search in the popup
if (window !== top && location.pathname === '/') {
window.addEventListener('message', ({data, origin}) => {
if (!data ||
!data.xhr ||
origin !== EXTENSION_ORIGIN) {
return;
}
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.onloadend = xhr.onerror = () => {
window.stop();
top.postMessage({
id: data.xhr.id,
status: xhr.status,
response: xhr.response,
}, EXTENSION_ORIGIN);
};
xhr.open('GET', data.xhr.url);
xhr.send();
});
}
// USO bug workaround: use the actual style settings in API response // USO bug workaround: use the actual style settings in API response
let settings; let settings;
const originalResponseJson = Response.prototype.json; const originalResponseJson = Response.prototype.json;
@ -426,7 +450,8 @@ document.documentElement.appendChild(document.createElement('script')).text = '(
return json; return json;
}); });
}; };
} + ')()'; }
})('${chrome.runtime.getURL('').slice(0, -1)}')`;
// TODO: remove the following statement when USO pagination is fixed // TODO: remove the following statement when USO pagination is fixed
if (location.search.includes('category=')) { if (location.search.includes('category=')) {

View File

@ -14,6 +14,8 @@
"permissions": [ "permissions": [
"tabs", "tabs",
"webNavigation", "webNavigation",
"webRequest",
"webRequestBlocking",
"contextMenus", "contextMenus",
"storage", "storage",
"alarms", "alarms",
@ -60,7 +62,7 @@
{ {
"matches": ["http://userstyles.org/*", "https://userstyles.org/*"], "matches": ["http://userstyles.org/*", "https://userstyles.org/*"],
"run_at": "document_start", "run_at": "document_start",
"all_frames": false, "all_frames": true,
"js": ["content/install-hook-userstyles.js"] "js": ["content/install-hook-userstyles.js"]
}, },
{ {

View File

@ -51,6 +51,9 @@ window.addEventListener('showStyles:done', function _() {
let searchCurrentPage = 1; let searchCurrentPage = 1;
let searchExhausted = false; let searchExhausted = false;
let searchFrame;
let searchFrameQueue;
const processedResults = []; const processedResults = [];
const unprocessedResults = []; const unprocessedResults = [];
@ -697,15 +700,7 @@ window.addEventListener('showStyles:done', function _() {
return readCache(cacheKey) return readCache(cacheKey)
.then(json => .then(json =>
json || json ||
download(searchURL, { searchInFrame(searchURL).then(writeCache))
method: 'GET',
headers: {
'Content-type': 'application/json',
'Accept': '*/*'
},
responseType: 'json',
body: null
}).then(writeCache))
.then(json => { .then(json => {
searchCurrentPage = json.current_page + 1; searchCurrentPage = json.current_page + 1;
searchTotalPages = json.total_pages; searchTotalPages = json.total_pages;
@ -783,5 +778,78 @@ window.addEventListener('showStyles:done', function _() {
ignoreChromeError(); ignoreChromeError();
} }
//endregion
//region USO referrer spoofing via iframe
function searchInFrame(url) {
return searchFrame ? new Promise((resolve, reject) => {
const id = performance.now();
const timeout = setTimeout(() => {
searchFrameQueue.get(id).reject();
searchFrameQueue.delete(id);
}, 10e3);
searchFrameQueue.set(id, {resolve, reject, timeout});
searchFrame.contentWindow.postMessage({xhr: {id, url}}, '*');
}) : setupFrame().then(() => searchInFrame(url));
}
function setupFrame() {
searchFrame = $create('iframe', {src: BASE_URL});
searchFrameQueue = new Map();
const stripHeaders = info => ({
responseHeaders: info.responseHeaders.filter(({name}) => !/^X-Frame-Options$/i.test(name)),
});
chrome.webRequest.onHeadersReceived.addListener(stripHeaders, {
urls: [BASE_URL + '/'],
types: ['sub_frame'],
}, [
'blocking',
'responseHeaders',
]);
let frameId;
const stripResources = info => {
if (!frameId && info.url === BASE_URL + '/') {
frameId = info.frameId;
} else if (frameId === info.frameId && info.type !== 'xmlhttprequest') {
return {redirectUrl: 'data:,'};
}
};
chrome.webRequest.onBeforeRequest.addListener(stripResources, {
urls: ['<all_urls>'],
}, [
'blocking',
]);
setTimeout(() => {
chrome.webRequest.onBeforeRequest.removeListener(stripResources);
}, 10e3);
window.addEventListener('message', ({data, origin}) => {
if (!data || origin !== BASE_URL) return;
const {resolve, reject, timeout} = searchFrameQueue.get(data.id) || {};
if (!resolve) return;
chrome.webRequest.onBeforeRequest.removeListener(stripResources);
searchFrameQueue.delete(data.id);
clearTimeout(timeout);
if (data.response && data.status < 400) {
resolve(data.response);
} else {
reject(data.status);
}
});
return new Promise((resolve, reject) => {
const done = event => {
chrome.webRequest.onHeadersReceived.removeListener(stripHeaders);
(event.type === 'load' ? resolve : reject)();
};
searchFrame.addEventListener('load', done, {once: true});
searchFrame.addEventListener('error', done, {once: true});
searchFrame.style.setProperty('display', 'none', 'important');
document.body.appendChild(searchFrame);
});
}
//endregion //endregion
}); });