Skip to main content

script/dom/xmlhttprequest/
xmlhttprequest.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::borrow::ToOwned;
6use std::cell::Cell;
7use std::cmp;
8use std::default::Default;
9use std::str::{self, FromStr};
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13use atomic_refcell::AtomicRefCell;
14use bytes::{Bytes, BytesMut};
15use data_url::mime::Mime;
16use dom_struct::dom_struct;
17use encoding_rs::{Encoding, UTF_8};
18use headers::{ContentLength, ContentType, HeaderMapExt};
19use html5ever::serialize;
20use html5ever::serialize::SerializeOpts;
21use http::Method;
22use http::header::{self, HeaderMap, HeaderName, HeaderValue};
23use hyper_serde::Serde;
24use js::context::{JSContext, NoGC};
25use js::conversions::ToJSValConvertible;
26use js::jsapi::Heap;
27use js::jsval::{JSVal, NullValue};
28use js::rust::wrappers2::{JS_ClearPendingException, JS_ParseJSON};
29use js::rust::{HandleObject, MutableHandleValue};
30use js::typedarray::{ArrayBufferU8, HeapArrayBuffer};
31use net_traits::blob_url_store::UrlWithBlobClaim;
32use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime;
33use net_traits::http_status::HttpStatus;
34use net_traits::request::{CredentialsMode, Referrer, RequestBuilder, RequestId, RequestMode};
35use net_traits::{
36    FetchMetadata, FilteredMetadata, NetworkError, ReferrerPolicy, ResourceFetchTiming,
37    trim_http_whitespace,
38};
39use script_bindings::cell::DomRefCell;
40use script_bindings::num::Finite;
41use script_bindings::reflector::reflect_dom_object_with_proto;
42use script_bindings::trace::RootedTraceableBox;
43use script_traits::DocumentActivity;
44use servo_constellation_traits::BlobImpl;
45use servo_url::ServoUrl;
46use stylo_atoms::Atom;
47use url::Position;
48
49use crate::dom::bindings::buffer_source::{HeapBufferSource, get_buffer_source_copy};
50use crate::dom::bindings::cell::AtomicSafeBorrowMut;
51use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
52use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::{
53    XMLHttpRequestMethods, XMLHttpRequestResponseType,
54};
55use crate::dom::bindings::codegen::UnionTypes::DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrStringOrURLSearchParams as DocumentOrXMLHttpRequestBodyInit;
56use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
57use crate::dom::bindings::inheritance::Castable;
58use crate::dom::bindings::refcounted::Trusted;
59use crate::dom::bindings::reflector::DomGlobal;
60use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
61use crate::dom::bindings::str::{ByteString, DOMString, USVString, is_token};
62use crate::dom::blob::{Blob, normalize_type_string};
63use crate::dom::csp::{GlobalCspReporting, Violation};
64use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument};
65use crate::dom::event::{Event, EventBubbles, EventCancelable};
66use crate::dom::eventtarget::EventTarget;
67use crate::dom::globalscope::GlobalScope;
68use crate::dom::headers::is_forbidden_request_header;
69use crate::dom::node::Node;
70use crate::dom::performance::performanceresourcetiming::InitiatorType;
71use crate::dom::progressevent::ProgressEvent;
72use crate::dom::servoparser::ServoParser;
73use crate::dom::servoparser::html::HtmlSerialize;
74use crate::dom::stream::readablestream::ReadableStream;
75use crate::dom::window::Window;
76use crate::dom::workerglobalscope::WorkerGlobalScope;
77use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
78use crate::dom::xmlhttprequestupload::XMLHttpRequestUpload;
79use crate::event_loop::document_loader::DocumentLoader;
80use crate::event_loop::timers::{OneshotTimerCallback, OneshotTimerHandle};
81use crate::fetch::body::{
82    BodySource, Extractable, ExtractedBody, decode_to_utf16_with_bom_removal,
83};
84use crate::fetch::fetch::{FetchCanceller, RequestWithGlobalScope};
85use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
86use crate::mime::{APPLICATION, CHARSET, HTML, MimeExt, TEXT, XML};
87use crate::tasks::task_source::{SendableTaskSource, TaskSourceName};
88use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
89
90#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
91enum XMLHttpRequestState {
92    Unsent = 0,
93    Opened = 1,
94    HeadersReceived = 2,
95    Loading = 3,
96    Done = 4,
97}
98
99#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
100pub(crate) struct GenerationId(u32);
101
102/// Closure of required data for each async network event that comprises the
103/// XHR's response.
104struct XHRContext {
105    xhr: TrustedXHRAddress,
106    gen_id: GenerationId,
107    sync_status: Arc<AtomicRefCell<Option<ErrorResult>>>,
108    url: ServoUrl,
109}
110
111impl FetchResponseListener for XHRContext {
112    fn process_request_body(&mut self, _: RequestId) {
113        // todo
114    }
115
116    fn process_response(
117        &mut self,
118        cx: &mut JSContext,
119        _: RequestId,
120        metadata: Result<FetchMetadata, NetworkError>,
121    ) {
122        let xhr = self.xhr.root();
123        let rv = xhr.process_headers_available(cx, self.gen_id, metadata);
124        if rv.is_err() {
125            *self.sync_status.safe_borrow_mut(cx.no_gc()) = Some(rv);
126        }
127    }
128
129    fn process_response_chunk(&mut self, cx: &mut JSContext, _: RequestId, chunk: Bytes) {
130        self.xhr
131            .root()
132            .process_data_available(cx, self.gen_id, chunk);
133    }
134
135    fn process_response_eof(
136        self,
137        cx: &mut JSContext,
138        _: RequestId,
139        response: Result<(), NetworkError>,
140        timing: ResourceFetchTiming,
141    ) {
142        network_listener::submit_timing(cx, &self, &response, &timing);
143
144        let rv = self
145            .xhr
146            .root()
147            .process_response_complete(cx, self.gen_id, response.map(|_| ()));
148        *self.sync_status.safe_borrow_mut(cx.no_gc()) = Some(rv);
149    }
150
151    fn process_csp_violations(
152        &mut self,
153        cx: &mut js::context::JSContext,
154        _request_id: RequestId,
155        violations: Vec<Violation>,
156    ) {
157        let global = &self.resource_timing_global();
158        global.report_csp_violations(cx, violations, None, None);
159    }
160
161    fn should_invoke(&self) -> bool {
162        self.xhr.root().generation_id.get() == self.gen_id
163    }
164}
165
166impl ResourceTimingListener for XHRContext {
167    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
168        (InitiatorType::XMLHttpRequest, self.url.clone())
169    }
170
171    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
172        self.xhr.root().global()
173    }
174}
175
176#[derive(Clone)]
177pub(crate) enum XHRProgress {
178    /// Notify that headers have been received
179    HeadersReceived(GenerationId, Option<HeaderMap>, HttpStatus),
180    /// Partial progress (after receiving headers), containing portion of the response
181    Loading(GenerationId, Bytes),
182    /// Loading is done
183    Done(GenerationId),
184    /// There was an error (only Error::Abort(None), Error::Timeout(None) or Error::Network(None) is used)
185    Errored(GenerationId, Error),
186}
187
188impl XHRProgress {
189    fn generation_id(&self) -> GenerationId {
190        match *self {
191            XHRProgress::HeadersReceived(id, _, _) |
192            XHRProgress::Loading(id, _) |
193            XHRProgress::Done(id) |
194            XHRProgress::Errored(id, _) => id,
195        }
196    }
197}
198
199#[dom_struct]
200pub(crate) struct XMLHttpRequest {
201    eventtarget: XMLHttpRequestEventTarget,
202    ready_state: Cell<XMLHttpRequestState>,
203    timeout: Cell<Duration>,
204    with_credentials: Cell<bool>,
205    upload: Dom<XMLHttpRequestUpload>,
206    response_url: DomRefCell<String>,
207    #[no_trace]
208    status: DomRefCell<HttpStatus>,
209    #[no_trace]
210    response: DomRefCell<BytesMut>,
211    response_type: Cell<XMLHttpRequestResponseType>,
212    response_xml: MutNullableDom<Document>,
213    response_blob: MutNullableDom<Blob>,
214    #[ignore_malloc_size_of = "mozjs"]
215    response_arraybuffer: HeapBufferSource<ArrayBufferU8>,
216    #[ignore_malloc_size_of = "Defined in rust-mozjs"]
217    response_json: Heap<JSVal>,
218    #[no_trace]
219    response_headers: DomRefCell<HeaderMap>,
220    #[no_trace]
221    override_mime_type: DomRefCell<Option<Mime>>,
222
223    // Associated concepts
224    #[no_trace]
225    request_method: DomRefCell<Method>,
226    #[no_trace]
227    request_url: DomRefCell<Option<UrlWithBlobClaim>>,
228    #[no_trace]
229    request_headers: DomRefCell<HeaderMap>,
230    request_body_len: Cell<usize>,
231    sync: Cell<bool>,
232    upload_complete: Cell<bool>,
233    upload_listener: Cell<bool>,
234    send_flag: Cell<bool>,
235
236    timeout_cancel: DomRefCell<Option<OneshotTimerHandle>>,
237    fetch_time: Cell<Instant>,
238    generation_id: Cell<GenerationId>,
239    response_status: Cell<Result<(), ()>>,
240    #[no_trace]
241    referrer: Referrer,
242    #[no_trace]
243    referrer_policy: ReferrerPolicy,
244    canceller: DomRefCell<FetchCanceller>,
245}
246
247impl XMLHttpRequest {
248    fn new_inherited(global: &GlobalScope, upload: &XMLHttpRequestUpload) -> XMLHttpRequest {
249        XMLHttpRequest {
250            eventtarget: XMLHttpRequestEventTarget::new_inherited(),
251            ready_state: Cell::new(XMLHttpRequestState::Unsent),
252            timeout: Cell::new(Duration::ZERO),
253            with_credentials: Cell::new(false),
254            upload: Dom::from_ref(upload),
255            response_url: DomRefCell::new(String::new()),
256            status: DomRefCell::new(HttpStatus::new_error()),
257            response: DomRefCell::new(BytesMut::new()),
258            response_type: Cell::new(XMLHttpRequestResponseType::_empty),
259            response_xml: Default::default(),
260            response_blob: Default::default(),
261            response_arraybuffer: HeapBufferSource::default(),
262            response_json: Heap::default(),
263            response_headers: DomRefCell::new(HeaderMap::new()),
264            override_mime_type: DomRefCell::new(None),
265
266            request_method: DomRefCell::new(Method::GET),
267            request_url: DomRefCell::new(None),
268            request_headers: DomRefCell::new(HeaderMap::new()),
269            request_body_len: Cell::new(0),
270            sync: Cell::new(false),
271            upload_complete: Cell::new(false),
272            upload_listener: Cell::new(false),
273            send_flag: Cell::new(false),
274
275            timeout_cancel: DomRefCell::new(None),
276            fetch_time: Cell::new(Instant::now()),
277            generation_id: Cell::new(GenerationId(0)),
278            response_status: Cell::new(Ok(())),
279            referrer: global.get_referrer(),
280            referrer_policy: global.get_referrer_policy(),
281            canceller: DomRefCell::new(Default::default()),
282        }
283    }
284
285    fn new(
286        cx: &mut JSContext,
287        global: &GlobalScope,
288        proto: Option<HandleObject>,
289    ) -> DomRoot<XMLHttpRequest> {
290        let upload = XMLHttpRequestUpload::new(cx, global);
291        reflect_dom_object_with_proto(
292            cx,
293            Box::new(XMLHttpRequest::new_inherited(global, &upload)),
294            global,
295            proto,
296        )
297    }
298
299    fn sync_in_window(&self) -> bool {
300        self.sync.get() && self.global().is::<Window>()
301    }
302}
303
304impl XMLHttpRequestMethods<crate::DomTypeHolder> for XMLHttpRequest {
305    /// <https://xhr.spec.whatwg.org/#constructors>
306    fn Constructor(
307        cx: &mut JSContext,
308        global: &GlobalScope,
309        proto: Option<HandleObject>,
310    ) -> Fallible<DomRoot<XMLHttpRequest>> {
311        Ok(XMLHttpRequest::new(cx, global, proto))
312    }
313
314    // https://xhr.spec.whatwg.org/#handler-xhr-onreadystatechange
315    event_handler!(
316        readystatechange,
317        GetOnreadystatechange,
318        SetOnreadystatechange
319    );
320
321    /// <https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate>
322    fn ReadyState(&self) -> u16 {
323        self.ready_state.get() as u16
324    }
325
326    /// <https://xhr.spec.whatwg.org/#the-open()-method>
327    fn Open(&self, cx: &mut JSContext, method: ByteString, url: USVString) -> ErrorResult {
328        // Step 8
329        self.Open_(cx, method, url, true, None, None)
330    }
331
332    /// <https://xhr.spec.whatwg.org/#the-open()-method>
333    fn Open_(
334        &self,
335        cx: &mut JSContext,
336        method: ByteString,
337        url: USVString,
338        asynch: bool,
339        username: Option<USVString>,
340        password: Option<USVString>,
341    ) -> ErrorResult {
342        // Step 1. If this’s relevant global object is a Window object and its associated
343        // Document is not fully active, then throw an "InvalidStateError" DOMException.
344        let global = self.global();
345        if let Some(window) = global.downcast::<Window>() &&
346            !window.Document().is_fully_active()
347        {
348            return Err(Error::InvalidState(None));
349        }
350
351        // Step 5
352        // FIXME(seanmonstar): use a Trie instead?
353        let maybe_method = method.as_str().and_then(|s| {
354            // Note: hyper tests against the uppercase versions
355            // Since we want to pass methods not belonging to the short list above
356            // without changing capitalization, this will actually sidestep rust-http's type system
357            // since methods like "patch" or "PaTcH" will be considered extension methods
358            // despite the there being a rust-http method variant for them
359            let upper = s.to_ascii_uppercase();
360            match &*upper {
361                "DELETE" | "GET" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "CONNECT" | "TRACE" |
362                "TRACK" => upper.parse().ok(),
363                _ => s.parse().ok(),
364            }
365        });
366
367        match maybe_method {
368            // Step 4
369            Some(Method::CONNECT) | Some(Method::TRACE) => Err(Error::Security(None)),
370            Some(ref t) if t.as_str() == "TRACK" => Err(Error::Security(None)),
371            Some(parsed_method) => {
372                // Step 3
373                if !is_token(&method) {
374                    return Err(Error::Syntax(None));
375                }
376
377                // Step 5. Let parsedURL be the result of encoding-parsing a URL url, relative to this’s
378                // relevant settings object.
379                let mut parsed_url = match self
380                    .global()
381                    .encoding_parse_a_url(&url.0)
382                    .map(|url| ensure_blob_referenced_by_url_is_kept_alive(&global, url))
383                {
384                    Ok(parsed) => parsed,
385                    Err(_) => {
386                        // Step 6. If parsedURL is failure, then throw a "SyntaxError" DOMException.
387                        return Err(Error::Syntax(None));
388                    },
389                };
390
391                // Step 8. If parsedURL’s host is non-null, then:
392                if parsed_url.host().is_some() {
393                    // Step 8.1 If the username argument is not null, set the username given parsedURL and username.
394                    if let Some(user_str) = username &&
395                        let Err(error) = parsed_url.set_username(&user_str.0)
396                    {
397                        warn!("Could not set username on XMLHttpRequest: {error:?}");
398                    }
399
400                    // Step 8.2 If the password argument is not null, set the password given parsedURL and password.
401                    if let Some(pass_str) = password &&
402                        let Err(error) = parsed_url.set_password(Some(&pass_str.0))
403                    {
404                        warn!("Could not set password on XMLHttpRequest: {error:?}");
405                    }
406                }
407
408                // Step 9. If async is false, the current global object is a Window object, and
409                // either this’s timeout is not 0 or this’s response type is not the empty string,
410                // then throw an "InvalidAccessError" DOMException.
411                if !asynch {
412                    // FIXME: This should only happen if the global environment is a document environment
413                    if !self.timeout.get().is_zero() ||
414                        self.response_type.get() != XMLHttpRequestResponseType::_empty
415                    {
416                        return Err(Error::InvalidAccess(None));
417                    }
418                }
419
420                // Step 10. Terminate this’s fetch controller.
421                self.terminate_ongoing_fetch(cx.no_gc());
422
423                // FIXME(#13767): In the WPT test: FileAPI/blob/Blob-XHR-revoke.html,
424                // the xhr.open(url) is expected to hold a reference to the URL,
425                // thus renders following revocations invalid. Though we won't
426                // implement this for now, if ever needed, we should check blob
427                // scheme and trigger corresponding actions here.
428
429                // Step 12
430                *self.request_method.safe_borrow_mut(cx.no_gc()) = parsed_method;
431                *self.request_url.safe_borrow_mut(cx.no_gc()) = Some(parsed_url);
432                self.sync.set(!asynch);
433                *self.request_headers.safe_borrow_mut(cx.no_gc()) = HeaderMap::new();
434                self.send_flag.set(false);
435                self.upload_listener.set(false);
436                *self.status.safe_borrow_mut(cx.no_gc()) = HttpStatus::new_error();
437
438                // Step 13
439                if self.ready_state.get() != XMLHttpRequestState::Opened {
440                    self.change_ready_state(cx, XMLHttpRequestState::Opened);
441                }
442                Ok(())
443            },
444            // Step 3
445            // This includes cases where as_str() returns None, and when is_token() returns false,
446            // both of which indicate invalid extension method names
447            _ => Err(Error::Syntax(None)),
448        }
449    }
450
451    /// <https://xhr.spec.whatwg.org/#the-setrequestheader()-method>
452    fn SetRequestHeader(&self, no_gc: &NoGC, name: ByteString, value: ByteString) -> ErrorResult {
453        // Step 1: If this’s state is not opened, then throw an "InvalidStateError" DOMException.
454        // Step 2: If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
455        if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() {
456            return Err(Error::InvalidState(None));
457        }
458
459        // Step 3: Normalize value.
460        let value = trim_http_whitespace(&value);
461
462        // Step 4: If name is not a header name or value is not a header value, then throw a
463        // "SyntaxError" DOMException.
464        if !is_token(&name) || !is_field_value(value) {
465            return Err(Error::Syntax(None));
466        }
467
468        let name_str = name.as_str().ok_or(Error::Syntax(None))?;
469
470        // Step 5: If (name, value) is a forbidden request-header, then return.
471        if is_forbidden_request_header(name_str, value) {
472            return Ok(());
473        }
474
475        debug!(
476            "SetRequestHeader: name={:?}, value={:?}",
477            name_str,
478            str::from_utf8(value).ok()
479        );
480        let mut headers = self.request_headers.safe_borrow_mut(no_gc);
481
482        // Step 6: Combine (name, value) in this’s author request headers.
483        // https://fetch.spec.whatwg.org/#concept-header-list-combine
484        let value = match headers.get(name_str).map(HeaderValue::as_bytes) {
485            Some(raw) => {
486                let mut buf = raw.to_vec();
487                buf.extend_from_slice(b", ");
488                buf.extend_from_slice(value);
489                buf
490            },
491            None => value.into(),
492        };
493
494        if let (Ok(header_name), Ok(header_value)) = (
495            HeaderName::from_str(name_str),
496            HeaderValue::from_bytes(&value),
497        ) {
498            headers.insert(header_name, header_value);
499        } else {
500            warn!("Not setting header in XMLHttpRequest {name_str:?}: {value:?}");
501        }
502
503        Ok(())
504    }
505
506    /// <https://xhr.spec.whatwg.org/#the-timeout-attribute>
507    fn Timeout(&self) -> u32 {
508        self.timeout.get().as_millis() as u32
509    }
510
511    /// <https://xhr.spec.whatwg.org/#the-timeout-attribute>
512    fn SetTimeout(&self, no_gc: &NoGC, timeout: u32) -> ErrorResult {
513        // Step 1
514        if self.sync_in_window() {
515            return Err(Error::InvalidAccess(None));
516        }
517
518        // Step 2
519        let timeout = Duration::from_millis(timeout as u64);
520        self.timeout.set(timeout);
521
522        if self.send_flag.get() {
523            if timeout.is_zero() {
524                self.cancel_timeout(no_gc);
525                return Ok(());
526            }
527            let progress = Instant::now() - self.fetch_time.get();
528            if timeout > progress {
529                self.set_timeout(no_gc, timeout - progress);
530            } else {
531                // Immediately execute the timeout steps
532                self.set_timeout(no_gc, Duration::ZERO);
533            }
534        }
535        Ok(())
536    }
537
538    /// <https://xhr.spec.whatwg.org/#the-withcredentials-attribute>
539    fn WithCredentials(&self) -> bool {
540        self.with_credentials.get()
541    }
542
543    /// <https://xhr.spec.whatwg.org/#dom-xmlhttprequest-withcredentials>
544    fn SetWithCredentials(&self, with_credentials: bool) -> ErrorResult {
545        match self.ready_state.get() {
546            // Step 1
547            XMLHttpRequestState::HeadersReceived |
548            XMLHttpRequestState::Loading |
549            XMLHttpRequestState::Done => Err(Error::InvalidState(None)),
550            // Step 2
551            _ if self.send_flag.get() => Err(Error::InvalidState(None)),
552            // Step 3
553            _ => {
554                self.with_credentials.set(with_credentials);
555                Ok(())
556            },
557        }
558    }
559
560    /// <https://xhr.spec.whatwg.org/#the-upload-attribute>
561    fn Upload(&self) -> DomRoot<XMLHttpRequestUpload> {
562        DomRoot::from_ref(&*self.upload)
563    }
564
565    /// <https://xhr.spec.whatwg.org/#dom-xmlhttprequest-send>
566    fn Send(
567        &self,
568        cx: &mut JSContext,
569        data: Option<DocumentOrXMLHttpRequestBodyInit>,
570    ) -> ErrorResult {
571        // Step 1. If this’s state is not opened, then throw an "InvalidStateError" DOMException.
572        // Step 2. If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
573        if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() {
574            return Err(Error::InvalidState(Some(
575                "XMLHttpRequest not open or already sent".into(),
576            )));
577        }
578        let Some(url) = self.request_url.borrow().clone() else {
579            return Err(Error::InvalidState(Some("XMLHttpRequest not open".into())));
580        };
581
582        // Step 3. If this’s request method is `GET` or `HEAD`, then set body to null.
583        let data = match *self.request_method.borrow() {
584            Method::GET | Method::HEAD => None,
585            _ => data,
586        };
587
588        // Step 4 (first half)
589        let mut extracted_or_serialized = match data {
590            Some(DocumentOrXMLHttpRequestBodyInit::Document(ref doc)) => {
591                let bytes = Vec::from(&*serialize_document(doc)?.as_bytes());
592                let content_type = if doc.is_html_document() {
593                    "text/html;charset=UTF-8"
594                } else {
595                    "application/xml;charset=UTF-8"
596                };
597                let total_bytes = bytes.len();
598                let global = self.global();
599                let stream = ReadableStream::new_from_bytes(cx, &global, bytes)?;
600                Some(ExtractedBody {
601                    stream,
602                    total_bytes: Some(total_bytes),
603                    content_type: Some(DOMString::from(content_type)),
604                    source: BodySource::Object,
605                })
606            },
607            Some(DocumentOrXMLHttpRequestBodyInit::Blob(ref b)) => {
608                let extracted_body = b
609                    .extract(cx, &self.global(), false)
610                    .expect("Couldn't extract body.");
611                if !extracted_body.in_memory() && self.sync.get() {
612                    warn!("Sync XHR with not in-memory Blob as body not supported");
613                    None
614                } else {
615                    Some(extracted_body)
616                }
617            },
618            Some(DocumentOrXMLHttpRequestBodyInit::FormData(ref formdata)) => Some(
619                formdata
620                    .extract(cx, &self.global(), false)
621                    .expect("Couldn't extract body."),
622            ),
623            Some(DocumentOrXMLHttpRequestBodyInit::String(ref str)) => Some(
624                str.extract(cx, &self.global(), false)
625                    .expect("Couldn't extract body."),
626            ),
627            Some(DocumentOrXMLHttpRequestBodyInit::URLSearchParams(ref urlsp)) => Some(
628                urlsp
629                    .extract(cx, &self.global(), false)
630                    .expect("Couldn't extract body."),
631            ),
632            Some(DocumentOrXMLHttpRequestBodyInit::ArrayBuffer(ref typedarray)) => {
633                let bytes = get_buffer_source_copy(typedarray.into());
634                let total_bytes = bytes.len();
635                let global = self.global();
636                let stream = ReadableStream::new_from_bytes(cx, &global, bytes)?;
637                Some(ExtractedBody {
638                    stream,
639                    total_bytes: Some(total_bytes),
640                    content_type: None,
641                    source: BodySource::Object,
642                })
643            },
644            Some(DocumentOrXMLHttpRequestBodyInit::ArrayBufferView(ref typedarray)) => {
645                let bytes = get_buffer_source_copy(typedarray.into());
646                let total_bytes = bytes.len();
647                let global = self.global();
648                let stream = ReadableStream::new_from_bytes(cx, &global, bytes)?;
649                Some(ExtractedBody {
650                    stream,
651                    total_bytes: Some(total_bytes),
652                    content_type: None,
653                    source: BodySource::Object,
654                })
655            },
656            None => None,
657        };
658
659        self.request_body_len.set(
660            extracted_or_serialized
661                .as_ref()
662                .map_or(0, |e| e.total_bytes.unwrap_or(0)),
663        );
664
665        // Step 5. If one or more event listeners are registered on this’s upload object,
666        // then set this’s upload listener flag.
667        // If we dont have data to upload, we dont want to emit events
668        let has_handlers = self.upload.upcast::<EventTarget>().has_handlers();
669        self.upload_listener.set(has_handlers && data.is_some());
670
671        // todo preserved headers?
672
673        // Step 7. Unset this’s upload complete flag.
674        self.upload_complete.set(false);
675
676        // Step 8. Unset this’s timed out flag.
677        // FIXME handle the 'timed out flag'
678
679        // Step 9. If req’s body is null, then set this’s upload complete flag.
680        self.upload_complete.set(extracted_or_serialized.is_none());
681
682        // Step 10. Set this’s send() flag.
683        self.send_flag.set(true);
684
685        // Step 11. If this’s synchronous flag is unset, then:
686        if !self.sync.get() {
687            // If one of the event handlers below aborts the fetch by calling
688            // abort or open we will need the current generation id to detect it.
689            let gen_id = self.generation_id.get();
690
691            // Step 11.1 Fire a progress event named loadstart at this with 0 and 0.
692            self.dispatch_response_progress_event(cx, atom!("loadstart"));
693            if self.generation_id.get() != gen_id {
694                return Ok(());
695            }
696
697            // Step 11.2 If this’s upload complete flag is unset and this’s upload listener flag is set,
698            // then fire a progress event named loadstart at this’s upload object with requestBodyTransmitted
699            // and requestBodyLength.
700            if !self.upload_complete.get() && self.upload_listener.get() {
701                self.dispatch_upload_progress_event(cx, atom!("loadstart"), Ok(Some(0)));
702                if self.generation_id.get() != gen_id {
703                    return Ok(());
704                }
705            }
706        }
707
708        // Step 6
709        // TODO - set referrer_policy/referrer_url in request
710        let credentials_mode = if self.with_credentials.get() {
711            CredentialsMode::Include
712        } else {
713            CredentialsMode::CredentialsSameOrigin
714        };
715        let use_url_credentials = !url.username().is_empty() || url.password().is_some();
716
717        let content_type = match extracted_or_serialized.as_mut() {
718            Some(body) => body.content_type.take(),
719            None => None,
720        };
721
722        let global = self.global();
723        let mut request = RequestBuilder::new(global.webview_id(), url, self.referrer.clone())
724            .method(self.request_method.borrow().clone())
725            .headers((*self.request_headers.borrow()).clone())
726            .unsafe_request(true)
727            // XXXManishearth figure out how to avoid this clone
728            .body(extracted_or_serialized.map(|e| e.into_net_request_body(cx).0))
729            .synchronous(self.sync.get())
730            .mode(RequestMode::CorsMode)
731            .use_cors_preflight(self.upload_listener.get())
732            .credentials_mode(credentials_mode)
733            .use_url_credentials(use_url_credentials)
734            .with_global_scope(&global)
735            .referrer_policy(self.referrer_policy);
736
737        // step 4 (second half)
738        if let Some(content_type) = content_type {
739            let encoding = match data {
740                Some(DocumentOrXMLHttpRequestBodyInit::String(_)) |
741                Some(DocumentOrXMLHttpRequestBodyInit::Document(_)) =>
742                // XHR spec differs from http, and says UTF-8 should be in capitals,
743                // instead of "utf-8", which is what Hyper defaults to. So not
744                // using content types provided by Hyper.
745                {
746                    Some("UTF-8")
747                },
748                _ => None,
749            };
750
751            // We cannot use typed header insertion with `mime::Mime` parsing here,
752            // since it lowercases `charset=UTF-8`: https://github.com/hyperium/mime/issues/116
753            let mut content_type_set = false;
754            if !request.headers.contains_key(header::CONTENT_TYPE) &&
755                let Ok(content_type_value) = HeaderValue::from_str(&content_type.str())
756            {
757                request
758                    .headers
759                    .insert(header::CONTENT_TYPE, content_type_value);
760                content_type_set = true;
761            }
762
763            if !content_type_set {
764                let content_type = request.headers.typed_get::<ContentType>();
765                if let Some(content_type) = content_type &&
766                    let Some(encoding) = encoding &&
767                    let Ok(mime) = content_type.to_string().parse::<Mime>()
768                {
769                    for param in mime.parameters.iter() {
770                        if param.0 == CHARSET && !param.1.as_str().eq_ignore_ascii_case(encoding) {
771                            let new_params: Vec<_> =
772                                mime.parameters.iter().filter(|p| p.0 != CHARSET).collect();
773
774                            let new_mime = format!(
775                                "{}/{};charset={encoding}{}{}",
776                                mime.type_,
777                                mime.subtype,
778                                if new_params.is_empty() { "" } else { "; " },
779                                new_params
780                                    .iter()
781                                    .map(|param| format!("{}={}", param.0, param.1))
782                                    .collect::<Vec<String>>()
783                                    .join("; ")
784                            );
785
786                            if let Ok(content_type_header) = HeaderValue::from_str(&new_mime) {
787                                request
788                                    .headers
789                                    .insert(header::CONTENT_TYPE, content_type_header);
790                            }
791                        }
792                    }
793                }
794            }
795        }
796
797        self.fetch_time.set(Instant::now());
798
799        let rv = self.fetch(cx, request, &self.global());
800        // Step 10
801        if self.sync.get() {
802            return rv;
803        }
804
805        let timeout = self.timeout.get();
806        if timeout > Duration::ZERO {
807            self.set_timeout(cx.no_gc(), timeout);
808        }
809        Ok(())
810    }
811
812    /// <https://xhr.spec.whatwg.org/#the-abort()-method>
813    fn Abort(&self, cx: &mut JSContext) {
814        // Step 1
815        self.terminate_ongoing_fetch(cx.no_gc());
816        // Step 2
817        let state = self.ready_state.get();
818        if (state == XMLHttpRequestState::Opened && self.send_flag.get()) ||
819            state == XMLHttpRequestState::HeadersReceived ||
820            state == XMLHttpRequestState::Loading
821        {
822            let gen_id = self.generation_id.get();
823            self.process_partial_response(cx, XHRProgress::Errored(gen_id, Error::Abort(None)));
824            // If open was called in one of the handlers invoked by the
825            // above call then we should terminate the abort sequence
826            if self.generation_id.get() != gen_id {
827                return;
828            }
829        }
830        // Step 3
831        if self.ready_state.get() == XMLHttpRequestState::Done {
832            self.change_ready_state(cx, XMLHttpRequestState::Unsent);
833            self.response_status.set(Err(()));
834            *self.status.safe_borrow_mut(cx.no_gc()) = HttpStatus::new_error();
835            self.response.safe_borrow_mut(cx.no_gc()).clear();
836            self.response_headers.safe_borrow_mut(cx.no_gc()).clear();
837        }
838    }
839
840    /// <https://xhr.spec.whatwg.org/#the-responseurl-attribute>
841    fn ResponseURL(&self) -> USVString {
842        USVString(self.response_url.borrow().clone())
843    }
844
845    /// <https://xhr.spec.whatwg.org/#the-status-attribute>
846    fn Status(&self) -> u16 {
847        self.status.borrow().raw_code()
848    }
849
850    /// <https://xhr.spec.whatwg.org/#the-statustext-attribute>
851    fn StatusText(&self) -> ByteString {
852        ByteString::new(self.status.borrow().message().to_vec())
853    }
854
855    /// <https://xhr.spec.whatwg.org/#the-getresponseheader()-method>
856    fn GetResponseHeader(&self, name: ByteString) -> Option<ByteString> {
857        let headers = self.filter_response_headers();
858        // > All custom header names are lower cased upon conversion to a HeaderName value.
859        // https://docs.rs/http/latest/http/header/struct.HeaderName.html#representation
860        let headers = headers.get_all(HeaderName::from_str(name.as_str()?).ok()?);
861        let mut first = true;
862        let s = headers.iter().fold(Vec::new(), |mut vec, value| {
863            if !first {
864                vec.extend(", ".as_bytes());
865            }
866            if let Ok(v) = str::from_utf8(value.as_bytes()).map(|s| s.trim().as_bytes()) {
867                vec.extend(v);
868                first = false;
869            }
870            vec
871        });
872
873        // There was no header with that name so we never got to change that value
874        if first {
875            None
876        } else {
877            Some(ByteString::new(s))
878        }
879    }
880
881    /// <https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method>
882    fn GetAllResponseHeaders(&self) -> ByteString {
883        let headers = self.filter_response_headers();
884        let keys = headers.keys();
885        let v = keys.fold(Vec::new(), |mut vec, k| {
886            let values = headers.get_all(k);
887            vec.extend(k.as_str().as_bytes());
888            vec.extend(": ".as_bytes());
889            let mut first = true;
890            for value in values {
891                if !first {
892                    vec.extend(", ".as_bytes());
893                    first = false;
894                }
895                vec.extend(value.as_bytes());
896            }
897            vec.extend("\r\n".as_bytes());
898            vec
899        });
900
901        ByteString::new(v)
902    }
903
904    /// <https://xhr.spec.whatwg.org/#the-overridemimetype()-method>
905    fn OverrideMimeType(&self, no_gc: &NoGC, mime: DOMString) -> ErrorResult {
906        // 1. If this’s state is loading or done, then throw an "InvalidStateError"
907        //   DOMException.
908        match self.ready_state.get() {
909            XMLHttpRequestState::Loading | XMLHttpRequestState::Done => {
910                return Err(Error::InvalidState(None));
911            },
912            _ => {},
913        }
914
915        // 2. Set this’s override MIME type to the result of parsing mime.
916        // 3. If this’s override MIME type is failure, then set this’s override MIME type
917        //    to application/octet-stream.
918        let override_mime = match mime.parse::<Mime>() {
919            Ok(mime) => mime,
920            Err(_) => "application/octet-stream"
921                .parse::<Mime>()
922                .map_err(|_| Error::Syntax(None))?,
923        };
924
925        *self.override_mime_type.safe_borrow_mut(no_gc) = Some(override_mime);
926        Ok(())
927    }
928
929    /// <https://xhr.spec.whatwg.org/#the-responsetype-attribute>
930    fn ResponseType(&self) -> XMLHttpRequestResponseType {
931        self.response_type.get()
932    }
933
934    /// <https://xhr.spec.whatwg.org/#the-responsetype-attribute>
935    fn SetResponseType(&self, response_type: XMLHttpRequestResponseType) -> ErrorResult {
936        // Step 1
937        if self.global().is::<WorkerGlobalScope>() &&
938            response_type == XMLHttpRequestResponseType::Document
939        {
940            return Ok(());
941        }
942        match self.ready_state.get() {
943            // Step 2
944            XMLHttpRequestState::Loading | XMLHttpRequestState::Done => {
945                Err(Error::InvalidState(None))
946            },
947            _ => {
948                if self.sync_in_window() {
949                    // Step 3
950                    Err(Error::InvalidAccess(None))
951                } else {
952                    // Step 4
953                    self.response_type.set(response_type);
954                    Ok(())
955                }
956            },
957        }
958    }
959
960    /// <https://xhr.spec.whatwg.org/#the-response-attribute>
961    fn Response(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
962        match self.response_type.get() {
963            XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => {
964                let ready_state = self.ready_state.get();
965                // Step 2
966                if ready_state == XMLHttpRequestState::Done ||
967                    ready_state == XMLHttpRequestState::Loading
968                {
969                    self.text_response().to_jsval(cx, rval);
970                } else {
971                    // Step 1
972                    "".to_jsval(cx, rval);
973                }
974            },
975            // Step 1
976            _ if self.ready_state.get() != XMLHttpRequestState::Done => {
977                rval.set(NullValue());
978            },
979            // Step 2
980            XMLHttpRequestResponseType::Document => self.document_response(cx).to_jsval(cx, rval),
981            XMLHttpRequestResponseType::Json => self.json_response(cx, rval),
982            XMLHttpRequestResponseType::Blob => self.blob_response(cx).to_jsval(cx, rval),
983            XMLHttpRequestResponseType::Arraybuffer => match self.arraybuffer_response(cx) {
984                Some(array_buffer) => array_buffer.to_jsval(cx, rval),
985                None => rval.set(NullValue()),
986            },
987        }
988    }
989
990    /// <https://xhr.spec.whatwg.org/#the-responsetext-attribute>
991    fn GetResponseText(&self) -> Fallible<USVString> {
992        match self.response_type.get() {
993            XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => {
994                Ok(USVString(match self.ready_state.get() {
995                    // Step 3
996                    XMLHttpRequestState::Loading | XMLHttpRequestState::Done => {
997                        self.text_response()
998                    },
999                    // Step 2
1000                    _ => "".to_owned(),
1001                }))
1002            },
1003            // Step 1
1004            _ => Err(Error::InvalidState(None)),
1005        }
1006    }
1007
1008    /// <https://xhr.spec.whatwg.org/#the-responsexml-attribute>
1009    fn GetResponseXML(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Document>>> {
1010        match self.response_type.get() {
1011            XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Document => {
1012                // Step 3
1013                if let XMLHttpRequestState::Done = self.ready_state.get() {
1014                    Ok(self.document_response(cx))
1015                } else {
1016                    // Step 2
1017                    Ok(None)
1018                }
1019            },
1020            // Step 1
1021            _ => Err(Error::InvalidState(None)),
1022        }
1023    }
1024}
1025
1026pub(crate) type TrustedXHRAddress = Trusted<XMLHttpRequest>;
1027
1028impl XMLHttpRequest {
1029    fn change_ready_state(&self, cx: &mut JSContext, rs: XMLHttpRequestState) {
1030        assert_ne!(self.ready_state.get(), rs);
1031        self.ready_state.set(rs);
1032        if rs != XMLHttpRequestState::Unsent {
1033            let event = Event::new(
1034                cx,
1035                &self.global(),
1036                atom!("readystatechange"),
1037                EventBubbles::DoesNotBubble,
1038                EventCancelable::Cancelable,
1039            );
1040            event.fire(cx, self.upcast());
1041        }
1042    }
1043
1044    fn process_headers_available(
1045        &self,
1046        cx: &mut JSContext,
1047        gen_id: GenerationId,
1048        metadata: Result<FetchMetadata, NetworkError>,
1049    ) -> Result<(), Error> {
1050        let metadata = match metadata {
1051            Ok(meta) => match meta {
1052                FetchMetadata::Unfiltered(m) => m,
1053                FetchMetadata::Filtered { filtered, .. } => match filtered {
1054                    FilteredMetadata::Basic(m) => m,
1055                    FilteredMetadata::Cors(m) => m,
1056                    FilteredMetadata::Opaque => return Err(Error::Network(None)),
1057                    FilteredMetadata::OpaqueRedirect(_) => return Err(Error::Network(None)),
1058                },
1059            },
1060            Err(_) => {
1061                self.process_partial_response(
1062                    cx,
1063                    XHRProgress::Errored(gen_id, Error::Network(None)),
1064                );
1065                return Err(Error::Network(None));
1066            },
1067        };
1068
1069        metadata.final_url[..Position::AfterQuery]
1070            .clone_into(&mut self.response_url.safe_borrow_mut(cx.no_gc()));
1071
1072        // XXXManishearth Clear cache entries in case of a network error
1073        self.process_partial_response(
1074            cx,
1075            XHRProgress::HeadersReceived(
1076                gen_id,
1077                metadata.headers.map(Serde::into_inner),
1078                metadata.status,
1079            ),
1080        );
1081        Ok(())
1082    }
1083
1084    fn process_data_available(&self, cx: &mut JSContext, gen_id: GenerationId, payload: Bytes) {
1085        self.process_partial_response(cx, XHRProgress::Loading(gen_id, payload));
1086    }
1087
1088    fn process_response_complete(
1089        &self,
1090        cx: &mut JSContext,
1091        gen_id: GenerationId,
1092        status: Result<(), NetworkError>,
1093    ) -> ErrorResult {
1094        match status {
1095            Ok(()) => {
1096                self.process_partial_response(cx, XHRProgress::Done(gen_id));
1097                Ok(())
1098            },
1099            Err(_) => {
1100                self.process_partial_response(
1101                    cx,
1102                    XHRProgress::Errored(gen_id, Error::Network(None)),
1103                );
1104                Err(Error::Network(None))
1105            },
1106        }
1107    }
1108
1109    fn process_partial_response(&self, cx: &mut JSContext, progress: XHRProgress) {
1110        let msg_id = progress.generation_id();
1111
1112        // Aborts processing if abort() or open() was called
1113        // (including from one of the event handlers called below)
1114        macro_rules! return_if_fetch_was_terminated(
1115            () => (
1116                if msg_id != self.generation_id.get() {
1117                    return
1118                }
1119            );
1120        );
1121
1122        // Ignore message if it belongs to a terminated fetch
1123        return_if_fetch_was_terminated!();
1124
1125        // Ignore messages coming from previously-errored responses or requests that have timed out
1126        if self.response_status.get().is_err() {
1127            return;
1128        }
1129
1130        match progress {
1131            XHRProgress::HeadersReceived(_, headers, status) => {
1132                assert!(self.ready_state.get() == XMLHttpRequestState::Opened);
1133                // For synchronous requests, this should not fire any events, and just store data
1134                // XXXManishearth Find a way to track partial progress of the send (onprogresss for XHRUpload)
1135
1136                // Part of step 13, send() (processing request end of file)
1137                // Substep 1
1138                self.upload_complete.set(true);
1139                // Substeps 2-4
1140                if !self.sync.get() && self.upload_listener.get() {
1141                    self.dispatch_upload_progress_event(cx, atom!("progress"), Ok(None));
1142                    return_if_fetch_was_terminated!();
1143                    self.dispatch_upload_progress_event(cx, atom!("load"), Ok(None));
1144                    return_if_fetch_was_terminated!();
1145                    self.dispatch_upload_progress_event(cx, atom!("loadend"), Ok(None));
1146                    return_if_fetch_was_terminated!();
1147                }
1148                // Part of step 13, send() (processing response)
1149                // XXXManishearth handle errors, if any (substep 1)
1150                // Substep 2
1151                if !status.is_error() {
1152                    *self.status.safe_borrow_mut(cx.no_gc()) = status;
1153                }
1154                if let Some(h) = headers.as_ref() {
1155                    *self.response_headers.safe_borrow_mut(cx.no_gc()) = h.clone();
1156                }
1157                {
1158                    let len = headers.and_then(|h| h.typed_get::<ContentLength>());
1159                    let mut response = self.response.safe_borrow_mut(cx.no_gc());
1160                    response.clear();
1161                    if let Some(len) = len {
1162                        // don't attempt to prereserve more than 4 MB of memory,
1163                        // to avoid giving servers the ability to DOS the client by
1164                        // providing arbitrarily large content-lengths.
1165                        //
1166                        // this number is arbitrary, it's basically big enough that most
1167                        // XHR requests won't hit it, but not so big that it allows for DOS
1168                        let size = cmp::min(0b100_0000000000_0000000000, len.0 as usize);
1169
1170                        // preallocate the buffer
1171                        response.reserve(size);
1172                    }
1173                }
1174                // Substep 3
1175                if !self.sync.get() {
1176                    self.change_ready_state(cx, XMLHttpRequestState::HeadersReceived);
1177                }
1178            },
1179            XHRProgress::Loading(_, partial_response) => {
1180                // For synchronous requests, this should not fire any events, and just store data
1181                // Part of step 11, send() (processing response body)
1182                // XXXManishearth handle errors, if any (substep 2)
1183
1184                self.response
1185                    .safe_borrow_mut(cx.no_gc())
1186                    .extend_from_slice(&partial_response);
1187                if !self.sync.get() {
1188                    if self.ready_state.get() == XMLHttpRequestState::HeadersReceived {
1189                        self.ready_state.set(XMLHttpRequestState::Loading);
1190                    }
1191                    let event = Event::new(
1192                        cx,
1193                        &self.global(),
1194                        atom!("readystatechange"),
1195                        EventBubbles::DoesNotBubble,
1196                        EventCancelable::Cancelable,
1197                    );
1198                    event.fire(cx, self.upcast());
1199                    return_if_fetch_was_terminated!();
1200                    self.dispatch_response_progress_event(cx, atom!("progress"));
1201                }
1202            },
1203            XHRProgress::Done(_) => {
1204                assert!(
1205                    self.ready_state.get() == XMLHttpRequestState::HeadersReceived ||
1206                        self.ready_state.get() == XMLHttpRequestState::Loading ||
1207                        self.sync.get()
1208                );
1209
1210                self.cancel_timeout(cx.no_gc());
1211                self.canceller.safe_borrow_mut(cx.no_gc()).ignore();
1212
1213                // Part of step 11, send() (processing response end of file)
1214                // XXXManishearth handle errors, if any (substep 2)
1215
1216                // Subsubsteps 6-8
1217                self.send_flag.set(false);
1218
1219                self.change_ready_state(cx, XMLHttpRequestState::Done);
1220                return_if_fetch_was_terminated!();
1221                // Subsubsteps 11-12
1222                self.dispatch_response_progress_event(cx, atom!("load"));
1223                return_if_fetch_was_terminated!();
1224                self.dispatch_response_progress_event(cx, atom!("loadend"));
1225            },
1226            XHRProgress::Errored(_, e) => {
1227                self.cancel_timeout(cx.no_gc());
1228                self.canceller.safe_borrow_mut(cx.no_gc()).ignore();
1229
1230                self.discard_subsequent_responses();
1231                self.send_flag.set(false);
1232                *self.status.safe_borrow_mut(cx.no_gc()) = HttpStatus::new_error();
1233                self.response_headers.safe_borrow_mut(cx.no_gc()).clear();
1234                // XXXManishearth set response to NetworkError
1235                self.change_ready_state(cx, XMLHttpRequestState::Done);
1236                return_if_fetch_was_terminated!();
1237
1238                let errormsg = match e {
1239                    Error::Abort(None) => "abort",
1240                    Error::Timeout(None) => "timeout",
1241                    _ => "error",
1242                };
1243
1244                let upload_complete = &self.upload_complete;
1245                if !upload_complete.get() {
1246                    upload_complete.set(true);
1247                    if self.upload_listener.get() {
1248                        self.dispatch_upload_progress_event(cx, Atom::from(errormsg), Err(()));
1249                        return_if_fetch_was_terminated!();
1250                        self.dispatch_upload_progress_event(cx, atom!("loadend"), Err(()));
1251                        return_if_fetch_was_terminated!();
1252                    }
1253                }
1254                self.dispatch_response_progress_event(cx, Atom::from(errormsg));
1255                return_if_fetch_was_terminated!();
1256                self.dispatch_response_progress_event(cx, atom!("loadend"));
1257            },
1258        }
1259    }
1260
1261    fn terminate_ongoing_fetch(&self, no_gc: &NoGC) {
1262        self.canceller.safe_borrow_mut(no_gc).abort();
1263        let GenerationId(prev_id) = self.generation_id.get();
1264        self.generation_id.set(GenerationId(prev_id + 1));
1265        self.response_status.set(Ok(()));
1266    }
1267
1268    fn dispatch_progress_event(
1269        &self,
1270        cx: &mut JSContext,
1271        upload: bool,
1272        type_: Atom,
1273        loaded: u64,
1274        total: Option<u64>,
1275    ) {
1276        let (total_length, length_computable) = if self
1277            .response_headers
1278            .borrow()
1279            .contains_key(header::CONTENT_ENCODING)
1280        {
1281            (0, false)
1282        } else {
1283            (total.unwrap_or(0), total.is_some())
1284        };
1285        let progressevent = ProgressEvent::new(
1286            cx,
1287            &self.global(),
1288            type_,
1289            EventBubbles::DoesNotBubble,
1290            EventCancelable::NotCancelable,
1291            length_computable,
1292            Finite::wrap(loaded as f64),
1293            Finite::wrap(total_length as f64),
1294        );
1295        let target = if upload {
1296            self.upload.upcast()
1297        } else {
1298            self.upcast()
1299        };
1300        progressevent.upcast::<Event>().fire(cx, target);
1301    }
1302
1303    fn dispatch_upload_progress_event(
1304        &self,
1305        cx: &mut JSContext,
1306        type_: Atom,
1307        partial_load: Result<Option<u64>, ()>,
1308    ) {
1309        // If partial_load is Ok(None), loading has completed and we can just use the value from the request body
1310        // If an error occurred, we pass 0 for both loaded and total
1311
1312        let request_body_len = self.request_body_len.get() as u64;
1313        let (loaded, total) = match partial_load {
1314            Ok(l) => match l {
1315                Some(loaded) => (loaded, Some(request_body_len)),
1316                None => (request_body_len, Some(request_body_len)),
1317            },
1318            Err(()) => (0, None),
1319        };
1320        self.dispatch_progress_event(cx, true, type_, loaded, total);
1321    }
1322
1323    fn dispatch_response_progress_event(&self, cx: &mut JSContext, type_: Atom) {
1324        let len = self.response.borrow().len() as u64;
1325        let total = self
1326            .response_headers
1327            .borrow()
1328            .typed_get::<ContentLength>()
1329            .map(|v| v.0);
1330        self.dispatch_progress_event(cx, false, type_, len, total);
1331    }
1332
1333    fn set_timeout(&self, no_gc: &NoGC, duration: Duration) {
1334        // Sets up the object to timeout in a given number of milliseconds
1335        // This will cancel all previous timeouts
1336        let callback = OneshotTimerCallback::XhrTimeout(XHRTimeoutCallback {
1337            xhr: Trusted::new(self),
1338            generation_id: self.generation_id.get(),
1339        });
1340        *self.timeout_cancel.safe_borrow_mut(no_gc) =
1341            Some(self.global().schedule_callback(callback, duration));
1342    }
1343
1344    fn cancel_timeout(&self, no_gc: &NoGC) {
1345        if let Some(handle) = self.timeout_cancel.safe_borrow_mut(no_gc).take() {
1346            self.global().unschedule_callback(handle);
1347        }
1348    }
1349
1350    /// <https://xhr.spec.whatwg.org/#text-response>
1351    fn text_response(&self) -> String {
1352        // Step 3, 5
1353        let charset = self.final_charset().unwrap_or(UTF_8);
1354        // TODO: Step 4 - add support for XML encoding guess stuff using XML spec
1355
1356        // According to Simon, decode() should never return an error, so unwrap()ing
1357        // the result should be fine. XXXManishearth have a closer look at this later
1358        // Step 1, 2, 6
1359        let response = self.response.borrow();
1360        let (text, _, _) = charset.decode(&response);
1361        text.into_owned()
1362    }
1363
1364    /// <https://xhr.spec.whatwg.org/#blob-response>
1365    fn blob_response(&self, cx: &mut JSContext) -> DomRoot<Blob> {
1366        // Step 1
1367        if let Some(response) = self.response_blob.get() {
1368            return response;
1369        }
1370        // Step 2
1371        let mime = normalize_type_string(&self.final_mime_type().to_string());
1372
1373        // Step 3, 4
1374        let bytes = self.response.borrow().to_vec();
1375        let blob = Blob::new(cx, &self.global(), BlobImpl::new_from_bytes(bytes, mime));
1376        self.response_blob.set(Some(&blob));
1377        blob
1378    }
1379
1380    /// <https://xhr.spec.whatwg.org/#arraybuffer-response>
1381    fn arraybuffer_response(
1382        &self,
1383        cx: &mut JSContext,
1384    ) -> Option<RootedTraceableBox<HeapArrayBuffer>> {
1385        // Step 5: Set the response object to a new ArrayBuffer with the received bytes
1386        // For caching purposes, skip this step if the response is already created
1387        if !self.response_arraybuffer.is_initialized() {
1388            let bytes = self.response.borrow();
1389
1390            // If this is not successful, the response won't be set and the function will return None
1391            self.response_arraybuffer.set_data(cx, &bytes).ok()?;
1392        }
1393
1394        // Return the correct ArrayBuffer
1395        self.response_arraybuffer.get_typed_array().ok()
1396    }
1397
1398    /// <https://xhr.spec.whatwg.org/#document-response>
1399    fn document_response(&self, cx: &mut JSContext) -> Option<DomRoot<Document>> {
1400        // Caching: if we have existing response xml, redirect it directly
1401        let response = self.response_xml.get();
1402        if response.is_some() {
1403            return response;
1404        }
1405
1406        // Step 1: If xhr’s response’s body is null, then return.
1407        if self.response_status.get().is_err() {
1408            return None;
1409        }
1410
1411        // Step 2: Let finalMIME be the result of get a final MIME type for xhr.
1412        let final_mime = self.final_mime_type();
1413
1414        // Step 3: If finalMIME is not an HTML MIME type or an XML MIME type, then return.
1415        let is_xml_mime_type = final_mime.matches(TEXT, XML) ||
1416            final_mime.matches(APPLICATION, XML) ||
1417            final_mime.has_suffix(XML);
1418        if !final_mime.matches(TEXT, HTML) && !is_xml_mime_type {
1419            return None;
1420        }
1421
1422        // Step 4: If xhr’s response type is the empty string and finalMIME is an HTML MIME
1423        //         type, then return.
1424        let charset;
1425        let temp_doc;
1426        if final_mime.matches(TEXT, HTML) {
1427            if self.response_type.get() == XMLHttpRequestResponseType::_empty {
1428                return None;
1429            }
1430
1431            // Step 5: If finalMIME is an HTML MIME type, then:
1432            // Step 5.1: Let charset be the result of get a final encoding for xhr.
1433            // Step 5.2: If charset is null, prescan the first 1024 bytes of xhr’s received bytes
1434            // and if that does not terminate unsuccessfully then let charset be the return value.
1435            // TODO: This isn't happening right now.
1436            // Step 5.3. If charset is null, then set charset to UTF-8.
1437            charset = Some(self.final_charset().unwrap_or(UTF_8));
1438
1439            // Step 5.4: Let document be a document that represents the result parsing xhr’s
1440            // received bytes following the rules set forth in the HTML Standard for an HTML parser
1441            // with scripting disabled and a known definite encoding charset. [HTML]
1442            temp_doc = self.document_text_html(cx);
1443        } else {
1444            assert!(is_xml_mime_type);
1445
1446            // Step 6: Otherwise, let document be a document that represents the result of running
1447            // the XML parser with XML scripting support disabled on xhr’s received bytes. If that
1448            // fails (unsupported character encoding, namespace well-formedness error, etc.), then
1449            // return null. [HTML]
1450            //
1451            // TODO: The spec seems to suggest the charset should come from the XML parser here.
1452            temp_doc = self.handle_xml(cx);
1453            charset = self.final_charset();
1454
1455            // Not sure it the parser should throw an error for this case
1456            // The specification does not indicates this test,
1457            // but for now we check the document has no child nodes
1458            let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none();
1459            if has_no_child_nodes {
1460                return None;
1461            }
1462        }
1463
1464        // Step 7: If charset is null, then set charset to UTF-8.
1465        let charset = charset.unwrap_or(UTF_8);
1466
1467        // Step 8: Set document’s encoding to charset.
1468        temp_doc.set_encoding(charset);
1469
1470        // Step 9: Set document’s content type to finalMIME.
1471        // Step 10: Set document’s URL to xhr’s response’s URL.
1472        // Step 11: Set document’s origin to xhr’s relevant settings object’s origin.
1473        //
1474        // Done by `handle_text_html()` and `handle_xml()`.
1475
1476        // Step 12: Set xhr’s response object to document.
1477        self.response_xml.set(Some(&temp_doc));
1478        self.response_xml.get()
1479    }
1480
1481    #[expect(unsafe_code)]
1482    /// <https://xhr.spec.whatwg.org/#json-response>
1483    fn json_response(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
1484        // Step 1
1485        let response_json = self.response_json.get();
1486        if !response_json.is_null_or_undefined() {
1487            return rval.set(response_json);
1488        }
1489        // Step 2
1490        let bytes = self.response.borrow();
1491        // Step 3
1492        if bytes.is_empty() {
1493            return rval.set(NullValue());
1494        }
1495        // Step 4
1496        // https://xhr.spec.whatwg.org/#json-response refers to
1497        // https://infra.spec.whatwg.org/#parse-json-from-bytes which refers to
1498        // https://encoding.spec.whatwg.org/#utf-8-decode which means
1499        // that the encoding is always UTF-8 and the UTF-8 BOM is removed,
1500        // if present, but UTF-16BE/LE BOM must not be honored.
1501        let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
1502        // Step 5
1503        unsafe {
1504            if !JS_ParseJSON(
1505                cx,
1506                json_text.as_ptr(),
1507                json_text.len() as u32,
1508                rval.reborrow(),
1509            ) {
1510                JS_ClearPendingException(cx);
1511                return rval.set(NullValue());
1512            }
1513        }
1514        // Step 6
1515        self.response_json.set(rval.get());
1516    }
1517
1518    fn document_text_html(&self, cx: &mut JSContext) -> DomRoot<Document> {
1519        let charset = self.final_charset().unwrap_or(UTF_8);
1520        let wr = self.global();
1521        let response = self.response.borrow();
1522        let (decoded, _, _) = charset.decode(&response);
1523        let document = self.new_doc(cx, IsHTMLDocument::HTMLDocument);
1524        // TODO: Disable scripting while parsing
1525        ServoParser::parse_html_document(
1526            cx,
1527            &document,
1528            Some(DOMString::from(decoded)),
1529            wr.get_url(),
1530            None,
1531            None,
1532        );
1533        document
1534    }
1535
1536    fn handle_xml(&self, cx: &mut JSContext) -> DomRoot<Document> {
1537        let charset = self.final_charset().unwrap_or(UTF_8);
1538        let wr = self.global();
1539        let response = self.response.borrow();
1540        let (decoded, _, _) = charset.decode(&response);
1541        let document = self.new_doc(cx, IsHTMLDocument::NonHTMLDocument);
1542        // TODO: Disable scripting while parsing
1543        ServoParser::parse_xml_document(
1544            cx,
1545            &document,
1546            Some(DOMString::from(decoded)),
1547            wr.get_url(),
1548            None,
1549        );
1550        document
1551    }
1552
1553    fn new_doc(&self, cx: &mut JSContext, is_html_document: IsHTMLDocument) -> DomRoot<Document> {
1554        let wr = self.global();
1555        let win = wr.as_window();
1556        let doc = win.Document();
1557        let docloader = DocumentLoader::new(&doc.loader());
1558        let base = wr.get_url();
1559        let parsed_url = base.join(&self.ResponseURL().0).ok();
1560        let content_type = Some(self.final_mime_type());
1561        Document::new(
1562            cx,
1563            win,
1564            HasBrowsingContext::No,
1565            parsed_url,
1566            None,
1567            doc.origin().clone(),
1568            is_html_document,
1569            content_type,
1570            None,
1571            DocumentActivity::Inactive,
1572            docloader,
1573            None,
1574            None,
1575            Default::default(),
1576            false,
1577            false,
1578            Some(doc.insecure_requests_policy()),
1579            doc.has_trustworthy_ancestor_origin(),
1580            doc.custom_element_reaction_stack(),
1581            doc.creation_sandboxing_flag_set(),
1582            doc.pipeline_id(),
1583            doc.image_cache(),
1584        )
1585    }
1586
1587    fn filter_response_headers(&self) -> HeaderMap {
1588        // https://fetch.spec.whatwg.org/#concept-response-header-list
1589        let mut headers = self.response_headers.borrow().clone();
1590        headers.remove(header::SET_COOKIE);
1591        headers.remove(HeaderName::from_static("set-cookie2"));
1592        // XXXManishearth additional CORS filtering goes here
1593        headers
1594    }
1595
1596    fn discard_subsequent_responses(&self) {
1597        self.response_status.set(Err(()));
1598    }
1599
1600    fn fetch(
1601        &self,
1602        cx: &mut JSContext,
1603        request_builder: RequestBuilder,
1604        global: &GlobalScope,
1605    ) -> ErrorResult {
1606        let xhr = Trusted::new(self);
1607
1608        let sync_status = Arc::new(AtomicRefCell::new(None));
1609        let context = XHRContext {
1610            xhr,
1611            gen_id: self.generation_id.get(),
1612            sync_status: sync_status.clone(),
1613            url: request_builder.url.url(),
1614        };
1615
1616        let (task_source, script_port) = if self.sync.get() {
1617            let (sender, receiver) = global.new_script_pair();
1618            (
1619                SendableTaskSource {
1620                    sender,
1621                    pipeline_id: global.pipeline_id(),
1622                    name: TaskSourceName::Networking,
1623                    canceller: Default::default(),
1624                },
1625                Some(receiver),
1626            )
1627        } else {
1628            (
1629                global.task_manager().networking_task_source().to_sendable(),
1630                None,
1631            )
1632        };
1633
1634        *self.canceller.safe_borrow_mut(cx.no_gc()) =
1635            FetchCanceller::new(request_builder.id, false, global.core_resource_thread());
1636
1637        global.fetch(request_builder, context, task_source);
1638
1639        if let Some(script_port) = script_port {
1640            loop {
1641                if !global.process_event(script_port.recv().unwrap(), cx) {
1642                    // We're exiting.
1643                    return Err(Error::Abort(None));
1644                }
1645                if let Some(ref status) = *sync_status.borrow() {
1646                    return status.clone();
1647                }
1648            }
1649        }
1650        Ok(())
1651    }
1652
1653    /// <https://xhr.spec.whatwg.org/#final-charset>
1654    fn final_charset(&self) -> Option<&'static Encoding> {
1655        // 1. Let label be null.
1656        // 2. Let responseMIME be the result of get a response MIME type for xhr.
1657        // 3. If responseMIME’s parameters["charset"] exists, then set label to it.
1658        let response_charset = self
1659            .response_mime_type()
1660            .get_parameter(CHARSET)
1661            .map(ToString::to_string);
1662
1663        // 4. If xhr’s override MIME type’s parameters["charset"] exists, then set label to it.
1664        let override_charset = self
1665            .override_mime_type
1666            .borrow()
1667            .as_ref()
1668            .and_then(|mime| mime.get_parameter(CHARSET))
1669            .map(ToString::to_string);
1670
1671        // 5. If label is null, then return null.
1672        // 6. Let encoding be the result of getting an encoding from label.
1673        // 7. If encoding is failure, then return null.
1674        // 8. Return encoding.
1675        override_charset
1676            .or(response_charset)
1677            .and_then(|charset| Encoding::for_label(charset.as_bytes()))
1678    }
1679
1680    /// <https://xhr.spec.whatwg.org/#response-mime-type>
1681    fn response_mime_type(&self) -> Mime {
1682        // 1. Let mimeType be the result of extracting a MIME type from xhr’s response’s
1683        //    header list.
1684        // 2. If mimeType is failure, then set mimeType to text/xml.
1685        // 3. Return mimeType.
1686        extract_mime_type_as_dataurl_mime(&self.response_headers.borrow())
1687            .unwrap_or_else(|| Mime::new(TEXT, XML))
1688    }
1689
1690    /// <https://xhr.spec.whatwg.org/#final-mime-type>
1691    fn final_mime_type(&self) -> Mime {
1692        self.override_mime_type
1693            .borrow()
1694            .as_ref()
1695            .map(MimeExt::clone)
1696            .unwrap_or_else(|| self.response_mime_type())
1697    }
1698}
1699
1700#[derive(JSTraceable, MallocSizeOf)]
1701pub(crate) struct XHRTimeoutCallback {
1702    #[ignore_malloc_size_of = "Because it is non-owning"]
1703    xhr: Trusted<XMLHttpRequest>,
1704    generation_id: GenerationId,
1705}
1706
1707impl XHRTimeoutCallback {
1708    pub(crate) fn invoke(self, cx: &mut JSContext) {
1709        let xhr = self.xhr.root();
1710        if xhr.ready_state.get() != XMLHttpRequestState::Done {
1711            xhr.process_partial_response(
1712                cx,
1713                XHRProgress::Errored(self.generation_id, Error::Timeout(None)),
1714            );
1715        }
1716    }
1717}
1718
1719fn serialize_document(doc: &Document) -> Fallible<DOMString> {
1720    let mut writer = vec![];
1721    if serialize(
1722        &mut writer,
1723        &HtmlSerialize::new(doc.upcast::<Node>()),
1724        SerializeOpts::default(),
1725    )
1726    .is_err()
1727    {
1728        return Err(Error::InvalidState(Some(
1729            "Could not serialize document".into(),
1730        )));
1731    };
1732    let Ok(string) = String::from_utf8(writer) else {
1733        return Err(Error::InvalidState(Some(
1734            "Could not serialize document".into(),
1735        )));
1736    };
1737    Ok(DOMString::from(string))
1738}
1739
1740/// Returns whether `bs` is a `field-value`, as defined by
1741/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
1742pub(crate) fn is_field_value(slice: &[u8]) -> bool {
1743    // Classifications of characters necessary for the [CRLF] (SP|HT) rule
1744    #[derive(PartialEq)]
1745    #[expect(clippy::upper_case_acronyms)]
1746    enum PreviousCharacter {
1747        Other,
1748        CR,
1749        LF,
1750        SPHT, // SP or HT
1751    }
1752    let mut prev = PreviousCharacter::Other; // The previous character
1753    slice.iter().all(|&x| {
1754        // http://tools.ietf.org/html/rfc2616#section-2.2
1755        match x {
1756            13 if (prev == PreviousCharacter::Other || prev == PreviousCharacter::SPHT) => {
1757                // CR
1758                prev = PreviousCharacter::CR;
1759                true
1760            },
1761            10 if prev == PreviousCharacter::CR => {
1762                // LF
1763                prev = PreviousCharacter::LF;
1764                true
1765            },
1766            10 => false,
1767            32 => {
1768                // SP
1769                if prev == PreviousCharacter::LF || prev == PreviousCharacter::SPHT {
1770                    prev = PreviousCharacter::SPHT;
1771                    true
1772                } else if prev == PreviousCharacter::Other {
1773                    // Counts as an Other here, since it's not preceded by a CRLF
1774                    // SP is not a CTL, so it can be used anywhere
1775                    // though if used immediately after a CR the CR is invalid
1776                    // We don't change prev since it's already Other
1777                    true
1778                } else {
1779                    false
1780                }
1781            },
1782            9 if prev == PreviousCharacter::LF || prev == PreviousCharacter::SPHT => {
1783                // HT
1784                prev = PreviousCharacter::SPHT;
1785                true
1786            },
1787            9 => false,
1788            0..=31 | 127 => false, // CTLs
1789            x if x > 127 => false, // non ASCII
1790            _ if prev == PreviousCharacter::Other || prev == PreviousCharacter::SPHT => {
1791                prev = PreviousCharacter::Other;
1792                true
1793            },
1794            _ => false, // Previous character was a CR/LF but not part of the [CRLF] (SP|HT) rule
1795        }
1796    })
1797}