mirror of
https://github.com/QuiteAFancyEmerald/Holy-Unblocker.git
synced 2025-05-12 11:30:01 -04:00
Heroku/Repl.it fix?
This commit is contained in:
parent
73cdcb3186
commit
2cbcaebca8
44 changed files with 1097 additions and 35748 deletions
|
@ -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.'),
|
||||
],
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
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]]);
|
||||
};
|
||||
function rewriteUrl() {
|
||||
if (ctx.originalAccessors.DocumentURL) {
|
||||
overrideAccessors(ctx.docProto, 'URL', {
|
||||
getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta),
|
||||
};
|
||||
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);
|
||||
},
|
||||
});
|
||||
};
|
||||
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);
|
||||
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);
|
||||
},
|
||||
});
|
||||
};
|
||||
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);
|
||||
[
|
||||
'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, ]);
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
[
|
||||
['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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
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;
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
function rewriteOpen() {
|
||||
if (ctx.originalFn.open) {
|
||||
overrideFunction(ctx.window, 'open', (target, that, args) => {
|
||||
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 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),
|
||||
return Reflect.apply(target, that, args)
|
||||
},
|
||||
});
|
||||
};
|
||||
return true;
|
||||
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 function rewriteHttp() {
|
||||
rewriteXhr();
|
||||
rewriteFetch();
|
||||
rewriteWebSocket();
|
||||
rewriteOpen();
|
||||
rewriteEventSource();
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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,77 +18,177 @@ 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);
|
||||
if (!this.serviceWorker && this.window.document.currentScript) this.window.document.currentScript.remove();
|
||||
};
|
||||
processStyle(val, context) {
|
||||
return this.css.process(val, { meta: this.meta, context });
|
||||
get parent() {
|
||||
if (this.serviceWorker) return false;
|
||||
try {
|
||||
return this.window.parent.$corrosion ? this.window.parent : this.window;
|
||||
} catch(e) {
|
||||
return this.window;
|
||||
};
|
||||
processScript(val) {
|
||||
return this.js.process(val, this.meta.url.href);
|
||||
};
|
||||
processHtml(val, document = false) {
|
||||
if (!val) return val;
|
||||
return this.html.process(val, { document, meta: this.meta, });
|
||||
get top() {
|
||||
if (this.serviceWorker) return false;
|
||||
try {
|
||||
return this.window.top.$corrosion ? this.window.top : this.parent;
|
||||
} catch(e) {
|
||||
return this.parent;
|
||||
};
|
||||
call(obj, method, args) {
|
||||
return obj[method](...args);
|
||||
};
|
||||
proxyToOriginal(input) {
|
||||
return this.proxyToOriginalMp.get(input) || input;
|
||||
};
|
||||
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);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -1,5 +1,13 @@
|
|||
const { wrapFunction } = require("./utils");
|
||||
const locationProperties = [
|
||||
class Location {
|
||||
get [Symbol.toPrimitive]() {
|
||||
return () => this.href;
|
||||
};
|
||||
};
|
||||
|
||||
function createLocation(ctx, url) {
|
||||
const _location = new Location();
|
||||
const _url = new URL(url);
|
||||
[
|
||||
'hash',
|
||||
'host',
|
||||
'hostname',
|
||||
|
@ -9,26 +17,12 @@ const locationProperties = [
|
|||
'protocol',
|
||||
'search',
|
||||
'origin',
|
||||
];
|
||||
const locationMethods = [
|
||||
'replace',
|
||||
'reload',
|
||||
'assign',
|
||||
];
|
||||
|
||||
class Location {
|
||||
get [Symbol.toPrimitive]() {
|
||||
return () => this.href;
|
||||
};
|
||||
};
|
||||
|
||||
function createLocation(ctx, url) {
|
||||
const _url = new URL(url);
|
||||
const _location = new Location();
|
||||
for (const property of locationProperties) {
|
||||
].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 (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);
|
||||
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 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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 };
|
|
@ -1,16 +1,12 @@
|
|||
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) => {
|
||||
function createWorkerRewriter(ctx = {}) {
|
||||
return function rewriteWorker() {
|
||||
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.window.XMLHttpRequest();
|
||||
ctx.proxyToOriginal(ctx.window.XMLHttpRequest.prototype.open).call(xhr, 'GET', args[0], false)
|
||||
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' });
|
||||
|
@ -19,34 +15,18 @@ function createWorkerRewriter(ctx) {
|
|||
args[0] = ctx.url.wrap(args[0], ctx.meta);
|
||||
};
|
||||
};
|
||||
return new target(...args);
|
||||
return Reflect.construct(target, args);
|
||||
},
|
||||
});
|
||||
return true;
|
||||
};
|
||||
};
|
||||
function rewriteImportScripts() {
|
||||
if (ctx.originalFn.importScripts) {
|
||||
overrideFunction(ctx.window, 'importScripts', (target, that, 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 target.apply(that, args);
|
||||
return Reflect.apply(target, 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);
|
||||
});
|
||||
};
|
||||
};
|
||||
return function rewriteWorker() {
|
||||
rewriteWorkerConstruct();
|
||||
rewriteImportScripts();
|
||||
rewriteWorklet();
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
71
lib/css.js
Normal file
71
lib/css.js
Normal 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;
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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;
|
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -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
230
lib/html.js
Normal 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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
189
lib/js.js
Normal 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;
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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;
|
|
@ -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) ]));
|
||||
},
|
||||
};
|
|
@ -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, };
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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, };
|
|
@ -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);
|
||||
|
|
34006
lib/server/bundle.js
34006
lib/server/bundle.js
File diff suppressed because one or more lines are too long
|
@ -13,6 +13,7 @@ const fs = require('fs');
|
|||
const defaultConfig = {
|
||||
prefix: '/service/',
|
||||
codec: 'plain',
|
||||
forceHttps: false,
|
||||
ws: true,
|
||||
cookie: true,
|
||||
title: 'Service',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
|
@ -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,
|
||||
|
|
33
lib/url.js
33
lib/url.js
|
@ -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;
|
||||
|
|
0
views/pages/proxnav/rammerhead.html
Normal file
0
views/pages/proxnav/rammerhead.html
Normal file
Loading…
Add table
Add a link
Reference in a new issue