js_deobfuscator/value/
json.rs1use super::JsValue;
4
5pub fn stringify(val: &JsValue) -> Option<JsValue> {
7 let s = match val {
8 JsValue::Number(n) => {
9 if n.is_nan() || n.is_infinite() { "null".to_string() }
10 else { super::coerce::number_to_string(*n) }
11 }
12 JsValue::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
13 JsValue::Boolean(b) => b.to_string(),
14 JsValue::Null => "null".to_string(),
15 JsValue::Undefined => return Some(JsValue::Undefined), };
17 Some(JsValue::String(s))
18}
19
20pub fn parse(s: &str) -> Option<JsValue> {
22 let trimmed = s.trim();
23 match trimmed {
24 "null" => Some(JsValue::Null),
25 "true" => Some(JsValue::Boolean(true)),
26 "false" => Some(JsValue::Boolean(false)),
27 _ if trimmed.starts_with('"') && trimmed.ends_with('"') => {
28 let inner = &trimmed[1..trimmed.len()-1];
29 Some(JsValue::String(json_unescape(inner)))
30 }
31 _ => {
32 trimmed.parse::<f64>().ok().map(JsValue::Number)
33 }
34 }
35}
36
37fn json_unescape(s: &str) -> String {
39 let mut result = String::with_capacity(s.len());
40 let mut chars = s.chars();
41 while let Some(c) = chars.next() {
42 if c == '\\' {
43 match chars.next() {
44 Some('"') => result.push('"'),
45 Some('\\') => result.push('\\'),
46 Some('/') => result.push('/'),
47 Some('n') => result.push('\n'),
48 Some('r') => result.push('\r'),
49 Some('t') => result.push('\t'),
50 Some('b') => result.push('\u{0008}'),
51 Some('f') => result.push('\u{000C}'),
52 Some('u') => {
53 let hex: String = chars.by_ref().take(4).collect();
54 if let Ok(code) = u32::from_str_radix(&hex, 16) {
55 if let Some(ch) = char::from_u32(code) {
56 result.push(ch);
57 }
58 }
59 }
60 Some(other) => { result.push('\\'); result.push(other); }
61 None => result.push('\\'),
62 }
63 } else {
64 result.push(c);
65 }
66 }
67 result
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 fn n(v: f64) -> JsValue { JsValue::Number(v) }
74 fn s(v: &str) -> JsValue { JsValue::String(v.into()) }
75
76 #[test]
77 fn test_stringify() {
78 assert_eq!(stringify(&n(42.0)), Some(s("42")));
79 assert_eq!(stringify(&s("hello")), Some(s("\"hello\"")));
80 assert_eq!(stringify(&JsValue::Boolean(true)), Some(s("true")));
81 assert_eq!(stringify(&JsValue::Null), Some(s("null")));
82 assert_eq!(stringify(&n(f64::NAN)), Some(s("null")));
83 }
84
85 #[test]
86 fn test_parse() {
87 assert_eq!(parse("42"), Some(n(42.0)));
88 assert_eq!(parse("\"hello\""), Some(s("hello")));
89 assert_eq!(parse("true"), Some(JsValue::Boolean(true)));
90 assert_eq!(parse("null"), Some(JsValue::Null));
91 }
92
93 #[test]
94 fn test_parse_escape_sequences() {
95 assert_eq!(parse("\"hello\\nworld\""), Some(s("hello\nworld")));
96 assert_eq!(parse("\"tab\\there\""), Some(s("tab\there")));
97 assert_eq!(parse("\"back\\\\slash\""), Some(s("back\\slash")));
98 assert_eq!(parse("\"quote\\\"inside\""), Some(s("quote\"inside")));
99 }
100
101 #[test]
102 fn test_parse_unicode_escape() {
103 assert_eq!(parse("\"\\u0041\""), Some(s("A")));
104 }
105}