spoof HTTP Referer for USO directly (#608)
This commit is contained in:
parent
68ce3a653e
commit
8b8a3d60ab
|
@ -16,6 +16,8 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
const RESULT_ID_PREFIX = 'search-result-';
|
const RESULT_ID_PREFIX = 'search-result-';
|
||||||
|
|
||||||
const BASE_URL = 'https://userstyles.org';
|
const BASE_URL = 'https://userstyles.org';
|
||||||
|
const JSON_URL = BASE_URL + '/styles/chrome/';
|
||||||
|
const API_URL = BASE_URL + '/api/v1/styles/';
|
||||||
const UPDATE_URL = 'https://update.userstyles.org/%.md5';
|
const UPDATE_URL = 'https://update.userstyles.org/%.md5';
|
||||||
|
|
||||||
// normal category is just one word like 'github' or 'google'
|
// normal category is just one word like 'github' or 'google'
|
||||||
|
@ -54,8 +56,10 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
let searchCurrentPage = 1;
|
let searchCurrentPage = 1;
|
||||||
let searchExhausted = false;
|
let searchExhausted = false;
|
||||||
|
|
||||||
let usoFrame;
|
// currently active USO requests
|
||||||
let usoFrameQueue;
|
const xhrSpoofIds = new Set();
|
||||||
|
// used as an HTTP header name to identify spoofed requests
|
||||||
|
const xhrSpoofTelltale = getRandomId();
|
||||||
|
|
||||||
const processedResults = [];
|
const processedResults = [];
|
||||||
const unprocessedResults = [];
|
const unprocessedResults = [];
|
||||||
|
@ -653,7 +657,7 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
function fetchStyleJson(result) {
|
function fetchStyleJson(result) {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
result.json ||
|
result.json ||
|
||||||
downloadInFrame(BASE_URL + '/styles/chrome/' + result.id + '.json').then(json => {
|
downloadFromUSO(JSON_URL + result.id + '.json').then(json => {
|
||||||
result.json = json;
|
result.json = json;
|
||||||
return json;
|
return json;
|
||||||
}));
|
}));
|
||||||
|
@ -667,7 +671,7 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
function fetchStyle(userstylesId) {
|
function fetchStyle(userstylesId) {
|
||||||
return readCache(userstylesId).then(json =>
|
return readCache(userstylesId).then(json =>
|
||||||
json ||
|
json ||
|
||||||
downloadInFrame(BASE_URL + '/api/v1/styles/' + userstylesId).then(writeCache));
|
downloadFromUSO(API_URL + userstylesId).then(writeCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -685,8 +689,7 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
return Promise.resolve({'data':[]});
|
return Promise.resolve({'data':[]});
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchURL = BASE_URL +
|
const searchURL = API_URL + 'subcategory' +
|
||||||
'/api/v1/styles/subcategory' +
|
|
||||||
'?search=' + encodeURIComponent(category) +
|
'?search=' + encodeURIComponent(category) +
|
||||||
'&page=' + searchCurrentPage +
|
'&page=' + searchCurrentPage +
|
||||||
'&per_page=10' +
|
'&per_page=10' +
|
||||||
|
@ -697,7 +700,7 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
return readCache(cacheKey)
|
return readCache(cacheKey)
|
||||||
.then(json =>
|
.then(json =>
|
||||||
json ||
|
json ||
|
||||||
downloadInFrame(searchURL).then(writeCache))
|
downloadFromUSO(searchURL).then(writeCache))
|
||||||
.then(json => {
|
.then(json => {
|
||||||
searchCurrentPage = json.current_page + 1;
|
searchCurrentPage = json.current_page + 1;
|
||||||
searchTotalPages = json.total_pages;
|
searchTotalPages = json.total_pages;
|
||||||
|
@ -778,89 +781,74 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
//region USO referrer spoofing via iframe
|
//region USO referrer spoofing
|
||||||
|
|
||||||
function downloadInFrame(url) {
|
function downloadFromUSO(url) {
|
||||||
return usoFrame ? new Promise((resolve, reject) => {
|
const requestId = getRandomId();
|
||||||
const id = performance.now();
|
xhrSpoofIds.add(requestId);
|
||||||
const timeout = setTimeout(() => {
|
xhrSpoofStart();
|
||||||
const {reject} = usoFrameQueue.get(id) || {};
|
return download(url, {
|
||||||
usoFrameQueue.delete(id);
|
body: null,
|
||||||
if (reject) reject();
|
responseType: 'json',
|
||||||
}, 10e3);
|
headers: {
|
||||||
const data = {url, resolve, reject, timeout};
|
'Referrer-Policy': 'origin-when-cross-origin',
|
||||||
usoFrameQueue.set(id, data);
|
[xhrSpoofTelltale]: requestId,
|
||||||
usoFrame.contentWindow.postMessage({xhr: {id, url}}, '*');
|
}
|
||||||
}) : setupFrame().then(() => downloadInFrame(url));
|
}).then(data => {
|
||||||
|
xhrSpoofDone(requestId);
|
||||||
|
return data;
|
||||||
|
}).catch(data => {
|
||||||
|
xhrSpoofDone(requestId);
|
||||||
|
return Promise.reject(data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupFrame() {
|
function xhrSpoofStart() {
|
||||||
usoFrame = $create('iframe', {src: BASE_URL});
|
if (chrome.webRequest.onBeforeSendHeaders.hasListener(xhrSpoof)) {
|
||||||
usoFrameQueue = new Map();
|
return;
|
||||||
|
}
|
||||||
|
const urls = [API_URL + '*', JSON_URL + '*'];
|
||||||
|
const types = ['xmlhttprequest'];
|
||||||
|
const options = ['blocking', 'requestHeaders'];
|
||||||
|
// spoofing Referer requires extraHeaders in Chrome 72+
|
||||||
|
if (chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS) {
|
||||||
|
options.push(chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS);
|
||||||
|
}
|
||||||
|
chrome.webRequest.onBeforeSendHeaders.addListener(xhrSpoof, {urls, types}, options);
|
||||||
|
}
|
||||||
|
|
||||||
const stripHeaders = info => ({
|
function xhrSpoofDone(requestId) {
|
||||||
responseHeaders: info.responseHeaders.filter(({name}) => !/^X-Frame-Options$/i.test(name)),
|
xhrSpoofIds.delete(requestId);
|
||||||
});
|
if (!xhrSpoofIds.size) {
|
||||||
chrome.webRequest.onHeadersReceived.addListener(stripHeaders, {
|
chrome.webRequest.onBeforeSendHeaders.removeListener(xhrSpoof);
|
||||||
urls: [BASE_URL + '/'],
|
}
|
||||||
types: ['sub_frame'],
|
}
|
||||||
}, [
|
|
||||||
'blocking',
|
|
||||||
'responseHeaders',
|
|
||||||
]);
|
|
||||||
|
|
||||||
let frameId;
|
function xhrSpoof({requestHeaders}) {
|
||||||
const stripResources = info => {
|
let referer, hasTelltale;
|
||||||
if (!frameId &&
|
for (let i = requestHeaders.length; --i >= 0;) {
|
||||||
info.frameId &&
|
const header = requestHeaders[i];
|
||||||
info.type === 'sub_frame' &&
|
if (header.name.toLowerCase() === 'referer') {
|
||||||
(info.initiator === location.origin || !info.initiator) && // Chrome 63+
|
referer = header;
|
||||||
(info.originUrl === location.href || !info.originUrl) && // FF 48+
|
} else if (header.name === xhrSpoofTelltale) {
|
||||||
info.url === BASE_URL + '/') {
|
hasTelltale = xhrSpoofIds.has(header.value);
|
||||||
frameId = info.frameId;
|
requestHeaders.splice(i, 1);
|
||||||
} else if (frameId === info.frameId && info.type !== 'xmlhttprequest') {
|
|
||||||
return {redirectUrl: 'data:,'};
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
chrome.webRequest.onBeforeRequest.addListener(stripResources, {
|
if (!hasTelltale) {
|
||||||
urls: ['<all_urls>'],
|
// not our request (unlikely but just in case)
|
||||||
}, [
|
return;
|
||||||
'blocking',
|
}
|
||||||
]);
|
if (referer) {
|
||||||
setTimeout(() => {
|
referer.value = BASE_URL;
|
||||||
chrome.webRequest.onBeforeRequest.removeListener(stripResources);
|
} else {
|
||||||
}, 10e3);
|
requestHeaders.push({name: 'Referer', value: BASE_URL});
|
||||||
|
}
|
||||||
|
return {requestHeaders};
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('message', ({data, origin}) => {
|
function getRandomId() {
|
||||||
if (!data || origin !== BASE_URL) return;
|
return btoa(Math.random()).replace(/[^a-z]/gi, '');
|
||||||
const {resolve, reject, timeout} = usoFrameQueue.get(data.id) || {};
|
|
||||||
if (!resolve) return;
|
|
||||||
chrome.webRequest.onBeforeRequest.removeListener(stripResources);
|
|
||||||
usoFrameQueue.delete(data.id);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
// [being overcautious] a string response is used instead of relying on responseType=json
|
|
||||||
// because it was invoked in a web page context so another extension may have incorrectly spoofed it
|
|
||||||
const json = tryJSONparse(data.response);
|
|
||||||
if (json && data.status < 400) {
|
|
||||||
resolve(json);
|
|
||||||
} else {
|
|
||||||
reject(data.status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const done = event => {
|
|
||||||
chrome.webRequest.onHeadersReceived.removeListener(stripHeaders);
|
|
||||||
(event.type === 'load' ? resolve : reject)();
|
|
||||||
usoFrameQueue.forEach(({url}, id) => {
|
|
||||||
usoFrame.contentWindow.postMessage({xhr: {id, url}}, '*');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
usoFrame.addEventListener('load', done, {once: true});
|
|
||||||
usoFrame.addEventListener('error', done, {once: true});
|
|
||||||
usoFrame.style.setProperty('display', 'none', 'important');
|
|
||||||
document.body.appendChild(usoFrame);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
Loading…
Reference in New Issue
Block a user