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