cargo fmt

This commit is contained in:
Toshit Chawda 2024-07-29 22:18:07 -07:00
parent 3d1b5b4c09
commit 9ad3faa331
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
4 changed files with 478 additions and 472 deletions

2
rewriter/rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
imports_granularity = "Crate"
hard_tabs = true

View file

@ -9,52 +9,52 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(js_namespace = console)] #[wasm_bindgen(js_namespace = console)]
fn log(s: &str); fn log(s: &str);
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn init() { pub fn init() {
panic::set_hook(Box::new(console_error_panic_hook::hook)); panic::set_hook(Box::new(console_error_panic_hook::hook));
} }
fn create_encode_function(encode: Function) -> EncodeFn { fn create_encode_function(encode: Function) -> EncodeFn {
Box::new(move |str| { Box::new(move |str| {
encode encode
.call1(&JsValue::NULL, &str.into()) .call1(&JsValue::NULL, &str.into())
.unwrap() .unwrap()
.as_string() .as_string()
.unwrap() .unwrap()
.to_string() .to_string()
}) })
} }
fn get_str(config: &Object, k: &str) -> String { fn get_str(config: &Object, k: &str) -> String {
Reflect::get(config, &k.into()) Reflect::get(config, &k.into())
.unwrap() .unwrap()
.as_string() .as_string()
.unwrap() .unwrap()
} }
fn get_config(config: Object) -> Config { fn get_config(config: Object) -> Config {
Config { Config {
prefix: get_str(&config, "prefix"), prefix: get_str(&config, "prefix"),
encode: create_encode_function(Reflect::get(&config, &"encode".into()).unwrap().into()), encode: create_encode_function(Reflect::get(&config, &"encode".into()).unwrap().into()),
wrapfn: get_str(&config, "wrapfn"), wrapfn: get_str(&config, "wrapfn"),
importfn: get_str(&config, "importfn"), importfn: get_str(&config, "importfn"),
rewritefn: get_str(&config, "rewritefn"), rewritefn: get_str(&config, "rewritefn"),
} }
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn rewrite_js(js: &str, url: &str, config: Object) -> Vec<u8> { pub fn rewrite_js(js: &str, url: &str, config: Object) -> Vec<u8> {
rewrite(js, Url::from_str(url).unwrap(), get_config(config)) rewrite(js, Url::from_str(url).unwrap(), get_config(config))
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn rewrite_js_from_arraybuffer(js: &[u8], url: &str, config: Object) -> Vec<u8> { pub fn rewrite_js_from_arraybuffer(js: &[u8], url: &str, config: Object) -> Vec<u8> {
// we know that this is a valid utf-8 string // we know that this is a valid utf-8 string
let js = unsafe { std::str::from_utf8_unchecked(js) }; let js = unsafe { std::str::from_utf8_unchecked(js) };
rewrite(js, Url::from_str(url).unwrap(), get_config(config)) rewrite(js, Url::from_str(url).unwrap(), get_config(config))
} }

View file

@ -1,9 +1,9 @@
#![allow(clippy::print_stdout)] #![allow(clippy::print_stdout)]
use std::{ use std::{
borrow::Cow, borrow::Cow,
env, env,
path::Path, path::Path,
str::{from_utf8, FromStr}, str::{from_utf8, FromStr},
}; };
pub mod rewrite; pub mod rewrite;
@ -24,108 +24,112 @@ use crate::rewrite::Config;
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn encode(data: &str) -> Cow<'_, str> { pub fn encode(data: &str) -> Cow<'_, str> {
encode_binary(data.as_bytes()) encode_binary(data.as_bytes())
} }
/// Percent-encodes every byte except alphanumerics and `-`, `_`, `.`, `~`. /// Percent-encodes every byte except alphanumerics and `-`, `_`, `.`, `~`.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn encode_binary(data: &[u8]) -> Cow<'_, str> { pub fn encode_binary(data: &[u8]) -> Cow<'_, str> {
// add maybe extra capacity, but try not to exceed allocator's bucket size // add maybe extra capacity, but try not to exceed allocator's bucket size
let mut escaped = String::new(); let mut escaped = String::new();
let _ = escaped.try_reserve(data.len() | 15); let _ = escaped.try_reserve(data.len() | 15);
let unmodified = append_string(data, &mut escaped, true); let unmodified = append_string(data, &mut escaped, true);
if unmodified { if unmodified {
return Cow::Borrowed(unsafe { return Cow::Borrowed(unsafe {
// encode_into has checked it's ASCII // encode_into has checked it's ASCII
std::str::from_utf8_unchecked(data) std::str::from_utf8_unchecked(data)
}); });
} }
Cow::Owned(escaped) Cow::Owned(escaped)
} }
fn append_string(data: &[u8], escaped: &mut String, may_skip: bool) -> bool { fn append_string(data: &[u8], escaped: &mut String, may_skip: bool) -> bool {
encode_into(data, may_skip, |s| { encode_into(data, may_skip, |s| {
escaped.push_str(s); escaped.push_str(s);
Ok::<_, std::convert::Infallible>(()) Ok::<_, std::convert::Infallible>(())
}) })
.unwrap() .unwrap()
} }
fn encode_into<E>( fn encode_into<E>(
mut data: &[u8], mut data: &[u8],
may_skip_write: bool, may_skip_write: bool,
mut push_str: impl FnMut(&str) -> Result<(), E>, mut push_str: impl FnMut(&str) -> Result<(), E>,
) -> Result<bool, E> { ) -> Result<bool, E> {
let mut pushed = false; let mut pushed = false;
loop { loop {
// Fast path to skip over safe chars at the beginning of the remaining string // Fast path to skip over safe chars at the beginning of the remaining string
let ascii_len = data.iter() let ascii_len = data
.take_while(|&&c| matches!(c, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'.' | b'_' | b'~')).count(); .iter()
.take_while(
|&&c| matches!(c, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'.' | b'_' | b'~'),
)
.count();
let (safe, rest) = if ascii_len >= data.len() { let (safe, rest) = if ascii_len >= data.len() {
if !pushed && may_skip_write { if !pushed && may_skip_write {
return Ok(true); return Ok(true);
} }
(data, &[][..]) // redundatnt to optimize out a panic in split_at (data, &[][..]) // redundatnt to optimize out a panic in split_at
} else { } else {
data.split_at(ascii_len) data.split_at(ascii_len)
}; };
pushed = true; pushed = true;
if !safe.is_empty() { if !safe.is_empty() {
push_str(unsafe { std::str::from_utf8_unchecked(safe) })?; push_str(unsafe { std::str::from_utf8_unchecked(safe) })?;
} }
if rest.is_empty() { if rest.is_empty() {
break; break;
} }
match rest.split_first() { match rest.split_first() {
Some((byte, rest)) => { Some((byte, rest)) => {
let enc = &[b'%', to_hex_digit(byte >> 4), to_hex_digit(byte & 15)]; let enc = &[b'%', to_hex_digit(byte >> 4), to_hex_digit(byte & 15)];
push_str(unsafe { std::str::from_utf8_unchecked(enc) })?; push_str(unsafe { std::str::from_utf8_unchecked(enc) })?;
data = rest; data = rest;
} }
None => break, None => break,
}; };
} }
Ok(false) Ok(false)
} }
#[inline] #[inline]
fn to_hex_digit(digit: u8) -> u8 { fn to_hex_digit(digit: u8) -> u8 {
match digit { match digit {
0..=9 => b'0' + digit, 0..=9 => b'0' + digit,
10..=255 => b'A' - 10 + digit, 10..=255 => b'A' - 10 + digit,
} }
} }
fn encode_string(s: String) -> String { fn encode_string(s: String) -> String {
encode(&s).to_string() encode(&s).to_string()
} }
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string()); let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
let path = Path::new(&name); let path = Path::new(&name);
let source_text = std::fs::read_to_string(path)?; let source_text = std::fs::read_to_string(path)?;
println!( println!(
"{}", "{}",
from_utf8( from_utf8(
rewrite( rewrite(
&source_text, &source_text,
Url::from_str("https://google.com/glorngle/si.js").unwrap(), Url::from_str("https://google.com/glorngle/si.js").unwrap(),
Config { Config {
prefix: "/scrammedjet/".to_string(), prefix: "/scrammedjet/".to_string(),
encode: Box::new(encode_string), encode: Box::new(encode_string),
wrapfn: "$wrap".to_string(), wrapfn: "$wrap".to_string(),
importfn: "$import".to_string(), importfn: "$import".to_string(),
rewritefn: "$rewrite".to_string(), rewritefn: "$rewrite".to_string(),
} }
) )
.as_slice() .as_slice()
) )
.unwrap() .unwrap()
); );
Ok(()) Ok(())
} }

