diff --git a/client/dom/style.js b/client/dom/style.js index 77e476d..8ef0474 100644 --- a/client/dom/style.js +++ b/client/dom/style.js @@ -10,6 +10,7 @@ class StyleApi extends EventEmitter { this.cssStyleProto = this.CSSStyleDeclaration.prototype || {}; this.getPropertyValue = this.cssStyleProto.getPropertyValue || null; this.setProperty = this.cssStyleProto.setProperty || null; + this.cssText - ctx.nativeMethods.getOwnPropertyDescriptors(this.cssStyleProto, 'cssText'); this.urlProps = ['background', 'backgroundImage', 'borderImage', 'borderImageSource', 'listStyle', 'listStyleImage', 'cursor']; this.dashedUrlProps = ['background', 'background-image', 'border-image', 'border-image-source', 'list-style', 'list-style-image', 'cursor']; this.propToDashed = { @@ -45,6 +46,24 @@ class StyleApi extends EventEmitter { return event.target.call(event.that, event.data.property, event.data.value); }); }; + overrideCssText() { + this.ctx.overrideDescriptor(this.cssStyleProto, 'cssText', { + get: (target, that) => { + const event = new HookEvent({ value: target.call(that) }, target, that); + this.emit('getCssText', event); + + if (event.intercepted) return event.returnValue; + return event.data.value; + }, + set: (target, that, [ val ]) => { + const event = new HookEvent({ value: val }, target, that); + this.emit('setCssText', event); + + if (event.intercepted) return event.returnValue; + return event.target.call(event.that, event.data.value); + }, + }); + }; }; export default StyleApi; \ No newline at end of file diff --git a/example/index.js b/example/index.js index 6ed10fc..118565c 100644 --- a/example/index.js +++ b/example/index.js @@ -36,6 +36,12 @@ server.on('request', (req, res) => { return true; }; + if (req.url.startsWith('/uv.config.js')) { + res.writeHead(200, { "Content-Type": "application/javascript" }); + createUVFileStream('uv.config.js').pipe(res); + return true; + }; + if (req.url.startsWith(config.prefix)) { res.writeHead(200, { "Content-Type": "text/html" }); createReadStream(path.join(__dirname, './load.html')).pipe(res); @@ -49,7 +55,7 @@ server.on('upgrade', (req, socket, head) => { if (!bare.route_upgrade(req, socket, head)) socket.end(); }); -server.listen(3030); +server.listen(443); function createUVFileStream(file) { return createReadStream( diff --git a/example/load.html b/example/load.html index 10e363f..3f63e21 100644 --- a/example/load.html +++ b/example/load.html @@ -5,7 +5,7 @@ - - + + -

Testing

- - + +
+ +
Ultraviolet
+
+
+ +
+ \ No newline at end of file diff --git a/example/static/uv.png b/example/static/uv.png new file mode 100644 index 0000000..9ee3ec5 Binary files /dev/null and b/example/static/uv.png differ diff --git a/lib/uv.bundle.js b/lib/uv.bundle.js index 87309a1..781fe0a 100644 --- a/lib/uv.bundle.js +++ b/lib/uv.bundle.js @@ -43,6 +43,7 @@ class HTML extends _events_js__WEBPACK_IMPORTED_MODULE_0__["default"] { this.iterate(ast, fn, options); return (0,parse5__WEBPACK_IMPORTED_MODULE_1__.serialize)(ast); } catch(e) { + console.log(e); return str; }; }; @@ -35773,7 +35774,7 @@ function injectHead(ctx) { }); }; -function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', cookies = '', referrer = '') { +function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', config = '/uv.config.js', cookies = '', referrer = '') { return [ { tagName: 'script', @@ -35784,7 +35785,13 @@ function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', c value: `window.__uv$cookies = atob("${btoa(cookies)}");\nwindow.__uv$referrer = atob("${btoa(referrer)}");` }, ], - attrs: [], + attrs: [ + { + name: '__uv-script', + value: '1', + skip: true, + } + ], skip: true, }, { @@ -35792,7 +35799,12 @@ function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', c nodeName: 'script', childNodes: [], attrs: [ - { name: 'src', value: bundle, skip: true } + { name: 'src', value: bundle, skip: true }, + { + name: '__uv-script', + value: '1', + skip: true, + } ], }, { @@ -35800,9 +35812,27 @@ function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', c nodeName: 'script', childNodes: [], attrs: [ - { name: 'src', value: handler, skip: true } + { name: 'src', value: config, skip: true }, + { + name: '__uv-script', + value: '1', + skip: true, + } ], }, + { + tagName: 'script', + nodeName: 'script', + childNodes: [], + attrs: [ + { name: 'src', value: handler, skip: true }, + { + name: '__uv-script', + value: '1', + skip: true, + } + ], + } ]; }; @@ -38861,6 +38891,7 @@ class StyleApi extends _events_js__WEBPACK_IMPORTED_MODULE_0__["default"] { this.cssStyleProto = this.CSSStyleDeclaration.prototype || {}; this.getPropertyValue = this.cssStyleProto.getPropertyValue || null; this.setProperty = this.cssStyleProto.setProperty || null; + this.cssText - ctx.nativeMethods.getOwnPropertyDescriptors(this.cssStyleProto, 'cssText'); this.urlProps = ['background', 'backgroundImage', 'borderImage', 'borderImageSource', 'listStyle', 'listStyleImage', 'cursor']; this.dashedUrlProps = ['background', 'background-image', 'border-image', 'border-image-source', 'list-style', 'list-style-image', 'cursor']; this.propToDashed = { @@ -38896,6 +38927,24 @@ class StyleApi extends _events_js__WEBPACK_IMPORTED_MODULE_0__["default"] { return event.target.call(event.that, event.data.property, event.data.value); }); }; + overrideCssText() { + this.ctx.overrideDescriptor(this.cssStyleProto, 'cssText', { + get: (target, that) => { + const event = new _hook_js__WEBPACK_IMPORTED_MODULE_1__["default"]({ value: target.call(that) }, target, that); + this.emit('getCssText', event); + + if (event.intercepted) return event.returnValue; + return event.data.value; + }, + set: (target, that, [ val ]) => { + const event = new _hook_js__WEBPACK_IMPORTED_MODULE_1__["default"]({ value: val }, target, that); + this.emit('setCssText', event); + + if (event.intercepted) return event.returnValue; + return event.target.call(event.that, event.data.value); + }, + }); + }; }; /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (StyleApi); @@ -39020,6 +39069,7 @@ class Ultraviolet { this.meta.origin ||= ''; this.bundleScript = options.bundleScript || '/uv.bundle.js'; this.handlerScript = options.handlerScript || '/uv.handler.js'; + this.configScript = options.handlerScript || '/uv.config.js'; this.meta.url ||= this.meta.base || ''; this.codec = Ultraviolet.codec; this.html = new _html_js__WEBPACK_IMPORTED_MODULE_0__["default"](this); diff --git a/lib/uv.config.js b/lib/uv.config.js new file mode 100644 index 0000000..a997ac4 --- /dev/null +++ b/lib/uv.config.js @@ -0,0 +1,9 @@ +self.__uv$config = { + prefix: '/sw/', + bare: '/bare/', + encodeUrl: Ultraviolet.codec.xor.encode, + decodeUrl: Ultraviolet.codec.xor.decode, + handler: '/uv.handler.js', + bundle: '/uv.bundle.js', + config: '/uv.config.js' +}; \ No newline at end of file diff --git a/lib/uv.handler.js b/lib/uv.handler.js index b471f9d..8e99c31 100644 --- a/lib/uv.handler.js +++ b/lib/uv.handler.js @@ -1,14 +1,13 @@ if (!self.__uv) { - __uvHook(self, { - prefix: '/sw/', - encodeUrl: Ultraviolet.codec.xor.encode, - decodeUrl: Ultraviolet.codec.xor.decode, - }, '/bare/'); + __uvHook(self, self.__uv$config, self.__uv$config.bare); }; async function __uvHook(window, config = {}, bare = '/bare/') { if ('__uv' in window && window.__uv instanceof Ultraviolet) return false; + if (window.document && !!window.window) { + window.document.querySelectorAll("script[__uv-script]").forEach(node => node.remove()) + }; const worker = !window.window; const master = '__uv'; @@ -42,6 +41,7 @@ async function __uvHook(window, config = {}, bare = '/bare/') { enumerable: false, }); + __uv.meta.origin = location.origin; __uv.location = client.location.emulate( (href) => { @@ -146,6 +146,7 @@ async function __uvHook(window, config = {}, bare = '/bare/') { methodPrefix + 'storageObj', methodPrefix + 'url', methodPrefix + 'modifiedStyle', + methodPrefix + 'config', 'Ultraviolet', '__uvHook', ]; @@ -403,7 +404,7 @@ async function __uvHook(window, config = {}, bare = '/bare/') { if (__uv.attrs.isHtml(event.data.name)) { event.target.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value); - event.data.value = __uv.rewriteHtml(event.data.value, {...__uv.meta, document: true, injectHead: __uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.cookieStr, window.location.href) }); + event.data.value = __uv.rewriteHtml(event.data.value, {...__uv.meta, document: true, injectHead:__uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.configScript, __uv.cookieStr, window.location.href) }); }; if (__uv.attrs.isSrcset(event.data.name)) { @@ -522,7 +523,7 @@ async function __uvHook(window, config = {}, bare = '/bare/') { set: (target, that, [val]) => { target.call(that, __uv.rewriteHtml(val, { document: true, - injectHead: __uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.cookieStr, window.location.href) + injectHead: __uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.configScript, __uv.cookieStr, window.location.href) })) }, }); @@ -585,7 +586,7 @@ async function __uvHook(window, config = {}, bare = '/bare/') { if (__uv.attrs.isHtml(event.data.name)) { client.element.setAttribute.call(event.that.ownerElement, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value); - event.data.value = __uv.rewriteHtml(event.data.value, {...__uv.meta, document: true, injectHead: __uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.cookieStr, window.location.href) }); + event.data.value = __uv.rewriteHtml(event.data.value, {...__uv.meta, document: true, injectHead:__uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.configScript, __uv.cookieStr, window.location.href) }); }; if (__uv.attrs.isSrcset(event.data.name)) { @@ -599,8 +600,7 @@ async function __uvHook(window, config = {}, bare = '/bare/') { client.url.on('createObjectURL', event => { let url = event.target.call(event.that, event.data.object); if (url.startsWith('blob:' + location.origin)) { - let newUrl = 'blob:' + __uv.meta.url.origin + url.slice('blob:'.length + location.origin.length); - + let newUrl = 'blob:' + (__uv.meta.url.href !== 'about:blank' ? __uv.meta.url.origin : window.parent.__uv.meta.url.origin) + url.slice('blob:'.length + location.origin.length); __uv.blobUrls.set(newUrl, url); event.respondWith(newUrl); } else { @@ -859,6 +859,20 @@ async function __uvHook(window, config = {}, bare = '/bare/') { ); }; + client.style.on('setCssText', event => { + event.data.value = __uv.rewriteCSS(event.data.value, { + context: 'declarationList', + ...__uv.meta + }); + }); + + client.style.on('getCssText', event => { + event.data.value = __uv.sourceCSS(event.data.value, { + context: 'declarationList', + ...__uv.meta + }); + }); + // Hooking functions & descriptors client.fetch.overrideRequest(); client.fetch.overrideUrl(); @@ -900,6 +914,7 @@ async function __uvHook(window, config = {}, bare = '/bare/') { client.workers.overrideImportScripts(); client.workers.overridePostMessage(); client.style.overrideSetGetProperty(); + client.style.overrideCssText(); client.navigator.overrideSendBeacon(); client.function.overrideFunction(); client.function.overrideToString(); @@ -940,6 +955,12 @@ async function __uvHook(window, config = {}, bare = '/bare/') { __uv.$get = function(that) { if (that === window.location) return __uv.location; if (that === window.eval) return __uv.eval; + if (that === window.parent) { + return window.__uv$parent; + }; + if (that === window.top) { + return window.__uv$top; + }; return that; }; diff --git a/lib/uv.sw.js b/lib/uv.sw.js index 9df4c42..71c52d3 100644 --- a/lib/uv.sw.js +++ b/lib/uv.sw.js @@ -1,4 +1,5 @@ importScripts('/uv.bundle.js'); +importScripts('/uv.config.js') const csp = [ 'cross-origin-embedder-policy', @@ -63,11 +64,7 @@ const statusCode = { ], }; -const handler = UVServiceWorker('/bare/', { - prefix: '/sw/', - encodeUrl: Ultraviolet.codec.xor.encode, - decodeUrl: Ultraviolet.codec.xor.decode, -}); +const handler = UVServiceWorker(__uv$config.bare, __uv$config); addEventListener('fetch', async event => event.respondWith(handler(event)) @@ -204,7 +201,7 @@ function UVServiceWorker(bare = '/bare/', options) { switch(request.destination) { case 'script': case 'worker': - responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('/uv.bundle.js', '/uv.handler.js');\n`; + responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`; responseCtx.body += uv.js.rewrite( await response.text() ); @@ -222,8 +219,9 @@ function UVServiceWorker(bare = '/bare/', options) { { document: true , injectHead: uv.createHtmlInject( - scripts.handler, - scripts.package, + options.handler, + options.bundle, + options.config, uv.cookie.serialize(cookies, uv.meta, true), request.referrer ) diff --git a/rewrite/index.js b/rewrite/index.js index 444ddbf..cc9f0a2 100644 --- a/rewrite/index.js +++ b/rewrite/index.js @@ -33,6 +33,7 @@ class Ultraviolet { this.meta.origin ||= ''; this.bundleScript = options.bundleScript || '/uv.bundle.js'; this.handlerScript = options.handlerScript || '/uv.handler.js'; + this.configScript = options.handlerScript || '/uv.config.js'; this.meta.url ||= this.meta.base || ''; this.codec = Ultraviolet.codec; this.html = new HTML(this); diff --git a/rewrite/rewrite.html.js b/rewrite/rewrite.html.js index 897ea54..3becc9d 100644 --- a/rewrite/rewrite.html.js +++ b/rewrite/rewrite.html.js @@ -172,7 +172,7 @@ function injectHead(ctx) { }); }; -function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', cookies = '', referrer = '') { +function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', config = '/uv.config.js', cookies = '', referrer = '') { return [ { tagName: 'script', @@ -183,7 +183,13 @@ function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', c value: `window.__uv$cookies = atob("${btoa(cookies)}");\nwindow.__uv$referrer = atob("${btoa(referrer)}");` }, ], - attrs: [], + attrs: [ + { + name: '__uv-script', + value: '1', + skip: true, + } + ], skip: true, }, { @@ -191,7 +197,12 @@ function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', c nodeName: 'script', childNodes: [], attrs: [ - { name: 'src', value: bundle, skip: true } + { name: 'src', value: bundle, skip: true }, + { + name: '__uv-script', + value: '1', + skip: true, + } ], }, { @@ -199,9 +210,27 @@ function createInjection(handler = '/uv.handler.js', bundle = '/uv.bundle.js', c nodeName: 'script', childNodes: [], attrs: [ - { name: 'src', value: handler, skip: true } + { name: 'src', value: config, skip: true }, + { + name: '__uv-script', + value: '1', + skip: true, + } ], }, + { + tagName: 'script', + nodeName: 'script', + childNodes: [], + attrs: [ + { name: 'src', value: handler, skip: true }, + { + name: '__uv-script', + value: '1', + skip: true, + } + ], + } ]; }; diff --git a/tomp/tomp.js b/tomp/tomp.js new file mode 100644 index 0000000..9b2d8a9 --- /dev/null +++ b/tomp/tomp.js @@ -0,0 +1,10 @@ +import http from 'http'; +import { Server as Bare } from './Server.mjs'; + +const bare = new Bare('/'); + +http.createServer().on('request', (req, res) => + bare.route_request(req, res) +).on('upgrade', (req, socket, head) => + bare.route_upgrade(req, socket, head) +).listen(4545);