use std::io::Cursor;
use std::rc::Rc;
use std::{fs, ptr, slice, str};
use encoding_rs::{Encoding, UTF_8};
use http::HeaderMap;
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use js::jsapi::{Heap, JS_ClearPendingException, JSObject, Value as JSValue};
use js::jsval::{JSVal, UndefinedValue};
use js::realm::CurrentRealm;
use js::rust::HandleValue;
use js::rust::wrappers::{JS_GetPendingException, JS_ParseJSON};
use js::typedarray::{ArrayBufferU8, Uint8};
use mime::{self, Mime};
use mime_multipart_hyper1::{Node, read_multipart_body};
use net_traits::request::{
BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
};
use servo_base::generic_channel::GenericSharedMemory;
use servo_constellation_traits::BlobImpl;
use url::form_urlencoded;
use crate::dom::bindings::buffer_source::create_buffer_source;
use crate::dom::bindings::codegen::Bindings::BlobBinding::Blob_Binding::BlobMethods;
use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{DomGlobal, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::blob::{Blob, normalize_type_string};
use crate::dom::file::File;
use crate::dom::formdata::FormData;
use crate::dom::globalscope::GlobalScope;
use crate::dom::html::htmlformelement::{encode_multipart_form_data, generate_boundary};
use crate::dom::promise::Promise;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::readablestream::{ReadableStream, get_read_promise_bytes, get_read_promise_done};
use crate::dom::urlsearchparams::URLSearchParams;
use crate::realms::{AlreadyInRealm, InRealm, enter_auto_realm, enter_realm};
use crate::script_runtime::{CanGc, JSContext};
use crate::task_source::SendableTaskSource;
pub(crate) fn clone_body_stream_for_dom_body(
original_body_stream: &MutNullableDom<ReadableStream>,
cloned_body_stream: &MutNullableDom<ReadableStream>,
can_gc: CanGc,
) -> Fallible<()> {
let Some(stream) = original_body_stream.get() else {
return Ok(());
};
let branches = stream.tee(true, can_gc)?;
let out1 = &*branches[0];
let out2 = &*branches[1];
original_body_stream.set(Some(out1));
cloned_body_stream.set(Some(out2));
Ok(())
}
#[derive(Clone, PartialEq)]
pub(crate) enum BodySource {
Null,
Object,
}
enum StopReading {
Error,
Done,
}
#[derive(Clone)]
struct TransmitBodyConnectHandler {
stream: Trusted<ReadableStream>,
task_source: SendableTaskSource,
bytes_sender: Option<IpcSender<BodyChunkResponse>>,
control_sender: Option<IpcSender<BodyChunkRequest>>,
in_memory: Option<GenericSharedMemory>,
in_memory_done: bool,
source: BodySource,
}
impl TransmitBodyConnectHandler {
pub(crate) fn new(
stream: Trusted<ReadableStream>,
task_source: SendableTaskSource,
control_sender: IpcSender<BodyChunkRequest>,
in_memory: Option<GenericSharedMemory>,
source: BodySource,
) -> TransmitBodyConnectHandler {
TransmitBodyConnectHandler {
stream,
task_source,
bytes_sender: None,
control_sender: Some(control_sender),
in_memory,
in_memory_done: false,
source,
}
}
pub(crate) fn reset_in_memory_done(&mut self) {
self.in_memory_done = false;
}
fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
let mut body_handler = self.clone();
body_handler.reset_in_memory_done();
ROUTER.add_typed_route(
chunk_request_receiver,
Box::new(move |message| {
let request = message.unwrap();
match request {
BodyChunkRequest::Connect(sender) => {
body_handler.start_reading(sender);
},
BodyChunkRequest::Extract(receiver) => {
body_handler.re_extract(receiver);
},
BodyChunkRequest::Chunk => body_handler.transmit_source(),
BodyChunkRequest::Done => {
body_handler.stop_reading(StopReading::Done);
},
BodyChunkRequest::Error => {
body_handler.stop_reading(StopReading::Error);
},
}
}),
);
}
fn transmit_source(&mut self) {
if self.in_memory_done {
self.stop_reading(StopReading::Done);
return;
}
if let BodySource::Null = self.source {
panic!("ReadableStream(Null) sources should not re-direct.");
}
if let Some(bytes) = self.in_memory.clone() {
self.in_memory_done = true;
let _ = self
.bytes_sender
.as_ref()
.expect("No bytes sender to transmit source.")
.send(BodyChunkResponse::Chunk(bytes));
return;
}
warn!("Re-directs for file-based Blobs not supported yet.");
}
fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
self.bytes_sender = Some(sender);
if self.source == BodySource::Null {
let stream = self.stream.clone();
self.task_source
.queue(task!(start_reading_request_body_stream: move || {
let rooted_stream = stream.root();
rooted_stream.acquire_default_reader(CanGc::note())
.expect("Couldn't acquire a reader for the body stream.");
}));
}
}
fn stop_reading(&mut self, reason: StopReading) {
let bytes_sender = self
.bytes_sender
.take()
.expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
match reason {
StopReading::Error => {
let _ = bytes_sender.send(BodyChunkResponse::Error);
},
StopReading::Done => {
let _ = bytes_sender.send(BodyChunkResponse::Done);
},
}
let _ = self.control_sender.take();
}
fn transmit_body_chunk(&mut self) {
if self.in_memory_done {
self.stop_reading(StopReading::Done);
return;
}
let stream = self.stream.clone();
let control_sender = self.control_sender.clone();
let bytes_sender = self
.bytes_sender
.clone()
.expect("No bytes sender to transmit chunk.");
if let Some(bytes) = self.in_memory.clone() {
let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
self.in_memory_done = true;
return;
}
self.task_source.queue(
task!(setup_native_body_promise_handler: move |cx| {
let rooted_stream = stream.root();
let global = rooted_stream.global();
let promise = rooted_stream.read_a_chunk(cx);
rooted!(&in(cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
bytes_sender: bytes_sender.clone(),
stream: Dom::from_ref(&rooted_stream),
control_sender: control_sender.clone().unwrap(),
}));
rooted!(&in(cx) let mut rejection_handler = Some(TransmitBodyPromiseRejectionHandler {
bytes_sender,
stream: Dom::from_ref(&rooted_stream),
control_sender: control_sender.unwrap(),
}));
let handler =
PromiseNativeHandler::new(&global, promise_handler.take().map(|h| Box::new(h) as Box<_>), rejection_handler.take().map(|h| Box::new(h) as Box<_>), CanGc::from_cx(cx));
let mut realm = enter_auto_realm(cx, &*global);
let realm = &mut realm.current_realm();
let in_realm_proof = realm.into();
let comp = InRealm::Already(&in_realm_proof);
promise.append_native_handler(&handler, comp, CanGc::from_cx(realm));
})
);
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct TransmitBodyPromiseHandler {
#[no_trace]
bytes_sender: IpcSender<BodyChunkResponse>,
stream: Dom<ReadableStream>,
#[no_trace]
control_sender: IpcSender<BodyChunkRequest>,
}
impl js::gc::Rootable for TransmitBodyPromiseHandler {}
impl Callback for TransmitBodyPromiseHandler {
fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
let can_gc = CanGc::from_cx(cx);
let _realm = InRealm::Already(&cx.into());
let cx = cx.into();
let is_done = match get_read_promise_done(cx, &v, can_gc) {
Ok(is_done) => is_done,
Err(_) => {
let _ = self.control_sender.send(BodyChunkRequest::Done);
return self.stream.stop_reading(can_gc);
},
};
if is_done {
let _ = self.control_sender.send(BodyChunkRequest::Done);
return self.stream.stop_reading(can_gc);
}
let chunk = match get_read_promise_bytes(cx, &v, can_gc) {
Ok(chunk) => chunk,
Err(_) => {
let _ = self.control_sender.send(BodyChunkRequest::Error);
return self.stream.stop_reading(can_gc);
},
};
let _ = self
.bytes_sender
.send(BodyChunkResponse::Chunk(GenericSharedMemory::from_bytes(
&chunk,
)));
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct TransmitBodyPromiseRejectionHandler {
#[no_trace]
bytes_sender: IpcSender<BodyChunkResponse>,
stream: Dom<ReadableStream>,
#[no_trace]
control_sender: IpcSender<BodyChunkRequest>,
}
impl js::gc::Rootable for TransmitBodyPromiseRejectionHandler {}
impl Callback for TransmitBodyPromiseRejectionHandler {
fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
let _ = self.control_sender.send(BodyChunkRequest::Error);
self.stream.stop_reading(CanGc::from_cx(cx));
}
}
pub(crate) struct ExtractedBody {
pub(crate) stream: DomRoot<ReadableStream>,
pub(crate) source: BodySource,
pub(crate) total_bytes: Option<usize>,
pub(crate) content_type: Option<DOMString>,
}
impl ExtractedBody {
pub(crate) fn into_net_request_body(self) -> (RequestBody, DomRoot<ReadableStream>) {
let ExtractedBody {
stream,
total_bytes,
content_type: _,
source,
} = self;
let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
let trusted_stream = Trusted::new(&*stream);
let global = stream.global();
let task_source = global.task_manager().networking_task_source();
let in_memory = stream.get_in_memory_bytes();
let net_source = match source {
BodySource::Null => NetBodySource::Null,
_ => NetBodySource::Object,
};
let mut body_handler = TransmitBodyConnectHandler::new(
trusted_stream,
task_source.into(),
chunk_request_sender.clone(),
in_memory,
source,
);
ROUTER.add_typed_route(
chunk_request_receiver,
Box::new(move |message| {
match message.unwrap() {
BodyChunkRequest::Connect(sender) => {
body_handler.start_reading(sender);
},
BodyChunkRequest::Extract(receiver) => {
body_handler.re_extract(receiver);
},
BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
BodyChunkRequest::Done => {
body_handler.stop_reading(StopReading::Done);
},
BodyChunkRequest::Error => {
body_handler.stop_reading(StopReading::Error);
},
}
}),
);
let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
(request_body, stream)
}
pub(crate) fn in_memory(&self) -> bool {
self.stream.in_memory()
}
}
pub(crate) trait Extractable {
fn extract(
&self,
global: &GlobalScope,
keep_alive: bool,
can_gc: CanGc,
) -> Fallible<ExtractedBody>;
}
impl Extractable for BodyInit {
fn extract(
&self,
global: &GlobalScope,
keep_alive: bool,
can_gc: CanGc,
) -> Fallible<ExtractedBody> {
match self {
BodyInit::String(s) => s.extract(global, keep_alive, can_gc),
BodyInit::URLSearchParams(usp) => usp.extract(global, keep_alive, can_gc),
BodyInit::Blob(b) => b.extract(global, keep_alive, can_gc),
BodyInit::FormData(formdata) => formdata.extract(global, keep_alive, can_gc),
BodyInit::ArrayBuffer(typedarray) => {
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: None,
source: BodySource::Object,
})
},
BodyInit::ArrayBufferView(typedarray) => {
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: None,
source: BodySource::Object,
})
},
BodyInit::ReadableStream(stream) => {
if keep_alive {
return Err(Error::Type(
c"The body's stream is for a keepalive request".to_owned(),
));
}
if stream.is_locked() || stream.is_disturbed() {
return Err(Error::Type(
c"The body's stream is disturbed or locked".to_owned(),
));
}
Ok(ExtractedBody {
stream: stream.clone(),
total_bytes: None,
content_type: None,
source: BodySource::Null,
})
},
}
}
}
impl Extractable for Vec<u8> {
fn extract(
&self,
global: &GlobalScope,
_keep_alive: bool,
can_gc: CanGc,
) -> Fallible<ExtractedBody> {
let bytes = self.clone();
let total_bytes = self.len();
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: None,
source: BodySource::Object,
})
}
}
impl Extractable for Blob {
fn extract(
&self,
_global: &GlobalScope,
_keep_alive: bool,
can_gc: CanGc,
) -> Fallible<ExtractedBody> {
let blob_type = self.Type();
let content_type = if blob_type.is_empty() {
None
} else {
Some(blob_type)
};
let total_bytes = self.Size() as usize;
let stream = self.get_stream(can_gc)?;
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type,
source: BodySource::Object,
})
}
}
impl Extractable for DOMString {
fn extract(
&self,
global: &GlobalScope,
_keep_alive: bool,
can_gc: CanGc,
) -> Fallible<ExtractedBody> {
let bytes = self.as_bytes().to_owned();
let total_bytes = bytes.len();
let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type,
source: BodySource::Object,
})
}
}
impl Extractable for FormData {
fn extract(
&self,
global: &GlobalScope,
_keep_alive: bool,
can_gc: CanGc,
) -> Fallible<ExtractedBody> {
let boundary = generate_boundary();
let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
let total_bytes = bytes.len();
let content_type = Some(DOMString::from(format!(
"multipart/form-data; boundary={}",
boundary
)));
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type,
source: BodySource::Object,
})
}
}
impl Extractable for URLSearchParams {
fn extract(
&self,
global: &GlobalScope,
_keep_alive: bool,
can_gc: CanGc,
) -> Fallible<ExtractedBody> {
let bytes = self.serialize_utf8().into_bytes();
let total_bytes = bytes.len();
let content_type = Some(DOMString::from(
"application/x-www-form-urlencoded;charset=UTF-8",
));
let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
Ok(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type,
source: BodySource::Object,
})
}
}
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
pub(crate) enum BodyType {
Blob,
Bytes,
FormData,
Json,
Text,
ArrayBuffer,
}
pub(crate) enum FetchedData {
Text(String),
Json(RootedTraceableBox<Heap<JSValue>>),
BlobData(DomRoot<Blob>),
Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
FormData(DomRoot<FormData>),
ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
JSException(RootedTraceableBox<Heap<JSVal>>),
}
pub(crate) fn consume_body<T: BodyMixin + DomObject>(
object: &T,
body_type: BodyType,
can_gc: CanGc,
) -> Rc<Promise> {
let global = object.global();
let cx = GlobalScope::get_cx();
let realm = enter_realm(&*global);
let comp = InRealm::Entered(&realm);
let promise = Promise::new_in_current_realm(comp, can_gc);
if object.is_unusable() {
promise.reject_error(
Error::Type(c"The body's stream is disturbed or locked".to_owned()),
can_gc,
);
return promise;
}
let stream = match object.body() {
Some(stream) => stream,
None => {
resolve_result_promise(
body_type,
&promise,
object.get_mime_type(can_gc),
Vec::with_capacity(0),
cx,
can_gc,
);
return promise;
},
};
if stream.is_errored() {
rooted!(in(*cx) let mut stored_error = UndefinedValue());
stream.get_stored_error(stored_error.handle_mut());
promise.reject(cx, stored_error.handle(), can_gc);
return promise;
}
let reader = match stream.acquire_default_reader(can_gc) {
Ok(r) => r,
Err(e) => {
promise.reject_error(e, can_gc);
return promise;
},
};
let error_promise = promise.clone();
let mime_type = object.get_mime_type(can_gc);
let success_promise = promise.clone();
reader.read_all_bytes(
cx,
Rc::new(move |bytes: &[u8]| {
resolve_result_promise(
body_type,
&success_promise,
mime_type.clone(),
bytes.to_vec(),
cx,
can_gc,
);
}),
Rc::new(move |cx, v| {
error_promise.reject(cx, v, can_gc);
}),
can_gc,
);
promise
}
fn resolve_result_promise(
body_type: BodyType,
promise: &Promise,
mime_type: Vec<u8>,
body: Vec<u8>,
cx: JSContext,
can_gc: CanGc,
) {
let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type, can_gc);
match pkg_data_results {
Ok(results) => {
match results {
FetchedData::Text(s) => promise.resolve_native(&USVString(s), can_gc),
FetchedData::Json(j) => promise.resolve_native(&j, can_gc),
FetchedData::BlobData(b) => promise.resolve_native(&b, can_gc),
FetchedData::FormData(f) => promise.resolve_native(&f, can_gc),
FetchedData::Bytes(b) => promise.resolve_native(&b, can_gc),
FetchedData::ArrayBuffer(a) => promise.resolve_native(&a, can_gc),
FetchedData::JSException(e) => promise.reject_native(&e.handle(), can_gc),
};
},
Err(err) => promise.reject_error(err, can_gc),
}
}
fn run_package_data_algorithm(
cx: JSContext,
bytes: Vec<u8>,
body_type: BodyType,
mime_type: Vec<u8>,
can_gc: CanGc,
) -> Fallible<FetchedData> {
let mime = &*mime_type;
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
let global = GlobalScope::from_safe_context(cx, InRealm::Already(&in_realm_proof));
match body_type {
BodyType::Text => run_text_data_algorithm(bytes),
BodyType::Json => run_json_data_algorithm(cx, bytes),
BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime, can_gc),
BodyType::FormData => run_form_data_algorithm(&global, bytes, mime, can_gc),
BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes, can_gc),
BodyType::Bytes => run_bytes_data_algorithm(cx, bytes, can_gc),
}
}
fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
&bytes[3..]
} else {
&bytes
};
Ok(FetchedData::Text(
String::from_utf8_lossy(no_bom_bytes).into_owned(),
))
}
#[expect(unsafe_code)]
fn run_json_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallible<FetchedData> {
let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
rooted!(in(*cx) let mut rval = UndefinedValue());
unsafe {
if !JS_ParseJSON(
*cx,
json_text.as_ptr(),
json_text.len() as u32,
rval.handle_mut(),
) {
rooted!(in(*cx) let mut exception = UndefinedValue());
assert!(JS_GetPendingException(*cx, exception.handle_mut()));
JS_ClearPendingException(*cx);
return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
Heap::boxed(exception.get()),
)));
}
let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
Ok(FetchedData::Json(rooted_heap))
}
}
fn run_blob_data_algorithm(
root: &GlobalScope,
bytes: Vec<u8>,
mime: &[u8],
can_gc: CanGc,
) -> Fallible<FetchedData> {
let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
s
} else {
"".to_string()
};
let blob = Blob::new(
root,
BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
can_gc,
);
Ok(FetchedData::BlobData(blob))
}
fn extract_name_from_content_disposition(headers: &HeaderMap) -> Option<String> {
let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
for part in cd.split(';').map(|s| s.trim()) {
if let Some(rest) = part.strip_prefix("name=") {
let v = rest.trim();
let v = v.strip_prefix('"').unwrap_or(v);
let v = v.strip_suffix('"').unwrap_or(v);
return Some(v.to_string());
}
}
None
}
fn extract_filename_from_content_disposition(headers: &HeaderMap) -> Option<String> {
let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
if let Some(index) = cd.find("filename=") {
let start = index + "filename=".len();
return Some(
cd.get(start..)
.unwrap_or_default()
.trim_matches('"')
.to_owned(),
);
}
if let Some(index) = cd.find("filename*=UTF-8''") {
let start = index + "filename*=UTF-8''".len();
return Some(
cd.get(start..)
.unwrap_or_default()
.trim_matches('"')
.to_owned(),
);
}
None
}
fn content_type_from_headers(headers: &HeaderMap) -> Result<String, Error> {
match headers.get(CONTENT_TYPE) {
Some(value) => Ok(value
.to_str()
.map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?
.to_string()),
None => Ok("text/plain".to_string()),
}
}
fn append_form_data_entry_from_part(
root: &GlobalScope,
formdata: &FormData,
headers: &HeaderMap,
body: Vec<u8>,
can_gc: CanGc,
) -> Fallible<()> {
let Some(name) = extract_name_from_content_disposition(headers) else {
return Ok(());
};
let filename = extract_filename_from_content_disposition(headers);
if let Some(filename) = filename {
let content_type = content_type_from_headers(headers)?;
let file = File::new(
root,
BlobImpl::new_from_bytes(body, normalize_type_string(&content_type)),
DOMString::from(filename),
None,
can_gc,
);
let blob = file.upcast::<Blob>();
formdata.Append_(USVString(name), blob, None);
} else {
let (value, _) = UTF_8.decode_without_bom_handling(&body);
formdata.Append(USVString(name), USVString(value.to_string()));
}
Ok(())
}
fn append_multipart_nodes(
root: &GlobalScope,
formdata: &FormData,
nodes: Vec<Node>,
can_gc: CanGc,
) -> Fallible<()> {
for node in nodes {
match node {
Node::Part(part) => {
append_form_data_entry_from_part(root, formdata, &part.headers, part.body, can_gc)?;
},
Node::File(file_part) => {
let body = fs::read(&file_part.path)
.map_err(|_| Error::Type(c"file part could not be read".to_owned()))?;
append_form_data_entry_from_part(root, formdata, &file_part.headers, body, can_gc)?;
},
Node::Multipart((_, inner)) => {
append_multipart_nodes(root, formdata, inner, can_gc)?;
},
}
}
Ok(())
}
fn run_form_data_algorithm(
root: &GlobalScope,
bytes: Vec<u8>,
mime: &[u8],
can_gc: CanGc,
) -> Fallible<FetchedData> {
let mime_str = str::from_utf8(mime).unwrap_or_default();
let mime: Mime = mime_str
.parse()
.map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
let mut headers = HeaderMap::new();
headers.insert(
CONTENT_TYPE,
mime_str
.parse()
.map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?,
);
if let Some(boundary) = mime.get_param(mime::BOUNDARY) {
let closing_boundary = format!("--{}--", boundary.as_str()).into_bytes();
let trimmed_bytes = bytes.strip_suffix(b"\r\n").unwrap_or(&bytes);
if trimmed_bytes == closing_boundary {
let formdata = FormData::new(None, root, can_gc);
return Ok(FetchedData::FormData(formdata));
}
}
let mut cursor = Cursor::new(bytes);
let nodes = read_multipart_body(&mut cursor, &headers, false)
.map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
let formdata = FormData::new(None, root, can_gc);
append_multipart_nodes(root, &formdata, nodes, can_gc)?;
return Ok(FetchedData::FormData(formdata));
}
if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
let entries = form_urlencoded::parse(&bytes);
let formdata = FormData::new(None, root, can_gc);
for (k, e) in entries {
formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
}
return Ok(FetchedData::FormData(formdata));
}
Err(Error::Type(c"Inappropriate MIME-type for Body".to_owned()))
}
fn run_bytes_data_algorithm(cx: JSContext, bytes: Vec<u8>, can_gc: CanGc) -> Fallible<FetchedData> {
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
create_buffer_source::<Uint8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
.map_err(|_| Error::JSFailed)?;
let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
Ok(FetchedData::Bytes(rooted_heap))
}
pub(crate) fn run_array_buffer_data_algorithm(
cx: JSContext,
bytes: Vec<u8>,
can_gc: CanGc,
) -> Fallible<FetchedData> {
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
create_buffer_source::<ArrayBufferU8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
.map_err(|_| Error::JSFailed)?;
let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
Ok(FetchedData::ArrayBuffer(rooted_heap))
}
#[expect(unsafe_code)]
pub(crate) fn decode_to_utf16_with_bom_removal(
bytes: &[u8],
encoding: &'static Encoding,
) -> Vec<u16> {
let mut decoder = encoding.new_decoder_with_bom_removal();
let capacity = decoder
.max_utf16_buffer_length(bytes.len())
.expect("Overflow");
let mut utf16 = Vec::with_capacity(capacity);
let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) };
let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, true);
assert_eq!(read, bytes.len());
unsafe { utf16.set_len(written) }
utf16
}
pub(crate) trait BodyMixin {
fn is_body_used(&self) -> bool;
fn is_unusable(&self) -> bool;
fn body(&self) -> Option<DomRoot<ReadableStream>>;
fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8>;
}