View file

@ -1,8 +1,8 @@
use oxc_allocator::Allocator; use oxc_allocator::Allocator;
use oxc_ast::{ use oxc_ast::{
ast::{AssignmentTarget, Expression, IdentifierReference, ObjectPropertyKind}, ast::{AssignmentTarget, Expression, IdentifierReference, ObjectPropertyKind},
visit::walk, visit::walk,
Visit, Visit,
}; };
use oxc_parser::Parser; use oxc_parser::Parser;
use oxc_span::{SourceType, Span}; use oxc_span::{SourceType, Span};
@ -11,425 +11,425 @@ use url::Url;
#[derive(Debug)] #[derive(Debug)]
enum JsChange { enum JsChange {
GenericChange { GenericChange {
span: Span, span: Span,
text: String, text: String,
}, },
DebugInject { DebugInject {
span: Span, span: Span,
}, },
Assignment { Assignment {
name: String, name: String,
entirespan: Span, entirespan: Span,
rhsspan: Span, rhsspan: Span,
op: AssignmentOperator, op: AssignmentOperator,
}, },
} }
pub type EncodeFn = Box<dyn Fn(String) -> String>; pub type EncodeFn = Box<dyn Fn(String) -> String>;
struct Rewriter { struct Rewriter {
jschanges: Vec<JsChange>, jschanges: Vec<JsChange>,
base: Url, base: Url,
config: Config, config: Config,
} }
pub struct Config { pub struct Config {
pub prefix: String, pub prefix: String,
pub wrapfn: String, pub wrapfn: String,
pub importfn: String, pub importfn: String,
pub rewritefn: String, pub rewritefn: String,
pub encode: EncodeFn, pub encode: EncodeFn,
} }
impl Rewriter { impl Rewriter {
fn rewrite_url(&mut self, url: String) -> String { fn rewrite_url(&mut self, url: String) -> String {
let url = self.base.join(&url).unwrap(); let url = self.base.join(&url).unwrap();
let urlencoded = (self.config.encode)(url.to_string()); let urlencoded = (self.config.encode)(url.to_string());
format!("\"{}{}\"", self.config.prefix, urlencoded) format!("\"{}{}\"", self.config.prefix, urlencoded)
} }
} }
impl<'a> Visit<'a> for Rewriter { impl<'a> Visit<'a> for Rewriter {
fn visit_identifier_reference(&mut self, it: &IdentifierReference<'a>) { fn visit_identifier_reference(&mut self, it: &IdentifierReference<'a>) {
// self.jschanges.push(JsChange::GenericChange { // self.jschanges.push(JsChange::GenericChange {
// span: it.span, // span: it.span,
// text: format!( // text: format!(
// "({}(typeof {} == 'undefined' || {}, (()=>{{ try {{return arguments}} catch(_){{}} }})()))", // "({}(typeof {} == 'undefined' || {}, (()=>{{ try {{return arguments}} catch(_){{}} }})()))",
// self.wrapfn, it.name, it.name // self.wrapfn, it.name, it.name
// ), // ),
// }); // });
if UNSAFE_GLOBALS.contains(&it.name.to_string().as_str()) { if UNSAFE_GLOBALS.contains(&it.name.to_string().as_str()) {
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: it.span, span: it.span,
text: format!("({}({}))", self.config.wrapfn, it.name), text: format!("({}({}))", self.config.wrapfn, it.name),
}); });
} }
} }
fn visit_this_expression(&mut self, it: &oxc_ast::ast::ThisExpression) { fn visit_this_expression(&mut self, it: &oxc_ast::ast::ThisExpression) {
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: it.span, span: it.span,
text: format!("({}(this))", self.config.wrapfn), text: format!("({}(this))", self.config.wrapfn),
}); });
} }
fn visit_debugger_statement(&mut self, it: &oxc_ast::ast::DebuggerStatement) { fn visit_debugger_statement(&mut self, it: &oxc_ast::ast::DebuggerStatement) {
// delete debugger statements entirely. some sites will spam debugger as an anti-debugging measure, and we don't want that! // delete debugger statements entirely. some sites will spam debugger as an anti-debugging measure, and we don't want that!
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: it.span, span: it.span,
text: "".to_string(), text: "".to_string(),
}); });
} }
// we can't overwrite window.eval in the normal way because that would make everything an // we can't overwrite window.eval in the normal way because that would make everything an
// indirect eval, which could break things. we handle that edge case here // indirect eval, which could break things. we handle that edge case here
fn visit_call_expression(&mut self, it: &oxc_ast::ast::CallExpression<'a>) { fn visit_call_expression(&mut self, it: &oxc_ast::ast::CallExpression<'a>) {
if let Expression::Identifier(s) = &it.callee { if let Expression::Identifier(s) = &it.callee {
// if it's optional that actually makes it an indirect eval which is handled separately // if it's optional that actually makes it an indirect eval which is handled separately
if s.name == "eval" && !it.optional { if s.name == "eval" && !it.optional {
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: Span::new(s.span.start, s.span.end + 1), span: Span::new(s.span.start, s.span.end + 1),
text: format!("eval({}(", self.config.rewritefn), text: format!("eval({}(", self.config.rewritefn),
}); });
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: Span::new(it.span.end, it.span.end), span: Span::new(it.span.end, it.span.end),
text: ")".to_string(), text: ")".to_string(),
}); });
// then we walk the arguments, but not the callee, since we want it to resolve to // then we walk the arguments, but not the callee, since we want it to resolve to
// the real eval // the real eval
walk::walk_arguments(self, &it.arguments); walk::walk_arguments(self, &it.arguments);
return; return;
} }
} }
walk::walk_call_expression(self, it); walk::walk_call_expression(self, it);
} }
fn visit_import_declaration(&mut self, it: &oxc_ast::ast::ImportDeclaration<'a>) { fn visit_import_declaration(&mut self, it: &oxc_ast::ast::ImportDeclaration<'a>) {
let name = it.source.value.to_string(); let name = it.source.value.to_string();
let text = self.rewrite_url(name); let text = self.rewrite_url(name);
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: it.source.span, span: it.source.span,
text, text,
}); });
walk::walk_import_declaration(self, it); walk::walk_import_declaration(self, it);
} }
fn visit_import_expression(&mut self, it: &oxc_ast::ast::ImportExpression<'a>) { fn visit_import_expression(&mut self, it: &oxc_ast::ast::ImportExpression<'a>) {
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: Span::new(it.span.start, it.span.start + 6), span: Span::new(it.span.start, it.span.start + 6),
text: format!("({}(\"{}\"))", self.config.importfn, self.base), text: format!("({}(\"{}\"))", self.config.importfn, self.base),
}); });
walk::walk_import_expression(self, it); walk::walk_import_expression(self, it);
} }
fn visit_export_all_declaration(&mut self, it: &oxc_ast::ast::ExportAllDeclaration<'a>) { fn visit_export_all_declaration(&mut self, it: &oxc_ast::ast::ExportAllDeclaration<'a>) {
let name = it.source.value.to_string(); let name = it.source.value.to_string();
let text = self.rewrite_url(name); let text = self.rewrite_url(name);
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: it.source.span, span: it.source.span,
text, text,
}); });
} }
fn visit_export_named_declaration(&mut self, it: &oxc_ast::ast::ExportNamedDeclaration<'a>) { fn visit_export_named_declaration(&mut self, it: &oxc_ast::ast::ExportNamedDeclaration<'a>) {
if let Some(source) = &it.source { if let Some(source) = &it.source {
let name = source.value.to_string(); let name = source.value.to_string();
let text = self.rewrite_url(name); let text = self.rewrite_url(name);
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: source.span, span: source.span,
text, text,
}); });
} }
// do not walk further, we don't want to rewrite the identifiers // do not walk further, we don't want to rewrite the identifiers
} }
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
fn visit_try_statement(&mut self, it: &oxc_ast::ast::TryStatement<'a>) { fn visit_try_statement(&mut self, it: &oxc_ast::ast::TryStatement<'a>) {
// for debugging we need to know what the error was // for debugging we need to know what the error was
if let Some(h) = &it.handler { if let Some(h) = &it.handler {
if let Some(name) = &h.param { if let Some(name) = &h.param {
if let Some(name) = name.pattern.get_identifier() { if let Some(name) = name.pattern.get_identifier() {
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: Span::new(h.body.span.start + 1, h.body.span.start + 1), span: Span::new(h.body.span.start + 1, h.body.span.start + 1),
text: format!("$scramerr({});", name), text: format!("$scramerr({});", name),
}); });
} }
} }
} }
} }
fn visit_object_expression(&mut self, it: &oxc_ast::ast::ObjectExpression<'a>) { fn visit_object_expression(&mut self, it: &oxc_ast::ast::ObjectExpression<'a>) {
for prop in &it.properties { for prop in &it.properties {
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match prop { match prop {
ObjectPropertyKind::ObjectProperty(p) => match &p.value { ObjectPropertyKind::ObjectProperty(p) => match &p.value {
Expression::Identifier(s) => { Expression::Identifier(s) => {
if UNSAFE_GLOBALS.contains(&s.name.to_string().as_str()) && p.shorthand { if UNSAFE_GLOBALS.contains(&s.name.to_string().as_str()) && p.shorthand {
self.jschanges.push(JsChange::GenericChange { self.jschanges.push(JsChange::GenericChange {
span: s.span, span: s.span,
text: format!("{}: ({}({}))", s.name, self.config.wrapfn, s.name), text: format!("{}: ({}({}))", s.name, self.config.wrapfn, s.name),
}); });
return; return;
} }
} }
_ => {} _ => {}
}, },
_ => {} _ => {}
} }
} }
walk::walk_object_expression(self, it); walk::walk_object_expression(self, it);
} }
fn visit_return_statement(&mut self, it: &oxc_ast::ast::ReturnStatement<'a>) { fn visit_return_statement(&mut self, it: &oxc_ast::ast::ReturnStatement<'a>) {
self.jschanges.push(JsChange::DebugInject { self.jschanges.push(JsChange::DebugInject {
span: Span::new(it.span.start + 6, it.span.start + 6), span: Span::new(it.span.start + 6, it.span.start + 6),
}); });
walk::walk_return_statement(self, it); walk::walk_return_statement(self, it);
} }
// we don't want to rewrite the identifiers here because of a very specific edge case // we don't want to rewrite the identifiers here because of a very specific edge case
fn visit_for_in_statement(&mut self, it: &oxc_ast::ast::ForInStatement<'a>) { fn visit_for_in_statement(&mut self, it: &oxc_ast::ast::ForInStatement<'a>) {
walk::walk_statement(self, &it.body); walk::walk_statement(self, &it.body);
} }
fn visit_for_of_statement(&mut self, it: &oxc_ast::ast::ForOfStatement<'a>) { fn visit_for_of_statement(&mut self, it: &oxc_ast::ast::ForOfStatement<'a>) {
walk::walk_statement(self, &it.body); walk::walk_statement(self, &it.body);
} }
fn visit_update_expression(&mut self, _it: &oxc_ast::ast::UpdateExpression<'a>) { fn visit_update_expression(&mut self, _it: &oxc_ast::ast::UpdateExpression<'a>) {
// then no, don't walk it, we don't care // then no, don't walk it, we don't care
} }
fn visit_assignment_expression(&mut self, it: &oxc_ast::ast::AssignmentExpression<'a>) { fn visit_assignment_expression(&mut self, it: &oxc_ast::ast::AssignmentExpression<'a>) {
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match &it.left { match &it.left {
AssignmentTarget::AssignmentTargetIdentifier(s) => { AssignmentTarget::AssignmentTargetIdentifier(s) => {
if ["location"].contains(&s.name.to_string().as_str()) { if ["location"].contains(&s.name.to_string().as_str()) {
self.jschanges.push(JsChange::Assignment { self.jschanges.push(JsChange::Assignment {
name: s.name.to_string(), name: s.name.to_string(),
entirespan: it.span, entirespan: it.span,
rhsspan: expression_span(&it.right), rhsspan: expression_span(&it.right),
op: it.operator, op: it.operator,
}); });
// avoid walking rest of tree, i would need to figure out nested rewrites // avoid walking rest of tree, i would need to figure out nested rewrites
// somehow // somehow
return; return;
} }
} }
AssignmentTarget::ArrayAssignmentTarget(_) => { AssignmentTarget::ArrayAssignmentTarget(_) => {
// [location] = ["https://example.com"] // [location] = ["https://example.com"]
// this is such a ridiculously specific edge case. just ignore it // this is such a ridiculously specific edge case. just ignore it
return; return;
} }
_ => { _ => {
// only walk the left side if it isn't an identifier, we can't replace the // only walk the left side if it isn't an identifier, we can't replace the
// identifier with a function obviously // identifier with a function obviously
walk::walk_assignment_target(self, &it.left); walk::walk_assignment_target(self, &it.left);
} }
} }
walk::walk_expression(self, &it.right); walk::walk_expression(self, &it.right);
} }
} }
fn expression_span(e: &Expression) -> Span { fn expression_span(e: &Expression) -> Span {
// enums.split("\n").filter(f=>f).map(p=>p.trimLeft()).filter(p=>!p.startsWith("#")).map(p=>p.replace(/\(.*/,"")).map(p=>`E::${p}(s) => s.span`).join(",\n") // enums.split("\n").filter(f=>f).map(p=>p.trimLeft()).filter(p=>!p.startsWith("#")).map(p=>p.replace(/\(.*/,"")).map(p=>`E::${p}(s) => s.span`).join(",\n")
use Expression as E; use Expression as E;
match e { match e {
E::BooleanLiteral(s) => s.span, E::BooleanLiteral(s) => s.span,
E::NullLiteral(s) => s.span, E::NullLiteral(s) => s.span,
E::NumericLiteral(s) => s.span, E::NumericLiteral(s) => s.span,
E::BigIntLiteral(s) => s.span, E::BigIntLiteral(s) => s.span,
E::RegExpLiteral(s) => s.span, E::RegExpLiteral(s) => s.span,
E::StringLiteral(s) => s.span, E::StringLiteral(s) => s.span,
E::TemplateLiteral(s) => s.span, E::TemplateLiteral(s) => s.span,
E::Identifier(s) => s.span, E::Identifier(s) => s.span,
E::MetaProperty(s) => s.span, E::MetaProperty(s) => s.span,
E::Super(s) => s.span, E::Super(s) => s.span,
E::ArrayExpression(s) => s.span, E::ArrayExpression(s) => s.span,
E::ArrowFunctionExpression(s) => s.span, E::ArrowFunctionExpression(s) => s.span,
E::AssignmentExpression(s) => s.span, E::AssignmentExpression(s) => s.span,
E::AwaitExpression(s) => s.span, E::AwaitExpression(s) => s.span,
E::BinaryExpression(s) => s.span, E::BinaryExpression(s) => s.span,
E::CallExpression(s) => s.span, E::CallExpression(s) => s.span,
E::ChainExpression(s) => s.span, E::ChainExpression(s) => s.span,
E::ClassExpression(s) => s.span, E::ClassExpression(s) => s.span,
E::ConditionalExpression(s) => s.span, E::ConditionalExpression(s) => s.span,
E::FunctionExpression(s) => s.span, E::FunctionExpression(s) => s.span,
E::ImportExpression(s) => s.span, E::ImportExpression(s) => s.span,
E::LogicalExpression(s) => s.span, E::LogicalExpression(s) => s.span,
E::NewExpression(s) => s.span, E::NewExpression(s) => s.span,
E::ObjectExpression(s) => s.span, E::ObjectExpression(s) => s.span,
E::ParenthesizedExpression(s) => s.span, E::ParenthesizedExpression(s) => s.span,
E::SequenceExpression(s) => s.span, E::SequenceExpression(s) => s.span,
E::TaggedTemplateExpression(s) => s.span, E::TaggedTemplateExpression(s) => s.span,
E::ThisExpression(s) => s.span, E::ThisExpression(s) => s.span,
E::UnaryExpression(s) => s.span, E::UnaryExpression(s) => s.span,
E::UpdateExpression(s) => s.span, E::UpdateExpression(s) => s.span,
E::YieldExpression(s) => s.span, E::YieldExpression(s) => s.span,
E::PrivateInExpression(s) => s.span, E::PrivateInExpression(s) => s.span,
E::JSXElement(s) => s.span, E::JSXElement(s) => s.span,
E::JSXFragment(s) => s.span, E::JSXFragment(s) => s.span,
E::TSAsExpression(s) => s.span, E::TSAsExpression(s) => s.span,
E::TSSatisfiesExpression(s) => s.span, E::TSSatisfiesExpression(s) => s.span,
E::TSTypeAssertion(s) => s.span, E::TSTypeAssertion(s) => s.span,
E::TSNonNullExpression(s) => s.span, E::TSNonNullExpression(s) => s.span,
E::TSInstantiationExpression(s) => s.span, E::TSInstantiationExpression(s) => s.span,
E::ComputedMemberExpression(s) => s.span, E::ComputedMemberExpression(s) => s.span,
E::StaticMemberExpression(s) => s.span, E::StaticMemberExpression(s) => s.span,
E::PrivateFieldExpression(s) => s.span, E::PrivateFieldExpression(s) => s.span,
} }
} }
// js MUST not be able to get a reference to any of these because sbx // js MUST not be able to get a reference to any of these because sbx
const UNSAFE_GLOBALS: [&str; 9] = [ const UNSAFE_GLOBALS: [&str; 9] = [
"window", "window",
"self", "self",
"globalThis", "globalThis",
"this", "this",
"parent", "parent",
"top", "top",
"location", "location",
"document", "document",
"eval", "eval",
]; ];
pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> { pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_type = SourceType::default(); let source_type = SourceType::default();
let ret = Parser::new(&allocator, js, source_type).parse(); let ret = Parser::new(&allocator, js, source_type).parse();
for error in ret.errors { for error in ret.errors {
let cloned = js.to_string(); let cloned = js.to_string();
let error = error.with_source_code(cloned); let error = error.with_source_code(cloned);
println!("{error:?}"); println!("{error:?}");
} }
let program = ret.program; let program = ret.program;
// dbg!(&program); // dbg!(&program);
let mut ast_pass = Rewriter { let mut ast_pass = Rewriter {
jschanges: Vec::new(), jschanges: Vec::new(),
base: url, base: url,
config, config,
}; };
ast_pass.visit_program(&program); ast_pass.visit_program(&program);
// sorrt changse // sorrt changse
ast_pass.jschanges.sort_by(|a, b| { ast_pass.jschanges.sort_by(|a, b| {
let a = match a { let a = match a {
JsChange::GenericChange { span, text: _ } => span.start, JsChange::GenericChange { span, text: _ } => span.start,
JsChange::Assignment { JsChange::Assignment {
name: _, name: _,
entirespan, entirespan,
rhsspan: _, rhsspan: _,
op: _, op: _,
} => entirespan.start, } => entirespan.start,
JsChange::DebugInject { span } => span.start, JsChange::DebugInject { span } => span.start,
}; };
let b = match b { let b = match b {
JsChange::GenericChange { span, text: _ } => span.start, JsChange::GenericChange { span, text: _ } => span.start,
JsChange::Assignment { JsChange::Assignment {
name: _, name: _,
entirespan, entirespan,
rhsspan: _, rhsspan: _,
op: _, op: _,
} => entirespan.start, } => entirespan.start,
JsChange::DebugInject { span } => span.start, JsChange::DebugInject { span } => span.start,
}; };
a.cmp(&b) a.cmp(&b)
}); });
let original_len = js.len(); let original_len = js.len();
let mut difference = 0i32; let mut difference = 0i32;
for change in &ast_pass.jschanges { for change in &ast_pass.jschanges {
match &change { match &change {
JsChange::GenericChange { span, text } => { JsChange::GenericChange { span, text } => {
difference += text.len() as i32 - (span.end - span.start) as i32; difference += text.len() as i32 - (span.end - span.start) as i32;
} }
JsChange::Assignment { JsChange::Assignment {
name, name,
entirespan, entirespan,
rhsspan: _, rhsspan: _,
op: _, op: _,
} => difference += entirespan.size() as i32 + name.len() as i32 + 10, } => difference += entirespan.size() as i32 + name.len() as i32 + 10,
_ => {} _ => {}
} }
} }
let size_estimate = (original_len as i32 + difference) as usize; let size_estimate = (original_len as i32 + difference) as usize;
let mut buffer: Vec<u8> = Vec::with_capacity(size_estimate); let mut buffer: Vec<u8> = Vec::with_capacity(size_estimate);
let mut offset = 0; let mut offset = 0;
for change in ast_pass.jschanges { for change in ast_pass.jschanges {
match &change { match &change {
JsChange::GenericChange { span, text } => { JsChange::GenericChange { span, text } => {
let start = span.start as usize; let start = span.start as usize;
let end = span.end as usize; let end = span.end as usize;
buffer.extend_from_slice(unsafe { js.get_unchecked(offset..start) }.as_bytes()); buffer.extend_from_slice(unsafe { js.get_unchecked(offset..start) }.as_bytes());
buffer.extend_from_slice(text.as_bytes()); buffer.extend_from_slice(text.as_bytes());
offset = end; offset = end;
} }
JsChange::Assignment { JsChange::Assignment {
name, name,
entirespan, entirespan,
rhsspan, rhsspan,
op, op,
} => { } => {
let start = entirespan.start as usize; let start = entirespan.start as usize;
buffer.extend_from_slice(js[offset..start].as_bytes()); buffer.extend_from_slice(js[offset..start].as_bytes());
buffer.extend_from_slice( buffer.extend_from_slice(
format!( format!(
"((t)=>$scramjet$tryset({},\"{}\",t)||{}{}t)({})", "((t)=>$scramjet$tryset({},\"{}\",t)||{}{}t)({})",
name, name,
fmt_op(*op), fmt_op(*op),
name, name,
fmt_op(*op), fmt_op(*op),
&js[rhsspan.start as usize..rhsspan.end as usize] &js[rhsspan.start as usize..rhsspan.end as usize]
) )
.as_bytes(), .as_bytes(),
); );
offset = entirespan.end as usize; offset = entirespan.end as usize;
} }
JsChange::DebugInject { span } => { JsChange::DebugInject { span } => {
let start = span.start as usize; let start = span.start as usize;
buffer.extend_from_slice(unsafe { js.get_unchecked(offset..start) }.as_bytes()); buffer.extend_from_slice(unsafe { js.get_unchecked(offset..start) }.as_bytes());
offset = span.end as usize; offset = span.end as usize;
} }
} }
} }
buffer.extend_from_slice(js[offset..].as_bytes()); buffer.extend_from_slice(js[offset..].as_bytes());
buffer buffer
} }
fn fmt_op(op: AssignmentOperator) -> &'static str { fn fmt_op(op: AssignmentOperator) -> &'static str {
match op { match op {
AssignmentOperator::Assign => "=", AssignmentOperator::Assign => "=",
AssignmentOperator::Addition => "+=", AssignmentOperator::Addition => "+=",
AssignmentOperator::Subtraction => "-=", AssignmentOperator::Subtraction => "-=",
AssignmentOperator::Multiplication => "*=", AssignmentOperator::Multiplication => "*=",
AssignmentOperator::Division => "/=", AssignmentOperator::Division => "/=",
AssignmentOperator::Remainder => "%=", AssignmentOperator::Remainder => "%=",
AssignmentOperator::Exponential => "**=", AssignmentOperator::Exponential => "**=",
AssignmentOperator::ShiftLeft => "<<=", AssignmentOperator::ShiftLeft => "<<=",
AssignmentOperator::ShiftRight => ">>=", AssignmentOperator::ShiftRight => ">>=",
AssignmentOperator::ShiftRightZeroFill => ">>>=", AssignmentOperator::ShiftRightZeroFill => ">>>=",
AssignmentOperator::BitwiseAnd => "&=", AssignmentOperator::BitwiseAnd => "&=",
AssignmentOperator::BitwiseXOR => "^=", AssignmentOperator::BitwiseXOR => "^=",
AssignmentOperator::BitwiseOR => "|=", AssignmentOperator::BitwiseOR => "|=",
AssignmentOperator::LogicalAnd => "&&=", AssignmentOperator::LogicalAnd => "&&=",
AssignmentOperator::LogicalOr => "||=", AssignmentOperator::LogicalOr => "||=",
AssignmentOperator::LogicalNullish => "??=", AssignmentOperator::LogicalNullish => "??=",
} }
} }