soiz1's picture
Upload 150 files
bee6636 verified
use std::{collections::HashMap, error::Error};
use html::{Rewriter, VisitorExternalTool, rule::RewriteRule};
use js_sys::{Array, Function, Object, Uint8Array};
use oxc::allocator::Allocator;
use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen};
use crate::{
error::{Result, RewriterError},
get_obj, set_obj,
};
#[wasm_bindgen(typescript_custom_section)]
const REWRITER_OUTPUT: &'static str = r#"
export type HtmlRewriterOutput = {
html: Uint8Array,
};
"#;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "HtmlRewriterOutput")]
pub type HtmlRewriterOutput;
}
pub type HtmlRewriter = Rewriter<(Object, Object, Function, Function, Function, Function)>;
fn build_rules(
rules: Vec<Object>,
) -> Result<Vec<RewriteRule<(Object, Object, Function, Function, Function, Function)>>> {
rules
.into_iter()
.map(|x| {
let entries = Object::entries(&x)
.to_vec()
.into_iter()
.map(|x| {
let arr = x
.dyn_into::<Array>()
.map_err(|_| RewriterError::not_arr("Object.entries"))?
.to_vec();
Ok((
arr[0]
.as_string()
.ok_or(RewriterError::not_str("Object.entries key"))?,
arr[1].clone(),
))
})
.collect::<Result<Vec<(String, JsValue)>>>()?;
let mut func = None;
let mut attrs = HashMap::new();
for (k, v) in entries {
if k == "fn" {
func = Some(
v.dyn_into::<Function>()
.map_err(|_| RewriterError::not_fn("htmlRules fn"))?,
);
} else if let Ok(v) = v.clone().dyn_into::<Array>() {
let els = v
.to_vec()
.into_iter()
.map(|x| {
x.as_string()
.ok_or(RewriterError::not_str("htmlRules value array value"))
})
.collect::<Result<_>>()?;
attrs.insert(k, Some(els));
} else {
let el = v
.as_string()
.ok_or(RewriterError::not_str("htmlRules value"))?;
if el == "*" {
attrs.insert(k, None);
}
}
}
Ok(RewriteRule {
attrs,
func: Box::new(
move |alloc,
val,
(meta, cookie, _, _, _, _): &(
Object,
Object,
Function,
Function,
Function,
Function,
)| {
func.as_ref()
.map(|x| {
let ret = x
.call3(&JsValue::NULL, &val.into(), meta, cookie)
.map_err(RewriterError::from)
.map_err(Box::new)?;
Ok(ret.as_string().map(|x| alloc.alloc_str(&x)))
})
.transpose()
.map(Option::flatten)
},
),
})
})
.collect::<Result<_>>()
}
#[wasm_bindgen(inline_js = r#"
export function setMeta(meta, val) {
meta.base = new URL(val, meta.origin);
}
export function base64(bytes) {
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte)
).join("");
return btoa(binString);
}
export function rewriteJsAttr(fn, attr, code, meta) {
return fn(code, `(inline ${attr} on element)`, meta);
}
export function rewriteJsInline(fn, code, module, meta) {
return fn(code, `(inline script element)`, meta, module);
}
export function rewriteCss(fn, code, meta) {
return fn(code, meta);
}
export function rewriteHttpEquiv(fn, content, meta) {
const contentArray = content.split("url=");
if (contentArray[1])
contentArray[1] = fn(contentArray[1].trim(), meta);
return contentArray.join("url=");
}
export function getInjectCode(func, cookie, found_head) {
return func(cookie, found_head);
}
export function log(val) { console.log("aaaaa", val) }
"#)]
extern "C" {
#[wasm_bindgen(js_name = "setMeta")]
fn __external_tool_set_meta(meta: &Object, val: &str);
#[wasm_bindgen(js_name = "base64")]
fn __external_tool_base64(bytes: Uint8Array) -> String;
#[wasm_bindgen(js_name = "rewriteJsAttr")]
fn __external_tool_rewrite_js_attr(
func: &Function,
attr: &str,
code: &str,
meta: &Object,
) -> String;
#[wasm_bindgen(js_name = "rewriteJsInline")]
fn __external_tool_rewrite_js_inline(
func: &Function,
code: &str,
module: bool,
meta: &Object,
) -> String;
#[wasm_bindgen(js_name = "rewriteCss")]
fn __external_tool_rewrite_css(func: &Function, code: &str, meta: &Object) -> String;
#[wasm_bindgen(js_name = "rewriteHttpEquiv")]
fn __external_tool_rewrite_http_equiv(func: &Function, content: &str, meta: &Object) -> String;
#[wasm_bindgen(js_name = "getInjectCode")]
fn __external_tool_get_inject_code(
func: &Function,
cookie: &Object,
found_head: bool,
) -> String;
#[wasm_bindgen(js_name = "log")]
fn __external_tool_log(val: &str);
}
fn external_tool<'alloc, 'data>(
alloc: &'alloc Allocator,
tool: VisitorExternalTool<'data>,
(meta, cookie, rewrite_js, rewrite_css, rewrite_url, get_inject): &'data (
Object,
Object,
Function,
Function,
Function,
Function,
),
) -> std::result::Result<Option<&'alloc str>, Box<dyn Error + Sync + Send>> {
match tool {
VisitorExternalTool::SetMetaBase(val) => {
__external_tool_set_meta(meta, val);
Ok(None)
}
VisitorExternalTool::Base64(val) => Ok(Some(
alloc.alloc_str(&__external_tool_base64(Uint8Array::from(val.as_bytes()))),
)),
VisitorExternalTool::RewriteJsAttr { attr, code } => Ok(Some(alloc.alloc_str(
&__external_tool_rewrite_js_attr(rewrite_js, attr, code, meta),
))),
VisitorExternalTool::RewriteInlineScript { code, module } => Ok(Some(alloc.alloc_str(
&__external_tool_rewrite_js_inline(rewrite_js, code, module, meta),
))),
VisitorExternalTool::RewriteCss(css) => Ok(Some(
alloc.alloc_str(&__external_tool_rewrite_css(rewrite_css, css, meta)),
)),
VisitorExternalTool::RewriteHttpEquivContent(content) => Ok(Some(alloc.alloc_str(
&__external_tool_rewrite_http_equiv(rewrite_url, content, meta),
))),
VisitorExternalTool::Log(log) => {
__external_tool_log(log);
Ok(None)
}
VisitorExternalTool::GetScriptText { found_head } => Ok(Some(alloc.alloc_str(
&__external_tool_get_inject_code(get_inject, cookie, found_head),
))),
}
}
pub fn create_html(scramjet: &Object) -> Result<HtmlRewriter> {
let shared = get_obj(scramjet, "shared")?;
let rewrite = get_obj(&shared, "rewrite")?;
let html_rules = get_obj(&rewrite, "htmlRules")?
.dyn_into::<Array>()
.map_err(|_| RewriterError::not_arr("htmlRules"))?;
let html_rules = html_rules.to_vec().into_iter().map(Object::from).collect();
let rules = build_rules(html_rules)?;
Ok(HtmlRewriter::new(rules, Box::new(external_tool))?)
}
pub fn get_html_params(scramjet: &Object) -> Result<(Function, Function, Function, Function)> {
let shared = get_obj(scramjet, "shared")?;
let rewrite = get_obj(&shared, "rewrite")?;
let js = get_obj(&rewrite, "rewriteJs")?
.dyn_into::<Function>()
.map_err(|_| RewriterError::not_fn("rewriteJs"))?;
let css = get_obj(&rewrite, "rewriteCss")?
.dyn_into::<Function>()
.map_err(|_| RewriterError::not_fn("rewriteCss"))?;
let url = get_obj(&rewrite, "rewriteUrl")?
.dyn_into::<Function>()
.map_err(|_| RewriterError::not_fn("rewriteUrl"))?;
let inject_code = get_obj(&rewrite, "getHtmlInjectCode")?
.dyn_into::<Function>()
.map_err(|_| RewriterError::not_fn("getHtmlInjectCode"))?;
Ok((js, css, url, inject_code))
}
pub fn create_html_output(out: &[u8]) -> Result<HtmlRewriterOutput> {
let obj = Object::new();
set_obj(&obj, "html", &Uint8Array::from(out).into())?;
Ok(HtmlRewriterOutput::from(JsValue::from(obj)))
}