Heroku/Repl.it fix?

This commit is contained in:
QuiteAFancyEmerald 2022-02-09 22:33:23 -08:00
parent 73cdcb3186
commit 2cbcaebca8
44 changed files with 1097 additions and 35748 deletions

View file

@ -108,6 +108,7 @@ const proxy = new corrosion({
prefix: config.prefix || '/search/',
codec: config.codec || 'xor',
ws: config.ws,
forceHttps: true,
requestMiddleware: [
corrosion.middleware.blacklist(blacklist, 'Service not allowed due to bot protection! Make sure you are not trying to verify on a proxy.'),
],

View file

@ -1,69 +0,0 @@
const { overrideAccessors } = require("./utils");
function createAttributeRewriter(ctx) {
if (ctx.serviceWorker) return () => null;
const {
HTMLMediaElement,
HTMLScriptElement,
HTMLAudioElement,
HTMLVideoElement,
HTMLInputElement,
HTMLEmbedElement,
HTMLTrackElement,
HTMLAnchorElement,
HTMLIFrameElement,
HTMLAreaElement,
HTMLLinkElement,
HTMLBaseElement,
HTMLFormElement,
HTMLImageElement,
HTMLSourceElement,
} = ctx.window;
function rewriteAttribute(elem, attr, handler) {
if (Array.isArray(elem)) {
elem.forEach(elem => rewriteAttribute(elem, attr, handler));
return true;
};
if (!elem.prototype || !elem.prototype.hasOwnProperty(attr)) return;
const proto = elem.prototype;
overrideAccessors(proto, attr, {
getter: (target, that) => {
const val = target.call(that);
switch(handler) {
case 'url':
return ctx.url.unwrap(val, ctx.meta);
case 'srcset':
return ctx.html.unsrcset(val, ctx.meta);
case 'delete':
return ctx.originalFn.elementGetAttribute.call(that, `corrosion-attr`) || '';
default:
return val;
};
},
setter: (target, that, [ val ]) => {
switch(handler) {
case 'url':
return target.call(that, ctx.url.wrap(val, ctx.meta));
case 'srcset':
return target.call(that, ctx.html.srcset(val, ctx.meta));
case 'delete':
ctx.originalFn.elementSetAttribute.call(that, `corrosion-attr`, val);
return val;
default:
return target.call(that, val);
};
},
});
return true;
};
return function rewriteAttributes() {
rewriteAttribute([ HTMLScriptElement, HTMLMediaElement, HTMLImageElement, HTMLAudioElement, HTMLVideoElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement ], 'src', 'url');
rewriteAttribute(HTMLFormElement, 'action', 'url');
rewriteAttribute([ HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement ], 'href', 'url');
rewriteAttribute([ HTMLImageElement, HTMLSourceElement ], 'srcset', 'srcset');
rewriteAttribute(HTMLScriptElement, 'integrity', 'delete');
return true;
};
};
module.exports = createAttributeRewriter;

View file

@ -1,81 +1,271 @@
const { overrideAccessors, overrideFunction } = require("./utils");
function createDocumentRewriter(ctx) {
if (ctx.serviceWorker) return () => false;
if (ctx.docProto.hasOwnProperty('cookie')) ctx.originalAccessors.DocumentCookie = Object.getOwnPropertyDescriptor(ctx.docProto, 'cookie');
if (ctx.docProto.hasOwnProperty('domain')) ctx.originalAccessors.DocumentDomain = Object.getOwnPropertyDescriptor(ctx.docProto, 'domain');
if (ctx.docProto.hasOwnProperty('title')) ctx.originalAccessors.DocumentTitle = Object.getOwnPropertyDescriptor(ctx.docProto, 'title');
if (ctx.docProto.hasOwnProperty('documentURI')) ctx.originalAccessors.DocumentURI = Object.getOwnPropertyDescriptor(ctx.docProto, 'documentURI');
if (ctx.docProto.hasOwnProperty('URL')) ctx.originalAccessors.DocumentURL = Object.getOwnPropertyDescriptor(ctx.docProto, 'URL');
if (ctx.docProto.write) ctx.originalFn.documentWrite = ctx.docProto.write;
if (ctx.docProto.writeln) ctx.originalFn.documentWriteln = ctx.docProto.writeln;
function rewriteDomain() {
let spoof = ctx.location.hostname;
if (ctx.originalAccessors.DocumentDomain) {
overrideAccessors(ctx.docProto, 'domain', {
getter: () => spoof,
setter: (target, that, [ val ]) => {
if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) target.call(that, '');
return spoof = val;
return function rewriteDocument() {
if (ctx.serviceWorker) return;
const {
HTMLMediaElement,
HTMLScriptElement,
HTMLAudioElement,
HTMLVideoElement,
HTMLInputElement,
HTMLEmbedElement,
HTMLTrackElement,
HTMLAnchorElement,
HTMLIFrameElement,
HTMLAreaElement,
HTMLLinkElement,
HTMLBaseElement,
HTMLFormElement,
HTMLImageElement,
HTMLSourceElement,
} = ctx.window;
const cookie = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'cookie');
const domain = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'domain');
const title = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'title');
const baseURI = Object.getOwnPropertyDescriptor(ctx.window.Node.prototype, 'baseURI');
const cookieEnabled = Object.getOwnPropertyDescriptor(ctx.window.Navigator.prototype, 'cookieEnabled');
let spoofTitle = '';
let spoofDomain = ctx.location.hostname;
if (ctx.window.Document.prototype.write) {
ctx.window.Document.prototype.write = new Proxy(ctx.window.Document.prototype.write, {
apply: (target, that , args) => {
if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ];
return Reflect.apply(target, that, args);
},
});
};
return true;
};
function rewriteCookie() {
if (ctx.originalAccessors.DocumentCookie) {
overrideAccessors(ctx.docProto, 'cookie', {
getter: (target, that) => ctx.config.cookie ? ctx.cookies.decode(target.call(that), ctx.meta) : '',
setter: (target, that, [ val ]) => target.call(that, ctx.config.cookie ? ctx.cookies.encode(val, ctx.meta) : ''),
if (ctx.window.Document.prototype.hasOwnProperty('cookie')) {
Object.defineProperty(ctx.window.Document.prototype, 'cookie', {
get: new Proxy(cookie.get, {
apply: (target, that, args) => {
const cookies = Reflect.apply(target, that, args);
return ctx.config.cookie ? ctx.cookies.decode(cookies, ctx.meta) : '';
},
}),
set: new Proxy(cookie.set, {
apply: (target, that, [ val ]) => {
return Reflect.apply(target, that, [ ctx.config.cookie ? ctx.cookies.encode(val, ctx.meta) : '' ]);
},
}),
});
};
return true;
};
function rewriteTitle() {
let spoof = '';
if (ctx.originalAccessors.DocumentTitle && ctx.config.title) {
overrideAccessors(ctx.docProto, 'title', {
getter: () => spoof,
setter: (target, that, [ val ]) => spoof = val,
if (ctx.window.Document.prototype.writeln) {
ctx.window.Document.prototype.writeln = new Proxy(ctx.window.Document.prototype.writeln, {
apply: (target, that , args) => {
if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ];
return Reflect.apply(target, that, args);
},
});
};
return true;
};
function rewriteUrl() {
if (ctx.originalAccessors.DocumentURL) {
overrideAccessors(ctx.docProto, 'URL', {
getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta),
if (ctx.window.Element.prototype.setAttribute) {
ctx.window.Element.prototype.setAttribute = new Proxy(ctx.window.Element.prototype.setAttribute, {
apply: (target, that, args) => {
if (args[0] && args[1]) {
const handler = ctx.html.attributeRoute({
name: args[0],
value: args[1],
node: that,
});
switch(handler) {
case 'url':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
//if (that.tagName == 'SCRIPT' && args[0] == 'src') flags.push('js');
args[1] = ctx.url.wrap(args[1], ctx.meta);
break;
case 'srcset':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
args[1] = ctx.html.srcset(args[1], ctx.meta);
break;
case 'css':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
args[1] = ctx.css.process(args[1], { ...ctx.meta, context: 'declarationList' });
break;
case 'html':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
args[1] = ctx.html.process(args[1], ctx.meta);
break;
case 'delete':
return Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
};
};
return Reflect.apply(target, that, args);
},
});
};
if (ctx.originalAccessors.DocumentURI) {
overrideAccessors(ctx.docProto, 'documentURI', {
getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta),
if (ctx.window.Element.prototype.getAttribute) {
ctx.window.Element.prototype.getAttribute = new Proxy(ctx.window.Element.prototype.getAttribute, {
apply: (target, that, args) => {
if (args[0] && that.hasAttribute(`corrosion-${args[0]}`)) args[0] = `corrosion-${args[0]}`;
return Reflect.apply(target, that, args);
},
});
};
ctx.window.CSSStyleDeclaration.prototype.setProperty = new Proxy(ctx.window.CSSStyleDeclaration.prototype.setProperty, {
apply: (target, that, args) => {
if (args[1]) args[1] = ctx.css.process(args[1], { context: 'value', ...ctx.meta, });
return Reflect.apply(target, that, args);
},
});
if (ctx.window.Audio) {
ctx.window.Audio = new Proxy(ctx.window.Audio, {
construct: (target, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return Reflect.construct(target, args);
},
});
};
return true;
};
function rewriteWrite() {
if (ctx.originalFn.documentWrite) {
overrideFunction(ctx.docProto, 'write', (target, that, args) => {
if (args.length) args = [ ctx.html.process(args.join(''), { meta: ctx.meta }) ];
return target.apply(that, args);
[
'innerHTML',
'outerHTML',
].forEach(html => {
const descriptor = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, html);
Object.defineProperty(ctx.window.Element.prototype, html, {
get: new Proxy(descriptor.get, {
apply: (target, that, args) => {
const body = Reflect.apply(target, that, args);
if (!body || html == 'innerHTML' && that.tagName == 'SCRIPT') return body;
return ctx.html.source(body, ctx.meta);
},
}),
set: new Proxy(descriptor.set, {
apply(target, that, [ val ]) {
return Reflect.apply(target, that, [ val ? ctx.html.process(val.toString(), ctx.meta) : val, ]);
},
}),
});
};
if (ctx.originalFn.documentWriteln) {
overrideFunction(ctx.docProto, 'writeln', (target, that, args) => {
if (args.length) args = [ ctx.html.process(args.join(''), { meta: ctx.meta }) ];
return target.apply(that, args);
});
[
['background', 'background'],
['backgroundImage', 'background-image'],
['listStyleImage', 'list-style-image'],
].forEach(([key, cssProperty]) => {
Object.defineProperty(ctx.window.CSS2Properties ? ctx.window.CSS2Properties.prototype : ctx.window.CSSStyleDeclaration.prototype, key, {
get() {
return this.getPropertyValue(cssProperty);
},
set(val) {
return this.setProperty(cssProperty, val);
},
});
});
Object.defineProperty(ctx.window.Document.prototype, 'domain', {
get: new Proxy(domain.get, {
apply: () => spoofDomain,
}),
set: new Proxy(domain.set, {
apply: (target, that, [ val ]) => {
if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) return Reflect.apply(target, that, ['']);
return spoofDomain = val;
},
}),
});
if (ctx.config.title) Object.defineProperty(ctx.window.Document.prototype, 'title', {
get: new Proxy(title.get, {
apply: () => spoofTitle,
}),
set: new Proxy(title.set, {
apply: (target, that, [ val ]) => spoofTitle = val,
}),
});
Object.defineProperty(ctx.window.Navigator.prototype, 'cookieEnabled', {
get: new Proxy(cookieEnabled.get, {
apply: () => ctx.config.cookie,
}),
});
Object.defineProperty(ctx.window.Node.prototype, 'baseURI', {
get: new Proxy(baseURI.get, {
apply: (target, that, args) => {
const val = Reflect.apply(target, that, args);
return val.startsWith(ctx.meta.origin) ? ctx.url.unwrap(val, ctx.meta) : val;
},
}),
});
[
{
elements: [ HTMLScriptElement, HTMLMediaElement, HTMLImageElement, HTMLAudioElement, HTMLVideoElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement],
properties: ['src'],
handler: 'url',
},
{
elements: [ HTMLFormElement ],
properties: ['action'],
handler: 'url',
},
{
elements: [ HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement ],
properties: ['href'],
handler: 'url',
},
{
elements: [ HTMLImageElement, HTMLSourceElement ],
properties: ['srcset'],
handler: 'srcset',
},
{
elements: [ HTMLScriptElement ],
properties: ['integrity'],
handler: 'delete',
},
{
elements: [ HTMLIFrameElement ],
properties: ['contentWindow'],
handler: 'window',
},
].forEach(entry => {
entry.elements.forEach(element => {
if (!element) return;
entry.properties.forEach(property => {
if (!element.prototype.hasOwnProperty(property)) return;
const descriptor = Object.getOwnPropertyDescriptor(element.prototype, property);
Object.defineProperty(element.prototype, property, {
get: descriptor.get ? new Proxy(descriptor.get, {
apply: (target, that, args) => {
let val = Reflect.apply(target, that, args);
let flags = [];
switch(entry.handler) {
case 'url':
//if (that.tagName == 'SCRIPT' && property == 'src') flags.push('js');
val = ctx.url.unwrap(val, ctx.meta);
break;
case 'srcset':
val = ctx.html.unsrcset(val, ctx.meta);
break;
case 'delete':
val = that.getAttribute(`corrosion-${property}`);
break;
case 'window':
try {
if (!val.$corrosion) {
val.$corrosion = new ctx.constructor({ ...ctx.config, window: val, });
val.$corrosion.init();
val.$corrosion.meta = ctx.meta;
};
} catch(e) {};
};
return val;
},
}) : undefined,
set: descriptor.set ? new Proxy(descriptor.set, {
apply(target, that, [ val ]) {
let newVal = val;
switch(entry.handler) {
case 'url':
newVal = ctx.url.wrap(newVal, ctx.meta);
break;
case 'srcset':
newVal = ctx.html.srcset(newVal, ctx.meta);
break;
case 'delete':
that.setAttribute(property, newVal);
return newVal;
};
return Reflect.apply(target, that, [ newVal ]);
},
}) : undefined,
});
});
});
};
};
return function rewriteDocument() {
rewriteCookie();
rewriteUrl();
rewriteTitle();
rewriteDomain();
rewriteWrite();
return true;
});
};
};
module.exports = createDocumentRewriter;

View file

@ -1,113 +0,0 @@
const { overrideAccessors, overrideFunction, overrideConstructor } = require("./utils");
function createDomRewriter(ctx) {
if (ctx.serviceWorker) return () => null;
if (ctx.window.Node && ctx.window.Node.prototype) {
ctx.originalAccessors.nodeBaseURI = Object.getOwnPropertyDescriptor(ctx.window.Node.prototype, 'baseURI');
ctx.originalAccessors.nodeTextContent = Object.getOwnPropertyDescriptor(ctx.window.Node.prototype, 'textContent');
};
if (ctx.window.Element && ctx.window.Element.prototype) {
ctx.originalAccessors.elementInnerHtml = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, 'innerHTML');
ctx.originalAccessors.elementOuterHtml = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, 'outerHTML');
ctx.originalFn.elementSetAttribute = ctx.window.Element.prototype.setAttribute;
ctx.originalFn.elementGetAttribute = ctx.window.Element.prototype.getAttribute;
ctx.originalFn.elementHasAttribute = ctx.window.Element.prototype.hasAttribute;
};
if (ctx.window.Audio) ctx.originalFn.Audio = ctx.window.Audio;
function rewriteTextContent() {
if (ctx.originalAccessors.nodeTextContent) {
overrideAccessors(ctx.window.Node.prototype, 'textContent', {
setter: (target, that, [ val ]) => {
switch(that.tagName) {
case 'SCRIPT':
val = ctx.processScript(val);
break;
case 'STYLE':
val = ctx.processStyle(val);
break;
};
return target.call(that, val);
},
});
};
};
function rewriteUrl() {
if (ctx.originalAccessors.nodeBaseURI) {
overrideAccessors(ctx.window.Node.prototype, 'baseURI', {
getter: (target, that) => {
const url = target.call(that);
return url.startsWith(ctx.meta.origin) ? ctx.url.unwrap(url, ctx.meta) : url;
},
});
};
};
function rewriteHtml() {
if (ctx.originalAccessors.elementInnerHtml) {
overrideAccessors(ctx.window.Element.prototype, 'innerHTML', {
getter: (target, that) => ['STYLE', 'SCRIPT'].includes(that.tagName) ? target.call(that) : ctx.html.source(target.call(that)),
setter: (target, that, [ val ]) => {
switch(that.tagName) {
case 'STYLE':
val = ctx.processStyle(val);
break;
case 'SCRIPT':
val = ctx.processScript(val);
break;
default:
val = ctx.processHtml(val);
break;
};
return target.call(that, val);
},
});
};
};
function rewriteAttribute() {
if (ctx.originalFn.elementSetAttribute) {
overrideFunction(ctx.window.Element.prototype, 'setAttribute', (target, that, args) => {
if (args[0] && args[1]) {
let data = {
attr: {
name: args[0],
value: args[1],
},
node: that,
meta: ctx.meta,
setAttribute: ctx.originalFn.elementSetAttribute.bind(that),
delete: false,
ctx,
};
const tag = ctx.html.attrs.get(that.tagName.toLowerCase()) || ctx.html.attrs.get('*');
if (tag[data.attr.name]) tag[data.attr.name](that, data);
args[0] = data.attr.name;
args[1] = data.attr.value;
};
return target.apply(that, args);
});
};
if (ctx.originalFn.elementGetAttribute) {
overrideFunction(ctx.window.Element.prototype, 'getAttribute', (target, that, args) => {
if (args[0] && ctx.originalFn.elementHasAttribute.call(that, `corrosion-attr-${args[0]}`)) args[0] = `corrosion-attr-${args[0]}`;
return target.apply(that, args);
});
};
};
function rewriteAudio() {
if (ctx.originalFn.Audio) {
overrideConstructor(ctx.window, 'Audio', (target, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return new target(...args);
});
};
};
return function rewriteDom() {
rewriteUrl();
rewriteTextContent();
rewriteHtml();
rewriteAttribute();
rewriteAudio();
return true;
};
};
module.exports = createDomRewriter;

View file

@ -1,70 +0,0 @@
const { overrideFunction, overrideAccessors, overrideConstructor, wrapFnString } = require("./utils");
function createFunctionRewriter(ctx) {
if (ctx.window.Function && ctx.window.Function.prototype) {
ctx.originalFn.Function = ctx.window.Function;
ctx.originalFn.FunctionToString = ctx.window.Function.prototype.toString;
ctx.originalAccessors.FunctionArguments = Object.getOwnPropertyDescriptor(ctx.window.Function.prototype, 'arguments');
};
function rewriteFn() {
if (ctx.originalFn.Function) {
//const fnProto = ctx.window.Function.prototype;
/*overrideFunction(ctx.window, 'Function', (target, that, args) => {
if (args.length) args[args.length - 1] = ctx.js.process(args[args.length - 1], ctx.meta.url);
return target.apply(that, args);
});*/
/*ctx.window.Function = new Proxy(ctx.window.Function, {
apply: (target, that, args) => {
if (args.length) args[args.length - 1] = ctx.processScript(args[args.length - 1]);
return target.apply(that, args);
},
construct: (target, args) => {
if (args.length) args[args.length - 1] = ctx.processScript(args[args.length - 1]);
return new target(...args);
},
get: (target, prop) => target[prop],
set: (target, prop, val) => target[prop] = val,
});*/
/*
ctx.window.Function = function(...args) {
if (args.length) args[args.length - 1] = ctx.processScript(args[args.length - 1]);
return new ctx.originalFn.Function(...args);
};
ctx.window.Function.prototype = ctx.originalFn.Function.prototype;
ctx.window.Function.prototype.constructor = ctx.window.Function;
wrapFnString(ctx.originalFn.Function, ctx.window.Function);
*/
overrideConstructor(ctx.window, 'Function', (target, args) => {
const old = args[args.length - 1];
if (args.length) args[args.length - 1] = ctx.processScript(args[args.length - 1]);
console.log(old, args[args.length - 1]);
return target.apply(this, args);
});
};
return true;
};
function rewriteFnArguments() {
if (ctx.originalAccessors.FunctionArguments) {
overrideAccessors(ctx.window.Function.prototype, 'arguments', {
getter: (target, that) => target.call(ctx.proxyToOriginal(that)),
});
};
return true;
};
function rewriteFnString() {
if (ctx.originalFn.FunctionToString) {
overrideFunction(ctx.window.Function.prototype, 'toString', (target, that, args) => {
if (that.hasOwnProperty('$corrosion_string')) return that.$corrosion_string;
return target.apply(that, args);
});
};
return true;
};
return function rewriteFunction() {
rewriteFnString();
rewriteFn();
//rewriteFnArguments();
};
};
module.exports = createFunctionRewriter;

View file

@ -1,30 +1,26 @@
const { overrideFunction } = require("./utils");
function createHistoryRewriter(ctx) {
if (ctx.serviceWorker) return () => null;
if (ctx.window.History && ctx.window.History.prototype) {
ctx.originalFn.historyPushstate = ctx.window.History.prototype.pushState;
ctx.originalFn.historyReplacestate = ctx.window.History.prototype.replaceState;
};
function rewritePushReplaceState() {
const handler = (target, that, args) => {
if (args[2]) {
/*if (new URL(args[2], ctx.meta.base).origin != ctx.location.origin) {
args[2] = '';
} else {*/
args[2] = ctx.url.wrap(args[2], ctx.meta);
//};
};
return target.apply(that, args);
}
if (ctx.originalFn.historyPushstate) overrideFunction(ctx.window.History.prototype, 'pushState', handler);
if (ctx.originalFn.historyReplacestate) overrideFunction(ctx.window.History.prototype, 'replaceState', handler);
return true;
};
return function rewriteHistory() {
rewritePushReplaceState();
return true;
if (ctx.serviceWorker) return;
if (ctx.window.History.prototype.pushState) {
ctx.window.History.prototype.pushState = new Proxy(ctx.window.History.prototype.pushState, {
apply: (target, that, args) => {
if (args[2]) args[2] = ctx.url.wrap(args[2], ctx.meta);
const ret = Reflect.apply(target, that, args);
ctx.updateLocation();
return ret;
},
});
};
if (ctx.window.History.prototype.replaceState) {
ctx.window.History.prototype.replaceState = new Proxy(ctx.window.History.prototype.replaceState, {
apply: (target, that, args) => {
if (args[2]) args[2] = ctx.url.wrap(args[2], ctx.meta);
const ret = Reflect.apply(target, that, args);
ctx.updateLocation();
return ret;
},
});
};
};
};
module.exports = createHistoryRewriter;

View file

@ -1,117 +1,91 @@
const { overrideFunction, overrideConstructor, overrideAccessors } = require("./utils");
function createHttpRewriter(ctx) {
if (ctx.window.fetch) ctx.originalFn.fetch = ctx.window.fetch;
if (ctx.window.WebSocket && ctx.window.WebSocket.prototype) ctx.originalFn.WebSocket = ctx.window.WebSocket;
if (ctx.window.Request && ctx.window.Request.prototype) {
ctx.originalFn.Request = ctx.window.Request;
ctx.originalAccessors.RequestUrl = Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url');
};
if (ctx.window.Response && ctx.window.Response.prototype) {
ctx.originalAccessors.ResponseUrl = Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url');
};
if (ctx.window.XMLHttpRequest && ctx.window.XMLHttpRequest.prototype) {
ctx.originalFn.xhrOpen = ctx.window.XMLHttpRequest.prototype.open;
ctx.originalFn.xhrSend = ctx.window.XMLHttpRequest.prototype.send;
ctx.originalAccessors.xhrResponseUrl = Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL');
};
if (ctx.window.EventSource && ctx.window.EventSource.prototype) {
ctx.originalFn.EventSource = ctx.window.EventSource;
ctx.originalAccessors.EventSourceUrl = Object.getOwnPropertyDescriptor(ctx.window.EventSource.prototype, 'url');
};
if (ctx.window.open) ctx.originalFn.open = ctx.window.open;
function rewriteFetch() {
if (ctx.originalFn.Request) {
overrideConstructor(ctx.window, 'Request', (target, args) => {
if (args[0] instanceof ctx.window.Request) return;
if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'] });
return new target(...args);
});
overrideAccessors(ctx.window.Request.prototype, 'url', {
getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta),
});
ctx.proxyToOriginalMp.set(Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url').get, ctx.originalAccessors.RequestUrl);
ctx.proxyToOriginalMp.set(ctx.window.Request, ctx.originalFn.Request);
};
if (ctx.originalAccessors.ResponseUrl) {
overrideAccessors(ctx.window.Response.prototype, 'url', {
getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta),
});
ctx.proxyToOriginalMp.set(Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url').get, ctx.originalAccessors.ResponseUrl);
};
if (ctx.originalFn.fetch) {
overrideFunction(ctx.window, 'fetch', async (target, that, args) => {
if (args[0] instanceof ctx.window.Request) return target.apply(that, args);
if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], query: new URL(args[0], ctx.location.href).origin != ctx.location.origin ? `origin=${ctx.codec.base64.encode(ctx.location.origin)}` : null });
return target.apply(that, args);
});
ctx.proxyToOriginalMp.set(ctx.window.fetch, ctx.originalFn.fetch);
};
return true;
};
function rewriteXhr() {
if (ctx.originalFn.xhrOpen) {
overrideFunction(ctx.window.XMLHttpRequest.prototype, 'open', (target, that, args) => {
if (args[1]) {
args[1] = ctx.url.wrap(args[1], { ...ctx.meta, flags: ['xhr'], query: new URL(args[1], ctx.location.href).origin != ctx.location.origin ? `origin=${ctx.codec.base64.encode(ctx.location.origin)}` : null });
};
return target.apply(that, args);
});
ctx.proxyToOriginalMp.set(ctx.window.XMLHttpRequest.prototype.open, ctx.originalFn.xhrOpen);
};
if (ctx.originalAccessors.xhrResponseUrl) {
overrideAccessors(ctx.window.XMLHttpRequest.prototype, 'responseURL', {
getter: (target, that) => {
const url = target.call(that);
return url ? ctx.url.unwrap(url, ctx.meta) : url;
function createHttpRewriter(ctx = {}) {
return function rewriteHttp() {
if (ctx.window.Request) {
const requestURL = Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url');
ctx.window.Request = new Proxy(ctx.window.Request, {
construct(target, args) {
if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], })
return Reflect.construct(target, args);
},
});
ctx.proxyToOriginalMp.set(Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL').get, ctx.originalAccessors.xhrResponseUrl.get);
};
return true;
};
function rewriteWebSocket() {
if (ctx.originalFn.WebSocket) {
overrideConstructor(ctx.window, 'WebSocket', (target, args) => {
try {
let url = new URL(args[0]);
args[0] = ['wss:', 'ws:'].includes(url.protocol) ? ctx.url.wrap(url.href.replace('ws', 'http'), ctx.meta).replace('http', 'ws') + '?origin=' + ctx.codec.base64.encode(ctx.location.origin) : '';
} catch(e) {
args[0] = '';
};
return new target(...args);
});
};
};
function rewriteOpen() {
if (ctx.originalFn.open) {
overrideFunction(ctx.window, 'open', (target, that, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return target.apply(that, args);
});
ctx.proxyToOriginalMp.set(ctx.window.open, ctx.originalFn.open);
};
return true;
};
function rewriteEventSource() {
if (ctx.originalFn.EventSource) {
overrideConstructor(ctx.window, 'EventSource', (target, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return new target(...args);
});
overrideAccessors(ctx.window.EventSource.prototype, 'url', {
getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta),
Object.defineProperty(ctx.window.Request.prototype, 'url', {
get: new Proxy(requestURL.get, {
apply: (target, that, args) => {
var url = Reflect.apply(target, that, args);
return url ? ctx.url.unwrap(url, ctx.meta) : url;
},
}),
});
};
if (ctx.window.Response) {
const responseURL = Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url');
Object.defineProperty(ctx.window.Response.prototype, 'url', {
get: new Proxy(responseURL.get, {
apply: (target, that, args) => {
var url = Reflect.apply(target, that, args);
return url ? ctx.url.unwrap(url, ctx.meta) : url;
},
}),
});
};
if (ctx.window.open) {
ctx.window.open = new Proxy(ctx.window.open, {
apply: (target, that, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return Reflect.apply(target, that, args)
},
});
};
if (ctx.window.fetch) {
ctx.window.fetch = new Proxy(ctx.window.fetch, {
apply: (target, that, args) => {
if (args[0] instanceof ctx.window.Request) return Reflect.apply(target, that, args);
if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], });
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.Navigator && ctx.window.Navigator.prototype.sendBeacon) {
ctx.window.Navigator.prototype.sendBeacon = new Proxy(ctx.window.Navigator.prototype.sendBeacon, {
apply: (target, that, args) => {
if (args[0]) ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], });
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.XMLHttpRequest) {
const responseURL = Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL');
ctx.window.XMLHttpRequest.prototype.open = new Proxy(ctx.window.XMLHttpRequest.prototype.open, {
apply: (target, that, args) => {
if (args[1]) args[1] = ctx.url.wrap(args[1], { ...ctx.meta, flags: ['xhr'], });
return Reflect.apply(target, that, args);
},
});
Object.defineProperty(ctx.window.XMLHttpRequest.prototype, 'responseURL', {
get: new Proxy(responseURL.get, {
apply: (target, that, args) => {
const url = Reflect.apply(target, that, args);
return url ? ctx.url.unwrap(url, ctx.meta) : url;
},
}),
});
};
if (ctx.window.postMessage) {
ctx.window.postMessage = new Proxy(ctx.window.postMessage, {
apply: (target, that, args) => {
if (!ctx.serviceWorker && args[1]) args[1] = ctx.meta.origin;
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.WebSocket && ctx.config.ws) {
ctx.window.WebSocket = new Proxy(ctx.window.WebSocket, {
construct: (target, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0].toString().replace('ws', 'http'), ctx.meta).replace('http', 'ws') + '?origin=' + ctx.location.origin;
return Reflect.construct(target, args);
},
});
};
return true;
};
return function rewriteHttp() {
rewriteXhr();
rewriteFetch();
rewriteWebSocket();
rewriteOpen();
rewriteEventSource();
return true;
};
};

View file

@ -1,45 +0,0 @@
const { overrideAccessors } = require("./utils");
function createIframeRewriter(ctx) {
if (ctx.serviceWorker) return () => null;
const {
HTMLIFrameElement
} = ctx.window;
function rewriteContentDocument() {
overrideAccessors(HTMLIFrameElement.prototype, 'contentDocument', {
getter: (target, that) => {
const doc = target.call(that);
try {
if (!doc.defaultView.$corrosion) initCorrosion(doc.defaultView);
return doc;
} catch(e) {
return doc;
};
},
});
};
function rewriteContentWindow() {
overrideAccessors(HTMLIFrameElement.prototype, 'contentWindow', {
getter: (target, that) => {
const win = target.call(that);
try {
if (!win.$corrosion) initCorrosion(win);
return win;
} catch(e) {
return win;
};
},
});
};
function initCorrosion(win) {
win.$corrosion = new ctx.constructor({ ...ctx.config, window: win, });
win.$corrosion.init();
win.$corrosion.meta = ctx.meta;
};
return function rewriteIframe() {
rewriteContentWindow();
rewriteContentDocument();
};
};
module.exports = createIframeRewriter;

View file

@ -1,16 +1,8 @@
const createAttributeRewriter = require('./attributes');
const createDocumentRewriter = require('./document');
const createDomRewriter = require('./dom');
const createFunctionRewriter = require('./function');
const createHistoryRewriter = require('./history');
const createHttpRewriter = require('./http');
const createIframeRewriter = require('./iframe');
const createInstrumentation = require('./instrumentation');
const { createLocation } = require('./location');
const createMessageRewriter = require('./message');
const createLocation = require('./location');
const createWorkerRewriter = require('./worker');
const createStyleRewriter = require('./style');
const utils = require('./utils');
const defaultConfig = {
prefix: '/service/',
codec: 'plain',
@ -26,78 +18,178 @@ class Corrosion extends require('../rewrite') {
constructor(config = defaultConfig) {
super(Object.assign(defaultConfig, config));
const corrosion = this;
this.utils = utils;
this.serviceWorker = this.config.serviceWorker;
this.window = config.window;
this.Window = this.serviceWorker ? null : this.window.Window;
this.Document = this.Window ? this.window.Document : null;
this.docProto = (this.Document || {}).prototype;
this.origin = this.window.location.origin;
if (!this.config.window) throw 'Corrosion Error: No window was given.';
this.serviceWorker = this.config.serviceWorker || false;
this.window = this.config.window;
this.document = this.serviceWorker ? this.window.document : {};
this._url = new URL((this.config.url || this.url.unwrap(this.window.location.href, { origin: this.window.location.origin, })));
this.proxyToOriginalMp = new Map();
this.location = createLocation(this, this._url);
this.originalXhr = this.window.XMLHttpRequest;
this.meta = {
origin: this.origin,
origin: this.window.location.origin,
get base() {
if (corrosion.serviceWorker) return corrosion._url;
return corrosion.window.document.baseURI != corrosion.location.href ? new URL(corrosion.window.document.baseURI) : corrosion._url;
},
url: this._url,
};
this.originalFn = {};
this.originalAccessors = {};
this.rewriteFunction = createFunctionRewriter(this);
this.location = createLocation(this, this._url);
this.rewriteHttp = createHttpRewriter(this);
this.rewriteDocument = createDocumentRewriter(this);
this.rewriteDom = createDomRewriter(this);
this.rewriteAttributes = createAttributeRewriter(this);
this.rewriteIframes = createIframeRewriter(this);
this.rewriteMessage = createMessageRewriter(this);
this.rewriteHistory = createHistoryRewriter(this);
this.rewriteWorker = createWorkerRewriter(this);
this.rewriteStyle = createStyleRewriter(this);
this.instrumentation = createInstrumentation(this);
this.windowEvents = [];
this.window.__get$ = this.instrumentation.get;
this.window.__get$Parent = this.instrumentation.getParent;
this.window.__get$Top = this.instrumentation.getTop;
this.window.__get$Loc = this.instrumentation.getLoc;
this.window.__set$ = this.instrumentation.set;
this.window.__set$Loc = this.instrumentation.setLoc;
this.window.__processScript = this.processScript.bind(this);
this.window.__processStyle = this.processStyle.bind(this);
this.window.__processHtml = this.processHtml.bind(this);
this.window.__call$ = this.call.bind(this);
};
processStyle(val, context) {
return this.css.process(val, { meta: this.meta, context });
if (!this.serviceWorker && this.window.document.currentScript) this.window.document.currentScript.remove();
};
processScript(val) {
return this.js.process(val, this.meta.url.href);
get parent() {
if (this.serviceWorker) return false;
try {
return this.window.parent.$corrosion ? this.window.parent : this.window;
} catch(e) {
return this.window;
};
};
processHtml(val, document = false) {
if (!val) return val;
return this.html.process(val, { document, meta: this.meta, });
};
call(obj, method, args) {
return obj[method](...args);
};
proxyToOriginal(input) {
return this.proxyToOriginalMp.get(input) || input;
get top() {
if (this.serviceWorker) return false;
try {
return this.window.top.$corrosion ? this.window.top : this.parent;
} catch(e) {
return this.parent;
};
};
init() {
this.rewriteFunction();
this.rewriteHttp();
this.rewriteDocument();
this.rewriteDom();
this.rewriteAttributes();
this.rewriteIframes();
this.rewriteMessage();
this.rewriteHistory();
this.rewriteWorker();
this.rewriteStyle();
console.log('Corrosion initiated.');
this.window.Location = createLocation.Location;
this.window.$corrosionGet$ = this.get$.bind(this);
this.window.$corrosionSet$ = this.set$.bind(this);
this.window.$corrosionGet$m = this.get$m.bind(this);
this.window.$corrosionSet$m = this.set$m.bind(this);
this.window.$corrosionCall$m = this.call$m.bind(this);
};
get$m(obj, key) {
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) {
return this.parent.$corrosion.get$m(this.parent, key);
};
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) {
return this.top.$corrosion.get$m(this.top, key);
};
if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') return this.location;
if (!this.serviceWorker && obj == this.window && key == 'parent' && this.window != this.window.parent) return this.parent;
if (!this.serviceWorker && obj == this.window && key == 'top' && this.window != this.window.top) return this.top;
return obj[key];
};
set$m(obj, key, val, operator) {
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) {
return this.parent.$corrosion.set$m(this.parent, key, val, operator);
};
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) {
return this.top.$corrosion.set$m(this.top, key, val, operator);
};
if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') obj = this;
switch(operator) {
case '+=':
return obj[key] += val;
case '-=':
return obj[key] -= val;
case '*=':
return obj[key] *= val;
case '/=':
return obj[key] /= val;
case '%=':
return obj[key] %= val;
case '**=':
return obj[key] **= val;
case '<<=':
return obj[key] <<= val;
case '>>=':
return obj[key] >>= val;
case '>>>=':
return obj[key] >>>= val;
case '&=':
return obj[key] &= val;
case '^=':
return obj[key] ^= val;
case '|=':
return obj[key] |= val;
case '&&=':
return obj[key] &&= val;
case '||=':
return obj[key] ||= val;
case '??=':
return obj[key] ??= val;
case '++':
return obj[key]++;
case '--':
return obj[key]--;
case '=':
default:
return obj[key] = val;
};
};
call$m(obj, key, args) {
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) {
return this.parent.$corrosion.call$m(this.parent, key, args);
};
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) {
return this.top.$corrosion.call$m(this.top, key, args);
};
return obj[key](...args);
};
get$(obj) {
if (obj == this.window.location) return this.location;
if (!this.serviceWorker && obj == this.window.parent) return this.parent;
if (!this.serviceWorker && obj == this.window.top) return this.top;
return obj;
};
set$(obj, val, operator) {
if (obj == this.window.location) return this.set$(this.location, val, operator);
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) return this.parent.set$(this.parent, val, operator);
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) return this.top.set$(this.top, val, operator);
switch(operator) {
case '+=':
return obj += val;
case '-=':
return obj -= val;
case '*=':
return obj *= val;
case '/=':
return obj /= val;
case '%=':
return obj %= val;
case '**=':
return obj **= val;
case '<<=':
return obj <<= val;
case '>>=':
return obj >>= val;
case '>>>=':
return obj >>>= val;
case '&=':
return obj &= val;
case '^=':
return obj ^= val;
case '|=':
return obj |= val;
case '&&=':
return obj &&= val;
case '||=':
return obj ||= val;
case '??=':
return obj ??= val;
case '++':
return obj++;
case '--':
return obj--;
case '=':
default:
return obj = val;
};
};
updateLocation() {
this._url = new URL(this.url.unwrap(this.window.location.href, this.meta));
this.location = createLocation(this, this._url);
};
};
globalThis.Corrosion = Corrosion;

View file

@ -1,94 +0,0 @@
function createInstrumentation(ctx) {
function getParent(input) {
try {
if (!ctx.serviceWorker && input == ctx.window.parent && !ctx.window.parent.$corrosion) return ctx.window;
return input;
} catch(e) {
return input;
};
};
function getTop(input) {
try {
if (!ctx.serviceWorker && input == ctx.window.parent && !ctx.window.parent.$corrosion) return ctx.window;
return input;
} catch(e) {
return input;
};
};
function getLoc(input) {
if (input == ctx.window.location) return ctx.location;
return input;
};
function setLoc(input, val, operator) {
if (input == ctx.window.location) return set(ctx.window, 'location', val, operator);
return false;
};
function get(obj, prop) {
if (!ctx.serviceWorker) {
if (obj instanceof ctx.Window && obj != ctx.window && obj.$corrosion) return obj.$corrosion.instrumentation.get(obj, prop);
if (obj instanceof ctx.Document && obj != ctx.window.document && obj.defaultView.$corrosion) return obj.defaultView.$corrosion.instrumentation.get(obj, prop);
};
switch(prop) {
case 'top':
return getTop(obj[prop]);
case 'parent':
return getParent(obj[prop]);
case 'location':
return getLoc(obj[prop]);
default:
return obj[prop];
};
};
function set(obj, prop, val, operator) {
if (!ctx.serviceWorker) {
if (obj instanceof ctx.Window && obj != ctx.window && obj.$corrosion) return obj.$corrosion.instrumentation.set(obj, prop, val, operator);
if (obj instanceof ctx.Document && obj != ctx.window.document && obj.defaultView.$corrosion) return obj.defaultView.$corrosion.instrumentation.set(obj, prop, val, operator);
};
if (obj == ctx.window.location) obj = ctx.location;
if (prop == 'location' && (obj == ctx.window || !ctx.serviceWorker && obj == ctx.window.document)) {
obj = ctx.location;
prop = 'href';
};
switch(operator) {
case '+=':
return obj[prop] += val;
case '-=':
return obj[prop] -= val;
case '*=':
return obj[prop] *= val;
case '/=':
return obj[prop] /= val;
case '%=':
return obj[prop] %= val;
case '**=':
return obj[prop] **= val;
case '<<=':
return obj[prop] <<= val;
case '>>=':
return obj[prop] >>= val;
case '>>>=':
return obj[prop] >>>= val;
case '&=':
return obj[prop] &= val;
case '^=':
return obj[prop] ^= val;
case '|=':
return obj[prop] |= val;
case '&&=':
return obj[prop] &&= val;
case '||=':
return obj[prop] ||= val;
case '??=':
return obj[prop] ??= val;
case '++':
return obj[prop]++;
case '--':
return obj[prop]--;
default:
return obj[prop] = val;
};
};
return { set, get, setLoc, getLoc, getParent, getTop, }
};
module.exports = createInstrumentation;

View file

@ -1,21 +1,3 @@
const { wrapFunction } = require("./utils");
const locationProperties = [
'hash',
'host',
'hostname',
'href',
'pathname',
'port',
'protocol',
'search',
'origin',
];
const locationMethods = [
'replace',
'reload',
'assign',
];
class Location {
get [Symbol.toPrimitive]() {
return () => this.href;
@ -23,12 +5,24 @@ class Location {
};
function createLocation(ctx, url) {
const _url = new URL(url);
const _location = new Location();
for (const property of locationProperties) {
const _url = new URL(url);
[
'hash',
'host',
'hostname',
'href',
'pathname',
'port',
'protocol',
'search',
'origin',
].forEach(property => {
Object.defineProperty(_location, property, {
get: () => _url[property],
set: val => {
get() {
return _url[property];
},
set(val) {
if (ctx.serviceWorker || property == 'origin') return;
if (property == 'href') {
return ctx.window.location.href = ctx.url.wrap(new URL(val, _url).href);
@ -37,18 +31,26 @@ function createLocation(ctx, url) {
return ctx.window.location.href = ctx.url.wrap(_url);
},
});
};
if (!ctx.serviceWorker) {
for (const method of locationMethods) {
_location[method] = wrapFunction(ctx.window.location[method], (target, that, args) => {
});
if (!ctx.serviceWorker) [
'assign',
'replace',
'reload',
].forEach(method => {
_location[method] = new Proxy(ctx.window.location[method], {
apply(target, that, args) {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return target.apply(ctx.window.location, args);
});
};
};
_location.toString = wrapFunction(_url.toString, target => target.call(_url));
ctx.proxyToOriginalMp.set(_location, ctx.window.location);
return Reflect.apply(target.bind(ctx.window.location), that, args);
},
});
});
_location.toString = new Proxy(_url.toString, {
apply(target, that, args) {
return Reflect.apply(target.bind(_url), that, args);
},
});
return _location;
};
module.exports = { createLocation, Location, locationMethods, locationProperties };
createLocation.Location = Location;
module.exports = createLocation;

View file

@ -1,118 +0,0 @@
const { overrideFunction, wrapFunction, overrideAccessors } = require('./utils');
function createMessageRewriter(ctx) {
if (ctx.window.postMessage) ctx.originalFn.postMessage = ctx.window.postMessage;
if (ctx.window.addEventListener) ctx.originalFn.addEventListener = ctx.window.addEventListener;
if (ctx.window.removeEventListener) ctx.originalFn.removeEventListener = ctx.window.removeEventListener;
if (ctx.window.Event) ctx.originalFn.Event = ctx.window.Event;
function rewritePostMessage() {
if (ctx.originalFn.postMessage) {
overrideFunction(ctx.window, 'postMessage', (target, that, args) => {
/*if (args[0]) {
args[0] = {
from: ctx.serviceWorker ? ctx.location.origin : args[1] || ctx.location.origin,
message: args[0],
};
};
console.log(args[0]);*/
if (!ctx.serviceWorker && args[1]) args[1] = ctx.window.location.origin;
return target.apply(that, args);
});
};
return true;
};
function rewriteMessageEvent() {
if (ctx.originalFn.Event) {
overrideAccessors(ctx.window.Event.prototype, 'target', {
getter: (target, that) => {
console.log(that.$corrosion_messageTarget);
return that.$corrosion_messageTarget
},
});
overrideAccessors(ctx.window.Event.prototype, 'srcElement', {
getter: (target, that) => that.$corrosion_messageSrcElement || target.call(that),
});
overrideAccessors(ctx.window.Event.prototype, 'currentTarget', {
getter: (target, that) => that.$corrosion_messageCurrentTarget || target.call(that),
});
overrideAccessors(ctx.window.Event.prototype, 'eventPhase', {
getter: (target, that) => that.$corrosion_messageEventPhase || target.call(that),
});
overrideAccessors(ctx.window.Event.prototype, 'path', {
getter: (target, that) => that.$corrosion_messagePath || target.call(that),
});
};
};
function rewriteEventListener() {
if (ctx.originalFn.addEventListener) {
overrideFunction(ctx.window, 'addEventListener', (target, that, args) => {
if (args[0] == 'message' && typeof args[1] == 'function') {
const data = {
type: 'message',
originalHandler: args[1],
proxyHandler: wrapFunction(args[1], (target, that, [ event ]) => {
let rw = new ctx.window.MessageEvent('message', {
bubbles: event.bubbles,
cancelable: event.cancelable,
data: event.data.message,
origin: event.data.from,
lastEventId: event.lastEventId,
source: event.source,
target: event.target,
ports: event.ports
});
return target.call(that, rw);
}),
};
ctx.windowEvents.push(data);
args[1] = data.proxyHandler;
};
return target.apply(that, args);
});
};
if (ctx.originalFn.removeEventListener) {
overrideFunction(ctx.window, 'removeEventListener', (target, that, args) => {
if (args[0] == 'message' && ctx.windowEvents.find(entry => entry.originalHandler == args[1])) {
args[1] = ctx.windowEvents.find(entry => entry.originalHandler == args[1]).proxyHandler;
};
return target.apply(that, args);
});
};
overrideAccessors(ctx.window, 'onmessage', {
setter: (target, that, [ val ]) => {
return target.call(that, wrapFunction(val, (target, that, [ event ]) => {
let rw = new ctx.window.MessageEvent('message', {
bubbles: event.bubbles,
cancelable: event.cancelable,
data: event.data.message,
origin: event.data.from,
lastEventId: event.lastEventId,
source: event.source,
ports: event.ports
});
[
'target',
'srcElement',
'currentTarget',
'eventPhase',
'path',
].forEach(key => {
Object.defineProperty(rw, key, {
get: () => event[key],
});
});
return target.call(that, rw);
}));
},
});
};
return function rewriteMessage() {
//rewriteMessageEvent();
rewritePostMessage();
//rewriteEventListener();
return true;
};
};
module.exports = createMessageRewriter;

View file

@ -1,52 +0,0 @@
const { overrideAccessors, overrideFunction } = require("./utils");
function createStyleRewriter(ctx) {
if (ctx.serviceWorker) return () => null;
if (ctx.window.CSSStyleDeclaration && ctx.window.CSSStyleDeclaration.prototype.setProperty) ctx.originalFn.CSSSetProperty = ctx.window.CSSStyleDeclaration.prototype.setProperty;
function rewriteSetProperty() {
if (ctx.originalFn.CSSSetProperty) {
overrideFunction(ctx.window.CSSStyleDeclaration.prototype, 'setProperty', (target, that, args) => {
if (args[1]) args[1] = ctx.processStyle(args[1], 'value');
return target.apply(that, args);
});
};
};
function rewriteStyleProperty(property, attribute) {
let type = null;
if (ctx.window.CSSStyleDeclaration || ctx.window.CSS2Properties) {
type = !!ctx.window.CSS2Properties ? 'CSS2Properties' : 'CSSStyleDeclaration';
};
switch(type) {
case 'CSS2Properties':
overrideAccessors(ctx.window.CSS2Properties.prototype, property, {
setter: (target, that, [ val ]) => target.call(that, ctx.processStyle(val, 'value')),
});
break;
default:
Object.defineProperty(ctx.window.CSSStyleDeclaration.prototype, property, {
get() {
return this.getProperty(attribute);
},
set(val) {
return this.setProperty(attribute, val);
},
});
};
return true;
};
return function rewriteStyle() {
rewriteStyleProperty('background', 'background');
rewriteStyleProperty('backgroundImage', 'background-image');
rewriteStyleProperty('cursor', 'cursor');
rewriteStyleProperty('listStyle', 'list-style');
rewriteStyleProperty('listStyleImage', 'list-style-image');
rewriteStyleProperty('border', 'border');
rewriteStyleProperty('borderImage', 'border-image');
rewriteStyleProperty('borderImageSource', 'border-image-source');
rewriteStyleProperty('maskImage', 'mask-image');
rewriteSetProperty();
return true;
};
};
module.exports = createStyleRewriter;

View file

@ -1,81 +0,0 @@
const corrosionProperties = [
'$corrosion_string',
'$corrosion_messagePath',
'$corrosion_messageTarget',
'$corrosion_messageSrcElement',
'$corrosion_messageCurrentTarget',
'$corrosion_messageEventPhase',
];
function overrideFunction(obj, prop, wrap) {
if (!obj || typeof obj[prop] != 'function') return false;
const wrapped = wrapFunction(obj[prop], wrap);
return obj[prop] = wrapped;
};
function overrideConstructor(obj, prop, wrap) {
if (!obj || typeof obj[prop] != 'function') return false;
const wrapped = wrapConstructor(obj[prop], wrap);
return obj[prop] = wrapped;
};
function overrideAccessors(obj, prop, handler) {
if (!obj || !obj.hasOwnProperty(prop)) return false;
const descriptor = Object.getOwnPropertyDescriptor(obj, prop) || {};
if (descriptor.get && handler.getter) overrideFunction(descriptor, 'get', handler.getter);
if (descriptor.set && handler.setter) overrideFunction(descriptor, 'set', handler.setter);
Object.defineProperty(obj, prop, descriptor);
return true;
};
function wrapFunction(fn, wrap) {
/*const wrapped = new Proxy(fn, {
apply: wrap,
get: (target, prop) => target[prop],
set: (target, prop, val) => target[prop] = val,
});*/
const wrapped = 'prototype' in fn ? function attach() {
return wrap(fn, this, [...arguments]);
} : {
attach() {
return wrap(fn, this, [...arguments]);
},
}['attach'];
['name', 'length', 'displayName'].forEach(key => {
if (key in wrapped) {
Object.defineProperty(wrapped, key, Object.getOwnPropertyDescriptor(fn, key));
};
});
wrapFnString(fn, wrapped);
return wrapped;
};
function wrapConstructor(fn, wrap) {
/*
const wrapped = new Proxy(fn, {
construct: wrap,
get: (target, prop) => target[prop],
set: (target, prop, val) => target[prop] = val,
});*/
const wrapped = function () {
return new.target ? wrap(fn, [...arguments]) : fn(...arguments);
};
['name', 'length'].forEach(key => {
if (key in wrapped) {
Object.defineProperty(wrapped, key, Object.getOwnPropertyDescriptor(fn, key));
};
});
wrapped.prototype = fn.prototype;
wrapped.prototype.constructor = wrapped;
wrapFnString(fn, wrapped)
return wrapped;
};
function wrapFnString(fn, wrapped) {
wrapped.$corrosion_string = Function.prototype.toString.call(fn);
return true;
};
module.exports = { corrosionProperties, overrideFunction, overrideConstructor, overrideAccessors, wrapFunction, wrapConstructor, wrapFnString };

View file

@ -1,52 +1,32 @@
const { overrideFunction, overrideConstructor } = require("./utils");
function createWorkerRewriter(ctx) {
if (ctx.window.Worker) ctx.originalFn.Worker = ctx.window.Worker;
if (ctx.serviceWorker && ctx.window.importScripts) ctx.originalFn.importScripts = ctx.window.importScripts;
if (ctx.window.Worklet) ctx.originalFn.WorkletAddModule = ctx.window.Worklet.prototype.addModule;
function rewriteWorkerConstruct() {
if (ctx.originalFn.Worker) {
overrideConstructor(ctx.window, 'Worker', (target, args) => {
if (args[0]) {
if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) {
const xhr = new ctx.window.XMLHttpRequest();
ctx.proxyToOriginal(ctx.window.XMLHttpRequest.prototype.open).call(xhr, 'GET', args[0], false)
xhr.send();
const script = ctx.js.process(xhr.responseText, ctx.location.origin + args[0].trim().slice(`blob:${ctx.window.location.origin}`.length));
const blob = new Blob([ script ], { type: 'application/javascript' });
args[0] = URL.createObjectURL(blob);
} else {
args[0] = ctx.url.wrap(args[0], ctx.meta);
};
};
return new target(...args);
});
return true;
};
};
function rewriteImportScripts() {
if (ctx.originalFn.importScripts) {
overrideFunction(ctx.window, 'importScripts', (target, that, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return target.apply(that, args);
});
};
return true;
};
function rewriteWorklet() {
if (ctx.originalFn.WorkletAddModule) {
overrideFunction(ctx.window.Worklet.prototype, 'addModule', (target, that, args) => {
console.log(args[0]);
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return target.apply(that, args);
});
};
};
function createWorkerRewriter(ctx = {}) {
return function rewriteWorker() {
rewriteWorkerConstruct();
rewriteImportScripts();
rewriteWorklet();
return true;
if (ctx.window.Worker) {
ctx.window.Worker = new Proxy(ctx.window.Worker, {
construct: (target, args) => {
if (args[0]) {
if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) {
const xhr = new ctx.originalXhr();
xhr.open('GET', args[0], false);
xhr.send();
const script = ctx.js.process(xhr.responseText, ctx.location.origin + args[0].trim().slice(`blob:${ctx.window.location.origin}`.length));
const blob = new Blob([ script ], { type: 'application/javascript' });
args[0] = URL.createObjectURL(blob);
} else {
args[0] = ctx.url.wrap(args[0], ctx.meta);
};
};
return Reflect.construct(target, args);
},
});
};
if (ctx.serviceWorker && ctx.window.importScripts) {
ctx.window.importScripts = new Proxy(ctx.window.importScripts, {
apply: (target, that, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return Reflect.apply(target, that, args);
},
});
};
};
};

71
lib/css.js Normal file
View file

@ -0,0 +1,71 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const csstree = require('css-tree');
class CSSRewriter {
constructor(ctx) {
this.ctx = ctx;
};
process(source, config = {}) {
const ast = csstree.parse(source, {
context: config.context || 'stylesheet',
parseCustomProperty: true,
});
const urls = csstree.findAll(ast, node =>
node.type == 'Url'
);
const imports = csstree.findAll(ast, node =>
node.type == 'Atrule' && node.name == 'import' && node.prelude && node.prelude.type == 'AtrulePrelude' && node.prelude.children.head.data.type == 'String'
);
urls.forEach(({ value }) => {
switch(value.type) {
case 'String':
const quote = value.value.substring(0, 1);
value.value = quote + this.ctx.url.wrap(value.value.slice(1).slice(0, -1), config) + quote;
break;
case 'Raw':
value.value = this.ctx.url.wrap(value.value, config);
break;
};
});
imports.forEach(({ prelude }) => {
const { data } = prelude.children.head;
const quote = data.value.substring(0, 1);
data.value = quote + this.ctx.url.wrap(data.value.slice(1).slice(0, -1), config) + quote;
});
return csstree.generate(ast);
};
source(processed, config = {}) {
const ast = csstree.parse(processed, {
context: config.context || 'stylesheet',
parseCustomProperty: true,
});
const urls = csstree.findAll(ast, node =>
node.type == 'Url'
);
const imports = csstree.findAll(ast, node =>
node.type == 'Atrule' && node.name == 'import' && node.prelude && node.prelude.type == 'AtrulePrelude' && node.prelude.children.head.data.type == 'String'
);
urls.forEach(({ value }) => {
switch(value.type) {
case 'String':
const quote = value.value.substring(0, 1);
value.value = quote + this.ctx.url.unwrap(value.value.slice(1).slice(0, -1), config) + quote;
break;
case 'Raw':
value.value = this.ctx.url.unwrap(value.value, config);
break;
};
});
imports.forEach(({ prelude }) => {
const { data } = prelude.children.head;
const quote = data.value.substring(0, 1);
data.value = quote + this.ctx.url.unwrap(data.value.slice(1).slice(0, -1), config) + quote;
});
return csstree.generate(ast);
};
};
module.exports = CSSRewriter;

View file

@ -1,14 +0,0 @@
module.exports = {
type: 'Atrule',
condition: node => node.name == 'import' && node.prelude && node.prelude.type == 'AtrulePrelude' && node.prelude.children.head.data.type == 'String',
rewrite: ({ prelude }, data) => {
const head = prelude.children.head.data;
const quote = head.value.substring(0, 1);
head.value = quote + data.ctx.url.wrap(head.value.slice(1).slice(0, -1), data.meta) + quote;
},
source: ({ prelude }, data) => {
const head = prelude.children.head.data;
const quote = head.value.substring(0, 1);
head.value = quote + data.ctx.url.unwrap(head.value.slice(1).slice(0, -1)) + quote;
},
};

View file

@ -1,70 +0,0 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const csstree = require('css-tree');
const rawMap = [
require('./import'),
require('./url-string'),
require('./url-raw'),
];
class CSSRewriter {
constructor(ctx) {
this.ctx = ctx;
this.map = new Map();
rawMap.forEach(({ type, ...keys }) => {
if (!this.map.has(type)) this.map.set(type, []);
this.map.get(type).push(keys);
});
};
process(source, config = {}) {
try {
const ast = csstree.parse(source, {
context: config.context || 'stylesheet',
parseCustomProperty: true,
});
csstree.walk(ast, node => {
if (this.map.has(node.type)) {
const data = {
ctx: this.ctx,
meta: config.meta || {},
}
const transformers = this.map.get(node.type);
for (const transformer of transformers) {
if (!transformer.condition || transformer.condition(node, data)) {
if (transformer.rewrite) transformer.rewrite(node, data);
};
};
};
});
return csstree.generate(ast);
} catch(e) {
return source;
};
};
source(processed, config = {}) {
const ast = csstree.parse(processed, {
context: config.context || 'stylesheet',
parseCustomProperty: true,
});
csstree.walk(ast, node => {
if (this.map.has(node.type)) {
const data = {
ctx: this.ctx,
meta: config.meta || {},
}
const transformers = this.map.get(node.type);
for (const transformer of transformers) {
if (!transformer.condition || transformer.condition(node, data)) {
console.log(transformer)
if (transformer.source) transformer.source(node, data);
};
};
};
});
return csstree.generate(ast);
};
};
module.exports = CSSRewriter;

View file

@ -1,10 +0,0 @@
module.exports = {
type: 'Url',
condition: ({ value }) => value.type == 'Raw',
rewrite: ({ value }, data) => {
value.value = data.ctx.url.wrap(value.value, data.meta);
},
source: ({ value }, data) => {
value.value = data.ctx.url.unwrap(value.value);
},
};

View file

@ -1,12 +0,0 @@
module.exports = {
type: 'Url',
condition: ({ value }) => value.type == 'String',
rewrite: ({ value }, data) => {
const quote = value.value.substring(0, 1);
value.value = quote + data.ctx.url.wrap(value.value.slice(1).slice(0, -1), data.meta) + quote;
},
source: ({ value }, data) => {
const quote = value.value.substring(0, 1);
value.value = quote + data.ctx.url.unwrap(value.value.slice(1).slice(0, -1)) + quote;
},
};

230
lib/html.js Normal file
View file

@ -0,0 +1,230 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const parse5 = require('parse5');
class HTMLRewriter {
constructor(ctx) {
this.ctx = ctx;
this.attrs = [
{
tags: ['form', 'object', 'a', 'link', 'area', 'base', 'script', 'img', 'audio', 'video', 'input', 'embed', 'iframe', 'track', 'source', 'html', 'table', 'head'],
attrs: ['src', 'href', 'ping', 'data', 'movie', 'action', 'poster', 'profile', 'background'],
handler: 'url',
},
{
tags: ['iframe'],
attrs: ['srcdoc'],
handler: 'html',
},
{
tags: ['img', 'link', 'source'],
attrs: ['srcset', 'imagesrcset'],
handler: 'srcset',
},
{
tags: '*',
attrs: ['style'],
handler: 'css',
},
{
tags: '*',
attrs: ['http-equiv', 'integrity', 'nonce', 'crossorigin'],
handler: 'delete',
},
];
};
process(source, config = {}) {
const ast = parse5[config.document ? 'parse' : 'parseFragment'](source);
const meta = {
origin: config.origin,
base: new URL(config.base),
};
iterate(ast, node => {
if (!node.tagName) return;
switch(node.tagName) {
case 'STYLE':
if (node.textContent) node.textContent = this.ctx.css.process(node.textContent, meta);
break;
case 'TITLE':
if (node.textContent && this.ctx.config.title) node.textContent = this.ctx.config.title;
break;
case 'SCRIPT':
if (node.getAttribute('type') != 'application/json' && node.textContent) node.textContent = this.ctx.js.process(node.textContent);
break;
case 'BASE':
if (node.hasAttribute('href')) meta.base = new URL(node.getAttribute('href'), config.base);
break;
};
node.attrs.forEach(attr => {
const handler = this.attributeRoute({
...attr,
node,
});
let flags = [];
if (node.tagName == 'SCRIPT' && attr.name == 'src') flags.push('js');
if (node.tagName == 'LINK' && node.getAttribute('rel') == 'stylesheet') flags.push('css');
switch(handler) {
case 'url':
node.setAttribute(`corrosion-${attr.name}`, attr.value);
attr.value = this.ctx.url.wrap(attr.value, { ...meta, flags });
break;
case 'srcset':
node.setAttribute(`corrosion-${attr.name}`, attr.value);
attr.value = this.srcset(attr.value, meta);
break;
case 'css':
attr.value = this.ctx.css.process(attr.value, { ...meta, context: 'declarationList' });
break;
case 'html':
node.setAttribute(`corrosion-${attr.name}`, attr.value);
attr.value = this.process(attr.value, { ...config, ...meta });
break;
case 'delete':
node.removeAttribute(attr.name);
break;
};
});
});
if (config.document) {
for (let i in ast.childNodes) if (ast.childNodes[i].tagName == 'html') ast.childNodes[i].childNodes.forEach(node => {
if (node.tagName == 'head') {
node.childNodes.unshift(...this.injectHead(config.base));
};
});
};
return parse5.serialize(ast);
};
source(processed, config = {}) {
const ast = parse5[config.document ? 'parse' : 'parseFragment'](processed);
iterate(ast, node => {
if (!node.tagName) return;
node.attrs.forEach(attr => {
if (node.hasAttribute(`corrosion-${attr.name}`)) {
attr.value = node.getAttribute(`corrosion-${attr.name}`);
node.removeAttribute(`corrosion-${attr.name}`)
};
});
});
return parse5.serialize(ast);
};
injectHead() {
return [
{
nodeName: 'title',
tagName: 'title',
childNodes: [
{
nodeName: '#text',
value: this.ctx.config.title || '',
}
],
attrs: [],
},
{
nodeName: 'script',
tagName: 'script',
childNodes: [],
attrs: [
{
name: 'src',
value: this.ctx.prefix + 'index.js',
},
],
},
{
nodeName: 'script',
tagName: 'script',
childNodes: [
{
nodeName: '#text',
value: `window.$corrosion = new Corrosion({ window, codec: '${this.ctx.config.codec || 'plain'}', prefix: '${this.ctx.prefix}', ws: ${this.ctx.config.ws}, cookie: ${this.ctx.config.cookie}, title: ${typeof this.ctx.config.title == 'boolean' ? this.ctx.config.title : '\'' + this.ctx.config.title + '\''}, }); $corrosion.init(); document.currentScript.remove();`
},
],
attrs: [],
}
];
}
attributeRoute(data) {
const match = this.attrs.find(entry => entry.tags == '*' && entry.attrs.includes(data.name) || entry.tags.includes(data.node.tagName.toLowerCase()) && entry.attrs.includes(data.name));
return match ? match.handler : false;
};
srcset(val, config = {}) {
return val.split(',').map(src => {
const parts = src.trimStart().split(' ');
if (parts[0]) parts[0] = this.ctx.url.wrap(parts[0], config);
return parts.join(' ');
}).join(', ');
};
unsrcset(val, config = {}) {
return val.split(',').map(src => {
const parts = src.trimStart().split(' ');
if (parts[0]) parts[0] = this.ctx.url.unwrap(parts[0], config);
return parts.join(' ');
}).join(', ');
};
};
class Parse5Wrapper {
constructor(node){
this.raw = node || {
attrs: [],
childNodes: [],
namespaceURI: '',
nodeName: '',
parentNode: {},
tagName: '',
};
};
hasAttribute(name){
return this.raw.attrs.some(attr => attr.name == name);
};
removeAttribute(name){
if (!this.hasAttribute(name)) return;
this.raw.attrs.splice(this.raw.attrs.findIndex(attr => attr.name == name), 1);
};
setAttribute(name, val = ''){
if (!name) return;
this.removeAttribute(name);
this.raw.attrs.push({
name: name,
value: val,
});
};
getAttribute(name){
return (this.raw.attrs.find(attr => attr.name == name) || { value: null }).value;
};
get textContent(){
return (this.raw.childNodes.find(node => node.nodeName == '#text') || { value: '', }).value
};
set textContent(val){
if (this.raw.childNodes.some(node => node.nodeName == '#text')) return this.raw.childNodes[this.raw.childNodes.findIndex(node => node.nodeName == '#text')].value = val;
this.raw.childNodes.push({
nodeName: '#text',
value: val,
});
};
get tagName(){
return (this.raw.tagName || '').toUpperCase();
};
get nodeName(){
return this.raw.nodeName;
};
get parentNode(){
return this.raw.parentNode;
};
get childNodes(){
return this.raw.childNodes || [];
};
get attrs() {
return this.raw.attrs || [];
};
};
function iterate(ast, fn = (node = Parse5Wrapper.prototype) => null) {
fn(new Parse5Wrapper(ast));
if (ast.childNodes) for (let i in ast.childNodes) iterate(ast.childNodes[i], fn);
};
module.exports = HTMLRewriter;

View file

@ -1,48 +0,0 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const attrs = [
{
tag: ['form', 'object', 'a', 'link', 'area', 'base', 'script', 'img', 'audio', 'video', 'input', 'embed', 'iframe', 'track', 'source', 'html', 'table', 'head'],
attribute: ['src', 'href', 'ping', 'data', 'movie', 'action', 'poster', 'profile', 'background'],
run: (node, data) => {
(data.setAttribute || node.setAttribute.bind(node))(`corrosion-attr-${data.attr.name}`, data.attr.value);
data.attr.value = data.ctx.url.wrap(data.attr.value, data.meta);
},
},
{
tag: 'iframe',
attribute: 'srcdoc',
run: (node, data) => {
(data.setAttribute || node.setAttribute.bind(node))(`corrosion-attr-${data.attr.name}`, data.attr.value);
data.attr.value = data.ctx.html.process(data.attr.value, data.meta);
},
},
{
tag: ['img', 'link', 'source'],
attribute: ['srcset', 'imagesrcset'],
run: (node, data) => {
(data.setAttribute || node.setAttribute.bind(node))(`corrosion-attr-${data.attr.name}`, data.attr.value);
data.attr.value = data.ctx.html.srcset(data.attr.value, data.meta);
},
},
{
tag: '*',
attribute: 'style',
run: (node, data) => {
(data.setAttribute || node.setAttribute.bind(node))(`corrosion-attr-${data.attr.name}`, data.attr.value);
data.attr.value = data.ctx.css.process(data.attr.value, data.meta);
},
},
{
tag: '*',
attribute: ['http-equiv', 'integrity', 'nonce', 'crossorigin'],
run: (node, data) => {
(data.setAttribute || node.setAttribute.bind(node))(`corrosion-attr-${data.attr.name}`, data.attr.value);
data.node.removeAttribute(data.attr.name);
},
},
];
module.exports = attrs;

View file

@ -1,144 +0,0 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const parse5 = require('parse5');
const rawAttrs = require('./attributes');
const { iterate } = require('./parse5');
class HTMLRewriter {
constructor(ctx) {
const master = this;
this.ctx = ctx;
this.attrs = new Map();
rawAttrs.forEach(({ tag, attribute, run }) => {
if (Array.isArray(tag)) {
for (const entry of tag) processAttribute(entry, attribute, run);
} else {
processAttribute(tag, attribute, run);
};
});
function processAttribute(tag, attribute, run) {
if (!master.attrs.has(tag)) master.attrs.set(tag, {});
if (Array.isArray(attribute)) {
for (const entry of attribute) master.attrs.get(tag)[entry] = run;
} else {
master.attrs.get(tag)[attribute] = run;
};
};
};
process(source, config = {}) {
try {
const ast = parse5[config.document ? 'parse' : 'parseFragment'](source);
let head = null;
iterate(ast, node => {
if (!node.tagName) return false;
if (!!node.textContent) {
switch(node.nodeName) {
case 'title':
if (this.ctx.config.title) {
node.setAttribute('corrosion-text', node.textContent);
node.textContent = this.ctx.config.title;
};
break;
case 'script':
node.textContent = this.ctx.js.process(node.textContent);
break;
case 'style':
node.textContent = this.ctx.css.process(node.textContent, { meta: config.meta });
break;
case 'noscript':
node.textContent = this.process(node.textContent, { meta: config.meta || {}, });
break;
};
};
if (config.document && node.tagName == 'HEAD') head = node;
for (const attr of node.attrs) {
let data = {
ctx: this.ctx,
attr,
node,
meta: config.meta,
delete: false,
};
if (this.attrs.get('*')[attr.name]) this.attrs.get('*')[attr.name](node, data);
if (this.attrs.has(node.nodeName) && this.attrs.get(node.nodeName)[attr.name]) this.attrs.get(node.nodeName)[attr.name](node, data);
};
});
if (config.document && head) {
head.childNodes.unshift(
{
nodeName: 'title',
tagName: 'title',
childNodes: [
{
nodeName: '#text',
value: this.ctx.config.title || '',
}
],
attrs: [],
},
{
nodeName: 'script',
tagName: 'script',
childNodes: [],
attrs: [
{
name: 'src',
value: this.ctx.prefix + 'index.js',
},
],
},
{
nodeName: 'script',
tagName: 'script',
childNodes: [
{
nodeName: '#text',
value: `window.$corrosion = new Corrosion({ window, codec: '${this.ctx.config.codec || 'plain'}', prefix: '${this.ctx.prefix}', ws: ${this.ctx.config.ws}, cookie: ${this.ctx.config.cookie}, title: ${typeof this.ctx.config.title == 'boolean' ? this.ctx.config.title : '\'' + this.ctx.config.title + '\''}, }); $corrosion.init(); document.currentScript.remove();`
},
],
attrs: [],
}
)
};
return parse5.serialize(ast);
} catch(e) {
return source;
};
};
source(processed, config = {}) {
const ast = parse5[config.document ? 'parse' : 'parseFragment'](processed);
iterate(ast, node => {
if (node.nodeName == 'noscript') node.textContent = this.source(node.textContent);
for (const attr of node.attrs) {
if (attr.name.startsWith('corrosion-attr-')) {
node.setAttribute(attr.name.slice('corrosion-attr-'.length), attr.value);
node.removeAttribute(attr.name);
};
if (attr.name.startsWith('corrosion-text')) {
node.textContent = attr.value;
node.removeAttribute(attr.name);
};
};
});
return parse5.serialize(ast);
};
srcset(val, meta = {}) {
return val.split(',').map(src => {
const parts = src.trimStart().split(' ');
if (parts[0]) parts[0] = this.ctx.url.wrap(parts[0], meta);
return parts.join(' ');
}).join(', ');
};
unsrcset(val, meta = {}) {
return val.split(',').map(src => {
const parts = src.trimStart().split(' ');
if (parts[0]) parts[0] = this.ctx.url.unwrap(parts[0], meta);
return parts.join(' ');
}).join(', ');
};
}
module.exports = HTMLRewriter;

View file

@ -1,66 +0,0 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
class Parse5Wrapper {
constructor(node){
this.raw = node || {
attrs: [],
childNodes: [],
namespaceURI: '',
nodeName: '',
parentNode: {},
tagName: '',
};
};
hasAttribute(name){
return this.raw.attrs.some(attr => attr.name == name);
};
removeAttribute(name){
if (!this.hasAttribute(name)) return;
this.raw.attrs.splice(this.raw.attrs.findIndex(attr => attr.name == name), 1);
};
setAttribute(name, val = ''){
if (!name) return;
this.removeAttribute(name);
this.raw.attrs.push({
name: name,
value: val,
});
};
getAttribute(name){
return (this.raw.attrs.find(attr => attr.name == name) || { value: null }).value;
};
get textContent(){
return (this.raw.childNodes.find(node => node.nodeName == '#text') || { value: '', }).value
};
set textContent(val){
if (this.raw.childNodes.some(node => node.nodeName == '#text')) return this.raw.childNodes[this.raw.childNodes.findIndex(node => node.nodeName == '#text')].value = val;
this.raw.childNodes.push({
nodeName: '#text',
value: val,
});
};
get tagName(){
return (this.raw.tagName || '').toUpperCase();
};
get nodeName(){
return this.raw.nodeName;
};
get parentNode(){
return this.raw.parentNode;
};
get childNodes(){
return this.raw.childNodes || [];
};
get attrs() {
return this.raw.attrs || [];
};
};
function iterate(ast, fn) {
fn(new Parse5Wrapper(ast), null);
if (ast.childNodes) for (let i in ast.childNodes) iterate(ast.childNodes[i], fn);
};
module.exports = { Parse5Wrapper, iterate };

189
lib/js.js Normal file
View file

@ -0,0 +1,189 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const { parse } = require('acorn-hammerhead');
const { generate } = require('./esotope');
class JSRewriter {
constructor(ctx) {
this.parseOptions = {
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
ecmaVersion: 2021,
};
this.generationOptions = {
format: {
quotes: 'double',
escapeless: true,
compact: true,
},
};
this.rewrite = ['location', 'parent', 'top'];
this.map = [
{
type: 'MemberExpression',
handler: (node, parent) => {
let rewrite = false;
if (parent.type == 'UnaryExpression' && parent.operator == 'delete') return;
if (parent.type == 'NewExpression' && parent.callee == node) return;
if (parent.type === 'CallExpression' && parent.callee === node) return;
if (node.preventRewrite) return;
switch(node.property.type) {
case 'Identifier':
//if (node.computed) rewrite = true;
if (!node.computed && this.rewrite.includes(node.property.name)) {
node.property = this.createLiteral(node.property.name);
rewrite = true;
};
break;
case 'Literal':
if (this.rewrite.includes(node.property.name)) rewrite = true;
break;
case 'TemplateLiteral':
rewrite = true;
break;
default:
if (node.computed) rewrite = true;
};
if (rewrite) {
let identifier = '$corrosionGet$m';
let nodeToRewrite = node;
const args = [
node.object,
node.property,
];
if (node.computed) args[1].preventRewrite = true;
if (parent.type == 'AssignmentExpression' && parent.left == node) {
identifier = '$corrosionSet$m';
nodeToRewrite = parent;
args.push(parent.right, this.createLiteral(parent.operator));
};
if (parent.type == 'CallExpression' && parent.callee == node) {
identifier = '$corrosionCall$m';
nodeToRewrite = parent;
args.push(this.createArrayExpression(...parent.arguments))
};
if (parent.type == 'UpdateExpression') {
identifier = '$corrosionSet$m';
nodeToRewrite = parent;
args.push(this.createLiteral(null), this.createLiteral(parent.operator));
};
Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier, }, args));
};
},
},
{
type: 'Identifier',
handler: (node, parent) => {
if (parent.type == 'MemberExpression' && parent.property == node) return; // window.location;
if (parent.type == 'LabeledStatement') return; // { location: null, };
if (parent.type == 'VariableDeclarator' && parent.id == node) return;
if (parent.type == 'Property' && parent.key == node) return;
if (parent.type == 'MethodDefinition') return;
if (parent.type == 'ClassDeclaration') return;
if (parent.type == 'RestElement') return;
if (parent.type == 'ExportSpecifier') return;
if (parent.type == 'ImportSpecifier') return;
if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression' || parent.type == 'ArrowFunctionExpression') && parent.params.includes(node)) return;
if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression') && parent.id == node) return;
if (parent.type == 'AssignmentPattern' && parent.left == node) return;
if (!this.rewrite.includes(node.name)) return;
if (node.preventRewrite) return;
let identifier = '$corrosionGet$';
let nodeToRewrite = node;
const args = [
this.createIdentifier(node.name, true),
];
if (parent.type == 'AssignmentExpression' && parent.left == node) {
identifier = '$corrosionSet$';
nodeToRewrite = parent;
args.push(parent.right);
args.push(this.createLiteral(parent.operator));
};
Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier }, args));
},
},
{
type: 'ImportDeclaration',
handler: (node, parent, url) => {
if (node.source.type != 'Literal' || !url) return;
node.source = this.createLiteral(ctx.url.wrap(node.source.value, { base: url, }));
},
},
{
type: 'ImportExpression',
handler: (node, parent) => {
node.source = this.createCallExpression(this.createMemberExpression(this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('url')), this.createIdentifier('wrap')), [
node.source,
this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('meta')),
]);
},
},
];
this.ctx = ctx;
};
process(source, url) {
try {
const ast = parse(source, this.parseOptions);
this.iterate(ast, (node, parent) => {
const fn = this.map.find(entry => entry.type == (node || {}).type);
if (fn) fn.handler(node, parent, url);
});
return (url ? this.createHead(url) : '') + generate(ast, this.generationOptions);
} catch(e) {
return source;
};
};
createHead(url) {
return `
if (!self.$corrosion && self.importScripts) {
importScripts(location.origin + '${this.ctx.prefix}index.js');
self.$corrosion = new Corrosion({ url: '${url}', codec: '${this.ctx.config.codec || 'plain'}', serviceWorker: true, window: self, prefix: '${this.ctx.prefix || '/service/'}', ws: ${this.ctx.config.ws || true}, cookies: ${this.ctx.config.cookies || false}, title: '${this.ctx.config.title}', }); $corrosion.init();
};\n`;
};
iterate(ast, handler) {
if (typeof ast != 'object' || !handler) return;
walk(ast, null, handler);
function walk(node, parent, handler) {
if (typeof node != 'object' || !handler) return;
handler(node, parent, handler);
for (const child in node) {
if (Array.isArray(node[child])) {
node[child].forEach(entry => walk(entry, node, handler));
} else {
walk(node[child], node, handler);
};
};
};
};
createCallExpression(callee, args) {
return { type: 'CallExpression', callee, arguments: args, optional: false, };
};
createArrayExpression(...elements) {
return {
type: 'ArrayExpression',
elements,
};
};
createMemberExpression(object, property) {
return {
type: 'MemberExpression',
object,
property,
};
};
createLiteral(value) {
return {
type: 'Literal',
value,
}
};
createIdentifier(name, preventRewrite) {
return { type: 'Identifier', name, preventRewrite: preventRewrite || false, };
};
};
module.exports = JSRewriter;

View file

@ -1,28 +0,0 @@
const { Syntax } = require('./esotope');
const { createCallExpression, createIdentifier, createLiteral, createFunctionExpression, createReturnStatement, createLogicalExpression, createMemberExpression, createThisExpression, createAssignmentExpression, } = require('./node-builder');
const name = '__set$Loc'
module.exports = {
type: Syntax.AssignmentExpression,
condition: (node, parent) => {
if (node.noRewrite || node.left.type != 'Identifier' || node.left.name != 'location') return false;
if (parent.type == Syntax.CallExpression && parent.callee.type == Syntax.Identifier && ['__get$Loc', '__get$Top', '__get$Parent', '__set$Loc'].includes(parent.callee.name)) return false;
return true;
},
run: node => {
const fn = createFunctionExpression(null, [], [
createReturnStatement(
createLogicalExpression(
createCallExpression(createIdentifier(name), [ { ...node.left, noRewrite: true, }, node.right, createLiteral(node.operator), ]),
{ ...createAssignmentExpression(createIdentifier('location'), node.right), noRewrite: true, },
)
)
]);
Object.assign(node, createCallExpression(
createMemberExpression(fn, createIdentifier('apply')),
[ createThisExpression() ]
));
node.wrap = true;
return true;
},
};

View file

@ -1,20 +0,0 @@
const { Syntax } = require('./esotope');
const { createCallExpression, createIdentifier, createLiteral } = require('./node-builder');
const { shouldRewriteProperty } = require('./utils');
module.exports = {
type: Syntax.AssignmentExpression,
condition: node => {
const { left } = node;
if (left.type != Syntax.MemberExpression) return false;
if (left.object.type == Syntax.Super) return false;
return shouldRewriteProperty(left);
},
run: node => {
const { left } = node;
let key = left.property;
if (key.type == 'Identifier' && !left.computed) key = createLiteral(key.name);
Object.assign(node, createCallExpression(createIdentifier('__set$'), [ left.object, key, node.right, createLiteral(node.operator), ]));
return true;
},
};

View file

@ -1,11 +0,0 @@
const { Syntax } = require('./esotope');
const { createCallExpression, createIdentifier } = require('./node-builder');
module.exports = {
type: Syntax.CallExpression,
condition: node => node.callee.type == 'Identifier' && node.callee.name == 'eval' && node.arguments.length,
run: node => {
node.arguments[0] = createCallExpression(createIdentifier('__processScript'), [ node.arguments[0] ]);
return true;
},
};

View file

@ -1,18 +0,0 @@
const { Syntax } = require('./esotope');
const { createCallExpression, createIdentifier, createArrayExpression } = require('./node-builder');
function create(type) {
return {
type,
condition: node => {
for (let param of node.params) {
if (param.type == Syntax.AssignmentPattern) param = param.left;
if (param.type == Syntax.ObjectPattern || param.type == Syntax.ArrayPattern) return true;
};
return false;
},
run: node => {
let num = 0;
}
};
};

View file

@ -1,33 +0,0 @@
const { Syntax } = require('./esotope');
const { createCallExpression, createIdentifier } = require('./node-builder');
const map = {
location: '__get$Loc',
top: '__get$Top',
parent: '__get$Parent'
};
module.exports = {
type: Syntax.Identifier,
condition: (node, parent) => {
if (node.noRewrite || !map[node.name] || !parent) return false;
if (parent.type == Syntax.VariableDeclarator && parent.id == node) return false;
if ((parent.type == Syntax.AssignmentExpression || parent.type == Syntax.AssignmentPattern) && parent.left == node) return false;
if ((parent.type == Syntax.FunctionExpression || parent.type == Syntax.FunctionDeclaration) && parent.id == node) return false;
if (parent.type == Syntax.MemberExpression && parent.property == node) return false;
if (parent.type == Syntax.Property && parent.key == node) return false;
if (parent.type == Syntax.Property && parent.value == node && parent.shorthand) return false;
if (parent.type == Syntax.UpdateExpression && (parent.operator == '++' || parent.operator == '--')) return false;
if ((parent.type == Syntax.FunctionExpression || parent.type == Syntax.FunctionDeclaration || parent.type == Syntax.ArrowFunctionExpression) && parent.params.indexOf(node) !== -1) return false;
if (parent.type == Syntax.CallExpression && parent.callee.type == Syntax.Identifier && ['__get$Loc', '__get$Top', '__get$Parent', '__set$Loc'].includes(parent.callee.name)) return false;
if (parent.type == Syntax.MethodDefinition) return false;
if (parent.type == Syntax.ClassDeclaration) return false;
if (parent.type == Syntax.RestElement) return false;
if (parent.type == Syntax.ExportSpecifier) return false;
if (parent.type == Syntax.ImportSpecifier) return false;
return true;
},
run: node => {
Object.assign(node, createCallExpression(createIdentifier(map[node.name]), [ createIdentifier(node.name) ]))
node.noRewrite = true;
},
};

View file

@ -1,80 +0,0 @@
// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const { parseScript } = require('meriyah');
const { generate } = require('./esotope');
const { iterate } = require('./utils');
const rawMap = [
require('./property'),
require('./assignment-property'),
require('./assignment-location'),
//require('./method-call'),
require('./identifier'),
require('./eval'),
];
class JSRewriter {
constructor(ctx) {
this.parseOptions = {
ranges: true,
globalReturn: true,
};
this.generationOptions = {
format: {
quotes: 'double',
escapeless: true,
compact: true,
},
};
this.map = new Map();
rawMap.forEach(({ type, condition, run }) => {
if (!this.map.has(type)) this.map.set(type, []);
this.map.get(type).push({ condition, run, });
});
};
process(source, config) {
try {
let slice = 0;
const ast = parseScript(source, this.parseOptions);
const changes = [];
const output = [];
iterate(ast, (node, parent) => {
if (!node) return;
if (parent && !!parent.inTransformer) node.inTransformer = true;
if (this.map.has(node.type)) {
const transformers = this.map.get(node.type);
for (const transformer of transformers) {
if (!transformer.condition || transformer.condition(node, parent)) {
transformer.run(node, parent);
if (!node.inTransformer) {
changes.push({
node,
start: node.start,
end: node.end,
parentType: parent.type,
});
node.inTransformer = true;
};
};
};
};
});
changes.sort((a, b) => (a.start - b.start) || (a.end - b.end));
for (const change of changes) {
const parentheses = change.node.wrap || (change.node.type == 'SequenceExpression '&& change.parentType != 'ExpressionStatement' && change.parentType != 'SequenceExpression');
output.push(source.slice(slice, change.start));
output.push(parentheses ? '(' : ' ');
output.push(generate(change.node, this.generationOptions));
output.push(parentheses ? ')' : ' ');
slice = change.end;
};
output.push(source.slice(slice));
return output.join('');
} catch(e) {
return source;
};
};
};
module.exports = JSRewriter;

View file

@ -1,13 +0,0 @@
const { Syntax } = require('./esotope');
const { createCallExpression, createIdentifier, createArrayExpression } = require('./node-builder');
module.exports = {
type: Syntax.CallExpression,
condition: node => {
if (node.callee.type == Syntax.MemberExpression && node.callee.computed && node.callee.object.type != Syntax.Super && node.callee.property.type != Syntax.Literal) return true;
return false;
},
run: node => {
Object.assign(node, createCallExpression(createIdentifier('__call$'), [ node.callee.object, node.callee.property, createArrayExpression(...node.arguments) ]));
},
};

View file

@ -1,65 +0,0 @@
const { Syntax } = require("./esotope");
function createCallExpression(callee, args) {
return { type: Syntax.CallExpression, callee, arguments: args, optional: false, };
};
function createArrayExpression(...elements) {
return {
type: Syntax.ArrayExpression,
elements,
};
};
function createMemberExpression(object, property) {
return {
type: Syntax.MemberExpression,
object,
property,
};
};
function createLiteral(value) {
return {
type: Syntax.Literal,
value,
}
};
function createFunctionExpression(id = null, params = [], body = [], async = false) {
return {
type: Syntax.FunctionExpression,
id,
params,
body: {
type: Syntax.BlockStatement,
body,
},
async,
};
};
function createReturnStatement(argument = null) {
return {
type: Syntax.ReturnStatement,
argument,
}
};
function createLogicalExpression(left, right) {
return {
type: Syntax.LogicalExpression,
left,
right,
operator: '||',
};
};
function createIdentifier(name) {
return { type: Syntax.Identifier, name };
};
function createThisExpression() {
return { type: Syntax.ThisExpression, };
};
function createAssignmentExpression(left, right, operator = '=') {
return {
type: Syntax.AssignmentExpression,
operator,
left,
right,
}
};
module.exports = { createAssignmentExpression, createThisExpression, createLogicalExpression, createReturnStatement, createFunctionExpression, createCallExpression, createArrayExpression, createMemberExpression, createLiteral, createIdentifier, };

View file

@ -1,22 +0,0 @@
const { Syntax } = require('./esotope');
const { createCallExpression, createIdentifier, createLiteral } = require('./node-builder');
const { shouldRewriteProperty } = require('./utils');
module.exports = {
type: Syntax.MemberExpression,
condition: (node, parent) => {
if (parent.type == 'UpdateExpression' && ['--', '++'].includes(parent.operator)) return false;
if (parent.type == 'UnaryExpression' && parent.operator == 'delete') return false;
if (parent.type == 'NewExpression' && parent.callee == node) return false;
if (parent.type === 'CallExpression' && parent.callee === node) return false;
if (parent.type == 'AssignmentExpression' && parent.left == node) return false;
if (node.object.type == 'Super') return false;
return shouldRewriteProperty(node);
},
run: node => {
let key = node.property;
if (key.type == 'Identifier' && !node.computed) key = createLiteral(key.name);
Object.assign(node, createCallExpression(createIdentifier('__get$'), [ node.object, key, ]));
return true;
},
};

View file

@ -1,30 +0,0 @@
const { Syntax } = require('./esotope');
function iterate(ast, handler) {
if (typeof ast != 'object' || !handler) return;
walk(ast, null, handler);
function walk(node, parent, handler) {
if (typeof node != 'object' || !handler) return;
handler(node, parent, handler);
for (const child in node) {
if (Array.isArray(node[child])) {
node[child].forEach(entry => walk(entry, node, handler));
} else {
walk(node[child], node, handler);
};
};
};
};
function canWrapIdentifier(node, parent) {
if (parent.type != Syntax.CallExpression || parent.callee.type != Syntax.Identifier) return true;
return !['__get$Loc', '__get$Top', '__get$Parent', '__set$Loc'].includes(parent.callee.name);
};
function shouldRewriteProperty(node) {
if (node.computed) {
return node.property.type == 'Literal' ? ['parent', 'location', 'top'].includes(node.property.value) : true;
} else {
return ['parent', 'location', 'top'].includes(node.property.name);
};
};
module.exports = { iterate, shouldRewriteProperty, canWrapIdentifier, };

View file

@ -4,10 +4,9 @@
// -------------------------------------------------------------
const URLWrapper = require('./url');
const CookieRewriter = require('./cookie');
const CSSRewriter = require('./css/');
const HTMLRewriter = require('./html/');
const JSRewriter = require('./js/');
const codec = require('./codec');
const CSSRewriter = require('./css');
const HTMLRewriter = require('./html');
const JSRewriter = require('./js');
const defaultConfig = {
prefix: '/service/',
codec: 'plain',
@ -20,8 +19,9 @@ class Rewrite {
constructor(config = defaultConfig) {
this.config = Object.assign(defaultConfig, config);
this.prefix = this.config.prefix;
this.url = new URLWrapper(this.config || {}, this);
this.codec = codec;
this.forceHttps = this.config.forceHttps;
this.url = new URLWrapper(this.config || {});
this.codec = this.url.codec;
this.cookies = new CookieRewriter(this);
this.css = new CSSRewriter(this);
this.js = new JSRewriter(this);

File diff suppressed because one or more lines are too long

View file

@ -13,6 +13,7 @@ const fs = require('fs');
const defaultConfig = {
prefix: '/service/',
codec: 'plain',
forceHttps: false,
ws: true,
cookie: true,
title: 'Service',

View file

@ -13,12 +13,12 @@ function createRequestProxy(ctx) {
const requestContext = {
url: urlData.value,
flags: urlData.flags,
origin: (clientRequest.socket.encrypted ? 'https://' : 'http://') + clientRequest.headers.host,
origin: ((clientRequest.socket.encrypted || ctx.config.forceHttps) ? 'https://' : 'http://') + clientRequest.headers.host,
body: await getChunks(clientRequest),
headers: { ...clientRequest.headers },
method: clientRequest.method,
rewrite: ctx,
agent: new (urlData.value.protocol == 'https:' ? https : http).Agent({
agent: new ((urlData.value.protocol == 'https:' || ctx.config.forceHttps) ? https : http).Agent({
rejectUnauthorized: false,
}),
address: null,
@ -27,7 +27,7 @@ function createRequestProxy(ctx) {
};
for (let i in ctx.config.requestMiddleware) ctx.config.requestMiddleware[i](requestContext);
if (clientResponse.writableEnded) return;
(requestContext.url.protocol == 'https:' ? https : http).request({
((requestContext.url.protocol == 'https:' || ctx.config.forceHttps) ? https : http).request({
headers: requestContext.headers,
method: requestContext.method,
hostname: requestContext.url.hostname,

View file

@ -1,4 +1,4 @@
/*const route = [
const route = [
{
types: ['text/html'],
handler: 'html',
@ -22,61 +22,15 @@ function rewriteBody(ctx) {
switch(data.handler) {
case 'html':
ctx.body = ctx.rewrite.html.process.bind(ctx.rewrite.html)(ctx.body.toString(), { meta, document: true });
ctx.body = ctx.rewrite.html.process(ctx.body.toString(), { ...meta, document: true });
break;
case 'css':
ctx.body = ctx.rewrite.css.process(ctx.body.toString(), { meta });
ctx.body = ctx.rewrite.css.process(ctx.body.toString(), meta);
break;
case 'js':
ctx.body = ctx.rewrite.js.process(ctx.body.toString(), ctx.url);
break;
};
};
*/
const map = [
{
condition: ctx => {
if (!ctx.contentType && !ctx.extension) return false;
if (ctx.contentType && !['application/javascript', 'application/x-javascript', 'text/javascript', 'text/x-javascript'].includes(ctx.contentType)) return false;
if (ctx.extension != 'js') return false;
return true;
},
run: (ctx) => ctx.rewrite.js.process(ctx.body.toString()),
},
{
condition: ctx => {
if (!ctx.contentType && !ctx.extension) return false;
if (ctx.contentType && ctx.contentType != 'text/css') return false;
if (ctx.extension != 'css') return false;
return true;
},
run: (ctx, meta) => ctx.rewrite.css.process(ctx.body.toString(), { meta, }),
},
{
condition: ctx => {
if (ctx.contentType && ctx.contentType != 'text/html') return false;
if (ctx.extension && !['html', 'htm'].includes(ctx.extension)) return false;
return true;
},
run: (ctx, meta) => ctx.rewrite.html.process.bind(ctx.rewrite.html)(ctx.body.toString(), { meta, document: true })
},
];
function rewriteBody(ctx) {
if (!ctx.body || !ctx.remoteResponse || ctx.flags.includes('xhr')) return;
const meta = {
base: ctx.url,
origin: ctx.origin,
};
ctx.contentType = (ctx.headers['content-type'] || '').split(';')[0];
ctx.extension = ctx.url.pathname.includes('.') ? ctx.url.pathname.split('.').slice(-1)[0] : '';
for (const entry of map) {
if (entry.condition(ctx, meta)) {
ctx.body = entry.run(ctx, meta);
break;
};
};
};
module.exports = rewriteBody;

View file

@ -13,7 +13,7 @@ function createWebSocketProxy(ctx) {
headers: { ...clientRequest.headers },
method: clientRequest.method,
rewrite: ctx,
agent: new (urlData.value.protocol == 'https:' ? https : http).Agent({
agent: new ((urlData.value.protocol == 'https:' || ctx.config.forceHttps) ? https : http).Agent({
rejectUnauthorized: false,
}),
address: null,
@ -22,7 +22,7 @@ function createWebSocketProxy(ctx) {
clientHead,
};
ctx.config.requestMiddleware.forEach(fn => fn(requestContext));
(requestContext.url.protocol == 'https:' ? https : http).request({
((requestContext.url.protocol == 'https:' || ctx.config.forceHttps) ? https : http).request({
headers: requestContext.headers,
method: requestContext.method,
hostname: requestContext.url.hostname,

View file

@ -9,29 +9,30 @@ const defaultConfig = {
};
class URLWrapper {
constructor(config = defaultConfig, ctx) {
constructor(config = defaultConfig) {
this.prefix = config.prefix || defaultConfig.prefix;
this.codec = codec[config.codec || 'plain'] || codec['plain'];
this.regex = /^(#|about:|data:|blob:|mailto:)/;
this.javascript = /^javascript:/;
this.protocols = ['http:', 'https:', 'ws:', 'wss:'];
this.ctx = ctx;
this.regex = /^(#|about:|data:|blob:|mailto:|javascript:)/;
};
wrap(val, config = {}) {
if (!val && val != null || this.regex.test(val)) return val;
if (this.javascript.test(val)) {
return 'javascript:' + this.ctx.js.process(val.slice('javascript:'.length));
if (!val || this.regex.test(val)) return val;
let flags = '';
(config.flags || []).forEach(flag => flags += `${flag}_/`);
if (config.base) try {
if (!['http:', 'https:', 'ws:', 'wss:'].includes(new URL(val, config.base).protocol)) return val;
} catch(e) {
return val;
};
const url = new URL(val, config.base);
if (!this.protocols.includes(url.protocol)) return val;
return (config.origin || '') + this.prefix.slice(0, -1) + [ '', ...(config.flags || []) ].join('/_') + '/' + this.codec.encode(url.href);
return (config.origin || '') + this.prefix + flags + this.codec.encode(config.base ? new URL(val, config.base) : val) + '/';
};
unwrap(val, config = {}) {
if (!val && val != null || this.regex.test(val) || this.javascript.test(val)) return val;
const flags = (val.match(/\/_(.*)\//) || [ null, ''])[1].split('/_');
const url = this.codec.decode(val.slice(((config.origin || '') + this.prefix + (flags[0] ? '_' + flags.join('/_') + '/' : '')).length));
return config.flags ? { flags, value: url } : url;
if (!val || this.regex.test(val)) return val;
let processed = val.slice((config.origin || '').length + this.prefix.length);
const flags = ('/' + processed).match(/(?<=\/)(.*?)(?=_\/)/g) || [];
flags.forEach(flag => processed = processed.slice(`${flag}_/`.length));
let [ url, leftovers ] = processed.split(/\/(.+)?/);
return config.flags ? { value: this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : ''), flags } : this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : '');
};
}
};
module.exports = URLWrapper;

View file