nodejs/stdlib/
querystring.rs1use crate::host::{with_host, JsObj};
7use fusevm::Value;
8use indexmap::IndexMap;
9
10pub const METHODS: &[&str] = &[
11 "parse",
12 "stringify",
13 "escape",
14 "unescape",
15 "encode",
16 "decode",
17 "unescapeBuffer",
18];
19
20pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
21 Some(match method {
22 "parse" | "decode" => Ok(parse(&super::arg_str(args, 0), args)),
23 "stringify" | "encode" => Ok(stringify(args)),
24 "escape" => {
25 let s = super::arg_str(args, 0);
27 Ok(with_host(|h| h.new_str(escape(&s))))
28 }
29 "unescape" => {
30 let s = super::arg_str(args, 0);
31 Ok(with_host(|h| h.new_str(unescape(&s))))
32 }
33 "unescapeBuffer" => {
37 let s = super::arg_str(args, 0);
38 let decode_spaces = matches!(args.get(1), Some(Value::Bool(true)));
39 Ok(super::buffer::from_bytes(&unescape_buffer(
40 &s,
41 decode_spaces,
42 )))
43 }
44 _ => return None,
45 })
46}
47
48fn parse(s: &str, args: &[Value]) -> Value {
51 let sep = args
52 .get(1)
53 .map(|_| super::arg_str(args, 1))
54 .filter(|s| !s.is_empty())
55 .unwrap_or_else(|| "&".into());
56 let eq = args
57 .get(2)
58 .map(|_| super::arg_str(args, 2))
59 .filter(|s| !s.is_empty())
60 .unwrap_or_else(|| "=".into());
61 let mut map: IndexMap<String, Value> = IndexMap::new();
62 if !s.is_empty() {
63 for pair in s.split(&sep) {
64 if pair.is_empty() {
65 continue;
66 }
67 let (k, v) = match pair.split_once(&eq) {
68 Some((k, v)) => (unescape(k), unescape(v)),
69 None => (unescape(pair), String::new()),
70 };
71 let val = with_host(|h| h.new_str(v));
72 match map.get(&k).cloned() {
74 Some(existing) => {
75 let is_arr = with_host(|h| matches!(h.get(&existing), Some(JsObj::Array(_))));
76 if is_arr {
77 with_host(|h| {
78 if let Some(JsObj::Array(items)) = h.get_mut(&existing) {
79 items.push(val);
80 }
81 });
82 } else {
83 let arr = with_host(|h| h.new_array(vec![existing, val]));
84 map.insert(k, arr);
85 }
86 }
87 None => {
88 map.insert(k, val);
89 }
90 }
91 }
92 }
93 with_host(|h| h.new_object(map))
94}
95
96fn stringify(args: &[Value]) -> Value {
98 let obj = args.first().cloned().unwrap_or(Value::Undef);
99 let sep = args
100 .get(1)
101 .map(|_| super::arg_str(args, 1))
102 .filter(|s| !s.is_empty())
103 .unwrap_or_else(|| "&".into());
104 let eq = args
105 .get(2)
106 .map(|_| super::arg_str(args, 2))
107 .filter(|s| !s.is_empty())
108 .unwrap_or_else(|| "=".into());
109 let entries = with_host(|h| match h.get(&obj) {
110 Some(JsObj::Object(p)) => p
111 .iter()
112 .filter(|(k, _)| !k.starts_with("@@"))
113 .map(|(k, v)| (k.clone(), v.clone()))
114 .collect::<Vec<_>>(),
115 _ => Vec::new(),
116 });
117 let mut parts: Vec<String> = Vec::new();
118 for (k, v) in entries {
119 let ek = escape(&k);
120 let elems = with_host(|h| match h.get(&v) {
122 Some(JsObj::Array(items)) => {
123 Some(items.iter().map(|x| h.str_of(x)).collect::<Vec<_>>())
124 }
125 _ => None,
126 });
127 match elems {
128 Some(list) => {
129 for e in list {
130 parts.push(format!("{ek}{eq}{}", escape(&e)));
131 }
132 }
133 None => {
134 let ev = with_host(|h| h.str_of(&v));
135 parts.push(format!("{ek}{eq}{}", escape(&ev)));
136 }
137 }
138 }
139 with_host(|h| h.new_str(parts.join(&sep)))
140}
141
142fn unescape_buffer(s: &str, decode_spaces: bool) -> Vec<u8> {
145 let b = s.as_bytes();
146 let mut out: Vec<u8> = Vec::with_capacity(b.len());
147 let mut i = 0;
148 while i < b.len() {
149 match b[i] {
150 b'+' if decode_spaces => {
151 out.push(b' ');
152 i += 1;
153 }
154 b'%' if i + 2 < b.len() => {
155 let hi = (b[i + 1] as char).to_digit(16);
156 let lo = (b[i + 2] as char).to_digit(16);
157 match (hi, lo) {
158 (Some(h), Some(l)) => {
159 out.push((h * 16 + l) as u8);
160 i += 3;
161 }
162 _ => {
163 out.push(b'%');
164 i += 1;
165 }
166 }
167 }
168 c => {
169 out.push(c);
170 i += 1;
171 }
172 }
173 }
174 out
175}
176
177fn escape(s: &str) -> String {
179 const UNRESERVED: &[u8] =
180 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'()";
181 let mut out = String::with_capacity(s.len());
182 for &b in s.as_bytes() {
183 if UNRESERVED.contains(&b) {
184 out.push(b as char);
185 } else {
186 out.push('%');
187 out.push(
188 char::from_digit((b >> 4) as u32, 16)
189 .unwrap()
190 .to_ascii_uppercase(),
191 );
192 out.push(
193 char::from_digit((b & 0xf) as u32, 16)
194 .unwrap()
195 .to_ascii_uppercase(),
196 );
197 }
198 }
199 out
200}
201
202fn unescape(s: &str) -> String {
205 let bytes = s.as_bytes();
206 let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
207 let mut i = 0;
208 while i < bytes.len() {
209 match bytes[i] {
210 b'+' => {
211 out.push(b' ');
212 i += 1;
213 }
214 b'%' if i + 2 < bytes.len() => {
215 let hi = (bytes[i + 1] as char).to_digit(16);
216 let lo = (bytes[i + 2] as char).to_digit(16);
217 match (hi, lo) {
218 (Some(h), Some(l)) => {
219 out.push((h * 16 + l) as u8);
220 i += 3;
221 }
222 _ => {
223 out.push(b'%');
224 i += 1;
225 }
226 }
227 }
228 b => {
229 out.push(b);
230 i += 1;
231 }
232 }
233 }
234 String::from_utf8_lossy(&out).into_owned()
235}