runtime/globals/fetch/request/
mod.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use std::str::FromStr as _;
8
9use http::{HeaderMap, Method};
10use ion::class::{NativeObject as _, Reflector};
11use ion::function::Opt;
12use ion::typedarray::{ArrayBufferWrapper, Uint8ArrayWrapper};
13use ion::{ClassDefinition as _, Context, Error, ErrorKind, FromValue, Object, Promise, Result, TracedHeap, js_class};
14use mozjs::jsapi::{Heap, JSObject};
15pub use options::*;
16use url::Url;
17
18use crate::globals::abort::AbortSignal;
19use crate::globals::fetch::Headers;
20use crate::globals::fetch::body::FetchBody;
21use crate::globals::fetch::header::HeadersKind;
22use crate::promise::future_to_promise;
23
24mod options;
25
26#[derive(FromValue)]
27#[ion(inherit)]
28pub enum RequestInfo<'cx> {
29	Request(&'cx Request),
30	String(String),
31}
32
33#[js_class]
34pub struct Request {
35	reflector: Reflector,
36
37	pub(crate) headers: Box<Heap<*mut JSObject>>,
38	pub(crate) body: Option<FetchBody>,
39
40	#[trace(no_trace)]
41	pub(crate) method: Method,
42	#[trace(no_trace)]
43	pub(crate) url: Url,
44	#[trace(no_trace)]
45	pub(crate) locations: Vec<Url>,
46
47	pub(crate) referrer: Referrer,
48	pub(crate) referrer_policy: ReferrerPolicy,
49
50	pub(crate) mode: RequestMode,
51	pub(crate) credentials: RequestCredentials,
52	pub(crate) cache: RequestCache,
53	pub(crate) redirect: RequestRedirect,
54
55	pub(crate) integrity: String,
56
57	pub(crate) unsafe_request: bool,
58	pub(crate) keepalive: bool,
59
60	pub(crate) client_window: bool,
61	pub(crate) signal_object: Box<Heap<*mut JSObject>>,
62}
63
64#[js_class]
65impl Request {
66	#[ion(constructor)]
67	pub fn constructor(cx: &Context, info: RequestInfo, Opt(init): Opt<RequestInit>) -> Result<Request> {
68		let mut fallback_cors = false;
69
70		let mut request = match info {
71			RequestInfo::Request(request) => request.clone(),
72			RequestInfo::String(url) => {
73				let url = Url::from_str(&url)?;
74				if url.username() != "" || url.password().is_some() {
75					return Err(Error::new("Received URL with embedded credentials", ErrorKind::Type));
76				}
77
78				fallback_cors = true;
79
80				Request {
81					reflector: Reflector::default(),
82
83					headers: Box::default(),
84					body: None,
85
86					method: Method::GET,
87					url: url.clone(),
88					locations: vec![url],
89
90					referrer: Referrer::default(),
91					referrer_policy: ReferrerPolicy::default(),
92
93					mode: RequestMode::default(),
94					credentials: RequestCredentials::default(),
95					cache: RequestCache::default(),
96					redirect: RequestRedirect::default(),
97
98					integrity: String::new(),
99
100					unsafe_request: false,
101					keepalive: false,
102
103					client_window: true,
104					signal_object: Heap::boxed(AbortSignal::new_object(cx, Box::default())),
105				}
106			}
107		};
108
109		let mut headers = None;
110		let mut body = None;
111
112		if let Some(init) = init {
113			if let Some(window) = init.window {
114				if window.is_null() {
115					request.client_window = false;
116				} else {
117					return Err(Error::new("Received non-null window type", ErrorKind::Type));
118				}
119			}
120
121			if request.mode == RequestMode::Navigate {
122				request.mode = RequestMode::SameOrigin;
123			}
124
125			if let Some(referrer) = init.referrer {
126				request.referrer = referrer;
127			}
128			if let Some(policy) = init.referrer_policy {
129				request.referrer_policy = policy;
130			}
131
132			let mode = init.mode.or(fallback_cors.then_some(RequestMode::Cors));
133			if let Some(mode) = mode {
134				if mode == RequestMode::Navigate {
135					return Err(Error::new("Received 'navigate' mode", ErrorKind::Type));
136				}
137				request.mode = mode;
138			}
139
140			if let Some(credentials) = init.credentials {
141				request.credentials = credentials;
142			}
143			if let Some(cache) = init.cache {
144				request.cache = cache;
145			}
146			if let Some(redirect) = init.redirect {
147				request.redirect = redirect;
148			}
149			if let Some(integrity) = init.integrity {
150				request.integrity = integrity;
151			}
152			if let Some(keepalive) = init.keepalive {
153				request.keepalive = keepalive;
154			}
155
156			if let Some(signal_object) = init.signal {
157				request.signal_object.set(signal_object);
158			}
159
160			if let Some(mut method) = init.method {
161				method.make_ascii_uppercase();
162				let method = Method::from_str(&method)?;
163				if method == Method::CONNECT || method == Method::TRACE {
164					return Err(Error::new("Received invalid request method", ErrorKind::Type));
165				}
166				request.method = method;
167			}
168
169			headers = init.headers;
170			body = init.body;
171		}
172
173		if request.cache == RequestCache::OnlyIfCached && request.mode != RequestMode::SameOrigin {
174			return Err(Error::new(
175				"Request cache mode 'only-if-cached' can only be used with request mode 'same-origin'",
176				ErrorKind::Type,
177			));
178		}
179
180		if request.mode == RequestMode::NoCors && !matches!(request.method, Method::GET | Method::HEAD | Method::POST) {
181			return Err(Error::new("Invalid request method", ErrorKind::Type));
182		}
183
184		let kind = if request.mode == RequestMode::NoCors {
185			HeadersKind::RequestNoCors
186		} else {
187			HeadersKind::Request
188		};
189
190		let mut headers = if let Some(headers) = headers {
191			headers.into_headers(HeaderMap::new(), kind)?
192		} else {
193			Headers {
194				reflector: Reflector::default(),
195				headers: HeaderMap::new(),
196				kind,
197			}
198		};
199
200		if let Some(body) = body {
201			body.add_content_type_header(&mut headers.headers);
202			request.body = Some(body);
203		}
204		request.headers.set(Headers::new_object(cx, Box::new(headers)));
205
206		Ok(request)
207	}
208
209	#[ion(get)]
210	pub fn get_method(&self) -> String {
211		self.method.to_string()
212	}
213
214	#[ion(get)]
215	pub fn get_url(&self) -> String {
216		self.url.to_string()
217	}
218
219	#[ion(get)]
220	pub fn get_headers(&self) -> *mut JSObject {
221		self.headers.get()
222	}
223
224	#[ion(get)]
225	pub fn get_destination(&self) -> String {
226		String::new()
227	}
228
229	#[ion(get)]
230	pub fn get_referrer(&self) -> String {
231		self.referrer.to_string()
232	}
233
234	#[ion(get)]
235	pub fn get_referrer_policy(&self) -> String {
236		self.referrer.to_string()
237	}
238
239	#[ion(get)]
240	pub fn get_mode(&self) -> String {
241		self.mode.to_string()
242	}
243
244	#[ion(get)]
245	pub fn get_credentials(&self) -> String {
246		self.credentials.to_string()
247	}
248
249	#[ion(get)]
250	pub fn get_cache(&self) -> String {
251		self.cache.to_string()
252	}
253
254	#[ion(get)]
255	pub fn get_redirect(&self) -> String {
256		self.redirect.to_string()
257	}
258
259	#[ion(get)]
260	pub fn get_integrity(&self) -> String {
261		self.integrity.clone()
262	}
263
264	#[ion(get)]
265	pub fn get_keepalive(&self) -> bool {
266		self.keepalive
267	}
268
269	#[ion(get)]
270	pub fn get_is_reload_navigation(&self) -> bool {
271		false
272	}
273
274	#[ion(get)]
275	pub fn get_is_history_navigation(&self) -> bool {
276		false
277	}
278
279	#[ion(get)]
280	pub fn get_signal(&self) -> *mut JSObject {
281		self.signal_object.get()
282	}
283
284	#[ion(get)]
285	pub fn get_duplex(&self) -> String {
286		String::from("half")
287	}
288
289	#[ion(get)]
290	pub fn get_body_used(&self) -> bool {
291		self.body.is_none()
292	}
293
294	async fn read_to_bytes(&mut self) -> Result<Vec<u8>> {
295		if let Some(body) = self.body.take() {
296			body.read_to_bytes().await
297		} else {
298			Err(Error::new("Request body has already been used.", None))
299		}
300	}
301
302	#[ion(name = "arrayBuffer")]
303	pub fn array_buffer<'cx>(&mut self, cx: &'cx Context) -> Option<Promise<'cx>> {
304		let this = TracedHeap::new(self.reflector().get());
305		future_to_promise::<_, _, _, Error>(cx, |cx| async move {
306			let request = Object::from(this.to_local());
307			let request = Request::get_mut_private(&cx, &request)?;
308			let bytes = request.read_to_bytes().await?;
309			Ok(ArrayBufferWrapper::from(bytes))
310		})
311	}
312
313	pub fn bytes<'cx>(&mut self, cx: &'cx Context) -> Option<Promise<'cx>> {
314		let this = TracedHeap::new(self.reflector().get());
315		future_to_promise::<_, _, _, Error>(cx, |cx| async move {
316			let request = Object::from(this.to_local());
317			let request = Request::get_mut_private(&cx, &request)?;
318			let bytes = request.read_to_bytes().await?;
319			Ok(Uint8ArrayWrapper::from(bytes))
320		})
321	}
322
323	pub fn text<'cx>(&mut self, cx: &'cx Context) -> Option<Promise<'cx>> {
324		let this = TracedHeap::new(self.reflector().get());
325		future_to_promise::<_, _, _, Error>(cx, |cx| async move {
326			let request = Object::from(this.to_local());
327			let request = Request::get_mut_private(&cx, &request)?;
328			let bytes = request.read_to_bytes().await?;
329			String::from_utf8(bytes).map_err(|e| Error::new(format!("Invalid UTF-8 sequence: {e}"), None))
330		})
331	}
332}
333
334impl Clone for Request {
335	fn clone(&self) -> Request {
336		let url = self.locations.last().unwrap().clone();
337
338		Request {
339			reflector: Reflector::default(),
340
341			headers: Box::default(),
342			body: self.body.clone(),
343
344			method: self.method.clone(),
345			url: url.clone(),
346			locations: vec![url],
347
348			referrer: self.referrer.clone(),
349			referrer_policy: self.referrer_policy,
350
351			mode: self.mode,
352			credentials: self.credentials,
353			cache: self.cache,
354			redirect: self.redirect,
355
356			integrity: self.integrity.clone(),
357
358			unsafe_request: true,
359			keepalive: self.keepalive,
360
361			client_window: self.client_window,
362			signal_object: Heap::boxed(self.signal_object.get()),
363		}
364	}
365}