sandstone-proxy
Version:
An experimental web proxy utilizing sandboxed iframes and no service worker.
225 lines (199 loc) • 6.19 kB
JavaScript
import * as network from "./network.mjs";
import * as polyfill from "./polyfill/index.mjs";
import * as intercept from "./intercept/index.mjs";
import * as parser from "./parser.mjs";
import * as loader from "./loader.mjs";
export const is_worker = typeof importScripts === "function";
export const ctx_vars = [];
export const unreadable_vars = ["localStorage", "sessionStorage", "importScripts"];
if (is_worker)
unreadable_vars.push("document");
const internal = {
location: null,
self: null,
globalThis: null,
eval: null,
history: null,
localStorage: null,
sessionStorage: null
};
function create_func_proxy(target, func) {
let proxy = new Proxy(func, {
apply: function(func_target, this_arg, args) {
return Reflect.apply(func_target, target, args);
}
});
proxy.apply = function(this_arg, args) {
if (this_arg) this_arg = target;
return Reflect.apply(func, this_arg, args);
}
proxy.call = function(this_arg, ...args) {
if (this_arg) this_arg = target;
return Reflect.apply(func, this_arg, args);
}
return proxy;
}
export function get_handler_keys(obj) {
let keys = [];
let own_keys = Reflect.ownKeys(Object.getPrototypeOf(obj));
for (let key of own_keys) {
if (key === "constructor") continue;
if (key.startsWith("__")) continue;
keys.push(key);
}
return keys;
}
export function create_obj_proxy(obj, ctx_vars, target) {
let proxies = new Map();
return new Proxy(target, {
get: (_, key) => {
if (typeof obj[key] !== "undefined")
return obj[key];
if (typeof target[key] === "function" && !target[key].prototype) {
if (!proxies.has(key))
proxies.set(key, create_func_proxy(target, target[key]))
return proxies.get(key)
}
return target[key];
},
set: (_, key, value) => {
if (ctx_vars.includes(key))
obj[key] = value;
else
target[key] = value;
return true;
}
});
}
export class CustomCTX {
constructor() {
ctx_vars.push(...get_handler_keys(this));
this.__proxy__ = create_obj_proxy(this, ctx_vars, globalThis);
}
set location(value) {internal.location.assign(value)}
get location() {return internal.location}
set self(value) {internal.self = value}
get self() {return internal.self}
set globalThis(value) {internal.globalThis = value}
get globalThis() {return internal.globalThis}
get window() {return this.__proxy__}
get origin() {return this.location.origin}
get document() {return is_worker ? undefined : intercept.document.__proxy__}
get parent() {
if (loader.is_iframe)
return globalThis.parent;
return this.__proxy__;
}
get top() {
if (loader.is_iframe)
return globalThis.parent;
return this.__proxy__;
}
fetch() {return polyfill.fetch(...arguments)}
get URL() {return polyfill.FakeURL}
get Worker() {return polyfill.FakeWorker}
get importScripts() {return is_worker ? polyfill.fakeImportScripts : undefined}
get XMLHttpRequest() {return polyfill.FakeXMLHttpRequest}
get history() {return internal.history}
get localStorage() {return internal.localStorage}
get sessionStorage() {return internal.sessionStorage}
get WebSocket() {return network.WebSocket}
eval(js) {
return run_script(String(js));
}
__get_this__(this_obj) {
if (this_obj === globalThis)
return ctx.__proxy__;
return this_obj;
}
__get_var__(var_value, var_name) {
let global_obj = globalThis[var_name];
if (var_value === global_obj)
return ctx.__proxy__[var_name];
else
return var_value;
}
}
export const ctx = new CustomCTX();
export function proxy_function(target, key, apply_callback) {
if (!target) return;
target[key] = new Proxy(target[key], {apply: apply_callback});
}
export function wrap_function(key, wrapper, target) {
wrapper[key] = new Proxy(target[key], {
apply: function(func_target, this_arg, arguments_list) {
return Reflect.apply(func_target, target, arguments_list);
}
});
}
export function wrap_obj(wrapper, target) {
wrapper.__target__ = target;
let wrapper_proto = Object.getPrototypeOf(wrapper);
let target_keys = Reflect.ownKeys(target);
let target_proto = Object.getPrototypeOf(target);
while (target_proto != null) {
target_keys.push(...Reflect.ownKeys(target_proto));
target_proto = Object.getPrototypeOf(target_proto);
}
let exclude = ["eval"];
for (let key of target_keys) {
if (wrapper_proto.hasOwnProperty(key)) continue;
if (key === "__proto__") continue;
if (exclude.includes(key)) continue;
try {
if (typeof target[key] === "function") {
wrap_function(key, wrapper, target);
continue;
}
try {
wrapper[key] = target[key];
}
catch {
Object.defineProperty(wrapper, key, {
configurable: true,
value: target[key],
writable: true
})
}
}
catch (e) {
if (e instanceof DOMException) continue;
if (e instanceof TypeError) continue;
}
}
}
export function update_ctx() {
internal.location = new polyfill.FakeLocation();
internal.self = ctx.__proxy__;
internal.globalThis = ctx.__proxy__;
internal.history = new polyfill.FakeHistory();
internal.localStorage = new polyfill.FakeStorage("local");
internal.sessionStorage = new polyfill.FakeStorage("session");
delete globalThis.caches;
globalThis.__ctx__ = ctx.__proxy__;
globalThis.__get_this__ = ctx.__get_this__;
globalThis.__get_var__ = ctx.__get_var__;
}
export function convert_url(url, base) {
let url_obj = new URL(url, base);
return url_obj.href;
}
export function run_script_safe(js) {
try {
run_script(js);
}
catch (e) {
console.error(e);
}
}
export function run_script(js) {
//indirect eval preserves global variables
let rewritten_js = parser.rewrite_js(js);
return eval?.(rewritten_js);
}
export function intercept_property(target, key, handler) {
let descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), key);
if (!descriptor) return;
Object.defineProperty(target, key, handler);
return descriptor;
}