rammerhead uyghsurhgush

This commit is contained in:
wearrrrr 2024-02-11 20:19:57 -06:00
parent 5b08275dfe
commit 6e79bc92f9
2 changed files with 26859 additions and 0 deletions

26362
public/hammerhead.js Normal file

File diff suppressed because it is too large Load diff

497
public/rammerhead.js Normal file
View file

@ -0,0 +1,497 @@
(function () {
var hammerhead = window['%hammerhead%'];
if (!hammerhead) throw new Error('hammerhead not loaded yet');
if (hammerhead.settings._settings.sessionId) {
// task.js already loaded. this will likely never happen though since this file loads before task.js
console.warn('unexpected task.js to load before rammerhead.js. url shuffling cannot be used');
main();
} else {
// wait for task.js to load
hookHammerheadStartOnce(main);
// before task.js, we need to add url shuffling
addUrlShuffling();
}
function main() {
fixUrlRewrite();
fixElementGetter();
fixCrossWindowLocalStorage();
delete window.overrideGetProxyUrl;
delete window.overrideParseProxyUrl;
delete window.overrideIsCrossDomainWindows;
// other code if they want to also hook onto hammerhead start //
if (window.rammerheadStartListeners) {
for (const eachListener of window.rammerheadStartListeners) {
try {
eachListener();
} catch (e) {
console.error(e);
}
}
delete window.rammerheadStartListeners;
}
// sync localStorage code //
// disable if other code wants to implement their own localStorage site wrapper
if (window.rammerheadDisableLocalStorageImplementation) {
delete window.rammerheadDisableLocalStorageImplementation;
return;
}
// consts
var timestampKey = 'rammerhead_synctimestamp';
var updateInterval = 5000;
var isSyncing = false;
var proxiedLocalStorage = localStorage;
var realLocalStorage = proxiedLocalStorage.internal.nativeStorage;
var sessionId = hammerhead.settings._settings.sessionId;
var origin = window.__get$(window, 'location').origin;
var keyChanges = [];
try {
syncLocalStorage();
} catch (e) {
if (e.message !== 'server wants to disable localStorage syncing') {
throw e;
}
return;
}
proxiedLocalStorage.addChangeEventListener(function (event) {
if (isSyncing) return;
if (keyChanges.indexOf(event.key) === -1) keyChanges.push(event.key);
});
setInterval(function () {
var update = compileUpdate();
if (!update) return;
localStorageRequest({ type: 'update', updateData: update }, function (data) {
updateTimestamp(data.timestamp);
});
keyChanges = [];
}, updateInterval);
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'hidden') {
var update = compileUpdate();
if (update) {
// even though we'll never get the timestamp, it's fine. this way,
// the data is safer
hammerhead.nativeMethods.sendBeacon.call(
window.navigator,
getSyncStorageEndpoint(),
JSON.stringify({
type: 'update',
updateData: update
})
);
}
}
});
function syncLocalStorage() {
isSyncing = true;
var timestamp = getTimestamp();
var response;
if (!timestamp) {
// first time syncing
response = localStorageRequest({ type: 'sync', fetch: true });
if (response.timestamp) {
updateTimestamp(response.timestamp);
overwriteLocalStorage(response.data);
}
} else {
// resync
response = localStorageRequest({ type: 'sync', timestamp: timestamp, data: proxiedLocalStorage });
if (response.timestamp) {
updateTimestamp(response.timestamp);
overwriteLocalStorage(response.data);
}
}
isSyncing = false;
function overwriteLocalStorage(data) {
if (!data || typeof data !== 'object') throw new TypeError('data must be an object');
proxiedLocalStorage.clear();
for (var prop in data) {
proxiedLocalStorage[prop] = data[prop];
}
}
}
function updateTimestamp(timestamp) {
if (!timestamp) throw new TypeError('timestamp must be defined');
if (isNaN(parseInt(timestamp))) throw new TypeError('timestamp must be a number. received' + timestamp);
realLocalStorage[timestampKey] = timestamp;
}
function getTimestamp() {
var rawTimestamp = realLocalStorage[timestampKey];
var timestamp = parseInt(rawTimestamp);
if (isNaN(timestamp)) {
if (rawTimestamp) {
console.warn('invalid timestamp retrieved from storage: ' + rawTimestamp);
}
return null;
}
return timestamp;
}
function getSyncStorageEndpoint() {
return (
'/syncLocalStorage?sessionId=' + encodeURIComponent(sessionId) + '&origin=' + encodeURIComponent(origin)
);
}
function localStorageRequest(data, callback) {
if (!data || typeof data !== 'object') throw new TypeError('data must be an object');
var request = hammerhead.createNativeXHR();
// make synchronous if there is no callback
request.open('POST', getSyncStorageEndpoint(), !!callback);
request.setRequestHeader('content-type', 'application/json');
request.send(JSON.stringify(data));
function check() {
if (request.status === 404) {
throw new Error('server wants to disable localStorage syncing');
}
if (request.status !== 200)
throw new Error(
'server sent a non 200 code. got ' + request.status + '. Response: ' + request.responseText
);
}
if (!callback) {
check();
return JSON.parse(request.responseText);
} else {
request.onload = function () {
check();
callback(JSON.parse(request.responseText));
};
}
}
function compileUpdate() {
if (!keyChanges.length) return null;
var updates = {};
for (var i = 0; i < keyChanges.length; i++) {
updates[keyChanges[i]] = proxiedLocalStorage[keyChanges[i]];
}
keyChanges = [];
return updates;
}
}
var noShuffling = false;
function addUrlShuffling() {
const request = new XMLHttpRequest();
const sessionId = (location.pathname.slice(1).match(/^[a-z0-9]+/i) || [])[0];
if (!sessionId) {
console.warn('cannot get session id from url');
return;
}
request.open('GET', '/api/shuffleDict?id=' + sessionId, false);
request.send();
if (request.status !== 200) {
console.warn(
`received a non 200 status code while trying to fetch shuffleDict:\nstatus: ${request.status}\nresponse: ${request.responseText}`
);
return;
}
const shuffleDict = JSON.parse(request.responseText);
if (!shuffleDict) return;
// pasting entire thing here "because lazy" - m28
const mod = (n, m) => ((n % m) + m) % m;
const baseDictionary = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-';
const shuffledIndicator = '_rhs';
const generateDictionary = function () {
let str = '';
const split = baseDictionary.split('');
while (split.length > 0) {
str += split.splice(Math.floor(Math.random() * split.length), 1)[0];
}
return str;
};
class StrShuffler {
constructor(dictionary = generateDictionary()) {
this.dictionary = dictionary;
}
shuffle(str) {
if (str.startsWith(shuffledIndicator)) {
return str;
}
let shuffledStr = '';
for (let i = 0; i < str.length; i++) {
const char = str.charAt(i);
const idx = baseDictionary.indexOf(char);
if (char === '%' && str.length - i >= 3) {
shuffledStr += char;
shuffledStr += str.charAt(++i);
shuffledStr += str.charAt(++i);
} else if (idx === -1) {
shuffledStr += char;
} else {
shuffledStr += this.dictionary.charAt(mod(idx + i, baseDictionary.length));
}
}
return shuffledIndicator + shuffledStr;
}
unshuffle(str) {
if (!str.startsWith(shuffledIndicator)) {
return str;
}
str = str.slice(shuffledIndicator.length);
let unshuffledStr = '';
for (let i = 0; i < str.length; i++) {
const char = str.charAt(i);
const idx = this.dictionary.indexOf(char);
if (char === '%' && str.length - i >= 3) {
unshuffledStr += char;
unshuffledStr += str.charAt(++i);
unshuffledStr += str.charAt(++i);
} else if (idx === -1) {
unshuffledStr += char;
} else {
unshuffledStr += baseDictionary.charAt(mod(idx - i, baseDictionary.length));
}
}
return unshuffledStr;
}
}
function patch(url) {
// url = _rhsEPrcb://bqhQko.tHR/
// remove slash
return url.replace(/(^.*?:\/)\//, '$1');
}
function unpatch(url) {
// url = _rhsEPrcb:/bqhQko.tHR/
// restore slash
return url.replace(/^.*?:\/(?!\/)/, '$&/');
}
const replaceUrl = (url, replacer) => {
// regex: https://google.com/ sessionid/ url
return (url || '').replace(/^((?:[a-z0-9]+:\/\/[^/]+)?(?:\/[^/]+\/))([^]+)/i, function (_, g1, g2) {
return g1 + replacer(g2);
})
};
const shuffler = new StrShuffler(shuffleDict);
// shuffle current url if it isn't already shuffled (unshuffled urls likely come from user input)
const oldUrl = location.href;
const newUrl = replaceUrl(location.href, (url) => shuffler.shuffle(url));
if (oldUrl !== newUrl) {
history.replaceState(null, null, newUrl);
}
const getProxyUrl = hammerhead.utils.url.getProxyUrl;
const parseProxyUrl = hammerhead.utils.url.parseProxyUrl;
hammerhead.utils.url.overrideGetProxyUrl(function (url, opts) {
if (noShuffling) {
return getProxyUrl(url, opts);
}
return replaceUrl(getProxyUrl(url, opts), (u) => patch(shuffler.shuffle(u)), true)
});
hammerhead.utils.url.overrideParseProxyUrl(function (url) {
return parseProxyUrl(replaceUrl(url, (u) => shuffler.unshuffle(unpatch(u)), false));
});
// manual hooks //
window.overrideGetProxyUrl(
(getProxyUrl$1) =>
function (url, opts) {
if (noShuffling) {
return getProxyUrl$1(url, opts);
}
return replaceUrl(getProxyUrl$1(url, opts), (u) => patch(shuffler.shuffle(u)), true);
}
);
window.overrideParseProxyUrl(
(parseProxyUrl$1) =>
function (url) {
return parseProxyUrl$1(replaceUrl(url, (u) => shuffler.unshuffle(unpatch(u)), false));
}
);
}
function fixUrlRewrite() {
const port = location.port || (location.protocol === 'https:' ? '443' : '80');
const getProxyUrl = hammerhead.utils.url.getProxyUrl;
hammerhead.utils.url.overrideGetProxyUrl(function (url, opts = {}) {
if (!opts.proxyPort) {
opts.proxyPort = port;
}
return getProxyUrl(url, opts);
});
window.overrideParseProxyUrl(
(parseProxyUrl$1) =>
function (url) {
const parsed = parseProxyUrl$1(url);
if (!parsed || !parsed.proxy) return parsed;
if (!parsed.proxy.port) {
parsed.proxy.port = port;
}
return parsed;
}
);
}
function fixElementGetter() {
const fixList = {
HTMLAnchorElement: ['href'],
HTMLAreaElement: ['href'],
HTMLBaseElement: ['href'],
HTMLEmbedElement: ['src'],
HTMLFormElement: ['action'],
HTMLFrameElement: ['src'],
HTMLIFrameElement: ['src'],
HTMLImageElement: ['src'],
HTMLInputElement: ['src'],
HTMLLinkElement: ['href'],
HTMLMediaElement: ['src'],
HTMLModElement: ['cite'],
HTMLObjectElement: ['data'],
HTMLQuoteElement: ['cite'],
HTMLScriptElement: ['src'],
HTMLSourceElement: ['src'],
HTMLTrackElement: ['src']
};
const urlRewrite = (url) => (hammerhead.utils.url.parseProxyUrl(url) || {}).destUrl || url;
for (const ElementClass in fixList) {
for (const attr of fixList[ElementClass]) {
if (!window[ElementClass]) {
console.warn('unexpected unsupported element class ' + ElementClass);
continue;
}
const desc = Object.getOwnPropertyDescriptor(window[ElementClass].prototype, attr);
const originalGet = desc.get;
desc.get = function () {
return urlRewrite(originalGet.call(this));
};
if (attr === 'action') {
const originalSet = desc.set;
// don't shuffle form action urls
desc.set = function (value) {
noShuffling = true;
try {
var returnVal = originalSet.call(this, value);
} catch (e) {
noShuffling = false;
throw e;
}
noShuffling = false;
return returnVal;
};
}
Object.defineProperty(window[ElementClass].prototype, attr, desc);
}
}
}
function fixCrossWindowLocalStorage() {
// completely replace hammerhead's implementation as restore() and save() on every
// call is just not viable (mainly memory issues as the garbage collector is sometimes not fast enough)
const prefix = `rammerhead|storage-wrapper|${hammerhead.settings._settings.sessionId}|${
window.__get$(window, 'location').host
}|`;
const toRealStorageKey = (key = '') => prefix + key;
const fromRealStorageKey = (key = '') => {
if (!key.startsWith(prefix)) return null;
return key.slice(prefix.length);
};
const replaceStorageInstance = (storageProp, realStorage) => {
const reservedProps = ['internal', 'clear', 'key', 'getItem', 'setItem', 'removeItem', 'length'];
Object.defineProperty(window, storageProp, {
// define a value-based instead of getter-based property, since with this localStorage implementation,
// we don't need to rely on sharing a single memory-based storage across frames, unlike hammerhead
configurable: true,
writable: true,
// still use window[storageProp] as basis to allow scripts to access localStorage.internal
value: new Proxy(window[storageProp], {
get(target, prop, receiver) {
if (reservedProps.includes(prop) && prop !== 'length') {
return Reflect.get(target, prop, receiver);
} else if (prop === 'length') {
let len = 0;
for (const [key] of Object.entries(realStorage)) {
if (fromRealStorageKey(key)) len++;
}
return len;
} else {
return realStorage[toRealStorageKey(prop)];
}
},
set(_, prop, value) {
if (!reservedProps.includes(prop)) {
realStorage[toRealStorageKey(prop)] = value;
}
return true;
},
deleteProperty(_, prop) {
delete realStorage[toRealStorageKey(prop)];
return true;
},
has(target, prop) {
return toRealStorageKey(prop) in realStorage || prop in target;
},
ownKeys() {
const list = [];
for (const [key] of Object.entries(realStorage)) {
const proxyKey = fromRealStorageKey(key);
if (proxyKey && !reservedProps.includes(proxyKey)) list.push(proxyKey);
}
return list;
},
getOwnPropertyDescriptor(_, prop) {
return Object.getOwnPropertyDescriptor(realStorage, toRealStorageKey(prop));
},
defineProperty(_, prop, desc) {
if (!reservedProps.includes(prop)) {
Object.defineProperty(realStorage, toRealStorageKey(prop), desc);
}
return true;
}
})
});
};
const rewriteFunction = (prop, newFunc) => {
Storage.prototype[prop] = new Proxy(Storage.prototype[prop], {
apply(_, thisArg, args) {
return newFunc.apply(thisArg, args);
}
});
};
replaceStorageInstance('localStorage', hammerhead.storages.localStorageProxy.internal.nativeStorage);
replaceStorageInstance('sessionStorage', hammerhead.storages.sessionStorageProxy.internal.nativeStorage);
rewriteFunction('clear', function () {
for (const [key] of Object.entries(this)) {
delete this[key];
}
});
rewriteFunction('key', function (keyNum) {
return (Object.entries(this)[keyNum] || [])[0] || null;
});
rewriteFunction('getItem', function (key) {
return this.internal.nativeStorage[toRealStorageKey(key)] || null;
});
rewriteFunction('setItem', function (key, value) {
if (key) {
this.internal.nativeStorage[toRealStorageKey(key)] = value;
}
});
rewriteFunction('removeItem', function (key) {
delete this.internal.nativeStorage[toRealStorageKey(key)];
});
}
function hookHammerheadStartOnce(callback) {
var originalStart = hammerhead.__proto__.start;
hammerhead.__proto__.start = function () {
originalStart.apply(this, arguments);
hammerhead.__proto__.start = originalStart;
callback();
};
}
})();