This commit is contained in:
Toshit Chawda 2024-12-11 13:15:12 -08:00
parent 00de00e7ca
commit cb2ab0cedd
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
36 changed files with 1825 additions and 1355 deletions

View file

@ -11,7 +11,7 @@
},
"scripts": {
"build": "rspack build --mode production",
"rewriter:build": "cd rewriter && bash build.sh && cd ..",
"rewriter:build": "cd rewriter/wasm/ && bash build.sh && cd ../../",
"dev": "node server.js",
"prepack": "RELEASE=1 npm run rewriter:build && npm run build",
"pub": "npm publish --no-git-checks --access public",

404
rewriter/Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
version = 3
[[package]]
name = "ahash"
@ -16,9 +16,15 @@ dependencies = [
[[package]]
name = "allocator-api2"
version = "0.2.18"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "anyhow"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]]
name = "arrayvec"
@ -46,13 +52,14 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "boa_ast"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a69ee3a749ea36d4e56d92941e7b25076b493d4917c3d155b6cf369e23547d9"
checksum = "2c340fe0f0b267787095cbe35240c6786ff19da63ec7b69367ba338eace8169b"
dependencies = [
"bitflags",
"boa_interner",
"boa_macros",
"boa_string",
"indexmap",
"num-bigint",
"rustc-hash",
@ -60,9 +67,9 @@ dependencies = [
[[package]]
name = "boa_engine"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e4559b35b80ceb2e6328481c0eca9a24506663ea33ee1e279be6b5b618b25c"
checksum = "f620c3f06f51e65c0504ddf04978be1b814ac6586f0b45f6019801ab5efd37f9"
dependencies = [
"arrayvec",
"bitflags",
@ -75,9 +82,9 @@ dependencies = [
"boa_string",
"bytemuck",
"cfg-if",
"dashmap 5.5.3",
"fast-float",
"hashbrown 0.14.5",
"dashmap",
"fast-float2",
"hashbrown 0.15.2",
"icu_normalizer",
"indexmap",
"intrusive-collections",
@ -99,32 +106,32 @@ dependencies = [
"static_assertions",
"tap",
"thin-vec",
"thiserror 1.0.64",
"thiserror 2.0.6",
"time",
]
[[package]]
name = "boa_gc"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "716406f57d67bc3ac7fd227d5513b42df401dff14a3be22cbd8ee29817225363"
checksum = "2425c0b7720d42d73eaa6a883fbb77a5c920da8694964a3d79a67597ac55cce2"
dependencies = [
"boa_macros",
"boa_profiler",
"boa_string",
"hashbrown 0.14.5",
"hashbrown 0.15.2",
"thin-vec",
]
[[package]]
name = "boa_interner"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e18df2272616e1ba0322a69333d37dbb78797f1aa0595aad9dc41e8ecd06ad9"
checksum = "42407a3b724cfaecde8f7d4af566df4b56af32a2f11f0956f5570bb974e7f749"
dependencies = [
"boa_gc",
"boa_macros",
"hashbrown 0.14.5",
"hashbrown 0.15.2",
"indexmap",
"once_cell",
"phf",
@ -134,9 +141,9 @@ dependencies = [
[[package]]
name = "boa_macros"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240f4126219a83519bad05c9a40bfc0303921eeb571fc2d7e44c17ffac99d3f1"
checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c"
dependencies = [
"proc-macro2",
"quote",
@ -146,16 +153,16 @@ dependencies = [
[[package]]
name = "boa_parser"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b59dc05bf1dc019b11478a92986f590cff43fced4d20e866eefb913493e91c"
checksum = "9cc142dac798cdc6e2dbccfddeb50f36d2523bb977a976e19bdb3ae19b740804"
dependencies = [
"bitflags",
"boa_ast",
"boa_interner",
"boa_macros",
"boa_profiler",
"fast-float",
"fast-float2",
"icu_properties",
"num-bigint",
"num-traits",
@ -165,17 +172,17 @@ dependencies = [
[[package]]
name = "boa_profiler"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00ee0645509b3b91abd724f25072649d9e8e65653a78ff0b6e592788a58dd838"
checksum = "4064908e7cdf9b6317179e9b04dcb27f1510c1c144aeab4d0394014f37a0f922"
[[package]]
name = "boa_string"
version = "0.19.1"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae85205289bab1f2c7c8a30ddf0541cf89ba2ff7dbd144feef50bbfa664288d4"
checksum = "7debc13fbf7997bf38bf8e9b20f1ad5e2a7d27a900e1f6039fe244ce30f589b5"
dependencies = [
"fast-float",
"fast-float2",
"paste",
"rustc-hash",
"sptr",
@ -193,9 +200,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.19.0"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
dependencies = [
"bytemuck_derive",
]
@ -258,19 +265,6 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "dashmap"
version = "6.1.0"
@ -318,10 +312,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fast-float"
version = "0.2.0"
name = "fast-float2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55"
[[package]]
name = "foldhash"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
[[package]]
name = "form_urlencoded"
@ -332,15 +332,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "ftree"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241f9dd9089e67c0b269989e9f884b12a61f68fc07ea8a4be6af8ee164e1abf7"
dependencies = [
"serde",
]
[[package]]
name = "getrandom"
version = "0.2.15"
@ -364,9 +355,14 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.0"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "icu_collections"
@ -488,31 +484,33 @@ dependencies = [
[[package]]
name = "idna"
version = "0.5.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
]
[[package]]
name = "indexset"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6279c421b4feea3fb8e07ea6bd0934b38b641f6601d5f6677062fad15272ed57"
dependencies = [
"ftree",
"hashbrown 0.15.2",
]
[[package]]
@ -547,30 +545,31 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.11"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
version = "0.3.72"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.161"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "litemap"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "lock_api"
@ -603,6 +602,18 @@ dependencies = [
"autocfg",
]
[[package]]
name = "native"
version = "0.1.0"
dependencies = [
"anyhow",
"boa_engine",
"oxc",
"rewriter",
"url",
"urlencoding",
]
[[package]]
name = "nonmax"
version = "0.5.5"
@ -688,14 +699,13 @@ checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
[[package]]
name = "oxc"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc2b8d593792ca631b252b043dcaa6e65195432922fde0c4d2397cfba25d02d"
checksum = "49d6a3adf0b9cb2baa889d6e978d792c695a83886bb8e623d27ef0996be8f52b"
dependencies = [
"oxc_allocator",
"oxc_ast",
"oxc_diagnostics",
"oxc_index",
"oxc_parser",
"oxc_regular_expression",
"oxc_span",
@ -712,7 +722,7 @@ dependencies = [
"owo-colors",
"oxc-miette-derive",
"textwrap",
"thiserror 1.0.64",
"thiserror 1.0.69",
"unicode-width 0.2.0",
]
@ -729,9 +739,9 @@ dependencies = [
[[package]]
name = "oxc_allocator"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3137194d5acbead2fd78db27c03b27faa2dfa53628514b1843ae31b2ba304d28"
checksum = "6b1409be2036cb370daee57ec0942c3aa22949da7e269d583767327b9286493e"
dependencies = [
"allocator-api2",
"bumpalo",
@ -739,9 +749,9 @@ dependencies = [
[[package]]
name = "oxc_ast"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a142fb8a722e9eb9bf4c9ef37bc7f37e9cc7fc38acba1889d8fb9f2df8ca85a"
checksum = "9353f8fc2507736ce314763ea7e365e25845f431800f8cba196b8365565a6139"
dependencies = [
"bitflags",
"cow-utils",
@ -757,9 +767,9 @@ dependencies = [
[[package]]
name = "oxc_ast_macros"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ba3432be9886577f6ea36a3ecba6a9b7f2f0bc16aae8a759a0d43f91e0cb4d5"
checksum = "ffc4de384e05599bb89541ebbff124947e326a02783d66d2c2c534ecf58a66b4"
dependencies = [
"proc-macro2",
"quote",
@ -768,31 +778,44 @@ dependencies = [
[[package]]
name = "oxc_diagnostics"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2bc8acd3740a2b2b32e1826d966b65f11b9ef5bebb460ebc7cbadf898d7efec"
checksum = "425d8c272e885c3318878acf0a1e985b7bdb8b8e57d695f4ab72e444ba781432"
dependencies = [
"oxc-miette",
"rustc-hash",
]
[[package]]
name = "oxc_estree"
version = "0.37.0"
name = "oxc_ecmascript"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00fbdfd3e49faee78dada84e8cd79fdcf0bbb9a4c3f77a312b823d867e1894b"
checksum = "754e92029d7deb771b5ae9fb628903d0a8da64ca9e61b2e05eb6e01e7613976c"
dependencies = [
"num-bigint",
"num-traits",
"oxc_ast",
"oxc_span",
"oxc_syntax",
]
[[package]]
name = "oxc_estree"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89eb8a7cf00b2fd6a8e63f38548ae0f9b4f8e21734ef0ae71424104e8503d667"
[[package]]
name = "oxc_index"
version = "0.37.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4852baa0de945b11def319a618cb31546f71b0a782f22c2e80f32f0ed356804d"
checksum = "5eca5d9726cd0a6e433debe003b7bc88b2ecad0bb6109f0cef7c55e692139a34"
[[package]]
name = "oxc_parser"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf3395e8cad18ec9cda1bd530720d8e2747c5e4767573ba9bb3f0c798d944a4"
checksum = "56fff5de3a699593b5b7e8fd4b30ffedd6d0a9188580afd28745aaab83961e23"
dependencies = [
"assert-unchecked",
"bitflags",
@ -803,6 +826,7 @@ dependencies = [
"oxc_allocator",
"oxc_ast",
"oxc_diagnostics",
"oxc_ecmascript",
"oxc_regular_expression",
"oxc_span",
"oxc_syntax",
@ -812,9 +836,9 @@ dependencies = [
[[package]]
name = "oxc_regular_expression"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab44622a5b4cb09e12eda5f70d9c033c3a820b26f92a1bc86e596e137dc42089"
checksum = "e420360e2c8e83a3eef7c74bbf3a11b375370fa3656c8cee65e4dfe96e26bb93"
dependencies = [
"oxc_allocator",
"oxc_ast_macros",
@ -828,9 +852,9 @@ dependencies = [
[[package]]
name = "oxc_span"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "363c6c4329272836347089768df8c87c298f3bd329668306e1f50fc1f8804b7c"
checksum = "e940a7230413a1a138e04c1cf45fea6014d2e995b9c95857252259f5cec9f844"
dependencies = [
"compact_str",
"oxc-miette",
@ -841,13 +865,12 @@ dependencies = [
[[package]]
name = "oxc_syntax"
version = "0.37.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8697e0c9c99df9f5863196f82cb705685d747b1732733b80f5203cf70d46335a"
checksum = "ef274df7ce9d14a3d4f0b9d8453920b7a4f22bfcc1a4ec394233280cca07ed97"
dependencies = [
"assert-unchecked",
"bitflags",
"dashmap 6.1.0",
"nonmax",
"oxc_allocator",
"oxc_ast_macros",
@ -856,6 +879,7 @@ dependencies = [
"oxc_span",
"phf",
"rustc-hash",
"ryu-js",
"unicode-id-start",
]
@ -928,15 +952,15 @@ dependencies = [
[[package]]
name = "pollster"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]]
name = "portable-atomic"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "powerfmt"
@ -1012,9 +1036,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.7"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags",
]
@ -1033,22 +1057,17 @@ dependencies = [
name = "rewriter"
version = "0.1.0"
dependencies = [
"boa_engine",
"indexset",
"instant",
"js-sys",
"oxc",
"thiserror 2.0.3",
"smallvec",
"thiserror 2.0.6",
"url",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "rustc-hash"
version = "2.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]]
name = "rustversion"
@ -1082,18 +1101,18 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
[[package]]
name = "serde"
version = "1.0.210"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
@ -1102,9 +1121,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
@ -1150,9 +1169,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "2.0.89"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@ -1195,27 +1214,27 @@ checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b"
[[package]]
name = "thiserror"
version = "1.0.64"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.64",
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.3"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
dependencies = [
"thiserror-impl 2.0.3",
"thiserror-impl 2.0.6",
]
[[package]]
name = "thiserror-impl"
version = "1.0.64"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
@ -1224,9 +1243,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.3"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
dependencies = [
"proc-macro2",
"quote",
@ -1235,9 +1254,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.36"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
@ -1259,9 +1278,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@ -1277,21 +1296,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml_datetime"
version = "0.6.8"
@ -1309,12 +1313,6 @@ dependencies = [
"winnow",
]
[[package]]
name = "unicode-bidi"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
[[package]]
name = "unicode-id-start"
version = "1.3.1"
@ -1323,9 +1321,9 @@ checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b"
[[package]]
name = "unicode-ident"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-linebreak"
@ -1333,15 +1331,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
@ -1356,15 +1345,21 @@ checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "url"
version = "2.5.2"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf16_iter"
version = "1.0.5"
@ -1389,11 +1384,25 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm"
version = "0.1.0"
dependencies = [
"instant",
"js-sys",
"oxc",
"rewriter",
"thiserror 2.0.6",
"url",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
@ -1402,13 +1411,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
@ -1417,9 +1425,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1427,9 +1435,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
@ -1440,15 +1448,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "web-sys"
version = "0.3.72"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -1541,9 +1549,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "yoke"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
@ -1553,9 +1561,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
@ -1586,18 +1594,18 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",

View file

@ -1,14 +1,6 @@
[package]
name = "rewriter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[features]
default = ["debug"]
debug = []
[workspace]
members = ["native", "rewriter", "wasm"]
resolver = "2"
[profile.release]
opt-level = 3
@ -17,18 +9,6 @@ lto = true
codegen-units = 1
panic = "abort"
[dependencies]
indexset = "0.5.0"
instant = { version = "0.1.13", features = ["wasm-bindgen"] }
js-sys = "0.3.69"
oxc = "0.37.0"
thiserror = "2.0.3"
url = "2.5.2"
wasm-bindgen = "0.2.95"
web-sys = { version = "0.3.72", features = ["Url"] }
[workspace.dependencies]
oxc = "0.40.1"
[dev-dependencies]
boa_engine = "0.19.0"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }

View file

@ -1,14 +0,0 @@
set RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+simd128"
cargo build --lib --target wasm32-unknown-unknown -Z build-std=panic_abort,std --release
wasm-bindgen --weak-refs --target web --out-dir out/ target/wasm32-unknown-unknown/release/rewriter.wasm
cd ..
$WASM = "rewriter/out/rewriter_bg.wasm"
Measure-Command -Expression { wasm-opt -Oz --vacuum --dce --enable-threads --enable-bulk-memory --enable-simd "$WASM" -o rewriter/out/optimized.wasm }
Write-Output "self.WASM = '" | Out-File -NoNewline -FilePath static/wasm.js
[System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes("rewriter/out/optimized.wasm")) | Out-File -Append -NoNewline -FilePath static/wasm.js
Write-Output "';" | Out-File -Append -NoNewline -FilePath static/wasm.js
Write-Output "Rewriter Build Complete!"

View file

@ -0,0 +1,14 @@
[package]
name = "native"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.94"
oxc = { workspace = true }
rewriter = { version = "0.1.0", path = "../rewriter", features = ["debug"] }
url = "2.5.4"
urlencoding = "2.1.3"
[dev-dependencies]
boa_engine = "0.20.0"

File diff suppressed because one or more lines are too long

View file

@ -3394,4 +3394,4 @@ this._hd = this._hd || {}; (function (_) {
_.z();
} catch (e) { _._DumpException(e) }
})(this._hd);
// Google Inc.
// Google Inc.

166
rewriter/native/src/main.rs Normal file
View file

@ -0,0 +1,166 @@
use std::{env, fs, str::FromStr, sync::Arc};
use anyhow::{Context, Result};
use oxc::diagnostics::NamedSource;
use rewriter::{cfg::Config, rewrite, RewriteResult};
use url::Url;
use urlencoding::encode;
fn encode_string(str: String) -> String {
encode(&str).to_string()
}
fn dorewrite(data: &str) -> Result<RewriteResult> {
rewrite(
data,
Config {
prefix: "/scrammedjet/".to_string(),
encoder: Box::new(encode_string),
base: Url::from_str("https://google.com/glorngle/si.js").context("invalid base")?,
sourcetag: "glongle1".to_string(),
wrapfn: "$wrap".to_string(),
wrapthisfn: "$gwrap".to_string(),
importfn: "$import".to_string(),
rewritefn: "$rewrite".to_string(),
metafn: "$meta".to_string(),
setrealmfn: "$setrealm".to_string(),
pushsourcemapfn: "$pushsourcemap".to_string(),
capture_errors: true,
do_sourcemaps: true,
scramitize: false,
strict_rewrites: true,
},
)
.context("failed to rewrite file")
}
fn main() -> Result<()> {
let file = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
let data = fs::read_to_string(file).context("failed to read file")?;
let res = dorewrite(&data)?;
let source = Arc::new(
NamedSource::new(data, "https://google.com/glorngle/si.js").with_language("javascript"),
);
eprintln!("errors:");
for err in res.errors {
eprintln!("{}", err.with_source_code(source.clone()));
}
println!("changes: {:#?}", res.changes);
println!(
"rewritten:\n{}",
String::from_utf8(res.js).context("failed to parse rewritten js")?
);
Ok(())
}
#[cfg(test)]
mod test {
use std::fs;
use boa_engine::{
js_str, js_string,
object::ObjectInitializer,
property::{Attribute, PropertyDescriptorBuilder},
Context, NativeFunction, Source,
};
use crate::dorewrite;
#[test]
fn google() {
let source_text = include_str!("../sample/google.js");
dorewrite(source_text).unwrap();
}
#[test]
fn test() {
let files = fs::read_dir("./tests").unwrap();
for file in files {
if !file
.as_ref()
.unwrap()
.file_name()
.to_str()
.unwrap()
.ends_with(".js")
{
continue;
}
let content = fs::read_to_string(file.unwrap().path()).unwrap();
let mut context = Context::default();
let window = ObjectInitializer::new(&mut context).build();
context
.register_global_property(js_str!("window"), window, Attribute::READONLY)
.unwrap();
context
.global_object()
.define_property_or_throw(
js_str!("location"),
PropertyDescriptorBuilder::new()
.get(
NativeFunction::from_copy_closure(|_, _, _| {
Ok(js_str!("location").into())
})
.to_js_function(context.realm()),
)
.set(
NativeFunction::from_copy_closure(|_, _, _| {
panic!("fail: window.location got set")
})
.to_js_function(context.realm()),
)
.build(),
&mut context,
)
.unwrap();
context
.register_global_callable(
js_string!("fail"),
0,
NativeFunction::from_copy_closure(|_, _, _| {
panic!("fail");
}),
)
.unwrap();
let result = context
.eval(Source::from_bytes(
br#"
function $wrap(val) {
if (val === window || val === "location" || val === globalThis) return "";
return val;
}
function assert(val) {
if (!val) fail();
}
function check(val) {
if (val === window || val === "location") fail();
}
"#,
))
.unwrap();
let rewritten = dorewrite(&content).unwrap();
println!("{:?}", rewritten);
context.eval(Source::from_bytes(&rewritten.js)).unwrap();
println!("PASS");
}
}
}

View file

@ -25,5 +25,3 @@ function f() { return import("x") }
let window = (1, window);

View file

@ -0,0 +1,13 @@
[package]
name = "rewriter"
version = "0.1.0"
edition = "2021"
[dependencies]
oxc = { workspace = true }
smallvec = "1.13.2"
thiserror = "2.0.6"
url = "2.5.4"
[features]
debug = []

View file

@ -0,0 +1,24 @@
use url::Url;
pub struct Config {
pub prefix: String,
pub sourcetag: String,
pub base: Url,
pub wrapfn: String,
pub wrapthisfn: String,
pub importfn: String,
pub rewritefn: String,
pub setrealmfn: String,
pub metafn: String,
pub pushsourcemapfn: String,
pub encoder: EncodeFn,
pub capture_errors: bool,
pub scramitize: bool,
pub do_sourcemaps: bool,
pub strict_rewrites: bool,
}
pub type EncodeFn = Box<dyn Fn(String) -> String>;

View file

@ -0,0 +1,277 @@
use oxc::{
ast::ast::AssignmentOperator,
span::{CompactStr, Span},
};
use smallvec::{smallvec, SmallVec};
use crate::cfg::Config;
#[derive(Debug, PartialEq, Eq)]
pub enum JsChange {
/// `(cfg.wrapfn(ident))` | `cfg.wrapfn(ident)`
WrapFn {
span: Span,
ident: CompactStr,
wrapped: bool,
},
/// `cfg.setrealmfn({}).ident`
SetRealmFn {
span: Span,
ident: CompactStr,
},
/// `cfg.wrapthis(this)`
WrapThisFn {
span: Span,
},
/// `(cfg.importfn("cfg.base"))`
ImportFn {
span: Span,
},
/// `cfg.metafn("cfg.base")`
MetaFn {
span: Span,
},
/// `$scramerr(name)`
ScramErr {
span: Span,
name: CompactStr,
},
/// `$scramitize(span)`
Scramitize {
span: Span,
},
/// `eval(cfg.rewritefn(inner))`
Eval {
span: Span,
inner: Span,
},
/// `((t)=>$scramjet$tryset(name,"op",t)||(name op t))(rhsspan)`
Assignment {
name: CompactStr,
entirespan: Span,
rhsspan: Span,
op: AssignmentOperator,
},
/// `ident,` -> `ident: cfg.wrapfn(ident)`
ShorthandObj {
span: Span,
name: CompactStr,
},
SourceTag {
span: Span,
tagname: String,
},
// don't use for anything static, only use for stuff like rewriteurl
Replace {
span: Span,
text: String,
},
Delete {
span: Span,
},
}
type Changes<'a> = SmallVec<[&'a str; 8]>;
enum JsChangeInner<'a> {
Wrap {
/// Changes to add before span
before: Changes<'a>,
/// Span to add in between
span: &'a Span,
/// Changes to add after span
after: Changes<'a>,
},
Replace(Changes<'a>),
}
impl JsChange {
pub fn get_span(&self) -> &Span {
match self {
Self::WrapFn { span, .. } => span,
Self::SetRealmFn { span, .. } => span,
Self::WrapThisFn { span } => span,
Self::ImportFn { span } => span,
Self::MetaFn { span } => span,
Self::ScramErr { span, .. } => span,
Self::Scramitize { span } => span,
Self::Eval { span, .. } => span,
Self::Assignment { entirespan, .. } => entirespan,
Self::ShorthandObj { span, .. } => span,
Self::SourceTag { span, .. } => span,
Self::Replace { span, .. } => span,
Self::Delete { span } => span,
}
}
// returns (bunch of stuff to add before, option<bunch of stuff to add after>)
// bunch of stuff to add after should only be some if it's not a replace op
fn to_inner<'a>(&'a self, cfg: &'a Config) -> JsChangeInner<'a> {
match self {
Self::WrapFn { ident, wrapped, .. } => JsChangeInner::Replace(if *wrapped {
smallvec!["(", cfg.wrapfn.as_str(), "(", ident.as_str(), ")", ")"]
} else {
smallvec![cfg.wrapfn.as_str(), "(", ident.as_str(), ")"]
}),
Self::SetRealmFn { ident, .. } => {
JsChangeInner::Replace(smallvec![cfg.setrealmfn.as_str(), "({}).", ident.as_str()])
}
Self::WrapThisFn { .. } => {
JsChangeInner::Replace(smallvec![cfg.wrapthisfn.as_str(), "(this)"])
}
Self::ImportFn { .. } => JsChangeInner::Replace(smallvec![
"(",
cfg.importfn.as_str(),
"(\"",
cfg.base.as_str(),
"\"))"
]),
Self::MetaFn { .. } => JsChangeInner::Replace(smallvec![
cfg.metafn.as_str(),
"(\"",
cfg.base.as_str(),
"\")"
]),
Self::ScramErr { name, .. } => {
JsChangeInner::Replace(smallvec!["$scramerr(", name.as_str(), ");"])
}
Self::Scramitize { span } => JsChangeInner::Wrap {
before: smallvec!["$scramitize("],
span,
after: smallvec![")"],
},
Self::Eval { inner, .. } => JsChangeInner::Wrap {
before: smallvec!["eval(", cfg.rewritefn.as_str(), "("],
span: inner,
after: smallvec![")"],
},
Self::Assignment {
name, rhsspan, op, ..
} => JsChangeInner::Wrap {
before: smallvec![
"((t)=>$scramjet$tryset(",
name.as_str(),
",\"",
op.as_str(),
"\",t)||(",
name.as_str(),
op.as_str(),
"t))("
],
span: rhsspan,
after: smallvec![")"],
},
Self::ShorthandObj { name, .. } => JsChangeInner::Replace(smallvec![
name.as_str(),
":",
cfg.wrapfn.as_str(),
"(",
name.as_str(),
")"
]),
Self::SourceTag { tagname, .. } => JsChangeInner::Replace(smallvec![
"/*scramtag ",
tagname.as_str(),
" ",
cfg.sourcetag.as_str(),
"*/"
]),
Self::Replace { text, .. } => JsChangeInner::Replace(smallvec![text.as_str()]),
Self::Delete { .. } => JsChangeInner::Replace(smallvec![]),
}
}
}
impl PartialOrd for JsChange {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for JsChange {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.get_span().start.cmp(&other.get_span().start)
}
}
pub(crate) struct JsChangeResult {
pub js: Vec<u8>,
pub sourcemap: Vec<u8>,
}
pub(crate) struct JsChanges {
pub inner: Vec<JsChange>,
}
impl JsChanges {
pub fn new() -> Self {
Self { inner: Vec::new() }
}
pub fn add(&mut self, change: JsChange) {
self.inner.push(change);
}
pub fn perform(&mut self, js: &str, cfg: &Config) -> JsChangeResult {
let mut offset = 0;
let mut buffer = Vec::with_capacity(((js.len() as u64 * 120) / 100) as usize);
// TODO: add sourcemaps
let map = Vec::with_capacity(js.len() * 2);
self.inner.sort();
for change in &self.inner {
println!("{:?}", change);
let span = change.get_span();
let start = span.start as usize;
let end = span.end as usize;
buffer.extend_from_slice(js[offset..start].as_bytes());
let change = change.to_inner(cfg);
match change {
JsChangeInner::Wrap {
before,
span: wrapspan,
after,
} => {
// wrap op
for str in before {
buffer.extend_from_slice(str.as_bytes());
}
let wrapstart = wrapspan.start as usize;
let wrapend = wrapspan.end as usize;
buffer.extend_from_slice(js[wrapstart..wrapend].as_bytes());
for str in after {
buffer.extend_from_slice(str.as_bytes());
}
}
JsChangeInner::Replace(list) => {
for str in list {
buffer.extend_from_slice(str.as_bytes());
}
}
}
offset = end;
}
buffer.extend_from_slice(js[offset..].as_bytes());
JsChangeResult {
js: buffer,
sourcemap: map,
}
}
}

View file

@ -0,0 +1,65 @@
use cfg::Config;
use changes::{JsChange, JsChangeResult, JsChanges};
use oxc::{
allocator::Allocator,
ast::Visit,
diagnostics::OxcDiagnostic,
parser::{ParseOptions, Parser},
span::SourceType,
};
use thiserror::Error;
use visitor::Visitor;
pub mod cfg;
pub mod changes;
mod visitor;
#[derive(Error, Debug)]
pub enum RewriterError {
#[error("oxc panicked in parser")]
OxcPanicked,
}
#[derive(Debug)]
pub struct RewriteResult {
pub js: Vec<u8>,
pub sourcemap: Vec<u8>,
pub errors: Vec<OxcDiagnostic>,
pub changes: Vec<JsChange>,
}
pub fn rewrite(js: &str, config: Config) -> Result<RewriteResult, RewriterError> {
let allocator = Allocator::default();
let source_type = SourceType::default();
let ret = Parser::new(&allocator, js, source_type)
.with_options(ParseOptions {
parse_regular_expression: false, // default
allow_return_outside_function: true,
preserve_parens: true, // default
})
.parse();
if ret.panicked {
return Err(RewriterError::OxcPanicked);
}
let mut visitor = Visitor {
jschanges: JsChanges::new(),
config,
};
visitor.visit_program(&ret.program);
let Visitor {
mut jschanges,
config,
} = visitor;
let JsChangeResult { js, sourcemap } = jschanges.perform(js, &config);
let JsChanges { inner: changes } = jschanges;
Ok(RewriteResult {
js,
sourcemap,
errors: ret.errors,
changes,
})
}

View file

@ -0,0 +1,340 @@
use oxc::{
ast::{
ast::{
AssignmentExpression, AssignmentTarget, CallExpression, DebuggerStatement,
ExportAllDeclaration, ExportNamedDeclaration, Expression, ForInStatement,
ForOfStatement, FunctionBody, IdentifierReference, ImportDeclaration, ImportExpression,
MemberExpression, MetaProperty, NewExpression, ObjectExpression, ObjectPropertyKind,
ReturnStatement, ThisExpression, UnaryExpression, UnaryOperator, UpdateExpression,
},
visit::walk,
Visit,
},
span::{Atom, GetSpan, Span},
};
use crate::{
cfg::Config,
changes::{JsChange, JsChanges},
};
// 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",
];
pub struct Visitor {
pub jschanges: JsChanges,
pub config: Config,
}
impl Visitor {
fn rewrite_url(&mut self, url: String) -> String {
let url = self.config.base.join(&url).unwrap();
let urlencoded = (self.config.encoder)(url.to_string());
format!("\"{}{}\"", self.config.prefix, urlencoded)
}
fn rewrite_ident(&mut self, name: &Atom, span: Span) {
if UNSAFE_GLOBALS.contains(&name.as_str()) {
self.jschanges.add(JsChange::WrapFn {
span,
ident: name.to_compact_str(),
wrapped: true,
});
}
}
fn walk_member_expression(&mut self, it: &Expression) -> bool {
if 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,
} {
return true;
}
// TODO: WE SHOULD PROBABLY WALK THE REST OF THE TREE
// walk::walk_expression(self, it);
false
}
fn scramitize(&mut self, span: Span) {
self.jschanges.add(JsChange::Scramitize { span });
}
}
impl<'a> Visit<'a> for Visitor {
fn visit_identifier_reference(&mut self, it: &IdentifierReference) {
// if self.config.capture_errors {
// self.jschanges.insert(JsChange::GenericChange {
// span: it.span,
// text: format!(
// "{}({}, typeof arguments != 'undefined' && arguments)",
// self.config.wrapfn, it.name
// ),
// });
// } else {
//
if UNSAFE_GLOBALS.contains(&it.name.as_str()) {
self.jschanges.add(JsChange::WrapFn {
span: it.span,
ident: it.name.to_compact_str(),
wrapped: false,
});
}
// }
}
// we need to rewrite `new Something` to `new (wrapfn(Something))` instead of `new wrapfn(Something)`, that's why there's weird extra code here
fn visit_new_expression(&mut self, it: &NewExpression) {
self.walk_member_expression(&it.callee);
walk::walk_arguments(self, &it.arguments);
}
fn visit_member_expression(&mut self, it: &MemberExpression) {
match it {
MemberExpression::StaticMemberExpression(s) => {
if s.property.name == "postMessage" {
self.jschanges.add(JsChange::SetRealmFn {
span: s.property.span,
ident: s.property.name.to_compact_str(),
});
walk::walk_expression(self, &s.object);
return; // unwise to walk the rest of the tree
}
if !self.config.strict_rewrites
&& !UNSAFE_GLOBALS.contains(&s.property.name.as_str())
{
if let Expression::Identifier(_) = &s.object {
// cull tree - this should be safe
return;
}
if let Expression::ThisExpression(_) = &s.object {
return;
}
}
if self.config.scramitize
&& !matches!(s.object, Expression::MetaProperty(_) | Expression::Super(_))
{
self.scramitize(s.object.span());
}
}
_ => {
// 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
}
}
walk::walk_member_expression(self, it);
}
fn visit_this_expression(&mut self, it: &ThisExpression) {
self.jschanges.add(JsChange::WrapThisFn { span: it.span });
}
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!
self.jschanges.add(JsChange::Delete { span: it.span });
}
// 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
fn visit_call_expression(&mut self, it: &CallExpression<'a>) {
if let Expression::Identifier(s) = &it.callee {
// if it's optional that actually makes it an indirect eval which is handled separately
if s.name == "eval" && !it.optional {
self.jschanges.add(JsChange::Eval {
span: Span::new(it.span.start, it.span.end),
inner: Span::new(s.span.end + 1, it.span.end),
});
// then we walk the arguments, but not the callee, since we want it to resolve to
// the real eval
walk::walk_arguments(self, &it.arguments);
return;
}
}
if self.config.scramitize {
self.scramitize(it.span);
}
walk::walk_call_expression(self, it);
}
fn visit_import_declaration(&mut self, it: &ImportDeclaration<'a>) {
let name = it.source.value.to_string();
let text = self.rewrite_url(name);
self.jschanges.add(JsChange::Replace {
span: it.source.span,
text,
});
walk::walk_import_declaration(self, it);
}
fn visit_import_expression(&mut self, it: &ImportExpression<'a>) {
self.jschanges.add(JsChange::ImportFn {
span: Span::new(it.span.start, it.span.start + 6),
});
walk::walk_import_expression(self, it);
}
fn visit_export_all_declaration(&mut self, it: &ExportAllDeclaration<'a>) {
let name = it.source.value.to_string();
let text = self.rewrite_url(name);
self.jschanges.add(JsChange::Replace {
span: it.source.span,
text,
});
}
fn visit_export_named_declaration(&mut self, it: &ExportNamedDeclaration<'a>) {
if let Some(source) = &it.source {
let name = source.value.to_string();
let text = self.rewrite_url(name);
self.jschanges.add(JsChange::Replace {
span: source.span,
text,
});
}
// do not walk further, we don't want to rewrite the identifiers
}
#[cfg(feature = "debug")]
fn visit_try_statement(&mut self, it: &oxc::ast::ast::TryStatement<'a>) {
// for debugging we need to know what the error was
if self.config.capture_errors {
if let Some(h) = &it.handler {
if let Some(name) = &h.param {
if let Some(name) = name.pattern.get_identifier() {
self.jschanges.add(JsChange::ScramErr {
span: Span::new(h.body.span.start + 1, h.body.span.start + 1),
name: name.to_compact_str(),
});
}
}
}
}
walk::walk_try_statement(self, it);
}
fn visit_object_expression(&mut self, it: &ObjectExpression<'a>) {
for prop in &it.properties {
if let ObjectPropertyKind::ObjectProperty(p) = prop {
match &p.value {
Expression::Identifier(s) => {
if UNSAFE_GLOBALS.contains(&s.name.to_string().as_str()) && p.shorthand {
self.jschanges.add(JsChange::ShorthandObj {
span: s.span,
name: s.name.to_compact_str(),
});
return;
}
}
_ => {}
}
}
}
walk::walk_object_expression(self, it);
}
fn visit_function_body(&mut self, it: &FunctionBody<'a>) {
// tag function for use in sourcemaps
if self.config.do_sourcemaps {
self.jschanges.add(JsChange::SourceTag {
span: Span::new(it.span.start, it.span.start),
tagname: it.span.start.to_string(),
});
}
walk::walk_function_body(self, it);
}
fn visit_return_statement(&mut self, it: &ReturnStatement<'a>) {
// if let Some(arg) = &it.argument {
// self.jschanges.insert(JsChange::GenericChange {
// span: Span::new(it.span.start + 6, it.span.start + 6),
// text: format!(" $scramdbg((()=>{{ try {{return arguments}} catch(_){{}} }})(),("),
// });
// self.jschanges.insert(JsChange::GenericChange {
// span: Span::new(expression_span(arg).end, expression_span(arg).end),
// text: format!("))"),
// });
// }
walk::walk_return_statement(self, it);
}
fn visit_unary_expression(&mut self, it: &UnaryExpression<'a>) {
if matches!(it.operator, UnaryOperator::Typeof) {
// don't walk to identifier rewrites since it won't matter
return;
}
walk::walk_unary_expression(self, it);
}
// we don't want to rewrite the identifiers here because of a very specific edge case
fn visit_for_in_statement(&mut self, it: &ForInStatement<'a>) {
walk::walk_statement(self, &it.body);
}
fn visit_for_of_statement(&mut self, it: &ForOfStatement<'a>) {
walk::walk_statement(self, &it.body);
}
fn visit_update_expression(&mut self, _it: &UpdateExpression<'a>) {
// then no, don't walk it, we don't care
}
fn visit_meta_property(&mut self, it: &MetaProperty<'a>) {
if it.meta.name == "import" {
self.jschanges.add(JsChange::MetaFn { span: it.span });
}
}
fn visit_assignment_expression(&mut self, it: &AssignmentExpression<'a>) {
match &it.left {
AssignmentTarget::AssignmentTargetIdentifier(s) => {
if ["location"].contains(&s.name.to_string().as_str()) {
self.jschanges.add(JsChange::Assignment {
name: s.name.to_compact_str(),
entirespan: it.span,
rhsspan: it.right.span(),
op: it.operator,
});
// avoid walking rest of tree, i would need to figure out nested rewrites
// somehow
return;
}
}
AssignmentTarget::ArrayAssignmentTarget(_) => {
// [location] = ["https://example.com"]
// this is such a ridiculously specific edge case. just ignore it
return;
}
_ => {
// only walk the left side if it isn't an identifier, we can't replace the
// identifier with a function obviously
walk::walk_assignment_target(self, &it.left);
}
}
walk::walk_expression(self, &it.right);
}
}

View file

@ -2,3 +2,4 @@
channel = "nightly"
targets = [ "wasm32-unknown-unknown" ]
components = [ "rust-src" ]

View file

@ -1,48 +0,0 @@
use js_sys::Error;
use thiserror::Error;
use wasm_bindgen::{JsError, JsValue};
#[derive(Debug, Error)]
pub enum RewriterError {
#[error("JS: {0}")]
Js(String),
#[error("URL parse error: {0}")]
Url(#[from] url::ParseError),
#[error("str fromutf8 error: {0}")]
Str(#[from] std::str::Utf8Error),
#[error("reflect set failed: {0}")]
ReflectSetFail(String),
#[error("{0} was not {1}")]
Not(String, &'static str),
#[error("❗❗❗ ❗❗❗ REWRITER OFFSET OOB FAIL ❗❗❗ ❗❗❗")]
Oob,
}
impl From<JsValue> for RewriterError {
fn from(value: JsValue) -> Self {
Self::Js(Error::from(value).to_string().into())
}
}
impl From<RewriterError> for JsValue {
fn from(value: RewriterError) -> Self {
JsError::from(value).into()
}
}
impl RewriterError {
pub fn not_str(x: &str) -> Self {
Self::Not(x.to_string(), "string")
}
pub fn not_fn(x: &str) -> Self {
Self::Not(x.to_string(), "function")
}
pub fn not_bool(x: &str) -> Self {
Self::Not(x.to_string(), "bool")
}
}
pub type Result<T> = std::result::Result<T, RewriterError>;

View file

@ -1,179 +0,0 @@
pub mod error;
pub mod rewrite;
use std::{str::FromStr, sync::Arc, time::Duration};
use error::{Result, RewriterError};
use instant::Instant;
use js_sys::{Function, Object, Reflect};
use oxc::diagnostics::{NamedSource, OxcDiagnostic};
use rewrite::{rewrite, Config, EncodeFn};
use url::Url;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(typescript_custom_section)]
const REWRITER_OUTPUT: &'static str = r#"
type RewriterOutput = { js: Uint8Array, errors: string[], duration: bigint };
"#;
#[wasm_bindgen(inline_js = r#"
// slightly modified https://github.com/ungap/random-uuid/blob/main/index.js
export function scramtag() {
return (""+1e10).replace(/[018]/g,
c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
"#)]
extern "C" {
pub fn scramtag() -> String;
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "RewriterOutput")]
pub type RewriterOutput;
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn error(s: &str);
}
fn create_encode_function(encode: JsValue) -> Result<EncodeFn> {
let encode = encode.dyn_into::<Function>()?;
Ok(Box::new(move |str| {
encode
.call1(&JsValue::NULL, &str.into())
.unwrap()
.as_string()
.unwrap()
.to_string()
}))
}
fn get_obj(obj: &JsValue, k: &str) -> Result<JsValue> {
Ok(Reflect::get(obj, &k.into())?)
}
fn get_str(obj: &JsValue, k: &str) -> Result<String> {
Reflect::get(obj, &k.into())?
.as_string()
.ok_or_else(|| RewriterError::not_str(k))
}
fn set_obj(obj: &Object, k: &str, v: &JsValue) -> Result<()> {
if !Reflect::set(&obj.into(), &k.into(), v)? {
Err(RewriterError::ReflectSetFail(k.to_string()))
} else {
Ok(())
}
}
fn get_flag(scramjet: &Object, url: &str, flag: &str) -> Result<bool> {
let fenabled = get_obj(scramjet, "flagEnabled")?
.dyn_into::<Function>()
.map_err(|_| RewriterError::not_fn("scramjet.flagEnabled"))?;
let ret = fenabled.call2(
&JsValue::NULL,
&flag.into(),
&web_sys::Url::new(url)?.into(),
)?;
ret.as_bool().ok_or_else(|| RewriterError::not_bool("scramjet.flagEnabled return value"))
}
fn get_config(scramjet: &Object, url: &str) -> Result<Config> {
let codec = &get_obj(scramjet, "codec")?;
let config = &get_obj(scramjet, "config")?;
let globals = &get_obj(config, "globals")?;
Ok(Config {
prefix: get_str(config, "prefix")?,
encode: create_encode_function(get_obj(codec, "encode")?)?,
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")?,
setrealmfn: get_str(globals, "setrealmfn")?,
pushsourcemapfn: get_str(globals, "pushsourcemapfn")?,
do_sourcemaps: get_flag(scramjet, url, "sourcemaps")?,
capture_errors: get_flag(scramjet, url, "captureErrors")?,
scramitize: get_flag(scramjet, url, "scramitize")?,
strict_rewrites: get_flag(scramjet, url, "strictRewrites")?,
})
}
fn duration_to_millis_f64(duration: Duration) -> f64 {
(duration.as_secs() as f64) * 1_000f64 + (duration.subsec_nanos() as f64) / 1_000_000f64
}
fn create_rewriter_output(
out: (Vec<u8>, Vec<OxcDiagnostic>),
url: String,
src: String,
duration: Duration,
) -> Result<RewriterOutput> {
let src = Arc::new(NamedSource::new(url, src).with_language("javascript"));
#[cfg(feature = "debug")]
let errs: Vec<_> = out
.1
.into_iter()
.map(|x| format!("{}", x.with_source_code(src.clone())))
.collect();
let obj = Object::new();
set_obj(&obj, "js", &out.0.into())?;
#[cfg(feature = "debug")]
set_obj(&obj, "errors", &errs.into())?;
#[cfg(not(feature = "debug"))]
set_obj(&obj, "errors", &js_sys::Array::new())?;
set_obj(&obj, "duration", &duration_to_millis_f64(duration).into())?;
Ok(RewriterOutput::from(JsValue::from(obj)))
}
#[wasm_bindgen]
pub fn rewrite_js(
js: String,
url: &str,
script_url: String,
scramjet: &Object,
) -> Result<RewriterOutput> {
let before = Instant::now();
let out = rewrite(
&js,
Url::from_str(url)?,
scramtag(),
get_config(scramjet, url)?,
)?;
let after = Instant::now();
create_rewriter_output(out, script_url, js, after - before)
}
#[wasm_bindgen]
pub fn rewrite_js_from_arraybuffer(
js: Vec<u8>,
url: &str,
script_url: String,
scramjet: &Object,
) -> Result<RewriterOutput> {
// we know that this is a valid utf-8 string
let js = unsafe { String::from_utf8_unchecked(js) };
let before = Instant::now();
let out = rewrite(
&js,
Url::from_str(url)?,
scramtag(),
get_config(scramjet, url)?,
)?;
let after = Instant::now();
create_rewriter_output(out, script_url, js, after - before)
}

View file

@ -1,255 +0,0 @@
#![allow(clippy::print_stdout)]
use std::{
borrow::Cow,
env,
path::Path,
str::{from_utf8, FromStr},
};
pub mod error;
pub mod rewrite;
use error::Result;
use rewrite::rewrite;
use url::Url;
use crate::rewrite::Config;
// Instruction:
// create a `test.js`,
// run `cargo run -p oxc_parser --example visitor`
// or `cargo watch -x "run -p oxc_parser --example visitor"`
/// Percent-encodes every byte except alphanumerics and `-`, `_`, `.`, `~`. Assumes UTF-8 encoding.
///
/// Call `.into_owned()` if you need a `String`
#[inline(always)]
#[must_use]
pub fn encode(data: &str) -> Cow<'_, str> {
encode_binary(data.as_bytes())
}
/// Percent-encodes every byte except alphanumerics and `-`, `_`, `.`, `~`.
#[inline]
#[must_use]
pub fn encode_binary(data: &[u8]) -> Cow<'_, str> {
// add maybe extra capacity, but try not to exceed allocator's bucket size
let mut escaped = String::new();
let _ = escaped.try_reserve(data.len() | 15);
let unmodified = append_string(data, &mut escaped, true);
if unmodified {
return Cow::Borrowed(unsafe {
// encode_into has checked it's ASCII
std::str::from_utf8_unchecked(data)
});
}
Cow::Owned(escaped)
}
fn append_string(data: &[u8], escaped: &mut String, may_skip: bool) -> bool {
encode_into(data, may_skip, |s| {
escaped.push_str(s);
Ok::<_, std::convert::Infallible>(())
})
.unwrap()
}
fn encode_into<E>(
mut data: &[u8],
may_skip_write: bool,
mut push_str: impl FnMut(&str) -> std::result::Result<(), E>,
) -> std::result::Result<bool, E> {
let mut pushed = false;
loop {
// Fast path to skip over safe chars at the beginning of the remaining string
let ascii_len = data
.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() {
if !pushed && may_skip_write {
return Ok(true);
}
(data, &[][..]) // redundatnt to optimize out a panic in split_at
} else {
data.split_at(ascii_len)
};
pushed = true;
if !safe.is_empty() {
push_str(unsafe { std::str::from_utf8_unchecked(safe) })?;
}
if rest.is_empty() {
break;
}
match rest.split_first() {
Some((byte, rest)) => {
let enc = &[b'%', to_hex_digit(byte >> 4), to_hex_digit(byte & 15)];
push_str(unsafe { std::str::from_utf8_unchecked(enc) })?;
data = rest;
}
None => break,
};
}
Ok(false)
}
#[inline]
fn to_hex_digit(digit: u8) -> u8 {
match digit {
0..=9 => b'0' + digit,
10..=255 => b'A' - 10 + digit,
}
}
fn encode_string(s: String) -> String {
encode(&s).to_string()
}
fn dorewrite(source_text: &str) -> Result<String> {
Ok(from_utf8(
rewrite(
source_text,
Url::from_str("https://google.com/glorngle/si.js").unwrap(),
"glongle1".to_string(),
Config {
prefix: "/scrammedjet/".to_string(),
encode: Box::new(encode_string),
wrapfn: "$wrap".to_string(),
wrapthisfn: "$gwrap".to_string(),
importfn: "$import".to_string(),
rewritefn: "$rewrite".to_string(),
metafn: "$meta".to_string(),
setrealmfn: "$setrealm".to_string(),
pushsourcemapfn: "$pushsourcemap".to_string(),
capture_errors: true,
do_sourcemaps: true,
scramitize: false,
strict_rewrites: true,
},
)?
.0
.as_slice(),
)?
.to_string())
}
fn main() -> std::io::Result<()> {
let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
let path = Path::new(&name);
let source_text = std::fs::read_to_string(path)?;
println!("{}", dorewrite(&source_text).unwrap());
Ok(())
}
#[cfg(test)]
mod tests {
use std::fs;
use boa_engine::{
js_str, js_string,
object::ObjectInitializer,
property::{Attribute, PropertyDescriptorBuilder},
Context, NativeFunction, Source,
};
use crate::dorewrite;
#[test]
fn google() {
// sanity check- just making sure it won't crash
let source_text = include_str!("../sample/google.js");
dorewrite(source_text).unwrap();
}
#[test]
fn test() {
let files = fs::read_dir("./tests").unwrap();
for file in files {
if !file
.as_ref()
.unwrap()
.file_name()
.to_str()
.unwrap()
.ends_with(".js")
{
continue;
}
let content = fs::read_to_string(file.unwrap().path()).unwrap();
let mut context = Context::default();
let window = ObjectInitializer::new(&mut context).build();
context
.register_global_property(js_str!("window"), window, Attribute::READONLY)
.unwrap();
context
.global_object()
.define_property_or_throw(
js_str!("location"),
PropertyDescriptorBuilder::new()
.get(
NativeFunction::from_copy_closure(|_, _, _| {
Ok(js_str!("location").into())
})
.to_js_function(context.realm()),
)
.set(
NativeFunction::from_copy_closure(|_, _, _| {
panic!("fail: window.location got set")
})
.to_js_function(context.realm()),
)
.build(),
&mut context,
)
.unwrap();
context
.register_global_callable(
js_string!("fail"),
0,
NativeFunction::from_copy_closure(|_, _, _| {
panic!("fail");
}),
)
.unwrap();
let result = context
.eval(Source::from_bytes(
br#"
function $wrap(val) {
if (val === window || val === "location" || val === globalThis) return "";
return val;
}
function assert(val) {
if (!val) fail();
}
function check(val) {
if (val === window || val === "location") fail();
}
"#,
))
.unwrap();
let rewritten = dorewrite(&content).unwrap();
println!("{}", rewritten);
context
.eval(Source::from_bytes(rewritten.as_bytes()))
.unwrap();
println!("PASS");
}
}
}

View file

@ -1,591 +0,0 @@
use std::str;
use indexset::BTreeSet;
use oxc::{
allocator::Allocator,
ast::{
ast::{
AssignmentExpression, AssignmentTarget, CallExpression, DebuggerStatement,
ExportAllDeclaration, ExportNamedDeclaration, Expression, ForInStatement,
ForOfStatement, FunctionBody, IdentifierReference, ImportDeclaration, ImportExpression,
MemberExpression, MetaProperty, NewExpression, ObjectExpression, ObjectPropertyKind,
ReturnStatement, ThisExpression, UnaryExpression, UpdateExpression,
},
visit::walk,
Visit,
},
diagnostics::OxcDiagnostic,
parser::{ParseOptions, Parser},
span::{Atom, GetSpan, SourceType, Span},
syntax::operator::{AssignmentOperator, UnaryOperator},
};
use url::Url;
use crate::error::{Result, RewriterError};
#[derive(Debug, PartialEq, Eq)]
enum JsChange {
GenericChange {
span: Span,
text: String,
},
SourceTag {
tagstart: u32,
},
Assignment {
name: String,
entirespan: Span,
rhsspan: Span,
op: AssignmentOperator,
},
}
impl JsChange {
fn inner_cmp(&self, other: &Self) -> std::cmp::Ordering {
let a = match self {
JsChange::GenericChange { span, text: _ } => span.start,
JsChange::Assignment {
name: _,
entirespan,
rhsspan: _,
op: _,
} => entirespan.start,
JsChange::SourceTag { tagstart } => *tagstart,
};
let b = match other {
JsChange::GenericChange { span, text: _ } => span.start,
JsChange::Assignment {
name: _,
entirespan,
rhsspan: _,
op: _,
} => entirespan.start,
JsChange::SourceTag { tagstart } => *tagstart,
};
a.cmp(&b)
}
}
impl PartialOrd for JsChange {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.inner_cmp(other))
}
}
impl Ord for JsChange {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.inner_cmp(other)
}
}
pub type EncodeFn = Box<dyn Fn(String) -> String>;
struct Rewriter {
jschanges: BTreeSet<JsChange>,
base: Url,
config: Config,
}
pub struct Config {
pub prefix: String,
pub wrapfn: String,
pub wrapthisfn: String,
pub importfn: String,
pub rewritefn: String,
pub setrealmfn: String,
pub metafn: String,
pub pushsourcemapfn: String,
pub encode: EncodeFn,
pub capture_errors: bool,
pub scramitize: bool,
pub do_sourcemaps: bool,
pub strict_rewrites: bool,
}
impl Rewriter {
fn rewrite_url(&mut self, url: String) -> String {
let url = self.base.join(&url).unwrap();
let urlencoded = (self.config.encode)(url.to_string());
format!("\"{}{}\"", self.config.prefix, urlencoded)
}
fn rewrite_ident(&mut self, name: &Atom, span: Span) {
if UNSAFE_GLOBALS.contains(&name.to_string().as_str()) {
self.jschanges.insert(JsChange::GenericChange {
span,
text: format!("({}({}))", self.config.wrapfn, name),
});
}
}
fn walk_member_expression(&mut self, it: &Expression) -> bool {
if 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,
} {
return true;
}
// TODO: WE SHOULD PROBABLY WALK THE REST OF THE TREE
// walk::walk_expression(self, it);
false
}
}
impl<'a> Visit<'a> for Rewriter {
fn visit_identifier_reference(&mut self, it: &IdentifierReference<'a>) {
// if self.config.capture_errors {
// self.jschanges.insert(JsChange::GenericChange {
// span: it.span,
// text: format!(
// "{}({}, typeof arguments != 'undefined' && arguments)",
// self.config.wrapfn, it.name
// ),
// });
// } else {
if UNSAFE_GLOBALS.contains(&it.name.to_string().as_str()) {
self.jschanges.insert(JsChange::GenericChange {
span: it.span,
text: format!("{}({})", self.config.wrapfn, it.name),
});
}
// }
}
// we need to rewrite `new Something` to `new (wrapfn(Something))` instead of `new wrapfn(Something)`, that's why there's weird extra code here
fn visit_new_expression(&mut self, it: &NewExpression<'a>) {
self.walk_member_expression(&it.callee);
walk::walk_arguments(self, &it.arguments);
}
fn visit_member_expression(&mut self, it: &MemberExpression<'a>) {
match it {
MemberExpression::StaticMemberExpression(s) => {
if s.property.name == "postMessage" {
self.jschanges.insert(JsChange::GenericChange {
span: s.property.span,
// an empty object will let us safely reconstruct the realm later
text: format!("{}({{}}).{}", self.config.setrealmfn, s.property.name),
});
walk::walk_expression(self, &s.object);
return; // unwise to walk the rest of the tree
}
if !self.config.strict_rewrites
&& !UNSAFE_GLOBALS.contains(&s.property.name.as_str())
{
if let Expression::Identifier(_) = &s.object {
// cull tree - this should be safe
return;
}
if let Expression::ThisExpression(_) = &s.object {
return;
}
}
if self.config.scramitize
&& !matches!(s.object, Expression::MetaProperty(_))
&& !matches!(s.object, Expression::Super(_))
{
let span = s.object.span();
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(span.start, span.start),
text: " $scramitize(".to_string(),
});
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(span.end, span.end),
text: ")".to_string(),
});
}
}
_ => {
// 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
}
}
walk::walk_member_expression(self, it);
}
fn visit_this_expression(&mut self, it: &ThisExpression) {
self.jschanges.insert(JsChange::GenericChange {
span: it.span,
text: format!("{}(this)", self.config.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!
self.jschanges.insert(JsChange::GenericChange {
span: it.span,
text: "".to_string(),
});
}
// 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
fn visit_call_expression(&mut self, it: &CallExpression<'a>) {
if let Expression::Identifier(s) = &it.callee {
// if it's optional that actually makes it an indirect eval which is handled separately
if s.name == "eval" && !it.optional {
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(s.span.start, s.span.end + 1),
text: format!("eval({}(", self.config.rewritefn),
});
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(it.span.end, it.span.end),
text: ")".to_string(),
});
// then we walk the arguments, but not the callee, since we want it to resolve to
// the real eval
walk::walk_arguments(self, &it.arguments);
return;
}
}
if self.config.scramitize {
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(it.span.start, it.span.start),
text: " $scramitize(".to_string(),
});
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(it.span.end, it.span.end),
text: ")".to_string(),
});
}
walk::walk_call_expression(self, it);
}
fn visit_import_declaration(&mut self, it: &ImportDeclaration<'a>) {
let name = it.source.value.to_string();
let text = self.rewrite_url(name);
self.jschanges.insert(JsChange::GenericChange {
span: it.source.span,
text,
});
walk::walk_import_declaration(self, it);
}
fn visit_import_expression(&mut self, it: &ImportExpression<'a>) {
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(it.span.start, it.span.start + 6),
text: format!("({}(\"{}\"))", self.config.importfn, self.base),
});
walk::walk_import_expression(self, it);
}
fn visit_export_all_declaration(&mut self, it: &ExportAllDeclaration<'a>) {
let name = it.source.value.to_string();
let text = self.rewrite_url(name);
self.jschanges.insert(JsChange::GenericChange {
span: it.source.span,
text,
});
}
fn visit_export_named_declaration(&mut self, it: &ExportNamedDeclaration<'a>) {
if let Some(source) = &it.source {
let name = source.value.to_string();
let text = self.rewrite_url(name);
self.jschanges.insert(JsChange::GenericChange {
span: source.span,
text,
});
}
// do not walk further, we don't want to rewrite the identifiers
}
#[cfg(feature = "debug")]
fn visit_try_statement(&mut self, it: &oxc::ast::ast::TryStatement<'a>) {
// for debugging we need to know what the error was
if self.config.capture_errors {
if let Some(h) = &it.handler {
if let Some(name) = &h.param {
if let Some(name) = name.pattern.get_identifier() {
self.jschanges.insert(JsChange::GenericChange {
span: Span::new(h.body.span.start + 1, h.body.span.start + 1),
text: format!("$scramerr({});", name),
});
}
}
}
}
walk::walk_try_statement(self, it);
}
fn visit_object_expression(&mut self, it: &ObjectExpression<'a>) {
for prop in &it.properties {
#[allow(clippy::single_match)]
match prop {
ObjectPropertyKind::ObjectProperty(p) => match &p.value {
Expression::Identifier(s) => {
if UNSAFE_GLOBALS.contains(&s.name.to_string().as_str()) && p.shorthand {
self.jschanges.insert(JsChange::GenericChange {
span: s.span,
text: format!("{}: ({}({}))", s.name, self.config.wrapfn, s.name),
});
return;
}
}
_ => {}
},
_ => {}
}
}
walk::walk_object_expression(self, it);
}
fn visit_function_body(&mut self, it: &FunctionBody<'a>) {
// tag function for use in sourcemaps
if self.config.do_sourcemaps {
self.jschanges.insert(JsChange::SourceTag {
tagstart: it.span.start,
});
}
walk::walk_function_body(self, it);
}
fn visit_return_statement(&mut self, it: &ReturnStatement<'a>) {
// if let Some(arg) = &it.argument {
// self.jschanges.insert(JsChange::GenericChange {
// span: Span::new(it.span.start + 6, it.span.start + 6),
// text: format!(" $scramdbg((()=>{{ try {{return arguments}} catch(_){{}} }})(),("),
// });
// self.jschanges.insert(JsChange::GenericChange {
// span: Span::new(expression_span(arg).end, expression_span(arg).end),
// text: format!("))"),
// });
// }
walk::walk_return_statement(self, it);
}
fn visit_unary_expression(&mut self, it: &UnaryExpression<'a>) {
if matches!(it.operator, UnaryOperator::Typeof) {
// don't walk to identifier rewrites since it won't matter
return;
}
walk::walk_unary_expression(self, it);
}
// we don't want to rewrite the identifiers here because of a very specific edge case
fn visit_for_in_statement(&mut self, it: &ForInStatement<'a>) {
walk::walk_statement(self, &it.body);
}
fn visit_for_of_statement(&mut self, it: &ForOfStatement<'a>) {
walk::walk_statement(self, &it.body);
}
fn visit_update_expression(&mut self, _it: &UpdateExpression<'a>) {
// then no, don't walk it, we don't care
}
fn visit_meta_property(&mut self, it: &MetaProperty<'a>) {
if it.meta.name == "import" {
self.jschanges.insert(JsChange::GenericChange {
span: it.span,
text: format!("{}(\"{}\")", self.config.metafn, self.base),
});
}
}
fn visit_assignment_expression(&mut self, it: &AssignmentExpression<'a>) {
#[allow(clippy::single_match)]
match &it.left {
AssignmentTarget::AssignmentTargetIdentifier(s) => {
if ["location"].contains(&s.name.to_string().as_str()) {
self.jschanges.insert(JsChange::Assignment {
name: s.name.to_string(),
entirespan: it.span,
rhsspan: it.right.span(),
op: it.operator,
});
// avoid walking rest of tree, i would need to figure out nested rewrites
// somehow
return;
}
}
AssignmentTarget::ArrayAssignmentTarget(_) => {
// [location] = ["https://example.com"]
// this is such a ridiculously specific edge case. just ignore it
return;
}
_ => {
// only walk the left side if it isn't an identifier, we can't replace the
// identifier with a function obviously
walk::walk_assignment_target(self, &it.left);
}
}
walk::walk_expression(self, &it.right);
}
}
// js MUST not be able to get a reference to any of these because sbx
const UNSAFE_GLOBALS: &[&str] = &[
"window",
"self",
"globalThis",
"this",
"parent",
"top",
"location",
"document",
"eval",
"frames",
];
pub fn rewrite(
js: &str,
url: Url,
sourcetag: String,
config: Config,
) -> Result<(Vec<u8>, Vec<OxcDiagnostic>)> {
let allocator = Allocator::default();
let source_type = SourceType::default();
let ret = Parser::new(&allocator, js, source_type)
.with_options(ParseOptions {
parse_regular_expression: false, // default
allow_return_outside_function: true,
preserve_parens: true, // default
})
.parse();
let program = ret.program;
let mut ast_pass = Rewriter {
jschanges: BTreeSet::new(),
base: url,
config,
};
ast_pass.visit_program(&program);
let original_len = js.len();
let mut difference = 0i32;
for change in &ast_pass.jschanges {
match &change {
JsChange::GenericChange { span, text } => {
difference += text.len() as i32 - (span.end - span.start) as i32;
}
JsChange::Assignment {
name,
entirespan,
rhsspan: _,
op: _,
} => difference += entirespan.size() as i32 + name.len() as i32 + 10,
_ => {}
}
}
let size_estimate = (original_len as i32 + difference) as usize;
let mut buffer: Vec<u8> = Vec::with_capacity(size_estimate);
let mut sourcemap: Vec<u8> = Vec::new();
if ast_pass.config.do_sourcemaps {
sourcemap.reserve(size_estimate * 2);
sourcemap.extend_from_slice(format!("{}([", ast_pass.config.pushsourcemapfn).as_bytes());
}
let mut offset = 0;
for change in ast_pass.jschanges {
match &change {
JsChange::GenericChange { span, text } => {
let start = span.start as usize;
let end = span.end as usize;
if ast_pass.config.do_sourcemaps {
let spliced = &js[start..end];
sourcemap.extend_from_slice(
format!(
"[\"{}\",{},{}],",
json_escape_string(spliced),
start,
start + text.len()
)
.as_bytes(),
);
}
buffer
.extend_from_slice(js.get(offset..start).ok_or(RewriterError::Oob)?.as_bytes());
buffer.extend_from_slice(text.as_bytes());
offset = end;
}
JsChange::Assignment {
name,
entirespan,
rhsspan,
op,
} => {
let start = entirespan.start as usize;
buffer.extend_from_slice(js[offset..start].as_bytes());
buffer.extend_from_slice(
format!(
"((t)=>$scramjet$tryset({},\"{}\",t)||({}{}t))({})",
name,
op.as_str(),
name,
op.as_str(),
&js[rhsspan.start as usize..rhsspan.end as usize]
)
.as_bytes(),
);
offset = entirespan.end as usize;
}
JsChange::SourceTag { tagstart } => {
let start = *tagstart as usize;
buffer
.extend_from_slice(js.get(offset..start).ok_or(RewriterError::Oob)?.as_bytes());
let inject = format!("/*scramtag {} {}*/", start, sourcetag);
buffer.extend_from_slice(inject.as_bytes());
offset = start;
}
}
}
buffer.extend_from_slice(js[offset..].as_bytes());
if ast_pass.config.do_sourcemaps {
sourcemap.extend_from_slice(b"],");
sourcemap.extend_from_slice(b"\"");
sourcemap.extend_from_slice(sourcetag.as_bytes());
sourcemap.extend_from_slice(b"\");\n");
sourcemap.extend_from_slice(&buffer);
return Ok((sourcemap, ret.errors));
}
Ok((buffer, ret.errors))
}
fn json_escape_string(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\x08' => out.push_str("\\b"),
'\x0C' => out.push_str("\\f"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
_ => out.push(c),
}
}
out
}

View file

@ -1,5 +0,0 @@
check(window);
check(this);
check(globalThis);
check(location);
check(globalThis["win" + "dow"])

View file

@ -1,3 +0,0 @@
const { location: x } = globalThis;
check(x);

View file

@ -1,6 +0,0 @@
function f(g = globalThis, l = location) {
check(g);
check(l);
}
f();

View file

@ -1,7 +0,0 @@
function f(location, globalThis) {
assert(location === 1)
assert(globalThis === 2)
}
f(1, 2);

View file

@ -1,8 +0,0 @@
let reached = false
let eval = (t) => t === "location = 1" && (reached = true)
// testing to make sure this doesn't get rewritten
eval("location = 1")
if (!reached) fail();

View file

@ -1,3 +0,0 @@
for (location of ["https://google.com"]) {
//
}

21
rewriter/wasm/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
instant = { version = "0.1.13", features = ["wasm-bindgen"] }
js-sys = "0.3.76"
oxc = { workspace = true }
rewriter = { version = "0.1.0", path = "../rewriter" }
thiserror = "2.0.6"
url = "2.5.4"
wasm-bindgen = "0.2.99"
web-sys = { version = "0.3.76", features = ["Url"] }
[features]
default = ["debug"]
debug = ["rewriter/debug"]

14
rewriter/build.sh → rewriter/wasm/build.sh Executable file → Normal file
View file

@ -8,7 +8,7 @@ which cargo wasm-bindgen wasm-opt &> /dev/null || {
exit 1
}
WBG="wasm-bindgen 0.2.95"
WBG="wasm-bindgen 0.2.99"
if ! [[ "$(wasm-bindgen -V)" =~ ^"$WBG" ]]; then
echo "Incorrect wasm-bindgen-cli version: '$(wasm-bindgen -V)' != '$WBG'"
exit 1
@ -23,16 +23,14 @@ else
fi
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+simd128 -Zlocation-detail=none -Zfmt-debug=none' cargo build --lib --target wasm32-unknown-unknown -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort --no-default-features --features "$FEATURES" --release
wasm-bindgen --target web --out-dir out/ target/wasm32-unknown-unknown/release/rewriter.wasm
wasm-bindgen --target web --out-dir out/ ../target/wasm32-unknown-unknown/release/wasm.wasm
sed -i 's/import.meta.url/""/g' out/rewriter.js
sed -i 's/import.meta.url/""/g' out/wasm.js
cd ..
WASM=rewriter/out/rewriter_bg.wasm
cd ../../
# shellcheck disable=SC2086
time wasm-opt $WASMOPTFLAGS --converge -tnh -O4 --vacuum --dce --enable-threads --enable-bulk-memory --enable-simd "$WASM" -o rewriter/out/optimized.wasm
time wasm-opt $WASMOPTFLAGS --converge -tnh -O4 --vacuum --dce --enable-threads --enable-bulk-memory --enable-simd rewriter/wasm/out/wasm_bg.wasm -o rewriter/wasm/out/optimized.wasm
mkdir dist/ || true
@ -44,7 +42,7 @@ if ("document" in self && document?.currentScript) {
}
EOF
echo -n "self.WASM = '"
base64 -w0 < "rewriter/out/optimized.wasm"
base64 -w0 < "rewriter/wasm/out/optimized.wasm"
echo -n "';"
} > dist/scramjet.wasm.js

Binary file not shown.

View file

@ -0,0 +1,7 @@
// slightly modified https://github.com/ungap/random-uuid/blob/main/index.js
export function scramtag() {
return (""+1e10).replace(/[018]/g,
c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}

48
rewriter/wasm/out/wasm.d.ts vendored Normal file
View file

@ -0,0 +1,48 @@
/* tslint:disable */
/* eslint-disable */
export function rewrite_js(js: string, url: string, script_url: string, scramjet: object): RewriterOutput;
export function rewrite_js_from_arraybuffer(js: Uint8Array, url: string, script_url: string, scramjet: object): RewriterOutput;
type RewriterOutput = { js: Uint8Array, errors: string[], duration: bigint };
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly rewrite_js: (a: number, b: number, c: number, d: number, e: number, f: number, g: any) => [number, number, number];
readonly rewrite_js_from_arraybuffer: (a: number, b: number, c: number, d: number, e: number, f: number, g: any) => [number, number, number];
readonly __wbindgen_exn_store: (a: number) => void;
readonly __externref_table_alloc: () => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly memory: WebAssembly.Memory;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __externref_table_dealloc: (a: number) => void;
readonly __wbindgen_thread_destroy: (a?: number, b?: number, c?: number) => void;
readonly __wbindgen_start: (a: number) => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {{ module: SyncInitInput, memory?: WebAssembly.Memory, thread_stack_size?: number }} module - Passing `SyncInitInput` directly is deprecated.
* @param {WebAssembly.Memory} memory - Deprecated.
*
* @returns {InitOutput}
*/
export function initSync(module: { module: SyncInitInput, memory?: WebAssembly.Memory, thread_stack_size?: number } | SyncInitInput, memory?: WebAssembly.Memory): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {{ module_or_path: InitInput | Promise<InitInput>, memory?: WebAssembly.Memory, thread_stack_size?: number }} module_or_path - Passing `InitInput` directly is deprecated.
* @param {WebAssembly.Memory} memory - Deprecated.
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput>, memory?: WebAssembly.Memory, thread_stack_size?: number } | InitInput | Promise<InitInput>, memory?: WebAssembly.Memory): Promise<InitOutput>;

390
rewriter/wasm/out/wasm.js Normal file
View file

@ -0,0 +1,390 @@
import { scramtag } from './snippets/wasm-4b0f351a8e6eeb46/inline0.js';
let wasm;
function addToExternrefTable0(obj) {
const idx = wasm.__externref_table_alloc();
wasm.__wbindgen_export_2.set(idx, obj);
return idx;
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
const idx = addToExternrefTable0(e);
wasm.__wbindgen_exn_store(idx);
}
}
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.buffer !== wasm.memory.buffer) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8ArrayMemory0().slice(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
const encodeString = function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
};
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
let cachedDataViewMemory0 = null;
function getDataViewMemory0() {
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer !== wasm.memory.buffer) {
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
}
return cachedDataViewMemory0;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
function getArrayU8FromWasm0(ptr, len) {
ptr = ptr >>> 0;
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
}
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_export_2.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
/**
* @param {string} js
* @param {string} url
* @param {string} script_url
* @param {object} scramjet
* @returns {RewriterOutput}
*/
export function rewrite_js(js, url, script_url, scramjet) {
const ptr0 = passStringToWasm0(js, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(script_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len2 = WASM_VECTOR_LEN;
const ret = wasm.rewrite_js(ptr0, len0, ptr1, len1, ptr2, len2, scramjet);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8ArrayMemory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
/**
* @param {Uint8Array} js
* @param {string} url
* @param {string} script_url
* @param {object} scramjet
* @returns {RewriterOutput}
*/
export function rewrite_js_from_arraybuffer(js, url, script_url, scramjet) {
const ptr0 = passArray8ToWasm0(js, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(script_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len2 = WASM_VECTOR_LEN;
const ret = wasm.rewrite_js_from_arraybuffer(ptr0, len0, ptr1, len1, ptr2, len2, scramjet);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_call_3b770f0d6eb4720e = function() { return handleError(function (arg0, arg1, arg2, arg3) {
const ret = arg0.call(arg1, arg2, arg3);
return ret;
}, arguments) };
imports.wbg.__wbg_call_500db948e69c7330 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = arg0.call(arg1, arg2);
return ret;
}, arguments) };
imports.wbg.__wbg_call_b0d8e36992d9900d = function() { return handleError(function (arg0, arg1) {
const ret = arg0.call(arg1);
return ret;
}, arguments) };
imports.wbg.__wbg_get_bbccf8970793c087 = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.get(arg0, arg1);
return ret;
}, arguments) };
imports.wbg.__wbg_new_17f755666e48d1d8 = function() { return handleError(function (arg0, arg1) {
const ret = new URL(getStringFromWasm0(arg0, arg1));
return ret;
}, arguments) };
imports.wbg.__wbg_new_688846f374351c92 = function() {
const ret = new Object();
return ret;
};
imports.wbg.__wbg_newnoargs_fd9e4bf8be2bc16d = function(arg0, arg1) {
const ret = new Function(getStringFromWasm0(arg0, arg1));
return ret;
};
imports.wbg.__wbg_now_62a101fe35b60230 = function(arg0) {
const ret = arg0.now();
return ret;
};
imports.wbg.__wbg_scramtag_bd98edaa0eaec45e = function(arg0) {
const ret = scramtag();
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
};
imports.wbg.__wbg_set_4e647025551483bd = function() { return handleError(function (arg0, arg1, arg2) {
const ret = Reflect.set(arg0, arg1, arg2);
return ret;
}, arguments) };
imports.wbg.__wbg_static_accessor_GLOBAL_0be7472e492ad3e3 = function() {
const ret = typeof global === 'undefined' ? null : global;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_static_accessor_GLOBAL_THIS_1a6eb482d12c9bfb = function() {
const ret = typeof globalThis === 'undefined' ? null : globalThis;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_static_accessor_SELF_1dc398a895c82351 = function() {
const ret = typeof self === 'undefined' ? null : self;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_static_accessor_WINDOW_ae1c80c7eea8d64a = function() {
const ret = typeof window === 'undefined' ? null : window;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_toString_cbcf95f260c441ae = function(arg0) {
const ret = arg0.toString();
return ret;
};
imports.wbg.__wbindgen_array_new = function() {
const ret = [];
return ret;
};
imports.wbg.__wbindgen_array_push = function(arg0, arg1) {
arg0.push(arg1);
};
imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = arg0;
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
return ret;
};
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1));
return ret;
};
imports.wbg.__wbindgen_init_externref_table = function() {
const table = wasm.__wbindgen_export_2;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
;
};
imports.wbg.__wbindgen_is_function = function(arg0) {
const ret = typeof(arg0) === 'function';
return ret;
};
imports.wbg.__wbindgen_is_undefined = function(arg0) {
const ret = arg0 === undefined;
return ret;
};
imports.wbg.__wbindgen_number_new = function(arg0) {
const ret = arg0;
return ret;
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = arg1;
const ret = typeof(obj) === 'string' ? obj : undefined;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return ret;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_uint8_array_new = function(arg0, arg1) {
var v0 = getArrayU8FromWasm0(arg0, arg1).slice();
wasm.__wbindgen_free(arg0, arg1 * 1, 1);
const ret = v0;
return ret;
};
return imports;
}
function __wbg_init_memory(imports, memory) {
imports.wbg.memory = memory || new WebAssembly.Memory({initial:21,maximum:16384,shared:true});
}
function __wbg_finalize_init(instance, module, thread_stack_size) {
wasm = instance.exports;
__wbg_init.__wbindgen_wasm_module = module;
cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null;
if (typeof thread_stack_size !== 'undefined' && (typeof thread_stack_size !== 'number' || thread_stack_size === 0 || thread_stack_size % 65536 !== 0)) { throw 'invalid stack size' }
wasm.__wbindgen_start(thread_stack_size);
return wasm;
}
function initSync(module, memory) {
if (wasm !== undefined) return wasm;
let thread_stack_size
if (typeof module !== 'undefined') {
if (Object.getPrototypeOf(module) === Object.prototype) {
({module, memory, thread_stack_size} = module)
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
}
}
const imports = __wbg_get_imports();
__wbg_init_memory(imports, memory);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module, thread_stack_size);
}
async function __wbg_init(module_or_path, memory) {
if (wasm !== undefined) return wasm;
let thread_stack_size
if (typeof module_or_path !== 'undefined') {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({module_or_path, memory, thread_stack_size} = module_or_path)
} else {
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
}
}
if (typeof module_or_path === 'undefined') {
module_or_path = new URL('wasm_bg.wasm', "");
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
module_or_path = fetch(module_or_path);
}
__wbg_init_memory(imports, memory);
const { instance, module } = await __wbg_load(await module_or_path, imports);
return __wbg_finalize_init(instance, module, thread_stack_size);
}
export { initSync };
export default __wbg_init;

Binary file not shown.

14
rewriter/wasm/out/wasm_bg.wasm.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
/* tslint:disable */
/* eslint-disable */
export const rewrite_js: (a: number, b: number, c: number, d: number, e: number, f: number, g: any) => [number, number, number];
export const rewrite_js_from_arraybuffer: (a: number, b: number, c: number, d: number, e: number, f: number, g: any) => [number, number, number];
export const __wbindgen_exn_store: (a: number) => void;
export const __externref_table_alloc: () => number;
export const __wbindgen_export_2: WebAssembly.Table;
export const memory: WebAssembly.Memory;
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __externref_table_dealloc: (a: number) => void;
export const __wbindgen_thread_destroy: (a?: number, b?: number, c?: number) => void;
export const __wbindgen_start: (a: number) => void;

View file

@ -0,0 +1,49 @@
use js_sys::Error;
use rewriter::RewriterError as InnerRewriterError;
use thiserror::Error;
use wasm_bindgen::{JsError, JsValue};
#[derive(Debug, Error)]
pub enum RewriterError {
#[error("JS: {0}")]
Js(String),
#[error("URL parse error: {0}")]
Url(#[from] url::ParseError),
#[error("str fromutf8 error: {0}")]
Str(#[from] std::str::Utf8Error),
#[error("Rewriter: {0}")]
Rewriter(#[from] InnerRewriterError),
#[error("reflect set failed: {0}")]
ReflectSetFail(String),
#[error("{0} was not {1}")]
Not(String, &'static str),
}
impl From<JsValue> for RewriterError {
fn from(value: JsValue) -> Self {
Self::Js(Error::from(value).to_string().into())
}
}
impl From<RewriterError> for JsValue {
fn from(value: RewriterError) -> Self {
JsError::from(value).into()
}
}
impl RewriterError {
pub fn not_str(x: &str) -> Self {
Self::Not(x.to_string(), "string")
}
pub fn not_fn(x: &str) -> Self {
Self::Not(x.to_string(), "function")
}
pub fn not_bool(x: &str) -> Self {
Self::Not(x.to_string(), "bool")
}
}
pub type Result<T> = std::result::Result<T, RewriterError>;

174
rewriter/wasm/src/lib.rs Normal file
View file

@ -0,0 +1,174 @@
pub mod error;
use std::{str::FromStr, sync::Arc, time::Duration};
use error::{Result, RewriterError};
use instant::Instant;
use js_sys::{Function, Object, Reflect};
use oxc::diagnostics::NamedSource;
use rewriter::{
cfg::{Config, EncodeFn},
rewrite, RewriteResult,
};
use url::Url;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(typescript_custom_section)]
const REWRITER_OUTPUT: &'static str = r#"
type RewriterOutput = { js: Uint8Array, errors: string[], duration: bigint };
"#;
#[wasm_bindgen(inline_js = r#"
// slightly modified https://github.com/ungap/random-uuid/blob/main/index.js
export function scramtag() {
return (""+1e10).replace(/[018]/g,
c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
"#)]
extern "C" {
pub fn scramtag() -> String;
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "RewriterOutput")]
pub type JsRewriterOutput;
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn error(s: &str);
}
fn create_encode_function(encode: JsValue) -> Result<EncodeFn> {
let encode = encode.dyn_into::<Function>()?;
Ok(Box::new(move |str| {
encode
.call1(&JsValue::NULL, &str.into())
.unwrap()
.as_string()
.unwrap()
.to_string()
}))
}
fn get_obj(obj: &JsValue, k: &str) -> Result<JsValue> {
Ok(Reflect::get(obj, &k.into())?)
}
fn get_str(obj: &JsValue, k: &str) -> Result<String> {
Reflect::get(obj, &k.into())?
.as_string()
.ok_or_else(|| RewriterError::not_str(k))
}
fn set_obj(obj: &Object, k: &str, v: &JsValue) -> Result<()> {
if !Reflect::set(&obj.into(), &k.into(), v)? {
Err(RewriterError::ReflectSetFail(k.to_string()))
} else {
Ok(())
}
}
fn get_flag(scramjet: &Object, url: &str, flag: &str) -> Result<bool> {
let fenabled = get_obj(scramjet, "flagEnabled")?
.dyn_into::<Function>()
.map_err(|_| RewriterError::not_fn("scramjet.flagEnabled"))?;
let ret = fenabled.call2(
&JsValue::NULL,
&flag.into(),
&web_sys::Url::new(url)?.into(),
)?;
ret.as_bool()
.ok_or_else(|| RewriterError::not_bool("scramjet.flagEnabled return value"))
}
fn get_config(scramjet: &Object, url: &str) -> Result<Config> {
let codec = &get_obj(scramjet, "codec")?;
let config = &get_obj(scramjet, "config")?;
let globals = &get_obj(config, "globals")?;
Ok(Config {
prefix: get_str(config, "prefix")?,
encoder: create_encode_function(get_obj(codec, "encode")?)?,
base: Url::from_str(url)?,
sourcetag: scramtag(),
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")?,
setrealmfn: get_str(globals, "setrealmfn")?,
pushsourcemapfn: get_str(globals, "pushsourcemapfn")?,
do_sourcemaps: get_flag(scramjet, url, "sourcemaps")?,
capture_errors: get_flag(scramjet, url, "captureErrors")?,
scramitize: get_flag(scramjet, url, "scramitize")?,
strict_rewrites: get_flag(scramjet, url, "strictRewrites")?,
})
}
fn duration_to_millis_f64(duration: Duration) -> f64 {
(duration.as_secs() as f64) * 1_000f64 + (duration.subsec_nanos() as f64) / 1_000_000f64
}
fn create_rewriter_output(
out: RewriteResult,
url: String,
src: String,
duration: Duration,
) -> Result<JsRewriterOutput> {
let src = Arc::new(NamedSource::new(url, src).with_language("javascript"));
#[cfg(feature = "debug")]
let errs: Vec<_> = out
.errors
.into_iter()
.map(|x| format!("{}", x.with_source_code(src.clone())))
.collect();
let obj = Object::new();
set_obj(&obj, "js", &out.js.into())?;
#[cfg(feature = "debug")]
set_obj(&obj, "errors", &errs.into())?;
#[cfg(not(feature = "debug"))]
set_obj(&obj, "errors", &js_sys::Array::new())?;
set_obj(&obj, "duration", &duration_to_millis_f64(duration).into())?;
Ok(JsRewriterOutput::from(JsValue::from(obj)))
}
#[wasm_bindgen]
pub fn rewrite_js(
js: String,
url: &str,
script_url: String,
scramjet: &Object,
) -> Result<JsRewriterOutput> {
let before = Instant::now();
let out = rewrite(&js, get_config(scramjet, url)?)?;
let after = Instant::now();
create_rewriter_output(out, script_url, js, after - before)
}
#[wasm_bindgen]
pub fn rewrite_js_from_arraybuffer(
js: Vec<u8>,
url: &str,
script_url: String,
scramjet: &Object,
) -> Result<JsRewriterOutput> {
// we know that this is a valid utf-8 string
let js = unsafe { String::from_utf8_unchecked(js) };
let before = Instant::now();
let out = rewrite(&js, get_config(scramjet, url)?)?;
let after = Instant::now();
create_rewriter_output(out, script_url, js, after - before)
}

View file

@ -6,7 +6,7 @@ import {
rewrite_js,
rewrite_js_from_arraybuffer,
RewriterOutput,
} from "../../../rewriter/out/rewriter.js";
} from "../../../rewriter/wasm/out/wasm.js";
import { $scramjet, flagEnabled } from "../../scramjet";
initSync({