diff --git a/runtime/src/globals/fetch/header.rs b/runtime/src/globals/fetch/header.rs index 9a676212..31b598cf 100644 --- a/runtime/src/globals/fetch/header.rs +++ b/runtime/src/globals/fetch/header.rs @@ -102,16 +102,61 @@ pub enum HeadersInit { } impl HeadersInit { - pub(crate) fn into_headers(self, kind: HeadersKind) -> Result { + pub(crate) fn into_headers(self, mut headers: HeadersInner, kind: HeadersKind) -> Result { match self { - HeadersInit::Existing(existing) => Ok(Headers { headers: existing.headers, kind }), - HeadersInit::Array(vec) => Headers::from_array(vec, kind), - HeadersInit::Object(object) => Ok(Headers { headers: object.0, kind }), - HeadersInit::Empty => Ok(Headers { headers: HeaderMap::new(), kind }), + HeadersInit::Existing(existing) => { + headers + .as_mut() + .extend(existing.headers.as_ref().into_iter().map(|(name, value)| (name.clone(), value.clone()))); + Ok(Headers { headers, kind }) + } + HeadersInit::Array(vec) => Headers::from_array(vec, headers, kind), + HeadersInit::Object(object) => { + headers.as_mut().extend(object.0); + Ok(Headers { headers, kind }) + } + HeadersInit::Empty => Ok(Headers { headers, kind }), + } + } +} + +#[derive(Debug)] +pub(crate) enum HeadersInner { + Owned(HeaderMap), + MutRef(*mut HeaderMap), +} + +impl HeadersInner { + pub fn as_ref(&self) -> &HeaderMap { + match self { + HeadersInner::Owned(map) => map, + HeadersInner::MutRef(map) => unsafe { &**map }, + } + } + + pub fn as_mut(&mut self) -> &mut HeaderMap { + match self { + HeadersInner::Owned(map) => map, + HeadersInner::MutRef(map) => unsafe { &mut **map }, } } } +impl Clone for HeadersInner { + fn clone(&self) -> HeadersInner { + match self { + HeadersInner::Owned(map) => HeadersInner::Owned(map.clone()), + HeadersInner::MutRef(map) => HeadersInner::Owned(unsafe { (**map).clone() }), + } + } +} + +impl Default for HeadersInner { + fn default() -> HeadersInner { + HeadersInner::Owned(HeaderMap::new()) + } +} + #[derive(Copy, Clone, Debug, Default, PartialEq)] pub enum HeadersKind { Immutable, @@ -134,19 +179,17 @@ mod class { use ion::conversions::ToValue; use ion::symbol::WellKnownSymbolCode; - use crate::globals::fetch::header::{get_header, Header, HeaderEntry, HeadersInit, HeadersKind}; + use crate::globals::fetch::header::{get_header, Header, HeadersInner, HeaderEntry, HeadersInit, HeadersKind}; #[derive(Clone, Default)] #[ion(from_value, to_value)] pub struct Headers { - pub(crate) headers: HeaderMap, + pub(crate) headers: HeadersInner, pub(crate) kind: HeadersKind, } impl Headers { - #[ion(skip)] - pub fn from_array(vec: Vec, kind: HeadersKind) -> Result { - let mut headers = HeaderMap::new(); + pub(crate) fn from_array(vec: Vec, mut headers: HeadersInner, kind: HeadersKind) -> Result { for entry in vec { let mut name = entry.name; let value = entry.value; @@ -154,21 +197,21 @@ mod class { let name = HeaderName::from_str(&name)?; let value = HeaderValue::try_from(&value)?; - headers.append(name, value); + headers.as_mut().append(name, value); } Ok(Headers { headers, kind }) } #[ion(constructor)] pub fn constructor(init: Option) -> Result { - init.unwrap_or_default().into_headers(HeadersKind::None) + init.unwrap_or_default().into_headers(HeadersInner::default(), HeadersKind::None) } pub fn append(&mut self, name: String, value: String) -> Result<()> { if self.kind != HeadersKind::Immutable { let name = HeaderName::from_str(&name.to_lowercase())?; let value = HeaderValue::from_str(&value)?; - self.headers.append(name, value); + self.headers.as_mut().append(name, value); Ok(()) } else { Err(Error::new("Cannot Modify Readonly Headers", None)) @@ -178,7 +221,7 @@ mod class { pub fn delete(&mut self, name: String) -> Result { if self.kind != HeadersKind::Immutable { let name = HeaderName::from_str(&name.to_lowercase())?; - match self.headers.entry(name) { + match self.headers.as_mut().entry(name) { Entry::Occupied(o) => { o.remove_entry_mult(); Ok(true) @@ -192,11 +235,11 @@ mod class { pub fn get(&self, name: String) -> Result> { let name = HeaderName::from_str(&name.to_lowercase())?; - get_header(&self.headers, &name) + get_header(self.headers.as_ref(), &name) } pub fn get_set_cookie(&self) -> Result> { - let header = get_header(&self.headers, &SET_COOKIE)?; + let header = get_header(self.headers.as_ref(), &SET_COOKIE)?; Ok(header.map_or_else(Vec::new, |header| match header { Header::Multiple(vec) => vec, Header::Single(str) => vec![str], @@ -205,14 +248,14 @@ mod class { pub fn has(&self, name: String) -> Result { let name = HeaderName::from_str(&name.to_lowercase())?; - Ok(self.headers.contains_key(name)) + Ok(self.headers.as_ref().contains_key(name)) } pub fn set(&mut self, name: String, value: String) -> Result<()> { if self.kind != HeadersKind::Immutable { let name = HeaderName::from_str(&name.to_lowercase())?; let value = HeaderValue::from_str(&value)?; - self.headers.insert(name, value); + self.headers.as_mut().insert(name, value); Ok(()) } else { Err(Error::new("Cannot Modify Readonly Headers", None)) @@ -222,9 +265,15 @@ mod class { #[ion(name = WellKnownSymbolCode::Iterator)] pub fn iterator<'cx: 'o, 'o>(&self, cx: &'cx Context, #[ion(this)] this: &Object<'o>) -> ion::Iterator { let thisv = this.as_value(cx); - let cookies: Vec<_> = self.headers.get_all(&SET_COOKIE).iter().map(HeaderValue::clone).collect(); - - let mut keys: Vec<_> = self.headers.keys().map(HeaderName::as_str).map(str::to_ascii_lowercase).collect(); + let cookies: Vec<_> = self.headers.as_ref().get_all(&SET_COOKIE).iter().map(HeaderValue::clone).collect(); + + let mut keys: Vec<_> = self + .headers + .as_ref() + .keys() + .map(HeaderName::as_str) + .map(str::to_ascii_lowercase) + .collect(); keys.reserve(cookies.len() - 1); for _ in 0..(cookies.len() - 1) { keys.push(String::from(SET_COOKIE.as_str())); @@ -255,7 +304,7 @@ mod class { if key == SET_COOKIE.as_str() { self.cookies.next().map(|value| [key.as_str(), value.to_str().unwrap()].as_value(cx)) } else { - get_header(&headers.headers, &HeaderName::from_bytes(key.as_bytes()).unwrap()) + get_header(headers.headers.as_ref(), &HeaderName::from_bytes(key.as_bytes()).unwrap()) .unwrap() .map(|value| [key.as_str(), &value.to_string()].as_value(cx)) } @@ -267,13 +316,13 @@ mod class { type Target = HeaderMap; fn deref(&self) -> &HeaderMap { - &self.headers + self.headers.as_ref() } } impl DerefMut for Headers { fn deref_mut(&mut self) -> &mut HeaderMap { - &mut self.headers + self.headers.as_mut() } } } diff --git a/runtime/src/globals/fetch/mod.rs b/runtime/src/globals/fetch/mod.rs index 273cd379..70cff0c3 100644 --- a/runtime/src/globals/fetch/mod.rs +++ b/runtime/src/globals/fetch/mod.rs @@ -24,7 +24,10 @@ mod response; #[js_fn] fn fetch<'cx>(cx: &'cx Context, resource: RequestInfo, init: Option) -> ResultExc> { let request = Request::constructor(cx, resource, init)?; - Ok(future_to_promise(cx, request_internal(request, GLOBAL_CLIENT.get().unwrap().clone()))) + let cx2 = unsafe { Context::new_unchecked(cx.as_ptr()) }; + Ok(future_to_promise(cx, async move { + request_internal(&cx2, request, GLOBAL_CLIENT.get().unwrap().clone()).await + })) } pub fn define(cx: &Context, global: &mut Object) -> bool { diff --git a/runtime/src/globals/fetch/network.rs b/runtime/src/globals/fetch/network.rs index 6f5b95f6..9395626e 100644 --- a/runtime/src/globals/fetch/network.rs +++ b/runtime/src/globals/fetch/network.rs @@ -14,25 +14,25 @@ use hyper::client::HttpConnector; use hyper_rustls::HttpsConnector; use url::Url; -use ion::{Error, Exception, ResultExc}; +use ion::{Context, Error, Exception, ResultExc}; use crate::globals::fetch::{Request, Response}; use crate::globals::fetch::body::FetchBody; use crate::globals::fetch::request::{clone_request, RequestRedirect}; -pub async fn request_internal(request: Request, client: Client>) -> ResultExc { +pub async fn request_internal<'c>(cx: &Context<'c>, request: Request, client: Client>) -> ResultExc { let signal = request.signal.poll(); - let send = Box::pin(send_requests(request, client)); + let send = Box::pin(send_requests(cx, request, client)); match select(send, signal).await { Either::Left((response, _)) => response, Either::Right((exception, _)) => Err(Exception::Other(exception)), } } -pub(crate) async fn send_requests(req: Request, client: Client>) -> ResultExc { +pub(crate) async fn send_requests<'c>(cx: &Context<'c>, req: Request, client: Client>) -> ResultExc { let mut redirections = 0; - let mut request = req.clone()?; + let mut request = req.clone(cx)?; *request.request.body_mut() = request.body.to_http_body(); let mut response = client.request(req.request).await?; @@ -83,13 +83,13 @@ pub(crate) async fn send_requests(req: Request, client: Client 0)); + return Ok(Response::new(cx, response, url, redirections > 0)); } } RequestRedirect::Error => return Err(Error::new("Received Redirection", None).into()), - RequestRedirect::Manual => return Ok(Response::new(response, url, redirections > 0)), + RequestRedirect::Manual => return Ok(Response::new(cx, response, url, redirections > 0)), } } - Ok(Response::new(response, url, redirections > 0)) + Ok(Response::new(cx, response, url, redirections > 0)) } diff --git a/runtime/src/globals/fetch/request/mod.rs b/runtime/src/globals/fetch/request/mod.rs index 2cba35ea..9a97232c 100644 --- a/runtime/src/globals/fetch/request/mod.rs +++ b/runtime/src/globals/fetch/request/mod.rs @@ -24,21 +24,21 @@ pub enum RequestInfo { #[js_class] pub mod class { use std::str::FromStr; + use http::header::CONTENT_TYPE; use http::HeaderValue; - use hyper::{Body, Method, Uri}; + use mozjs::gc::Traceable; + use mozjs::jsapi::{Heap, JSObject, JSTracer}; use url::Url; - use mozjs::jsapi::{Heap, JSObject, JSTracer}; - use mozjs::gc::Traceable; use ion::{ClassDefinition, Context, Error, ErrorKind, Object, Result, Value}; use ion::conversions::{FromValue, ToValue}; use crate::globals::abort::AbortSignal; use crate::globals::fetch::{Headers, RequestInfo}; use crate::globals::fetch::body::FetchBody; - use crate::globals::fetch::header::HeadersKind; + use crate::globals::fetch::header::{HeadersInner, HeadersKind}; use crate::globals::fetch::request::{ clone_request, Referrer, ReferrerPolicy, RequestBuilderInit, RequestCache, RequestCredentials, RequestMode, RequestRedirect, }; @@ -46,6 +46,7 @@ pub mod class { #[ion(into_value)] pub struct Request { pub(crate) request: hyper::Request, + pub(crate) headers: Box>, pub(crate) body: FetchBody, pub(crate) body_used: bool, @@ -82,7 +83,7 @@ pub mod class { let mut fallback_cors = false; let mut request = match info { - RequestInfo::Request(request) => request.clone()?, + RequestInfo::Request(request) => request.clone(cx)?, RequestInfo::String(url) => { let uri = Uri::from_str(&url)?; let url = Url::from_str(&url)?; @@ -94,10 +95,11 @@ pub mod class { fallback_cors = true; let signal = AbortSignal::default(); - let signal_object = Heap::boxed(AbortSignal::new_object(cx, signal.clone())); + let signal_object = AbortSignal::new_object(cx, signal.clone()); Request { request, + headers: Box::default(), body: FetchBody::default(), body_used: false, @@ -107,7 +109,7 @@ pub mod class { referrer: Referrer::default(), referrer_policy: ReferrerPolicy::default(), - mode: RequestMode::default(), + mode: RequestMode::Cors, credentials: RequestCredentials::default(), cache: RequestCache::default(), redirect: RequestRedirect::default(), @@ -121,7 +123,7 @@ pub mod class { client_window: true, signal, - signal_object, + signal_object: Heap::boxed(signal_object), } } }; @@ -203,14 +205,23 @@ pub mod class { if request.mode == RequestMode::NoCors { let method = request.request.method(); - if method != Method::GET || method != Method::HEAD || method != Method::POST { - return Err(Error::new("Invalid request method.", ErrorKind::Type)); + if method != Method::GET && method != Method::HEAD && method != Method::POST { + return Err(Error::new("Invalid request method", ErrorKind::Type)); } } - if let Some(headers) = headers { - *request.request.headers_mut() = headers.into_headers(HeadersKind::Request)?.headers; - } + let kind = if request.mode == RequestMode::NoCors { + HeadersKind::RequestNoCors + } else { + HeadersKind::Request + }; + + let headers = if let Some(headers) = headers { + headers.into_headers(HeadersInner::MutRef(request.request.headers_mut()), kind)? + } else { + Headers { headers: HeadersInner::default(), kind } + }; + request.headers.set(Headers::new_object(cx, headers)); if let Some(body) = body { if let Some(kind) = &body.kind { @@ -236,6 +247,11 @@ pub mod class { self.request.uri().to_string() } + #[ion(get)] + pub fn get_headers(&self) -> *mut JSObject { + self.headers.get() + } + #[ion(get)] pub fn get_destination(&self) -> String { String::new() @@ -288,12 +304,17 @@ pub mod class { #[allow(clippy::should_implement_trait)] #[ion(skip)] - pub fn clone(&self) -> Result { - let request = clone_request(&self.request)?; + pub fn clone(&self, cx: &Context) -> Result { + let mut request = clone_request(&self.request)?; let url = self.locations.last().unwrap().clone(); + let headers = Headers { + headers: HeadersInner::MutRef(request.headers_mut()), + kind: HeadersKind::Request, + }; Ok(Request { request, + headers: Heap::boxed(Headers::new_object(cx, headers)), body: self.body.clone(), body_used: self.body_used, @@ -320,19 +341,12 @@ pub mod class { signal_object: Heap::boxed(self.signal_object.get()), }) } - - #[ion(get)] - pub fn get_headers(&self) -> Headers { - Headers { - headers: self.request.headers().clone(), - kind: HeadersKind::Request, - } - } } unsafe impl Traceable for Request { unsafe fn trace(&self, trc: *mut JSTracer) { unsafe { + self.headers.trace(trc); self.signal_object.trace(trc); } } @@ -346,7 +360,7 @@ pub mod class { { let object = Object::from_value(cx, value, true, ())?; if Request::instance_of(cx, &object, None) { - Request::get_private(&object).clone() + Request::get_private(&object).clone(cx) } else { Err(Error::new("Expected Request", ErrorKind::Type)) } diff --git a/runtime/src/globals/fetch/response/mod.rs b/runtime/src/globals/fetch/response/mod.rs index 1a5f8716..58f77b76 100644 --- a/runtime/src/globals/fetch/response/mod.rs +++ b/runtime/src/globals/fetch/response/mod.rs @@ -16,17 +16,19 @@ pub mod class { use hyper::body::HttpBody; use url::Url; - use ion::{Error, ErrorKind, Result}; + use mozjs::jsapi::{Heap, JSObject}; + use ion::{Context, ClassDefinition, Error, ErrorKind, Result}; use ion::typedarray::ArrayBuffer; use crate::globals::fetch::body::FetchBody; - use crate::globals::fetch::header::HeadersKind; + use crate::globals::fetch::header::{HeadersInner, HeadersKind}; use crate::globals::fetch::Headers; use crate::globals::fetch::response::options::ResponseInit; #[ion(into_value)] pub struct Response { pub(crate) response: hyper::Response, + pub(crate) headers: Box>, pub(crate) body: Option, pub(crate) body_used: bool, @@ -38,13 +40,20 @@ pub mod class { } impl Response { - pub(crate) fn new(mut response: hyper::Response, url: Url, redirected: bool) -> Response { - *response.body_mut() = Body::empty(); + pub(crate) fn new(cx: &Context, mut response: hyper::Response, url: Url, redirected: bool) -> Response { + let headers = Headers::new_object( + cx, + Headers { + headers: HeadersInner::MutRef(response.headers_mut()), + kind: HeadersKind::Response, + }, + ); let status = response.status(); let status_text = String::from(status.canonical_reason().unwrap()); Response { response, + headers: Heap::boxed(headers), body: None, body_used: false, @@ -57,12 +66,13 @@ pub mod class { } #[ion(constructor)] - pub fn constructor(body: Option, init: Option) -> Result { + pub fn constructor(cx: &Context, body: Option, init: Option) -> Result { let init = init.unwrap_or_default(); let response = hyper::Response::builder().status(init.status).body(Body::empty())?; let mut response = Response { response, + headers: Box::default(), body: None, body_used: false, @@ -73,7 +83,10 @@ pub mod class { status_text: Some(init.status_text), }; - *response.response.headers_mut() = init.headers.into_headers(HeadersKind::Response)?.headers; + let headers = init + .headers + .into_headers(HeadersInner::MutRef(response.response.headers_mut()), HeadersKind::Response)?; + response.headers.set(Headers::new_object(cx, headers)); if let Some(body) = body { if init.status == StatusCode::NO_CONTENT || init.status == StatusCode::RESET_CONTENT || init.status == StatusCode::NOT_MODIFIED { @@ -87,16 +100,8 @@ pub mod class { } #[ion(get)] - pub fn get_body_used(&self) -> bool { - self.body_used - } - - #[ion(get)] - pub fn get_headers(&self) -> Headers { - Headers { - headers: self.response.headers().clone(), - kind: HeadersKind::Response, - } + pub fn get_headers(&self) -> *mut JSObject { + self.headers.get() } #[ion(get)] @@ -127,6 +132,11 @@ pub mod class { self.url.as_ref().map(Url::to_string).unwrap_or_default() } + #[ion(get)] + pub fn get_body_used(&self) -> bool { + self.body_used + } + async fn read_to_bytes(&mut self) -> Result> { if self.body_used { return Err(Error::new("Response body has already been used.", None));