1use serde::Serialize;
6use serde_json::value::RawValue;
7use serialize_to_javascript::Serialized;
8
9use super::CallbackFn;
10
11const MAX_JSON_STR_LEN: usize = usize::pow(2, 30) - 2;
19
20const MIN_JSON_PARSE_LEN: usize = 10_240;
24
25fn serialize_js_with<F: FnOnce(&str) -> String>(
44 json_string: String,
45 options: serialize_to_javascript::Options,
46 cb: F,
47) -> crate::Result<String> {
48 let raw = RawValue::from_string(json_string)?;
51
52 let json = raw.get();
54 let first = json.as_bytes()[0];
55
56 #[cfg(debug_assertions)]
57 if first == b'"' {
58 assert!(
59 json.len() < MAX_JSON_STR_LEN,
60 "passing a string larger than the max JavaScript literal string size"
61 )
62 }
63
64 let return_val = if json.len() > MIN_JSON_PARSE_LEN && (first == b'{' || first == b'[') {
65 let serialized = Serialized::new(&raw, &options).into_string();
66 if serialized.len() < MAX_JSON_STR_LEN {
69 cb(&serialized)
70 } else {
71 cb(json)
72 }
73 } else {
74 cb(json)
75 };
76
77 Ok(return_val)
78}
79
80pub fn format<T: Serialize>(function_name: CallbackFn, arg: &T) -> crate::Result<String> {
84 format_raw(function_name, serde_json::to_string(arg)?)
85}
86
87pub fn format_raw(function_name: CallbackFn, json_string: String) -> crate::Result<String> {
94 let callback_id = function_name.0;
95 serialize_js_with(json_string, Default::default(), |arg| {
96 format_raw_js(callback_id, arg)
97 })
98}
99
100pub fn format_raw_js(callback_id: u32, js: impl AsRef<str>) -> String {
102 fn format_inner(callback_id: u32, js: &str) -> String {
103 format!("window.__TAURI_INTERNALS__.runCallback({callback_id}, {js})")
104 }
105 format_inner(callback_id, js.as_ref())
106}
107
108pub fn format_result<T: Serialize, E: Serialize>(
112 result: Result<T, E>,
113 success_callback: CallbackFn,
114 error_callback: CallbackFn,
115) -> crate::Result<String> {
116 match result {
117 Ok(res) => format(success_callback, &res),
118 Err(err) => format(error_callback, &err),
119 }
120}
121
122pub fn format_result_raw(
133 raw_result: Result<String, String>,
134 success_callback: CallbackFn,
135 error_callback: CallbackFn,
136) -> crate::Result<String> {
137 match raw_result {
138 Ok(res) => format_raw(success_callback, res),
139 Err(err) => format_raw(error_callback, err),
140 }
141}
142
143#[cfg(test)]
144mod test {
145 use super::*;
146 use quickcheck::{Arbitrary, Gen};
147 use quickcheck_macros::quickcheck;
148
149 impl Arbitrary for CallbackFn {
150 fn arbitrary(g: &mut Gen) -> CallbackFn {
151 CallbackFn(u32::arbitrary(g))
152 }
153 }
154
155 #[derive(Debug, Clone)]
156 struct JsonStr(String);
157
158 impl Arbitrary for JsonStr {
159 fn arbitrary(g: &mut Gen) -> Self {
160 if bool::arbitrary(g) {
161 Self(format!(
162 "{{ {}: {} }}",
163 serde_json::to_string(&String::arbitrary(g)).unwrap(),
164 serde_json::to_string(&String::arbitrary(g)).unwrap()
165 ))
166 } else {
167 Self(serde_json::to_string(&String::arbitrary(g)).unwrap())
168 }
169 }
170 }
171
172 fn serialize_js<T: Serialize>(value: &T) -> crate::Result<String> {
173 serialize_js_with(serde_json::to_string(value)?, Default::default(), |v| {
174 v.into()
175 })
176 }
177
178 fn serialize_js_raw(value: impl Into<String>) -> crate::Result<String> {
179 serialize_js_with(value.into(), Default::default(), |v| v.into())
180 }
181
182 #[test]
183 fn test_serialize_js() {
184 assert_eq!(serialize_js(&()).unwrap(), "null");
185 assert_eq!(serialize_js(&5i32).unwrap(), "5");
186
187 #[derive(serde::Serialize)]
188 struct JsonObj {
189 value: String,
190 }
191
192 let raw_str = "T".repeat(MIN_JSON_PARSE_LEN);
193 assert_eq!(serialize_js(&raw_str).unwrap(), format!("\"{raw_str}\""));
194
195 assert_eq!(
196 serialize_js(&JsonObj {
197 value: raw_str.clone()
198 })
199 .unwrap(),
200 format!("JSON.parse('{{\"value\":\"{raw_str}\"}}')")
201 );
202
203 assert_eq!(
204 serialize_js(&JsonObj {
205 value: format!("\"{raw_str}\"")
206 })
207 .unwrap(),
208 format!("JSON.parse('{{\"value\":\"\\\\\"{raw_str}\\\\\"\"}}')")
209 );
210
211 let dangerous_json = RawValue::from_string(
212 r#"{"test":"don\\ππ±βπ€\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don't forget to escape me!","test3":"\\ππ±βπ€\\\\'''\\\\ππ±βπ€\\\\ππ±βπ€\\'''''"}"#.into()
213 ).unwrap();
214
215 let definitely_escaped_dangerous_json = format!(
216 "JSON.parse('{}')",
217 dangerous_json
218 .get()
219 .replace('\\', "\\\\")
220 .replace('\'', "\\'")
221 );
222 let escape_single_quoted_json_test =
223 serialize_to_javascript::Serialized::new(&dangerous_json, &Default::default()).into_string();
224
225 let result = r#"JSON.parse('{"test":"don\\\\ππ±βπ€\\\\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don\'t forget to escape me!","test3":"\\\\ππ±βπ€\\\\\\\\\'\'\'\\\\\\\\ππ±βπ€\\\\\\\\ππ±βπ€\\\\\'\'\'\'\'"}')"#;
226 assert_eq!(definitely_escaped_dangerous_json, result);
227 assert_eq!(escape_single_quoted_json_test, result);
228 }
229
230 #[quickcheck]
232 fn qc_formatting(f: CallbackFn, a: String) -> bool {
233 let fc = format(f, &a).unwrap();
235 fc.contains(&format!(
236 "window.__TAURI_INTERNALS__.runCallback({}, JSON.parse('{}'))",
237 f.0,
238 serde_json::Value::String(a.clone()),
239 )) || fc.contains(&format!(
240 r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
241 f.0,
242 serde_json::Value::String(a),
243 ))
244 }
245
246 #[quickcheck]
248 fn qc_format_res(result: Result<String, String>, c: CallbackFn, ec: CallbackFn) -> bool {
249 let resp = format_result(result.clone(), c, ec).expect("failed to format callback result");
250 let (function, value) = match result {
251 Ok(v) => (c, v),
252 Err(e) => (ec, e),
253 };
254
255 resp.contains(&format!(
256 r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
257 function.0,
258 serde_json::Value::String(value),
259 ))
260 }
261
262 #[test]
263 fn test_serialize_js_raw() {
264 assert_eq!(serialize_js_raw("null").unwrap(), "null");
265 assert_eq!(serialize_js_raw("5").unwrap(), "5");
266 assert_eq!(
267 serialize_js_raw("{ \"x\": [1, 2, 3] }").unwrap(),
268 "{ \"x\": [1, 2, 3] }"
269 );
270
271 #[derive(serde::Serialize)]
272 struct JsonObj {
273 value: String,
274 }
275
276 let raw_str = "T".repeat(MIN_JSON_PARSE_LEN);
277 assert_eq!(
278 serialize_js_raw(format!("\"{raw_str}\"")).unwrap(),
279 format!("\"{raw_str}\"")
280 );
281
282 assert_eq!(
283 serialize_js_raw(format!("{{\"value\":\"{raw_str}\"}}")).unwrap(),
284 format!("JSON.parse('{{\"value\":\"{raw_str}\"}}')")
285 );
286
287 assert_eq!(
288 serialize_js(&JsonObj {
289 value: format!("\"{raw_str}\"")
290 })
291 .unwrap(),
292 format!("JSON.parse('{{\"value\":\"\\\\\"{raw_str}\\\\\"\"}}')")
293 );
294
295 let dangerous_json = RawValue::from_string(
296 r#"{"test":"don\\ππ±βπ€\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don't forget to escape me!","test3":"\\ππ±βπ€\\\\'''\\\\ππ±βπ€\\\\ππ±βπ€\\'''''"}"#.into()
297 ).unwrap();
298
299 let definitely_escaped_dangerous_json = format!(
300 "JSON.parse('{}')",
301 dangerous_json
302 .get()
303 .replace('\\', "\\\\")
304 .replace('\'', "\\'")
305 );
306 let escape_single_quoted_json_test =
307 serialize_to_javascript::Serialized::new(&dangerous_json, &Default::default()).into_string();
308
309 let result = r#"JSON.parse('{"test":"don\\\\ππ±βπ€\\\\\'t forget to escape me!ππ±βπ€","teππ±βπ€st2":"don\'t forget to escape me!","test3":"\\\\ππ±βπ€\\\\\\\\\'\'\'\\\\\\\\ππ±βπ€\\\\\\\\ππ±βπ€\\\\\'\'\'\'\'"}')"#;
310 assert_eq!(definitely_escaped_dangerous_json, result);
311 assert_eq!(escape_single_quoted_json_test, result);
312 }
313
314 #[quickcheck]
316 fn qc_formatting_raw(f: CallbackFn, a: JsonStr) -> bool {
317 let a = a.0;
318 let fc = format_raw(f, a.clone()).unwrap();
320 fc.contains(&format!(
321 r#"window.__TAURI_INTERNALS__.runCallback({}, JSON.parse('{}'))"#,
322 f.0, a
323 )) || fc.contains(&format!(
324 r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
325 f.0, a
326 ))
327 }
328
329 #[quickcheck]
331 fn qc_format_raw_res(result: Result<JsonStr, JsonStr>, c: CallbackFn, ec: CallbackFn) -> bool {
332 let result = result.map(|v| v.0).map_err(|e| e.0);
333 let resp = format_result_raw(result.clone(), c, ec).expect("failed to format callback result");
334 let (function, value) = match result {
335 Ok(v) => (c, v),
336 Err(e) => (ec, e),
337 };
338
339 resp.contains(&format!(
340 r#"window.__TAURI_INTERNALS__.runCallback({}, {})"#,
341 function.0, value
342 ))
343 }
344}