diff --git a/backend.js b/backend.js index ad2a9585..398e02a0 100644 --- a/backend.js +++ b/backend.js @@ -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.'), ], diff --git a/lib/browser/attributes.js b/lib/browser/attributes.js deleted file mode 100644 index 6e5bac5d..00000000 --- a/lib/browser/attributes.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/browser/document.js b/lib/browser/document.js index 97315159..4cc19a7b 100644 --- a/lib/browser/document.js +++ b/lib/browser/document.js @@ -1,81 +1,271 @@ -const { overrideAccessors, overrideFunction } = require("./utils"); - function createDocumentRewriter(ctx) { - if (ctx.serviceWorker) return () => false; - if (ctx.docProto.hasOwnProperty('cookie')) ctx.originalAccessors.DocumentCookie = Object.getOwnPropertyDescriptor(ctx.docProto, 'cookie'); - if (ctx.docProto.hasOwnProperty('domain')) ctx.originalAccessors.DocumentDomain = Object.getOwnPropertyDescriptor(ctx.docProto, 'domain'); - if (ctx.docProto.hasOwnProperty('title')) ctx.originalAccessors.DocumentTitle = Object.getOwnPropertyDescriptor(ctx.docProto, 'title'); - if (ctx.docProto.hasOwnProperty('documentURI')) ctx.originalAccessors.DocumentURI = Object.getOwnPropertyDescriptor(ctx.docProto, 'documentURI'); - if (ctx.docProto.hasOwnProperty('URL')) ctx.originalAccessors.DocumentURL = Object.getOwnPropertyDescriptor(ctx.docProto, 'URL'); - if (ctx.docProto.write) ctx.originalFn.documentWrite = ctx.docProto.write; - if (ctx.docProto.writeln) ctx.originalFn.documentWriteln = ctx.docProto.writeln; - function rewriteDomain() { - let spoof = ctx.location.hostname; - if (ctx.originalAccessors.DocumentDomain) { - overrideAccessors(ctx.docProto, 'domain', { - getter: () => spoof, - setter: (target, that, [ val ]) => { - if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) target.call(that, ''); - return spoof = val; + return function rewriteDocument() { + if (ctx.serviceWorker) return; + const { + HTMLMediaElement, + HTMLScriptElement, + HTMLAudioElement, + HTMLVideoElement, + HTMLInputElement, + HTMLEmbedElement, + HTMLTrackElement, + HTMLAnchorElement, + HTMLIFrameElement, + HTMLAreaElement, + HTMLLinkElement, + HTMLBaseElement, + HTMLFormElement, + HTMLImageElement, + HTMLSourceElement, + } = ctx.window; + const cookie = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'cookie'); + const domain = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'domain'); + const title = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'title'); + const baseURI = Object.getOwnPropertyDescriptor(ctx.window.Node.prototype, 'baseURI'); + const cookieEnabled = Object.getOwnPropertyDescriptor(ctx.window.Navigator.prototype, 'cookieEnabled'); + let spoofTitle = ''; + let spoofDomain = ctx.location.hostname; + + if (ctx.window.Document.prototype.write) { + ctx.window.Document.prototype.write = new Proxy(ctx.window.Document.prototype.write, { + apply: (target, that , args) => { + if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ]; + return Reflect.apply(target, that, args); }, }); }; - return true; - }; - function rewriteCookie() { - if (ctx.originalAccessors.DocumentCookie) { - overrideAccessors(ctx.docProto, 'cookie', { - getter: (target, that) => ctx.config.cookie ? ctx.cookies.decode(target.call(that), ctx.meta) : '', - setter: (target, that, [ val ]) => target.call(that, ctx.config.cookie ? ctx.cookies.encode(val, ctx.meta) : ''), + if (ctx.window.Document.prototype.hasOwnProperty('cookie')) { + Object.defineProperty(ctx.window.Document.prototype, 'cookie', { + get: new Proxy(cookie.get, { + apply: (target, that, args) => { + const cookies = Reflect.apply(target, that, args); + return ctx.config.cookie ? ctx.cookies.decode(cookies, ctx.meta) : ''; + }, + }), + set: new Proxy(cookie.set, { + apply: (target, that, [ val ]) => { + return Reflect.apply(target, that, [ ctx.config.cookie ? ctx.cookies.encode(val, ctx.meta) : '' ]); + }, + }), }); }; - return true; - }; - function rewriteTitle() { - let spoof = ''; - if (ctx.originalAccessors.DocumentTitle && ctx.config.title) { - overrideAccessors(ctx.docProto, 'title', { - getter: () => spoof, - setter: (target, that, [ val ]) => spoof = val, + if (ctx.window.Document.prototype.writeln) { + ctx.window.Document.prototype.writeln = new Proxy(ctx.window.Document.prototype.writeln, { + apply: (target, that , args) => { + if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ]; + return Reflect.apply(target, that, args); + }, }); }; - return true; - }; - function rewriteUrl() { - if (ctx.originalAccessors.DocumentURL) { - overrideAccessors(ctx.docProto, 'URL', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), + if (ctx.window.Element.prototype.setAttribute) { + ctx.window.Element.prototype.setAttribute = new Proxy(ctx.window.Element.prototype.setAttribute, { + apply: (target, that, args) => { + if (args[0] && args[1]) { + const handler = ctx.html.attributeRoute({ + name: args[0], + value: args[1], + node: that, + }); + switch(handler) { + case 'url': + Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); + //if (that.tagName == 'SCRIPT' && args[0] == 'src') flags.push('js'); + args[1] = ctx.url.wrap(args[1], ctx.meta); + break; + case 'srcset': + Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); + args[1] = ctx.html.srcset(args[1], ctx.meta); + break; + case 'css': + Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); + args[1] = ctx.css.process(args[1], { ...ctx.meta, context: 'declarationList' }); + break; + case 'html': + Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); + args[1] = ctx.html.process(args[1], ctx.meta); + break; + case 'delete': + return Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); + }; + }; + return Reflect.apply(target, that, args); + }, }); }; - if (ctx.originalAccessors.DocumentURI) { - overrideAccessors(ctx.docProto, 'documentURI', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), + if (ctx.window.Element.prototype.getAttribute) { + ctx.window.Element.prototype.getAttribute = new Proxy(ctx.window.Element.prototype.getAttribute, { + apply: (target, that, args) => { + if (args[0] && that.hasAttribute(`corrosion-${args[0]}`)) args[0] = `corrosion-${args[0]}`; + return Reflect.apply(target, that, args); + }, + }); + }; + ctx.window.CSSStyleDeclaration.prototype.setProperty = new Proxy(ctx.window.CSSStyleDeclaration.prototype.setProperty, { + apply: (target, that, args) => { + if (args[1]) args[1] = ctx.css.process(args[1], { context: 'value', ...ctx.meta, }); + return Reflect.apply(target, that, args); + }, + }); + if (ctx.window.Audio) { + ctx.window.Audio = new Proxy(ctx.window.Audio, { + construct: (target, args) => { + if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); + return Reflect.construct(target, args); + }, }); }; - return true; - }; - function rewriteWrite() { - if (ctx.originalFn.documentWrite) { - overrideFunction(ctx.docProto, 'write', (target, that, args) => { - if (args.length) args = [ ctx.html.process(args.join(''), { meta: ctx.meta }) ]; - return target.apply(that, args); + [ + 'innerHTML', + 'outerHTML', + ].forEach(html => { + const descriptor = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, html); + Object.defineProperty(ctx.window.Element.prototype, html, { + get: new Proxy(descriptor.get, { + apply: (target, that, args) => { + const body = Reflect.apply(target, that, args); + if (!body || html == 'innerHTML' && that.tagName == 'SCRIPT') return body; + return ctx.html.source(body, ctx.meta); + }, + }), + set: new Proxy(descriptor.set, { + apply(target, that, [ val ]) { + return Reflect.apply(target, that, [ val ? ctx.html.process(val.toString(), ctx.meta) : val, ]); + }, + }), }); - }; - if (ctx.originalFn.documentWriteln) { - overrideFunction(ctx.docProto, 'writeln', (target, that, args) => { - if (args.length) args = [ ctx.html.process(args.join(''), { meta: ctx.meta }) ]; - return target.apply(that, args); + }); + [ + ['background', 'background'], + ['backgroundImage', 'background-image'], + ['listStyleImage', 'list-style-image'], + ].forEach(([key, cssProperty]) => { + Object.defineProperty(ctx.window.CSS2Properties ? ctx.window.CSS2Properties.prototype : ctx.window.CSSStyleDeclaration.prototype, key, { + get() { + return this.getPropertyValue(cssProperty); + }, + set(val) { + return this.setProperty(cssProperty, val); + }, + }); + }); + Object.defineProperty(ctx.window.Document.prototype, 'domain', { + get: new Proxy(domain.get, { + apply: () => spoofDomain, + }), + set: new Proxy(domain.set, { + apply: (target, that, [ val ]) => { + if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) return Reflect.apply(target, that, ['']); + return spoofDomain = val; + }, + }), + }); + if (ctx.config.title) Object.defineProperty(ctx.window.Document.prototype, 'title', { + get: new Proxy(title.get, { + apply: () => spoofTitle, + }), + set: new Proxy(title.set, { + apply: (target, that, [ val ]) => spoofTitle = val, + }), + }); + Object.defineProperty(ctx.window.Navigator.prototype, 'cookieEnabled', { + get: new Proxy(cookieEnabled.get, { + apply: () => ctx.config.cookie, + }), + }); + Object.defineProperty(ctx.window.Node.prototype, 'baseURI', { + get: new Proxy(baseURI.get, { + apply: (target, that, args) => { + const val = Reflect.apply(target, that, args); + return val.startsWith(ctx.meta.origin) ? ctx.url.unwrap(val, ctx.meta) : val; + }, + }), + }); + [ + { + elements: [ HTMLScriptElement, HTMLMediaElement, HTMLImageElement, HTMLAudioElement, HTMLVideoElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement], + properties: ['src'], + handler: 'url', + }, + { + elements: [ HTMLFormElement ], + properties: ['action'], + handler: 'url', + }, + { + elements: [ HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement ], + properties: ['href'], + handler: 'url', + }, + { + elements: [ HTMLImageElement, HTMLSourceElement ], + properties: ['srcset'], + handler: 'srcset', + }, + { + elements: [ HTMLScriptElement ], + properties: ['integrity'], + handler: 'delete', + }, + { + elements: [ HTMLIFrameElement ], + properties: ['contentWindow'], + handler: 'window', + }, + ].forEach(entry => { + entry.elements.forEach(element => { + if (!element) return; + entry.properties.forEach(property => { + if (!element.prototype.hasOwnProperty(property)) return; + const descriptor = Object.getOwnPropertyDescriptor(element.prototype, property); + Object.defineProperty(element.prototype, property, { + get: descriptor.get ? new Proxy(descriptor.get, { + apply: (target, that, args) => { + let val = Reflect.apply(target, that, args); + let flags = []; + switch(entry.handler) { + case 'url': + //if (that.tagName == 'SCRIPT' && property == 'src') flags.push('js'); + val = ctx.url.unwrap(val, ctx.meta); + break; + case 'srcset': + val = ctx.html.unsrcset(val, ctx.meta); + break; + case 'delete': + val = that.getAttribute(`corrosion-${property}`); + break; + case 'window': + try { + if (!val.$corrosion) { + val.$corrosion = new ctx.constructor({ ...ctx.config, window: val, }); + val.$corrosion.init(); + val.$corrosion.meta = ctx.meta; + }; + } catch(e) {}; + }; + return val; + }, + }) : undefined, + set: descriptor.set ? new Proxy(descriptor.set, { + apply(target, that, [ val ]) { + let newVal = val; + switch(entry.handler) { + case 'url': + newVal = ctx.url.wrap(newVal, ctx.meta); + break; + case 'srcset': + newVal = ctx.html.srcset(newVal, ctx.meta); + break; + case 'delete': + that.setAttribute(property, newVal); + return newVal; + }; + return Reflect.apply(target, that, [ newVal ]); + }, + }) : undefined, + }); + }); }); - }; - }; - return function rewriteDocument() { - rewriteCookie(); - rewriteUrl(); - rewriteTitle(); - rewriteDomain(); - rewriteWrite(); - return true; + }); }; }; - module.exports = createDocumentRewriter; \ No newline at end of file diff --git a/lib/browser/dom.js b/lib/browser/dom.js deleted file mode 100644 index 4f3d9702..00000000 --- a/lib/browser/dom.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/browser/function.js b/lib/browser/function.js deleted file mode 100644 index d34d33a0..00000000 --- a/lib/browser/function.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/browser/history.js b/lib/browser/history.js index b31dc56a..7c2f44d6 100644 --- a/lib/browser/history.js +++ b/lib/browser/history.js @@ -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; \ No newline at end of file diff --git a/lib/browser/http.js b/lib/browser/http.js index e1b62c32..df557b98 100644 --- a/lib/browser/http.js +++ b/lib/browser/http.js @@ -1,117 +1,91 @@ -const { overrideFunction, overrideConstructor, overrideAccessors } = require("./utils"); - -function createHttpRewriter(ctx) { - if (ctx.window.fetch) ctx.originalFn.fetch = ctx.window.fetch; - if (ctx.window.WebSocket && ctx.window.WebSocket.prototype) ctx.originalFn.WebSocket = ctx.window.WebSocket; - if (ctx.window.Request && ctx.window.Request.prototype) { - ctx.originalFn.Request = ctx.window.Request; - ctx.originalAccessors.RequestUrl = Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url'); - }; - if (ctx.window.Response && ctx.window.Response.prototype) { - ctx.originalAccessors.ResponseUrl = Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url'); - }; - if (ctx.window.XMLHttpRequest && ctx.window.XMLHttpRequest.prototype) { - ctx.originalFn.xhrOpen = ctx.window.XMLHttpRequest.prototype.open; - ctx.originalFn.xhrSend = ctx.window.XMLHttpRequest.prototype.send; - ctx.originalAccessors.xhrResponseUrl = Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL'); - }; - if (ctx.window.EventSource && ctx.window.EventSource.prototype) { - ctx.originalFn.EventSource = ctx.window.EventSource; - ctx.originalAccessors.EventSourceUrl = Object.getOwnPropertyDescriptor(ctx.window.EventSource.prototype, 'url'); - }; - if (ctx.window.open) ctx.originalFn.open = ctx.window.open; - function rewriteFetch() { - if (ctx.originalFn.Request) { - overrideConstructor(ctx.window, 'Request', (target, args) => { - if (args[0] instanceof ctx.window.Request) return; - if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'] }); - return new target(...args); - }); - overrideAccessors(ctx.window.Request.prototype, 'url', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), - }); - ctx.proxyToOriginalMp.set(Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url').get, ctx.originalAccessors.RequestUrl); - ctx.proxyToOriginalMp.set(ctx.window.Request, ctx.originalFn.Request); - }; - if (ctx.originalAccessors.ResponseUrl) { - overrideAccessors(ctx.window.Response.prototype, 'url', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), - }); - ctx.proxyToOriginalMp.set(Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url').get, ctx.originalAccessors.ResponseUrl); - }; - if (ctx.originalFn.fetch) { - overrideFunction(ctx.window, 'fetch', async (target, that, args) => { - if (args[0] instanceof ctx.window.Request) return target.apply(that, args); - if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], query: new URL(args[0], ctx.location.href).origin != ctx.location.origin ? `origin=${ctx.codec.base64.encode(ctx.location.origin)}` : null }); - return target.apply(that, args); - }); - ctx.proxyToOriginalMp.set(ctx.window.fetch, ctx.originalFn.fetch); - }; - return true; - }; - function rewriteXhr() { - if (ctx.originalFn.xhrOpen) { - overrideFunction(ctx.window.XMLHttpRequest.prototype, 'open', (target, that, args) => { - if (args[1]) { - args[1] = ctx.url.wrap(args[1], { ...ctx.meta, flags: ['xhr'], query: new URL(args[1], ctx.location.href).origin != ctx.location.origin ? `origin=${ctx.codec.base64.encode(ctx.location.origin)}` : null }); - }; - return target.apply(that, args); - }); - ctx.proxyToOriginalMp.set(ctx.window.XMLHttpRequest.prototype.open, ctx.originalFn.xhrOpen); - }; - if (ctx.originalAccessors.xhrResponseUrl) { - overrideAccessors(ctx.window.XMLHttpRequest.prototype, 'responseURL', { - getter: (target, that) => { - const url = target.call(that); - return url ? ctx.url.unwrap(url, ctx.meta) : url; +function createHttpRewriter(ctx = {}) { + return function rewriteHttp() { + if (ctx.window.Request) { + const requestURL = Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url'); + ctx.window.Request = new Proxy(ctx.window.Request, { + construct(target, args) { + if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], }) + return Reflect.construct(target, args); }, }); - ctx.proxyToOriginalMp.set(Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL').get, ctx.originalAccessors.xhrResponseUrl.get); - }; - return true; - }; - function rewriteWebSocket() { - if (ctx.originalFn.WebSocket) { - overrideConstructor(ctx.window, 'WebSocket', (target, args) => { - try { - let url = new URL(args[0]); - args[0] = ['wss:', 'ws:'].includes(url.protocol) ? ctx.url.wrap(url.href.replace('ws', 'http'), ctx.meta).replace('http', 'ws') + '?origin=' + ctx.codec.base64.encode(ctx.location.origin) : ''; - } catch(e) { - args[0] = ''; - }; - return new target(...args); - }); - }; - }; - function rewriteOpen() { - if (ctx.originalFn.open) { - overrideFunction(ctx.window, 'open', (target, that, args) => { - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return target.apply(that, args); - }); - ctx.proxyToOriginalMp.set(ctx.window.open, ctx.originalFn.open); - }; - return true; - }; - function rewriteEventSource() { - if (ctx.originalFn.EventSource) { - overrideConstructor(ctx.window, 'EventSource', (target, args) => { - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return new target(...args); - }); - overrideAccessors(ctx.window.EventSource.prototype, 'url', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), + Object.defineProperty(ctx.window.Request.prototype, 'url', { + get: new Proxy(requestURL.get, { + apply: (target, that, args) => { + var url = Reflect.apply(target, that, args); + return url ? ctx.url.unwrap(url, ctx.meta) : url; + }, + }), + }); + }; + if (ctx.window.Response) { + const responseURL = Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url'); + Object.defineProperty(ctx.window.Response.prototype, 'url', { + get: new Proxy(responseURL.get, { + apply: (target, that, args) => { + var url = Reflect.apply(target, that, args); + return url ? ctx.url.unwrap(url, ctx.meta) : url; + }, + }), + }); + }; + if (ctx.window.open) { + ctx.window.open = new Proxy(ctx.window.open, { + apply: (target, that, args) => { + if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); + return Reflect.apply(target, that, args) + }, + }); + }; + if (ctx.window.fetch) { + ctx.window.fetch = new Proxy(ctx.window.fetch, { + apply: (target, that, args) => { + if (args[0] instanceof ctx.window.Request) return Reflect.apply(target, that, args); + if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], }); + return Reflect.apply(target, that, args); + }, + }); + }; + if (ctx.window.Navigator && ctx.window.Navigator.prototype.sendBeacon) { + ctx.window.Navigator.prototype.sendBeacon = new Proxy(ctx.window.Navigator.prototype.sendBeacon, { + apply: (target, that, args) => { + if (args[0]) ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], }); + return Reflect.apply(target, that, args); + }, + }); + }; + if (ctx.window.XMLHttpRequest) { + const responseURL = Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL'); + ctx.window.XMLHttpRequest.prototype.open = new Proxy(ctx.window.XMLHttpRequest.prototype.open, { + apply: (target, that, args) => { + if (args[1]) args[1] = ctx.url.wrap(args[1], { ...ctx.meta, flags: ['xhr'], }); + return Reflect.apply(target, that, args); + }, + }); + Object.defineProperty(ctx.window.XMLHttpRequest.prototype, 'responseURL', { + get: new Proxy(responseURL.get, { + apply: (target, that, args) => { + const url = Reflect.apply(target, that, args); + return url ? ctx.url.unwrap(url, ctx.meta) : url; + }, + }), + }); + }; + if (ctx.window.postMessage) { + ctx.window.postMessage = new Proxy(ctx.window.postMessage, { + apply: (target, that, args) => { + if (!ctx.serviceWorker && args[1]) args[1] = ctx.meta.origin; + return Reflect.apply(target, that, args); + }, + }); + }; + if (ctx.window.WebSocket && ctx.config.ws) { + ctx.window.WebSocket = new Proxy(ctx.window.WebSocket, { + construct: (target, args) => { + if (args[0]) args[0] = ctx.url.wrap(args[0].toString().replace('ws', 'http'), ctx.meta).replace('http', 'ws') + '?origin=' + ctx.location.origin; + return Reflect.construct(target, args); + }, }); }; - return true; - }; - return function rewriteHttp() { - rewriteXhr(); - rewriteFetch(); - rewriteWebSocket(); - rewriteOpen(); - rewriteEventSource(); - return true; }; }; diff --git a/lib/browser/iframe.js b/lib/browser/iframe.js deleted file mode 100644 index 88bdbdd3..00000000 --- a/lib/browser/iframe.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/browser/index.js b/lib/browser/index.js index 4be2589d..41337a39 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -1,16 +1,8 @@ -const createAttributeRewriter = require('./attributes'); const createDocumentRewriter = require('./document'); -const createDomRewriter = require('./dom'); -const createFunctionRewriter = require('./function'); const createHistoryRewriter = require('./history'); const createHttpRewriter = require('./http'); -const createIframeRewriter = require('./iframe'); -const createInstrumentation = require('./instrumentation'); -const { createLocation } = require('./location'); -const createMessageRewriter = require('./message'); +const createLocation = require('./location'); const createWorkerRewriter = require('./worker'); -const createStyleRewriter = require('./style'); -const utils = require('./utils'); const defaultConfig = { prefix: '/service/', codec: 'plain', @@ -26,78 +18,178 @@ class Corrosion extends require('../rewrite') { constructor(config = defaultConfig) { super(Object.assign(defaultConfig, config)); const corrosion = this; - this.utils = utils; - this.serviceWorker = this.config.serviceWorker; - this.window = config.window; - this.Window = this.serviceWorker ? null : this.window.Window; - this.Document = this.Window ? this.window.Document : null; - this.docProto = (this.Document || {}).prototype; - this.origin = this.window.location.origin; + if (!this.config.window) throw 'Corrosion Error: No window was given.'; + this.serviceWorker = this.config.serviceWorker || false; + this.window = this.config.window; + this.document = this.serviceWorker ? this.window.document : {}; this._url = new URL((this.config.url || this.url.unwrap(this.window.location.href, { origin: this.window.location.origin, }))); - this.proxyToOriginalMp = new Map(); - this.location = createLocation(this, this._url); + this.originalXhr = this.window.XMLHttpRequest; this.meta = { - origin: this.origin, + origin: this.window.location.origin, get base() { if (corrosion.serviceWorker) return corrosion._url; return corrosion.window.document.baseURI != corrosion.location.href ? new URL(corrosion.window.document.baseURI) : corrosion._url; }, url: this._url, }; - this.originalFn = {}; - this.originalAccessors = {}; - this.rewriteFunction = createFunctionRewriter(this); + this.location = createLocation(this, this._url); this.rewriteHttp = createHttpRewriter(this); this.rewriteDocument = createDocumentRewriter(this); - this.rewriteDom = createDomRewriter(this); - this.rewriteAttributes = createAttributeRewriter(this); - this.rewriteIframes = createIframeRewriter(this); - this.rewriteMessage = createMessageRewriter(this); this.rewriteHistory = createHistoryRewriter(this); this.rewriteWorker = createWorkerRewriter(this); - this.rewriteStyle = createStyleRewriter(this); - this.instrumentation = createInstrumentation(this); - this.windowEvents = []; - this.window.__get$ = this.instrumentation.get; - this.window.__get$Parent = this.instrumentation.getParent; - this.window.__get$Top = this.instrumentation.getTop; - this.window.__get$Loc = this.instrumentation.getLoc; - this.window.__set$ = this.instrumentation.set; - this.window.__set$Loc = this.instrumentation.setLoc; - this.window.__processScript = this.processScript.bind(this); - this.window.__processStyle = this.processStyle.bind(this); - this.window.__processHtml = this.processHtml.bind(this); - this.window.__call$ = this.call.bind(this); - }; - processStyle(val, context) { - return this.css.process(val, { meta: this.meta, context }); + if (!this.serviceWorker && this.window.document.currentScript) this.window.document.currentScript.remove(); }; - processScript(val) { - return this.js.process(val, this.meta.url.href); + get parent() { + if (this.serviceWorker) return false; + try { + return this.window.parent.$corrosion ? this.window.parent : this.window; + } catch(e) { + return this.window; + }; }; - processHtml(val, document = false) { - if (!val) return val; - return this.html.process(val, { document, meta: this.meta, }); - }; - call(obj, method, args) { - return obj[method](...args); - }; - proxyToOriginal(input) { - return this.proxyToOriginalMp.get(input) || input; + get top() { + if (this.serviceWorker) return false; + try { + return this.window.top.$corrosion ? this.window.top : this.parent; + } catch(e) { + return this.parent; + }; }; init() { - this.rewriteFunction(); this.rewriteHttp(); this.rewriteDocument(); - this.rewriteDom(); - this.rewriteAttributes(); - this.rewriteIframes(); - this.rewriteMessage(); this.rewriteHistory(); this.rewriteWorker(); - this.rewriteStyle(); - console.log('Corrosion initiated.'); + this.window.Location = createLocation.Location; + this.window.$corrosionGet$ = this.get$.bind(this); + this.window.$corrosionSet$ = this.set$.bind(this); + this.window.$corrosionGet$m = this.get$m.bind(this); + this.window.$corrosionSet$m = this.set$m.bind(this); + this.window.$corrosionCall$m = this.call$m.bind(this); }; + get$m(obj, key) { + if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) { + return this.parent.$corrosion.get$m(this.parent, key); + }; + if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) { + return this.top.$corrosion.get$m(this.top, key); + }; + if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') return this.location; + if (!this.serviceWorker && obj == this.window && key == 'parent' && this.window != this.window.parent) return this.parent; + if (!this.serviceWorker && obj == this.window && key == 'top' && this.window != this.window.top) return this.top; + return obj[key]; + }; + set$m(obj, key, val, operator) { + if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) { + return this.parent.$corrosion.set$m(this.parent, key, val, operator); + }; + if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) { + return this.top.$corrosion.set$m(this.top, key, val, operator); + }; + if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') obj = this; + switch(operator) { + case '+=': + return obj[key] += val; + case '-=': + return obj[key] -= val; + case '*=': + return obj[key] *= val; + case '/=': + return obj[key] /= val; + case '%=': + return obj[key] %= val; + case '**=': + return obj[key] **= val; + case '<<=': + return obj[key] <<= val; + case '>>=': + return obj[key] >>= val; + case '>>>=': + return obj[key] >>>= val; + case '&=': + return obj[key] &= val; + case '^=': + return obj[key] ^= val; + case '|=': + return obj[key] |= val; + case '&&=': + return obj[key] &&= val; + case '||=': + return obj[key] ||= val; + case '??=': + return obj[key] ??= val; + case '++': + return obj[key]++; + case '--': + return obj[key]--; + case '=': + default: + return obj[key] = val; + }; + }; + call$m(obj, key, args) { + if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) { + return this.parent.$corrosion.call$m(this.parent, key, args); + }; + if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) { + return this.top.$corrosion.call$m(this.top, key, args); + }; + return obj[key](...args); + }; + get$(obj) { + if (obj == this.window.location) return this.location; + if (!this.serviceWorker && obj == this.window.parent) return this.parent; + if (!this.serviceWorker && obj == this.window.top) return this.top; + return obj; + }; + set$(obj, val, operator) { + if (obj == this.window.location) return this.set$(this.location, val, operator); + if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) return this.parent.set$(this.parent, val, operator); + if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) return this.top.set$(this.top, val, operator); + switch(operator) { + case '+=': + return obj += val; + case '-=': + return obj -= val; + case '*=': + return obj *= val; + case '/=': + return obj /= val; + case '%=': + return obj %= val; + case '**=': + return obj **= val; + case '<<=': + return obj <<= val; + case '>>=': + return obj >>= val; + case '>>>=': + return obj >>>= val; + case '&=': + return obj &= val; + case '^=': + return obj ^= val; + case '|=': + return obj |= val; + case '&&=': + return obj &&= val; + case '||=': + return obj ||= val; + case '??=': + return obj ??= val; + case '++': + return obj++; + case '--': + return obj--; + case '=': + default: + return obj = val; + }; + }; + updateLocation() { + this._url = new URL(this.url.unwrap(this.window.location.href, this.meta)); + this.location = createLocation(this, this._url); + }; }; globalThis.Corrosion = Corrosion; \ No newline at end of file diff --git a/lib/browser/instrumentation.js b/lib/browser/instrumentation.js deleted file mode 100644 index a3b247a5..00000000 --- a/lib/browser/instrumentation.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/browser/location.js b/lib/browser/location.js index bf97b6f4..02d7c5a0 100644 --- a/lib/browser/location.js +++ b/lib/browser/location.js @@ -1,21 +1,3 @@ -const { wrapFunction } = require("./utils"); -const locationProperties = [ - 'hash', - 'host', - 'hostname', - 'href', - 'pathname', - 'port', - 'protocol', - 'search', - 'origin', -]; -const locationMethods = [ - 'replace', - 'reload', - 'assign', -]; - class Location { get [Symbol.toPrimitive]() { return () => this.href; @@ -23,12 +5,24 @@ class Location { }; function createLocation(ctx, url) { - const _url = new URL(url); const _location = new Location(); - for (const property of locationProperties) { + const _url = new URL(url); + [ + 'hash', + 'host', + 'hostname', + 'href', + 'pathname', + 'port', + 'protocol', + 'search', + 'origin', + ].forEach(property => { Object.defineProperty(_location, property, { - get: () => _url[property], - set: val => { + get() { + return _url[property]; + }, + set(val) { if (ctx.serviceWorker || property == 'origin') return; if (property == 'href') { return ctx.window.location.href = ctx.url.wrap(new URL(val, _url).href); @@ -37,18 +31,26 @@ function createLocation(ctx, url) { return ctx.window.location.href = ctx.url.wrap(_url); }, }); - }; - if (!ctx.serviceWorker) { - for (const method of locationMethods) { - _location[method] = wrapFunction(ctx.window.location[method], (target, that, args) => { + }); + if (!ctx.serviceWorker) [ + 'assign', + 'replace', + 'reload', + ].forEach(method => { + _location[method] = new Proxy(ctx.window.location[method], { + apply(target, that, args) { if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return target.apply(ctx.window.location, args); - }); - }; - }; - _location.toString = wrapFunction(_url.toString, target => target.call(_url)); - ctx.proxyToOriginalMp.set(_location, ctx.window.location); + return Reflect.apply(target.bind(ctx.window.location), that, args); + }, + }); + }); + _location.toString = new Proxy(_url.toString, { + apply(target, that, args) { + return Reflect.apply(target.bind(_url), that, args); + }, + }); return _location; }; -module.exports = { createLocation, Location, locationMethods, locationProperties }; \ No newline at end of file +createLocation.Location = Location; +module.exports = createLocation; \ No newline at end of file diff --git a/lib/browser/message.js b/lib/browser/message.js deleted file mode 100644 index 2fc37175..00000000 --- a/lib/browser/message.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/browser/style.js b/lib/browser/style.js deleted file mode 100644 index 493c1a78..00000000 --- a/lib/browser/style.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/browser/utils.js b/lib/browser/utils.js deleted file mode 100644 index a389f17e..00000000 --- a/lib/browser/utils.js +++ /dev/null @@ -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 }; \ No newline at end of file diff --git a/lib/browser/worker.js b/lib/browser/worker.js index a32efa9a..c65486c0 100644 --- a/lib/browser/worker.js +++ b/lib/browser/worker.js @@ -1,52 +1,32 @@ -const { overrideFunction, overrideConstructor } = require("./utils"); - -function createWorkerRewriter(ctx) { - if (ctx.window.Worker) ctx.originalFn.Worker = ctx.window.Worker; - if (ctx.serviceWorker && ctx.window.importScripts) ctx.originalFn.importScripts = ctx.window.importScripts; - if (ctx.window.Worklet) ctx.originalFn.WorkletAddModule = ctx.window.Worklet.prototype.addModule; - function rewriteWorkerConstruct() { - if (ctx.originalFn.Worker) { - overrideConstructor(ctx.window, 'Worker', (target, args) => { - if (args[0]) { - if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) { - const xhr = new ctx.window.XMLHttpRequest(); - ctx.proxyToOriginal(ctx.window.XMLHttpRequest.prototype.open).call(xhr, 'GET', args[0], false) - xhr.send(); - const script = ctx.js.process(xhr.responseText, ctx.location.origin + args[0].trim().slice(`blob:${ctx.window.location.origin}`.length)); - const blob = new Blob([ script ], { type: 'application/javascript' }); - args[0] = URL.createObjectURL(blob); - } else { - args[0] = ctx.url.wrap(args[0], ctx.meta); - }; - }; - return new target(...args); - }); - return true; - }; - }; - function rewriteImportScripts() { - if (ctx.originalFn.importScripts) { - overrideFunction(ctx.window, 'importScripts', (target, that, args) => { - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return target.apply(that, args); - }); - }; - return true; - }; - function rewriteWorklet() { - if (ctx.originalFn.WorkletAddModule) { - overrideFunction(ctx.window.Worklet.prototype, 'addModule', (target, that, args) => { - console.log(args[0]); - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return target.apply(that, args); - }); - }; - }; +function createWorkerRewriter(ctx = {}) { return function rewriteWorker() { - rewriteWorkerConstruct(); - rewriteImportScripts(); - rewriteWorklet(); - return true; + if (ctx.window.Worker) { + ctx.window.Worker = new Proxy(ctx.window.Worker, { + construct: (target, args) => { + if (args[0]) { + if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) { + const xhr = new ctx.originalXhr(); + xhr.open('GET', args[0], false); + xhr.send(); + const script = ctx.js.process(xhr.responseText, ctx.location.origin + args[0].trim().slice(`blob:${ctx.window.location.origin}`.length)); + const blob = new Blob([ script ], { type: 'application/javascript' }); + args[0] = URL.createObjectURL(blob); + } else { + args[0] = ctx.url.wrap(args[0], ctx.meta); + }; + }; + return Reflect.construct(target, args); + }, + }); + }; + if (ctx.serviceWorker && ctx.window.importScripts) { + ctx.window.importScripts = new Proxy(ctx.window.importScripts, { + apply: (target, that, args) => { + if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); + return Reflect.apply(target, that, args); + }, + }); + }; }; }; diff --git a/lib/css.js b/lib/css.js new file mode 100644 index 00000000..65e4acce --- /dev/null +++ b/lib/css.js @@ -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; diff --git a/lib/css/import.js b/lib/css/import.js deleted file mode 100644 index edcd585f..00000000 --- a/lib/css/import.js +++ /dev/null @@ -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; - }, -}; \ No newline at end of file diff --git a/lib/css/index.js b/lib/css/index.js deleted file mode 100644 index 4b47f70d..00000000 --- a/lib/css/index.js +++ /dev/null @@ -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; diff --git a/lib/css/url-raw.js b/lib/css/url-raw.js deleted file mode 100644 index e8e2c5dd..00000000 --- a/lib/css/url-raw.js +++ /dev/null @@ -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); - }, -}; \ No newline at end of file diff --git a/lib/css/url-string.js b/lib/css/url-string.js deleted file mode 100644 index 499f7e12..00000000 --- a/lib/css/url-string.js +++ /dev/null @@ -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; - }, -}; \ No newline at end of file diff --git a/lib/js/esotope.js b/lib/esotope.js similarity index 100% rename from lib/js/esotope.js rename to lib/esotope.js diff --git a/lib/html.js b/lib/html.js new file mode 100644 index 00000000..a776a4ca --- /dev/null +++ b/lib/html.js @@ -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; diff --git a/lib/html/attributes.js b/lib/html/attributes.js deleted file mode 100644 index 638e329a..00000000 --- a/lib/html/attributes.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/html/index.js b/lib/html/index.js deleted file mode 100644 index fd5d8236..00000000 --- a/lib/html/index.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/lib/html/parse5.js b/lib/html/parse5.js deleted file mode 100644 index 6a2e2af9..00000000 --- a/lib/html/parse5.js +++ /dev/null @@ -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 }; \ No newline at end of file diff --git a/lib/js.js b/lib/js.js new file mode 100644 index 00000000..7e0b3a49 --- /dev/null +++ b/lib/js.js @@ -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; diff --git a/lib/js/assignment-location.js b/lib/js/assignment-location.js deleted file mode 100644 index e4463c96..00000000 --- a/lib/js/assignment-location.js +++ /dev/null @@ -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; - }, -}; \ No newline at end of file diff --git a/lib/js/assignment-property.js b/lib/js/assignment-property.js deleted file mode 100644 index eb1feb2a..00000000 --- a/lib/js/assignment-property.js +++ /dev/null @@ -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; - }, -}; \ No newline at end of file diff --git a/lib/js/eval.js b/lib/js/eval.js deleted file mode 100644 index 93dbf714..00000000 --- a/lib/js/eval.js +++ /dev/null @@ -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; - }, -}; \ No newline at end of file diff --git a/lib/js/func-args.js b/lib/js/func-args.js deleted file mode 100644 index 656b14c3..00000000 --- a/lib/js/func-args.js +++ /dev/null @@ -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; - } - }; -}; \ No newline at end of file diff --git a/lib/js/identifier.js b/lib/js/identifier.js deleted file mode 100644 index 0196e4ca..00000000 --- a/lib/js/identifier.js +++ /dev/null @@ -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; - }, -}; \ No newline at end of file diff --git a/lib/js/index.js b/lib/js/index.js deleted file mode 100644 index 6e5454fe..00000000 --- a/lib/js/index.js +++ /dev/null @@ -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; diff --git a/lib/js/method-call.js b/lib/js/method-call.js deleted file mode 100644 index 34f30eed..00000000 --- a/lib/js/method-call.js +++ /dev/null @@ -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) ])); - }, -}; \ No newline at end of file diff --git a/lib/js/node-builder.js b/lib/js/node-builder.js deleted file mode 100644 index 078bc4b7..00000000 --- a/lib/js/node-builder.js +++ /dev/null @@ -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, }; \ No newline at end of file diff --git a/lib/js/property.js b/lib/js/property.js deleted file mode 100644 index f60b6ead..00000000 --- a/lib/js/property.js +++ /dev/null @@ -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; - }, -}; \ No newline at end of file diff --git a/lib/js/utils.js b/lib/js/utils.js deleted file mode 100644 index 5682f605..00000000 --- a/lib/js/utils.js +++ /dev/null @@ -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, }; \ No newline at end of file diff --git a/lib/rewrite.js b/lib/rewrite.js index c3839850..0c981f28 100644 --- a/lib/rewrite.js +++ b/lib/rewrite.js @@ -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); diff --git a/lib/server/bundle.js b/lib/server/bundle.js deleted file mode 100644 index 26260b33..00000000 --- a/lib/server/bundle.js +++ /dev/null @@ -1,34006 +0,0 @@ -/******/ (() => { // webpackBootstrap -/******/ var __webpack_modules__ = ([ -/* 0 */, -/* 1 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideAccessors } = __webpack_require__(2); - -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; - -/***/ }), -/* 2 */ -/***/ ((module) => { - -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 }; - -/***/ }), -/* 3 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideAccessors, overrideFunction } = __webpack_require__(2); - -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 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) : ''), - }); - }; - 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, - }); - }; - return true; - }; - function rewriteUrl() { - if (ctx.originalAccessors.DocumentURL) { - overrideAccessors(ctx.docProto, 'URL', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), - }); - }; - if (ctx.originalAccessors.DocumentURI) { - overrideAccessors(ctx.docProto, 'documentURI', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), - }); - }; - 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); - }); - }; - 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); - }); - }; - }; - return function rewriteDocument() { - rewriteCookie(); - rewriteUrl(); - rewriteTitle(); - rewriteDomain(); - rewriteWrite(); - return true; - }; -}; - -module.exports = createDocumentRewriter; - -/***/ }), -/* 4 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideAccessors, overrideFunction, overrideConstructor } = __webpack_require__(2); - -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; - -/***/ }), -/* 5 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideFunction, overrideAccessors, overrideConstructor, wrapFnString } = __webpack_require__(2); - -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; - -/***/ }), -/* 6 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideFunction } = __webpack_require__(2); - -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; - }; -}; - -module.exports = createHistoryRewriter; - -/***/ }), -/* 7 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideFunction, overrideConstructor, overrideAccessors } = __webpack_require__(2); - -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; - }, - }); - ctx.proxyToOriginalMp.set(Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL').get, ctx.originalAccessors.xhrResponseUrl.get); - }; - return true; - }; - function rewriteWebSocket() { - if (ctx.originalFn.WebSocket) { - overrideConstructor(ctx.window, 'WebSocket', (target, args) => { - try { - let url = new URL(args[0]); - args[0] = ['wss:', 'ws:'].includes(url.protocol) ? ctx.url.wrap(url.href.replace('ws', 'http'), ctx.meta).replace('http', 'ws') + '?origin=' + ctx.codec.base64.encode(ctx.location.origin) : ''; - } catch(e) { - args[0] = ''; - }; - return new target(...args); - }); - }; - }; - function rewriteOpen() { - if (ctx.originalFn.open) { - overrideFunction(ctx.window, 'open', (target, that, args) => { - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return target.apply(that, args); - }); - ctx.proxyToOriginalMp.set(ctx.window.open, ctx.originalFn.open); - }; - return true; - }; - function rewriteEventSource() { - if (ctx.originalFn.EventSource) { - overrideConstructor(ctx.window, 'EventSource', (target, args) => { - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return new target(...args); - }); - overrideAccessors(ctx.window.EventSource.prototype, 'url', { - getter: (target, that) => ctx.url.unwrap(target.call(that), ctx.meta), - }); - }; - return true; - }; - return function rewriteHttp() { - rewriteXhr(); - rewriteFetch(); - rewriteWebSocket(); - rewriteOpen(); - rewriteEventSource(); - return true; - }; -}; - -module.exports = createHttpRewriter; - -/***/ }), -/* 8 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideAccessors } = __webpack_require__(2); - -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; - -/***/ }), -/* 9 */ -/***/ ((module) => { - -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; - -/***/ }), -/* 10 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { wrapFunction } = __webpack_require__(2); -const locationProperties = [ - 'hash', - 'host', - 'hostname', - 'href', - 'pathname', - 'port', - '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) { - Object.defineProperty(_location, property, { - get: () => _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); - }; - _url[property] = val; - 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); - return _location; -}; - -module.exports = { createLocation, Location, locationMethods, locationProperties }; - -/***/ }), -/* 11 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideFunction, wrapFunction, overrideAccessors } = __webpack_require__(2); - -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; - -/***/ }), -/* 12 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideFunction, overrideConstructor } = __webpack_require__(2); - -function createWorkerRewriter(ctx) { - if (ctx.window.Worker) ctx.originalFn.Worker = ctx.window.Worker; - if (ctx.serviceWorker && ctx.window.importScripts) ctx.originalFn.importScripts = ctx.window.importScripts; - if (ctx.window.Worklet) ctx.originalFn.WorkletAddModule = ctx.window.Worklet.prototype.addModule; - function rewriteWorkerConstruct() { - if (ctx.originalFn.Worker) { - overrideConstructor(ctx.window, 'Worker', (target, args) => { - if (args[0]) { - if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) { - const xhr = new ctx.window.XMLHttpRequest(); - ctx.proxyToOriginal(ctx.window.XMLHttpRequest.prototype.open).call(xhr, 'GET', args[0], false) - xhr.send(); - const script = ctx.js.process(xhr.responseText, ctx.location.origin + args[0].trim().slice(`blob:${ctx.window.location.origin}`.length)); - const blob = new Blob([ script ], { type: 'application/javascript' }); - args[0] = URL.createObjectURL(blob); - } else { - args[0] = ctx.url.wrap(args[0], ctx.meta); - }; - }; - return new target(...args); - }); - return true; - }; - }; - function rewriteImportScripts() { - if (ctx.originalFn.importScripts) { - overrideFunction(ctx.window, 'importScripts', (target, that, args) => { - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return target.apply(that, args); - }); - }; - return true; - }; - function rewriteWorklet() { - if (ctx.originalFn.WorkletAddModule) { - overrideFunction(ctx.window.Worklet.prototype, 'addModule', (target, that, args) => { - console.log(args[0]); - if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); - return target.apply(that, args); - }); - }; - }; - return function rewriteWorker() { - rewriteWorkerConstruct(); - rewriteImportScripts(); - rewriteWorklet(); - return true; - }; -}; - -module.exports = createWorkerRewriter; - -/***/ }), -/* 13 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const { overrideAccessors, overrideFunction } = __webpack_require__(2); - -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; - -/***/ }), -/* 14 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -// ------------------------------------------------------------- -// WARNING: this file is used by both the client and the server. -// Do not use any browser or node-specific API! -// ------------------------------------------------------------- -const URLWrapper = __webpack_require__(15); -const CookieRewriter = __webpack_require__(17); -const CSSRewriter = __webpack_require__(19); -const HTMLRewriter = __webpack_require__(145); -const JSRewriter = __webpack_require__(172); -const codec = __webpack_require__(16); -const defaultConfig = { - prefix: '/service/', - codec: 'plain', - ws: true, - cookie: true, - title: 'Service', -}; - -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.cookies = new CookieRewriter(this); - this.css = new CSSRewriter(this); - this.js = new JSRewriter(this); - this.html = new HTMLRewriter(this); - }; -}; - -module.exports = Rewrite; - - -/***/ }), -/* 15 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -// ------------------------------------------------------------- -// WARNING: this file is used by both the client and the server. -// Do not use any browser or node-specific API! -// ------------------------------------------------------------- -const codec = __webpack_require__(16); -const defaultConfig = { - prefix: '/service/', - codec: 'plain' -}; - -class URLWrapper { - constructor(config = defaultConfig, ctx) { - 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; - }; - 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)); - }; - 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); - }; - 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; - }; -} - -module.exports = URLWrapper; - - -/***/ }), -/* 16 */ -/***/ ((__unused_webpack_module, exports) => { - -// ------------------------------------------------------------- -// WARNING: this file is used by both the client and the server. -// Do not use any browser or node-specific API! -// ------------------------------------------------------------- -exports.xor = { - encode(str){ - if (!str) return str; - return encodeURIComponent(str.toString().split('').map((char, ind) => ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char).join('')); - }, - decode(str){ - if (!str) return str; - return decodeURIComponent(str).split('').map((char, ind) => ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char).join(''); - }, -}; -exports.plain = { - encode(str) { - if (!str) return str; - return encodeURIComponent(str); - }, - decode(str) { - if (!str) return str; - return decodeURIComponent(str); - }, -}; -exports.base64 = { - encode(str){ - if (!str) return str; - str = str.toString(); - const b64chs = Array.from('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='); - let u32; - let c0; - let c1; - let c2; - let asc = ''; - let pad = str.length % 3; - - for (let i = 0; i < str.length;) { - if((c0 = str.charCodeAt(i++)) > 255 || (c1 = str.charCodeAt(i++)) > 255 || (c2 = str.charCodeAt(i++)) > 255)throw new TypeError('invalid character found'); - u32 = (c0 << 16) | (c1 << 8) | c2; - asc += b64chs[u32 >> 18 & 63] - + b64chs[u32 >> 12 & 63] - + b64chs[u32 >> 6 & 63] - + b64chs[u32 & 63]; - } - - return encodeURIComponent(pad ? asc.slice(0, pad - 3) + '==='.substr(pad) : asc); - }, - decode(str){ - if (!str) return str; - str = decodeURIComponent(str.toString()); - const b64tab = {"0":52,"1":53,"2":54,"3":55,"4":56,"5":57,"6":58,"7":59,"8":60,"9":61,"A":0,"B":1,"C":2,"D":3,"E":4,"F":5,"G":6,"H":7,"I":8,"J":9,"K":10,"L":11,"M":12,"N":13,"O":14,"P":15,"Q":16,"R":17,"S":18,"T":19,"U":20,"V":21,"W":22,"X":23,"Y":24,"Z":25,"a":26,"b":27,"c":28,"d":29,"e":30,"f":31,"g":32,"h":33,"i":34,"j":35,"k":36,"l":37,"m":38,"n":39,"o":40,"p":41,"q":42,"r":43,"s":44,"t":45,"u":46,"v":47,"w":48,"x":49,"y":50,"z":51,"+":62,"/":63,"=":64}; - str = str.replace(/\s+/g, ''); - str += '=='.slice(2 - (str.length & 3)); - let u24; - let bin = ''; - let r1; - let r2; - - for (let i = 0; i < str.length;) { - u24 = b64tab[str.charAt(i++)] << 18 - | b64tab[str.charAt(i++)] << 12 - | (r1 = b64tab[str.charAt(i++)]) << 6 - | (r2 = b64tab[str.charAt(i++)]); - bin += r1 === 64 ? String.fromCharCode(u24 >> 16 & 255) - : r2 === 64 ? String.fromCharCode(u24 >> 16 & 255, u24 >> 8 & 255) - : String.fromCharCode(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255); - }; - return bin; - }, -}; - - -/***/ }), -/* 17 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -// ------------------------------------------------------------- -// WARNING: this file is used by both the client and the server. -// Do not use any browser or node-specific API! -// ------------------------------------------------------------- -const { SetCookie, CookieStore } = __webpack_require__(18); - -class CookieRewriter { - constructor(ctx) { - this.ctx = ctx; - }; - decode(store, config = {}) { - const url = new URL(config.url); - const cookies = new CookieStore(store); - cookies.forEach((val, key) => { - if (!key.includes('@') || key.slice(key.length - url.hostname.length) != url.hostname) return cookies.delete(key); - cookies.delete(key); - cookies.set(key.substr(0, key.length - url.hostname.length - 1), val); - }); - return cookies.serialize(); - }; - encode(input, config = {}) { - if (Array.isArray(input)) { - const rw = [ ...input ]; - for (let i in rw) rw[i] = this.encode(rw[i], config); - return rw; - }; - const url = new URL(config.url); - const cookie = new SetCookie(input); - if (!cookie.name) return null; - cookie.domain = config.domain; - cookie.secure = config.secure; - cookie.name += `@${url.hostname}`; - cookie.path = this.ctx.prefix; - return cookie.serialize() || ''; - }; -}; - -module.exports = CookieRewriter; - - -/***/ }), -/* 18 */ -/***/ ((__unused_webpack_module, exports) => { - -// ------------------------------------------------------------- -// WARNING: this file is used by both the client and the server. -// Do not use any browser or node-specific API! -// ------------------------------------------------------------- -class CookieStore { - constructor(val = ''){ - this.data = {}; - val.split(';').map(cookie => { - var [ name, val = ''] = cookie.trimStart().split('='); - if (name) this.data[name] = val; - }); - }; - has(name){ - if (!name || !this.data[name]) return false; - return true; - }; - get(name){ - return this.has(name) ? this.data[name] : null; - }; - set(name, val){ - if (!name || !val) return; - return this.data[name] = val; - }; - delete(name){ - if (!name) return; - return delete this.data[name]; - }; - forEach(action = (node, key) => null){ - for (let prop in this.data) action(this.data[prop], prop); - }; - serialize(){ - var str = ''; - for (let i in this.data) str += ` ${i}=${this.data[i]};`; - return str.substr(1); - }; -}; - -class SetCookie { - constructor(val = ''){ - - var [ [ name, value = '' ], ...data ] = val.split(';').map(str => str.trimStart().split('=')); - - this.name = name; - this.value = value; - this.expires = null; - this.maxAge = null; - this.domain = null; - this.secure = false; - this.httpOnly = false; - this.path = null; - this.sameSite = null; - - data.forEach(([name = null, value = null]) => { - if (typeof name == 'string') switch(name.toLowerCase()){ - case 'domain': - this.domain = value; - break; - case 'secure': - this.secure = true; - break; - case 'httponly': - this.httpOnly = true; - break; - case 'samesite': - this.sameSite = value; - break; - case 'path': - this.path = value; - break; - case 'expires': - this.expires = value; - break; - case 'maxage': - this.maxAge = value; - break; - }; - }); - }; - serialize(){ - if (!this.name) return; - var str = `${this.name}=${this.value};`; - if (this.expires) str += ` Expires=${this.expires};`; - if (this.maxAge) str += ` Max-Age=${this.max_age};`; - if (this.domain) str += ` Domain=${this.domain};`; - if (this.secure) str += ` Secure;`; - if (this.httpOnly) str += ` HttpOnly;`; - if (this.path) str += ` Path=${this.path};`; - if (this.sameSite) str += ` SameSite=${this.sameSite};`; - return str; - }; -}; - -exports.CookieStore = CookieStore; -exports.SetCookie = SetCookie; - - -/***/ }), -/* 19 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -// ------------------------------------------------------------- -// WARNING: this file is used by both the client and the server. -// Do not use any browser or node-specific API! -// ------------------------------------------------------------- -const csstree = __webpack_require__(20); -const rawMap = [ - __webpack_require__(142), - __webpack_require__(143), - __webpack_require__(144), -]; - -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; - - -/***/ }), -/* 20 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -module.exports = __webpack_require__(21); - - -/***/ }), -/* 21 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -function merge() { - var dest = {}; - - for (var i = 0; i < arguments.length; i++) { - var src = arguments[i]; - for (var key in src) { - dest[key] = src[key]; - } - } - - return dest; -} - -module.exports = __webpack_require__(22).create( - merge( - __webpack_require__(65), - __webpack_require__(112), - __webpack_require__(140) - ) -); -module.exports.version = __webpack_require__(141).version; - - -/***/ }), -/* 22 */ -/***/ ((__unused_webpack_module, exports, __webpack_require__) => { - -var List = __webpack_require__(23); -var SyntaxError = __webpack_require__(24); -var TokenStream = __webpack_require__(26); -var Lexer = __webpack_require__(30); -var definitionSyntax = __webpack_require__(49); -var tokenize = __webpack_require__(35); -var createParser = __webpack_require__(50); -var createGenerator = __webpack_require__(53); -var createConvertor = __webpack_require__(61); -var createWalker = __webpack_require__(62); -var clone = __webpack_require__(63); -var names = __webpack_require__(33); -var mix = __webpack_require__(64); - -function createSyntax(config) { - var parse = createParser(config); - var walk = createWalker(config); - var generate = createGenerator(config); - var convert = createConvertor(walk); - - var syntax = { - List: List, - SyntaxError: SyntaxError, - TokenStream: TokenStream, - Lexer: Lexer, - - vendorPrefix: names.vendorPrefix, - keyword: names.keyword, - property: names.property, - isCustomProperty: names.isCustomProperty, - - definitionSyntax: definitionSyntax, - lexer: null, - createLexer: function(config) { - return new Lexer(config, syntax, syntax.lexer.structure); - }, - - tokenize: tokenize, - parse: parse, - walk: walk, - generate: generate, - - find: walk.find, - findLast: walk.findLast, - findAll: walk.findAll, - - clone: clone, - fromPlainObject: convert.fromPlainObject, - toPlainObject: convert.toPlainObject, - - createSyntax: function(config) { - return createSyntax(mix({}, config)); - }, - fork: function(extension) { - var base = mix({}, config); // copy of config - return createSyntax( - typeof extension === 'function' - ? extension(base, Object.assign) - : mix(base, extension) - ); - } - }; - - syntax.lexer = new Lexer({ - generic: true, - types: config.types, - atrules: config.atrules, - properties: config.properties, - node: config.node - }, syntax); - - return syntax; -}; - -exports.create = function(config) { - return createSyntax(mix({}, config)); -}; - - -/***/ }), -/* 23 */ -/***/ ((module) => { - -// -// list -// ┌──────┐ -// ┌──────────────┼─head │ -// │ │ tail─┼──────────────┐ -// │ └──────┘ │ -// ▼ ▼ -// item item item item -// ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ -// null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │ -// │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null -// ├──────┤ ├──────┤ ├──────┤ ├──────┤ -// │ data │ │ data │ │ data │ │ data │ -// └──────┘ └──────┘ └──────┘ └──────┘ -// - -function createItem(data) { - return { - prev: null, - next: null, - data: data - }; -} - -function allocateCursor(node, prev, next) { - var cursor; - - if (cursors !== null) { - cursor = cursors; - cursors = cursors.cursor; - cursor.prev = prev; - cursor.next = next; - cursor.cursor = node.cursor; - } else { - cursor = { - prev: prev, - next: next, - cursor: node.cursor - }; - } - - node.cursor = cursor; - - return cursor; -} - -function releaseCursor(node) { - var cursor = node.cursor; - - node.cursor = cursor.cursor; - cursor.prev = null; - cursor.next = null; - cursor.cursor = cursors; - cursors = cursor; -} - -var cursors = null; -var List = function() { - this.cursor = null; - this.head = null; - this.tail = null; -}; - -List.createItem = createItem; -List.prototype.createItem = createItem; - -List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) { - var cursor = this.cursor; - - while (cursor !== null) { - if (cursor.prev === prevOld) { - cursor.prev = prevNew; - } - - if (cursor.next === nextOld) { - cursor.next = nextNew; - } - - cursor = cursor.cursor; - } -}; - -List.prototype.getSize = function() { - var size = 0; - var cursor = this.head; - - while (cursor) { - size++; - cursor = cursor.next; - } - - return size; -}; - -List.prototype.fromArray = function(array) { - var cursor = null; - - this.head = null; - - for (var i = 0; i < array.length; i++) { - var item = createItem(array[i]); - - if (cursor !== null) { - cursor.next = item; - } else { - this.head = item; - } - - item.prev = cursor; - cursor = item; - } - - this.tail = cursor; - - return this; -}; - -List.prototype.toArray = function() { - var cursor = this.head; - var result = []; - - while (cursor) { - result.push(cursor.data); - cursor = cursor.next; - } - - return result; -}; - -List.prototype.toJSON = List.prototype.toArray; - -List.prototype.isEmpty = function() { - return this.head === null; -}; - -List.prototype.first = function() { - return this.head && this.head.data; -}; - -List.prototype.last = function() { - return this.tail && this.tail.data; -}; - -List.prototype.each = function(fn, context) { - var item; - - if (context === undefined) { - context = this; - } - - // push cursor - var cursor = allocateCursor(this, null, this.head); - - while (cursor.next !== null) { - item = cursor.next; - cursor.next = item.next; - - fn.call(context, item.data, item, this); - } - - // pop cursor - releaseCursor(this); -}; - -List.prototype.forEach = List.prototype.each; - -List.prototype.eachRight = function(fn, context) { - var item; - - if (context === undefined) { - context = this; - } - - // push cursor - var cursor = allocateCursor(this, this.tail, null); - - while (cursor.prev !== null) { - item = cursor.prev; - cursor.prev = item.prev; - - fn.call(context, item.data, item, this); - } - - // pop cursor - releaseCursor(this); -}; - -List.prototype.forEachRight = List.prototype.eachRight; - -List.prototype.reduce = function(fn, initialValue, context) { - var item; - - if (context === undefined) { - context = this; - } - - // push cursor - var cursor = allocateCursor(this, null, this.head); - var acc = initialValue; - - while (cursor.next !== null) { - item = cursor.next; - cursor.next = item.next; - - acc = fn.call(context, acc, item.data, item, this); - } - - // pop cursor - releaseCursor(this); - - return acc; -}; - -List.prototype.reduceRight = function(fn, initialValue, context) { - var item; - - if (context === undefined) { - context = this; - } - - // push cursor - var cursor = allocateCursor(this, this.tail, null); - var acc = initialValue; - - while (cursor.prev !== null) { - item = cursor.prev; - cursor.prev = item.prev; - - acc = fn.call(context, acc, item.data, item, this); - } - - // pop cursor - releaseCursor(this); - - return acc; -}; - -List.prototype.nextUntil = function(start, fn, context) { - if (start === null) { - return; - } - - var item; - - if (context === undefined) { - context = this; - } - - // push cursor - var cursor = allocateCursor(this, null, start); - - while (cursor.next !== null) { - item = cursor.next; - cursor.next = item.next; - - if (fn.call(context, item.data, item, this)) { - break; - } - } - - // pop cursor - releaseCursor(this); -}; - -List.prototype.prevUntil = function(start, fn, context) { - if (start === null) { - return; - } - - var item; - - if (context === undefined) { - context = this; - } - - // push cursor - var cursor = allocateCursor(this, start, null); - - while (cursor.prev !== null) { - item = cursor.prev; - cursor.prev = item.prev; - - if (fn.call(context, item.data, item, this)) { - break; - } - } - - // pop cursor - releaseCursor(this); -}; - -List.prototype.some = function(fn, context) { - var cursor = this.head; - - if (context === undefined) { - context = this; - } - - while (cursor !== null) { - if (fn.call(context, cursor.data, cursor, this)) { - return true; - } - - cursor = cursor.next; - } - - return false; -}; - -List.prototype.map = function(fn, context) { - var result = new List(); - var cursor = this.head; - - if (context === undefined) { - context = this; - } - - while (cursor !== null) { - result.appendData(fn.call(context, cursor.data, cursor, this)); - cursor = cursor.next; - } - - return result; -}; - -List.prototype.filter = function(fn, context) { - var result = new List(); - var cursor = this.head; - - if (context === undefined) { - context = this; - } - - while (cursor !== null) { - if (fn.call(context, cursor.data, cursor, this)) { - result.appendData(cursor.data); - } - cursor = cursor.next; - } - - return result; -}; - -List.prototype.clear = function() { - this.head = null; - this.tail = null; -}; - -List.prototype.copy = function() { - var result = new List(); - var cursor = this.head; - - while (cursor !== null) { - result.insert(createItem(cursor.data)); - cursor = cursor.next; - } - - return result; -}; - -List.prototype.prepend = function(item) { - // head - // ^ - // item - this.updateCursors(null, item, this.head, item); - - // insert to the beginning of the list - if (this.head !== null) { - // new item <- first item - this.head.prev = item; - - // new item -> first item - item.next = this.head; - } else { - // if list has no head, then it also has no tail - // in this case tail points to the new item - this.tail = item; - } - - // head always points to new item - this.head = item; - - return this; -}; - -List.prototype.prependData = function(data) { - return this.prepend(createItem(data)); -}; - -List.prototype.append = function(item) { - return this.insert(item); -}; - -List.prototype.appendData = function(data) { - return this.insert(createItem(data)); -}; - -List.prototype.insert = function(item, before) { - if (before !== undefined && before !== null) { - // prev before - // ^ - // item - this.updateCursors(before.prev, item, before, item); - - if (before.prev === null) { - // insert to the beginning of list - if (this.head !== before) { - throw new Error('before doesn\'t belong to list'); - } - - // since head points to before therefore list doesn't empty - // no need to check tail - this.head = item; - before.prev = item; - item.next = before; - - this.updateCursors(null, item); - } else { - - // insert between two items - before.prev.next = item; - item.prev = before.prev; - - before.prev = item; - item.next = before; - } - } else { - // tail - // ^ - // item - this.updateCursors(this.tail, item, null, item); - - // insert to the ending of the list - if (this.tail !== null) { - // last item -> new item - this.tail.next = item; - - // last item <- new item - item.prev = this.tail; - } else { - // if list has no tail, then it also has no head - // in this case head points to new item - this.head = item; - } - - // tail always points to new item - this.tail = item; - } - - return this; -}; - -List.prototype.insertData = function(data, before) { - return this.insert(createItem(data), before); -}; - -List.prototype.remove = function(item) { - // item - // ^ - // prev next - this.updateCursors(item, item.prev, item, item.next); - - if (item.prev !== null) { - item.prev.next = item.next; - } else { - if (this.head !== item) { - throw new Error('item doesn\'t belong to list'); - } - - this.head = item.next; - } - - if (item.next !== null) { - item.next.prev = item.prev; - } else { - if (this.tail !== item) { - throw new Error('item doesn\'t belong to list'); - } - - this.tail = item.prev; - } - - item.prev = null; - item.next = null; - - return item; -}; - -List.prototype.push = function(data) { - this.insert(createItem(data)); -}; - -List.prototype.pop = function() { - if (this.tail !== null) { - return this.remove(this.tail); - } -}; - -List.prototype.unshift = function(data) { - this.prepend(createItem(data)); -}; - -List.prototype.shift = function() { - if (this.head !== null) { - return this.remove(this.head); - } -}; - -List.prototype.prependList = function(list) { - return this.insertList(list, this.head); -}; - -List.prototype.appendList = function(list) { - return this.insertList(list); -}; - -List.prototype.insertList = function(list, before) { - // ignore empty lists - if (list.head === null) { - return this; - } - - if (before !== undefined && before !== null) { - this.updateCursors(before.prev, list.tail, before, list.head); - - // insert in the middle of dist list - if (before.prev !== null) { - // before.prev <-> list.head - before.prev.next = list.head; - list.head.prev = before.prev; - } else { - this.head = list.head; - } - - before.prev = list.tail; - list.tail.next = before; - } else { - this.updateCursors(this.tail, list.tail, null, list.head); - - // insert to end of the list - if (this.tail !== null) { - // if destination list has a tail, then it also has a head, - // but head doesn't change - - // dest tail -> source head - this.tail.next = list.head; - - // dest tail <- source head - list.head.prev = this.tail; - } else { - // if list has no a tail, then it also has no a head - // in this case points head to new item - this.head = list.head; - } - - // tail always start point to new item - this.tail = list.tail; - } - - list.head = null; - list.tail = null; - - return this; -}; - -List.prototype.replace = function(oldItem, newItemOrList) { - if ('head' in newItemOrList) { - this.insertList(newItemOrList, oldItem); - } else { - this.insert(newItemOrList, oldItem); - } - - this.remove(oldItem); -}; - -module.exports = List; - - -/***/ }), -/* 24 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -var createCustomError = __webpack_require__(25); -var MAX_LINE_LENGTH = 100; -var OFFSET_CORRECTION = 60; -var TAB_REPLACEMENT = ' '; - -function sourceFragment(error, extraLines) { - function processLines(start, end) { - return lines.slice(start, end).map(function(line, idx) { - var num = String(start + idx + 1); - - while (num.length < maxNumLength) { - num = ' ' + num; - } - - return num + ' |' + line; - }).join('\n'); - } - - var lines = error.source.split(/\r\n?|\n|\f/); - var line = error.line; - var column = error.column; - var startLine = Math.max(1, line - extraLines) - 1; - var endLine = Math.min(line + extraLines, lines.length + 1); - var maxNumLength = Math.max(4, String(endLine).length) + 1; - var cutLeft = 0; - - // column correction according to replaced tab before column - column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length; - - if (column > MAX_LINE_LENGTH) { - cutLeft = column - OFFSET_CORRECTION + 3; - column = OFFSET_CORRECTION - 2; - } - - for (var i = startLine; i <= endLine; i++) { - if (i >= 0 && i < lines.length) { - lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT); - lines[i] = - (cutLeft > 0 && lines[i].length > cutLeft ? '\u2026' : '') + - lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) + - (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\u2026' : ''); - } - } - - return [ - processLines(startLine, line), - new Array(column + maxNumLength + 2).join('-') + '^', - processLines(line, endLine) - ].filter(Boolean).join('\n'); -} - -var SyntaxError = function(message, source, offset, line, column) { - var error = createCustomError('SyntaxError', message); - - error.source = source; - error.offset = offset; - error.line = line; - error.column = column; - - error.sourceFragment = function(extraLines) { - return sourceFragment(error, isNaN(extraLines) ? 0 : extraLines); - }; - Object.defineProperty(error, 'formattedMessage', { - get: function() { - return ( - 'Parse error: ' + error.message + '\n' + - sourceFragment(error, 2) - ); - } - }); - - // for backward capability - error.parseError = { - offset: offset, - line: line, - column: column - }; - - return error; -}; - -module.exports = SyntaxError; - - -/***/ }), -/* 25 */ -/***/ ((module) => { - -module.exports = function createCustomError(name, message) { - // use Object.create(), because some VMs prevent setting line/column otherwise - // (iOS Safari 10 even throws an exception) - var error = Object.create(SyntaxError.prototype); - var errorStack = new Error(); - - error.name = name; - error.message = message; - - Object.defineProperty(error, 'stack', { - get: function() { - return (errorStack.stack || '').replace(/^(.+\n){1,3}/, name + ': ' + message + '\n'); - } - }); - - return error; -}; - - -/***/ }), -/* 26 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -var constants = __webpack_require__(27); -var TYPE = constants.TYPE; -var NAME = constants.NAME; - -var utils = __webpack_require__(28); -var cmpStr = utils.cmpStr; - -var EOF = TYPE.EOF; -var WHITESPACE = TYPE.WhiteSpace; -var COMMENT = TYPE.Comment; - -var OFFSET_MASK = 0x00FFFFFF; -var TYPE_SHIFT = 24; - -var TokenStream = function() { - this.offsetAndType = null; - this.balance = null; - - this.reset(); -}; - -TokenStream.prototype = { - reset: function() { - this.eof = false; - this.tokenIndex = -1; - this.tokenType = 0; - this.tokenStart = this.firstCharOffset; - this.tokenEnd = this.firstCharOffset; - }, - - lookupType: function(offset) { - offset += this.tokenIndex; - - if (offset < this.tokenCount) { - return this.offsetAndType[offset] >> TYPE_SHIFT; - } - - return EOF; - }, - lookupOffset: function(offset) { - offset += this.tokenIndex; - - if (offset < this.tokenCount) { - return this.offsetAndType[offset - 1] & OFFSET_MASK; - } - - return this.source.length; - }, - lookupValue: function(offset, referenceStr) { - offset += this.tokenIndex; - - if (offset < this.tokenCount) { - return cmpStr( - this.source, - this.offsetAndType[offset - 1] & OFFSET_MASK, - this.offsetAndType[offset] & OFFSET_MASK, - referenceStr - ); - } - - return false; - }, - getTokenStart: function(tokenIndex) { - if (tokenIndex === this.tokenIndex) { - return this.tokenStart; - } - - if (tokenIndex > 0) { - return tokenIndex < this.tokenCount - ? this.offsetAndType[tokenIndex - 1] & OFFSET_MASK - : this.offsetAndType[this.tokenCount] & OFFSET_MASK; - } - - return this.firstCharOffset; - }, - - // TODO: -> skipUntilBalanced - getRawLength: function(startToken, mode) { - var cursor = startToken; - var balanceEnd; - var offset = this.offsetAndType[Math.max(cursor - 1, 0)] & OFFSET_MASK; - var type; - - loop: - for (; cursor < this.tokenCount; cursor++) { - balanceEnd = this.balance[cursor]; - - // stop scanning on balance edge that points to offset before start token - if (balanceEnd < startToken) { - break loop; - } - - type = this.offsetAndType[cursor] >> TYPE_SHIFT; - - // check token is stop type - switch (mode(type, this.source, offset)) { - case 1: - break loop; - - case 2: - cursor++; - break loop; - - default: - // fast forward to the end of balanced block - if (this.balance[balanceEnd] === cursor) { - cursor = balanceEnd; - } - - offset = this.offsetAndType[cursor] & OFFSET_MASK; - } - } - - return cursor - this.tokenIndex; - }, - isBalanceEdge: function(pos) { - return this.balance[this.tokenIndex] < pos; - }, - isDelim: function(code, offset) { - if (offset) { - return ( - this.lookupType(offset) === TYPE.Delim && - this.source.charCodeAt(this.lookupOffset(offset)) === code - ); - } - - return ( - this.tokenType === TYPE.Delim && - this.source.charCodeAt(this.tokenStart) === code - ); - }, - - getTokenValue: function() { - return this.source.substring(this.tokenStart, this.tokenEnd); - }, - getTokenLength: function() { - return this.tokenEnd - this.tokenStart; - }, - substrToCursor: function(start) { - return this.source.substring(start, this.tokenStart); - }, - - skipWS: function() { - for (var i = this.tokenIndex, skipTokenCount = 0; i < this.tokenCount; i++, skipTokenCount++) { - if ((this.offsetAndType[i] >> TYPE_SHIFT) !== WHITESPACE) { - break; - } - } - - if (skipTokenCount > 0) { - this.skip(skipTokenCount); - } - }, - skipSC: function() { - while (this.tokenType === WHITESPACE || this.tokenType === COMMENT) { - this.next(); - } - }, - skip: function(tokenCount) { - var next = this.tokenIndex + tokenCount; - - if (next < this.tokenCount) { - this.tokenIndex = next; - this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK; - next = this.offsetAndType[next]; - this.tokenType = next >> TYPE_SHIFT; - this.tokenEnd = next & OFFSET_MASK; - } else { - this.tokenIndex = this.tokenCount; - this.next(); - } - }, - next: function() { - var next = this.tokenIndex + 1; - - if (next < this.tokenCount) { - this.tokenIndex = next; - this.tokenStart = this.tokenEnd; - next = this.offsetAndType[next]; - this.tokenType = next >> TYPE_SHIFT; - this.tokenEnd = next & OFFSET_MASK; - } else { - this.tokenIndex = this.tokenCount; - this.eof = true; - this.tokenType = EOF; - this.tokenStart = this.tokenEnd = this.source.length; - } - }, - - forEachToken(fn) { - for (var i = 0, offset = this.firstCharOffset; i < this.tokenCount; i++) { - var start = offset; - var item = this.offsetAndType[i]; - var end = item & OFFSET_MASK; - var type = item >> TYPE_SHIFT; - - offset = end; - - fn(type, start, end, i); - } - }, - - dump() { - var tokens = new Array(this.tokenCount); - - this.forEachToken((type, start, end, index) => { - tokens[index] = { - idx: index, - type: NAME[type], - chunk: this.source.substring(start, end), - balance: this.balance[index] - }; - }); - - return tokens; - } -}; - -module.exports = TokenStream; - - -/***/ }), -/* 27 */ -/***/ ((module) => { - -// CSS Syntax Module Level 3 -// https://www.w3.org/TR/css-syntax-3/ -var TYPE = { - EOF: 0, // - Ident: 1, // - Function: 2, // - AtKeyword: 3, // - Hash: 4, // - String: 5, // - BadString: 6, // - Url: 7, // - BadUrl: 8, // - Delim: 9, // - Number: 10, // - Percentage: 11, // - Dimension: 12, // - WhiteSpace: 13, // - CDO: 14, // - CDC: 15, // - Colon: 16, // : - Semicolon: 17, // ; - Comma: 18, // , - LeftSquareBracket: 19, // <[-token> - RightSquareBracket: 20, // <]-token> - LeftParenthesis: 21, // <(-token> - RightParenthesis: 22, // <)-token> - LeftCurlyBracket: 23, // <{-token> - RightCurlyBracket: 24, // <}-token> - Comment: 25 -}; - -var NAME = Object.keys(TYPE).reduce(function(result, key) { - result[TYPE[key]] = key; - return result; -}, {}); - -module.exports = { - TYPE: TYPE, - NAME: NAME -}; - - -/***/ }), -/* 28 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -var charCodeDef = __webpack_require__(29); -var isDigit = charCodeDef.isDigit; -var isHexDigit = charCodeDef.isHexDigit; -var isUppercaseLetter = charCodeDef.isUppercaseLetter; -var isName = charCodeDef.isName; -var isWhiteSpace = charCodeDef.isWhiteSpace; -var isValidEscape = charCodeDef.isValidEscape; - -function getCharCode(source, offset) { - return offset < source.length ? source.charCodeAt(offset) : 0; -} - -function getNewlineLength(source, offset, code) { - if (code === 13 /* \r */ && getCharCode(source, offset + 1) === 10 /* \n */) { - return 2; - } - - return 1; -} - -function cmpChar(testStr, offset, referenceCode) { - var code = testStr.charCodeAt(offset); - - // code.toLowerCase() for A..Z - if (isUppercaseLetter(code)) { - code = code | 32; - } - - return code === referenceCode; -} - -function cmpStr(testStr, start, end, referenceStr) { - if (end - start !== referenceStr.length) { - return false; - } - - if (start < 0 || end > testStr.length) { - return false; - } - - for (var i = start; i < end; i++) { - var testCode = testStr.charCodeAt(i); - var referenceCode = referenceStr.charCodeAt(i - start); - - // testCode.toLowerCase() for A..Z - if (isUppercaseLetter(testCode)) { - testCode = testCode | 32; - } - - if (testCode !== referenceCode) { - return false; - } - } - - return true; -} - -function findWhiteSpaceStart(source, offset) { - for (; offset >= 0; offset--) { - if (!isWhiteSpace(source.charCodeAt(offset))) { - break; - } - } - - return offset + 1; -} - -function findWhiteSpaceEnd(source, offset) { - for (; offset < source.length; offset++) { - if (!isWhiteSpace(source.charCodeAt(offset))) { - break; - } - } - - return offset; -} - -function findDecimalNumberEnd(source, offset) { - for (; offset < source.length; offset++) { - if (!isDigit(source.charCodeAt(offset))) { - break; - } - } - - return offset; -} - -// § 4.3.7. Consume an escaped code point -function consumeEscaped(source, offset) { - // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and - // that the next input code point has already been verified to be part of a valid escape. - offset += 2; - - // hex digit - if (isHexDigit(getCharCode(source, offset - 1))) { - // Consume as many hex digits as possible, but no more than 5. - // Note that this means 1-6 hex digits have been consumed in total. - for (var maxOffset = Math.min(source.length, offset + 5); offset < maxOffset; offset++) { - if (!isHexDigit(getCharCode(source, offset))) { - break; - } - } - - // If the next input code point is whitespace, consume it as well. - var code = getCharCode(source, offset); - if (isWhiteSpace(code)) { - offset += getNewlineLength(source, offset, code); - } - } - - return offset; -} - -// §4.3.11. Consume a name -// Note: This algorithm does not do the verification of the first few code points that are necessary -// to ensure the returned code points would constitute an . If that is the intended use, -// ensure that the stream starts with an identifier before calling this algorithm. -function consumeName(source, offset) { - // Let result initially be an empty string. - // Repeatedly consume the next input code point from the stream: - for (; offset < source.length; offset++) { - var code = source.charCodeAt(offset); - - // name code point - if (isName(code)) { - // Append the code point to result. - continue; - } - - // the stream starts with a valid escape - if (isValidEscape(code, getCharCode(source, offset + 1))) { - // Consume an escaped code point. Append the returned code point to result. - offset = consumeEscaped(source, offset) - 1; - continue; - } - - // anything else - // Reconsume the current input code point. Return result. - break; - } - - return offset; -} - -// §4.3.12. Consume a number -function consumeNumber(source, offset) { - var code = source.charCodeAt(offset); - - // 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), - // consume it and append it to repr. - if (code === 0x002B || code === 0x002D) { - code = source.charCodeAt(offset += 1); - } - - // 3. While the next input code point is a digit, consume it and append it to repr. - if (isDigit(code)) { - offset = findDecimalNumberEnd(source, offset + 1); - code = source.charCodeAt(offset); - } - - // 4. If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then: - if (code === 0x002E && isDigit(source.charCodeAt(offset + 1))) { - // 4.1 Consume them. - // 4.2 Append them to repr. - code = source.charCodeAt(offset += 2); - - // 4.3 Set type to "number". - // TODO - - // 4.4 While the next input code point is a digit, consume it and append it to repr. - - offset = findDecimalNumberEnd(source, offset); - } - - // 5. If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) - // or U+0065 LATIN SMALL LETTER E (e), ... , followed by a digit, then: - if (cmpChar(source, offset, 101 /* e */)) { - var sign = 0; - code = source.charCodeAt(offset + 1); - - // ... optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+) ... - if (code === 0x002D || code === 0x002B) { - sign = 1; - code = source.charCodeAt(offset + 2); - } - - // ... followed by a digit - if (isDigit(code)) { - // 5.1 Consume them. - // 5.2 Append them to repr. - - // 5.3 Set type to "number". - // TODO - - // 5.4 While the next input code point is a digit, consume it and append it to repr. - offset = findDecimalNumberEnd(source, offset + 1 + sign + 1); - } - } - - return offset; -} - -// § 4.3.14. Consume the remnants of a bad url -// ... its sole use is to consume enough of the input stream to reach a recovery point -// where normal tokenizing can resume. -function consumeBadUrlRemnants(source, offset) { - // Repeatedly consume the next input code point from the stream: - for (; offset < source.length; offset++) { - var code = source.charCodeAt(offset); - - // U+0029 RIGHT PARENTHESIS ()) - // EOF - if (code === 0x0029) { - // Return. - offset++; - break; - } - - if (isValidEscape(code, getCharCode(source, offset + 1))) { - // Consume an escaped code point. - // Note: This allows an escaped right parenthesis ("\)") to be encountered - // without ending the . This is otherwise identical to - // the "anything else" clause. - offset = consumeEscaped(source, offset); - } - } - - return offset; -} - -module.exports = { - consumeEscaped: consumeEscaped, - consumeName: consumeName, - consumeNumber: consumeNumber, - consumeBadUrlRemnants: consumeBadUrlRemnants, - - cmpChar: cmpChar, - cmpStr: cmpStr, - - getNewlineLength: getNewlineLength, - findWhiteSpaceStart: findWhiteSpaceStart, - findWhiteSpaceEnd: findWhiteSpaceEnd -}; - - -/***/ }), -/* 29 */ -/***/ ((module) => { - -var EOF = 0; - -// https://drafts.csswg.org/css-syntax-3/ -// § 4.2. Definitions - -// digit -// A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9). -function isDigit(code) { - return code >= 0x0030 && code <= 0x0039; -} - -// hex digit -// A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F), -// or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f). -function isHexDigit(code) { - return ( - isDigit(code) || // 0 .. 9 - (code >= 0x0041 && code <= 0x0046) || // A .. F - (code >= 0x0061 && code <= 0x0066) // a .. f - ); -} - -// uppercase letter -// A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z). -function isUppercaseLetter(code) { - return code >= 0x0041 && code <= 0x005A; -} - -// lowercase letter -// A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z). -function isLowercaseLetter(code) { - return code >= 0x0061 && code <= 0x007A; -} - -// letter -// An uppercase letter or a lowercase letter. -function isLetter(code) { - return isUppercaseLetter(code) || isLowercaseLetter(code); -} - -// non-ASCII code point -// A code point with a value equal to or greater than U+0080 . -function isNonAscii(code) { - return code >= 0x0080; -} - -// name-start code point -// A letter, a non-ASCII code point, or U+005F LOW LINE (_). -function isNameStart(code) { - return isLetter(code) || isNonAscii(code) || code === 0x005F; -} - -// name code point -// A name-start code point, a digit, or U+002D HYPHEN-MINUS (-). -function isName(code) { - return isNameStart(code) || isDigit(code) || code === 0x002D; -} - -// non-printable code point -// A code point between U+0000 NULL and U+0008 BACKSPACE, or U+000B LINE TABULATION, -// or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE, or U+007F DELETE. -function isNonPrintable(code) { - return ( - (code >= 0x0000 && code <= 0x0008) || - (code === 0x000B) || - (code >= 0x000E && code <= 0x001F) || - (code === 0x007F) - ); -} - -// newline -// U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition, -// as they are converted to U+000A LINE FEED during preprocessing. -// TODO: we doesn't do a preprocessing, so check a code point for U+000D CARRIAGE RETURN and U+000C FORM FEED -function isNewline(code) { - return code === 0x000A || code === 0x000D || code === 0x000C; -} - -// whitespace -// A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE. -function isWhiteSpace(code) { - return isNewline(code) || code === 0x0020 || code === 0x0009; -} - -// § 4.3.8. Check if two code points are a valid escape -function isValidEscape(first, second) { - // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. - if (first !== 0x005C) { - return false; - } - - // Otherwise, if the second code point is a newline or EOF, return false. - if (isNewline(second) || second === EOF) { - return false; - } - - // Otherwise, return true. - return true; -} - -// § 4.3.9. Check if three code points would start an identifier -function isIdentifierStart(first, second, third) { - // Look at the first code point: - - // U+002D HYPHEN-MINUS - if (first === 0x002D) { - // If the second code point is a name-start code point or a U+002D HYPHEN-MINUS, - // or the second and third code points are a valid escape, return true. Otherwise, return false. - return ( - isNameStart(second) || - second === 0x002D || - isValidEscape(second, third) - ); - } - - // name-start code point - if (isNameStart(first)) { - // Return true. - return true; - } - - // U+005C REVERSE SOLIDUS (\) - if (first === 0x005C) { - // If the first and second code points are a valid escape, return true. Otherwise, return false. - return isValidEscape(first, second); - } - - // anything else - // Return false. - return false; -} - -// § 4.3.10. Check if three code points would start a number -function isNumberStart(first, second, third) { - // Look at the first code point: - - // U+002B PLUS SIGN (+) - // U+002D HYPHEN-MINUS (-) - if (first === 0x002B || first === 0x002D) { - // If the second code point is a digit, return true. - if (isDigit(second)) { - return 2; - } - - // Otherwise, if the second code point is a U+002E FULL STOP (.) - // and the third code point is a digit, return true. - // Otherwise, return false. - return second === 0x002E && isDigit(third) ? 3 : 0; - } - - // U+002E FULL STOP (.) - if (first === 0x002E) { - // If the second code point is a digit, return true. Otherwise, return false. - return isDigit(second) ? 2 : 0; - } - - // digit - if (isDigit(first)) { - // Return true. - return 1; - } - - // anything else - // Return false. - return 0; -} - -// -// Misc -// - -// detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark) -function isBOM(code) { - // UTF-16BE - if (code === 0xFEFF) { - return 1; - } - - // UTF-16LE - if (code === 0xFFFE) { - return 1; - } - - return 0; -} - -// Fast code category -// -// https://drafts.csswg.org/css-syntax/#tokenizer-definitions -// > non-ASCII code point -// > A code point with a value equal to or greater than U+0080 -// > name-start code point -// > A letter, a non-ASCII code point, or U+005F LOW LINE (_). -// > name code point -// > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-) -// That means only ASCII code points has a special meaning and we define a maps for 0..127 codes only -var CATEGORY = new Array(0x80); -charCodeCategory.Eof = 0x80; -charCodeCategory.WhiteSpace = 0x82; -charCodeCategory.Digit = 0x83; -charCodeCategory.NameStart = 0x84; -charCodeCategory.NonPrintable = 0x85; - -for (var i = 0; i < CATEGORY.length; i++) { - switch (true) { - case isWhiteSpace(i): - CATEGORY[i] = charCodeCategory.WhiteSpace; - break; - - case isDigit(i): - CATEGORY[i] = charCodeCategory.Digit; - break; - - case isNameStart(i): - CATEGORY[i] = charCodeCategory.NameStart; - break; - - case isNonPrintable(i): - CATEGORY[i] = charCodeCategory.NonPrintable; - break; - - default: - CATEGORY[i] = i || charCodeCategory.Eof; - } -} - -function charCodeCategory(code) { - return code < 0x80 ? CATEGORY[code] : charCodeCategory.NameStart; -}; - -module.exports = { - isDigit: isDigit, - isHexDigit: isHexDigit, - isUppercaseLetter: isUppercaseLetter, - isLowercaseLetter: isLowercaseLetter, - isLetter: isLetter, - isNonAscii: isNonAscii, - isNameStart: isNameStart, - isName: isName, - isNonPrintable: isNonPrintable, - isNewline: isNewline, - isWhiteSpace: isWhiteSpace, - isValidEscape: isValidEscape, - isIdentifierStart: isIdentifierStart, - isNumberStart: isNumberStart, - - isBOM: isBOM, - charCodeCategory: charCodeCategory -}; - - -/***/ }), -/* 30 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -var SyntaxReferenceError = __webpack_require__(31).SyntaxReferenceError; -var SyntaxMatchError = __webpack_require__(31).SyntaxMatchError; -var names = __webpack_require__(33); -var generic = __webpack_require__(34); -var parse = __webpack_require__(39); -var generate = __webpack_require__(32); -var walk = __webpack_require__(42); -var prepareTokens = __webpack_require__(43); -var buildMatchGraph = __webpack_require__(44).buildMatchGraph; -var matchAsTree = __webpack_require__(45).matchAsTree; -var trace = __webpack_require__(46); -var search = __webpack_require__(47); -var getStructureFromConfig = __webpack_require__(48).getStructureFromConfig; -var cssWideKeywords = buildMatchGraph('inherit | initial | unset'); -var cssWideKeywordsWithExpression = buildMatchGraph('inherit | initial | unset | <-ms-legacy-expression>'); - -function dumpMapSyntax(map, compact, syntaxAsAst) { - var result = {}; - - for (var name in map) { - if (map[name].syntax) { - result[name] = syntaxAsAst - ? map[name].syntax - : generate(map[name].syntax, { compact: compact }); - } - } - - return result; -} - -function dumpAtruleMapSyntax(map, compact, syntaxAsAst) { - const result = {}; - - for (const [name, atrule] of Object.entries(map)) { - result[name] = { - prelude: atrule.prelude && ( - syntaxAsAst - ? atrule.prelude.syntax - : generate(atrule.prelude.syntax, { compact }) - ), - descriptors: atrule.descriptors && dumpMapSyntax(atrule.descriptors, compact, syntaxAsAst) - }; - } - - return result; -} - -function valueHasVar(tokens) { - for (var i = 0; i < tokens.length; i++) { - if (tokens[i].value.toLowerCase() === 'var(') { - return true; - } - } - - return false; -} - -function buildMatchResult(match, error, iterations) { - return { - matched: match, - iterations: iterations, - error: error, - getTrace: trace.getTrace, - isType: trace.isType, - isProperty: trace.isProperty, - isKeyword: trace.isKeyword - }; -} - -function matchSyntax(lexer, syntax, value, useCommon) { - var tokens = prepareTokens(value, lexer.syntax); - var result; - - if (valueHasVar(tokens)) { - return buildMatchResult(null, new Error('Matching for a tree with var() is not supported')); - } - - if (useCommon) { - result = matchAsTree(tokens, lexer.valueCommonSyntax, lexer); - } - - if (!useCommon || !result.match) { - result = matchAsTree(tokens, syntax.match, lexer); - if (!result.match) { - return buildMatchResult( - null, - new SyntaxMatchError(result.reason, syntax.syntax, value, result), - result.iterations - ); - } - } - - return buildMatchResult(result.match, null, result.iterations); -} - -var Lexer = function(config, syntax, structure) { - this.valueCommonSyntax = cssWideKeywords; - this.syntax = syntax; - this.generic = false; - this.atrules = {}; - this.properties = {}; - this.types = {}; - this.structure = structure || getStructureFromConfig(config); - - if (config) { - if (config.types) { - for (var name in config.types) { - this.addType_(name, config.types[name]); - } - } - - if (config.generic) { - this.generic = true; - for (var name in generic) { - this.addType_(name, generic[name]); - } - } - - if (config.atrules) { - for (var name in config.atrules) { - this.addAtrule_(name, config.atrules[name]); - } - } - - if (config.properties) { - for (var name in config.properties) { - this.addProperty_(name, config.properties[name]); - } - } - } -}; - -Lexer.prototype = { - structure: {}, - checkStructure: function(ast) { - function collectWarning(node, message) { - warns.push({ - node: node, - message: message - }); - } - - var structure = this.structure; - var warns = []; - - this.syntax.walk(ast, function(node) { - if (structure.hasOwnProperty(node.type)) { - structure[node.type].check(node, collectWarning); - } else { - collectWarning(node, 'Unknown node type `' + node.type + '`'); - } - }); - - return warns.length ? warns : false; - }, - - createDescriptor: function(syntax, type, name, parent = null) { - var ref = { - type: type, - name: name - }; - var descriptor = { - type: type, - name: name, - parent: parent, - syntax: null, - match: null - }; - - if (typeof syntax === 'function') { - descriptor.match = buildMatchGraph(syntax, ref); - } else { - if (typeof syntax === 'string') { - // lazy parsing on first access - Object.defineProperty(descriptor, 'syntax', { - get: function() { - Object.defineProperty(descriptor, 'syntax', { - value: parse(syntax) - }); - - return descriptor.syntax; - } - }); - } else { - descriptor.syntax = syntax; - } - - // lazy graph build on first access - Object.defineProperty(descriptor, 'match', { - get: function() { - Object.defineProperty(descriptor, 'match', { - value: buildMatchGraph(descriptor.syntax, ref) - }); - - return descriptor.match; - } - }); - } - - return descriptor; - }, - addAtrule_: function(name, syntax) { - if (!syntax) { - return; - } - - this.atrules[name] = { - type: 'Atrule', - name: name, - prelude: syntax.prelude ? this.createDescriptor(syntax.prelude, 'AtrulePrelude', name) : null, - descriptors: syntax.descriptors - ? Object.keys(syntax.descriptors).reduce((res, descName) => { - res[descName] = this.createDescriptor(syntax.descriptors[descName], 'AtruleDescriptor', descName, name); - return res; - }, {}) - : null - }; - }, - addProperty_: function(name, syntax) { - if (!syntax) { - return; - } - - this.properties[name] = this.createDescriptor(syntax, 'Property', name); - }, - addType_: function(name, syntax) { - if (!syntax) { - return; - } - - this.types[name] = this.createDescriptor(syntax, 'Type', name); - - if (syntax === generic['-ms-legacy-expression']) { - this.valueCommonSyntax = cssWideKeywordsWithExpression; - } - }, - - checkAtruleName: function(atruleName) { - if (!this.getAtrule(atruleName)) { - return new SyntaxReferenceError('Unknown at-rule', '@' + atruleName); - } - }, - checkAtrulePrelude: function(atruleName, prelude) { - let error = this.checkAtruleName(atruleName); - - if (error) { - return error; - } - - var atrule = this.getAtrule(atruleName); - - if (!atrule.prelude && prelude) { - return new SyntaxError('At-rule `@' + atruleName + '` should not contain a prelude'); - } - - if (atrule.prelude && !prelude) { - return new SyntaxError('At-rule `@' + atruleName + '` should contain a prelude'); - } - }, - checkAtruleDescriptorName: function(atruleName, descriptorName) { - let error = this.checkAtruleName(atruleName); - - if (error) { - return error; - } - - var atrule = this.getAtrule(atruleName); - var descriptor = names.keyword(descriptorName); - - if (!atrule.descriptors) { - return new SyntaxError('At-rule `@' + atruleName + '` has no known descriptors'); - } - - if (!atrule.descriptors[descriptor.name] && - !atrule.descriptors[descriptor.basename]) { - return new SyntaxReferenceError('Unknown at-rule descriptor', descriptorName); - } - }, - checkPropertyName: function(propertyName) { - var property = names.property(propertyName); - - // don't match syntax for a custom property - if (property.custom) { - return new Error('Lexer matching doesn\'t applicable for custom properties'); - } - - if (!this.getProperty(propertyName)) { - return new SyntaxReferenceError('Unknown property', propertyName); - } - }, - - matchAtrulePrelude: function(atruleName, prelude) { - var error = this.checkAtrulePrelude(atruleName, prelude); - - if (error) { - return buildMatchResult(null, error); - } - - if (!prelude) { - return buildMatchResult(null, null); - } - - return matchSyntax(this, this.getAtrule(atruleName).prelude, prelude, false); - }, - matchAtruleDescriptor: function(atruleName, descriptorName, value) { - var error = this.checkAtruleDescriptorName(atruleName, descriptorName); - - if (error) { - return buildMatchResult(null, error); - } - - var atrule = this.getAtrule(atruleName); - var descriptor = names.keyword(descriptorName); - - return matchSyntax(this, atrule.descriptors[descriptor.name] || atrule.descriptors[descriptor.basename], value, false); - }, - matchDeclaration: function(node) { - if (node.type !== 'Declaration') { - return buildMatchResult(null, new Error('Not a Declaration node')); - } - - return this.matchProperty(node.property, node.value); - }, - matchProperty: function(propertyName, value) { - var error = this.checkPropertyName(propertyName); - - if (error) { - return buildMatchResult(null, error); - } - - return matchSyntax(this, this.getProperty(propertyName), value, true); - }, - matchType: function(typeName, value) { - var typeSyntax = this.getType(typeName); - - if (!typeSyntax) { - return buildMatchResult(null, new SyntaxReferenceError('Unknown type', typeName)); - } - - return matchSyntax(this, typeSyntax, value, false); - }, - match: function(syntax, value) { - if (typeof syntax !== 'string' && (!syntax || !syntax.type)) { - return buildMatchResult(null, new SyntaxReferenceError('Bad syntax')); - } - - if (typeof syntax === 'string' || !syntax.match) { - syntax = this.createDescriptor(syntax, 'Type', 'anonymous'); - } - - return matchSyntax(this, syntax, value, false); - }, - - findValueFragments: function(propertyName, value, type, name) { - return search.matchFragments(this, value, this.matchProperty(propertyName, value), type, name); - }, - findDeclarationValueFragments: function(declaration, type, name) { - return search.matchFragments(this, declaration.value, this.matchDeclaration(declaration), type, name); - }, - findAllFragments: function(ast, type, name) { - var result = []; - - this.syntax.walk(ast, { - visit: 'Declaration', - enter: function(declaration) { - result.push.apply(result, this.findDeclarationValueFragments(declaration, type, name)); - }.bind(this) - }); - - return result; - }, - - getAtrule: function(atruleName, fallbackBasename = true) { - var atrule = names.keyword(atruleName); - var atruleEntry = atrule.vendor && fallbackBasename - ? this.atrules[atrule.name] || this.atrules[atrule.basename] - : this.atrules[atrule.name]; - - return atruleEntry || null; - }, - getAtrulePrelude: function(atruleName, fallbackBasename = true) { - const atrule = this.getAtrule(atruleName, fallbackBasename); - - return atrule && atrule.prelude || null; - }, - getAtruleDescriptor: function(atruleName, name) { - return this.atrules.hasOwnProperty(atruleName) && this.atrules.declarators - ? this.atrules[atruleName].declarators[name] || null - : null; - }, - getProperty: function(propertyName, fallbackBasename = true) { - var property = names.property(propertyName); - var propertyEntry = property.vendor && fallbackBasename - ? this.properties[property.name] || this.properties[property.basename] - : this.properties[property.name]; - - return propertyEntry || null; - }, - getType: function(name) { - return this.types.hasOwnProperty(name) ? this.types[name] : null; - }, - - validate: function() { - function validate(syntax, name, broken, descriptor) { - if (broken.hasOwnProperty(name)) { - return broken[name]; - } - - broken[name] = false; - if (descriptor.syntax !== null) { - walk(descriptor.syntax, function(node) { - if (node.type !== 'Type' && node.type !== 'Property') { - return; - } - - var map = node.type === 'Type' ? syntax.types : syntax.properties; - var brokenMap = node.type === 'Type' ? brokenTypes : brokenProperties; - - if (!map.hasOwnProperty(node.name) || validate(syntax, node.name, brokenMap, map[node.name])) { - broken[name] = true; - } - }, this); - } - } - - var brokenTypes = {}; - var brokenProperties = {}; - - for (var key in this.types) { - validate(this, key, brokenTypes, this.types[key]); - } - - for (var key in this.properties) { - validate(this, key, brokenProperties, this.properties[key]); - } - - brokenTypes = Object.keys(brokenTypes).filter(function(name) { - return brokenTypes[name]; - }); - brokenProperties = Object.keys(brokenProperties).filter(function(name) { - return brokenProperties[name]; - }); - - if (brokenTypes.length || brokenProperties.length) { - return { - types: brokenTypes, - properties: brokenProperties - }; - } - - return null; - }, - dump: function(syntaxAsAst, pretty) { - return { - generic: this.generic, - types: dumpMapSyntax(this.types, !pretty, syntaxAsAst), - properties: dumpMapSyntax(this.properties, !pretty, syntaxAsAst), - atrules: dumpAtruleMapSyntax(this.atrules, !pretty, syntaxAsAst) - }; - }, - toString: function() { - return JSON.stringify(this.dump()); - } -}; - -module.exports = Lexer; - - -/***/ }), -/* 31 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -const createCustomError = __webpack_require__(25); -const generate = __webpack_require__(32); -const defaultLoc = { offset: 0, line: 1, column: 1 }; - -function locateMismatch(matchResult, node) { - const tokens = matchResult.tokens; - const longestMatch = matchResult.longestMatch; - const mismatchNode = longestMatch < tokens.length ? tokens[longestMatch].node || null : null; - const badNode = mismatchNode !== node ? mismatchNode : null; - let mismatchOffset = 0; - let mismatchLength = 0; - let entries = 0; - let css = ''; - let start; - let end; - - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i].value; - - if (i === longestMatch) { - mismatchLength = token.length; - mismatchOffset = css.length; - } - - if (badNode !== null && tokens[i].node === badNode) { - if (i <= longestMatch) { - entries++; - } else { - entries = 0; - } - } - - css += token; - } - - if (longestMatch === tokens.length || entries > 1) { // last - start = fromLoc(badNode || node, 'end') || buildLoc(defaultLoc, css); - end = buildLoc(start); - } else { - start = fromLoc(badNode, 'start') || - buildLoc(fromLoc(node, 'start') || defaultLoc, css.slice(0, mismatchOffset)); - end = fromLoc(badNode, 'end') || - buildLoc(start, css.substr(mismatchOffset, mismatchLength)); - } - - return { - css, - mismatchOffset, - mismatchLength, - start, - end - }; -} - -function fromLoc(node, point) { - const value = node && node.loc && node.loc[point]; - - if (value) { - return 'line' in value ? buildLoc(value) : value; - } - - return null; -} - -function buildLoc({ offset, line, column }, extra) { - const loc = { - offset, - line, - column - }; - - if (extra) { - const lines = extra.split(/\n|\r\n?|\f/); - - loc.offset += extra.length; - loc.line += lines.length - 1; - loc.column = lines.length === 1 ? loc.column + extra.length : lines.pop().length + 1; - } - - return loc; -} - -const SyntaxReferenceError = function(type, referenceName) { - const error = createCustomError( - 'SyntaxReferenceError', - type + (referenceName ? ' `' + referenceName + '`' : '') - ); - - error.reference = referenceName; - - return error; -}; - -const SyntaxMatchError = function(message, syntax, node, matchResult) { - const error = createCustomError('SyntaxMatchError', message); - const { - css, - mismatchOffset, - mismatchLength, - start, - end - } = locateMismatch(matchResult, node); - - error.rawMessage = message; - error.syntax = syntax ? generate(syntax) : ''; - error.css = css; - error.mismatchOffset = mismatchOffset; - error.mismatchLength = mismatchLength; - error.message = message + '\n' + - ' syntax: ' + error.syntax + '\n' + - ' value: ' + (css || '') + '\n' + - ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^'; - - Object.assign(error, start); - error.loc = { - source: (node && node.loc && node.loc.source) || '', - start, - end - }; - - return error; -}; - -module.exports = { - SyntaxReferenceError, - SyntaxMatchError -}; - - -/***/ }), -/* 32 */ -/***/ ((module) => { - -function noop(value) { - return value; -} - -function generateMultiplier(multiplier) { - if (multiplier.min === 0 && multiplier.max === 0) { - return '*'; - } - - if (multiplier.min === 0 && multiplier.max === 1) { - return '?'; - } - - if (multiplier.min === 1 && multiplier.max === 0) { - return multiplier.comma ? '#' : '+'; - } - - if (multiplier.min === 1 && multiplier.max === 1) { - return ''; - } - - return ( - (multiplier.comma ? '#' : '') + - (multiplier.min === multiplier.max - ? '{' + multiplier.min + '}' - : '{' + multiplier.min + ',' + (multiplier.max !== 0 ? multiplier.max : '') + '}' - ) - ); -} - -function generateTypeOpts(node) { - switch (node.type) { - case 'Range': - return ( - ' [' + - (node.min === null ? '-∞' : node.min) + - ',' + - (node.max === null ? '∞' : node.max) + - ']' - ); - - default: - throw new Error('Unknown node type `' + node.type + '`'); - } -} - -function generateSequence(node, decorate, forceBraces, compact) { - var combinator = node.combinator === ' ' || compact ? node.combinator : ' ' + node.combinator + ' '; - var result = node.terms.map(function(term) { - return generate(term, decorate, forceBraces, compact); - }).join(combinator); - - if (node.explicit || forceBraces) { - result = (compact || result[0] === ',' ? '[' : '[ ') + result + (compact ? ']' : ' ]'); - } - - return result; -} - -function generate(node, decorate, forceBraces, compact) { - var result; - - switch (node.type) { - case 'Group': - result = - generateSequence(node, decorate, forceBraces, compact) + - (node.disallowEmpty ? '!' : ''); - break; - - case 'Multiplier': - // return since node is a composition - return ( - generate(node.term, decorate, forceBraces, compact) + - decorate(generateMultiplier(node), node) - ); - - case 'Type': - result = '<' + node.name + (node.opts ? decorate(generateTypeOpts(node.opts), node.opts) : '') + '>'; - break; - - case 'Property': - result = '<\'' + node.name + '\'>'; - break; - - case 'Keyword': - result = node.name; - break; - - case 'AtKeyword': - result = '@' + node.name; - break; - - case 'Function': - result = node.name + '('; - break; - - case 'String': - case 'Token': - result = node.value; - break; - - case 'Comma': - result = ','; - break; - - default: - throw new Error('Unknown node type `' + node.type + '`'); - } - - return decorate(result, node); -} - -module.exports = function(node, options) { - var decorate = noop; - var forceBraces = false; - var compact = false; - - if (typeof options === 'function') { - decorate = options; - } else if (options) { - forceBraces = Boolean(options.forceBraces); - compact = Boolean(options.compact); - if (typeof options.decorate === 'function') { - decorate = options.decorate; - } - } - - return generate(node, decorate, forceBraces, compact); -}; - - -/***/ }), -/* 33 */ -/***/ ((module) => { - -var hasOwnProperty = Object.prototype.hasOwnProperty; -var keywords = Object.create(null); -var properties = Object.create(null); -var HYPHENMINUS = 45; // '-'.charCodeAt() - -function isCustomProperty(str, offset) { - offset = offset || 0; - - return str.length - offset >= 2 && - str.charCodeAt(offset) === HYPHENMINUS && - str.charCodeAt(offset + 1) === HYPHENMINUS; -} - -function getVendorPrefix(str, offset) { - offset = offset || 0; - - // verdor prefix should be at least 3 chars length - if (str.length - offset >= 3) { - // vendor prefix starts with hyper minus following non-hyper minus - if (str.charCodeAt(offset) === HYPHENMINUS && - str.charCodeAt(offset + 1) !== HYPHENMINUS) { - // vendor prefix should contain a hyper minus at the ending - var secondDashIndex = str.indexOf('-', offset + 2); - - if (secondDashIndex !== -1) { - return str.substring(offset, secondDashIndex + 1); - } - } - } - - return ''; -} - -function getKeywordDescriptor(keyword) { - if (hasOwnProperty.call(keywords, keyword)) { - return keywords[keyword]; - } - - var name = keyword.toLowerCase(); - - if (hasOwnProperty.call(keywords, name)) { - return keywords[keyword] = keywords[name]; - } - - var custom = isCustomProperty(name, 0); - var vendor = !custom ? getVendorPrefix(name, 0) : ''; - - return keywords[keyword] = Object.freeze({ - basename: name.substr(vendor.length), - name: name, - vendor: vendor, - prefix: vendor, - custom: custom - }); -} - -function getPropertyDescriptor(property) { - if (hasOwnProperty.call(properties, property)) { - return properties[property]; - } - - var name = property; - var hack = property[0]; - - if (hack === '/') { - hack = property[1] === '/' ? '//' : '/'; - } else if (hack !== '_' && - hack !== '*' && - hack !== '$' && - hack !== '#' && - hack !== '+' && - hack !== '&') { - hack = ''; - } - - var custom = isCustomProperty(name, hack.length); - - // re-use result when possible (the same as for lower case) - if (!custom) { - name = name.toLowerCase(); - if (hasOwnProperty.call(properties, name)) { - return properties[property] = properties[name]; - } - } - - var vendor = !custom ? getVendorPrefix(name, hack.length) : ''; - var prefix = name.substr(0, hack.length + vendor.length); - - return properties[property] = Object.freeze({ - basename: name.substr(prefix.length), - name: name.substr(hack.length), - hack: hack, - vendor: vendor, - prefix: prefix, - custom: custom - }); -} - -module.exports = { - keyword: getKeywordDescriptor, - property: getPropertyDescriptor, - isCustomProperty: isCustomProperty, - vendorPrefix: getVendorPrefix -}; - - -/***/ }), -/* 34 */ -/***/ ((module, __unused_webpack_exports, __webpack_require__) => { - -var tokenizer = __webpack_require__(35); -var isIdentifierStart = tokenizer.isIdentifierStart; -var isHexDigit = tokenizer.isHexDigit; -var isDigit = tokenizer.isDigit; -var cmpStr = tokenizer.cmpStr; -var consumeNumber = tokenizer.consumeNumber; -var TYPE = tokenizer.TYPE; -var anPlusB = __webpack_require__(37); -var urange = __webpack_require__(38); - -var cssWideKeywords = ['unset', 'initial', 'inherit']; -var calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc(']; - -// https://www.w3.org/TR/css-values-3/#lengths -var LENGTH = { - // absolute length units - 'px': true, - 'mm': true, - 'cm': true, - 'in': true, - 'pt': true, - 'pc': true, - 'q': true, - - // relative length units - 'em': true, - 'ex': true, - 'ch': true, - 'rem': true, - - // viewport-percentage lengths - 'vh': true, - 'vw': true, - 'vmin': true, - 'vmax': true, - 'vm': true -}; - -var ANGLE = { - 'deg': true, - 'grad': true, - 'rad': true, - 'turn': true -}; - -var TIME = { - 's': true, - 'ms': true -}; - -var FREQUENCY = { - 'hz': true, - 'khz': true -}; - -// https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution) -var RESOLUTION = { - 'dpi': true, - 'dpcm': true, - 'dppx': true, - 'x': true // https://github.com/w3c/csswg-drafts/issues/461 -}; - -// https://drafts.csswg.org/css-grid/#fr-unit -var FLEX = { - 'fr': true -}; - -// https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume -var DECIBEL = { - 'db': true -}; - -// https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch -var SEMITONES = { - 'st': true -}; - -// safe char code getter -function charCode(str, index) { - return index < str.length ? str.charCodeAt(index) : 0; -} - -function eqStr(actual, expected) { - return cmpStr(actual, 0, actual.length, expected); -} - -function eqStrAny(actual, expected) { - for (var i = 0; i < expected.length; i++) { - if (eqStr(actual, expected[i])) { - return true; - } - } - - return false; -} - -// IE postfix hack, i.e. 123\0 or 123px\9 -function isPostfixIeHack(str, offset) { - if (offset !== str.length - 2) { - return false; - } - - return ( - str.charCodeAt(offset) === 0x005C && // U+005C REVERSE SOLIDUS (\) - isDigit(str.charCodeAt(offset + 1)) - ); -} - -function outOfRange(opts, value, numEnd) { - if (opts && opts.type === 'Range') { - var num = Number( - numEnd !== undefined && numEnd !== value.length - ? value.substr(0, numEnd) - : value - ); - - if (isNaN(num)) { - return true; - } - - if (opts.min !== null && num < opts.min) { - return true; - } - - if (opts.max !== null && num > opts.max) { - return true; - } - } - - return false; -} - -function consumeFunction(token, getNextToken) { - var startIdx = token.index; - var length = 0; - - // balanced token consuming - do { - length++; - - if (token.balance <= startIdx) { - break; - } - } while (token = getNextToken(length)); - - return length; -} - -// TODO: implement -// can be used wherever , , ,