1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use rong::{
function::{Optional, This},
js_class, js_export, js_method, *,
};
#[js_export]
#[derive(Default)]
pub struct Headers {
headers: HeaderMap<HeaderValue>,
}
impl Headers {
/// Create `Headers` from an existing `HeaderMap`.
pub(crate) fn from_header_map(headers: HeaderMap<HeaderValue>) -> Self {
Self { headers }
}
// Get a reference to the inner HeaderMap
pub(crate) fn as_header_map(&self) -> &HeaderMap<HeaderValue> {
&self.headers
}
}
#[js_class]
impl Headers {
#[js_method(constructor)]
pub(crate) fn new(init: Optional<JSValue>) -> JSResult<Self> {
let mut headers = HeaderMap::new();
if let Some(init) = init.0 {
if let Some(obj) = init.into_object() {
// instance of Headers
if let Ok(other_headers) = obj.borrow::<Headers>() {
headers.extend(
other_headers
.headers
.iter()
.map(|(k, v)| (k.clone(), v.clone())),
);
} else if let Some(array) = JSArray::from_object(obj.clone()) {
for item in array.iter_values()? {
let item = item?;
if let Some(pair) = item.into_object().and_then(JSArray::from_object) {
if pair.len()? != 2 {
return Err(HostError::new(
rong::error::E_INVALID_ARG,
"Each header must be an array of [name, value]",
)
.with_name("TypeError")
.into());
}
let key: String = pair.get_opt(0)?.ok_or_else(|| {
HostError::new(
rong::error::E_INVALID_ARG,
"Header name is required",
)
.with_name("TypeError")
})?;
let value: String = pair.get_opt(1)?.ok_or_else(|| {
HostError::new(
rong::error::E_INVALID_ARG,
"Header value is required",
)
.with_name("TypeError")
})?;
match (
HeaderName::try_from(key.as_str()),
HeaderValue::try_from(value.as_str()),
) {
(Ok(name), Ok(value)) => {
headers.append(name, value);
}
(Err(_), _) => {
return Err(HostError::new(
rong::error::E_INVALID_ARG,
format!("Invalid header name: {}", key),
)
.with_name("TypeError")
.into());
}
(_, Err(_)) => {
return Err(HostError::new(
rong::error::E_INVALID_ARG,
"Invalid header value",
)
.with_name("TypeError")
.into());
}
}
} else {
return Err(HostError::new(
rong::error::E_INVALID_ARG,
"Each header must be an array of [name, value]",
)
.with_name("TypeError")
.into());
}
}
} else {
for entry in obj.entries()? {
let (key, value) = entry.try_into::<String, String>()?;
match (
HeaderName::try_from(key.as_str()),
HeaderValue::try_from(value.as_str()),
) {
(Ok(name), Ok(value)) => {
headers.append(name, value);
}
(Err(_), _) => {
return Err(HostError::new(
rong::error::E_INVALID_ARG,
format!("Invalid header name: {}", key),
)
.with_name("TypeError")
.into());
}
(_, Err(_)) => {
return Err(HostError::new(
rong::error::E_INVALID_ARG,
"Invalid header value",
)
.with_name("TypeError")
.into());
}
}
}
}
} else {
return Err(
HostError::new(rong::error::E_INVALID_ARG, "Invalid Headers init")
.with_name("TypeError")
.into(),
);
}
}
Ok(Self { headers })
}
/// The append() method of the Headers interface appends a new value onto an
/// existing header inside a Headers object, or adds the header if it does not
/// already exist.
#[js_method]
pub(crate) fn append(&mut self, name: String, value: String) {
if let (Ok(name), Ok(value)) = (
HeaderName::try_from(name.as_str()),
HeaderValue::try_from(value.as_str()),
) {
self.headers.append(name, value);
}
}
/// The delete() method of the Headers interface deletes a header from the current Headers object.
#[js_method]
pub(crate) fn delete(&mut self, name: String) {
if let Ok(name) = HeaderName::try_from(name.as_str()) {
self.headers.remove(&name);
}
}
/// The get() method of the Headers interface returns a byte string of all the
/// values of a header within a Headers object with a given name. If the requested
/// header doesn't exist in the Headers object, it returns null.
///
/// The name of the HTTP header whose values you want to retrieve from the Headers
/// object. If the given name is not the name of an HTTP header, this method throws
/// a TypeError. The name is case-insensitive.
#[js_method]
pub(crate) fn get(&self, name: String) -> JSResult<Option<String>> {
match HeaderName::try_from(name.as_str()) {
Ok(name) => {
let values: Vec<&str> = self
.headers
.get_all(&name)
.into_iter()
.filter_map(|v| v.to_str().ok())
.collect();
if values.is_empty() {
return Ok(None);
}
Ok(Some(values.join(", ")))
}
Err(_) => Err(HostError::new(
rong::error::E_INVALID_ARG,
format!("Invalid header name: {}", name),
)
.with_name("TypeError")
.into()),
}
}
/// The has() method returns a boolean stating whether a Headers object contains
/// a certain header.
///
/// The name of the HTTP header you want to test for. If the given name is not a
/// valid HTTP header name, this method throws a TypeError.
#[js_method]
pub(crate) fn has(&self, name: String) -> JSResult<bool> {
match HeaderName::try_from(name.as_str()) {
Ok(name) => Ok(self.headers.contains_key(&name)),
Err(_) => Err(HostError::new(
rong::error::E_INVALID_ARG,
format!("Invalid header name: {}", name),
)
.with_name("TypeError")
.into()),
}
}
/// The set() method sets a new value for an existing header inside a Headers
/// object, or adds the header if it does not already exist.
///
/// The name of the HTTP header you want to set to a new value. If the given
/// name is not the name of an HTTP header, this method throws a TypeError.
#[js_method]
pub(crate) fn set(&mut self, name: String, value: String) -> JSResult<()> {
// Check for null characters in value
if value.contains('\0') {
return Err(HostError::new(
rong::error::E_INVALID_ARG,
"Header value must not contain null characters",
)
.with_name("TypeError")
.into());
}
match (
HeaderName::try_from(name.as_str()),
HeaderValue::try_from(value.as_str()),
) {
(Ok(name), Ok(value)) => {
self.headers.insert(name, value);
Ok(())
}
(Err(_), _) => Err(HostError::new(
rong::error::E_INVALID_ARG,
format!("Invalid header name: {}", name),
)
.with_name("TypeError")
.into()),
(_, Err(_)) => Err(
HostError::new(rong::error::E_INVALID_ARG, "Invalid header value")
.with_name("TypeError")
.into(),
),
}
}
/// The Headers.entries() method returns an iterator allowing to go through all
/// key/value pairs contained in this object. Both the key and value of each pair are String objects
#[js_method]
fn entries(&self, ctx: JSContext) -> JSResult<JSObject> {
let entries = self
.headers
.iter()
.map(|(name, value)| {
vec![
name.as_str().to_lowercase(),
value.to_str().unwrap_or_default().to_string(),
]
})
.collect::<Vec<_>>();
// Use new simplified API
entries.to_js_iter(&ctx)
}
/// The Headers.keys() method returns an iterator allowing to go through all
/// keys contained in this object. The keys are String objects.
#[js_method]
fn keys(&self, ctx: JSContext) -> JSResult<JSObject> {
let keys = self
.headers
.keys()
.map(|name| name.as_str().to_lowercase())
.collect::<Vec<_>>();
// Use new simplified API
keys.to_js_iter(&ctx)
}
/// The Headers.values() method returns an iterator allowing to go through all
/// values contained in this object. The values are String objects
#[js_method]
fn values(&self, ctx: JSContext) -> JSResult<JSObject> {
let values = self
.headers
.values()
.filter_map(|value| value.to_str().ok().map(|s| s.to_string()))
.collect::<Vec<_>>();
// Use new simplified API
values.to_js_iter(&ctx)
}
/// getSetCookie() returns an array containing the values of all Set-Cookie
/// headers associated with a response.
///
/// If no Set-Cookie headers are set, the method will return an empty array
#[js_method(rename = "getSetCookie")]
fn get_set_cookie(&self) -> Vec<String> {
let mut cookies = Vec::new();
// HeaderMap natively supports multi-value headers
for cookie in self.headers.get_all(header::SET_COOKIE) {
if let Ok(cookie_str) = cookie.to_str() {
cookies.push(cookie_str.to_string());
}
}
cookies
}
/// forEach() method executes a callback function once per each key/value pair
/// in the Headers object
#[js_method(rename = "forEach")]
fn for_each(
&self,
this: This<JSObject>, // Header Object
callback: JSFunc,
this_arg: Optional<JSObject>,
) -> JSResult<()> {
// Value to use as this when executing callback. It's optional
let this_arg = this_arg.0;
for (name, value) in &self.headers {
let value_str = value.to_str().unwrap_or_default();
let name_str = name.as_str();
callback.call::<_, ()>(this_arg.clone(), (value_str, name_str, this.0.clone()))?;
}
Ok(())
}
#[js_method(gc_mark)]
fn gc_mark_with<F>(&self, _mark_fn: F)
where
F: FnMut(&JSValue),
{
}
}
pub(crate) fn init(ctx: &JSContext) -> JSResult<()> {
ctx.register_class::<Headers>()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use rong_test::*;
#[test]
fn test_headers() {
async_run!(|ctx: JSContext| async move {
rong_assert::init(&ctx)?;
rong_console::init(&ctx)?;
rong_encoding::init(&ctx)?;
crate::header::init(&ctx)?; // Initialize Headers before running tests
let passed = UnitJSRunner::load_script(&ctx, "header.js")
.await?
.run()
.await?;
assert!(passed);
Ok(())
});
}
}