diff --git a/rewriter/js/src/cfg.rs b/rewriter/js/src/cfg.rs index abf08c7a..8f2d8970 100644 --- a/rewriter/js/src/cfg.rs +++ b/rewriter/js/src/cfg.rs @@ -17,7 +17,8 @@ pub struct Config { pub prefix: String, pub wrapfn: String, - pub wrapthisfn: String, + pub wrappropertybase: String, + pub wrappropertyfn: String, pub importfn: String, pub rewritefn: String, pub setrealmfn: String, diff --git a/rewriter/js/src/changes.rs b/rewriter/js/src/changes.rs index c0e72bd1..c765fa39 100644 --- a/rewriter/js/src/changes.rs +++ b/rewriter/js/src/changes.rs @@ -17,9 +17,6 @@ use crate::{ rewrite::Rewrite, }; -// const STRICTCHECKER: &str = "(function(a){arguments[0]=false;return a})(true)"; -const STRICTCHECKER: &str = "(function(){return!this;})()"; - macro_rules! change { ($span:expr, $($ty:tt)*) => { $crate::changes::JsChange::new($span, $crate::changes::JsChangeType::$($ty)*) @@ -30,21 +27,34 @@ pub(crate) use change; #[derive(Debug, PartialEq, Eq)] pub enum JsChangeType<'alloc: 'data, 'data> { /// insert `${cfg.wrapfn}(` - WrapFnLeft { wrap: bool }, - /// insert `,strictchecker)` - WrapFnRight { wrap: bool }, + WrapFnLeft { + enclose: bool, + }, + /// insert `)` + WrapFnRight { + enclose: bool, + }, + + WrapPropertyLeft, + WrapPropertyRight, + RewriteProperty { + ident: Atom<'data>, + }, + /// insert `${cfg.setrealmfn}({}).` SetRealmFn, - /// insert `${cfg.wrapthis}(` - WrapThisFn, /// insert `$scramerr(ident);` - ScramErrFn { ident: Atom<'data> }, + ScramErrFn { + ident: Atom<'data>, + }, /// insert `$scramitize(` ScramitizeFn, /// insert `eval(${cfg.rewritefn}(` EvalRewriteFn, /// insert `: ${cfg.wrapfn}(ident)` - ShorthandObj { ident: Atom<'data> }, + ShorthandObj { + ident: Atom<'data>, + }, /// insert scramtag SourceTag, @@ -59,10 +69,15 @@ pub enum JsChangeType<'alloc: 'data, 'data> { }, /// insert `)` - ClosingParen { semi: bool, replace: bool }, + ClosingParen { + semi: bool, + replace: bool, + }, /// replace span with text - Replace { text: &'alloc str }, + Replace { + text: &'alloc str, + }, /// replace span with "" Delete, } @@ -91,24 +106,28 @@ impl<'alloc: 'data, 'data> Transform<'data> for JsChange<'alloc, 'data> { (cfg, flags): &Self::ToLowLevelData, offset: i32, ) -> TransformLL<'data> { + dbg!(&&self); use JsChangeType as Ty; use TransformLL as LL; match self.ty { - Ty::WrapFnLeft { wrap } => LL::insert(if wrap { + Ty::WrapFnLeft { enclose } => LL::insert(if enclose { transforms!["(", &cfg.wrapfn, "("] } else { transforms![&cfg.wrapfn, "("] }), - Ty::WrapFnRight { wrap } => LL::insert(if wrap { - transforms![",", STRICTCHECKER, "))"] + Ty::WrapFnRight { enclose } => LL::insert(if enclose { + transforms!["))"] } else { - transforms![",", STRICTCHECKER, ")"] + transforms![")"] }), + Ty::WrapPropertyLeft => LL::insert(transforms![&cfg.wrappropertyfn, "(("]), + Ty::WrapPropertyRight => LL::insert(transforms!["))"]), + Ty::RewriteProperty { ident } => LL::replace(transforms![&cfg.wrappropertybase,ident]), + Ty::SetRealmFn => LL::insert(transforms![&cfg.setrealmfn, "({})."]), - Ty::WrapThisFn => LL::insert(transforms![&cfg.wrapthisfn, "("]), Ty::ScramErrFn { ident } => LL::insert(transforms!["$scramerr(", ident, ");"]), Ty::ScramitizeFn => LL::insert(transforms![" $scramitize("]), - Ty::EvalRewriteFn => LL::replace(transforms!["eval(", &cfg.rewritefn, "("]), + Ty::EvalRewriteFn => LL::insert(transforms![&cfg.rewritefn, "("]), Ty::ShorthandObj { ident } => { LL::insert(transforms![":", &cfg.wrapfn, "(", ident, ")"]) } @@ -164,6 +183,8 @@ impl Ord for JsChange<'_, '_> { Ordering::Equal => match (&self.ty, &other.ty) { (Ty::ScramErrFn { .. }, _) => Ordering::Less, (_, Ty::ScramErrFn { .. }) => Ordering::Greater, + (Ty::WrapFnRight { .. }, _) => Ordering::Less, + (_, Ty::WrapFnRight { .. }) => Ordering::Greater, _ => Ordering::Equal, }, x => x, diff --git a/rewriter/js/src/rewrite.rs b/rewriter/js/src/rewrite.rs index a188323a..b3fced88 100644 --- a/rewriter/js/src/rewrite.rs +++ b/rewriter/js/src/rewrite.rs @@ -15,19 +15,23 @@ pub(crate) use rewrite; #[derive(Debug, PartialEq, Eq)] pub(crate) enum RewriteType<'alloc: 'data, 'data> { - /// `(cfg.wrapfn(ident,strictchecker))` | `cfg.wrapfn(ident,strictchecker)` + /// `(cfg.wrapfn(ident))` | `cfg.wrapfn(ident)` WrapFn { - wrap: bool, + enclose: bool, }, /// `cfg.setrealmfn({}).ident` SetRealmFn, - /// `cfg.wrapthis(this)` - WrapThisFn, + /// `(cfg.importfn("cfg.base"))` ImportFn, /// `cfg.metafn("cfg.base")` MetaFn, + RewriteProperty { + ident: Atom<'data>, + }, + WrapProperty, + // dead code only if debug is disabled #[allow(dead_code)] /// `$scramerr(name)` @@ -60,6 +64,7 @@ pub(crate) enum RewriteType<'alloc: 'data, 'data> { Delete, } +#[derive(Debug)] pub(crate) struct Rewrite<'alloc, 'data> { span: Span, ty: RewriteType<'alloc, 'data>, @@ -77,6 +82,8 @@ impl<'alloc: 'data, 'data> Rewrite<'alloc, 'data> { impl<'alloc: 'data, 'data> RewriteType<'alloc, 'data> { fn into_inner(self, span: Span) -> SmallVec<[JsChange<'alloc, 'data>; 2]> { + + dbg!(&self); macro_rules! span { (start) => { Span::new(span.start, span.start) @@ -90,24 +97,24 @@ impl<'alloc: 'data, 'data> RewriteType<'alloc, 'data> { ($span1:ident $span2:ident end) => { Span::new($span1.end, $span2.end) }; + ($span1:ident $span2:ident between) => { + Span::new($span1.end, $span2.start) + }; } match self { - Self::WrapFn { wrap } => smallvec![ - change!(span!(start), WrapFnLeft { wrap }), - change!(span!(end), WrapFnRight { wrap }), + Self::WrapFn { enclose } => smallvec![ + change!(span!(start), WrapFnLeft { enclose }), + change!(span!(end), WrapFnRight { enclose }), ], + Self::RewriteProperty { ident } => smallvec![ + change!(span, RewriteProperty { ident }), + ], + Self::WrapProperty => smallvec![ + change!(span!(start), WrapPropertyLeft), + change!(span!(end), WrapPropertyRight), + ], Self::SetRealmFn => smallvec![change!(span, SetRealmFn)], - Self::WrapThisFn => smallvec![ - change!(span!(start), WrapThisFn), - change!( - span!(end), - ClosingParen { - semi: false, - replace: false - } - ), - ], Self::ImportFn => smallvec![change!(span, ImportFn)], Self::MetaFn => smallvec![change!(span, MetaFn)], Self::ScramErr { ident } => smallvec![change!(span!(end), ScramErrFn { ident })], @@ -122,12 +129,12 @@ impl<'alloc: 'data, 'data> RewriteType<'alloc, 'data> { ) ], Self::Eval { inner } => smallvec![ - change!(span!(span inner start), EvalRewriteFn), + change!(Span::new(inner.start, inner.start), EvalRewriteFn), change!( - span!(inner span end), + Span::new(inner.end, inner.end), ClosingParen { semi: false, - replace: true + replace: false, } ) ], diff --git a/rewriter/js/src/visitor.rs b/rewriter/js/src/visitor.rs index ab930a0a..ef005b2f 100644 --- a/rewriter/js/src/visitor.rs +++ b/rewriter/js/src/visitor.rs @@ -3,13 +3,9 @@ use std::error::Error; use oxc::{ allocator::{Allocator, StringBuilder}, ast::ast::{ - AssignmentExpression, AssignmentTarget, CallExpression, DebuggerStatement, - ExportAllDeclaration, ExportNamedDeclaration, Expression, FunctionBody, - IdentifierReference, ImportDeclaration, ImportExpression, MemberExpression, MetaProperty, - NewExpression, ObjectExpression, ObjectPropertyKind, ReturnStatement, StringLiteral, - ThisExpression, UnaryExpression, UnaryOperator, UpdateExpression, + AssignmentExpression, AssignmentTarget, CallExpression, ComputedMemberExpression, DebuggerStatement, ExportAllDeclaration, ExportNamedDeclaration, Expression, FunctionBody, IdentifierReference, ImportDeclaration, ImportExpression, MemberExpression, MetaProperty, NewExpression, ObjectExpression, ObjectPropertyKind, ReturnStatement, StringLiteral, ThisExpression, UnaryExpression, UnaryOperator, UpdateExpression }, - ast_visit::{Visit, walk}, + ast_visit::{walk, Visit}, span::{Atom, GetSpan, Span}, }; @@ -22,18 +18,7 @@ use crate::{ // js MUST not be able to get a reference to any of these because sbx // // maybe move this out of this lib? -const UNSAFE_GLOBALS: &[&str] = &[ - "window", - "self", - "globalThis", - "this", - "parent", - "top", - "location", - "document", - "eval", - "frames", -]; +const UNSAFE_GLOBALS: &[&str] = &["parent", "top", "location", "eval"]; pub struct Visitor<'alloc, 'data, E> where @@ -58,7 +43,7 @@ where builder.push_str("__URL_REWRITER_ALREADY_ERRORED__"); } else if let Err(err) = self.rewriter - .rewrite(self.config, &self.flags, &url.value, &mut builder,module) + .rewrite(self.config, &self.flags, &url.value, &mut builder, module) { self.error.replace(err); builder.push_str("__URL_REWRITER_ERROR__"); @@ -71,21 +56,46 @@ where fn rewrite_ident(&mut self, name: &Atom, span: Span) { if UNSAFE_GLOBALS.contains(&name.as_str()) { - self.jschanges.add(rewrite!(span, WrapFn { wrap: true })); + self.jschanges.add(rewrite!(span, WrapFn { enclose: true })); } } - fn walk_member_expression(&mut self, it: &Expression) -> bool { - match it { - Expression::Identifier(s) => { - self.rewrite_ident(&s.name, s.span); - true - } - Expression::StaticMemberExpression(s) => self.walk_member_expression(&s.object), - Expression::ComputedMemberExpression(s) => self.walk_member_expression(&s.object), - _ => false, - } - } + // fn walk_member_expression(&mut self, it: &Expression) -> bool { + // match it { + // Expression::Identifier(s) => false, + // Expression::StaticMemberExpression(s) => { + // if UNSAFE_GLOBALS.contains(&s.property.name.as_str()) { + // // self.jschanges.add(rewrite!(s.span, WrapAccess { + // // ident: s.property.name, + // // propspan: s.property.span, + // // } + // // )); + // } + // self.walk_member_expression(&s.object) + // } + // Expression::ComputedMemberExpression(s) => self.walk_member_expression(&s.object), + // _ => false, + // } + // } + fn walk_computed_member_expression(&mut self, it: &ComputedMemberExpression<'data>) { + match &it.expression{ + Expression::NullLiteral(_) | Expression::BigIntLiteral(_) | Expression::NumericLiteral(_) | Expression::RegExpLiteral(_) | Expression::BooleanLiteral(_) => {}, + Expression::StringLiteral(lit) =>{ + if UNSAFE_GLOBALS.contains(&lit.value.as_str()) { + self.jschanges.add(rewrite!( + it.expression.span(), + WrapProperty, + )); + } + }, + _=> { + self.jschanges.add(rewrite!( + it.expression.span(), + WrapProperty, + )); + } + } + } fn scramitize(&mut self, span: Span) { self.jschanges.add(rewrite!(span, Scramitize)); @@ -109,49 +119,58 @@ where // if UNSAFE_GLOBALS.contains(&it.name.as_str()) { self.jschanges - .add(rewrite!(it.span, WrapFn { wrap: false })); + .add(rewrite!(it.span, WrapFn { enclose: false })); } // } } fn visit_new_expression(&mut self, it: &NewExpression<'data>) { - self.walk_member_expression(&it.callee); + // ?? + // self.walk_member_expression(&it.callee); walk::walk_arguments(self, &it.arguments); } fn visit_member_expression(&mut self, it: &MemberExpression<'data>) { - // TODO - // you could break this with ["postMessage"] etc - // however this code only exists because of recaptcha whatever - // and it would slow down js execution a lot - if let MemberExpression::StaticMemberExpression(s) = it { - if s.property.name == "postMessage" { - self.jschanges.add(rewrite!(s.property.span, SetRealmFn)); + match &it { + MemberExpression::StaticMemberExpression(s) => { + // TODO + // you could break this with ["postMessage"] etc + // however this code only exists because of recaptcha whatever + // and it would slow down js execution a lot + if s.property.name == "postMessage" { + self.jschanges.add(rewrite!(s.property.span, SetRealmFn)); + + walk::walk_expression(self, &s.object); + return; // unwise to walk the rest of the tree + } - walk::walk_expression(self, &s.object); - return; // unwise to walk the rest of the tree + if UNSAFE_GLOBALS.contains(&s.property.name.as_str()) { + self.jschanges.add(rewrite!( + s.property.span(), + RewriteProperty { ident: s.property.name } + )); + } } - - if !self.flags.strict_rewrites - && !UNSAFE_GLOBALS.contains(&s.property.name.as_str()) - && let Expression::Identifier(_) | Expression::ThisExpression(_) = &s.object - { - // cull tree - this should be safe - return; - } - - if self.flags.scramitize - && !matches!(s.object, Expression::MetaProperty(_) | Expression::Super(_)) - { - self.scramitize(s.object.span()); + MemberExpression::ComputedMemberExpression(s) => { + self.walk_computed_member_expression(s); } + _ => {} // if !self.flags.strict_rewrites + // && !UNSAFE_GLOBALS.contains(&s.property.name.as_str()) + // && let Expression::Identifier(_) | Expression::ThisExpression(_) = &s.object + // { + // // cull tree - this should be safe + // return; + // } + + // if self.flags.scramitize + // && !matches!(s.object, Expression::MetaProperty(_) | Expression::Super(_)) + // { + // self.scramitize(s.object.span()); + // } } walk::walk_member_expression(self, it); } - fn visit_this_expression(&mut self, it: &ThisExpression) { - self.jschanges.add(rewrite!(it.span, WrapThisFn)); - } fn visit_debugger_statement(&mut self, it: &DebuggerStatement) { // delete debugger statements entirely. some sites will spam debugger as an anti-debugging measure, and we don't want that! @@ -167,7 +186,7 @@ where self.jschanges.add(rewrite!( it.span, Eval { - inner: Span::new(s.span.end + 1, it.span.end), + inner: Span::new(s.span.end + 1, it.span.end - 1), } )); @@ -199,11 +218,11 @@ where } fn visit_export_all_declaration(&mut self, it: &ExportAllDeclaration<'data>) { - self.rewrite_url(&it.source,true); + self.rewrite_url(&it.source, true); } fn visit_export_named_declaration(&mut self, it: &ExportNamedDeclaration<'data>) { if let Some(source) = &it.source { - self.rewrite_url(source,true); + self.rewrite_url(source, true); } // do not walk further, we don't want to rewrite the identifiers } @@ -272,9 +291,9 @@ where walk::walk_unary_expression(self, it); } - fn visit_update_expression(&mut self, _it: &UpdateExpression<'data>) { - // then no, don't walk it, we don't care - } + // fn visit_update_expression(&mut self, _it: &UpdateExpression<'data>) { + // // this is like a ++ or -- operator + // } fn visit_meta_property(&mut self, it: &MetaProperty<'data>) { if it.meta.name == "import" { @@ -300,6 +319,22 @@ where return; } } + AssignmentTarget::StaticMemberExpression(s) => { + if UNSAFE_GLOBALS.contains(&s.property.name.as_str()) { + self.jschanges.add(rewrite!( + s.property.span(), + RewriteProperty { ident: s.property.name } + )); + } + + // more to walk + walk::walk_expression(self, &s.object); + } + AssignmentTarget::ComputedMemberExpression(s) => { + self.walk_computed_member_expression(s); + walk::walk_expression(self, &s.object); + walk::walk_expression(self, &s.expression); + } AssignmentTarget::ArrayAssignmentTarget(_) => { // [location] = ["https://example.com"] // this is such a ridiculously specific edge case. just ignore it diff --git a/rewriter/native/src/main.rs b/rewriter/native/src/main.rs index 51de7835..e2714a7a 100644 --- a/rewriter/native/src/main.rs +++ b/rewriter/native/src/main.rs @@ -25,8 +25,10 @@ pub struct RewriterOptions { prefix: String, #[clap(long, default_value = "$wrap")] wrapfn: String, - #[clap(long, default_value = "$gwrap")] - wrapthisfn: String, + #[clap(long, default_value = "$sj_")] + wrappropertybase: String, + #[clap(long, default_value = "$prop")] + wrappropertyfn: String, #[clap(long, default_value = "$import")] importfn: String, #[clap(long, default_value = "$rewrite")] @@ -102,6 +104,10 @@ fn main() -> Result<()> { ); let unrewritten = NativeRewriter::unrewrite(&res); + // println!( + // "unrewritten:\n{}", + // str::from_utf8(&unrewritten).context("failed to parse unrewritten js")? + // ); eprintln!("errors:"); for err in res.errors { diff --git a/rewriter/native/src/rewriter.rs b/rewriter/native/src/rewriter.rs index 0094eedb..c55c7a9a 100644 --- a/rewriter/native/src/rewriter.rs +++ b/rewriter/native/src/rewriter.rs @@ -52,7 +52,8 @@ impl NativeRewriter { Config { prefix: cfg.prefix.clone(), wrapfn: cfg.wrapfn.clone(), - wrapthisfn: cfg.wrapthisfn.clone(), + wrappropertybase: cfg.wrappropertybase.clone(), + wrappropertyfn: cfg.wrappropertyfn.clone(), importfn: cfg.importfn.clone(), rewritefn: cfg.rewritefn.clone(), metafn: cfg.metafn.clone(), diff --git a/rewriter/wasm/src/jsr.rs b/rewriter/wasm/src/jsr.rs index 09ebda67..61720e77 100644 --- a/rewriter/wasm/src/jsr.rs +++ b/rewriter/wasm/src/jsr.rs @@ -28,7 +28,7 @@ extern "C" { #[wasm_bindgen(typescript_custom_section)] const REWRITER_OUTPUT: &'static str = r#" -export type JsRewriterOutput = { +export type JsRewriterOutput = { js: Uint8Array, map: Uint8Array, scramtag: string, @@ -49,8 +49,9 @@ fn get_config(scramjet: &Object) -> Result { Ok(Config { prefix: get_str(config, "prefix")?, + wrappropertybase: get_str(globals, "wrappropertybase")?, + wrappropertyfn: get_str(globals, "wrappropertyfn")?, wrapfn: get_str(globals, "wrapfn")?, - wrapthisfn: get_str(globals, "wrapthisfn")?, importfn: get_str(globals, "importfn")?, rewritefn: get_str(globals, "rewritefn")?, metafn: get_str(globals, "metafn")?, @@ -74,11 +75,12 @@ impl UrlRewriter for WasmUrlRewriter { .map_err(RewriterError::from)? .to_string(); - let mut rewritten = self.0 - .call1(&JsValue::NULL, &url.into()) - .map_err(RewriterError::from)? - .as_string() - .ok_or_else(|| RewriterError::not_str("url rewriter output"))?; + let mut rewritten = self + .0 + .call1(&JsValue::NULL, &url.into()) + .map_err(RewriterError::from)? + .as_string() + .ok_or_else(|| RewriterError::not_str("url rewriter output"))?; if module { rewritten.push_str("?type=module"); diff --git a/src/client/document.ts b/src/client/document.ts deleted file mode 100644 index 48065989..00000000 --- a/src/client/document.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { rewriteUrl } from "@rewriters/url"; -import { ScramjetClient } from "@client/index"; -import { getOwnPropertyDescriptorHandler } from "@client/helpers"; - -export function createDocumentProxy( - client: ScramjetClient, - self: typeof globalThis -) { - return new Proxy(self.document, { - get(target, prop) { - if (prop === "location") { - return client.locationProxy; - } - - if (prop === "defaultView") { - return client.globalProxy; - } - - const value = Reflect.get(target, prop); - - return value; - }, - set(target, prop, newValue) { - if (prop === "location") { - location.href = rewriteUrl(newValue, client.meta); - - return; - } - - return Reflect.set(target, prop, newValue); - }, - getOwnPropertyDescriptor: getOwnPropertyDescriptorHandler, - }); -} diff --git a/src/client/dom/element.ts b/src/client/dom/element.ts index 14053d9e..7d94f90c 100644 --- a/src/client/dom/element.ts +++ b/src/client/dom/element.ts @@ -410,15 +410,13 @@ export default function (client: ScramjetClient, self: typeof window) { const realwin = ctx.get() as Window; if (!realwin) return realwin; - if (SCRAMJETCLIENT in realwin) { - return realwin[SCRAMJETCLIENT].globalProxy; - } else { - // hook the iframe + if (!(SCRAMJETCLIENT in realwin)) { + // hook the iframe before the client can start to steal globals out of it const newclient = new ScramjetClient(realwin); newclient.hook(); - - return newclient.globalProxy; } + + return realwin; }, } ); @@ -438,14 +436,12 @@ export default function (client: ScramjetClient, self: typeof window) { ); if (!realwin) return realwin; - if (SCRAMJETCLIENT in realwin) { - return realwin[SCRAMJETCLIENT].documentProxy; - } else { + if (!(SCRAMJETCLIENT in realwin)) { const newclient = new ScramjetClient(realwin); newclient.hook(); - - return newclient.documentProxy; } + + return realwin.document; }, } ); @@ -467,76 +463,6 @@ export default function (client: ScramjetClient, self: typeof window) { } ); - client.Trap("TreeWalker.prototype.currentNode", { - get(ctx) { - return ctx.get(); - }, - set(ctx, value) { - if (value === client.documentProxy) { - return ctx.set(self.document); - } - - return ctx.set(value); - }, - }); - - client.Proxy("Document.prototype.open", { - apply(ctx) { - const doc = ctx.call() as Document; - - const scram: ScramjetClient = doc[SCRAMJETCLIENT]; - if (!scram) return ctx.return(doc); // ?? - - return ctx.return(scram.documentProxy); - }, - }); - - client.Trap("Node.prototype.ownerDocument", { - get(ctx) { - const doc = ctx.get() as Document | null; - if (!doc) return null; - - const scram: ScramjetClient = doc[SCRAMJETCLIENT]; - if (!scram) return doc; // ?? - - return scram.documentProxy; - }, - }); - - client.Trap( - [ - "Node.prototype.parentNode", - "Node.prototype.parentElement", - "Node.prototype.previousSibling", - "Node.prototype.nextSibling", - "Range.prototype.commonAncestorContainer", - "AbstractRange.prototype.endContainer", - "AbstractRange.prototype.startContainer", - ], - { - get(ctx) { - const n = ctx.get() as Node; - if (!(n instanceof Document)) return n; - - const scram: ScramjetClient = n[SCRAMJETCLIENT]; - if (!scram) return n; // ?? - - return scram.documentProxy; - }, - } - ); - client.Proxy("Node.prototype.getRootNode", { - apply(ctx) { - const n = ctx.call() as Node; - if (!(n instanceof Document)) return ctx.return(n); - - const scram: ScramjetClient = n[SCRAMJETCLIENT]; - if (!scram) return ctx.return(n); // ?? - - return ctx.return(scram.documentProxy); - }, - }); - client.Proxy("DOMParser.prototype.parseFromString", { apply(ctx) { if (ctx.args[1] === "text/html") { diff --git a/src/client/dom/open.ts b/src/client/dom/open.ts index b942d5f5..56ea7b5f 100644 --- a/src/client/dom/open.ts +++ b/src/client/dom/open.ts @@ -13,31 +13,7 @@ export default function (client: ScramjetClient) { const realwin = ctx.call(); - if (!realwin) return ctx.return(realwin); - - if (SCRAMJETCLIENT in realwin) { - return ctx.return(realwin[SCRAMJETCLIENT].globalProxy); - } else { - const newclient = new ScramjetClient(realwin); - // hook the opened window - newclient.hook(); - - return ctx.return(newclient.globalProxy); - } - }, - }); - - // opener will refer to the real window if it was opened by window.open - client.Trap("opener", { - get(ctx) { - const realwin = ctx.get() as Window; - - if (realwin && SCRAMJETCLIENT in realwin) { - return realwin[SCRAMJETCLIENT].globalProxy; - } else { - // the opener has to have been already hooked, so if we reach here then it's a real window - return undefined; - } + return ctx.return(realwin); }, }); diff --git a/src/client/global.ts b/src/client/global.ts deleted file mode 100644 index e09f2da6..00000000 --- a/src/client/global.ts +++ /dev/null @@ -1,90 +0,0 @@ -// import { encodeUrl } from "@/shared"; -import { iswindow } from "@client/entry"; -import { SCRAMJETCLIENT } from "@/symbols"; -import { ScramjetClient } from "@client/index"; -// import { config } from "@/shared"; -import { getOwnPropertyDescriptorHandler } from "@client/helpers"; - -export const UNSAFE_GLOBALS = [ - "window", - "self", - "globalThis", - "this", - "parent", - "top", - "location", - "document", - "eval", - "frames", -]; - -export function createGlobalProxy( - client: ScramjetClient, - self: typeof globalThis -): typeof globalThis { - return new Proxy(self, { - get(target, prop) { - const value = Reflect.get(target, prop); - - if ( - iswindow && - (typeof prop === "string" || typeof prop === "number") && - !isNaN(Number(prop)) && - value - ) { - const win: Self = value.self; - // indexing into window gives you the contentWindow of the subframes for some reason - // you can't *set* it so this should always be the right value - if (win) { - if (SCRAMJETCLIENT in win) { - // then we've already hooked this frame and we can just send over its proxy - return win[SCRAMJETCLIENT].globalProxy; - } else { - // this can happen if it's an about:blank iframe that we've never gotten the chance to inject into - // just make a new client for it and inject - const newclient = new ScramjetClient(win); - newclient.hook(); - - return newclient.globalProxy; - } - } - } - - if (prop === "$scramjet") return undefined; - - if (typeof prop === "string" && UNSAFE_GLOBALS.includes(prop)) { - // TODO strict mode detect - return client.wrapfn(value, true); - } - - return value; - }, - - set(target, prop, value) { - if (prop === "location") { - client.url = value; - - return; - } - - return Reflect.set(target, prop, value); - }, - has(target, prop) { - if (prop === "$scramjet") return false; - - return Reflect.has(target, prop); - }, - ownKeys(target) { - return Reflect.ownKeys(target).filter((key) => key !== "$scramjet"); - }, - defineProperty(target, property, attributes) { - if (!attributes.get && !attributes.set) { - attributes.writable = true; - } - attributes.configurable = true; - - return Reflect.defineProperty(target, property, attributes); - }, - getOwnPropertyDescriptor: getOwnPropertyDescriptorHandler, - }); -} diff --git a/src/client/index.ts b/src/client/index.ts index 699ee536..74b64bdd 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -6,8 +6,6 @@ import initEpoxy, { info as epoxyInfo, } from "@mercuryworkshop/epoxy-tls"; import { SCRAMJETCLIENT, SCRAMJETFRAME } from "@/symbols"; -import { createDocumentProxy } from "@client/document"; -import { createGlobalProxy } from "@client/global"; import { getOwnPropertyDescriptorHandler } from "@client/helpers"; import { createLocationProxy } from "@client/location"; import { nativeGetOwnPropertyDescriptor } from "@client/natives"; @@ -70,8 +68,6 @@ export type Trap = { }; export class ScramjetClient { - documentProxy: any; - globalProxy: any; locationProxy: any; serviceWorker: ServiceWorkerContainer; epoxy: EpoxyClient; @@ -132,13 +128,10 @@ export class ScramjetClient { this.serviceWorker = this.global.navigator.serviceWorker; if (iswindow) { - this.documentProxy = createDocumentProxy(this, global); - global.document[SCRAMJETCLIENT] = this; } this.locationProxy = createLocationProxy(this, global); - this.globalProxy = createGlobalProxy(this, global); this.wrapfn = createWrapFn(this, global); this.sourcemaps = {}; this.natives = { diff --git a/src/client/shared/event.ts b/src/client/shared/event.ts index 1f2facf3..341ececf 100644 --- a/src/client/shared/event.ts +++ b/src/client/shared/event.ts @@ -4,7 +4,6 @@ import { SCRAMJETCLIENT } from "@/symbols"; import { ScramjetClient } from "@client/index"; import { getOwnPropertyDescriptorHandler } from "@client/helpers"; import { nativeGetOwnPropertyDescriptor } from "@client/natives"; -import { unproxy } from "@client/shared/unproxy"; const realOnEvent = Symbol.for("scramjet original onevent function"); @@ -26,9 +25,9 @@ export default function (client: ScramjetClient, self: Self) { source() { if (this.source === null) return null; - const scram: ScramjetClient = this.source[SCRAMJETCLIENT]; + // const scram: ScramjetClient = this.source[SCRAMJETCLIENT]; - if (scram) return scram.globalProxy; + // if (scram) return scram.globalProxy; return this.source; }, @@ -127,7 +126,6 @@ export default function (client: ScramjetClient, self: Self) { client.Proxy("EventTarget.prototype.addEventListener", { apply(ctx) { - unproxy(ctx, client); if (typeof ctx.args[1] !== "function") return; const origlistener = ctx.args[1]; @@ -148,7 +146,6 @@ export default function (client: ScramjetClient, self: Self) { client.Proxy("EventTarget.prototype.removeEventListener", { apply(ctx) { - unproxy(ctx, client); if (typeof ctx.args[1] !== "function") return; const arr = client.eventcallbacks.get(ctx.this); @@ -166,12 +163,6 @@ export default function (client: ScramjetClient, self: Self) { }, }); - client.Proxy("EventTarget.prototype.dispatchEvent", { - apply(ctx) { - unproxy(ctx, client); - }, - }); - const targets = [self.self, self.MessagePort.prototype] as Array; if (iswindow) targets.push(self.HTMLElement.prototype); if (self.Worker) targets.push(self.Worker.prototype); diff --git a/src/client/shared/unproxy.ts b/src/client/shared/unproxy.ts deleted file mode 100644 index f54bf1e5..00000000 --- a/src/client/shared/unproxy.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { iswindow } from "@client/entry"; -import { SCRAMJETCLIENT } from "@/symbols"; -import { ProxyCtx, ScramjetClient } from "@client/index"; - -// we don't want to end up overriding a property on window that's derived from a prototype until we've proxied the prototype -export const order = 3; - -export default function (client: ScramjetClient, self: typeof window) { - // an automated approach to cleaning the documentProxy from dom functions - // it will trigger an illegal invocation if you pass the proxy to c++ code, we gotta hotswap it out with the real one - // admittedly this is pretty slow. worth investigating if there's ways to get back some of the lost performance - - for (const target of [self]) { - for (const prop in target) { - try { - const value = target[prop]; - if (typeof value === "function") { - client.RawProxy(target, prop, { - apply(ctx) { - unproxy(ctx, client); - }, - }); - } - } catch {} - } - } - - if (!iswindow) return; - - for (const target of [ - self.Node.prototype, - self.MutationObserver.prototype, - self.document, - self.MouseEvent.prototype, - self.Range.prototype, - ]) { - for (const prop in target) { - try { - const value = target[prop]; - if (typeof value === "function") { - client.RawProxy(target, prop, { - apply(ctx) { - unproxy(ctx, client); - }, - }); - } - } catch {} - } - } - - client.Proxy("IntersectionObserver", { - construct(ctx) { - unproxy(ctx, client); - if (typeof ctx.args[1] === "object" && "root" in ctx.args[1]) - if (ctx.args[1].root === client.documentProxy) - ctx.args[1].root = self.document; - }, - }); - - // this is probably not how stuff should be done but you cant run defineProperty on the window proxy so... - client.Proxy("Object.defineProperty", { - apply(ctx) { - unproxy(ctx, client); - }, - }); - - client.Proxy("Object.getOwnPropertyDescriptor", { - apply(ctx) { - const desc = ctx.call(); - - if (!desc) return; - - if (desc.get) { - client.RawProxy(desc, "get", { - apply(ctx) { - // value of this in the getter needs to be corrected - unproxy(ctx, client); - }, - }); - } - - if (desc.set) { - client.RawProxy(desc, "set", { - apply(ctx) { - unproxy(ctx, client); - }, - }); - } - - // i don't think we have to care about value but it's worth looking into - - ctx.return(desc); - }, - }); - client.Proxy("Function.prototype.bind", { - apply(ctx) { - if ( - (ctx.args[0] instanceof Window && ctx.args[0] !== client.globalProxy) || - (ctx.args[0] instanceof Document && - ctx.args[0] !== client.documentProxy) - ) { - const client = ctx.args[0][SCRAMJETCLIENT]; - console.log(ctx.this); - ctx.this = new Proxy(ctx.this, { - apply(target, that, args) { - if (that === client.globalProxy) that = client.global; - if (that === client.documentProxy) that = client.global.document; - - for (const i in args) { - if (args[i] === client.globalProxy) args[i] = client.global; - if (args[i] === client.documentProxy) - args[i] = client.global.document; - } - - return Reflect.apply(target, that, args); - }, - }); - } - }, - }); -} - -export function unproxy(ctx: ProxyCtx, client: ScramjetClient) { - const self = client.global; - if (ctx.this === client.globalProxy) ctx.this = self; - if (ctx.this === client.documentProxy) ctx.this = self.document; - - for (const i in ctx.args) { - if (ctx.args[i] === client.globalProxy) ctx.args[i] = self; - if (ctx.args[i] === client.documentProxy) ctx.args[i] = self.document; - } -} diff --git a/src/client/shared/wrap.ts b/src/client/shared/wrap.ts index e82757ad..9dcdc73e 100644 --- a/src/client/shared/wrap.ts +++ b/src/client/shared/wrap.ts @@ -7,7 +7,6 @@ import { indirectEval } from "@client/shared/eval"; export function createWrapFn(client: ScramjetClient, self: typeof globalThis) { return function (identifier: any, strict: boolean) { - if (identifier === self) return client.globalProxy; if (identifier === self.location) return client.locationProxy; if (identifier === eval) return indirectEval.bind(client, strict); @@ -15,13 +14,11 @@ export function createWrapFn(client: ScramjetClient, self: typeof globalThis) { if (identifier === self.parent) { if (SCRAMJETCLIENT in self.parent) { // ... then we're in a subframe, and the parent frame is also in a proxy context, so we should return its proxy - return self.parent[SCRAMJETCLIENT].globalProxy; + return self.parent; } else { // ... then we should pretend we aren't nested and return the current window - return client.globalProxy; + return self; } - } else if (identifier === self.document) { - return client.documentProxy; } else if (identifier === self.top) { // instead of returning top, we need to return the uppermost parent that's inside a scramjet context let current = self; @@ -37,7 +34,7 @@ export function createWrapFn(client: ScramjetClient, self: typeof globalThis) { current = test; } - return current[SCRAMJETCLIENT].globalProxy; + return current; } } @@ -47,23 +44,98 @@ export function createWrapFn(client: ScramjetClient, self: typeof globalThis) { export const order = 4; export default function (client: ScramjetClient, self: typeof globalThis) { - // the main magic of the proxy. all attempts to access any "banned objects" will be redirected here, and instead served a proxy object - // this contrasts from how other proxies will leave the root object alone and instead attempt to catch every member access - // this presents some issues (see element.ts), but makes us a good bit faster at runtime! Object.defineProperty(self, config.globals.wrapfn, { value: client.wrapfn, writable: false, configurable: false, + enumerable: false, }); - Object.defineProperty(self, config.globals.wrapthisfn, { - value: function (i) { - if (i === self) return client.globalProxy; + Object.defineProperty(self, config.globals.wrappropertyfn, { + value: function (str) { + if ( + str === "location" || + str === "parent" || + str === "top" || + str === "eval" + ) + return config.globals.wrappropertybase + str; - return i; + return str; }, writable: false, configurable: false, + enumerable: false, }); + console.log(config.globals.wrappropertybase); + + Object.defineProperty( + self.Object.prototype, + config.globals.wrappropertybase + "location", + { + get: function () { + // if (this.location.constructor.toString().includes("Location")) { + + if (this === self || this === self.document) { + return client.locationProxy; + } + + return this.location; + }, + set(value: any) { + if (this === self || this === self.document) { + client.url = value; + + return; + } + this.location = value; + }, + configurable: false, + enumerable: false, + } + ); + Object.defineProperty( + self.Object.prototype, + config.globals.wrappropertybase + "parent", + { + get: function () { + return client.wrapfn(this.parent, false); + }, + set(value: any) { + // i guess?? + this.parent = value; + }, + configurable: false, + enumerable: false, + } + ); + Object.defineProperty( + self.Object.prototype, + config.globals.wrappropertybase + "top", + { + get: function () { + return client.wrapfn(this.top, false); + }, + set(value: any) { + this.top = value; + }, + configurable: false, + enumerable: false, + } + ); + Object.defineProperty( + self.Object.prototype, + config.globals.wrappropertybase + "eval", + { + get: function () { + return client.wrapfn(this.eval, true); + }, + set(value: any) { + this.eval = value; + }, + configurable: false, + enumerable: false, + } + ); self.$scramitize = function (v) { if (v === self) debugger; diff --git a/src/controller/index.ts b/src/controller/index.ts index 2a0b00c8..cba51fde 100644 --- a/src/controller/index.ts +++ b/src/controller/index.ts @@ -18,7 +18,8 @@ export class ScramjetController { prefix: "/scramjet/", globals: { wrapfn: "$scramjet$wrap", - wrapthisfn: "$scramjet$wrapthis", + wrappropertybase: "$scramjet__", + wrappropertyfn: "$scramjet$prop", trysetfn: "$scramjet$tryset", importfn: "$scramjet$import", rewritefn: "$scramjet$rewrite", diff --git a/src/types.ts b/src/types.ts index a4baa974..4df27e5a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,7 +20,8 @@ export interface ScramjetConfig { prefix: string; globals: { wrapfn: string; - wrapthisfn: string; + wrappropertybase: string; + wrappropertyfn: string; trysetfn: string; importfn: string; rewritefn: string;