Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions core/runtime/src/fetch/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Response

use crate::fetch::headers::JsHeaders;
use boa_engine::object::builtins::{JsPromise, JsUint8Array};
use boa_engine::object::builtins::{
JsArrayBuffer, JsDataView, JsPromise, JsTypedArray, JsUint8Array,
};
use boa_engine::value::{Convert, TryFromJs, TryIntoJs};
use boa_engine::{
Context, JsData, JsNativeError, JsResult, JsString, JsValue, boa_class, js_error, js_str,
Expand Down Expand Up @@ -277,9 +279,47 @@ fn initialize_response(
///
/// See <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
fn extract_body(val: &JsValue, context: &mut Context) -> JsResult<(Vec<u8>, Option<&'static str>)> {
// TODO: handle other BodyInit types: Blob, ArrayBuffer, ArrayBufferView,
// FormData, URLSearchParams.
// Currently only USVString is supported.
if let Some(obj) = val.as_object() {
if let Ok(ab) = JsArrayBuffer::from_object(obj.clone()) {
let bytes = ab
.to_vec()
.ok_or_else(|| JsNativeError::typ().with_message("ArrayBuffer is detached"))?;
return Ok((bytes, None));
}

if let Ok(ta) = JsTypedArray::from_object(obj.clone()) {
let offset = ta.byte_offset(context)?;
let len = ta.byte_length(context)?;
let buffer_obj = ta.buffer(context)?.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("TypedArray buffer is not an object")
})?;

let ab = JsArrayBuffer::from_object(buffer_obj)?;
let data = ab
.data()
.ok_or_else(|| JsNativeError::typ().with_message("ArrayBuffer is detached"))?;
return Ok((data[offset..offset + len].to_vec(), None));
}

if let Ok(dv) = JsDataView::from_object(obj.clone()) {
let offset = usize::try_from(dv.byte_offset(context)?)
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;
let len = usize::try_from(dv.byte_length(context)?)
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;
let buffer_obj = dv.buffer(context)?.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("DataView buffer is not an object")
})?;

let ab = JsArrayBuffer::from_object(buffer_obj)?;
let data = ab
.data()
.ok_or_else(|| JsNativeError::typ().with_message("ArrayBuffer is detached"))?;
return Ok((data[offset..offset + len].to_vec(), None));
}
}

// TODO: handle other BodyInit types: Blob, FormData, URLSearchParams.
// Currently Strings and primitives fallback here.
//
// For a USVString, the type is "text/plain;charset=UTF-8".
// See https://fetch.spec.whatwg.org/#concept-bodyinit-extract step 6.
Expand Down
55 changes: 54 additions & 1 deletion core/runtime/src/fetch/tests/response.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::TestFetcher;
use crate::test::{TestAction, run_test_actions};
use boa_engine::{Context, js_str};
use boa_engine::{Context, JsValue, js_str};
use http::{Response, Uri};

fn register(responses: &[(&'static str, Response<Vec<u8>>)], ctx: &mut Context) {
Expand Down Expand Up @@ -354,3 +354,56 @@ fn response_clone_preserves_status() {
}),
]);
}

#[test]
fn response_body_buffer_sources() {
run_test_actions([
TestAction::harness(),
TestAction::inspect_context(|ctx| register(&[], ctx)),
TestAction::run(
r#"
// ArrayBuffer body
let ab = new Uint8Array([104,101,108,108,111]).buffer;
let r0 = new Response(ab);
assertEq(r0.headers.has("content-type"), false);

// Uint8Array body (no content-type should be injected)
let u8a = new Uint8Array([104,101,108,108,111]);
let r1 = new Response(u8a);
assertEq(r1.headers.has("content-type"), false);

// Subarray/view slicing
let sub = new Uint8Array([0,104,101,108,108,111,0]).subarray(1, 6);
let r2 = new Response(sub);
assertEq(r2.headers.has("content-type"), false);

// DataView with non-zero offset
let buffer = new Uint8Array([0,104,101,108,108,111,0]).buffer;
let dv = new DataView(buffer, 1, 5);
let r3 = new Response(dv);
assertEq(r3.headers.has("content-type"), false);

// String body fallback (should retain default text content-type)
let r4 = new Response("hello");
assertEq(r4.headers.get("content-type"), "text/plain;charset=UTF-8");

globalThis.verify = async () => {
assertEq(await r0.text(), "hello");
assertEq(await r1.text(), "hello");
assertEq(await r2.text(), "hello");
assertEq(await r3.text(), "hello");
assertEq(await r4.text(), "hello");
};
"#,
),
TestAction::inspect_context(|ctx| {
let verify_fn = ctx.global_object().get(js_str!("verify"), ctx).unwrap();
let promise = verify_fn
.as_callable()
.unwrap()
.call(&JsValue::undefined(), &[], ctx)
.unwrap();
promise.as_promise().unwrap().await_blocking(ctx).unwrap();
}),
]);
}
Loading