From a963cda44e61712a02d4bdc41f5e8e68a2007848 Mon Sep 17 00:00:00 2001 From: p0x games <151543501+p0xx@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:57:20 -0500 Subject: [PATCH] Create uv.sw.js --- js/uv.sw.js | 789 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 789 insertions(+) create mode 100644 js/uv.sw.js diff --git a/js/uv.sw.js b/js/uv.sw.js new file mode 100644 index 0000000..37b1d55 --- /dev/null +++ b/js/uv.sw.js @@ -0,0 +1,789 @@ +importScripts('/uv/uv.bundle.js'); +importScripts('/uv/uv.config.js'); + +class UVServiceWorker extends EventEmitter { + constructor(config = __uv$config) { + super(); + if (!config.bare) config.bare = '/bare/'; + this.addresses = typeof config.bare === 'string' ? [ new URL(config.bare, location) ] : config.bare.map(str => new URL(str, location)); + this.headers = { + csp: [ + 'cross-origin-embedder-policy', + 'cross-origin-opener-policy', + 'cross-origin-resource-policy', + 'content-security-policy', + 'content-security-policy-report-only', + 'expect-ct', + 'feature-policy', + 'origin-isolation', + 'strict-transport-security', + 'upgrade-insecure-requests', + 'x-content-type-options', + 'x-download-options', + 'x-frame-options', + 'x-permitted-cross-domain-policies', + 'x-powered-by', + 'x-xss-protection', + ], + forward: [ + 'accept-encoding', + 'connection', + 'content-length', + ], + }; + this.method = { + empty: [ + 'GET', + 'HEAD' + ] + }; + this.statusCode = { + empty: [ + 204, + 304, + ], + }; + this.config = config; + this.browser = Ultraviolet.Bowser.getParser(self.navigator.userAgent).getBrowserName(); + + if (this.browser === 'Firefox') { + this.headers.forward.push('user-agent'); + this.headers.forward.push('content-type'); + }; + }; + async fetch({ request }) { + if (!request.url.startsWith(location.origin + (this.config.prefix || '/service/'))) { + return fetch(request); + }; + try { + + const ultraviolet = new Ultraviolet(this.config); + + if (typeof this.config.construct === 'function') { + this.config.construct(ultraviolet, 'service'); + }; + + const db = await ultraviolet.cookie.db(); + + ultraviolet.meta.origin = location.origin; + ultraviolet.meta.base = ultraviolet.meta.url = new URL(ultraviolet.sourceUrl(request.url)); + + const requestCtx = new RequestContext( + request, + this, + ultraviolet, + !this.method.empty.includes(request.method.toUpperCase()) ? await request.blob() : null + ); + + if (ultraviolet.meta.url.protocol === 'blob:') { + requestCtx.blob = true; + requestCtx.base = requestCtx.url = new URL(requestCtx.url.pathname); + }; + + if (request.referrer && request.referrer.startsWith(location.origin)) { + const referer = new URL(ultraviolet.sourceUrl(request.referrer)); + + if (requestCtx.headers.origin || ultraviolet.meta.url.origin !== referer.origin && request.mode === 'cors') { + requestCtx.headers.origin = referer.origin; + }; + + requestCtx.headers.referer = referer.href; + }; + + const cookies = await ultraviolet.cookie.getCookies(db) || []; + const cookieStr = ultraviolet.cookie.serialize(cookies, ultraviolet.meta, false); + + if (this.browser === 'Firefox' && !(request.destination === 'iframe' || request.destination === 'document')) { + requestCtx.forward.shift(); + }; + + if (cookieStr) requestCtx.headers.cookie = cookieStr; + requestCtx.headers.Host = requestCtx.url.host; + + + const reqEvent = new HookEvent(requestCtx, null, null); + this.emit('request', reqEvent); + + if (reqEvent.intercepted) return reqEvent.returnValue; + + const response = await fetch(requestCtx.send); + + if (response.status === 500) { + return Promise.reject(''); + }; + + const responseCtx = new ResponseContext(requestCtx, response, this); + const resEvent = new HookEvent(responseCtx, null, null); + + this.emit('beforemod', resEvent); + if (resEvent.intercepted) return resEvent.returnValue; + + for (const name of this.headers.csp) { + if (responseCtx.headers[name]) delete responseCtx.headers[name]; + }; + + if (responseCtx.headers.location) { + responseCtx.headers.location = ultraviolet.rewriteUrl(responseCtx.headers.location); + }; + + if (responseCtx.headers['set-cookie']) { + Promise.resolve(ultraviolet.cookie.setCookies(responseCtx.headers['set-cookie'], db, ultraviolet.meta)).then(() => { + self.clients.matchAll().then(function (clients){ + clients.forEach(function(client){ + client.postMessage({ + msg: 'updateCookies', + url: ultraviolet.meta.url.href, + }); + }); + }); + }); + delete responseCtx.headers['set-cookie']; + }; + + if (responseCtx.body) { + switch(request.destination) { + case 'script': + case 'worker': + responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`; + responseCtx.body += ultraviolet.js.rewrite( + await response.text() + ); + break; + case 'style': + responseCtx.body = ultraviolet.rewriteCSS( + await response.text() + ); + break; + case 'iframe': + case 'document': + if (isHtml(ultraviolet.meta.url, (responseCtx.headers['content-type'] || ''))) { + responseCtx.body = ultraviolet.rewriteHtml( + await response.text(), + { + document: true , + injectHead: ultraviolet.createHtmlInject( + this.config.handler, + this.config.bundle, + this.config.config, + ultraviolet.cookie.serialize(cookies, ultraviolet.meta, true), + request.referrer + ) + } + ); + }; + }; + }; + + if (requestCtx.headers.accept === 'text/event-stream') { + responseCtx.headers['content-type'] = 'text/event-stream'; + }; + + this.emit('response', resEvent); + if (resEvent.intercepted) return resEvent.returnValue; + + return new Response(responseCtx.body, { + headers: responseCtx.headers, + status: responseCtx.status, + statusText: responseCtx.statusText, + }); + + } catch(err) { + return new Response(err.toString(), { + status: 500, + }); + }; + }; + getBarerResponse(response) { + const headers = {}; + const raw = JSON.parse(response.headers.get('x-bare-headers')); + + for (const key in raw) { + headers[key.toLowerCase()] = raw[key]; + }; + + return { + headers, + status: +response.headers.get('x-bare-status'), + statusText: response.headers.get('x-bare-status-text'), + body: !this.statusCode.empty.includes(+response.headers.get('x-bare-status')) ? response.body : null, + }; + }; + get address() { + return this.addresses[Math.floor(Math.random() * this.addresses.length)]; + }; + static Ultraviolet = Ultraviolet; +}; + +self.UVServiceWorker = UVServiceWorker; + + +class ResponseContext { + constructor(request, response, worker) { + const { headers, status, statusText, body } = !request.blob ? worker.getBarerResponse(response) : { + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries([...response.headers.entries()]), + body: response.body, + }; + this.request = request; + this.raw = response; + this.ultraviolet = request.ultraviolet; + this.headers = headers; + this.status = status; + this.statusText = statusText; + this.body = body; + }; + get url() { + return this.request.url; + } + get base() { + return this.request.base; + }; + set base(val) { + this.request.base = val; + }; +}; + +class RequestContext { + constructor(request, worker, ultraviolet, body = null) { + this.ultraviolet = ultraviolet; + this.request = request; + this.headers = Object.fromEntries([...request.headers.entries()]); + this.method = request.method; + this.forward = [...worker.headers.forward]; + this.address = worker.address; + this.body = body || null; + this.redirect = request.redirect; + this.credentials = 'omit'; + this.mode = request.mode === 'cors' ? request.mode : 'same-origin'; + this.blob = false; + }; + get send() { + return new Request((!this.blob ? this.address.href + 'v1/' : 'blob:' + location.origin + this.url.pathname), { + method: this.method, + headers: { + 'x-bare-protocol': this.url.protocol, + 'x-bare-host': this.url.hostname, + 'x-bare-path': this.url.pathname + this.url.search, + 'x-bare-port': this.url.port || (this.url.protocol === 'https:' ? '443' : '80'), + 'x-bare-headers': JSON.stringify(this.headers), + 'x-bare-forward-headers': JSON.stringify(this.forward), + }, + redirect: this.redirect, + credentials: this.credentials, + mode: location.origin !== this.address.origin ? 'cors' : this.mode, + body: this.body + }); + }; + get url() { + return this.ultraviolet.meta.url; + }; + set url(val) { + this.ultraviolet.meta.url = val; + }; + get base() { + return this.ultraviolet.meta.base; + }; + set base(val) { + this.ultraviolet.meta.base = val; + }; +} + +function isHtml(url, contentType = '') { + return (Ultraviolet.mime.contentType((contentType || url.pathname)) || 'text/html').split(';')[0] === 'text/html'; +}; + +class HookEvent { + #intercepted; + #returnValue; + constructor(data = {}, target = null, that = null) { + this.#intercepted = false; + this.#returnValue = null; + this.data = data; + this.target = target; + this.that = that; + }; + get intercepted() { + return this.#intercepted; + }; + get returnValue() { + return this.#returnValue; + }; + respondWith(input) { + this.#returnValue = input; + this.#intercepted = true; + }; +}; + +var R = typeof Reflect === 'object' ? Reflect : null +var ReflectApply = R && typeof R.apply === 'function' + ? R.apply + : function ReflectApply(target, receiver, args) { + return Function.prototype.apply.call(target, receiver, args); + } + +var ReflectOwnKeys +if (R && typeof R.ownKeys === 'function') { + ReflectOwnKeys = R.ownKeys +} else if (Object.getOwnPropertySymbols) { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); + }; +} else { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); + }; +} + +function ProcessEmitWarning(warning) { + if (console && console.warn) console.warn(warning); +} + +var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { + return value !== value; +} + +function EventEmitter() { + EventEmitter.init.call(this); +} + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +function checkListener(listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } +} + +Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); + } + defaultMaxListeners = arg; + } +}); + +EventEmitter.init = function() { + + if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +}; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +EventEmitter.prototype.emit = function emit(type) { + var args = []; + for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); + var doError = (type === 'error'); + + var events = this._events; + if (events !== undefined) + doError = (doError && events.error === undefined); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + var er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + // At least give some kind of context to the user + var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); + err.context = er; + throw err; // Unhandled 'error' event + } + + var handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + ReflectApply(handler, this, args); + } else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + ReflectApply(listeners[i], this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' ' + String(type) + ' listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + ProcessEmitWarning(w); + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + checkListener(listener); + + events = this._events; + if (events === undefined) + return this; + + list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (events === undefined) + return this; + + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = Object.keys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (events === undefined) + return []; + + var evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events !== undefined) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function once(emitter, name) { + return new Promise(function (resolve, reject) { + function errorListener(err) { + emitter.removeListener(name, resolver); + reject(err); + } + + function resolver() { + if (typeof emitter.removeListener === 'function') { + emitter.removeListener('error', errorListener); + } + resolve([].slice.call(arguments)); + }; + + eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); + if (name !== 'error') { + addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); + } + }); +} + +function addErrorHandlerIfEventEmitter(emitter, handler, flags) { + if (typeof emitter.on === 'function') { + eventTargetAgnosticAddListener(emitter, 'error', handler, flags); + } +} + +function eventTargetAgnosticAddListener(emitter, name, listener, flags) { + if (typeof emitter.on === 'function') { + if (flags.once) { + emitter.once(name, listener); + } else { + emitter.on(name, listener); + } + } else if (typeof emitter.addEventListener === 'function') { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we do not listen for `error` events here. + emitter.addEventListener(name, function wrapListener(arg) { + // IE does not have builtin `{ once: true }` support so we + // have to do it manually. + if (flags.once) { + emitter.removeEventListener(name, wrapListener); + } + listener(arg); + }); + } else { + throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); + } +}