use serde::Serialize;
use serde_json::value::RawValue;
use serialize_to_javascript::Serialized;
use super::CallbackFn;
const MAX_JSON_STR_LEN: usize = usize::pow(2, 30) - 2;
const MIN_JSON_PARSE_LEN: usize = 10_240;
fn serialize_js_with<F: FnOnce(&str) -> String>(
json_string: String,
options: serialize_to_javascript::Options,
cb: F,
) -> crate::Result<String> {
let raw = RawValue::from_string(json_string)?;
let json = raw.get();
let first = json.as_bytes()[0];
#[cfg(debug_assertions)]
if first == b'"' {
assert!(
json.len() < MAX_JSON_STR_LEN,
"passing a string larger than the max JavaScript literal string size"
)
}
let return_val = if json.len() > MIN_JSON_PARSE_LEN && (first == b'{' || first == b'[') {
let serialized = Serialized::new(&raw, &options).into_string();
if serialized.len() < MAX_JSON_STR_LEN {
cb(&serialized)
} else {
cb(json)
}
} else {
cb(json)
};
Ok(return_val)
}
pub fn format<T: Serialize>(function_name: CallbackFn, arg: &T) -> crate::Result<String> {
format_raw(function_name, serde_json::to_string(arg)?)
}
pub fn format_raw(function_name: CallbackFn, json_string: String) -> crate::Result<String> {
let callback_id = function_name.0;
serialize_js_with(json_string, Default::default(), |arg| {
format_raw_js(callback_id, arg)
})
}
pub fn format_raw_js(callback_id: u32, js: impl AsRef<str>) -> String {
fn format_inner(callback_id: u32, js: &str) -> String {
format!("window.__TAURI_INTERNALS__.runCallback({callback_id}, {js})")
}
format_inner(callback_id, js.as_ref())
}
pub fn format_result<T: Serialize, E: Serialize>(
result: Result<T, E>,
success_callback: CallbackFn,
error_callback: CallbackFn,
) -> crate::Result<String> {
match result {
Ok(res) => format(success_callback, &res),
Err(err) => format(error_callback, &err),
}
}
pub fn format_result_raw(
raw_result: Result<String, String>,
success_callback: CallbackFn,
error_callback: CallbackFn,
) -> crate::Result<String> {
match raw_result {
Ok(res) => format_raw(success_callback, res),
Err(err) => format_raw(error_callback, err),
}
}
#[cfg(test)]
mod test {
use super::*;
use quickcheck::{Arbitrary, Gen};
use quickcheck_macros::quickcheck;
impl Arbitrary for CallbackFn {
fn arbitrary(g: &mut Gen) -> CallbackFn {
CallbackFn(u32::arbitrary(g))
}
}
#[derive(Debug, Clone)]
struct JsonStr(String);
impl Arbitrary for JsonStr {
fn arbitrary(g: &mut Gen) -> Self {
if bool::arbitrary(g) {
Self(format!(
"{{ {}: {} }}",
serde_json::to_string(&String::arbitrary(g)).unwrap(),
serde_json::to_string(&String::arbitrary(g)).unwrap()
))
} else {
Self(serde_json::to_string(&String::arbitrary(g)).unwrap())
}
}
}
fn serialize_js<T: Serialize>(value: &T) -> crate::Result<String> {
serialize_js_with(serde_json::to_string(value)?, Default::default(), |v| {
v.into()
})
}
fn serialize_js_raw(value: impl Into<String>) -> crate::Result<String> {
serialize_js_with(value.into(), Default::default(), |v| v.into())
}
#[test]
fn test_serialize_js() {
assert_eq!(serialize_js(&()).unwrap(), "null");
assert_eq!(serialize_js(&5i32).unwrap(), "5");
#[derive(serde::Serialize)]
struct JsonObj {
value: String,
}
let raw_str = "T".repeat(MIN_JSON_PARSE_LEN);
assert_eq!(serialize_js(&raw_str).unwrap(), format!("\"{raw_str}\""));
assert_eq!(
serialize_js(&JsonObj {
value: raw_str.clone()
})
.unwrap(),
format!("JSON.parse('{{\"value\":\"{raw_str}\"}}')")
);
assert_eq!(
serialize_js(&JsonObj {
value: format!("\"{raw_str}\"")
})
.unwrap(),
format!("JSON.parse('{{\"value\":\"\\\\\"{raw_str}\\\\\"\"}}')")
);
let dangerous_json = RawValue::from_string(
r#"{"test":"don\\ππ±βπ€\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don't forget to escape me!","test3":"\\ππ±βπ€\\\\'''\\\\ππ±βπ€\\\\ππ±βπ€\\'''''"}"#.into()
).unwrap();
let definitely_escaped_dangerous_json = format!(
"JSON.parse('{}')",
dangerous_json
.get()
.replace('\\', "\\\\")
.replace('\'', "\\'")
);
let escape_single_quoted_json_test =
serialize_to_javascript::Serialized::new(&dangerous_json, &Default::default()).into_string();
let result = r#"JSON.parse('{"test":"don\\\\ππ±βπ€\\\\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don\'t forget to escape me!","test3":"\\\\ππ±βπ€\\\\\\\\\'\'\'\\\\\\\\ππ±βπ€\\\\\\\\ππ±βπ€\\\\\'\'\'\'\'"}')"#;
assert_eq!(definitely_escaped_dangerous_json, result);
assert_eq!(escape_single_quoted_json_test, result);
}
#[quickcheck]
fn qc_formatting(f: CallbackFn, a: String) -> bool {
let fc = format(f, &a).unwrap();
fc.contains(&format!(
"window.__TAURI_INTERNALS__.runCallback({}, JSON.parse('{}'))",
f.0,
serde_json::Value::String(a.clone()),
)) || fc.contains(&format!(
r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
f.0,
serde_json::Value::String(a),
))
}
#[quickcheck]
fn qc_format_res(result: Result<String, String>, c: CallbackFn, ec: CallbackFn) -> bool {
let resp = format_result(result.clone(), c, ec).expect("failed to format callback result");
let (function, value) = match result {
Ok(v) => (c, v),
Err(e) => (ec, e),
};
resp.contains(&format!(
r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
function.0,
serde_json::Value::String(value),
))
}
#[test]
fn test_serialize_js_raw() {
assert_eq!(serialize_js_raw("null").unwrap(), "null");
assert_eq!(serialize_js_raw("5").unwrap(), "5");
assert_eq!(
serialize_js_raw("{ \"x\": [1, 2, 3] }").unwrap(),
"{ \"x\": [1, 2, 3] }"
);
#[derive(serde::Serialize)]
struct JsonObj {
value: String,
}
let raw_str = "T".repeat(MIN_JSON_PARSE_LEN);
assert_eq!(
serialize_js_raw(format!("\"{raw_str}\"")).unwrap(),
format!("\"{raw_str}\"")
);
assert_eq!(
serialize_js_raw(format!("{{\"value\":\"{raw_str}\"}}")).unwrap(),
format!("JSON.parse('{{\"value\":\"{raw_str}\"}}')")
);
assert_eq!(
serialize_js(&JsonObj {
value: format!("\"{raw_str}\"")
})
.unwrap(),
format!("JSON.parse('{{\"value\":\"\\\\\"{raw_str}\\\\\"\"}}')")
);
let dangerous_json = RawValue::from_string(
r#"{"test":"don\\ππ±βπ€\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don't forget to escape me!","test3":"\\ππ±βπ€\\\\'''\\\\ππ±βπ€\\\\ππ±βπ€\\'''''"}"#.into()
).unwrap();
let definitely_escaped_dangerous_json = format!(
"JSON.parse('{}')",
dangerous_json
.get()
.replace('\\', "\\\\")
.replace('\'', "\\'")
);
let escape_single_quoted_json_test =
serialize_to_javascript::Serialized::new(&dangerous_json, &Default::default()).into_string();
let result = r#"JSON.parse('{"test":"don\\\\ππ±βπ€\\\\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don\'t forget to escape me!","test3":"\\\\ππ±βπ€\\\\\\\\\'\'\'\\\\\\\\ππ±βπ€\\\\\\\\ππ±βπ€\\\\\'\'\'\'\'"}')"#;
assert_eq!(definitely_escaped_dangerous_json, result);
assert_eq!(escape_single_quoted_json_test, result);
}
#[quickcheck]
fn qc_formatting_raw(f: CallbackFn, a: JsonStr) -> bool {
let a = a.0;
let fc = format_raw(f, a.clone()).unwrap();
fc.contains(&format!(
r#"window.__TAURI_INTERNALS__.runCallback({}, JSON.parse('{}'))"#,
f.0, a
)) || fc.contains(&format!(
r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
f.0, a
))
}
#[quickcheck]
fn qc_format_raw_res(result: Result<JsonStr, JsonStr>, c: CallbackFn, ec: CallbackFn) -> bool {
let result = result.map(|v| v.0).map_err(|e| e.0);
let resp = format_result_raw(result.clone(), c, ec).expect("failed to format callback result");
let (function, value) = match result {
Ok(v) => (c, v),
Err(e) => (ec, e),
};
resp.contains(&format!(
r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
function.0, value
))
}
}