From 6bd7e10c4dceea204be634a74d17de1fc584d7aa Mon Sep 17 00:00:00 2001 From: TheEmeraldStarr <46467239+Epicloudygamer@users.noreply.github.com> Date: Sun, 8 Aug 2021 14:55:47 -0700 Subject: [PATCH] Minor Change (oops) --- app.js | 2 +- src/Corrosion/README.md | 223 + src/Corrosion/demo/index.html | 16 + src/Corrosion/demo/index.js | 19 + src/Corrosion/demo/ssl.cert | 22 + src/Corrosion/demo/ssl.key | 28 + src/Corrosion/lib/browser/document.js | 271 + src/Corrosion/lib/browser/history.js | 26 + src/Corrosion/lib/browser/http.js | 92 + src/Corrosion/lib/browser/index.js | 195 + src/Corrosion/lib/browser/location.js | 56 + src/Corrosion/lib/browser/worker.js | 33 + src/Corrosion/lib/codec.js | 66 + src/Corrosion/lib/cookie-parser.js | 95 + src/Corrosion/lib/cookie.js | 34 + src/Corrosion/lib/css.js | 67 + src/Corrosion/lib/esotope.js | 2562 ++ src/Corrosion/lib/html.js | 226 + src/Corrosion/lib/js.js | 185 + src/Corrosion/lib/rewrite.js | 27 + src/Corrosion/lib/server/bundle.js | 30109 +++++++++++++++++++++ src/Corrosion/lib/server/decompress.js | 22 + src/Corrosion/lib/server/gateway.js | 21 + src/Corrosion/lib/server/headers.js | 45 + src/Corrosion/lib/server/index.js | 60 + src/Corrosion/lib/server/middleware.js | 14 + src/Corrosion/lib/server/request.js | 83 + src/Corrosion/lib/server/rewrite-body.js | 36 + src/Corrosion/lib/server/upgrade.js | 56 + src/Corrosion/lib/url.js | 34 + src/Corrosion/package.json | 47 + 31 files changed, 34771 insertions(+), 1 deletion(-) create mode 100644 src/Corrosion/README.md create mode 100644 src/Corrosion/demo/index.html create mode 100644 src/Corrosion/demo/index.js create mode 100644 src/Corrosion/demo/ssl.cert create mode 100644 src/Corrosion/demo/ssl.key create mode 100644 src/Corrosion/lib/browser/document.js create mode 100644 src/Corrosion/lib/browser/history.js create mode 100644 src/Corrosion/lib/browser/http.js create mode 100644 src/Corrosion/lib/browser/index.js create mode 100644 src/Corrosion/lib/browser/location.js create mode 100644 src/Corrosion/lib/browser/worker.js create mode 100644 src/Corrosion/lib/codec.js create mode 100644 src/Corrosion/lib/cookie-parser.js create mode 100644 src/Corrosion/lib/cookie.js create mode 100644 src/Corrosion/lib/css.js create mode 100644 src/Corrosion/lib/esotope.js create mode 100644 src/Corrosion/lib/html.js create mode 100644 src/Corrosion/lib/js.js create mode 100644 src/Corrosion/lib/rewrite.js create mode 100644 src/Corrosion/lib/server/bundle.js create mode 100644 src/Corrosion/lib/server/decompress.js create mode 100644 src/Corrosion/lib/server/gateway.js create mode 100644 src/Corrosion/lib/server/headers.js create mode 100644 src/Corrosion/lib/server/index.js create mode 100644 src/Corrosion/lib/server/middleware.js create mode 100644 src/Corrosion/lib/server/request.js create mode 100644 src/Corrosion/lib/server/rewrite-body.js create mode 100644 src/Corrosion/lib/server/upgrade.js create mode 100644 src/Corrosion/lib/url.js create mode 100644 src/Corrosion/package.json diff --git a/app.js b/app.js index c0a45436..f7fe9832 100644 --- a/app.js +++ b/app.js @@ -4,7 +4,7 @@ * MIT license: http://opensource.org/licenses/MIT * ----------------------------------------------- */ const - corrosion = require('corrosion'), + corrosion = require('./src/Corrosion'), path = require('path'), config = require('./config.json'), fs = require('fs'), diff --git a/src/Corrosion/README.md b/src/Corrosion/README.md new file mode 100644 index 00000000..4df0221c --- /dev/null +++ b/src/Corrosion/README.md @@ -0,0 +1,223 @@ +# Corrosion +Titanium Networks main web proxy. +Successor to [Alloy](https://github.com/titaniumnetwork-dev/alloy) +# Installation: +``` +npm i corrosion +``` + +# Example: +```javascript +const Corrosion = require('corrosion'); +const proxy = new Corrosion(); +const http = require('http') +http.createServer((req, res) => + proxy.request(req, res) // Request Proxy +).on('upgrade', (req, socket, head) => + proxy.upgrade(req, socket, head) // WebSocket Proxy +).listen(80); +``` +Much more in depth one is in the [demo folder](demo/). + +# API: + + +## Index +- `config` + - `prefix` String - URL Prefix + - `title` (Boolean / String) - Title used for HTML documents + - `ws` Boolean - WebSocket rewriting + - `cookie` Boolean - Request Cookies + - `codec` String - URL encoding (base64, plain, xor). + - `requestMiddleware` Array - Array of [middleware](#middleware) functions for proxy request (Server). + - `responseMiddleware` Array - Array of [middleware](#middleware) functions for proxy response (Server). + - `standardMiddleware` Boolean - Use the prebuilt [middleware](#middleware) used by default (Server). + +#### request + - `request` Request + - `response` Response + +#### upgrade + - `request` Request + - `socket` Socket + - `head` Head + +#### bundleScripts +Bundles scripts for client injection. Important when updating proxy. + +## Properties + - [url](#url) + - [html](#html) + - [js](#js) + - [css](#css) + - [cookies](#cookies) + - [config](#index) + - [codec](#codec) + - [prefix](#url) + + + +## url + +#### wrap + - `val` String + - `config` Configuration + - `base` WHATWG URL + - `origin` Location origin - Adds a location origin before the proxy url + - `flags` Array - ['xhr'] => /service/xhr_/https%3A%2F%2Fexample.org/ + +#### unwrap + - `val` String + - `config` Configuration + - `origin` Location origin - Required if a location origin starts before the proxy url + - `flags` Boolean - Returns with both the URL and flags found { value: 'https://example.org', flags: ['xhr'], }) + - `leftovers` Boolean - Use any leftovers if any after the encoded proxy url + + +## Properties + - `regex` Regex used to determine to rewrite the URL or not. + + - `prefix` URL Prefix + + - `codec` (base64, plain, xor) + + +## js + +#### process + - `source` JS script + - `url` URL for heading + +#### iterate + - `ast` JS AST + - `Callback` Handler initated on AST node + +#### createHead + - `url` URL for heading + +#### createCallExperssion + - `callee` Acorn.js Node + - `args` Array + +#### createArrayExpression + - `elements` Array + +#### createIdentifier + - `name` Identifier name + - `preventRewrite` Prevent further rewrites + +#### createLiteral + - `value` Literal value + +## css + +#### process + - `source` CSS + - `config` Configuration + - `base` WHATWG URL + - `origin` Location origin + - `context` CSS-Tree context + +## html + +#### process + - `source` HTML Source + - `config` Configuration + - `document` Determines of its a document or fragment for parsing + - `base` WHATWG URL + - `origin` Location origin + +#### source + - `processed` Rewritten HTML + - `config` Configuration + - `document` Determines of its a document or fragment for parsing + +### Properties +- `map` Map for attribute rewriting + + +## cookies + +#### encode + - `input` New (Cookie / Cookies) + - `config` Configuration + - `url` WHATWG URL + - `domain` Cookie Domain + - `secure` Cookie Secure + +#### decode + - `store` Encoded Cookies + - `config` Configuration + - `url` WHATWG URL + +## codec + +#### encode +#### decode + - `str` String + +## middleware + +Middleware are functions that will be executed either before request or after response. These can alter the way a request is made or response is sent. + +```javascript +function(ctx) {r + ctx.body; // (Request / Response) Body (Will return null if none) + ctx.headers; // (Request / Response) Headers + ctx.url; // WHATWG URL + ctx.flags; // URL Flags + ctx.origin; // Request origin + ctx.method; // Request method + ctx.rewrite; // Corrosion object + ctx.statusCode; // Response status (Only available on response) + ctx.agent; // HTTP agent + ctx.address; // Address used to make remote request + ctx.clientSocket; // Node.js Server Socket (Only available on upgrade) + ctx.clientRequest; // Node.js Server Request + ctx.clientResponse; // Node.js Server Response + ctx.remoteResponse; // Node.js Remote Response (Only available on response) +}; +``` + +### Default middleware + +- Request + - requestHeaders + +- Response + - responseHeaders + - decompress + - rewriteBody + +### Available Middleware + +#### address (Request) + - `arr` Array of IP addresses to use in request + +```javascript +const Corrosion = require('corrosion'); +const proxy = new Corrosion({ + requestMiddleware: [ + Corrosion.middleware.address([ + 0.0.0.0, + 0.0.0.0 + ]), + ], +}); +``` + +### blacklist + - `arr` Array of hostnames to block clients from seeing + - `page` Block page + +```javascript +const Corrosion = require('corrosion'); +const proxy = new Corrosion({ + requestMiddleware: [ + Corrosion.middleware.blacklist([ + 'example.org', + 'example.com', + ], 'Page is blocked'), + ], +}); +``` diff --git a/src/Corrosion/demo/index.html b/src/Corrosion/demo/index.html new file mode 100644 index 00000000..ad595e6e --- /dev/null +++ b/src/Corrosion/demo/index.html @@ -0,0 +1,16 @@ + + + + + + +
+ + +
+ + \ No newline at end of file diff --git a/src/Corrosion/demo/index.js b/src/Corrosion/demo/index.js new file mode 100644 index 00000000..d3a8b82b --- /dev/null +++ b/src/Corrosion/demo/index.js @@ -0,0 +1,19 @@ +const https = require('https'); +const fs = require('fs'); +const path = require('path'); +const ssl = { + key: fs.readFileSync(path.join(__dirname, '/ssl.key')), + cert: fs.readFileSync(path.join(__dirname, '/ssl.cert')), +}; +const server = https.createServer(ssl); +const Corrosion = require('../'); +const proxy = new Corrosion({ + codec: 'xor', +}); + +proxy.bundleScripts(); + +server.on('request', (request, response) => { + if (request.url.startsWith(proxy.prefix)) return proxy.request(request, response); + response.end(fs.readFileSync(__dirname + '/index.html', 'utf-8')); +}).on('upgrade', (clientRequest, clientSocket, clientHead) => proxy.upgrade(clientRequest, clientSocket, clientHead)).listen(443); \ No newline at end of file diff --git a/src/Corrosion/demo/ssl.cert b/src/Corrosion/demo/ssl.cert new file mode 100644 index 00000000..f87b9e0f --- /dev/null +++ b/src/Corrosion/demo/ssl.cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIJAJnCkScWtmL0MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRgwFgYDVQQKDA9UaXRhbml1bU5l +dHdvcmsxDjAMBgNVBAsMBWdhbWVyMR4wHAYDVQQDDBUqLnRpdGFuaXVtbmV0d29y +ay5vcmcwHhcNMjAwNjEzMTg0OTU2WhcNMjEwNjEzMTg0OTU2WjBsMQswCQYDVQQG +EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEYMBYGA1UECgwPVGl0YW5pdW1OZXR3 +b3JrMQ4wDAYDVQQLDAVnYW1lcjEeMBwGA1UEAwwVKi50aXRhbml1bW5ldHdvcmsu +b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPL69+RE6r8RrFh4 +njzC8ZRnLB+yNtuGw14C0dvNb5JwgdLl5g9/wK/s0V5NGlqwxlQlxQ/gUSuYEcUR +6MYjcnaUmZZe/gaKVV0fkfkuigOWhLnI5AQxx7rhkzx1ujuyJ9D2pkDtZpSvv0yn +2yrvWhJMtjuxGYip8jaLuRpbXoafvR7nrlDaNcE/GwIjnCCxsRnY2bGbxYK840mN +fuMfF2nz+fXKPuQ/9PT48e3wOo9vM5s7yKhiHYwrogqzGN4cH4sSr1FE8C7flFyT +Yw101u7fUaopfeGCo9Pg6IrfzyzE5Qb7OlqlVk2IkvXx7pPqVc6lZCJEhOX/qF9o +n3mFqwIDAQABo1AwTjAdBgNVHQ4EFgQUC561ob2kGtFQ4az6y64b98+Fy+IwHwYD +VR0jBBgwFoAUC561ob2kGtFQ4az6y64b98+Fy+IwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEAotvUsSLSzFyxQz329tEPyH6Tmi19FQoA5ZbLg6EqeTI9 +08qOByDGkSYJi0npaIlPO1I557NxRzdO0PxK3ybol6lnzuSlqCJP5nb1dr0z2Eax +wgKht9P+ap/yozU5ye05ah2nkpcaeDPnwnnWFmfsnYNfgu62EshOS+5FETWEKVUb +LXQhGInOdJq8KZvhoLZWJoUhyAqxBfW4oVvaqs+Ff96A2NNKrvbiAVYX30rVa+x0 +KIl0/DoVvDx2Q6TiL396cAXdKUW7edRQcSsGFcxwIrU5lePm0V05aN+oCoEBvXBG +ArPN+a5kpGjJwfcpcBVf9cJ6IsvptGS9de3eTHoTyw== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/Corrosion/demo/ssl.key b/src/Corrosion/demo/ssl.key new file mode 100644 index 00000000..a878bfd4 --- /dev/null +++ b/src/Corrosion/demo/ssl.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDA8vr35ETqvxGs +WHiePMLxlGcsH7I224bDXgLR281vknCB0uXmD3/Ar+zRXk0aWrDGVCXFD+BRK5gR +xRHoxiNydpSZll7+BopVXR+R+S6KA5aEucjkBDHHuuGTPHW6O7In0PamQO1mlK+/ +TKfbKu9aEky2O7EZiKnyNou5Gltehp+9HueuUNo1wT8bAiOcILGxGdjZsZvFgrzj +SY1+4x8XafP59co+5D/09Pjx7fA6j28zmzvIqGIdjCuiCrMY3hwfixKvUUTwLt+U +XJNjDXTW7t9Rqil94YKj0+Doit/PLMTlBvs6WqVWTYiS9fHuk+pVzqVkIkSE5f+o +X2ifeYWrAgMBAAECggEAbihK8Ev6rKr5RBQeiPjXs2SuoppV/MvIXLHHmliLKS/J +29S0PGyM202VPtM/4dP1KMXR6nft8WmaIEsKtoKoqijZHfajtRO21pWb+JLy5wi1 +XoFTGBrs8MLZFl5mODTsuZ6rsq9O2kn5LJZvHsmcbSgVc9UQfytvG0HY840ArS3g +kSDtUFb1xRui6wtCBKzHVvCT+FXhSBbwkHalmbqP6BefhJ3lW2VonkOcHDrdXPfW +CEN18IJ2v8QYgXqZP6VUlAweNXLJ33ZOl+jXGdygcOG24MFqdw0VtP0XFGk0jnSS +W6dX67BZKeZ71EKaTy02jw5LpQNXA70ismPJHQ2uQQKBgQDuROawnBIW1fC3xOle +m+JmP0eMe0eIQycxRsMXsXhYAA0wV3qYZSLZrNK2eRhmSNt+ODSmZ2Vt11dwOv5u +bo8WONrRlM097SmitS2S+8o7ASem2VKQzyRE72Y9517Q+aNBdLRVtjrRNSw/hfSu +ayLuG36+yukSH7wq7mfoUX34ZwKBgQDPTrgyyw8n5XhZT/qTTRnQJ2GTvPxDzNoJ +IAGhGJGFAb6wgLoSpGx6BC122vuRxcTjkjAiMDci5N2zNW+YZVni+F0KTVvNFfU2 +pOTJUg3luRTygCra6O02PxwpbP/9KCBAKq/kYw/eBW+gxhPwP3ZrbAirvBjgBh0I +kIrFijNOHQKBgGUUAbFGZD4fwCCVflLOWnr5uUaVPcFGi6fR1w2EEgNy8iVh1vYz +YVdqg3E5aepqWgLvoRY+or64LbXEsQ70A+tvbxSdxXvR0mnd5lmGS0JAuSuE4gvg +dAhybrMwJf8NB/7KnX4G8mix3/WKxEQB2y2bqGcT+U/g+phTzuy1NXVdAoGBAIrl +jVjK4J60iswcYCEteWwT1rbr2oF60WNnxG+xTF63apJLzWAMNnoSLnwCAKgMv/xR +yFo/v9FrUnduCBUtYupFyeDLMATa/27bUEbq6VDPjw9jfFMr2TONWUsQMvvlVKZp +c2wsS0dQkRhBXr6LZsZWngCiiHAg6HcCkVgFXpapAoGBAJ/8oLGt0Ar+0MTl+gyk +xSqgHnsc5jgqhix3nIoI5oEAbfibdGmRD1S3rtWD9YsnPxMIl+6E5bOAHrmd+Zr8 +O7EP+CLvbz4JXidaaa85h9ThXSG5xk1A1UTtSFrp+KolLE1Vvmjjd+R844XsM2wZ +OAHbihzk0iPPphjEWR4lU4Av +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/src/Corrosion/lib/browser/document.js b/src/Corrosion/lib/browser/document.js new file mode 100644 index 00000000..4cc19a7b --- /dev/null +++ b/src/Corrosion/lib/browser/document.js @@ -0,0 +1,271 @@ +function createDocumentRewriter(ctx) { + 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); + }, + }); + }; + 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) : '' ]); + }, + }), + }); + }; + 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); + }, + }); + }; + 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.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); + }, + }); + }; + [ + 'innerHTML', + 'outerHTML', + ].forEach(html => { + const descriptor = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, html); + Object.defineProperty(ctx.window.Element.prototype, html, { + get: new Proxy(descriptor.get, { + apply: (target, that, args) => { + const body = Reflect.apply(target, that, args); + if (!body || html == 'innerHTML' && that.tagName == 'SCRIPT') return body; + return ctx.html.source(body, ctx.meta); + }, + }), + set: new Proxy(descriptor.set, { + apply(target, that, [ val ]) { + return Reflect.apply(target, that, [ val ? ctx.html.process(val.toString(), ctx.meta) : val, ]); + }, + }), + }); + }); + [ + ['background', 'background'], + ['backgroundImage', 'background-image'], + ['listStyleImage', 'list-style-image'], + ].forEach(([key, cssProperty]) => { + Object.defineProperty(ctx.window.CSS2Properties ? ctx.window.CSS2Properties.prototype : ctx.window.CSSStyleDeclaration.prototype, key, { + get() { + return this.getPropertyValue(cssProperty); + }, + set(val) { + return this.setProperty(cssProperty, val); + }, + }); + }); + Object.defineProperty(ctx.window.Document.prototype, 'domain', { + get: new Proxy(domain.get, { + apply: () => spoofDomain, + }), + set: new Proxy(domain.set, { + apply: (target, that, [ val ]) => { + if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) return Reflect.apply(target, that, ['']); + return spoofDomain = val; + }, + }), + }); + if (ctx.config.title) Object.defineProperty(ctx.window.Document.prototype, 'title', { + get: new Proxy(title.get, { + apply: () => spoofTitle, + }), + set: new Proxy(title.set, { + apply: (target, that, [ val ]) => spoofTitle = val, + }), + }); + Object.defineProperty(ctx.window.Navigator.prototype, 'cookieEnabled', { + get: new Proxy(cookieEnabled.get, { + apply: () => ctx.config.cookie, + }), + }); + Object.defineProperty(ctx.window.Node.prototype, 'baseURI', { + get: new Proxy(baseURI.get, { + apply: (target, that, args) => { + const val = Reflect.apply(target, that, args); + return val.startsWith(ctx.meta.origin) ? ctx.url.unwrap(val, ctx.meta) : val; + }, + }), + }); + [ + { + elements: [ HTMLScriptElement, HTMLMediaElement, HTMLImageElement, HTMLAudioElement, HTMLVideoElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement], + properties: ['src'], + handler: 'url', + }, + { + elements: [ HTMLFormElement ], + properties: ['action'], + handler: 'url', + }, + { + elements: [ HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement ], + properties: ['href'], + handler: 'url', + }, + { + elements: [ HTMLImageElement, HTMLSourceElement ], + properties: ['srcset'], + handler: 'srcset', + }, + { + elements: [ HTMLScriptElement ], + properties: ['integrity'], + handler: 'delete', + }, + { + elements: [ HTMLIFrameElement ], + properties: ['contentWindow'], + handler: 'window', + }, + ].forEach(entry => { + entry.elements.forEach(element => { + if (!element) return; + entry.properties.forEach(property => { + if (!element.prototype.hasOwnProperty(property)) return; + const descriptor = Object.getOwnPropertyDescriptor(element.prototype, property); + Object.defineProperty(element.prototype, property, { + get: descriptor.get ? new Proxy(descriptor.get, { + apply: (target, that, args) => { + let val = Reflect.apply(target, that, args); + let flags = []; + switch(entry.handler) { + case 'url': + //if (that.tagName == 'SCRIPT' && property == 'src') flags.push('js'); + val = ctx.url.unwrap(val, ctx.meta); + break; + case 'srcset': + val = ctx.html.unsrcset(val, ctx.meta); + break; + case 'delete': + val = that.getAttribute(`corrosion-${property}`); + break; + case 'window': + try { + if (!val.$corrosion) { + val.$corrosion = new ctx.constructor({ ...ctx.config, window: val, }); + val.$corrosion.init(); + val.$corrosion.meta = ctx.meta; + }; + } catch(e) {}; + }; + return val; + }, + }) : undefined, + set: descriptor.set ? new Proxy(descriptor.set, { + apply(target, that, [ val ]) { + let newVal = val; + switch(entry.handler) { + case 'url': + newVal = ctx.url.wrap(newVal, ctx.meta); + break; + case 'srcset': + newVal = ctx.html.srcset(newVal, ctx.meta); + break; + case 'delete': + that.setAttribute(property, newVal); + return newVal; + }; + return Reflect.apply(target, that, [ newVal ]); + }, + }) : undefined, + }); + }); + }); + }); + }; +}; +module.exports = createDocumentRewriter; \ No newline at end of file diff --git a/src/Corrosion/lib/browser/history.js b/src/Corrosion/lib/browser/history.js new file mode 100644 index 00000000..7c2f44d6 --- /dev/null +++ b/src/Corrosion/lib/browser/history.js @@ -0,0 +1,26 @@ +function createHistoryRewriter(ctx) { + return function rewriteHistory() { + 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/src/Corrosion/lib/browser/http.js b/src/Corrosion/lib/browser/http.js new file mode 100644 index 00000000..df557b98 --- /dev/null +++ b/src/Corrosion/lib/browser/http.js @@ -0,0 +1,92 @@ +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); + }, + }); + 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); + }, + }); + }; + }; +}; + +module.exports = createHttpRewriter; \ No newline at end of file diff --git a/src/Corrosion/lib/browser/index.js b/src/Corrosion/lib/browser/index.js new file mode 100644 index 00000000..41337a39 --- /dev/null +++ b/src/Corrosion/lib/browser/index.js @@ -0,0 +1,195 @@ +const createDocumentRewriter = require('./document'); +const createHistoryRewriter = require('./history'); +const createHttpRewriter = require('./http'); +const createLocation = require('./location'); +const createWorkerRewriter = require('./worker'); +const defaultConfig = { + prefix: '/service/', + codec: 'plain', + ws: true, + cookie: true, + title: 'Service', + serviceWorker: false, + url: null, + window: false, +}; + +class Corrosion extends require('../rewrite') { + constructor(config = defaultConfig) { + super(Object.assign(defaultConfig, config)); + const corrosion = this; + 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.originalXhr = this.window.XMLHttpRequest; + this.meta = { + 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.location = createLocation(this, this._url); + this.rewriteHttp = createHttpRewriter(this); + this.rewriteDocument = createDocumentRewriter(this); + this.rewriteHistory = createHistoryRewriter(this); + this.rewriteWorker = createWorkerRewriter(this); + if (!this.serviceWorker && this.window.document.currentScript) this.window.document.currentScript.remove(); + }; + get parent() { + if (this.serviceWorker) return false; + try { + return this.window.parent.$corrosion ? this.window.parent : this.window; + } catch(e) { + return this.window; + }; + }; + 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.rewriteHttp(); + this.rewriteDocument(); + this.rewriteHistory(); + this.rewriteWorker(); + 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/src/Corrosion/lib/browser/location.js b/src/Corrosion/lib/browser/location.js new file mode 100644 index 00000000..02d7c5a0 --- /dev/null +++ b/src/Corrosion/lib/browser/location.js @@ -0,0 +1,56 @@ +class Location { + get [Symbol.toPrimitive]() { + return () => this.href; + }; +}; + +function createLocation(ctx, url) { + const _location = new Location(); + const _url = new URL(url); + [ + 'hash', + 'host', + 'hostname', + 'href', + 'pathname', + 'port', + 'protocol', + 'search', + 'origin', + ].forEach(property => { + Object.defineProperty(_location, property, { + 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); + }; + _url[property] = val; + return ctx.window.location.href = ctx.url.wrap(_url); + }, + }); + }); + if (!ctx.serviceWorker) [ + 'assign', + 'replace', + 'reload', + ].forEach(method => { + _location[method] = new Proxy(ctx.window.location[method], { + apply(target, that, args) { + if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); + return Reflect.apply(target.bind(ctx.window.location), that, args); + }, + }); + }); + _location.toString = new Proxy(_url.toString, { + apply(target, that, args) { + return Reflect.apply(target.bind(_url), that, args); + }, + }); + return _location; +}; + +createLocation.Location = Location; +module.exports = createLocation; \ No newline at end of file diff --git a/src/Corrosion/lib/browser/worker.js b/src/Corrosion/lib/browser/worker.js new file mode 100644 index 00000000..c65486c0 --- /dev/null +++ b/src/Corrosion/lib/browser/worker.js @@ -0,0 +1,33 @@ +function createWorkerRewriter(ctx = {}) { + return function rewriteWorker() { + if (ctx.window.Worker) { + ctx.window.Worker = new Proxy(ctx.window.Worker, { + construct: (target, args) => { + if (args[0]) { + if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) { + const xhr = new ctx.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); + }, + }); + }; + }; +}; + +module.exports = createWorkerRewriter; \ No newline at end of file diff --git a/src/Corrosion/lib/codec.js b/src/Corrosion/lib/codec.js new file mode 100644 index 00000000..a3fc2742 --- /dev/null +++ b/src/Corrosion/lib/codec.js @@ -0,0 +1,66 @@ +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; + }, +}; \ No newline at end of file diff --git a/src/Corrosion/lib/cookie-parser.js b/src/Corrosion/lib/cookie-parser.js new file mode 100644 index 00000000..c11dfe7d --- /dev/null +++ b/src/Corrosion/lib/cookie-parser.js @@ -0,0 +1,95 @@ +// ------------------- +// This file is shared both by the server and client. +// Do not include any browser or node specific APIs +// ------------------- + +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; \ No newline at end of file diff --git a/src/Corrosion/lib/cookie.js b/src/Corrosion/lib/cookie.js new file mode 100644 index 00000000..1d505968 --- /dev/null +++ b/src/Corrosion/lib/cookie.js @@ -0,0 +1,34 @@ +const { SetCookie, CookieStore } = require('./cookie-parser'); + +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; \ No newline at end of file diff --git a/src/Corrosion/lib/css.js b/src/Corrosion/lib/css.js new file mode 100644 index 00000000..7604eb88 --- /dev/null +++ b/src/Corrosion/lib/css.js @@ -0,0 +1,67 @@ +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; \ No newline at end of file diff --git a/src/Corrosion/lib/esotope.js b/src/Corrosion/lib/esotope.js new file mode 100644 index 00000000..21040764 --- /dev/null +++ b/src/Corrosion/lib/esotope.js @@ -0,0 +1,2562 @@ +// ------------------------------------------------------------- +// WARNING: this file is used by both the client and the server. +// Do not use any browser or node-specific API! +// ------------------------------------------------------------- + +/* + Copyright (C) 2014 Ivan Nikulin + Copyright (C) 2012-2014 Yusuke Suzuki + Copyright (C) 2012-2013 Michael Ficarra + Copyright (C) 2012-2013 Mathias Bynens + Copyright (C) 2013 Irakli Gozalishvili + Copyright (C) 2012 Robert Gust-Bardon + Copyright (C) 2012 John Freeman + Copyright (C) 2011-2012 Ariya Hidayat + Copyright (C) 2012 Joost-Wim Boekesteijn + Copyright (C) 2012 Kris Kowal + Copyright (C) 2012 Arpad Borsos + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + 'use strict'; + + var isArray, + json, + renumber, + hexadecimal, + quotes, + escapeless, + parentheses, + semicolons, + safeConcatenation, + directive, + extra, + parse; + + var Syntax = { + AssignmentExpression: 'AssignmentExpression', + AssignmentPattern: 'AssignmentPattern', + ArrayExpression: 'ArrayExpression', + ArrayPattern: 'ArrayPattern', + ArrowFunctionExpression: 'ArrowFunctionExpression', + AwaitExpression: 'AwaitExpression', + BlockStatement: 'BlockStatement', + BinaryExpression: 'BinaryExpression', + BreakStatement: 'BreakStatement', + CallExpression: 'CallExpression', + CatchClause: 'CatchClause', + ClassBody: 'ClassBody', + ClassDeclaration: 'ClassDeclaration', + ClassExpression: 'ClassExpression', + ComprehensionBlock: 'ComprehensionBlock', + ComprehensionExpression: 'ComprehensionExpression', + ConditionalExpression: 'ConditionalExpression', + ContinueStatement: 'ContinueStatement', + DirectiveStatement: 'DirectiveStatement', + DoWhileStatement: 'DoWhileStatement', + DebuggerStatement: 'DebuggerStatement', + EmptyStatement: 'EmptyStatement', + ExportAllDeclaration: 'ExportAllDeclaration', + ExportBatchSpecifier: 'ExportBatchSpecifier', + ExportDeclaration: 'ExportDeclaration', + ExportNamedDeclaration: 'ExportNamedDeclaration', + ExportSpecifier: 'ExportSpecifier', + ExpressionStatement: 'ExpressionStatement', + ForStatement: 'ForStatement', + ForInStatement: 'ForInStatement', + ForOfStatement: 'ForOfStatement', + FunctionDeclaration: 'FunctionDeclaration', + FunctionExpression: 'FunctionExpression', + GeneratorExpression: 'GeneratorExpression', + Identifier: 'Identifier', + IfStatement: 'IfStatement', + ImportExpression: 'ImportExpression', + ImportSpecifier: 'ImportSpecifier', + ImportDeclaration: 'ImportDeclaration', + ChainExpression: 'ChainExpression', + Literal: 'Literal', + LabeledStatement: 'LabeledStatement', + LogicalExpression: 'LogicalExpression', + MemberExpression: 'MemberExpression', + MetaProperty: 'MetaProperty', + MethodDefinition: 'MethodDefinition', + ModuleDeclaration: 'ModuleDeclaration', + NewExpression: 'NewExpression', + ObjectExpression: 'ObjectExpression', + ObjectPattern: 'ObjectPattern', + Program: 'Program', + Property: 'Property', + RestElement: 'RestElement', + ReturnStatement: 'ReturnStatement', + SequenceExpression: 'SequenceExpression', + SpreadElement: 'SpreadElement', + Super: 'Super', + SwitchStatement: 'SwitchStatement', + SwitchCase: 'SwitchCase', + TaggedTemplateExpression: 'TaggedTemplateExpression', + TemplateElement: 'TemplateElement', + TemplateLiteral: 'TemplateLiteral', + ThisExpression: 'ThisExpression', + ThrowStatement: 'ThrowStatement', + TryStatement: 'TryStatement', + UnaryExpression: 'UnaryExpression', + UpdateExpression: 'UpdateExpression', + VariableDeclaration: 'VariableDeclaration', + VariableDeclarator: 'VariableDeclarator', + WhileStatement: 'WhileStatement', + WithStatement: 'WithStatement', + YieldExpression: 'YieldExpression' + }; + + exports.Syntax = Syntax; + + var Precedence = { + Sequence: 0, + Yield: 1, + Assignment: 1, + Conditional: 2, + ArrowFunction: 2, + Coalesce: 3, + LogicalOR: 3, + LogicalAND: 4, + BitwiseOR: 5, + BitwiseXOR: 6, + BitwiseAND: 7, + Equality: 8, + Relational: 9, + BitwiseSHIFT: 10, + Additive: 11, + Multiplicative: 12, + Unary: 13, + Exponentiation: 14, + Postfix: 14, + Await: 14, + Call: 15, + New: 16, + TaggedTemplate: 17, + OptionalChaining: 17, + Member: 18, + Primary: 19 + }; + + var BinaryPrecedence = { + '||': Precedence.LogicalOR, + '&&': Precedence.LogicalAND, + '|': Precedence.BitwiseOR, + '^': Precedence.BitwiseXOR, + '&': Precedence.BitwiseAND, + '==': Precedence.Equality, + '!=': Precedence.Equality, + '===': Precedence.Equality, + '!==': Precedence.Equality, + 'is': Precedence.Equality, + 'isnt': Precedence.Equality, + '<': Precedence.Relational, + '>': Precedence.Relational, + '<=': Precedence.Relational, + '>=': Precedence.Relational, + 'in': Precedence.Relational, + 'instanceof': Precedence.Relational, + '<<': Precedence.BitwiseSHIFT, + '>>': Precedence.BitwiseSHIFT, + '>>>': Precedence.BitwiseSHIFT, + '+': Precedence.Additive, + '-': Precedence.Additive, + '*': Precedence.Multiplicative, + '%': Precedence.Multiplicative, + '/': Precedence.Multiplicative, + '??': Precedence.Coalesce, + '**': Precedence.Exponentiation + }; + + function getDefaultOptions () { + // default options + return { + indent: null, + base: null, + parse: null, + format: { + indent: { + style: ' ', + base: 0 + }, + newline: '\n', + space: ' ', + json: false, + renumber: false, + hexadecimal: false, + quotes: 'single', + escapeless: false, + compact: false, + parentheses: true, + semicolons: true, + safeConcatenation: false + }, + directive: false, + raw: true, + verbatim: null + }; + } + + //-------------------------------------------------===------------------------------------------------------ + // Lexical utils + //-------------------------------------------------===------------------------------------------------------ + + //Const + var NON_ASCII_WHITESPACES = [ + 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, + 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, + 0xFEFF + ]; + + //Regular expressions + var NON_ASCII_IDENTIFIER_CHARACTERS_REGEXP = new RegExp( + '[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376' + + '\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-' + + '\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA' + + '\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-' + + '\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0\u08A2-\u08AC\u08E4-\u08FE\u0900-' + + '\u0963\u0966-\u096F\u0971-\u0977\u0979-\u097F\u0981-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-' + + '\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-' + + '\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38' + + '\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83' + + '\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9' + + '\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-' + + '\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-' + + '\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E' + + '\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-' + + '\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D-\u0C44\u0C46-' + + '\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C82\u0C83\u0C85-\u0C8C\u0C8E-' + + '\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE' + + '\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44' + + '\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-' + + '\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A' + + '\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-' + + '\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9' + + '\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84' + + '\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-' + + '\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5' + + '\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-' + + '\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-' + + '\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD' + + '\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191C\u1920-\u192B' + + '\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E' + + '\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-' + + '\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1D00-\u1DE6\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-' + + '\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-' + + '\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F' + + '\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115' + + '\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188' + + '\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-' + + '\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-' + + '\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A' + + '\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5' + + '\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA697' + + '\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA827\uA840-\uA873' + + '\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-' + + '\uA9D9\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7B\uAA80-\uAAC2\uAADB-\uAADD\uAAE0-' + + '\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABEA\uABEC' + + '\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-' + + '\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D' + + '\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE26\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74' + + '\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-' + + '\uFFD7\uFFDA-\uFFDC]' + ); + + + //Methods + function isIdentifierCh (cp) { + if (cp < 0x80) { + return cp >= 97 && cp <= 122 || // a..z + cp >= 65 && cp <= 90 || // A..Z + cp >= 48 && cp <= 57 || // 0..9 + cp === 36 || cp === 95 || // $ (dollar) and _ (underscore) + cp === 92; // \ (backslash) + } + + var ch = String.fromCharCode(cp); + + return NON_ASCII_IDENTIFIER_CHARACTERS_REGEXP.test(ch); + } + + function isLineTerminator (cp) { + return cp === 0x0A || cp === 0x0D || cp === 0x2028 || cp === 0x2029; + } + + function isWhitespace (cp) { + return cp === 0x20 || cp === 0x09 || isLineTerminator(cp) || cp === 0x0B || cp === 0x0C || cp === 0xA0 || + (cp >= 0x1680 && NON_ASCII_WHITESPACES.indexOf(cp) >= 0); + } + + function isDecimalDigit (cp) { + return cp >= 48 && cp <= 57; + } + + function stringRepeat (str, num) { + var result = ''; + + for (num |= 0; num > 0; num >>>= 1, str += str) { + if (num & 1) { + result += str; + } + } + + return result; + } + + isArray = Array.isArray; + if (!isArray) { + isArray = function isArray (array) { + return Object.prototype.toString.call(array) === '[object Array]'; + }; + } + + + function updateDeeply (target, override) { + var key, val; + + function isHashObject (target) { + return typeof target === 'object' && target instanceof Object && !(target instanceof RegExp); + } + + for (key in override) { + if (override.hasOwnProperty(key)) { + val = override[key]; + if (isHashObject(val)) { + if (isHashObject(target[key])) { + updateDeeply(target[key], val); + } + else { + target[key] = updateDeeply({}, val); + } + } + else { + target[key] = val; + } + } + } + return target; + } + + function generateNumber (value) { + var result, point, temp, exponent, pos; + + if (value === 1 / 0) { + return json ? 'null' : renumber ? '1e400' : '1e+400'; + } + + result = '' + value; + if (!renumber || result.length < 3) { + return result; + } + + point = result.indexOf('.'); + //NOTE: 0x30 == '0' + if (!json && result.charCodeAt(0) === 0x30 && point === 1) { + point = 0; + result = result.slice(1); + } + temp = result; + result = result.replace('e+', 'e'); + exponent = 0; + if ((pos = temp.indexOf('e')) > 0) { + exponent = +temp.slice(pos + 1); + temp = temp.slice(0, pos); + } + if (point >= 0) { + exponent -= temp.length - point - 1; + temp = +(temp.slice(0, point) + temp.slice(point + 1)) + ''; + } + pos = 0; + + //NOTE: 0x30 == '0' + while (temp.charCodeAt(temp.length + pos - 1) === 0x30) { + --pos; + } + if (pos !== 0) { + exponent -= pos; + temp = temp.slice(0, pos); + } + if (exponent !== 0) { + temp += 'e' + exponent; + } + if ((temp.length < result.length || + (hexadecimal && value > 1e12 && Math.floor(value) === value && + (temp = '0x' + value.toString(16)).length + < result.length)) && + +temp === value) { + result = temp; + } + + return result; + } + + // Generate valid RegExp expression. + // This function is based on https://github.com/Constellation/iv Engine + + function escapeRegExpCharacter (ch, previousIsBackslash) { + // not handling '\' and handling \u2028 or \u2029 to unicode escape sequence + if ((ch & ~1) === 0x2028) { + return (previousIsBackslash ? 'u' : '\\u') + ((ch === 0x2028) ? '2028' : '2029'); + } + else if (ch === 10 || ch === 13) { // \n, \r + return (previousIsBackslash ? '' : '\\') + ((ch === 10) ? 'n' : 'r'); + } + return String.fromCharCode(ch); + } + + function generateRegExp (reg) { + var match, result, flags, i, iz, ch, characterInBrack, previousIsBackslash; + + result = reg.toString(); + + if (reg.source) { + // extract flag from toString result + match = result.match(/\/([^/]*)$/); + if (!match) { + return result; + } + + flags = match[1]; + result = ''; + + characterInBrack = false; + previousIsBackslash = false; + for (i = 0, iz = reg.source.length; i < iz; ++i) { + ch = reg.source.charCodeAt(i); + + if (!previousIsBackslash) { + if (characterInBrack) { + if (ch === 93) { // ] + characterInBrack = false; + } + } + else { + if (ch === 47) { // / + result += '\\'; + } + else if (ch === 91) { // [ + characterInBrack = true; + } + } + result += escapeRegExpCharacter(ch, previousIsBackslash); + previousIsBackslash = ch === 92; // \ + } + else { + // if new RegExp("\\\n') is provided, create /\n/ + result += escapeRegExpCharacter(ch, previousIsBackslash); + // prevent like /\\[/]/ + previousIsBackslash = false; + } + } + + return '/' + result + '/' + flags; + } + + return result; + } + + function escapeAllowedCharacter (code, next) { + var hex, result = '\\'; + + switch (code) { + case 0x08: // \b + result += 'b'; + break; + case 0x0C: // \f + result += 'f'; + break; + case 0x09: // \t + result += 't'; + break; + default: + hex = code.toString(16).toUpperCase(); + if (json || code > 0xFF) { + result += 'u' + '0000'.slice(hex.length) + hex; + } + + else if (code === 0x0000 && !isDecimalDigit(next)) { + result += '0'; + } + + else if (code === 0x000B) { // \v + result += 'x0B'; + } + + else { + result += 'x' + '00'.slice(hex.length) + hex; + } + break; + } + + return result; + } + + function escapeDisallowedCharacter (code) { + var result = '\\'; + switch (code) { + case 0x5C // \ + : + result += '\\'; + break; + case 0x0A // \n + : + result += 'n'; + break; + case 0x0D // \r + : + result += 'r'; + break; + case 0x2028: + result += 'u2028'; + break; + case 0x2029: + result += 'u2029'; + break; + } + + return result; + } + + function escapeDirective (str) { + var i, iz, code, quote; + + quote = quotes === 'double' ? '"' : '\''; + for (i = 0, iz = str.length; i < iz; ++i) { + code = str.charCodeAt(i); + if (code === 0x27) { // ' + quote = '"'; + break; + } + else if (code === 0x22) { // " + quote = '\''; + break; + } + else if (code === 0x5C) { // \ + ++i; + } + } + + return quote + str + quote; + } + + function escapeString (str) { + var result = '', i, len, code, singleQuotes = 0, doubleQuotes = 0, single, quote; + //TODO http://jsperf.com/character-counting/8 + for (i = 0, len = str.length; i < len; ++i) { + code = str.charCodeAt(i); + if (code === 0x27) { // ' + ++singleQuotes; + } + else if (code === 0x22) { // " + ++doubleQuotes; + } + else if (code === 0x2F && json) { // / + result += '\\'; + } + else if (isLineTerminator(code) || code === 0x5C) { // \ + result += escapeDisallowedCharacter(code); + continue; + } + else if ((json && code < 0x20) || // SP + !(json || escapeless || (code >= 0x20 && code <= 0x7E))) { // SP, ~ + result += escapeAllowedCharacter(code, str.charCodeAt(i + 1)); + continue; + } + result += String.fromCharCode(code); + } + + single = !(quotes === 'double' || (quotes === 'auto' && doubleQuotes < singleQuotes)); + quote = single ? '\'' : '"'; + + if (!(single ? singleQuotes : doubleQuotes)) { + return quote + result + quote; + } + + str = result; + result = quote; + + for (i = 0, len = str.length; i < len; ++i) { + code = str.charCodeAt(i); + if ((code === 0x27 && single) || (code === 0x22 && !single)) { // ', " + result += '\\'; + } + result += String.fromCharCode(code); + } + + return result + quote; + } + + + function join (l, r) { + if (!l.length) + return r; + + if (!r.length) + return l; + + var lCp = l.charCodeAt(l.length - 1), + rCp = r.charCodeAt(0); + + if (isIdentifierCh(lCp) && isIdentifierCh(rCp) || + lCp === rCp && (lCp === 0x2B || lCp === 0x2D) || // + +, - - + lCp === 0x2F && rCp === 0x69) { // /re/ instanceof foo + return l + _.space + r; + } + + else if (isWhitespace(lCp) || isWhitespace(rCp)) + return l + r; + + return l + _.optSpace + r; + } + + function shiftIndent () { + var prevIndent = _.indent; + + _.indent += _.indentUnit; + return prevIndent; + } + + function adoptionPrefix ($stmt) { + if ($stmt.type === Syntax.BlockStatement) + return _.optSpace; + + if ($stmt.type === Syntax.EmptyStatement) + return ''; + + return _.newline + _.indent + _.indentUnit; + } + + function adoptionSuffix ($stmt) { + if ($stmt.type === Syntax.BlockStatement) + return _.optSpace; + + return _.newline + _.indent; + } + + //Subentities generators + function generateVerbatim ($expr, settings) { + var verbatim = $expr[extra.verbatim], + strVerbatim = typeof verbatim === 'string', + precedence = !strVerbatim && + verbatim.precedence !== void 0 ? verbatim.precedence : Precedence.Sequence, + parenthesize = precedence < settings.precedence, + content = strVerbatim ? verbatim : verbatim.content, + chunks = content.split(/\r\n|\n/), + chunkCount = chunks.length; + + if (parenthesize) + _.js += '('; + + _.js += chunks[0]; + + for (var i = 1; i < chunkCount; i++) + _.js += _.newline + _.indent + chunks[i]; + + if (parenthesize) + _.js += ')'; + } + + function generateFunctionParams ($node) { + var $params = $node.params, + paramCount = $params.length, + lastParamIdx = paramCount - 1, + arrowFuncWithoutParentheses = $node.type === Syntax.ArrowFunctionExpression && paramCount === 1 && + $params[0].type === Syntax.Identifier; + + //NOTE: arg => { } case + if (arrowFuncWithoutParentheses) + _.js += $params[0].name; + + else { + _.js += '('; + + for (var i = 0; i < paramCount; ++i) { + var $param = $params[i]; + + if ($params[i].type === Syntax.Identifier) + _.js += $param.name; + + else + ExprGen[$param.type]($param, Preset.e4); + + if (i !== lastParamIdx) + _.js += ',' + _.optSpace; + } + + _.js += ')'; + } + } + + function generateFunctionBody ($node) { + var $body = $node.body; + + generateFunctionParams($node); + + if ($node.type === Syntax.ArrowFunctionExpression) + _.js += _.optSpace + '=>'; + + if ($node.expression) { + _.js += _.optSpace; + + var exprJs = exprToJs($body, Preset.e4); + + if (exprJs.charAt(0) === '{') + exprJs = '(' + exprJs + ')'; + + _.js += exprJs; + } + + else { + _.js += adoptionPrefix($body); + StmtGen[$body.type]($body, Preset.s8); + } + } + + + //-------------------------------------------------===------------------------------------------------------ + // Syntactic entities generation presets + //-------------------------------------------------===------------------------------------------------------ + + var Preset = { + e1: function (allowIn) { + return { + precedence: Precedence.Assignment, + allowIn: allowIn, + allowCall: true, + allowUnparenthesizedNew: true + }; + }, + + e2: function (allowIn) { + return { + precedence: Precedence.LogicalOR, + allowIn: allowIn, + allowCall: true, + allowUnparenthesizedNew: true + }; + }, + + e3: { + precedence: Precedence.Call, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: false + }, + + e4: { + precedence: Precedence.Assignment, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: true + }, + + e5: { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: true + }, + + e6: function (allowUnparenthesizedNew) { + return { + precedence: Precedence.New, + allowIn: true, + allowCall: false, + allowUnparenthesizedNew: allowUnparenthesizedNew + }; + }, + + e7: { + precedence: Precedence.Unary, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: true + }, + + e8: { + precedence: Precedence.Postfix, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: true + }, + + e9: { + precedence: void 0, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: true + }, + + e10: { + precedence: Precedence.Call, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: true + }, + + e11: function (allowCall) { + return { + precedence: Precedence.Call, + allowIn: true, + allowCall: allowCall, + allowUnparenthesizedNew: false + }; + }, + + e12: { + precedence: Precedence.Primary, + allowIn: false, + allowCall: false, + allowUnparenthesizedNew: true + }, + + e13: { + precedence: Precedence.Primary, + allowIn: true, + allowCall: true, + allowUnparenthesizedNew: true + }, + + + e14: { + precedence: Precedence.Sequence, + allowIn: false, + allowCall: true, + allowUnparenthesizedNew: true + }, + + + e15: function (allowCall) { + return { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: allowCall, + allowUnparenthesizedNew: true + }; + }, + + e16: function (precedence, allowIn) { + return { + precedence: precedence, + allowIn: allowIn, + allowCall: true, + allowUnparenthesizedNew: true + }; + }, + + e17: function (allowIn) { + return { + precedence: Precedence.Call, + allowIn: allowIn, + allowCall: true, + allowUnparenthesizedNew: true + } + }, + + e18: function (allowIn) { + return { + precedence: Precedence.Assignment, + allowIn: allowIn, + allowCall: true, + allowUnparenthesizedNew: true + } + }, + + e19: { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true, + semicolonOptional: false + }, + + e20: { + precedence: Precedence.Await, + allowCall: true + }, + + s1: function (functionBody, semicolonOptional) { + return { + allowIn: true, + functionBody: false, + directiveContext: functionBody, + semicolonOptional: semicolonOptional + }; + }, + + s2: { + allowIn: true, + functionBody: false, + directiveContext: false, + semicolonOptional: true + }, + + s3: function (allowIn) { + return { + allowIn: allowIn, + functionBody: false, + directiveContext: false, + semicolonOptional: false + }; + }, + + s4: function (semicolonOptional) { + return { + allowIn: true, + functionBody: false, + directiveContext: false, + semicolonOptional: semicolonOptional + }; + }, + + s5: function (semicolonOptional) { + return { + allowIn: true, + functionBody: false, + directiveContext: true, + semicolonOptional: semicolonOptional, + }; + }, + + s6: { + allowIn: false, + functionBody: false, + directiveContext: false, + semicolonOptional: false + }, + + s7: { + allowIn: true, + functionBody: false, + directiveContext: false, + semicolonOptional: false + }, + + s8: { + allowIn: true, + functionBody: true, + directiveContext: false, + semicolonOptional: false + } + }; + + + //-------------------------------------------------===------------------------------------------------------- + // Expressions + //-------------------------------------------------===------------------------------------------------------- + + //Regular expressions + var FLOATING_OR_OCTAL_REGEXP = /[.eExX]|^0[0-9]+/, + LAST_DECIMAL_DIGIT_REGEXP = /[0-9]$/; + + + //Common expression generators + function generateLogicalOrBinaryExpression ($expr, settings) { + var op = $expr.operator, + precedence = BinaryPrecedence[$expr.operator], + parenthesize = precedence < settings.precedence, + allowIn = settings.allowIn || parenthesize, + operandGenSettings = Preset.e16(precedence, allowIn), + exprJs = exprToJs($expr.left, operandGenSettings); + + parenthesize |= op === 'in' && !allowIn; + + if (parenthesize) + _.js += '('; + + // 0x2F = '/' + if (exprJs.charCodeAt(exprJs.length - 1) === 0x2F && isIdentifierCh(op.charCodeAt(0))) + exprJs = exprJs + _.space + op; + + else + exprJs = join(exprJs, op); + + operandGenSettings.precedence++; + + var rightJs = exprToJs($expr.right, operandGenSettings); + + //NOTE: If '/' concats with '/' or `<` concats with `!--`, it is interpreted as comment start + if (op === '/' && rightJs.charAt(0) === '/' || op.slice(-1) === '<' && rightJs.slice(0, 3) === '!--') + exprJs += _.space + rightJs; + + else + exprJs = join(exprJs, rightJs); + + _.js += exprJs; + + if (parenthesize) + _.js += ')'; + } + + function generateArrayPatternOrExpression ($expr) { + var $elems = $expr.elements, + elemCount = $elems.length; + + if (elemCount) { + var lastElemIdx = elemCount - 1, + multiline = elemCount > 1, + prevIndent = shiftIndent(), + itemPrefix = _.newline + _.indent; + + _.js += '['; + + for (var i = 0; i < elemCount; i++) { + var $elem = $elems[i]; + + if (multiline) + _.js += itemPrefix; + + if ($elem) + ExprGen[$elem.type]($elem, Preset.e4); + + if (i !== lastElemIdx || !$elem) + _.js += ','; + } + + _.indent = prevIndent; + + if (multiline) + _.js += _.newline + _.indent; + + _.js += ']'; + } + + else + _.js += '[]'; + } + + function generateGeneratorOrComprehensionExpression ($expr) { + //NOTE: GeneratorExpression should be parenthesized with (...), ComprehensionExpression with [...] + var $blocks = $expr.blocks, + $filter = $expr.filter, + isGenerator = $expr.type === Syntax.GeneratorExpression, + exprJs = isGenerator ? '(' : '[', + bodyJs = exprToJs($expr.body, Preset.e4); + + if ($blocks) { + var prevIndent = shiftIndent(), + blockCount = $blocks.length; + + for (var i = 0; i < blockCount; ++i) { + var blockJs = exprToJs($blocks[i], Preset.e5); + + exprJs = i > 0 ? join(exprJs, blockJs) : (exprJs + blockJs); + } + + _.indent = prevIndent; + } + + if ($filter) { + var filterJs = exprToJs($filter, Preset.e5); + + exprJs = join(exprJs, 'if' + _.optSpace); + exprJs = join(exprJs, '(' + filterJs + ')'); + } + + exprJs = join(exprJs, bodyJs); + exprJs += isGenerator ? ')' : ']'; + + _.js += exprJs; + } + + + //Expression raw generator dictionary + var ExprRawGen = { + SequenceExpression: function generateSequenceExpression ($expr, settings) { + var $children = $expr.expressions, + childrenCount = $children.length, + lastChildIdx = childrenCount - 1, + parenthesize = Precedence.Sequence < settings.precedence, + exprGenSettings = Preset.e1(settings.allowIn || parenthesize); + + if (parenthesize) + _.js += '('; + + for (var i = 0; i < childrenCount; i++) { + var $child = $children[i]; + + ExprGen[$child.type]($child, exprGenSettings); + + if (i !== lastChildIdx) + _.js += ',' + _.optSpace; + } + + if (parenthesize) + _.js += ')'; + }, + + AssignmentExpression: function generateAssignmentExpression ($expr, settings) { + var $left = $expr.left, + $right = $expr.right, + parenthesize = Precedence.Assignment < settings.precedence, + allowIn = settings.allowIn || parenthesize; + + if (parenthesize) + _.js += '('; + + ExprGen[$left.type]($left, Preset.e17(allowIn)); + _.js += _.optSpace + $expr.operator + _.optSpace; + ExprGen[$right.type]($right, Preset.e18(allowIn)); + + if (parenthesize) + _.js += ')'; + }, + + AssignmentPattern: function generateAssignmentPattern ($node) { + var $fakeAssign = { + left: $node.left, + right: $node.right, + operator: '=' + }; + + ExprGen.AssignmentExpression($fakeAssign, Preset.e4); + }, + + ArrowFunctionExpression: function generateArrowFunctionExpression ($expr, settings) { + var parenthesize = Precedence.ArrowFunction < settings.precedence; + + if (parenthesize) + _.js += '('; + + if ($expr.async) + _.js += 'async '; + + generateFunctionBody($expr); + + if (parenthesize) + _.js += ')'; + }, + + AwaitExpression: function generateAwaitExpression ($expr, settings) { + var parenthesize = Precedence.Await < settings.precedence; + + if (parenthesize) + _.js += '('; + + _.js += $expr.all ? 'await* ' : 'await '; + + ExprGen[$expr.argument.type]($expr.argument, Preset.e20); + + if (parenthesize) + _.js += ')'; + }, + + ConditionalExpression: function generateConditionalExpression ($expr, settings) { + var $test = $expr.test, + $conseq = $expr.consequent, + $alt = $expr.alternate, + parenthesize = Precedence.Conditional < settings.precedence, + allowIn = settings.allowIn || parenthesize, + testGenSettings = Preset.e2(allowIn), + branchGenSettings = Preset.e1(allowIn); + + if (parenthesize) + _.js += '('; + + ExprGen[$test.type]($test, testGenSettings); + _.js += _.optSpace + '?' + _.optSpace; + ExprGen[$conseq.type]($conseq, branchGenSettings); + _.js += _.optSpace + ':' + _.optSpace; + ExprGen[$alt.type]($alt, branchGenSettings); + + if (parenthesize) + _.js += ')'; + }, + + LogicalExpression: generateLogicalOrBinaryExpression, + + BinaryExpression: generateLogicalOrBinaryExpression, + + CallExpression: function generateCallExpression ($expr, settings) { + var $callee = $expr.callee, + $args = $expr['arguments'], + argCount = $args.length, + lastArgIdx = argCount - 1, + parenthesize = !settings.allowCall || Precedence.Call < settings.precedence; + + if (parenthesize) + _.js += '('; + + ExprGen[$callee.type]($callee, Preset.e3); + + if ($expr.optional) + _.js += '?.'; + + _.js += '('; + + for (var i = 0; i < argCount; ++i) { + var $arg = $args[i]; + + ExprGen[$arg.type]($arg, Preset.e4); + + if (i !== lastArgIdx) + _.js += ',' + _.optSpace; + } + + _.js += ')'; + + if (parenthesize) + _.js += ')'; + }, + + NewExpression: function generateNewExpression ($expr, settings) { + var $args = $expr['arguments'], + parenthesize = Precedence.New < settings.precedence, + argCount = $args.length, + lastArgIdx = argCount - 1, + withCall = !settings.allowUnparenthesizedNew || parentheses || argCount > 0, + calleeJs = exprToJs($expr.callee, Preset.e6(!withCall)); + + if (parenthesize) + _.js += '('; + + _.js += join('new', calleeJs); + + if (withCall) { + _.js += '('; + + for (var i = 0; i < argCount; ++i) { + var $arg = $args[i]; + + ExprGen[$arg.type]($arg, Preset.e4); + + if (i !== lastArgIdx) + _.js += ',' + _.optSpace; + } + + _.js += ')'; + } + + if (parenthesize) + _.js += ')'; + }, + + MemberExpression: function generateMemberExpression ($expr, settings) { + var $obj = $expr.object, + $prop = $expr.property, + parenthesize = Precedence.Member < settings.precedence, + isNumObj = !$expr.computed && $obj.type === Syntax.Literal && typeof $obj.value === 'number'; + + if (parenthesize) + _.js += '('; + + if (isNumObj) { + + //NOTE: When the following conditions are all true: + // 1. No floating point + // 2. Don't have exponents + // 3. The last character is a decimal digit + // 4. Not hexadecimal OR octal number literal + // then we should add a floating point. + + var numJs = exprToJs($obj, Preset.e11(settings.allowCall)), + withPoint = LAST_DECIMAL_DIGIT_REGEXP.test(numJs) && !FLOATING_OR_OCTAL_REGEXP.test(numJs); + + _.js += withPoint ? (numJs + '.') : numJs; + } + + else + ExprGen[$obj.type]($obj, Preset.e11(settings.allowCall)); + + if ($expr.computed) { + if ($expr.optional) + _.js += '?.'; + + _.js += '['; + ExprGen[$prop.type]($prop, Preset.e15(settings.allowCall)); + _.js += ']'; + } + + else + _.js += ($expr.optional ? '?.' : '.') + $prop.name; + + if (parenthesize) + _.js += ')'; + }, + + UnaryExpression: function generateUnaryExpression ($expr, settings) { + var parenthesize = Precedence.Unary < settings.precedence, + op = $expr.operator, + argJs = exprToJs($expr.argument, Preset.e7); + + if (parenthesize) + _.js += '('; + + //NOTE: delete, void, typeof + // get `typeof []`, not `typeof[]` + if (_.optSpace === '' || op.length > 2) + _.js += join(op, argJs); + + else { + _.js += op; + + //NOTE: Prevent inserting spaces between operator and argument if it is unnecessary + // like, `!cond` + var leftCp = op.charCodeAt(op.length - 1), + rightCp = argJs.charCodeAt(0); + + // 0x2B = '+', 0x2D = '-' + if (leftCp === rightCp && (leftCp === 0x2B || leftCp === 0x2D) || + isIdentifierCh(leftCp) && isIdentifierCh(rightCp)) { + _.js += _.space; + } + + _.js += argJs; + } + + if (parenthesize) + _.js += ')'; + }, + + YieldExpression: function generateYieldExpression ($expr, settings) { + var $arg = $expr.argument, + js = $expr.delegate ? 'yield*' : 'yield', + parenthesize = Precedence.Yield < settings.precedence; + + if (parenthesize) + _.js += '('; + + if ($arg) { + var argJs = exprToJs($arg, Preset.e4); + + js = join(js, argJs); + } + + _.js += js; + + if (parenthesize) + _.js += ')'; + }, + + UpdateExpression: function generateUpdateExpression ($expr, settings) { + var $arg = $expr.argument, + $op = $expr.operator, + prefix = $expr.prefix, + precedence = prefix ? Precedence.Unary : Precedence.Postfix, + parenthesize = precedence < settings.precedence; + + if (parenthesize) + _.js += '('; + + if (prefix) { + _.js += $op; + ExprGen[$arg.type]($arg, Preset.e8); + + } + + else { + ExprGen[$arg.type]($arg, Preset.e8); + _.js += $op; + } + + if (parenthesize) + _.js += ')'; + }, + + FunctionExpression: function generateFunctionExpression ($expr) { + var isGenerator = !!$expr.generator; + + if ($expr.async) + _.js += 'async '; + + _.js += isGenerator ? 'function*' : 'function'; + + if ($expr.id) { + _.js += isGenerator ? _.optSpace : _.space; + _.js += $expr.id.name; + } + else + _.js += _.optSpace; + + generateFunctionBody($expr); + }, + + ExportBatchSpecifier: function generateExportBatchSpecifier () { + _.js += '*'; + }, + + ArrayPattern: generateArrayPatternOrExpression, + + ArrayExpression: generateArrayPatternOrExpression, + + ClassExpression: function generateClassExpression ($expr) { + var $id = $expr.id, + $super = $expr.superClass, + $body = $expr.body, + exprJs = 'class'; + + if ($id) { + var idJs = exprToJs($id, Preset.e9); + + exprJs = join(exprJs, idJs); + } + + if ($super) { + var superJs = exprToJs($super, Preset.e4); + + superJs = join('extends', superJs); + exprJs = join(exprJs, superJs); + } + + _.js += exprJs + _.optSpace; + StmtGen[$body.type]($body, Preset.s2); + }, + + MetaProperty: function generateMetaProperty ($expr, settings) { + var $meta = $expr.meta, + $property = $expr.property, + parenthesize = Precedence.Member < settings.precedence; + + if (parenthesize) + _.js += '('; + + _.js += (typeof $meta === "string" ? $meta : $meta.name) + + '.' + (typeof $property === "string" ? $property : $property.name); + + if (parenthesize) + _.js += ')'; + }, + + MethodDefinition: function generateMethodDefinition ($expr) { + var exprJs = $expr['static'] ? 'static' + _.optSpace : '', + keyJs = exprToJs($expr.key, Preset.e5); + + if ($expr.computed) + keyJs = '[' + keyJs + ']'; + + if ($expr.kind === 'get' || $expr.kind === 'set') { + keyJs = join($expr.kind, keyJs); + _.js += join(exprJs, keyJs); + } + + else { + if ($expr.value.generator) + _.js += exprJs + '*' + keyJs; + else if ($expr.value.async) + _.js += exprJs + 'async ' + keyJs; + else + _.js += join(exprJs, keyJs); + } + + generateFunctionBody($expr.value); + }, + + Property: function generateProperty ($expr) { + var $val = $expr.value, + $kind = $expr.kind, + keyJs = exprToJs($expr.key, Preset.e4); + + if ($expr.computed) + keyJs = '[' + keyJs + ']'; + + if ($kind === 'get' || $kind === 'set') { + _.js += $kind + _.space + keyJs; + generateFunctionBody($val); + } + + else { + if ($expr.shorthand) + _.js += keyJs; + + else if ($expr.method) { + if ($val.generator) + keyJs = '*' + keyJs; + else if ($val.async) + keyJs = 'async ' + keyJs; + + _.js += keyJs; + generateFunctionBody($val) + } + + else { + _.js += keyJs + ':' + _.optSpace; + ExprGen[$val.type]($val, Preset.e4); + } + } + }, + + ObjectExpression: function generateObjectExpression ($expr) { + var $props = $expr.properties, + propCount = $props.length; + + if (propCount) { + var lastPropIdx = propCount - 1, + prevIndent = shiftIndent(); + + _.js += '{'; + + for (var i = 0; i < propCount; i++) { + var $prop = $props[i], + propType = $prop.type || Syntax.Property; + + _.js += _.newline + _.indent; + ExprGen[propType]($prop, Preset.e5); + + if (i !== lastPropIdx) + _.js += ','; + } + + _.indent = prevIndent; + _.js += _.newline + _.indent + '}'; + } + + else + _.js += '{}'; + }, + + ObjectPattern: function generateObjectPattern ($expr) { + var $props = $expr.properties, + propCount = $props.length; + + if (propCount) { + var lastPropIdx = propCount - 1, + multiline = false; + + if (propCount === 1 && $props[0].value) + multiline = $props[0].value.type !== Syntax.Identifier; + + else { + for (var i = 0; i < propCount; i++) { + if (!$props[i].shorthand) { + multiline = true; + break; + } + } + } + + _.js += multiline ? ('{' + _.newline) : '{'; + + var prevIndent = shiftIndent(), + propSuffix = ',' + (multiline ? _.newline : _.optSpace); + + for (var i = 0; i < propCount; i++) { + var $prop = $props[i]; + + if (multiline) + _.js += _.indent; + + ExprGen[$prop.type]($prop, Preset.e5); + + if (i !== lastPropIdx) + _.js += propSuffix; + } + + _.indent = prevIndent; + _.js += multiline ? (_.newline + _.indent + '}') : '}'; + } + else + _.js += '{}'; + }, + + ThisExpression: function generateThisExpression () { + _.js += 'this'; + }, + + Identifier: function generateIdentifier ($expr, precedence, flag) { + _.js += $expr.name; + }, + + ImportExpression: function generateImportExpression ($expr, settings) { + var parenthesize = Precedence.Call < settings.precedence; + var $source = $expr.source; + + if (parenthesize) + _.js += '('; + + _.js += 'import('; + + ExprGen[$source.type]($source, Preset.e4); + + _.js += ')'; + + if (parenthesize) + _.js += ')'; + }, + + ImportSpecifier: function generateImportSpecifier ($expr) { + _.js += $expr.imported.name; + + if ($expr.local) + _.js += _.space + 'as' + _.space + $expr.local.name; + }, + + ExportSpecifier: function generateImportOrExportSpecifier ($expr) { + _.js += $expr.local.name; + + if ($expr.exported) + _.js += _.space + 'as' + _.space + $expr.exported.name; + }, + + ChainExpression: function generateChainExpression ($expr, settings) { + var parenthesize = Precedence.OptionalChaining < settings.precedence; + var $expression = $expr.expression; + + settings = settings || {}; + + var newSettings = { + precedence: Precedence.OptionalChaining, + allowIn: settings.allowIn , + allowCall: settings.allowCall, + + allowUnparenthesizedNew: settings.allowUnparenthesizedNew + } + + if (parenthesize) { + newSettings.allowCall = true; + _.js += '('; + } + + ExprGen[$expression.type]($expression, newSettings); + + if (parenthesize) + _.js += ')'; + }, + + Literal: function generateLiteral ($expr) { + if (extra.raw && $expr.raw !== void 0) + _.js += $expr.raw; + + else if ($expr.value === null) + _.js += 'null'; + + else { + var valueType = typeof $expr.value; + + if (valueType === 'string') + _.js += escapeString($expr.value); + + else if (valueType === 'number') + _.js += generateNumber($expr.value); + + else if (valueType === 'boolean') + _.js += $expr.value ? 'true' : 'false'; + + else + _.js += generateRegExp($expr.value); + } + }, + + GeneratorExpression: generateGeneratorOrComprehensionExpression, + + ComprehensionExpression: generateGeneratorOrComprehensionExpression, + + ComprehensionBlock: function generateComprehensionBlock ($expr) { + var $left = $expr.left, + leftJs = void 0, + rightJs = exprToJs($expr.right, Preset.e5); + + if ($left.type === Syntax.VariableDeclaration) + leftJs = $left.kind + _.space + stmtToJs($left.declarations[0], Preset.s6); + + else + leftJs = exprToJs($left, Preset.e10); + + leftJs = join(leftJs, $expr.of ? 'of' : 'in'); + + _.js += 'for' + _.optSpace + '(' + join(leftJs, rightJs) + ')'; + }, + + RestElement: function generateRestElement ($node) { + _.js += '...' + $node.argument.name; + }, + + SpreadElement: function generateSpreadElement ($expr) { + var $arg = $expr.argument; + + _.js += '...'; + ExprGen[$arg.type]($arg, Preset.e4); + }, + + TaggedTemplateExpression: function generateTaggedTemplateExpression ($expr, settings) { + var $tag = $expr.tag, + $quasi = $expr.quasi, + parenthesize = Precedence.TaggedTemplate < settings.precedence; + + if (parenthesize) + _.js += '('; + + ExprGen[$tag.type]($tag, Preset.e11(settings.allowCall)); + ExprGen[$quasi.type]($quasi, Preset.e12); + + if (parenthesize) + _.js += ')'; + }, + + TemplateElement: function generateTemplateElement ($expr) { + //NOTE: Don't use "cooked". Since tagged template can use raw template + // representation. So if we do so, it breaks the script semantics. + _.js += $expr.value.raw; + }, + + TemplateLiteral: function generateTemplateLiteral ($expr) { + var $quasis = $expr.quasis, + $childExprs = $expr.expressions, + quasiCount = $quasis.length, + lastQuasiIdx = quasiCount - 1; + + _.js += '`'; + + for (var i = 0; i < quasiCount; ++i) { + var $quasi = $quasis[i]; + + ExprGen[$quasi.type]($quasi, Preset.e13); + + if (i !== lastQuasiIdx) { + var $childExpr = $childExprs[i]; + + _.js += '${' + _.optSpace; + ExprGen[$childExpr.type]($childExpr, Preset.e5); + _.js += _.optSpace + '}'; + } + } + + _.js += '`'; + }, + + Super: function generateSuper () { + _.js += 'super'; + } + }; + + + //-------------------------------------------------===------------------------------------------------------ + // Statements + //-------------------------------------------------===------------------------------------------------------ + + + //Regular expressions + var EXPR_STMT_UNALLOWED_EXPR_REGEXP = /^{|^class(?:\s|{)|^(async )?function(?:\s|\*|\()/; + + + //Common statement generators + function generateTryStatementHandlers (stmtJs, $finalizer, handlers) { + var handlerCount = handlers.length, + lastHandlerIdx = handlerCount - 1; + + for (var i = 0; i < handlerCount; ++i) { + var handlerJs = stmtToJs(handlers[i], Preset.s7); + + stmtJs = join(stmtJs, handlerJs); + + if ($finalizer || i !== lastHandlerIdx) + stmtJs += adoptionSuffix(handlers[i].body); + } + + return stmtJs; + } + + function generateForStatementIterator ($op, $stmt, settings) { + var $body = $stmt.body, + $left = $stmt.left, + bodySemicolonOptional = !semicolons && settings.semicolonOptional, + prevIndent1 = shiftIndent(), + awaitStr = $stmt.await ? ' await' : '', + stmtJs = 'for' + awaitStr + _.optSpace + '('; + + if ($left.type === Syntax.VariableDeclaration) { + var prevIndent2 = shiftIndent(); + + stmtJs += $left.kind + _.space + stmtToJs($left.declarations[0], Preset.s6); + _.indent = prevIndent2; + } + + else + stmtJs += exprToJs($left, Preset.e10); + + stmtJs = join(stmtJs, $op); + + var rightJs = exprToJs($stmt.right, Preset.e4); + + stmtJs = join(stmtJs, rightJs) + ')'; + + _.indent = prevIndent1; + + _.js += stmtJs + adoptionPrefix($body); + StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional)); + } + + + //Statement generator dictionary + var StmtRawGen = { + BlockStatement: function generateBlockStatement ($stmt, settings) { + var $body = $stmt.body, + len = $body.length, + lastIdx = len - 1, + prevIndent = shiftIndent(); + + _.js += '{' + _.newline; + + for (var i = 0; i < len; i++) { + var $item = $body[i]; + + _.js += _.indent; + StmtGen[$item.type]($item, Preset.s1(settings.functionBody, i === lastIdx)); + _.js += _.newline; + } + + _.indent = prevIndent; + _.js += _.indent + '}'; + }, + + BreakStatement: function generateBreakStatement ($stmt, settings) { + if ($stmt.label) + _.js += 'break ' + $stmt.label.name; + + else + _.js += 'break'; + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + ContinueStatement: function generateContinueStatement ($stmt, settings) { + if ($stmt.label) + _.js += 'continue ' + $stmt.label.name; + + else + _.js += 'continue'; + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + ClassBody: function generateClassBody ($stmt) { + var $body = $stmt.body, + itemCount = $body.length, + lastItemIdx = itemCount - 1, + prevIndent = shiftIndent(); + + _.js += '{' + _.newline; + + for (var i = 0; i < itemCount; i++) { + var $item = $body[i], + itemType = $item.type || Syntax.Property; + + _.js += _.indent; + ExprGen[itemType]($item, Preset.e5); + + if (i !== lastItemIdx) + _.js += _.newline; + } + + _.indent = prevIndent; + _.js += _.newline + _.indent + '}'; + }, + + ClassDeclaration: function generateClassDeclaration ($stmt) { + var $body = $stmt.body, + $super = $stmt.superClass, + js = 'class ' + $stmt.id.name; + + if ($super) { + var superJs = exprToJs($super, Preset.e4); + + js += _.space + join('extends', superJs); + } + + _.js += js + _.optSpace; + StmtGen[$body.type]($body, Preset.s2); + }, + + DirectiveStatement: function generateDirectiveStatement ($stmt, settings) { + if (extra.raw && $stmt.raw) + _.js += $stmt.raw; + + else + _.js += escapeDirective($stmt.directive); + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + DoWhileStatement: function generateDoWhileStatement ($stmt, settings) { + var $body = $stmt.body, + $test = $stmt.test, + bodyJs = adoptionPrefix($body) + + stmtToJs($body, Preset.s7) + + adoptionSuffix($body); + + //NOTE: Because `do 42 while (cond)` is Syntax Error. We need semicolon. + var stmtJs = join('do', bodyJs); + + _.js += join(stmtJs, 'while' + _.optSpace + '('); + ExprGen[$test.type]($test, Preset.e5); + _.js += ')'; + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + CatchClause: function generateCatchClause ($stmt) { + var $param = $stmt.param, + $guard = $stmt.guard, + $body = $stmt.body, + prevIndent = shiftIndent(); + + _.js += 'catch' + _.optSpace; + + if ($param) { + _.js += '('; + ExprGen[$param.type]($param, Preset.e5); + } + + if ($guard) { + _.js += ' if '; + ExprGen[$guard.type]($guard, Preset.e5); + } + + _.indent = prevIndent; + if ($param) { + _.js += ')'; + } + + _.js += adoptionPrefix($body); + StmtGen[$body.type]($body, Preset.s7); + }, + + DebuggerStatement: function generateDebuggerStatement ($stmt, settings) { + _.js += 'debugger'; + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + EmptyStatement: function generateEmptyStatement () { + _.js += ';'; + }, + + ExportAllDeclaration: function ($stmt, settings) { + StmtRawGen.ExportDeclaration($stmt, settings, true); + }, + + ExportDeclaration: function generateExportDeclaration ($stmt, settings, exportAll) { + var $specs = $stmt.specifiers, + $decl = $stmt.declaration, + withSemicolon = semicolons || !settings.semicolonOptional; + + // export default AssignmentExpression[In] ; + if ($stmt['default']) { + var declJs = exprToJs($decl, Preset.e4); + + _.js += join('export default', declJs); + + if (withSemicolon) + _.js += ';'; + } + + // export * FromClause ; + // export ExportClause[NoReference] FromClause ; + // export ExportClause ; + else if ($specs || exportAll) { + var stmtJs = 'export'; + + if (exportAll) + stmtJs += _.optSpace + '*'; + + else if ($specs.length === 0) + stmtJs += _.optSpace + '{' + _.optSpace + '}'; + + else if ($specs[0].type === Syntax.ExportBatchSpecifier) { + var specJs = exprToJs($specs[0], Preset.e5); + + stmtJs = join(stmtJs, specJs); + } + + else { + var prevIndent = shiftIndent(), + specCount = $specs.length, + lastSpecIdx = specCount - 1; + + stmtJs += _.optSpace + '{'; + + for (var i = 0; i < specCount; ++i) { + stmtJs += _.newline + _.indent; + stmtJs += exprToJs($specs[i], Preset.e5); + + if (i !== lastSpecIdx) + stmtJs += ','; + } + + _.indent = prevIndent; + stmtJs += _.newline + _.indent + '}'; + } + + if ($stmt.source) { + _.js += join(stmtJs, 'from' + _.optSpace); + ExprGen.Literal($stmt.source); + } + + else + _.js += stmtJs; + + if (withSemicolon) + _.js += ';'; + } + + // export VariableStatement + // export Declaration[Default] + else if ($decl) { + var declJs = stmtToJs($decl, Preset.s4(!withSemicolon)); + + _.js += join('export', declJs); + } + }, + + ExportNamedDeclaration: function ($stmt, settings) { + StmtRawGen.ExportDeclaration($stmt, settings); + }, + + ExpressionStatement: function generateExpressionStatement ($stmt, settings) { + var exprJs = exprToJs($stmt.expression, Preset.e5), + parenthesize = EXPR_STMT_UNALLOWED_EXPR_REGEXP.test(exprJs) || + (directive && + settings.directiveContext && + $stmt.expression.type === Syntax.Literal && + typeof $stmt.expression.value === 'string'); + + //NOTE: '{', 'function', 'class' are not allowed in expression statement. + // Therefore, they should be parenthesized. + if (parenthesize) + _.js += '(' + exprJs + ')'; + + else + _.js += exprJs; + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + ImportDeclaration: function generateImportDeclaration ($stmt, settings) { + var $specs = $stmt.specifiers, + stmtJs = 'import', + specCount = $specs.length; + + //NOTE: If no ImportClause is present, + // this should be `import ModuleSpecifier` so skip `from` + // ModuleSpecifier is StringLiteral. + if (specCount) { + var hasBinding = !!$specs[0]['default'], + firstNamedIdx = hasBinding ? 1 : 0, + lastSpecIdx = specCount - 1; + + // ImportedBinding + if (hasBinding) + stmtJs = join(stmtJs, $specs[0].id.name); + + // NamedImports + if (firstNamedIdx < specCount) { + if (hasBinding) + stmtJs += ','; + + stmtJs += _.optSpace + '{'; + + // import { ... } from "..."; + if (firstNamedIdx === lastSpecIdx) + stmtJs += _.optSpace + exprToJs($specs[firstNamedIdx], Preset.e5) + _.optSpace; + + else { + var prevIndent = shiftIndent(); + + // import { + // ..., + // ..., + // } from "..."; + for (var i = firstNamedIdx; i < specCount; i++) { + stmtJs += _.newline + _.indent + exprToJs($specs[i], Preset.e5); + + if (i !== lastSpecIdx) + stmtJs += ','; + } + + _.indent = prevIndent; + stmtJs += _.newline + _.indent; + } + + stmtJs += '}' + _.optSpace; + } + + stmtJs = join(stmtJs, 'from') + } + + _.js += stmtJs + _.optSpace; + ExprGen.Literal($stmt.source); + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + VariableDeclarator: function generateVariableDeclarator ($stmt, settings) { + var $id = $stmt.id, + $init = $stmt.init, + genSettings = Preset.e1(settings.allowIn); + + if ($init) { + ExprGen[$id.type]($id, genSettings); + _.js += _.optSpace + '=' + _.optSpace; + ExprGen[$init.type]($init, genSettings); + } + + else { + if ($id.type === Syntax.Identifier) + _.js += $id.name; + + else + ExprGen[$id.type]($id, genSettings); + } + }, + + VariableDeclaration: function generateVariableDeclaration ($stmt, settings) { + var $decls = $stmt.declarations, + len = $decls.length, + prevIndent = len > 1 ? shiftIndent() : _.indent, + declGenSettings = Preset.s3(settings.allowIn); + + _.js += $stmt.kind; + + for (var i = 0; i < len; i++) { + var $decl = $decls[i]; + + _.js += i === 0 ? _.space : (',' + _.optSpace); + StmtGen[$decl.type]($decl, declGenSettings); + } + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + + _.indent = prevIndent; + }, + + ThrowStatement: function generateThrowStatement ($stmt, settings) { + var argJs = exprToJs($stmt.argument, Preset.e5); + + _.js += join('throw', argJs); + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + TryStatement: function generateTryStatement ($stmt) { + var $block = $stmt.block, + $finalizer = $stmt.finalizer, + stmtJs = 'try' + + adoptionPrefix($block) + + stmtToJs($block, Preset.s7) + + adoptionSuffix($block); + + var $handlers = $stmt.handlers || $stmt.guardedHandlers; + + if ($handlers) + stmtJs = generateTryStatementHandlers(stmtJs, $finalizer, $handlers); + + if ($stmt.handler) { + $handlers = isArray($stmt.handler) ? $stmt.handler : [$stmt.handler]; + stmtJs = generateTryStatementHandlers(stmtJs, $finalizer, $handlers); + } + + if ($finalizer) { + stmtJs = join(stmtJs, 'finally' + adoptionPrefix($finalizer)); + stmtJs += stmtToJs($finalizer, Preset.s7); + } + + _.js += stmtJs; + }, + + SwitchStatement: function generateSwitchStatement ($stmt) { + var $cases = $stmt.cases, + $discr = $stmt.discriminant, + prevIndent = shiftIndent(); + + _.js += 'switch' + _.optSpace + '('; + ExprGen[$discr.type]($discr, Preset.e5); + _.js += ')' + _.optSpace + '{' + _.newline; + _.indent = prevIndent; + + if ($cases) { + var caseCount = $cases.length, + lastCaseIdx = caseCount - 1; + + for (var i = 0; i < caseCount; i++) { + var $case = $cases[i]; + + _.js += _.indent; + StmtGen[$case.type]($case, Preset.s4(i === lastCaseIdx)); + _.js += _.newline; + } + } + + _.js += _.indent + '}'; + }, + + SwitchCase: function generateSwitchCase ($stmt, settings) { + var $conseqs = $stmt.consequent, + $firstConseq = $conseqs[0], + $test = $stmt.test, + i = 0, + conseqSemicolonOptional = !semicolons && settings.semicolonOptional, + conseqCount = $conseqs.length, + lastConseqIdx = conseqCount - 1, + prevIndent = shiftIndent(); + + if ($test) { + var testJs = exprToJs($test, Preset.e5); + + _.js += join('case', testJs) + ':'; + } + + else + _.js += 'default:'; + + + if (conseqCount && $firstConseq.type === Syntax.BlockStatement) { + i++; + _.js += adoptionPrefix($firstConseq); + StmtGen[$firstConseq.type]($firstConseq, Preset.s7); + } + + for (; i < conseqCount; i++) { + var $conseq = $conseqs[i], + semicolonOptional = i === lastConseqIdx && conseqSemicolonOptional; + + _.js += _.newline + _.indent; + StmtGen[$conseq.type]($conseq, Preset.s4(semicolonOptional)); + } + + _.indent = prevIndent; + }, + + IfStatement: function generateIfStatement ($stmt, settings) { + var $conseq = $stmt.consequent, + $test = $stmt.test, + prevIndent = shiftIndent(), + semicolonOptional = !semicolons && settings.semicolonOptional; + + _.js += 'if' + _.optSpace + '('; + ExprGen[$test.type]($test, Preset.e5); + _.js += ')'; + _.indent = prevIndent; + _.js += adoptionPrefix($conseq); + + if ($stmt.alternate) { + var conseq = stmtToJs($conseq, Preset.s7) + adoptionSuffix($conseq), + alt = stmtToJs($stmt.alternate, Preset.s4(semicolonOptional)); + + if ($stmt.alternate.type === Syntax.IfStatement) + alt = 'else ' + alt; + + else + alt = join('else', adoptionPrefix($stmt.alternate) + alt); + + _.js += join(conseq, alt); + } + + else + StmtGen[$conseq.type]($conseq, Preset.s4(semicolonOptional)); + }, + + ForStatement: function generateForStatement ($stmt, settings) { + var $init = $stmt.init, + $test = $stmt.test, + $body = $stmt.body, + $update = $stmt.update, + bodySemicolonOptional = !semicolons && settings.semicolonOptional, + prevIndent = shiftIndent(); + + _.js += 'for' + _.optSpace + '('; + + if ($init) { + if ($init.type === Syntax.VariableDeclaration) + StmtGen[$init.type]($init, Preset.s6); + + else { + ExprGen[$init.type]($init, Preset.e14); + _.js += ';'; + } + } + + else + _.js += ';'; + + if ($test) { + _.js += _.optSpace; + ExprGen[$test.type]($test, Preset.e5); + } + + _.js += ';'; + + if ($update) { + _.js += _.optSpace; + ExprGen[$update.type]($update, Preset.e5); + } + + _.js += ')'; + _.indent = prevIndent; + _.js += adoptionPrefix($body); + StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional)); + }, + + ForInStatement: function generateForInStatement ($stmt, settings) { + generateForStatementIterator('in', $stmt, settings); + }, + + ForOfStatement: function generateForOfStatement ($stmt, settings) { + generateForStatementIterator('of', $stmt, settings); + }, + + LabeledStatement: function generateLabeledStatement ($stmt, settings) { + var $body = $stmt.body, + bodySemicolonOptional = !semicolons && settings.semicolonOptional, + prevIndent = _.indent; + + _.js += $stmt.label.name + ':' + adoptionPrefix($body); + + if ($body.type !== Syntax.BlockStatement) + prevIndent = shiftIndent(); + + StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional)); + _.indent = prevIndent; + }, + + ModuleDeclaration: function generateModuleDeclaration ($stmt, settings) { + _.js += 'module' + _.space + $stmt.id.name + _.space + 'from' + _.optSpace; + + ExprGen.Literal($stmt.source); + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + Program: function generateProgram ($stmt) { + var $body = $stmt.body, + len = $body.length, + lastIdx = len - 1; + + if (safeConcatenation && len > 0) + _.js += '\n'; + + for (var i = 0; i < len; i++) { + var $item = $body[i]; + + _.js += _.indent; + StmtGen[$item.type]($item, Preset.s5(!safeConcatenation && i === lastIdx)); + + if (i !== lastIdx) + _.js += _.newline; + } + }, + + FunctionDeclaration: function generateFunctionDeclaration ($stmt) { + var isGenerator = !!$stmt.generator; + + if ($stmt.async) + _.js += 'async '; + + _.js += isGenerator ? ('function*' + _.optSpace) : ('function' + _.space ); + _.js += $stmt.id.name; + generateFunctionBody($stmt); + }, + + ReturnStatement: function generateReturnStatement ($stmt, settings) { + var $arg = $stmt.argument; + + if ($arg) { + var argJs = exprToJs($arg, Preset.e5); + + _.js += join('return', argJs); + } + + else + _.js += 'return'; + + if (semicolons || !settings.semicolonOptional) + _.js += ';'; + }, + + WhileStatement: function generateWhileStatement ($stmt, settings) { + var $body = $stmt.body, + $test = $stmt.test, + bodySemicolonOptional = !semicolons && settings.semicolonOptional, + prevIndent = shiftIndent(); + + _.js += 'while' + _.optSpace + '('; + ExprGen[$test.type]($test, Preset.e5); + _.js += ')'; + _.indent = prevIndent; + + _.js += adoptionPrefix($body); + StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional)); + }, + + WithStatement: function generateWithStatement ($stmt, settings) { + var $body = $stmt.body, + $obj = $stmt.object, + bodySemicolonOptional = !semicolons && settings.semicolonOptional, + prevIndent = shiftIndent(); + + _.js += 'with' + _.optSpace + '('; + ExprGen[$obj.type]($obj, Preset.e5); + _.js += ')'; + _.indent = prevIndent; + _.js += adoptionPrefix($body); + StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional)); + } + }; + + function generateStatement ($stmt, option) { + StmtGen[$stmt.type]($stmt, option); + } + + //CodeGen + //----------------------------------------------------------------------------------- + function exprToJs ($expr, settings) { + var savedJs = _.js; + _.js = ''; + + // ExprGen[$expr.type]($expr, settings); + // Modified for Corrosion + if (typeof ExprGen[$expr.type] == 'function') ExprGen[$expr.type]($expr, settings); + + var src = _.js; + _.js = savedJs; + + return src; + } + + function stmtToJs ($stmt, settings) { + var savedJs = _.js; + _.js = ''; + + StmtGen[$stmt.type]($stmt, settings); + + var src = _.js; + _.js = savedJs; + + return src; + } + + function run ($node) { + _.js = ''; + + if (StmtGen[$node.type]) + StmtGen[$node.type]($node, Preset.s7); + + else + ExprGen[$node.type]($node, Preset.e19); + + return _.js; + } + + function wrapExprGen (gen) { + return function ($expr, settings) { + if (extra.verbatim && $expr.hasOwnProperty(extra.verbatim)) + generateVerbatim($expr, settings); + + else + gen($expr, settings); + } + } + + function createExprGenWithExtras () { + var gens = {}; + + for (var key in ExprRawGen) { + if (ExprRawGen.hasOwnProperty(key)) + gens[key] = wrapExprGen(ExprRawGen[key]); + } + + return gens; + } + + + //Strings + var _ = { + js: '', + newline: '\n', + optSpace: ' ', + space: ' ', + indentUnit: ' ', + indent: '' + }; + + + //Generators + var ExprGen = void 0, + StmtGen = StmtRawGen; + + + exports.generate = function ($node, options) { + var defaultOptions = getDefaultOptions(), result, pair; + + if (options != null) { + //NOTE: Obsolete options + // + // `options.indent` + // `options.base` + // + // Instead of them, we can use `option.format.indent`. + if (typeof options.indent === 'string') { + defaultOptions.format.indent.style = options.indent; + } + if (typeof options.base === 'number') { + defaultOptions.format.indent.base = options.base; + } + options = updateDeeply(defaultOptions, options); + _.indentUnit = options.format.indent.style; + if (typeof options.base === 'string') { + _.indent = options.base; + } + else { + _.indent = stringRepeat(_.indentUnit, options.format.indent.base); + } + } + else { + options = defaultOptions; + _.indentUnit = options.format.indent.style; + _.indent = stringRepeat(_.indentUnit, options.format.indent.base); + } + json = options.format.json; + renumber = options.format.renumber; + hexadecimal = json ? false : options.format.hexadecimal; + quotes = json ? 'double' : options.format.quotes; + escapeless = options.format.escapeless; + + _.newline = options.format.newline; + _.optSpace = options.format.space; + + if (options.format.compact) + _.newline = _.optSpace = _.indentUnit = _.indent = ''; + + _.space = _.optSpace ? _.optSpace : ' '; + parentheses = options.format.parentheses; + semicolons = options.format.semicolons; + safeConcatenation = options.format.safeConcatenation; + directive = options.directive; + parse = json ? null : options.parse; + extra = options; + + if (extra.verbatim) + ExprGen = createExprGenWithExtras(); + + else + ExprGen = ExprRawGen; + + return run($node); + }; \ No newline at end of file diff --git a/src/Corrosion/lib/html.js b/src/Corrosion/lib/html.js new file mode 100644 index 00000000..f610c67a --- /dev/null +++ b/src/Corrosion/lib/html.js @@ -0,0 +1,226 @@ +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; \ No newline at end of file diff --git a/src/Corrosion/lib/js.js b/src/Corrosion/lib/js.js new file mode 100644 index 00000000..4e6992ba --- /dev/null +++ b/src/Corrosion/lib/js.js @@ -0,0 +1,185 @@ +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; \ No newline at end of file diff --git a/src/Corrosion/lib/rewrite.js b/src/Corrosion/lib/rewrite.js new file mode 100644 index 00000000..0a1fc05d --- /dev/null +++ b/src/Corrosion/lib/rewrite.js @@ -0,0 +1,27 @@ +const URLWrapper = require('./url'); +const CookieRewriter = require('./cookie'); +const CSSRewriter = require('./css'); +const HTMLRewriter = require('./html'); +const JSRewriter = require('./js'); +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.codec = this.url.codec; + this.cookies = new CookieRewriter(this); + this.css = new CSSRewriter(this); + this.js = new JSRewriter(this); + this.html = new HTMLRewriter(this); + }; +}; + +module.exports = Rewrite; \ No newline at end of file diff --git a/src/Corrosion/lib/server/bundle.js b/src/Corrosion/lib/server/bundle.js new file mode 100644 index 00000000..fa3700b2 --- /dev/null +++ b/src/Corrosion/lib/server/bundle.js @@ -0,0 +1,30109 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/***/ ((module) => { + +function createDocumentRewriter(ctx) { + 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); + }, + }); + }; + 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) : '' ]); + }, + }), + }); + }; + 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); + }, + }); + }; + 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.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); + }, + }); + }; + [ + 'innerHTML', + 'outerHTML', + ].forEach(html => { + const descriptor = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, html); + Object.defineProperty(ctx.window.Element.prototype, html, { + get: new Proxy(descriptor.get, { + apply: (target, that, args) => { + const body = Reflect.apply(target, that, args); + if (!body || html == 'innerHTML' && that.tagName == 'SCRIPT') return body; + return ctx.html.source(body, ctx.meta); + }, + }), + set: new Proxy(descriptor.set, { + apply(target, that, [ val ]) { + return Reflect.apply(target, that, [ val ? ctx.html.process(val.toString(), ctx.meta) : val, ]); + }, + }), + }); + }); + [ + ['background', 'background'], + ['backgroundImage', 'background-image'], + ['listStyleImage', 'list-style-image'], + ].forEach(([key, cssProperty]) => { + Object.defineProperty(ctx.window.CSS2Properties ? ctx.window.CSS2Properties.prototype : ctx.window.CSSStyleDeclaration.prototype, key, { + get() { + return this.getPropertyValue(cssProperty); + }, + set(val) { + return this.setProperty(cssProperty, val); + }, + }); + }); + Object.defineProperty(ctx.window.Document.prototype, 'domain', { + get: new Proxy(domain.get, { + apply: () => spoofDomain, + }), + set: new Proxy(domain.set, { + apply: (target, that, [ val ]) => { + if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) return Reflect.apply(target, that, ['']); + return spoofDomain = val; + }, + }), + }); + if (ctx.config.title) Object.defineProperty(ctx.window.Document.prototype, 'title', { + get: new Proxy(title.get, { + apply: () => spoofTitle, + }), + set: new Proxy(title.set, { + apply: (target, that, [ val ]) => spoofTitle = val, + }), + }); + Object.defineProperty(ctx.window.Navigator.prototype, 'cookieEnabled', { + get: new Proxy(cookieEnabled.get, { + apply: () => ctx.config.cookie, + }), + }); + Object.defineProperty(ctx.window.Node.prototype, 'baseURI', { + get: new Proxy(baseURI.get, { + apply: (target, that, args) => { + const val = Reflect.apply(target, that, args); + return val.startsWith(ctx.meta.origin) ? ctx.url.unwrap(val, ctx.meta) : val; + }, + }), + }); + [ + { + elements: [ HTMLScriptElement, HTMLMediaElement, HTMLImageElement, HTMLAudioElement, HTMLVideoElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement], + properties: ['src'], + handler: 'url', + }, + { + elements: [ HTMLFormElement ], + properties: ['action'], + handler: 'url', + }, + { + elements: [ HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement ], + properties: ['href'], + handler: 'url', + }, + { + elements: [ HTMLImageElement, HTMLSourceElement ], + properties: ['srcset'], + handler: 'srcset', + }, + { + elements: [ HTMLScriptElement ], + properties: ['integrity'], + handler: 'delete', + }, + { + elements: [ HTMLIFrameElement ], + properties: ['contentWindow'], + handler: 'window', + }, + ].forEach(entry => { + entry.elements.forEach(element => { + if (!element) return; + entry.properties.forEach(property => { + if (!element.prototype.hasOwnProperty(property)) return; + const descriptor = Object.getOwnPropertyDescriptor(element.prototype, property); + Object.defineProperty(element.prototype, property, { + get: descriptor.get ? new Proxy(descriptor.get, { + apply: (target, that, args) => { + let val = Reflect.apply(target, that, args); + let flags = []; + switch(entry.handler) { + case 'url': + //if (that.tagName == 'SCRIPT' && property == 'src') flags.push('js'); + val = ctx.url.unwrap(val, ctx.meta); + break; + case 'srcset': + val = ctx.html.unsrcset(val, ctx.meta); + break; + case 'delete': + val = that.getAttribute(`corrosion-${property}`); + break; + case 'window': + try { + if (!val.$corrosion) { + val.$corrosion = new ctx.constructor({ ...ctx.config, window: val, }); + val.$corrosion.init(); + val.$corrosion.meta = ctx.meta; + }; + } catch(e) {}; + }; + return val; + }, + }) : undefined, + set: descriptor.set ? new Proxy(descriptor.set, { + apply(target, that, [ val ]) { + let newVal = val; + switch(entry.handler) { + case 'url': + newVal = ctx.url.wrap(newVal, ctx.meta); + break; + case 'srcset': + newVal = ctx.html.srcset(newVal, ctx.meta); + break; + case 'delete': + that.setAttribute(property, newVal); + return newVal; + }; + return Reflect.apply(target, that, [ newVal ]); + }, + }) : undefined, + }); + }); + }); + }); + }; +}; +module.exports = createDocumentRewriter; + +/***/ }), +/* 2 */ +/***/ ((module) => { + +function createHistoryRewriter(ctx) { + return function rewriteHistory() { + 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; + +/***/ }), +/* 3 */ +/***/ ((module) => { + +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); + }, + }); + 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); + }, + }); + }; + }; +}; + +module.exports = createHttpRewriter; + +/***/ }), +/* 4 */ +/***/ ((module) => { + +class Location { + get [Symbol.toPrimitive]() { + return () => this.href; + }; +}; + +function createLocation(ctx, url) { + const _location = new Location(); + const _url = new URL(url); + [ + 'hash', + 'host', + 'hostname', + 'href', + 'pathname', + 'port', + 'protocol', + 'search', + 'origin', + ].forEach(property => { + Object.defineProperty(_location, property, { + 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); + }; + _url[property] = val; + return ctx.window.location.href = ctx.url.wrap(_url); + }, + }); + }); + if (!ctx.serviceWorker) [ + 'assign', + 'replace', + 'reload', + ].forEach(method => { + _location[method] = new Proxy(ctx.window.location[method], { + apply(target, that, args) { + if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); + return Reflect.apply(target.bind(ctx.window.location), that, args); + }, + }); + }); + _location.toString = new Proxy(_url.toString, { + apply(target, that, args) { + return Reflect.apply(target.bind(_url), that, args); + }, + }); + return _location; +}; + +createLocation.Location = Location; +module.exports = createLocation; + +/***/ }), +/* 5 */ +/***/ ((module) => { + +function createWorkerRewriter(ctx = {}) { + return function rewriteWorker() { + if (ctx.window.Worker) { + ctx.window.Worker = new Proxy(ctx.window.Worker, { + construct: (target, args) => { + if (args[0]) { + if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) { + const xhr = new ctx.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); + }, + }); + }; + }; +}; + +module.exports = createWorkerRewriter; + +/***/ }), +/* 6 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +const URLWrapper = __webpack_require__(7); +const CookieRewriter = __webpack_require__(9); +const CSSRewriter = __webpack_require__(11); +const HTMLRewriter = __webpack_require__(134); +const JSRewriter = __webpack_require__(159); +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.codec = this.url.codec; + this.cookies = new CookieRewriter(this); + this.css = new CSSRewriter(this); + this.js = new JSRewriter(this); + this.html = new HTMLRewriter(this); + }; +}; + +module.exports = Rewrite; + +/***/ }), +/* 7 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +const codec = __webpack_require__(8); +const defaultConfig = { + prefix: '/service/', + codec: 'plain' +}; + +class URLWrapper { + constructor(config = defaultConfig) { + this.prefix = config.prefix || defaultConfig.prefix; + this.codec = codec[config.codec || 'plain'] || codec['plain']; + this.regex = /^(#|about:|data:|blob:|mailto:|javascript:)/; + }; + wrap(val, config = {}) { + if (!val || this.regex.test(val)) return val; + let flags = ''; + (config.flags || []).forEach(flag => flags += `${flag}_/`); + if (config.base) try { + if (!['http:', 'https:', 'ws:', 'wss:'].includes(new URL(val, config.base).protocol)) return val; + } catch(e) { + return val; + }; + return (config.origin || '') + this.prefix + flags + this.codec.encode(config.base ? new URL(val, config.base) : val) + '/'; + }; + unwrap(val, config = {}) { + if (!val || this.regex.test(val)) return val; + let processed = val.slice((config.origin || '').length + this.prefix.length); + const flags = ('/' + processed).match(/(?<=\/)(.*?)(?=_\/)/g) || []; + flags.forEach(flag => processed = processed.slice(`${flag}_/`.length)); + let [ url, leftovers ] = processed.split(/\/(.+)?/); + return config.flags ? { value: this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : ''), flags } : this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : ''); + }; +}; + +module.exports = URLWrapper; + +/***/ }), +/* 8 */ +/***/ ((__unused_webpack_module, exports) => { + +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; + }, +}; + +/***/ }), +/* 9 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +const { SetCookie, CookieStore } = __webpack_require__(10); + +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; + +/***/ }), +/* 10 */ +/***/ ((__unused_webpack_module, exports) => { + +// ------------------- +// This file is shared both by the server and client. +// Do not include any browser or node specific APIs +// ------------------- + +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; + +/***/ }), +/* 11 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +const csstree = __webpack_require__(12); + +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; + +/***/ }), +/* 12 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = __webpack_require__(13); + + +/***/ }), +/* 13 */ +/***/ ((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__(14).create( + merge( + __webpack_require__(57), + __webpack_require__(104), + __webpack_require__(132) + ) +); +module.exports.version = __webpack_require__(133).version; + + +/***/ }), +/* 14 */ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +var List = __webpack_require__(15); +var SyntaxError = __webpack_require__(16); +var TokenStream = __webpack_require__(18); +var Lexer = __webpack_require__(22); +var definitionSyntax = __webpack_require__(41); +var tokenize = __webpack_require__(27); +var createParser = __webpack_require__(42); +var createGenerator = __webpack_require__(45); +var createConvertor = __webpack_require__(53); +var createWalker = __webpack_require__(54); +var clone = __webpack_require__(55); +var names = __webpack_require__(25); +var mix = __webpack_require__(56); + +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)); +}; + + +/***/ }), +/* 15 */ +/***/ ((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; + + +/***/ }), +/* 16 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var createCustomError = __webpack_require__(17); +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; + + +/***/ }), +/* 17 */ +/***/ ((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; +}; + + +/***/ }), +/* 18 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var constants = __webpack_require__(19); +var TYPE = constants.TYPE; +var NAME = constants.NAME; + +var utils = __webpack_require__(20); +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; + + +/***/ }), +/* 19 */ +/***/ ((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 +}; + + +/***/ }), +/* 20 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var charCodeDef = __webpack_require__(21); +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 +}; + + +/***/ }), +/* 21 */ +/***/ ((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 +}; + + +/***/ }), +/* 22 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var SyntaxReferenceError = __webpack_require__(23).SyntaxReferenceError; +var SyntaxMatchError = __webpack_require__(23).SyntaxMatchError; +var names = __webpack_require__(25); +var generic = __webpack_require__(26); +var parse = __webpack_require__(31); +var generate = __webpack_require__(24); +var walk = __webpack_require__(34); +var prepareTokens = __webpack_require__(35); +var buildMatchGraph = __webpack_require__(36).buildMatchGraph; +var matchAsTree = __webpack_require__(37).matchAsTree; +var trace = __webpack_require__(38); +var search = __webpack_require__(39); +var getStructureFromConfig = __webpack_require__(40).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; + + +/***/ }), +/* 23 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +const createCustomError = __webpack_require__(17); +const generate = __webpack_require__(24); +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 +}; + + +/***/ }), +/* 24 */ +/***/ ((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); +}; + + +/***/ }), +/* 25 */ +/***/ ((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 +}; + + +/***/ }), +/* 26 */ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var tokenizer = __webpack_require__(27); +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__(29); +var urange = __webpack_require__(30); + +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 , , ,