Skip to content

Commit c1c2567

Browse files
author
Rajdeep Singh
committed
fix(fetch): address PR review comments for iterators, URL validation, and intrinsics
1 parent e9ccdb3 commit c1c2567

File tree

7 files changed

+108
-68
lines changed

7 files changed

+108
-68
lines changed

core/engine/src/builtins/string/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use crate::{
1313
Context, JsArgs, JsExpect, JsResult, JsString, JsValue,
1414
builtins::{Array, BuiltInObject, Number, RegExp},
1515
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
16-
error::RuntimeLimitError,
1716
error::JsNativeError,
17+
error::RuntimeLimitError,
1818
js_string,
1919
object::{JsObject, internal_methods::get_prototype_from_constructor},
2020
property::{Attribute, PropertyDescriptor},
@@ -721,8 +721,7 @@ impl String {
721721

722722
let n = u64::try_from(n).expect("n was checked to be non-negative");
723723

724-
if n
725-
.checked_mul(len as u64)
724+
if n.checked_mul(len as u64)
726725
.is_none_or(|total_len| total_len > (Self::MAX_STRING_LENGTH as u64))
727726
{
728727
return Err(JsNativeError::range()

core/engine/src/builtins/string/tests.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ use boa_macros::js_str;
22
use indoc::indoc;
33

44
use crate::{
5-
JsNativeErrorKind, JsValue, TestAction,
6-
error::RuntimeLimitError,
7-
js_string, run_test_actions,
5+
JsNativeErrorKind, JsValue, TestAction, error::RuntimeLimitError, js_string, run_test_actions,
86
};
97

108
#[test]

core/engine/src/module/loader/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub fn resolve_module_specifier(
6262

6363
// On Windows, also replace `/` with `\`. JavaScript imports use `/` as path separator.
6464
#[cfg(target_family = "windows")]
65+
#[allow(clippy::disallowed_methods)]
6566
let specifier = specifier.replace('/', "\\");
6667

6768
let short_path = Path::new(&specifier);

core/runtime/src/fetch/headers.rs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
//! See <https://developer.mozilla.org/en-US/docs/Web/API/Headers>.
44
#![allow(clippy::needless_pass_by_value)]
55

6+
use boa_engine::builtins::iterable::create_iter_result_object;
67
use boa_engine::interop::JsClass;
7-
use boa_engine::object::builtins::{JsArray, TypedJsFunction};
8+
use boa_engine::native_function::NativeFunction;
89
use boa_engine::object::FunctionObjectBuilder;
10+
use boa_engine::object::builtins::{JsArray, TypedJsFunction};
911
use boa_engine::property::PropertyDescriptor;
1012
use boa_engine::value::{Convert, TryFromJs};
1113
use boa_engine::{
1214
Context, Finalize, JsData, JsObject, JsResult, JsString, JsValue, Trace, boa_class, js_error,
1315
js_string,
1416
};
15-
use boa_engine::builtins::iterable::create_iter_result_object;
16-
use boa_engine::native_function::NativeFunction;
1717
use http::header::HeaderMap as HttpHeaderMap;
1818
use http::{HeaderName, HeaderValue};
1919
use std::cell::RefCell;
@@ -44,44 +44,54 @@ enum HeadersIteratorKind {
4444
/// - [WHATWG spec](https://fetch.spec.whatwg.org/#headers-class)
4545
#[derive(Debug, Clone, JsData, Trace, Finalize)]
4646
struct HeadersIterator {
47-
headers: JsHeaders,
47+
headers: Vec<(JsString, JsString)>,
4848
index: usize,
4949
kind: HeadersIteratorKind,
5050
}
5151

5252
impl HeadersIterator {
5353
/// Creates a new Headers iterator.
5454
fn new(headers: JsHeaders, kind: HeadersIteratorKind) -> Self {
55+
let headers_vec: Vec<(JsString, JsString)> = {
56+
let headers_data = headers.headers.borrow();
57+
headers_data
58+
.iter()
59+
.map(|(key, value)| {
60+
(
61+
JsString::from(key.as_str()),
62+
JsString::from(value.to_str().unwrap_or_default()),
63+
)
64+
})
65+
.collect()
66+
};
67+
5568
Self {
56-
headers,
69+
headers: headers_vec,
5770
index: 0,
5871
kind,
5972
}
6073
}
6174

6275
/// Gets the next entry in the headers iterator.
63-
fn next(&mut self, context: &mut Context) -> JsResult<JsValue> {
64-
let headers_data = self.headers.headers.borrow();
65-
let headers_vec: Vec<_> = headers_data.iter().collect();
66-
67-
if self.index >= headers_vec.len() {
68-
return Ok(create_iter_result_object(JsValue::undefined(), true, context));
76+
fn next(&mut self, context: &mut Context) -> JsValue {
77+
if self.index >= self.headers.len() {
78+
return create_iter_result_object(JsValue::undefined(), true, context);
6979
}
7080

71-
let (key, value) = headers_vec[self.index];
81+
let (key, value) = &self.headers[self.index];
7282
self.index += 1;
7383

7484
let value_obj = match self.kind {
7585
HeadersIteratorKind::Entries => {
76-
let key_val: JsValue = JsString::from(key.as_str()).into();
77-
let val_val: JsValue = JsString::from(value.to_str().unwrap_or_default()).into();
86+
let key_val: JsValue = key.clone().into();
87+
let val_val: JsValue = value.clone().into();
7888
JsArray::from_iter([key_val, val_val], context).into()
7989
}
80-
HeadersIteratorKind::Keys => JsString::from(key.as_str()).into(),
81-
HeadersIteratorKind::Values => JsString::from(value.to_str().unwrap_or_default()).into(),
90+
HeadersIteratorKind::Keys => key.clone().into(),
91+
HeadersIteratorKind::Values => value.clone().into(),
8292
};
8393

84-
Ok(create_iter_result_object(value_obj, false, context))
94+
create_iter_result_object(value_obj, false, context)
8595
}
8696
}
8797

@@ -122,12 +132,19 @@ fn headers_iterator_next(
122132
.and_then(JsObject::downcast_mut::<HeadersIterator>)
123133
.ok_or_else(|| js_error!(TypeError: "`this` is not a Headers iterator"))?;
124134

125-
iterator.next(context)
135+
Ok(iterator.next(context))
126136
}
127137

128138
/// Creates a Headers iterator object wrapper.
129-
fn create_headers_iterator_object(iterator: HeadersIterator, context: &mut Context) -> JsValue {
130-
let proto = context.intrinsics().objects().iterator_prototypes().iterator();
139+
fn create_headers_iterator_object(
140+
iterator: HeadersIterator,
141+
context: &mut Context,
142+
) -> JsResult<JsValue> {
143+
let proto = context
144+
.intrinsics()
145+
.objects()
146+
.iterator_prototypes()
147+
.iterator();
131148
let iterator_obj = JsObject::from_proto_and_data(proto, iterator);
132149

133150
let next_fn = FunctionObjectBuilder::new(
@@ -139,18 +156,17 @@ fn create_headers_iterator_object(iterator: HeadersIterator, context: &mut Conte
139156
.constructor(false)
140157
.build();
141158

142-
#[allow(let_underscore_drop)]
143-
let _ = iterator_obj.define_property_or_throw(
159+
iterator_obj.define_property_or_throw(
144160
js_string!("next"),
145161
PropertyDescriptor::builder()
146162
.value(next_fn)
147163
.writable(true)
148164
.enumerable(false)
149165
.configurable(true),
150166
context,
151-
);
167+
)?;
152168

153-
iterator_obj.into()
169+
Ok(iterator_obj.into())
154170
}
155171

156172
/// A JavaScript wrapper for the `Headers` object.
@@ -266,10 +282,13 @@ impl JsHeaders {
266282

267283
/// Returns an iterator allowing to go through all key/value pairs contained in this object.
268284
///
285+
/// # Errors
286+
/// Returns an error if the iterator object cannot be created.
287+
///
269288
/// More information:
270289
/// - [WHATWG spec](https://fetch.spec.whatwg.org/#headers-class)
271290
#[boa(method)]
272-
pub fn entries(this: JsClass<Self>, context: &mut Context) -> JsValue {
291+
pub fn entries(this: JsClass<Self>, context: &mut Context) -> JsResult<JsValue> {
273292
let iterator = HeadersIterator::new(this.clone_inner(), HeadersIteratorKind::Entries);
274293
create_headers_iterator_object(iterator, context)
275294
}
@@ -348,10 +367,13 @@ impl JsHeaders {
348367
/// Returns an iterator allowing you to go through all keys of the key/value pairs
349368
/// contained in this object.
350369
///
370+
/// # Errors
371+
/// Returns an error if the iterator object cannot be created.
372+
///
351373
/// More information:
352374
/// - [WHATWG spec](https://fetch.spec.whatwg.org/#headers-class)
353375
#[boa(method)]
354-
fn keys(this: JsClass<Self>, context: &mut Context) -> JsValue {
376+
fn keys(this: JsClass<Self>, context: &mut Context) -> JsResult<JsValue> {
355377
let iterator = HeadersIterator::new(this.clone_inner(), HeadersIteratorKind::Keys);
356378
create_headers_iterator_object(iterator, context)
357379
}
@@ -367,10 +389,13 @@ impl JsHeaders {
367389

368390
/// Returns an iterator allowing you to go through all values in the Headers object.
369391
///
392+
/// # Errors
393+
/// Returns an error if the iterator object cannot be created.
394+
///
370395
/// More information:
371396
/// - [WHATWG spec](https://fetch.spec.whatwg.org/#headers-class)
372397
#[boa(method)]
373-
pub fn values(this: JsClass<Self>, context: &mut Context) -> JsValue {
398+
pub fn values(this: JsClass<Self>, context: &mut Context) -> JsResult<JsValue> {
374399
let iterator = HeadersIterator::new(this.clone_inner(), HeadersIteratorKind::Values);
375400
create_headers_iterator_object(iterator, context)
376401
}

core/runtime/src/fetch/mod.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ use crate::fetch::headers::JsHeaders;
1111
use crate::fetch::request::{JsRequest, RequestInit};
1212
use crate::fetch::response::JsResponse;
1313
use boa_engine::class::Class;
14-
use boa_engine::object::FunctionObjectBuilder;
1514
use boa_engine::property::PropertyDescriptor;
1615
use boa_engine::realm::Realm;
1716
use boa_engine::{
1817
Context, Finalize, JsData, JsError, JsObject, JsResult, JsString, JsSymbol, JsValue,
19-
NativeObject, Trace, boa_module, js_error, js_string, native_function::NativeFunction,
18+
NativeObject, Trace, boa_module, js_error, js_string,
2019
};
2120
use either::Either;
2221
use http::{HeaderName, HeaderValue, Request as HttpRequest, Request};
@@ -204,22 +203,6 @@ pub mod js_module {
204203
#[doc(inline)]
205204
pub use js_module::fetch;
206205

207-
fn headers_iterator(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
208-
// Call the entries method through the JavaScript object mechanism
209-
let this_object = this.as_object()
210-
.ok_or_else(|| {
211-
js_error!(TypeError: "`Headers.prototype[Symbol.iterator]` requires a `Headers` object")
212-
})?;
213-
214-
// Get the entries method from the prototype and call it
215-
let entries_fn = this_object
216-
.get(js_string!("entries"), context)?;
217-
218-
entries_fn.as_function()
219-
.ok_or_else(|| js_error!(TypeError: "entries is not a function"))?
220-
.call(this, &[], context)
221-
}
222-
223206
/// Register the `fetch` function in the realm, as well as ALL supporting classes.
224207
/// Pass `None` as the realm to register globally.
225208
///
@@ -246,19 +229,12 @@ pub fn register<F: Fetcher>(
246229
.ok_or_else(|| js_error!(Error: "Headers class should be registered"))?
247230
.prototype();
248231

249-
let iterator = FunctionObjectBuilder::new(
250-
context.realm(),
251-
NativeFunction::from_fn_ptr(headers_iterator),
252-
)
253-
.name(js_string!("[Symbol.iterator]"))
254-
.length(0)
255-
.constructor(false)
256-
.build();
232+
let entries_fn = headers_proto.get(js_string!("entries"), context)?;
257233

258234
headers_proto.define_property_or_throw(
259235
JsSymbol::iterator(),
260236
PropertyDescriptor::builder()
261-
.value(iterator)
237+
.value(entries_fn)
262238
.writable(true)
263239
.enumerable(false)
264240
.configurable(true),

core/runtime/src/fetch/response.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,19 @@ impl JsResponse {
179179
}
180180

181181
let mut js_response = Self::basic(js_string!(""), http::Response::new(Vec::new()));
182-
js_response.status = Some(StatusCode::from_u16(status).expect("guaranteed to be a valid status by preceding match"));
182+
js_response.status = Some(
183+
StatusCode::from_u16(status)
184+
.expect("guaranteed to be a valid status by preceding match"),
185+
);
186+
187+
let url_str = url.to_std_string_escaped();
188+
let parsed_url = url::Url::parse(&url_str)
189+
.map_err(|_| JsNativeError::typ().with_message("Invalid redirect URL"))?;
183190

184191
let mut headers = JsHeaders::default();
185192
headers.append(
186193
Convert::from("Location".to_string()),
187-
Convert::from(url.to_std_string_escaped()),
194+
Convert::from(parsed_url.to_string()),
188195
)?;
189196
js_response.headers = headers;
190197

@@ -201,16 +208,13 @@ impl JsResponse {
201208
init: Option<JsResponseOptions>,
202209
context: &mut Context,
203210
) -> JsResult<Self> {
204-
let json = context.global_object().get(js_string!("JSON"), context)?;
205-
let stringify = json
206-
.as_object()
207-
.ok_or_else(|| JsNativeError::typ().with_message("JSON is not an object"))?
208-
.get(js_string!("stringify"), context)?;
211+
let json = context.intrinsics().objects().json();
212+
let stringify = json.get(js_string!("stringify"), context)?;
209213

210214
let text = stringify
211215
.as_callable()
212216
.ok_or_else(|| JsNativeError::typ().with_message("stringify is not a function"))?
213-
.call(&json, &[data], context)?;
217+
.call(&json.into(), &[data], context)?;
214218

215219
let text = if let Some(s) = text.as_string() {
216220
s.to_std_string_escaped()

core/runtime/src/fetch/tests/headers.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,40 @@ fn headers_are_iterable() {
2424
),
2525
]);
2626
}
27+
28+
#[test]
29+
fn headers_iterators() {
30+
run_test_actions([
31+
TestAction::harness(),
32+
TestAction::inspect_context(register),
33+
TestAction::run(
34+
r#"
35+
const headers = new Headers([["a", "1"], ["b", "2"]]);
36+
37+
// entries()
38+
const entriesList = [...headers.entries()];
39+
assertEq(entriesList.length, 2);
40+
assertEq(entriesList[0][0], "a");
41+
assertEq(entriesList[0][1], "1");
42+
43+
// keys()
44+
const keysList = [...headers.keys()];
45+
assertEq(keysList.length, 2);
46+
assertEq(keysList[0], "a");
47+
assertEq(keysList[1], "b");
48+
49+
// values()
50+
const valuesList = [...headers.values()];
51+
assertEq(valuesList.length, 2);
52+
assertEq(valuesList[0], "1");
53+
assertEq(valuesList[1], "2");
54+
55+
// .next() directly
56+
const it = headers.entries();
57+
const first = it.next();
58+
assertEq(first.done, false);
59+
assertEq(first.value[0], "a");
60+
"#,
61+
),
62+
]);
63+
}

0 commit comments

Comments
 (0)