442 lines
11 KiB
JavaScript
442 lines
11 KiB
JavaScript
const config = {
|
|
|
|
}
|
|
|
|
class Cyclone {
|
|
constructor() {
|
|
this.tmp = location.pathname.split('/service')[1]
|
|
|
|
this.tmp = this.tmp.substring(1, this.tmp.length);
|
|
this.tmp = this.tmp.replace("http://", '')
|
|
this.tmp = this.tmp.replace("https://", '')
|
|
this.tmp = this.tmp.replace("http:/", '')
|
|
this.tmp = this.tmp.replace("https:/", '')
|
|
this.tmp = location.protocol + "//" + this.tmp
|
|
|
|
document._location = new URL(this.tmp);
|
|
var l = new URL(this.tmp);
|
|
|
|
this.url = new URL(document._location.href);
|
|
|
|
this.prefix = location.pathname.split('/')[1]
|
|
this.bareEndpoint = location.host + "/" + this.prefix
|
|
|
|
if (this.url.pathname == "/") {
|
|
this.paths = ['/']
|
|
} else {
|
|
this.paths = this.url.pathname.split('/')
|
|
}
|
|
this.host = 'https://' + this.url.host
|
|
|
|
this.targetAttrs = ['href', 'src', 'action', 'srcdoc', 'srcset'];
|
|
|
|
if (!document.cycloneInjected) {
|
|
console.log("Cyclone Injected with paths of:", this.paths, this.url.pathname)
|
|
document.cycloneInjected = true
|
|
}
|
|
|
|
const LocationHandler = {
|
|
get(target, prop, reciver) {
|
|
return document._location[prop]
|
|
},
|
|
set(target, prop, val) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
//const locProxy = new Proxy(document.location, LocationHandler)
|
|
Object.defineProperty(document, '_location', {
|
|
writable: true,
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: l
|
|
});
|
|
Object.defineProperty(window.document, '_location', {
|
|
writable: true,
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: l
|
|
});
|
|
Object.defineProperty(window, '_location', {
|
|
writable: true,
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: l
|
|
});
|
|
}
|
|
|
|
rewriteUrl(link) {
|
|
if (!link) {
|
|
link = "";
|
|
}
|
|
|
|
var rewritten;
|
|
|
|
if (link.startsWith('https://') || link.startsWith('http://') || link.startsWith('//')) {
|
|
if (link.startsWith('//')) {
|
|
rewritten = 'https:' + link;
|
|
} else {
|
|
rewritten = link;
|
|
};
|
|
} else {
|
|
if (link.startsWith('.')) {
|
|
let offset = 1;
|
|
if (link.startsWith('..')) {
|
|
offset = 2;
|
|
}
|
|
let file = link.substr(link.indexOf('.') + 1 + offset, link.length)
|
|
|
|
rewritten = this.url.hostname + file
|
|
} else {
|
|
if (link.startsWith('/')) {
|
|
rewritten = this.host + link
|
|
} else {
|
|
rewritten = this.host + '/' + link;
|
|
}
|
|
}
|
|
}
|
|
|
|
var exceptions = ['about:', 'mailto:', 'javascript:', 'data:']
|
|
let needstowrite = true;
|
|
for (let i = 0; i < exceptions.length; i++) {
|
|
if (link.startsWith(exceptions[i])) {
|
|
needstowrite = false
|
|
}
|
|
}
|
|
|
|
|
|
if (needstowrite) {
|
|
rewritten = location.protocol + '//' + this.bareEndpoint + '/' + rewritten
|
|
return rewritten;
|
|
} else {
|
|
return link;
|
|
}
|
|
}
|
|
|
|
rewriteSrcset(sample) {
|
|
return sample.split(',').map(e => {
|
|
return (e.split(' ').map(a => {
|
|
if (a.startsWith('http') || (a.startsWith('/') && !a.startsWith(this.prefix))) {
|
|
var url = this.rewriteUrl(url);
|
|
}
|
|
return a.replace(a, (url || a))
|
|
}).join(' '))
|
|
}).join(',')
|
|
}
|
|
}
|
|
|
|
// Rewriting of data types
|
|
|
|
// CSS
|
|
class CSSRewriter extends Cyclone {
|
|
rewriteCSS(tag) {
|
|
var styles = window.getComputedStyle(tag)
|
|
var _values = styles['_values']
|
|
|
|
var prop = styles.getPropertyValue('background-image')
|
|
var name = "background-image"
|
|
|
|
if (prop == "") {
|
|
if (!styles.getPropertyValue('background') == "") {
|
|
prop = styles.getPropertyValue('background')
|
|
name = "background"
|
|
} else {
|
|
name = "";
|
|
prop = "";
|
|
}
|
|
}
|
|
|
|
if (prop.includes("url(")) {
|
|
var start = prop.indexOf('url(') + 4
|
|
var end = prop.indexOf(')') - 4
|
|
|
|
var url = prop.substring(start, end).toString('ascii');
|
|
|
|
if (url.startsWith(location.origin)) {
|
|
url = url.split(location.origin)
|
|
} else {
|
|
url = url.slice(url.indexOf(location.origin));
|
|
}
|
|
|
|
url = this.rewriteUrl(url)
|
|
tag.style[name] = url
|
|
}
|
|
}
|
|
}
|
|
|
|
// JS
|
|
|
|
class JavaScriptRewriter extends Cyclone {
|
|
constructor(proxy) {
|
|
super();
|
|
//Proxied methods
|
|
this.setAttrCy = HTMLElement.prototype.setAttribute;
|
|
this.getAttrCy = HTMLElement.prototype.getAttribute;
|
|
this.proxy = proxy
|
|
}
|
|
|
|
rewriteJavascript(js) {
|
|
var javascript = js.replace(/window\.location/g, 'window._dlocation')
|
|
javascript = javascript.replace(/document\.location/g, 'document._dlocation')
|
|
javascript = javascript.replace(/location\./g, '_location.')
|
|
return javascript
|
|
}
|
|
|
|
setAttribute(attr, value, mode) {
|
|
const setAttrCy = HTMLElement.prototype.setAttribute;
|
|
|
|
if (mode) {
|
|
this.setAttrCy.call(this, attr, value);
|
|
} else {
|
|
var url = attr
|
|
if (cyclone.targetAttrs.includes(attr)) {
|
|
url = cyclone.rewriteUrl(url);
|
|
}
|
|
|
|
setAttrCy.call(this, attr, value);
|
|
}
|
|
}
|
|
|
|
getAttribute(attrN, mode) {
|
|
const getAttrCy = HTMLElement.prototype.getAttribute;
|
|
|
|
if (mode) {
|
|
return getAttrCy.call(this, attrN);
|
|
} else {
|
|
var val = getAttrCy.call(this, attrN);
|
|
if (cyclone.targetAttrs.includes(attrN)) {
|
|
val = getAttrCy.call(this, 'data-origin-' + attrN);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
}
|
|
}
|
|
|
|
// HTML
|
|
class HTMLRewriter extends Cyclone {
|
|
rewriteElement(element) {
|
|
var targetAttrs = this.targetAttrs;
|
|
var attrs;
|
|
try {
|
|
attrs = [...element.attributes || {}].reduce((attrs, attribute) => {
|
|
attrs[attribute.name] = attribute.value;
|
|
return attrs;
|
|
}, {});
|
|
} catch {
|
|
attrs = {};
|
|
}
|
|
|
|
if (element.__proto__.getAttribute) {
|
|
var jsWrite = new JavaScriptRewriter();
|
|
var elementAttributes = [];
|
|
|
|
for (var i = 0; i < targetAttrs.length; i++) {
|
|
var attr = targetAttrs[i]
|
|
var attrName = Object.keys(attrs)[i];
|
|
var data = {
|
|
name: attr,
|
|
value: element.getAttribute('data-origin-' + attr, 'cyclone') || element.getAttribute(attr, 'cyclone')
|
|
}
|
|
|
|
if (data.value) {
|
|
elementAttributes.push(data);
|
|
}
|
|
|
|
if (element.nonce) {
|
|
element.setAttribute('nononce', element.nonce, '')
|
|
element.removeAttribute('nonce')
|
|
}
|
|
if (element.integrity) {
|
|
element.setAttribute('nointegrity', element.integrity, '')
|
|
element.removeAttribute('integrity')
|
|
}
|
|
|
|
if (element.tagName == "script") {
|
|
if (!element.getAttribute('src')) {
|
|
var jsRewrite = new JavaScriptRewriter();
|
|
element.innerHTML = jsRewrite.rewriteJavascript(element.innerHTML)
|
|
}
|
|
}
|
|
|
|
// Css
|
|
var cssRewrite = new CSSRewriter();
|
|
cssRewrite.rewriteCSS(element)
|
|
}
|
|
|
|
for (var i = 0; i < elementAttributes.length; i++) {
|
|
var attr = elementAttributes[i];
|
|
var attrName = attr.name;
|
|
var value = attr.value;
|
|
|
|
var bareValue = this.rewriteUrl(value);
|
|
if (attrName == "srcset" || attrName == 'srcset') {
|
|
bareValue = this.rewriteSrcset(value);
|
|
}
|
|
|
|
element.setAttribute(attrName, bareValue);
|
|
element.setAttribute("data-origin-" + attrName, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
rewriteDocument() {
|
|
var docElements = document.querySelectorAll('*');
|
|
for (var i = 0; i < docElements.length; i++) {
|
|
var element = docElements[i];
|
|
|
|
this.rewriteElement(element)
|
|
}
|
|
}
|
|
|
|
rewriteiFrame(iframe) {
|
|
var frameDoc = (iframe.contentWindow || iframe.contentDocument || iframe.document);
|
|
|
|
let tags = frameDoc.querySelectorAll('*')
|
|
|
|
for (var i = 0; i < tags.length; i++) {
|
|
var tag = tags[i]
|
|
this.rewriteElement(tag)
|
|
}
|
|
}
|
|
}
|
|
|
|
const cyclone = new Cyclone();
|
|
|
|
const htmlRewriter = new HTMLRewriter();
|
|
|
|
const FetchIntercept = window.fetch;
|
|
window.fetch = async (...args) => {
|
|
let [resource, config] = args;
|
|
resource = cyclone.rewriteUrl(resource);
|
|
|
|
const response = await FetchIntercept(resource, config);
|
|
return response;
|
|
}
|
|
|
|
const MessageIntercept = window.postMessage;
|
|
const messageInterceptionFunc = (...args) => {
|
|
let [message, target, config] = args;
|
|
target = cyclone.rewriteUrl(target);
|
|
|
|
const response = MessageIntercept(message, target, config);
|
|
console.log(response);
|
|
return response;
|
|
}
|
|
|
|
Object.defineProperty(window, 'postMessage', {
|
|
writable: false,
|
|
value: messageInterceptionFunc
|
|
}
|
|
)
|
|
|
|
var CWOriginal = Object.getOwnPropertyDescriptor(window.HTMLIFrameElement.prototype, 'contentWindow')
|
|
|
|
Object.defineProperty(window.HTMLIFrameElement.prototype, 'contentWindow', {
|
|
get() {
|
|
var iWindow = CWOriginal.get.call(this);
|
|
htmlRewriter.rewriteiFrame(iWindow);
|
|
|
|
return iWindow
|
|
},
|
|
set() {
|
|
return false;
|
|
}
|
|
})
|
|
|
|
|
|
const open = XMLHttpRequest.prototype.open;
|
|
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
url = cyclone.rewriteUrl(url)
|
|
|
|
return open.call(this, method, url, ...rest);
|
|
};
|
|
|
|
var oPush = window.history.pushState;
|
|
var oPlace = window.history.replaceState;
|
|
|
|
function CycloneStates(dat, unused, url) {
|
|
var cyUrl = cyclone.rewriteUrl(url);
|
|
|
|
oPush.call(this, dat, unused, cyUrl);
|
|
}
|
|
|
|
window.history.pushState = CycloneStates
|
|
window.history.replaceState = CycloneStates
|
|
history.pushState = CycloneStates
|
|
history.replaceState = CycloneStates
|
|
|
|
const OriginalWebsocket = window.WebSocket
|
|
const ProxiedWebSocket = function() {
|
|
const ws = new OriginalWebsocket(...arguments)
|
|
|
|
const originalAddEventListener = ws.addEventListener
|
|
const proxiedAddEventListener = function() {
|
|
if (arguments[0] === "message") {
|
|
const cb = arguments[1]
|
|
arguments[1] = function() {
|
|
var origin = arguments[0].origin
|
|
arguments[0].origin = cyclone.rewriteUrl(origin);
|
|
|
|
return cb.apply(this, arguments)
|
|
}
|
|
}
|
|
return originalAddEventListener.apply(this, arguments)
|
|
}
|
|
ws.addEventListener = proxiedAddEventListener
|
|
|
|
Object.defineProperty(ws, "onmessage", {
|
|
set(func) {
|
|
return proxiedAddEventListener.apply(this, [
|
|
"message",
|
|
func,
|
|
false
|
|
]);
|
|
}
|
|
});
|
|
return ws;
|
|
};
|
|
|
|
window.WebSocket = ProxiedWebSocket;
|
|
|
|
const nwtb = window.open
|
|
|
|
function openNewTab(url, target, features) {
|
|
url = cyclone.rewriteUrl(url)
|
|
nwtb(url, target, features)
|
|
}
|
|
|
|
window.open = openNewTab;
|
|
|
|
window.onload = function() {
|
|
for (var i = 0; i < 50; i++) {
|
|
setTimeout(() => {
|
|
htmlRewriter.rewriteDocument();
|
|
}, 500)
|
|
}
|
|
}
|
|
|
|
let mutationE = new MutationObserver((mutationList, observer) => {
|
|
for (const mutation of mutationList) {
|
|
mutation.addedNodes.forEach(node => {
|
|
htmlRewriter.rewriteElement(node);
|
|
});
|
|
}
|
|
}).observe(document, {
|
|
childList: true,
|
|
subtree: true
|
|
})
|
|
|
|
//For intercepting all requests
|
|
if (!document.serviceWorkerRegistered) {
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', function() {
|
|
navigator.serviceWorker.register(location.origin + '/cySw.js').then(function(registration) {
|
|
console.log('Service worker registered with scope: ', registration.scope);
|
|
}, function(err) {
|
|
console.log('ServiceWorker registration failed: ', err);
|
|
});
|
|
});
|
|
}
|
|
document.serviceWorkerRegistered = true
|
|
}
|