diff options
author | yvt <i@yvt.jp> | 2021-07-10 17:24:27 +0900 |
---|---|---|
committer | yvt <i@yvt.jp> | 2021-07-10 17:55:42 +0900 |
commit | 01a7de50ab1843d85295f9dccad7f4c099e7208c (patch) | |
tree | ee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/dom/xmlhttprequest.rs | |
parent | ff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff) | |
parent | 94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff) | |
download | servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip |
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
Diffstat (limited to 'components/script/dom/xmlhttprequest.rs')
-rw-r--r-- | components/script/dom/xmlhttprequest.rs | 1234 |
1 files changed, 738 insertions, 496 deletions
diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 362dad8a925..772e00982be 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -1,80 +1,82 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use document_loader::DocumentLoader; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BlobBinding::BlobBinding::BlobMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType; -use dom::bindings::codegen::UnionTypes::DocumentOrBodyInit; -use dom::bindings::conversions::ToJSValConvertible; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::{ByteString, DOMString, USVString, is_token}; -use dom::blob::{Blob, BlobImpl}; -use dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; -use dom::document::DocumentSource; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::formdata::FormData; -use dom::globalscope::GlobalScope; -use dom::headers::is_forbidden_header_name; -use dom::htmlformelement::{encode_multipart_form_data, generate_boundary}; -use dom::node::Node; -use dom::progressevent::ProgressEvent; -use dom::servoparser::ServoParser; -use dom::urlsearchparams::URLSearchParams; -use dom::window::Window; -use dom::workerglobalscope::WorkerGlobalScope; -use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget; -use dom::xmlhttprequestupload::XMLHttpRequestUpload; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::body::{BodySource, Extractable, ExtractedBody}; +use crate::document_loader::DocumentLoader; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods; +use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType; +use crate::dom::bindings::codegen::UnionTypes::DocumentOrXMLHttpRequestBodyInit; +use crate::dom::bindings::conversions::ToJSValConvertible; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{is_token, ByteString, DOMString, USVString}; +use crate::dom::blob::{normalize_type_string, Blob}; +use crate::dom::document::DocumentSource; +use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::headers::{extract_mime_type, is_forbidden_header_name}; +use crate::dom::node::Node; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::progressevent::ProgressEvent; +use crate::dom::readablestream::ReadableStream; +use crate::dom::servoparser::ServoParser; +use crate::dom::window::Window; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget; +use crate::dom::xmlhttprequestupload::XMLHttpRequestUpload; +use crate::fetch::FetchCanceller; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; +use crate::script_runtime::JSContext; +use crate::task_source::networking::NetworkingTaskSource; +use crate::task_source::TaskSourceName; +use crate::timers::{OneshotTimerCallback, OneshotTimerHandle}; use dom_struct::dom_struct; -use encoding::all::UTF_8; -use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, EncoderTrap, Encoding, EncodingRef}; -use euclid::length::Length; +use encoding_rs::{Encoding, UTF_8}; +use euclid::Length; +use headers::{ContentLength, ContentType, HeaderMapExt}; use html5ever::serialize; use html5ever::serialize::SerializeOpts; -use hyper::header::{ContentLength, ContentType, ContentEncoding}; -use hyper::header::Headers; -use hyper::method::Method; -use hyper::mime::{self, Attr as MimeAttr, Mime, Value as MimeValue}; +use http::header::{self, HeaderMap, HeaderName, HeaderValue}; +use hyper::Method; use hyper_serde::Serde; use ipc_channel::ipc; use ipc_channel::router::ROUTER; -use js::jsapi::{Heap, JSContext, JS_ParseJSON}; use js::jsapi::JS_ClearPendingException; +use js::jsapi::{Heap, JSObject}; use js::jsval::{JSVal, NullValue, UndefinedValue}; -use net_traits::{FetchMetadata, FilteredMetadata}; -use net_traits::{FetchResponseListener, NetworkError, ReferrerPolicy}; -use net_traits::CoreResourceMsg::Fetch; -use net_traits::request::{CredentialsMode, Destination, RequestInit, RequestMode}; +use js::rust::wrappers::JS_ParseJSON; +use js::typedarray::{ArrayBuffer, CreateWith}; +use mime::{self, Mime, Name}; +use net_traits::request::{CredentialsMode, Destination, Referrer, RequestBuilder, RequestMode}; use net_traits::trim_http_whitespace; -use network_listener::{NetworkListener, PreInvoke}; +use net_traits::CoreResourceMsg::Fetch; +use net_traits::{FetchChannels, FetchMetadata, FilteredMetadata}; +use net_traits::{FetchResponseListener, NetworkError, ReferrerPolicy}; +use net_traits::{ResourceFetchTiming, ResourceTimingType}; +use script_traits::serializable::BlobImpl; use script_traits::DocumentActivity; use servo_atoms::Atom; -use servo_config::prefs::PREFS; use servo_url::ServoUrl; -use std::ascii::AsciiExt; use std::borrow::ToOwned; use std::cell::Cell; +use std::cmp; use std::default::Default; -use std::str; +use std::ptr; +use std::ptr::NonNull; +use std::slice; +use std::str::{self, FromStr}; use std::sync::{Arc, Mutex}; -use task_source::networking::NetworkingTaskSource; -use time; -use timers::{OneshotTimerCallback, OneshotTimerHandle}; use url::Position; -#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] enum XMLHttpRequestState { Unsent = 0, Opened = 1, @@ -83,7 +85,7 @@ enum XMLHttpRequestState { Done = 4, } -#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub struct GenerationId(u32); /// Closure of required data for each async network event that comprises the @@ -91,16 +93,17 @@ pub struct GenerationId(u32); struct XHRContext { xhr: TrustedXHRAddress, gen_id: GenerationId, - buf: DOMRefCell<Vec<u8>>, - sync_status: DOMRefCell<Option<ErrorResult>>, + sync_status: DomRefCell<Option<ErrorResult>>, + resource_timing: ResourceFetchTiming, + url: ServoUrl, } #[derive(Clone)] pub enum XHRProgress { /// Notify that headers have been received - HeadersReceived(GenerationId, Option<Headers>, Option<(u16, Vec<u8>)>), + HeadersReceived(GenerationId, Option<HeaderMap>, Option<(u16, Vec<u8>)>), /// Partial progress (after receiving headers), containing portion of the response - Loading(GenerationId, ByteString), + Loading(GenerationId, Vec<u8>), /// Loading is done Done(GenerationId), /// There was an error (only Error::Abort, Error::Timeout or Error::Network is used) @@ -113,7 +116,7 @@ impl XHRProgress { XHRProgress::HeadersReceived(id, _, _) | XHRProgress::Loading(id, _) | XHRProgress::Done(id) | - XHRProgress::Errored(id, _) => id + XHRProgress::Errored(id, _) => id, } } } @@ -124,50 +127,53 @@ pub struct XMLHttpRequest { ready_state: Cell<XMLHttpRequestState>, timeout: Cell<u32>, with_credentials: Cell<bool>, - upload: JS<XMLHttpRequestUpload>, - response_url: DOMRefCell<String>, + upload: Dom<XMLHttpRequestUpload>, + response_url: DomRefCell<String>, status: Cell<u16>, - status_text: DOMRefCell<ByteString>, - response: DOMRefCell<ByteString>, + status_text: DomRefCell<ByteString>, + response: DomRefCell<Vec<u8>>, response_type: Cell<XMLHttpRequestResponseType>, - response_xml: MutNullableJS<Document>, - response_blob: MutNullableJS<Blob>, - #[ignore_heap_size_of = "Defined in rust-mozjs"] + response_xml: MutNullableDom<Document>, + response_blob: MutNullableDom<Blob>, + #[ignore_malloc_size_of = "mozjs"] + response_arraybuffer: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "Defined in rust-mozjs"] response_json: Heap<JSVal>, - #[ignore_heap_size_of = "Defined in hyper"] - response_headers: DOMRefCell<Headers>, - #[ignore_heap_size_of = "Defined in hyper"] - override_mime_type: DOMRefCell<Option<Mime>>, - #[ignore_heap_size_of = "Defined in rust-encoding"] - override_charset: DOMRefCell<Option<EncodingRef>>, + #[ignore_malloc_size_of = "Defined in hyper"] + response_headers: DomRefCell<HeaderMap>, + #[ignore_malloc_size_of = "Defined in hyper"] + override_mime_type: DomRefCell<Option<Mime>>, + override_charset: DomRefCell<Option<&'static Encoding>>, // Associated concepts - #[ignore_heap_size_of = "Defined in hyper"] - request_method: DOMRefCell<Method>, - request_url: DOMRefCell<Option<ServoUrl>>, - #[ignore_heap_size_of = "Defined in hyper"] - request_headers: DOMRefCell<Headers>, + #[ignore_malloc_size_of = "Defined in hyper"] + request_method: DomRefCell<Method>, + request_url: DomRefCell<Option<ServoUrl>>, + #[ignore_malloc_size_of = "Defined in hyper"] + request_headers: DomRefCell<HeaderMap>, request_body_len: Cell<usize>, sync: Cell<bool>, upload_complete: Cell<bool>, + upload_listener: Cell<bool>, send_flag: Cell<bool>, - timeout_cancel: DOMRefCell<Option<OneshotTimerHandle>>, + timeout_cancel: DomRefCell<Option<OneshotTimerHandle>>, fetch_time: Cell<i64>, generation_id: Cell<GenerationId>, response_status: Cell<Result<(), ()>>, - referrer_url: Option<ServoUrl>, + referrer: Referrer, referrer_policy: Option<ReferrerPolicy>, + canceller: DomRefCell<FetchCanceller>, } impl XMLHttpRequest { fn new_inherited(global: &GlobalScope) -> XMLHttpRequest { //TODO - update this when referrer policy implemented for workers - let (referrer_url, referrer_policy) = if let Some(window) = global.downcast::<Window>() { + let referrer_policy = if let Some(window) = global.downcast::<Window>() { let document = window.Document(); - (Some(document.url()), document.get_referrer_policy()) + document.get_referrer_policy() } else { - (None, None) + None }; XMLHttpRequest { @@ -175,43 +181,45 @@ impl XMLHttpRequest { ready_state: Cell::new(XMLHttpRequestState::Unsent), timeout: Cell::new(0u32), with_credentials: Cell::new(false), - upload: JS::from_ref(&*XMLHttpRequestUpload::new(global)), - response_url: DOMRefCell::new(String::new()), + upload: Dom::from_ref(&*XMLHttpRequestUpload::new(global)), + response_url: DomRefCell::new(String::new()), status: Cell::new(0), - status_text: DOMRefCell::new(ByteString::new(vec!())), - response: DOMRefCell::new(ByteString::new(vec!())), + status_text: DomRefCell::new(ByteString::new(vec![])), + response: DomRefCell::new(vec![]), response_type: Cell::new(XMLHttpRequestResponseType::_empty), response_xml: Default::default(), response_blob: Default::default(), + response_arraybuffer: Heap::default(), response_json: Heap::default(), - response_headers: DOMRefCell::new(Headers::new()), - override_mime_type: DOMRefCell::new(None), - override_charset: DOMRefCell::new(None), + response_headers: DomRefCell::new(HeaderMap::new()), + override_mime_type: DomRefCell::new(None), + override_charset: DomRefCell::new(None), - request_method: DOMRefCell::new(Method::Get), - request_url: DOMRefCell::new(None), - request_headers: DOMRefCell::new(Headers::new()), + request_method: DomRefCell::new(Method::GET), + request_url: DomRefCell::new(None), + request_headers: DomRefCell::new(HeaderMap::new()), request_body_len: Cell::new(0), sync: Cell::new(false), upload_complete: Cell::new(false), + upload_listener: Cell::new(false), send_flag: Cell::new(false), - timeout_cancel: DOMRefCell::new(None), + timeout_cancel: DomRefCell::new(None), fetch_time: Cell::new(0), generation_id: Cell::new(GenerationId(0)), response_status: Cell::new(Ok(())), - referrer_url: referrer_url, + referrer: global.get_referrer(), referrer_policy: referrer_policy, + canceller: DomRefCell::new(Default::default()), } } - pub fn new(global: &GlobalScope) -> Root<XMLHttpRequest> { - reflect_dom_object(box XMLHttpRequest::new_inherited(global), - global, - XMLHttpRequestBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<XMLHttpRequest> { + reflect_dom_object(Box::new(XMLHttpRequest::new_inherited(global)), global) } // https://xhr.spec.whatwg.org/#constructors - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<XMLHttpRequest>> { + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<XMLHttpRequest>> { Ok(XMLHttpRequest::new(global)) } @@ -219,10 +227,13 @@ impl XMLHttpRequest { self.sync.get() && self.global().is::<Window>() } - fn initiate_async_xhr(context: Arc<Mutex<XHRContext>>, - task_source: NetworkingTaskSource, - global: &GlobalScope, - init: RequestInit) { + fn initiate_async_xhr( + context: Arc<Mutex<XHRContext>>, + task_source: NetworkingTaskSource, + global: &GlobalScope, + init: RequestBuilder, + cancellation_chan: ipc::IpcReceiver<()>, + ) { impl FetchResponseListener for XHRContext { fn process_request_body(&mut self) { // todo @@ -232,8 +243,7 @@ impl XMLHttpRequest { // todo } - fn process_response(&mut self, - metadata: Result<FetchMetadata, NetworkError>) { + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { let xhr = self.xhr.root(); let rv = xhr.process_headers_available(self.gen_id, metadata); if rv.is_err() { @@ -241,15 +251,42 @@ impl XMLHttpRequest { } } - fn process_response_chunk(&mut self, mut chunk: Vec<u8>) { - self.buf.borrow_mut().append(&mut chunk); - self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone()); + fn process_response_chunk(&mut self, chunk: Vec<u8>) { + self.xhr.root().process_data_available(self.gen_id, chunk); } - fn process_response_eof(&mut self, response: Result<(), NetworkError>) { - let rv = self.xhr.root().process_response_complete(self.gen_id, response); + fn process_response_eof( + &mut self, + response: Result<ResourceFetchTiming, NetworkError>, + ) { + let rv = self + .xhr + .root() + .process_response_complete(self.gen_id, response.map(|_| ())); *self.sync_status.borrow_mut() = Some(rv); } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) + } + } + + impl ResourceTimingListener for XHRContext { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + (InitiatorType::XMLHttpRequest, self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot<GlobalScope> { + self.xhr.root().global() + } } impl PreInvoke for XHRContext { @@ -259,21 +296,35 @@ impl XMLHttpRequest { } let (action_sender, action_receiver) = ipc::channel().unwrap(); + let listener = NetworkListener { context: context, task_source: task_source, - wrapper: Some(global.get_runnable_wrapper()) + canceller: Some(global.task_canceller(TaskSourceName::Networking)), }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); - global.core_resource_thread().send(Fetch(init, action_sender)).unwrap(); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + listener.notify_fetch(message.to().unwrap()); + }), + ); + global + .core_resource_thread() + .send(Fetch( + init, + FetchChannels::ResponseMsg(action_sender, Some(cancellation_chan)), + )) + .unwrap(); } } impl XMLHttpRequestMethods for XMLHttpRequest { // https://xhr.spec.whatwg.org/#handler-xhr-onreadystatechange - event_handler!(readystatechange, GetOnreadystatechange, SetOnreadystatechange); + event_handler!( + readystatechange, + GetOnreadystatechange, + SetOnreadystatechange + ); // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate fn ReadyState(&self) -> u16 { @@ -287,10 +338,16 @@ impl XMLHttpRequestMethods for XMLHttpRequest { } // https://xhr.spec.whatwg.org/#the-open()-method - fn Open_(&self, method: ByteString, url: USVString, async: bool, - username: Option<USVString>, password: Option<USVString>) -> ErrorResult { + fn Open_( + &self, + method: ByteString, + url: USVString, + asynch: bool, + username: Option<USVString>, + password: Option<USVString>, + ) -> ErrorResult { // Step 1 - if let Some(window) = Root::downcast::<Window>(self.global()) { + if let Some(window) = DomRoot::downcast::<Window>(self.global()) { if !window.Document().is_fully_active() { return Err(Error::InvalidState); } @@ -306,21 +363,20 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // despite the there being a rust-http method variant for them let upper = s.to_ascii_uppercase(); match &*upper { - "DELETE" | "GET" | "HEAD" | "OPTIONS" | - "POST" | "PUT" | "CONNECT" | "TRACE" | + "DELETE" | "GET" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "CONNECT" | "TRACE" | "TRACK" => upper.parse().ok(), - _ => s.parse().ok() + _ => s.parse().ok(), } }); match maybe_method { // Step 4 - Some(Method::Connect) | Some(Method::Trace) => Err(Error::Security), - Some(Method::Extension(ref t)) if &**t == "TRACK" => Err(Error::Security), + Some(Method::CONNECT) | Some(Method::TRACE) => Err(Error::Security), + Some(ref t) if t.as_str() == "TRACK" => Err(Error::Security), Some(parsed_method) => { // Step 3 if !is_token(&method) { - return Err(Error::Syntax) + return Err(Error::Syntax); } // Step 2 @@ -329,23 +385,26 @@ impl XMLHttpRequestMethods for XMLHttpRequest { let mut parsed_url = match base.join(&url.0) { Ok(parsed) => parsed, // Step 7 - Err(_) => return Err(Error::Syntax) + Err(_) => return Err(Error::Syntax), }; // Step 9 if parsed_url.host().is_some() { if let Some(user_str) = username { parsed_url.set_username(&user_str.0).unwrap(); - let password = password.as_ref().map(|pass_str| &*pass_str.0); - parsed_url.set_password(password).unwrap(); + } + if let Some(pass_str) = password { + parsed_url.set_password(Some(&pass_str.0)).unwrap(); } } // Step 10 - if !async { + if !asynch { // FIXME: This should only happen if the global environment is a document environment - if self.timeout.get() != 0 || self.response_type.get() != XMLHttpRequestResponseType::_empty { - return Err(Error::InvalidAccess) + if self.timeout.get() != 0 || + self.response_type.get() != XMLHttpRequestResponseType::_empty + { + return Err(Error::InvalidAccess); } } // Step 11 - abort existing requests @@ -360,10 +419,11 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 12 *self.request_method.borrow_mut() = parsed_method; *self.request_url.borrow_mut() = Some(parsed_url); - self.sync.set(!async); - *self.request_headers.borrow_mut() = Headers::new(); + self.sync.set(!asynch); + *self.request_headers.borrow_mut() = HeaderMap::new(); self.send_flag.set(false); - *self.status_text.borrow_mut() = ByteString::new(vec!()); + self.upload_listener.set(false); + *self.status_text.borrow_mut() = ByteString::new(vec![]); self.status.set(0); // Step 13 @@ -375,7 +435,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 3 // This includes cases where as_str() returns None, and when is_token() returns false, // both of which indicate invalid extension method names - _ => Err(Error::Syntax) + _ => Err(Error::Syntax), } } @@ -405,27 +465,31 @@ impl XMLHttpRequestMethods for XMLHttpRequest { s } }, - None => unreachable!() + None => unreachable!(), }; - debug!("SetRequestHeader: name={:?}, value={:?}", name.as_str(), str::from_utf8(value).ok()); + debug!( + "SetRequestHeader: name={:?}, value={:?}", + name.as_str(), + str::from_utf8(value).ok() + ); let mut headers = self.request_headers.borrow_mut(); - // Step 6 - let value = match headers.get_raw(name_str) { + let value = match headers.get(name_str).map(HeaderValue::as_bytes) { Some(raw) => { - debug!("SetRequestHeader: old value = {:?}", raw[0]); - let mut buf = raw[0].clone(); + let mut buf = raw.to_vec(); buf.extend_from_slice(b", "); buf.extend_from_slice(value); - debug!("SetRequestHeader: new value = {:?}", buf); buf }, None => value.into(), }; - headers.set_raw(name_str.to_owned(), vec![value]); + headers.insert( + HeaderName::from_str(name_str).unwrap(), + HeaderValue::from_bytes(&value).unwrap(), + ); Ok(()) } @@ -477,17 +541,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest { _ => { self.with_credentials.set(with_credentials); Ok(()) - } + }, } } // https://xhr.spec.whatwg.org/#the-upload-attribute - fn Upload(&self) -> Root<XMLHttpRequestUpload> { - Root::from_ref(&*self.upload) + fn Upload(&self) -> DomRoot<XMLHttpRequestUpload> { + DomRoot::from_ref(&*self.upload) } // https://xhr.spec.whatwg.org/#the-send()-method - fn Send(&self, data: Option<DocumentOrBodyInit>) -> ErrorResult { + fn Send(&self, data: Option<DocumentOrXMLHttpRequestBodyInit>) -> ErrorResult { // Step 1, 2 if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() { return Err(Error::InvalidState); @@ -495,43 +559,100 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 3 let data = match *self.request_method.borrow() { - Method::Get | Method::Head => None, - _ => data + Method::GET | Method::HEAD => None, + _ => data, }; // Step 4 (first half) - let extracted_or_serialized = match data { - Some(DocumentOrBodyInit::Document(ref doc)) => { - let data = Vec::from(try!(serialize_document(&doc)).as_ref()); + let mut extracted_or_serialized = match data { + Some(DocumentOrXMLHttpRequestBodyInit::Document(ref doc)) => { + let bytes = Vec::from(serialize_document(&doc)?.as_ref()); let content_type = if doc.is_html_document() { "text/html;charset=UTF-8" } else { "application/xml;charset=UTF-8" }; - Some((data, Some(DOMString::from(content_type)))) + let total_bytes = bytes.len(); + let global = self.global(); + let stream = ReadableStream::new_from_bytes(&global, bytes); + Some(ExtractedBody { + stream, + total_bytes: Some(total_bytes), + content_type: Some(DOMString::from(content_type)), + source: BodySource::Object, + }) + }, + Some(DocumentOrXMLHttpRequestBodyInit::Blob(ref b)) => { + let extracted_body = b.extract(&self.global()).expect("Couldn't extract body."); + if !extracted_body.in_memory() && self.sync.get() { + warn!("Sync XHR with not in-memory Blob as body not supported"); + None + } else { + Some(extracted_body) + } + }, + Some(DocumentOrXMLHttpRequestBodyInit::FormData(ref formdata)) => Some( + formdata + .extract(&self.global()) + .expect("Couldn't extract body."), + ), + Some(DocumentOrXMLHttpRequestBodyInit::String(ref str)) => { + Some(str.extract(&self.global()).expect("Couldn't extract body.")) + }, + Some(DocumentOrXMLHttpRequestBodyInit::URLSearchParams(ref urlsp)) => Some( + urlsp + .extract(&self.global()) + .expect("Couldn't extract body."), + ), + Some(DocumentOrXMLHttpRequestBodyInit::ArrayBuffer(ref typedarray)) => { + let bytes = typedarray.to_vec(); + let total_bytes = bytes.len(); + let global = self.global(); + let stream = ReadableStream::new_from_bytes(&global, bytes); + Some(ExtractedBody { + stream, + total_bytes: Some(total_bytes), + content_type: None, + source: BodySource::Object, + }) + }, + Some(DocumentOrXMLHttpRequestBodyInit::ArrayBufferView(ref typedarray)) => { + let bytes = typedarray.to_vec(); + let total_bytes = bytes.len(); + let global = self.global(); + let stream = ReadableStream::new_from_bytes(&global, bytes); + Some(ExtractedBody { + stream, + total_bytes: Some(total_bytes), + content_type: None, + source: BodySource::Object, + }) }, - Some(DocumentOrBodyInit::Blob(ref b)) => Some(b.extract()), - Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(formdata.extract()), - Some(DocumentOrBodyInit::String(ref str)) => Some(str.extract()), - Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(urlsp.extract()), None => None, }; - self.request_body_len.set(extracted_or_serialized.as_ref().map_or(0, |e| e.0.len())); + self.request_body_len.set( + extracted_or_serialized + .as_ref() + .map_or(0, |e| e.total_bytes.unwrap_or(0)), + ); + + // Step 5 + // If we dont have data to upload, we dont want to emit events + let has_handlers = self.upload.upcast::<EventTarget>().has_handlers(); + self.upload_listener.set(has_handlers && data.is_some()); // todo preserved headers? - // Step 6 - self.upload_complete.set(false); // Step 7 - self.upload_complete.set(match extracted_or_serialized { - None => true, - Some(ref e) if e.0.is_empty() => true, - _ => false - }); + self.upload_complete.set(false); // Step 8 + // FIXME handle the 'timed out flag' + // Step 9 + self.upload_complete.set(extracted_or_serialized.is_none()); + // Step 10 self.send_flag.set(true); - // Step 9 + // Step 11 if !self.sync.get() { // If one of the event handlers below aborts the fetch by calling // abort or open we will need the current generation id to detect it. @@ -542,18 +663,16 @@ impl XMLHttpRequestMethods for XMLHttpRequest { return Ok(()); } // Substep 2 - if !self.upload_complete.get() { - self.dispatch_upload_progress_event(atom!("loadstart"), Some(0)); + if !self.upload_complete.get() && self.upload_listener.get() { + self.dispatch_upload_progress_event(atom!("loadstart"), Ok(Some(0))); if self.generation_id.get() != gen_id { return Ok(()); } } - } - // Step 5 + // Step 6 //TODO - set referrer_policy/referrer_url in request - let has_handlers = self.upload.upcast::<EventTarget>().has_handlers(); let credentials_mode = if self.with_credentials.get() { CredentialsMode::Include } else { @@ -565,87 +684,94 @@ impl XMLHttpRequestMethods for XMLHttpRequest { unreachable!() }; - let bypass_cross_origin_check = { - // We want to be able to do cross-origin requests in browser.html. - // If the XHR happens in a top level window and the mozbrowser - // preference is enabled, we allow bypassing the CORS check. - // This is a temporary measure until we figure out Servo privilege - // story. See https://github.com/servo/servo/issues/9582 - if let Some(win) = Root::downcast::<Window>(self.global()) { - let is_root_pipeline = win.parent_info().is_none(); - is_root_pipeline && PREFS.is_mozbrowser_enabled() - } else { - false - } - }; - - let mut request = RequestInit { - method: self.request_method.borrow().clone(), - url: self.request_url.borrow().clone().unwrap(), - headers: (*self.request_headers.borrow()).clone(), - unsafe_request: true, - // XXXManishearth figure out how to avoid this clone - body: extracted_or_serialized.as_ref().map(|e| e.0.clone()), - // XXXManishearth actually "subresource", but it doesn't exist - // https://github.com/whatwg/xhr/issues/71 - destination: Destination::None, - synchronous: self.sync.get(), - mode: RequestMode::CorsMode, - use_cors_preflight: has_handlers, - credentials_mode: credentials_mode, - use_url_credentials: use_url_credentials, - origin: self.global().get_url(), - referrer_url: self.referrer_url.clone(), - referrer_policy: self.referrer_policy.clone(), - pipeline_id: Some(self.global().pipeline_id()), - .. RequestInit::default() + let content_type = match extracted_or_serialized.as_mut() { + Some(body) => body.content_type.take(), + None => None, }; - if bypass_cross_origin_check { - request.mode = RequestMode::Navigate; - } + let mut request = RequestBuilder::new( + self.request_url.borrow().clone().unwrap(), + self.referrer.clone(), + ) + .method(self.request_method.borrow().clone()) + .headers((*self.request_headers.borrow()).clone()) + .unsafe_request(true) + // XXXManishearth figure out how to avoid this clone + .body(extracted_or_serialized.map(|e| e.into_net_request_body().0)) + // XXXManishearth actually "subresource", but it doesn't exist + // https://github.com/whatwg/xhr/issues/71 + .destination(Destination::None) + .synchronous(self.sync.get()) + .mode(RequestMode::CorsMode) + .use_cors_preflight(self.upload_listener.get()) + .credentials_mode(credentials_mode) + .use_url_credentials(use_url_credentials) + .origin(self.global().origin().immutable().clone()) + .referrer_policy(self.referrer_policy.clone()) + .pipeline_id(Some(self.global().pipeline_id())); // step 4 (second half) - match extracted_or_serialized { - Some((_, ref content_type)) => { + match content_type { + Some(content_type) => { let encoding = match data { - Some(DocumentOrBodyInit::String(_)) | Some(DocumentOrBodyInit::Document(_)) => + Some(DocumentOrXMLHttpRequestBodyInit::String(_)) | + Some(DocumentOrXMLHttpRequestBodyInit::Document(_)) => // XHR spec differs from http, and says UTF-8 should be in capitals, // instead of "utf-8", which is what Hyper defaults to. So not // using content types provided by Hyper. - Some(MimeValue::Ext("UTF-8".to_string())), + { + Some("UTF-8") + }, _ => None, }; let mut content_type_set = false; - if let Some(ref ct) = *content_type { - if !request.headers.has::<ContentType>() { - request.headers.set_raw("content-type", vec![ct.bytes().collect()]); - content_type_set = true; - } + if !request.headers.contains_key(header::CONTENT_TYPE) { + request.headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_str(&content_type).unwrap(), + ); + content_type_set = true; } if !content_type_set { - let ct = request.headers.get_mut::<ContentType>(); - if let Some(mut ct) = ct { + let ct = request.headers.typed_get::<ContentType>(); + if let Some(ct) = ct { if let Some(encoding) = encoding { - for param in &mut (ct.0).2 { - if param.0 == MimeAttr::Charset { - if !param.0.as_str().eq_ignore_ascii_case(encoding.as_str()) { - *param = (MimeAttr::Charset, encoding.clone()); + let mime: Mime = ct.into(); + for param in mime.params() { + if param.0 == mime::CHARSET { + if !param.1.as_ref().eq_ignore_ascii_case(encoding) { + let new_params: Vec<(Name, Name)> = mime + .params() + .filter(|p| p.0 != mime::CHARSET) + .map(|p| (p.0, p.1)) + .collect(); + + let new_mime = format!( + "{}/{}; charset={}{}{}", + mime.type_().as_ref(), + mime.subtype().as_ref(), + encoding, + if new_params.is_empty() { "" } else { "; " }, + new_params + .iter() + .map(|p| format!("{}={}", p.0, p.1)) + .collect::<Vec<String>>() + .join("; ") + ); + let new_mime: Mime = new_mime.parse().unwrap(); + request.headers.typed_insert(ContentType::from(new_mime)) } } } } } } - - } + }, _ => (), } - debug!("request.headers = {:?}", request.headers); - self.fetch_time.set(time::now().to_timespec().sec); let rv = self.fetch(request, &self.global()); @@ -668,18 +794,24 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 2 let state = self.ready_state.get(); if (state == XMLHttpRequestState::Opened && self.send_flag.get()) || - state == XMLHttpRequestState::HeadersReceived || - state == XMLHttpRequestState::Loading { + state == XMLHttpRequestState::HeadersReceived || + state == XMLHttpRequestState::Loading + { let gen_id = self.generation_id.get(); self.process_partial_response(XHRProgress::Errored(gen_id, Error::Abort)); // If open was called in one of the handlers invoked by the // above call then we should terminate the abort sequence if self.generation_id.get() != gen_id { - return + return; } } // Step 3 - self.ready_state.set(XMLHttpRequestState::Unsent); + if self.ready_state.get() == XMLHttpRequestState::Done { + self.change_ready_state(XMLHttpRequestState::Unsent); + self.response_status.set(Err(())); + self.response.borrow_mut().clear(); + self.response_headers.borrow_mut().clear(); + } } // https://xhr.spec.whatwg.org/#the-responseurl-attribute @@ -699,35 +831,76 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // https://xhr.spec.whatwg.org/#the-getresponseheader()-method fn GetResponseHeader(&self, name: ByteString) -> Option<ByteString> { - self.filter_response_headers().iter().find(|h| { - name.eq_ignore_case(&h.name().parse().unwrap()) - }).map(|h| { - ByteString::new(h.value_string().into_bytes()) - }) + let headers = self.filter_response_headers(); + let headers = headers.get_all(HeaderName::from_str(&name.as_str()?.to_lowercase()).ok()?); + let mut first = true; + let s = headers.iter().fold(Vec::new(), |mut vec, value| { + if !first { + vec.extend(", ".as_bytes()); + } + if let Ok(v) = str::from_utf8(value.as_bytes()).map(|s| s.trim().as_bytes()) { + vec.extend(v); + first = false; + } + vec + }); + + // There was no header with that name so we never got to change that value + if first { + None + } else { + Some(ByteString::new(s)) + } } // https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method fn GetAllResponseHeaders(&self) -> ByteString { - ByteString::new(self.filter_response_headers().to_string().into_bytes()) + let headers = self.filter_response_headers(); + let keys = headers.keys(); + let v = keys.fold(Vec::new(), |mut vec, k| { + let values = headers.get_all(k); + vec.extend(k.as_str().as_bytes()); + vec.extend(": ".as_bytes()); + let mut first = true; + for value in values { + if !first { + vec.extend(", ".as_bytes()); + first = false; + } + vec.extend(value.as_bytes()); + } + vec.extend("\r\n".as_bytes()); + vec + }); + + ByteString::new(v) } // https://xhr.spec.whatwg.org/#the-overridemimetype()-method fn OverrideMimeType(&self, mime: DOMString) -> ErrorResult { // Step 1 match self.ready_state.get() { - XMLHttpRequestState::Loading | XMLHttpRequestState::Done => return Err(Error::InvalidState), + XMLHttpRequestState::Loading | XMLHttpRequestState::Done => { + return Err(Error::InvalidState); + }, _ => {}, } // Step 2 - let override_mime = try!(mime.parse::<Mime>().map_err(|_| Error::Syntax)); + let override_mime = mime.parse::<Mime>().map_err(|_| Error::Syntax)?; // Step 3 - let mime_no_params = Mime(override_mime.clone().0, override_mime.clone().1, vec![]); + let mime_str = override_mime.as_ref(); + let mime_parts: Vec<&str> = mime_str.split(";").collect(); + let mime_no_params = if mime_parts.len() > 1 { + mime_parts[0].parse().unwrap() + } else { + override_mime.clone() + }; + *self.override_mime_type.borrow_mut() = Some(mime_no_params); // Step 4 - let value = override_mime.get_param(mime::Attr::Charset); - *self.override_charset.borrow_mut() = value.and_then(|value| { - encoding_from_whatwg_label(value) - }); + let value = override_mime.get_param(mime::CHARSET); + *self.override_charset.borrow_mut() = + value.and_then(|value| Encoding::for_label(value.as_ref().as_bytes())); Ok(()) } @@ -739,7 +912,9 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // https://xhr.spec.whatwg.org/#the-responsetype-attribute fn SetResponseType(&self, response_type: XMLHttpRequestResponseType) -> ErrorResult { // Step 1 - if self.global().is::<WorkerGlobalScope>() && response_type == XMLHttpRequestResponseType::Document { + if self.global().is::<WorkerGlobalScope>() && + response_type == XMLHttpRequestResponseType::Document + { return Ok(()); } match self.ready_state.get() { @@ -754,23 +929,25 @@ impl XMLHttpRequestMethods for XMLHttpRequest { self.response_type.set(response_type); Ok(()) } - } + }, } } #[allow(unsafe_code)] // https://xhr.spec.whatwg.org/#the-response-attribute - unsafe fn Response(&self, cx: *mut JSContext) -> JSVal { - rooted!(in(cx) let mut rval = UndefinedValue()); + fn Response(&self, cx: JSContext) -> JSVal { + rooted!(in(*cx) let mut rval = UndefinedValue()); match self.response_type.get() { - XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => { + XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => unsafe { let ready_state = self.ready_state.get(); // Step 2 - if ready_state == XMLHttpRequestState::Done || ready_state == XMLHttpRequestState::Loading { - self.text_response().to_jsval(cx, rval.handle_mut()); + if ready_state == XMLHttpRequestState::Done || + ready_state == XMLHttpRequestState::Loading + { + self.text_response().to_jsval(*cx, rval.handle_mut()); } else { - // Step 1 - "".to_jsval(cx, rval.handle_mut()); + // Step 1 + "".to_jsval(*cx, rval.handle_mut()); } }, // Step 1 @@ -778,19 +955,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest { return NullValue(); }, // Step 2 - XMLHttpRequestResponseType::Document => { - self.document_response().to_jsval(cx, rval.handle_mut()); + XMLHttpRequestResponseType::Document => unsafe { + self.document_response().to_jsval(*cx, rval.handle_mut()); }, - XMLHttpRequestResponseType::Json => { - self.json_response(cx).to_jsval(cx, rval.handle_mut()); + XMLHttpRequestResponseType::Json => unsafe { + self.json_response(cx).to_jsval(*cx, rval.handle_mut()); }, - XMLHttpRequestResponseType::Blob => { - self.blob_response().to_jsval(cx, rval.handle_mut()); + XMLHttpRequestResponseType::Blob => unsafe { + self.blob_response().to_jsval(*cx, rval.handle_mut()); + }, + XMLHttpRequestResponseType::Arraybuffer => match self.arraybuffer_response(cx) { + Some(js_object) => unsafe { js_object.to_jsval(*cx, rval.handle_mut()) }, + None => return NullValue(), }, - _ => { - // XXXManishearth handle other response types - self.response.borrow().to_jsval(cx, rval.handle_mut()); - } } rval.get() } @@ -801,58 +978,58 @@ impl XMLHttpRequestMethods for XMLHttpRequest { XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => { Ok(USVString(String::from(match self.ready_state.get() { // Step 3 - XMLHttpRequestState::Loading | XMLHttpRequestState::Done => self.text_response(), + XMLHttpRequestState::Loading | XMLHttpRequestState::Done => { + self.text_response() + }, // Step 2 - _ => "".to_owned() + _ => "".to_owned(), }))) }, // Step 1 - _ => Err(Error::InvalidState) + _ => Err(Error::InvalidState), } } // https://xhr.spec.whatwg.org/#the-responsexml-attribute - fn GetResponseXML(&self) -> Fallible<Option<Root<Document>>> { - // TODO(#2823): Until [Exposed] is implemented, this attribute needs to return null - // explicitly in the worker scope. - if self.global().is::<WorkerGlobalScope>() { - return Ok(None); - } - + fn GetResponseXML(&self) -> Fallible<Option<DomRoot<Document>>> { match self.response_type.get() { XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Document => { // Step 3 if let XMLHttpRequestState::Done = self.ready_state.get() { Ok(self.document_response()) } else { - // Step 2 + // Step 2 Ok(None) } - } + }, // Step 1 - _ => { Err(Error::InvalidState) } + _ => Err(Error::InvalidState), } } } pub type TrustedXHRAddress = Trusted<XMLHttpRequest>; - impl XMLHttpRequest { fn change_ready_state(&self, rs: XMLHttpRequestState) { - assert!(self.ready_state.get() != rs); + assert_ne!(self.ready_state.get(), rs); self.ready_state.set(rs); - let event = Event::new(&self.global(), - atom!("readystatechange"), - EventBubbles::DoesNotBubble, - EventCancelable::Cancelable); - event.fire(self.upcast()); + if rs != XMLHttpRequestState::Unsent { + let event = Event::new( + &self.global(), + atom!("readystatechange"), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + ); + event.fire(self.upcast()); + } } - fn process_headers_available(&self, - gen_id: GenerationId, - metadata: Result<FetchMetadata, NetworkError>) - -> Result<(), Error> { + fn process_headers_available( + &self, + gen_id: GenerationId, + metadata: Result<FetchMetadata, NetworkError>, + ) -> Result<(), Error> { let metadata = match metadata { Ok(meta) => match meta { FetchMetadata::Unfiltered(m) => m, @@ -860,8 +1037,8 @@ impl XMLHttpRequest { FilteredMetadata::Basic(m) => m, FilteredMetadata::Cors(m) => m, FilteredMetadata::Opaque => return Err(Error::Network), - FilteredMetadata::OpaqueRedirect => return Err(Error::Network) - } + FilteredMetadata::OpaqueRedirect(_) => return Err(Error::Network), + }, }, Err(_) => { self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network)); @@ -875,16 +1052,20 @@ impl XMLHttpRequest { self.process_partial_response(XHRProgress::HeadersReceived( gen_id, metadata.headers.map(Serde::into_inner), - metadata.status)); + metadata.status, + )); Ok(()) } fn process_data_available(&self, gen_id: GenerationId, payload: Vec<u8>) { - self.process_partial_response(XHRProgress::Loading(gen_id, ByteString::new(payload))); + self.process_partial_response(XHRProgress::Loading(gen_id, payload)); } - fn process_response_complete(&self, gen_id: GenerationId, status: Result<(), NetworkError>) - -> ErrorResult { + fn process_response_complete( + &self, + gen_id: GenerationId, + status: Result<(), NetworkError>, + ) -> ErrorResult { match status { Ok(()) => { self.process_partial_response(XHRProgress::Done(gen_id)); @@ -893,7 +1074,7 @@ impl XMLHttpRequest { Err(_) => { self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network)); Err(Error::Network) - } + }, } } @@ -928,12 +1109,12 @@ impl XMLHttpRequest { // Substep 1 self.upload_complete.set(true); // Substeps 2-4 - if !self.sync.get() { - self.dispatch_upload_progress_event(atom!("progress"), None); + if !self.sync.get() && self.upload_listener.get() { + self.dispatch_upload_progress_event(atom!("progress"), Ok(None)); return_if_fetch_was_terminated!(); - self.dispatch_upload_progress_event(atom!("load"), None); + self.dispatch_upload_progress_event(atom!("load"), Ok(None)); return_if_fetch_was_terminated!(); - self.dispatch_upload_progress_event(atom!("loadend"), None); + self.dispatch_upload_progress_event(atom!("loadend"), Ok(None)); return_if_fetch_was_terminated!(); } // Part of step 13, send() (processing response) @@ -943,19 +1124,37 @@ impl XMLHttpRequest { self.status.set(code); *self.status_text.borrow_mut() = ByteString::new(reason); }); - headers.as_ref().map(|h| *self.response_headers.borrow_mut() = h.clone()); - + headers + .as_ref() + .map(|h| *self.response_headers.borrow_mut() = h.clone()); + { + let len = headers.and_then(|h| h.typed_get::<ContentLength>()); + let mut response = self.response.borrow_mut(); + response.clear(); + if let Some(len) = len { + // don't attempt to prereserve more than 4 MB of memory, + // to avoid giving servers the ability to DOS the client by + // providing arbitrarily large content-lengths. + // + // this number is arbitrary, it's basically big enough that most + // XHR requests won't hit it, but not so big that it allows for DOS + let size = cmp::min(0b100_0000000000_0000000000, len.0 as usize); + + // preallocate the buffer + response.reserve(size); + } + } // Substep 3 if !self.sync.get() { self.change_ready_state(XMLHttpRequestState::HeadersReceived); } }, - XHRProgress::Loading(_, partial_response) => { + XHRProgress::Loading(_, mut partial_response) => { // For synchronous requests, this should not fire any events, and just store data // Part of step 11, send() (processing response body) // XXXManishearth handle errors, if any (substep 2) - *self.response.borrow_mut() = partial_response; + self.response.borrow_mut().append(&mut partial_response); if !self.sync.get() { if self.ready_state.get() == XMLHttpRequestState::HeadersReceived { self.ready_state.set(XMLHttpRequestState::Loading); @@ -964,18 +1163,22 @@ impl XMLHttpRequest { &self.global(), atom!("readystatechange"), EventBubbles::DoesNotBubble, - EventCancelable::Cancelable); + EventCancelable::Cancelable, + ); event.fire(self.upcast()); return_if_fetch_was_terminated!(); self.dispatch_response_progress_event(atom!("progress")); } }, XHRProgress::Done(_) => { - assert!(self.ready_state.get() == XMLHttpRequestState::HeadersReceived || + assert!( + self.ready_state.get() == XMLHttpRequestState::HeadersReceived || self.ready_state.get() == XMLHttpRequestState::Loading || - self.sync.get()); + self.sync.get() + ); self.cancel_timeout(); + self.canceller.borrow_mut().ignore(); // Part of step 11, send() (processing response end of file) // XXXManishearth handle errors, if any (substep 2) @@ -992,6 +1195,7 @@ impl XMLHttpRequest { }, XHRProgress::Errored(_, e) => { self.cancel_timeout(); + self.canceller.borrow_mut().ignore(); self.discard_subsequent_responses(); self.send_flag.set(false); @@ -1008,36 +1212,46 @@ impl XMLHttpRequest { let upload_complete = &self.upload_complete; if !upload_complete.get() { upload_complete.set(true); - self.dispatch_upload_progress_event(Atom::from(errormsg), None); - return_if_fetch_was_terminated!(); - self.dispatch_upload_progress_event(atom!("loadend"), None); - return_if_fetch_was_terminated!(); + if self.upload_listener.get() { + self.dispatch_upload_progress_event(Atom::from(errormsg), Err(())); + return_if_fetch_was_terminated!(); + self.dispatch_upload_progress_event(atom!("loadend"), Err(())); + return_if_fetch_was_terminated!(); + } } self.dispatch_response_progress_event(Atom::from(errormsg)); return_if_fetch_was_terminated!(); self.dispatch_response_progress_event(atom!("loadend")); - } + }, } } fn terminate_ongoing_fetch(&self) { + self.canceller.borrow_mut().cancel(); let GenerationId(prev_id) = self.generation_id.get(); self.generation_id.set(GenerationId(prev_id + 1)); self.response_status.set(Ok(())); } fn dispatch_progress_event(&self, upload: bool, type_: Atom, loaded: u64, total: Option<u64>) { - let (total_length, length_computable) = if self.response_headers.borrow().has::<ContentEncoding>() { + let (total_length, length_computable) = if self + .response_headers + .borrow() + .contains_key(header::CONTENT_ENCODING) + { (0, false) } else { (total.unwrap_or(0), total.is_some()) }; - let progressevent = ProgressEvent::new(&self.global(), - type_, - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable, - length_computable, loaded, - total_length); + let progressevent = ProgressEvent::new( + &self.global(), + type_, + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + length_computable, + loaded, + total_length, + ); let target = if upload { self.upload.upcast() } else { @@ -1046,18 +1260,31 @@ impl XMLHttpRequest { progressevent.upcast::<Event>().fire(target); } - fn dispatch_upload_progress_event(&self, type_: Atom, partial_load: Option<u64>) { - // If partial_load is None, loading has completed and we can just use the value from the request body + fn dispatch_upload_progress_event(&self, type_: Atom, partial_load: Result<Option<u64>, ()>) { + // If partial_load is Ok(None), loading has completed and we can just use the value from the request body + // If an error occured, we pass 0 for both loaded and total - let total = self.request_body_len.get() as u64; - self.dispatch_progress_event(true, type_, partial_load.unwrap_or(total), Some(total)); + let request_body_len = self.request_body_len.get() as u64; + let (loaded, total) = match partial_load { + Ok(l) => match l { + Some(loaded) => (loaded, Some(request_body_len)), + None => (request_body_len, Some(request_body_len)), + }, + Err(()) => (0, None), + }; + self.dispatch_progress_event(true, type_, loaded, total); } fn dispatch_response_progress_event(&self, type_: Atom) { let len = self.response.borrow().len() as u64; - let total = self.response_headers.borrow().get::<ContentLength>().map(|x| { **x as u64 }); + let total = self + .response_headers + .borrow() + .typed_get::<ContentLength>() + .map(|v| v.0); self.dispatch_progress_event(false, type_, len, total); } + fn set_timeout(&self, duration_ms: u32) { // Sets up the object to timeout in a given number of milliseconds // This will cancel all previous timeouts @@ -1085,73 +1312,126 @@ impl XMLHttpRequest { // According to Simon, decode() should never return an error, so unwrap()ing // the result should be fine. XXXManishearth have a closer look at this later // Step 1, 2, 6 - charset.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap() + let response = self.response.borrow(); + let (text, _, _) = charset.decode(&response); + text.into_owned() } // https://xhr.spec.whatwg.org/#blob-response - fn blob_response(&self) -> Root<Blob> { + fn blob_response(&self) -> DomRoot<Blob> { // Step 1 if let Some(response) = self.response_blob.get() { return response; } // Step 2 - let mime = self.final_mime_type().as_ref().map(Mime::to_string).unwrap_or("".to_owned()); + let mime = self + .final_mime_type() + .as_ref() + .map(|m| normalize_type_string(&m.to_string())) + .unwrap_or("".to_owned()); // Step 3, 4 let bytes = self.response.borrow().to_vec(); - let blob = Blob::new(&self.global(), BlobImpl::new_from_bytes(bytes), mime); + let blob = Blob::new(&self.global(), BlobImpl::new_from_bytes(bytes, mime)); self.response_blob.set(Some(&blob)); blob } - // https://xhr.spec.whatwg.org/#document-response - fn document_response(&self) -> Option<Root<Document>> { + // https://xhr.spec.whatwg.org/#arraybuffer-response + #[allow(unsafe_code)] + fn arraybuffer_response(&self, cx: JSContext) -> Option<NonNull<JSObject>> { // Step 1 + let created = self.response_arraybuffer.get(); + if let Some(nonnull) = NonNull::new(created) { + return Some(nonnull); + } + + // Step 2 + let bytes = self.response.borrow(); + rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>()); + unsafe { + ArrayBuffer::create(*cx, CreateWith::Slice(&bytes), array_buffer.handle_mut()) + .ok() + .and_then(|()| { + self.response_arraybuffer.set(array_buffer.get()); + Some(NonNull::new_unchecked(array_buffer.get())) + }) + } + } + + // https://xhr.spec.whatwg.org/#document-response + fn document_response(&self) -> Option<DomRoot<Document>> { + // Caching: if we have existing response xml, redirect it directly let response = self.response_xml.get(); if response.is_some() { - return self.response_xml.get(); + return response; + } + + // Step 1 + if self.response_status.get().is_err() { + return None; } + // Step 2 let mime_type = self.final_mime_type(); - // TODO: prescan the response to determine encoding if final charset is null + // Step 5.3, 7 let charset = self.final_charset().unwrap_or(UTF_8); - let temp_doc: Root<Document>; + let temp_doc: DomRoot<Document>; match mime_type { - Some(Mime(mime::TopLevel::Text, mime::SubLevel::Html, _)) => { - // Step 5 + Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::HTML => { + // Step 4 if self.response_type.get() == XMLHttpRequestResponseType::_empty { return None; } else { - // Step 6 + // TODO Step 5.2 "If charset is null, prescan the first 1024 bytes of xhr’s received bytes" + // Step 5 temp_doc = self.document_text_html(); } }, // Step 7 - Some(Mime(mime::TopLevel::Text, mime::SubLevel::Xml, _)) | - Some(Mime(mime::TopLevel::Application, mime::SubLevel::Xml, _)) | None => { temp_doc = self.handle_xml(); + // Not sure it the parser should throw an error for this case + // The specification does not indicates this test, + // but for now we check the document has no child nodes + let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); + if has_no_child_nodes { + return None; + } }, - Some(Mime(_, mime::SubLevel::Ext(sub), _)) => { - if sub.ends_with("+xml") { - temp_doc = self.handle_xml(); - } else { + Some(ref mime) + if (mime.type_() == mime::TEXT && mime.subtype() == mime::XML) || + (mime.type_() == mime::APPLICATION && mime.subtype() == mime::XML) || + mime.suffix() == Some(mime::XML) => + { + temp_doc = self.handle_xml(); + // Not sure it the parser should throw an error for this case + // The specification does not indicates this test, + // but for now we check the document has no child nodes + let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); + if has_no_child_nodes { return None; } + } + // Step 3 + _ => { + return None; }, - // Step 4 - _ => { return None; } } - // Step 9 + // Step 8 temp_doc.set_encoding(charset); - // Step 13 + + // Step 9 to 11 + // Done by handle_text_html and handle_xml + + // Step 12 self.response_xml.set(Some(&temp_doc)); return self.response_xml.get(); } #[allow(unsafe_code)] // https://xhr.spec.whatwg.org/#json-response - fn json_response(&self, cx: *mut JSContext) -> JSVal { + fn json_response(&self, cx: JSContext) -> JSVal { // Step 1 let response_json = self.response_json.get(); if !response_json.is_null_or_undefined() { @@ -1164,16 +1444,35 @@ impl XMLHttpRequest { return NullValue(); } // Step 4 - let json_text = UTF_8.decode(&bytes, DecoderTrap::Replace).unwrap(); - let json_text: Vec<u16> = json_text.encode_utf16().collect(); + fn decode_to_utf16_with_bom_removal(bytes: &[u8], encoding: &'static Encoding) -> Vec<u16> { + let mut decoder = encoding.new_decoder_with_bom_removal(); + let capacity = decoder + .max_utf16_buffer_length(bytes.len()) + .expect("Overflow"); + let mut utf16 = Vec::with_capacity(capacity); + let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) }; + let last = true; + let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, last); + assert_eq!(read, bytes.len()); + unsafe { utf16.set_len(written) } + utf16 + } + // https://xhr.spec.whatwg.org/#json-response refers to + // https://infra.spec.whatwg.org/#parse-json-from-bytes which refers to + // https://encoding.spec.whatwg.org/#utf-8-decode which means + // that the encoding is always UTF-8 and the UTF-8 BOM is removed, + // if present, but UTF-16BE/LE BOM must not be honored. + let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8); // Step 5 - rooted!(in(cx) let mut rval = UndefinedValue()); + rooted!(in(*cx) let mut rval = UndefinedValue()); unsafe { - if !JS_ParseJSON(cx, - json_text.as_ptr(), - json_text.len() as u32, - rval.handle_mut()) { - JS_ClearPendingException(cx); + if !JS_ParseJSON( + *cx, + json_text.as_ptr(), + json_text.len() as u32, + rval.handle_mut(), + ) { + JS_ClearPendingException(*cx); return NullValue(); } } @@ -1182,33 +1481,29 @@ impl XMLHttpRequest { self.response_json.get() } - fn document_text_html(&self) -> Root<Document> { + fn document_text_html(&self) -> DomRoot<Document> { let charset = self.final_charset().unwrap_or(UTF_8); let wr = self.global(); - let decoded = charset.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap(); + let response = self.response.borrow(); + let (decoded, _, _) = charset.decode(&response); let document = self.new_doc(IsHTMLDocument::HTMLDocument); // TODO: Disable scripting while parsing - ServoParser::parse_html_document( - &document, - DOMString::from(decoded), - wr.get_url()); + ServoParser::parse_html_document(&document, Some(DOMString::from(decoded)), wr.get_url()); document } - fn handle_xml(&self) -> Root<Document> { + fn handle_xml(&self) -> DomRoot<Document> { let charset = self.final_charset().unwrap_or(UTF_8); let wr = self.global(); - let decoded = charset.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap(); + let response = self.response.borrow(); + let (decoded, _, _) = charset.decode(&response); let document = self.new_doc(IsHTMLDocument::NonHTMLDocument); // TODO: Disable scripting while parsing - ServoParser::parse_xml_document( - &document, - DOMString::from(decoded), - wr.get_url()); + ServoParser::parse_xml_document(&document, Some(DOMString::from(decoded)), wr.get_url()); document } - fn new_doc(&self, is_html_document: IsHTMLDocument) -> Root<Document> { + fn new_doc(&self, is_html_document: IsHTMLDocument) -> DomRoot<Document> { let wr = self.global(); let win = wr.as_window(); let doc = win.Document(); @@ -1216,54 +1511,31 @@ impl XMLHttpRequest { let base = wr.get_url(); let parsed_url = match base.join(&self.ResponseURL().0) { Ok(parsed) => Some(parsed), - Err(_) => None // Step 7 + Err(_) => None, // Step 7 }; - let mime_type = self.final_mime_type(); - let content_type = mime_type.map(|mime|{ - DOMString::from(format!("{}", mime)) - }); - Document::new(win, - HasBrowsingContext::No, - parsed_url, - doc.origin().clone(), - is_html_document, - content_type, - None, - DocumentActivity::Inactive, - DocumentSource::FromParser, - docloader, - None, - None) + let content_type = self.final_mime_type(); + Document::new( + win, + HasBrowsingContext::No, + parsed_url, + doc.origin().clone(), + is_html_document, + content_type, + None, + DocumentActivity::Inactive, + DocumentSource::FromParser, + docloader, + None, + None, + Default::default(), + ) } - fn filter_response_headers(&self) -> Headers { + fn filter_response_headers(&self) -> HeaderMap { // https://fetch.spec.whatwg.org/#concept-response-header-list - use hyper::error::Result; - use hyper::header::{Header, HeaderFormat}; - use hyper::header::SetCookie; - use std::fmt; - - // a dummy header so we can use headers.remove::<SetCookie2>() - #[derive(Clone, Debug, HeapSizeOf)] - struct SetCookie2; - impl Header for SetCookie2 { - fn header_name() -> &'static str { - "set-cookie2" - } - - fn parse_header(_: &[Vec<u8>]) -> Result<SetCookie2> { - unimplemented!() - } - } - impl HeaderFormat for SetCookie2 { - fn fmt_header(&self, _f: &mut fmt::Formatter) -> fmt::Result { - unimplemented!() - } - } - let mut headers = self.response_headers.borrow().clone(); - headers.remove::<SetCookie>(); - headers.remove::<SetCookie2>(); + headers.remove(header::SET_COOKIE); + headers.remove(HeaderName::from_static("set-cookie2")); // XXXManishearth additional CORS filtering goes here headers } @@ -1272,31 +1544,40 @@ impl XMLHttpRequest { self.response_status.set(Err(())); } - fn fetch(&self, - init: RequestInit, - global: &GlobalScope) -> ErrorResult { + fn fetch(&self, init: RequestBuilder, global: &GlobalScope) -> ErrorResult { let xhr = Trusted::new(self); let context = Arc::new(Mutex::new(XHRContext { xhr: xhr, gen_id: self.generation_id.get(), - buf: DOMRefCell::new(vec!()), - sync_status: DOMRefCell::new(None), + sync_status: DomRefCell::new(None), + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), + url: init.url.clone(), })); let (task_source, script_port) = if self.sync.get() { let (tx, rx) = global.new_script_pair(); - (NetworkingTaskSource(tx), Some(rx)) + (NetworkingTaskSource(tx, global.pipeline_id()), Some(rx)) } else { (global.networking_task_source(), None) }; - XMLHttpRequest::initiate_async_xhr(context.clone(), task_source, - global, init); + let cancel_receiver = self.canceller.borrow_mut().initialize(); + + XMLHttpRequest::initiate_async_xhr( + context.clone(), + task_source, + global, + init, + cancel_receiver, + ); if let Some(script_port) = script_port { loop { - global.process_event(script_port.recv().unwrap()); + if !global.process_event(script_port.recv().unwrap()) { + // We're exiting. + return Err(Error::Abort); + } let context = context.lock().unwrap(); let sync_status = context.sync_status.borrow(); if let Some(ref status) = *sync_status { @@ -1307,37 +1588,47 @@ impl XMLHttpRequest { Ok(()) } - fn final_charset(&self) -> Option<EncodingRef> { + fn final_charset(&self) -> Option<&'static Encoding> { if self.override_charset.borrow().is_some() { self.override_charset.borrow().clone() } else { - match self.response_headers.borrow().get() { - Some(&ContentType(ref mime)) => { - let value = mime.get_param(mime::Attr::Charset); - value.and_then(|value|{ - encoding_from_whatwg_label(value) - }) - } - None => { None } + match self.response_headers.borrow().typed_get::<ContentType>() { + Some(ct) => { + let mime: Mime = ct.into(); + let value = mime.get_param(mime::CHARSET); + value.and_then(|value| Encoding::for_label(value.as_ref().as_bytes())) + }, + None => None, } } } + /// <https://xhr.spec.whatwg.org/#response-mime-type> + fn response_mime_type(&self) -> Option<Mime> { + return extract_mime_type(&self.response_headers.borrow()) + .map(|mime_as_bytes| { + String::from_utf8(mime_as_bytes) + .unwrap_or_default() + .parse() + .ok() + }) + .flatten() + .or(Some(mime::TEXT_XML)); + } + + /// <https://xhr.spec.whatwg.org/#final-mime-type> fn final_mime_type(&self) -> Option<Mime> { if self.override_mime_type.borrow().is_some() { self.override_mime_type.borrow().clone() } else { - match self.response_headers.borrow().get() { - Some(&ContentType(ref mime)) => { Some(mime.clone()) }, - None => { None } - } + return self.response_mime_type(); } } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] pub struct XHRTimeoutCallback { - #[ignore_heap_size_of = "Because it is non-owning"] + #[ignore_malloc_size_of = "Because it is non-owning"] xhr: Trusted<XMLHttpRequest>, generation_id: GenerationId, } @@ -1351,47 +1642,6 @@ impl XHRTimeoutCallback { } } -pub trait Extractable { - fn extract(&self) -> (Vec<u8>, Option<DOMString>); -} - -impl Extractable for Blob { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - let content_type = if self.Type().as_ref().is_empty() { - None - } else { - Some(self.Type()) - }; - let bytes = self.get_bytes().unwrap_or(vec![]); - (bytes, content_type) - } -} - - -impl Extractable for DOMString { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - (UTF_8.encode(self, EncoderTrap::Replace).unwrap(), - Some(DOMString::from("text/plain;charset=UTF-8"))) - } -} - -impl Extractable for FormData { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - let boundary = generate_boundary(); - let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), - UTF_8 as EncodingRef); - (bytes, Some(DOMString::from(format!("multipart/form-data;boundary={}", boundary)))) - } -} - -impl Extractable for URLSearchParams { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - // Default encoding is UTF-8. - (self.serialize(None).into_bytes(), - Some(DOMString::from("application/x-www-form-urlencoded;charset=UTF-8"))) - } -} - fn serialize_document(doc: &Document) -> Fallible<DOMString> { let mut writer = vec![]; match serialize(&mut writer, &doc.upcast::<Node>(), SerializeOpts::default()) { @@ -1400,18 +1650,6 @@ fn serialize_document(doc: &Document) -> Fallible<DOMString> { } } -impl Extractable for BodyInit { - // https://fetch.spec.whatwg.org/#concept-bodyinit-extract - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - match *self { - BodyInit::String(ref s) => s.extract(), - BodyInit::URLSearchParams(ref usp) => usp.extract(), - BodyInit::Blob(ref b) => b.extract(), - BodyInit::FormData(ref formdata) => formdata.extract(), - } - } -} - /// Returns whether `bs` is a `field-value`, as defined by /// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32). pub fn is_field_value(slice: &[u8]) -> bool { @@ -1427,7 +1665,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { slice.iter().all(|&x| { // http://tools.ietf.org/html/rfc2616#section-2.2 match x { - 13 => { // CR + 13 => { + // CR if prev == PreviousCharacter::Other || prev == PreviousCharacter::SPHT { prev = PreviousCharacter::CR; true @@ -1435,7 +1674,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 10 => { // LF + 10 => { + // LF if prev == PreviousCharacter::CR { prev = PreviousCharacter::LF; true @@ -1443,7 +1683,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 32 => { // SP + 32 => { + // SP if prev == PreviousCharacter::LF || prev == PreviousCharacter::SPHT { prev = PreviousCharacter::SPHT; true @@ -1457,7 +1698,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 9 => { // HT + 9 => { + // HT if prev == PreviousCharacter::LF || prev == PreviousCharacter::SPHT { prev = PreviousCharacter::SPHT; true @@ -1465,13 +1707,13 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 0...31 | 127 => false, // CTLs + 0..=31 | 127 => false, // CTLs x if x > 127 => false, // non ASCII _ if prev == PreviousCharacter::Other || prev == PreviousCharacter::SPHT => { prev = PreviousCharacter::Other; true }, - _ => false // Previous character was a CR/LF but not part of the [CRLF] (SP|HT) rule + _ => false, // Previous character was a CR/LF but not part of the [CRLF] (SP|HT) rule } }) } |