Skip to main content

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