From 5e6f32a59bcefd15e2a081a65b4ea44161e913f8 Mon Sep 17 00:00:00 2001 From: Ravi Shankar Date: Fri, 1 Jan 2016 00:01:24 +0530 Subject: [PATCH] Propagating the load errors from network loader --- components/gfx/font_cache_thread.rs | 22 ++-- components/net/about_loader.rs | 4 +- components/net/data_loader.rs | 8 +- components/net/file_loader.rs | 10 +- components/net/http_loader.rs | 143 ++++++++++----------- components/net/image_cache_thread.rs | 4 +- components/net/resource_thread.rs | 25 ++-- components/net_traits/lib.rs | 22 +++- components/script/cors.rs | 8 +- components/script/dom/bindings/trace.rs | 3 +- components/script/dom/htmllinkelement.rs | 13 +- components/script/dom/htmlscriptelement.rs | 30 +++-- components/script/dom/servohtmlparser.rs | 40 ++++-- components/script/dom/xmlhttprequest.rs | 22 ++-- components/script/script_thread.rs | 6 +- 15 files changed, 208 insertions(+), 152 deletions(-) diff --git a/components/gfx/font_cache_thread.rs b/components/gfx/font_cache_thread.rs index 315702902b9e..0aa732a2418e 100644 --- a/components/gfx/font_cache_thread.rs +++ b/components/gfx/font_cache_thread.rs @@ -183,15 +183,21 @@ impl FontCache { ROUTER.add_route(data_receiver.to_opaque(), box move |message| { let response: ResponseAction = message.to().unwrap(); match response { - ResponseAction::HeadersAvailable(metadata) => { - let is_response_valid = - metadata.content_type.as_ref().map_or(false, |content_type| { - let mime = &content_type.0; - is_supported_font_type(&mime.0, &mime.1) - }); - info!("{} font with MIME type {:?}", + ResponseAction::HeadersAvailable(meta_result) => { + let is_response_valid = match meta_result { + Ok(ref metadata) => { + metadata.content_type.as_ref().map_or(false, |content_type| { + let mime = &content_type.0; + is_supported_font_type(&mime.0, &mime.1) + }) + } + Err(_) => false, + }; + + info!("{} font with MIME type {}", if is_response_valid { "Loading" } else { "Ignoring" }, - metadata.content_type); + meta_result.map(|ref meta| format!("{:?}", meta.content_type)) + .unwrap_or(format!(""))); *response_valid.lock().unwrap() = is_response_valid; } ResponseAction::DataAvailable(new_bytes) => { diff --git a/components/net/about_loader.rs b/components/net/about_loader.rs index 5fbaaa686f6b..c4b52e55116e 100644 --- a/components/net/about_loader.rs +++ b/components/net/about_loader.rs @@ -9,7 +9,7 @@ use hyper::mime::{Mime, SubLevel, TopLevel}; use mime_classifier::MIMEClassifier; use net_traits::ProgressMsg::Done; use net_traits::response::HttpsState; -use net_traits::{LoadConsumer, LoadData, Metadata}; +use net_traits::{LoadConsumer, LoadData, Metadata, NetworkError}; use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt}; use std::sync::Arc; use url::Url; @@ -49,7 +49,7 @@ pub fn factory(mut load_data: LoadData, load_data.url = Url::from_file_path(&*path).unwrap(); } _ => { - send_error(load_data.url, "Unknown about: URL.".to_owned(), start_chan); + send_error(load_data.url, NetworkError::Internal("Unknown about: URL.".to_owned()), start_chan); return } }; diff --git a/components/net/data_loader.rs b/components/net/data_loader.rs index 50452302627b..ad881ae40444 100644 --- a/components/net/data_loader.rs +++ b/components/net/data_loader.rs @@ -6,7 +6,7 @@ use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use mime_classifier::MIMEClassifier; use net_traits::LoadConsumer; use net_traits::ProgressMsg::{Payload, Done}; -use net_traits::{LoadData, Metadata}; +use net_traits::{LoadData, Metadata, NetworkError}; use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt}; use rustc_serialize::base64::FromBase64; use std::sync::Arc; @@ -110,7 +110,9 @@ pub fn load(load_data: LoadData, let _ = chan.send(Done(Ok(()))); } }, - Err(DecodeError::InvalidDataUri) => send_error(url, "invalid data uri".to_owned(), start_chan), - Err(DecodeError::NonBase64DataUri) => send_error(url, "non-base64 data uri".to_owned(), start_chan), + Err(DecodeError::InvalidDataUri) => + send_error(url, NetworkError::Internal("invalid data uri".to_owned()), start_chan), + Err(DecodeError::NonBase64DataUri) => + send_error(url, NetworkError::Internal("non-base64 data uri".to_owned()), start_chan), } } diff --git a/components/net/file_loader.rs b/components/net/file_loader.rs index b3b905c7dcbf..ac1c881768b6 100644 --- a/components/net/file_loader.rs +++ b/components/net/file_loader.rs @@ -6,7 +6,7 @@ use about_loader; use mime_classifier::MIMEClassifier; use mime_guess::guess_mime_type; use net_traits::ProgressMsg::{Done, Payload}; -use net_traits::{LoadConsumer, LoadData, Metadata}; +use net_traits::{LoadConsumer, LoadData, Metadata, NetworkError}; use resource_thread::{CancellationListener, ProgressSender}; use resource_thread::{send_error, start_sending_sniffed_opt}; use std::borrow::ToOwned; @@ -50,7 +50,7 @@ fn read_all(reader: &mut File, progress_chan: &ProgressSender, cancel_listener: ReadStatus::EOF => return Ok(LoadResult::Finished), } } - let _ = progress_chan.send(Done(Err("load cancelled".to_owned()))); + let _ = progress_chan.send(Done(Err(NetworkError::Internal("load cancelled".to_owned())))); Ok(LoadResult::Cancelled) } @@ -72,7 +72,7 @@ pub fn factory(load_data: LoadData, let file_path = match load_data.url.to_file_path() { Ok(file_path) => file_path, Err(_) => { - send_error(load_data.url, "Could not parse path".to_owned(), senders); + send_error(load_data.url, NetworkError::Internal("Could not parse path".to_owned()), senders); return; }, }; @@ -92,7 +92,7 @@ pub fn factory(load_data: LoadData, if cancel_listener.is_cancelled() { if let Ok(progress_chan) = get_progress_chan(load_data, file_path, senders, classifier, &[]) { - let _ = progress_chan.send(Done(Err("load cancelled".to_owned()))); + let _ = progress_chan.send(Done(Err(NetworkError::Internal("load cancelled".to_owned())))); } return; } @@ -116,7 +116,7 @@ pub fn factory(load_data: LoadData, } } Err(e) => { - send_error(load_data.url, e, senders); + send_error(load_data.url, NetworkError::Internal(e), senders); } } }); diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 5473a83fce54..97a1eea3968e 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -8,7 +8,6 @@ use cookie; use cookie_storage::CookieStorage; use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest}; use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent}; -use file_loader; use flate2::read::{DeflateDecoder, GzDecoder}; use hsts::{HstsEntry, HstsList, secure_url}; use hyper::Error as HttpError; @@ -28,7 +27,8 @@ use msg::constellation_msg::{PipelineId}; use net_traits::ProgressMsg::{Done, Payload}; use net_traits::hosts::replace_hosts; use net_traits::response::HttpsState; -use net_traits::{CookieSource, IncludeSubdomains, LoadConsumer, LoadContext, LoadData, Metadata}; +use net_traits::{CookieSource, IncludeSubdomains, LoadConsumer, LoadContext, LoadData}; +use net_traits::{Metadata, NetworkError}; use openssl::ssl::error::{SslError, OpensslError}; use openssl::ssl::{SSL_OP_NO_SSLV2, SSL_OP_NO_SSLV3, SSL_VERIFY_PEER, SslContext, SslMethod}; use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt, AuthCacheEntry}; @@ -158,32 +158,15 @@ fn load_for_consumer(load_data: LoadData, match load(&load_data, &ui_provider, &http_state, devtools_chan, &factory, user_agent, &cancel_listener) { - Err(LoadError::UnsupportedScheme(url)) => { - let s = format!("{} request, but we don't support that scheme", &*url.scheme); - send_error(url, s, start_chan) - } - Err(LoadError::Connection(url, e)) => { - send_error(url, e, start_chan) - } - Err(LoadError::MaxRedirects(url, _)) => { - send_error(url, "too many redirects".to_owned(), start_chan) - } - Err(LoadError::Cors(url, msg)) | - Err(LoadError::Cancelled(url, msg)) | - Err(LoadError::InvalidRedirect(url, msg)) | - Err(LoadError::Decoding(url, msg)) => { - send_error(url, msg, start_chan) - } - Err(LoadError::Ssl(url, msg)) => { - info!("ssl validation error {}, '{}'", url.serialize(), msg); - - let mut image = resources_dir_path(); - image.push("badcert.html"); - let load_data = LoadData::new(load_data.context, Url::from_file_path(&*image).unwrap(), None); - - file_loader::factory(load_data, start_chan, classifier, cancel_listener) + Err(error) => { + match error.error { + LoadErrorType::ConnectionAborted => unreachable!(), + LoadErrorType::Ssl => send_error(error.url.clone(), + NetworkError::SslValidation(error.url), + start_chan), + _ => send_error(error.url, NetworkError::Internal(error.reason), start_chan) + } } - Err(LoadError::ConnectionAborted(_)) => unreachable!(), Ok(mut load_response) => { let metadata = load_response.metadata.clone(); send_data(load_data.context, &mut load_response, start_chan, metadata, classifier, &cancel_listener) @@ -268,20 +251,15 @@ impl HttpRequestFactory for NetworkHttpRequestFactory { let error: &(Error + Send + 'static) = &**error; if let Some(&SslError::OpenSslErrors(ref errors)) = error.downcast_ref::() { if errors.iter().any(is_cert_verify_error) { - return Err( - LoadError::Ssl(url, format!("ssl error: {:?} {:?}", - error.description(), - error.cause()))); + let msg = format!("ssl error: {:?} {:?}", error.description(), error.cause()); + return Err(LoadError::new(url, LoadErrorType::Ssl, msg)); } } } let mut request = match connection { Ok(req) => req, - - Err(e) => { - return Err(LoadError::Connection(url, e.description().to_owned())) - } + Err(e) => return Err(LoadError::new(url, LoadErrorType::Connection, e.description().to_owned())), }; *request.headers_mut() = headers; @@ -306,21 +284,23 @@ impl HttpRequest for WrappedHttpRequest { let url = self.request.url.clone(); let mut request_writer = match self.request.start() { Ok(streaming) => streaming, - Err(e) => return Err(LoadError::Connection(url, e.description().to_owned())) + Err(e) => return Err(LoadError::new(url, LoadErrorType::Connection, e.description().to_owned())), }; if let Some(ref data) = *body { if let Err(e) = request_writer.write_all(&data) { - return Err(LoadError::Connection(url, e.description().to_owned())) + return Err(LoadError::new(url, LoadErrorType::Connection, e.description().to_owned())) } } let response = match request_writer.send() { Ok(w) => w, Err(HttpError::Io(ref io_error)) if io_error.kind() == io::ErrorKind::ConnectionAborted => { - return Err(LoadError::ConnectionAborted(io_error.description().to_owned())); + return Err(LoadError::new(url, LoadErrorType::ConnectionAborted, + io_error.description().to_owned())); }, - Err(e) => return Err(LoadError::Connection(url, e.description().to_owned())) + Err(e) => return Err(LoadError::new(url, LoadErrorType::Connection, + e.description().to_owned())), }; Ok(WrappedHttpResponse { response: response }) @@ -328,16 +308,33 @@ impl HttpRequest for WrappedHttpRequest { } #[derive(Debug)] -pub enum LoadError { - UnsupportedScheme(Url), - Connection(Url, String), - Cors(Url, String), - Ssl(Url, String), - InvalidRedirect(Url, String), - Decoding(Url, String), - MaxRedirects(Url, u32), // u32 indicates number of redirects that occurred - ConnectionAborted(String), - Cancelled(Url, String), +pub struct LoadError { + url: Url, + error: LoadErrorType, + reason: String, +} + +impl LoadError { + fn new(url: Url, error: LoadErrorType, reason: String) -> LoadError { + LoadError { + url: url, + error: error, + reason: reason, + } + } +} + +#[derive(Debug)] +pub enum LoadErrorType { + Cancelled, + Connection, + ConnectionAborted, + Cors, + Decoding, + InvalidRedirect, + MaxRedirects(u32), // u32 indicates number of redirects that occurred + Ssl, + UnsupportedScheme, } fn set_default_accept_encoding(headers: &mut Headers) { @@ -456,12 +453,8 @@ impl StreamedResponse { Some(Encoding::Gzip) => { let result = GzDecoder::new(response); match result { - Ok(response_decoding) => { - Ok(StreamedResponse::new(m, Decoder::Gzip(response_decoding))) - } - Err(err) => { - Err(LoadError::Decoding(m.final_url, err.to_string())) - } + Ok(response_decoding) => Ok(StreamedResponse::new(m, Decoder::Gzip(response_decoding))), + Err(err) => Err(LoadError::new(m.final_url, LoadErrorType::Decoding, err.to_string())), } } Some(Encoding::Deflate) => { @@ -670,7 +663,7 @@ pub fn obtain_response(request_factory: &HttpRequestFactory, headers.clone())); if cancel_listener.is_cancelled() { - return Err(LoadError::Cancelled(connection_url.clone(), "load cancelled".to_owned())); + return Err(LoadError::new(connection_url.clone(), LoadErrorType::Cancelled, "load cancelled".to_owned())); } let maybe_response = req.send(request_body); @@ -685,11 +678,14 @@ pub fn obtain_response(request_factory: &HttpRequestFactory, response = match maybe_response { Ok(r) => r, - Err(LoadError::ConnectionAborted(reason)) => { - debug!("connection aborted ({:?}), possibly stale, trying new connection", reason); - continue; - } - Err(e) => return Err(e), + Err(e) => { + if let LoadErrorType::ConnectionAborted = e.error { + debug!("connection aborted ({:?}), possibly stale, trying new connection", e.reason); + continue; + } else { + return Err(e) + } + }, }; // if no ConnectionAborted, break the loop @@ -736,7 +732,7 @@ pub fn load(load_data: &LoadData, let mut new_auth_header: Option> = None; if cancel_listener.is_cancelled() { - return Err(LoadError::Cancelled(doc_url, "load cancelled".to_owned())); + return Err(LoadError::new(doc_url, LoadErrorType::Cancelled, "load cancelled".to_owned())); } // If the URL is a view-source scheme then the scheme data contains the @@ -758,15 +754,17 @@ pub fn load(load_data: &LoadData, } if iters > max_redirects { - return Err(LoadError::MaxRedirects(doc_url, iters - 1)); + return Err(LoadError::new(doc_url, LoadErrorType::MaxRedirects(iters - 1), + "too many redirects".to_owned())); } if &*doc_url.scheme != "http" && &*doc_url.scheme != "https" { - return Err(LoadError::UnsupportedScheme(doc_url)); + let s = format!("{} request, but we don't support that scheme", &*doc_url.scheme); + return Err(LoadError::new(doc_url, LoadErrorType::UnsupportedScheme, s)); } if cancel_listener.is_cancelled() { - return Err(LoadError::Cancelled(doc_url, "load cancelled".to_owned())); + return Err(LoadError::new(doc_url, LoadErrorType::Cancelled, "load cancelled".to_owned())); } info!("requesting {}", doc_url.serialize()); @@ -832,10 +830,9 @@ pub fn load(load_data: &LoadData, // CORS (https://fetch.spec.whatwg.org/#http-fetch, status section, point 9, 10) if let Some(ref c) = load_data.cors { if c.preflight { - return Err( - LoadError::Cors( - doc_url, - "Preflight fetch inconsistent with main fetch".to_owned())); + return Err(LoadError::new(doc_url, + LoadErrorType::Cors, + "Preflight fetch inconsistent with main fetch".to_owned())); } else { // XXXManishearth There are some CORS-related steps here, // but they don't seem necessary until credentials are implemented @@ -844,9 +841,7 @@ pub fn load(load_data: &LoadData, let new_doc_url = match doc_url.join(&new_url) { Ok(u) => u, - Err(e) => { - return Err(LoadError::InvalidRedirect(doc_url, e.to_string())); - } + Err(e) => return Err(LoadError::new(doc_url, LoadErrorType::InvalidRedirect, e.to_string())), }; // According to https://tools.ietf.org/html/rfc7231#section-6.4.2, @@ -858,7 +853,7 @@ pub fn load(load_data: &LoadData, } if redirected_to.contains(&new_doc_url) { - return Err(LoadError::InvalidRedirect(doc_url, "redirect loop".to_owned())); + return Err(LoadError::new(doc_url, LoadErrorType::InvalidRedirect, "redirect loop".to_owned())); } info!("redirecting to {}", new_doc_url); @@ -921,7 +916,7 @@ fn send_data(context: LoadContext, loop { if cancel_listener.is_cancelled() { - let _ = progress_chan.send(Done(Err("load cancelled".to_owned()))); + let _ = progress_chan.send(Done(Err(NetworkError::Internal("load cancelled".to_owned())))); return; } diff --git a/components/net/image_cache_thread.rs b/components/net/image_cache_thread.rs index 91a0cf0dc32f..660ba87cb4e5 100644 --- a/components/net/image_cache_thread.rs +++ b/components/net/image_cache_thread.rs @@ -10,7 +10,7 @@ use net_traits::image_cache_thread::ImageResponder; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheCommand, ImageCacheThread, ImageState}; use net_traits::image_cache_thread::{ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, UsePlaceholder}; use net_traits::{AsyncResponseTarget, ControlMsg, LoadConsumer, LoadData, ResourceThread}; -use net_traits::{ResponseAction, LoadContext}; +use net_traits::{ResponseAction, LoadContext, NetworkError}; use std::borrow::ToOwned; use std::collections::HashMap; use std::collections::hash_map::Entry::{Occupied, Vacant}; @@ -44,7 +44,7 @@ struct PendingLoad { metadata: Option, // Once loading is complete, the result of the operation. - result: Option>, + result: Option>, listeners: Vec, // The url being loaded. Do not forget that this may be several Mb diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 0bc6f19e053e..aa53a83782a7 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -21,7 +21,7 @@ use net_traits::LoadContext; use net_traits::ProgressMsg::Done; use net_traits::{AsyncResponseTarget, Metadata, ProgressMsg, ResourceThread, ResponseAction}; use net_traits::{ControlMsg, CookieSource, LoadConsumer, LoadData, LoadResponse, ResourceId}; -use net_traits::{WebSocketCommunicate, WebSocketConnectData}; +use net_traits::{NetworkError, WebSocketCommunicate, WebSocketConnectData}; use std::borrow::ToOwned; use std::boxed::FnBox; use std::cell::Cell; @@ -55,11 +55,11 @@ impl ProgressSender { } } -pub fn send_error(url: Url, err: String, start_chan: LoadConsumer) { +pub fn send_error(url: Url, err: NetworkError, start_chan: LoadConsumer) { let mut metadata: Metadata = Metadata::default(url); metadata.status = None; - if let Ok(p) = start_sending_opt(start_chan, metadata) { + if let Ok(p) = start_sending_opt(start_chan, metadata, Some(err.clone())) { p.send(Done(Err(err))).unwrap(); } } @@ -107,16 +107,19 @@ pub fn start_sending_sniffed_opt(start_chan: LoadConsumer, mut metadata: Metadat metadata.content_type = Some(ContentType(Mime(mime_tp, mime_sb, vec![]))); } - start_sending_opt(start_chan, metadata) + start_sending_opt(start_chan, metadata, None) } /// For use by loaders in responding to a Load message. -fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata) -> Result { +/// It takes an optional NetworkError, so that we can extract the SSL Validation errors +/// and take it to the HTML parser +fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata, + network_error: Option) -> Result { match start_chan { LoadConsumer::Channel(start_chan) => { let (progress_chan, progress_port) = ipc::channel().unwrap(); let result = start_chan.send(LoadResponse { - metadata: metadata, + metadata: metadata, progress_port: progress_port, }); match result { @@ -125,7 +128,13 @@ fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata) -> Result { - target.invoke_with_listener(ResponseAction::HeadersAvailable(metadata)); + match network_error { + Some(NetworkError::SslValidation(url)) => { + let error = NetworkError::SslValidation(url); + target.invoke_with_listener(ResponseAction::HeadersAvailable(Err(error))); + } + _ => target.invoke_with_listener(ResponseAction::HeadersAvailable(Ok(metadata))), + } Ok(ProgressSender::Listener(target)) } } @@ -339,7 +348,7 @@ impl ResourceManager { "about" => from_factory(about_loader::factory), _ => { debug!("resource_thread: no loader for scheme {}", load_data.url.scheme); - send_error(load_data.url, "no loader for scheme".to_owned(), consumer); + send_error(load_data.url, NetworkError::Internal("no loader for scheme".to_owned()), consumer); return } }; diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index 819d0a80335c..6d4bc855d19e 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -113,13 +113,13 @@ pub trait AsyncFetchListener { /// A listener for asynchronous network events. Cancelling the underlying request is unsupported. pub trait AsyncResponseListener { /// The response headers for a request have been received. - fn headers_available(&mut self, metadata: Metadata); + fn headers_available(&mut self, metadata: Result); /// A portion of the response body has been received. This data is unavailable after /// this method returned, and must be stored accordingly. fn data_available(&mut self, payload: Vec); /// The response is complete. If the provided status is an Err value, there is no guarantee /// that the response body was completely read. - fn response_complete(&mut self, status: Result<(), String>); + fn response_complete(&mut self, status: Result<(), NetworkError>); } /// Data for passing between threads/processes to indicate a particular action to @@ -127,11 +127,11 @@ pub trait AsyncResponseListener { #[derive(Deserialize, Serialize)] pub enum ResponseAction { /// Invoke headers_available - HeadersAvailable(Metadata), + HeadersAvailable(Result), /// Invoke data_available DataAvailable(Vec), /// Invoke response_complete - ResponseComplete(Result<(), String>) + ResponseComplete(Result<(), NetworkError>) } impl ResponseAction { @@ -376,7 +376,7 @@ pub enum ProgressMsg { /// Binary data - there may be multiple of these Payload(Vec), /// Indicates loading is complete, either successfully or not - Done(Result<(), String>) + Done(Result<(), NetworkError>), } /// Convenience function for synchronously loading a whole resource. @@ -384,7 +384,7 @@ pub fn load_whole_resource(context: LoadContext, resource_thread: &ResourceThread, url: Url, pipeline_id: Option) - -> Result<(Metadata, Vec), String> { + -> Result<(Metadata, Vec), NetworkError> { let (start_chan, start_port) = ipc::channel().unwrap(); resource_thread.send(ControlMsg::Load(LoadData::new(context, url, pipeline_id), LoadConsumer::Channel(start_chan), None)).unwrap(); @@ -413,3 +413,13 @@ pub enum ConstellationMsg { /// Queries whether a pipeline or its ancestors are private IsPrivate(PipelineId, Sender), } + +/// Network errors that have to be exported out of the loaders +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize, HeapSizeOf)] +pub enum NetworkError { + /// Could be any of the internal errors, like unsupported scheme, load + /// cancellation, connection errors, etc. + Internal(String), + /// SSL validation error that has to be handled in the HTML parser + SslValidation(Url), +} diff --git a/components/script/cors.rs b/components/script/cors.rs index da8c3db68cae..626d3985d8ca 100644 --- a/components/script/cors.rs +++ b/components/script/cors.rs @@ -18,7 +18,7 @@ use hyper::header::{HeaderView, Headers}; use hyper::method::Method; use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::status::StatusClass::Success; -use net_traits::{AsyncResponseListener, Metadata, ResponseAction}; +use net_traits::{AsyncResponseListener, Metadata, NetworkError, ResponseAction}; use network_listener::{NetworkListener, PreInvoke}; use script_runtime::ScriptChan; use std::ascii::AsciiExt; @@ -124,13 +124,15 @@ impl CORSRequest { // This is shoe-horning the CORSReponse stuff into the rest of the async network // framework right now. It would be worth redesigning http_fetch to do this properly. impl AsyncResponseListener for CORSContext { - fn headers_available(&mut self, _metadata: Metadata) { + fn headers_available(&mut self, _metadata: Result) { + } fn data_available(&mut self, _payload: Vec) { + } - fn response_complete(&mut self, _status: Result<(), String>) { + fn response_complete(&mut self, _status: Result<(), NetworkError>) { let response = self.response.take().unwrap(); self.listener.response_available(response); } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 50c3fbf682ff..b7dc3e2f6630 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -57,11 +57,11 @@ use layout_interface::{LayoutChan, LayoutRPC}; use libc; use msg::constellation_msg::ConstellationChan; use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData}; -use net_traits::Metadata; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread}; use net_traits::response::HttpsState; use net_traits::storage_thread::StorageType; +use net_traits::{Metadata, NetworkError}; use offscreen_gl_context::GLLimits; use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan; @@ -287,6 +287,7 @@ no_jsmanaged_fields!(Size2D); no_jsmanaged_fields!(Arc); no_jsmanaged_fields!(Image, ImageMetadata, ImageCacheChan, ImageCacheThread); no_jsmanaged_fields!(Metadata); +no_jsmanaged_fields!(NetworkError); no_jsmanaged_fields!(Atom, Namespace, QualName); no_jsmanaged_fields!(Trusted); no_jsmanaged_fields!(PropertyDeclarationBlock); diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 1ad52f45d6c3..a971a90ecbfe 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -24,7 +24,7 @@ use ipc_channel::ipc; use ipc_channel::router::ROUTER; use layout_interface::{LayoutChan, Msg}; use msg::constellation_msg::ConstellationChan; -use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata}; +use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError}; use network_listener::{NetworkListener, PreInvoke}; use script_traits::{MozBrowserEvent, ScriptMsg as ConstellationMsg}; use std::ascii::AsciiExt; @@ -271,8 +271,8 @@ struct StylesheetContext { impl PreInvoke for StylesheetContext {} impl AsyncResponseListener for StylesheetContext { - fn headers_available(&mut self, metadata: Metadata) { - self.metadata = Some(metadata); + fn headers_available(&mut self, metadata: Result) { + self.metadata = metadata.ok(); } fn data_available(&mut self, payload: Vec) { @@ -280,9 +280,12 @@ impl AsyncResponseListener for StylesheetContext { self.data.append(&mut payload); } - fn response_complete(&mut self, _status: Result<(), String>) { + fn response_complete(&mut self, _status: Result<(), NetworkError>) { let data = mem::replace(&mut self.data, vec!()); - let metadata = self.metadata.take().unwrap(); + let metadata = match self.metadata.take() { + Some(meta) => meta, + None => return, + }; // TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding let environment_encoding = UTF_8 as EncodingRef; let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s); diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index b5a6a473dd37..af9339571fbf 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -33,7 +33,7 @@ use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::jsapi::RootedValue; use js::jsval::UndefinedValue; -use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata}; +use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError}; use network_listener::{NetworkListener, PreInvoke}; use script_runtime::ScriptChan; use script_thread::MainThreadScriptChan; @@ -124,7 +124,7 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[ #[derive(HeapSizeOf, JSTraceable)] pub enum ScriptOrigin { Internal(DOMString, Url), - External(Result<(Metadata, Vec), String>), + External(Result<(Metadata, Vec), NetworkError>), } /// The context required for asynchronously loading an external script source. @@ -138,23 +138,25 @@ struct ScriptContext { /// The initial URL requested. url: Url, /// Indicates whether the request failed, and why - status: Result<(), String> + status: Result<(), NetworkError> } impl AsyncResponseListener for ScriptContext { - fn headers_available(&mut self, metadata: Metadata) { - let status_code = match metadata.status { - Some(RawStatus(c, _)) => c, - _ => 0 - }; + fn headers_available(&mut self, metadata: Result) { + self.metadata = metadata.ok(); + + let status_code = self.metadata.as_ref().and_then(|m| { + match m.status { + Some(RawStatus(c, _)) => Some(c), + _ => None, + } + }).unwrap_or(0); self.status = match status_code { - 0 => Err("No http status code received".to_owned()), + 0 => Err(NetworkError::Internal("No http status code received".to_owned())), 200...299 => Ok(()), // HTTP ok status codes - _ => Err(format!("HTTP error code {}", status_code)) + _ => Err(NetworkError::Internal(format!("HTTP error code {}", status_code))) }; - - self.metadata = Some(metadata); } fn data_available(&mut self, payload: Vec) { @@ -164,7 +166,7 @@ impl AsyncResponseListener for ScriptContext { } } - fn response_complete(&mut self, status: Result<(), String>) { + fn response_complete(&mut self, status: Result<(), NetworkError>) { let load = status.and(self.status.clone()).map(|_| { let data = mem::replace(&mut self.data, vec!()); let metadata = self.metadata.take().unwrap(); @@ -398,7 +400,7 @@ impl HTMLScriptElement { let (source, external, url) = match load { // Step 2.a. ScriptOrigin::External(Err(e)) => { - error!("error loading script {}", e); + error!("error loading script {:?}", e); self.dispatch_error_event(); return; } diff --git a/components/script/dom/servohtmlparser.rs b/components/script/dom/servohtmlparser.rs index 2312ac96795d..d1d179aa2a7b 100644 --- a/components/script/dom/servohtmlparser.rs +++ b/components/script/dom/servohtmlparser.rs @@ -26,7 +26,7 @@ use hyper::header::ContentType; use hyper::mime::{Mime, SubLevel, TopLevel}; use js::jsapi::JSTracer; use msg::constellation_msg::{PipelineId, SubpageId}; -use net_traits::{AsyncResponseListener, Metadata}; +use net_traits::{AsyncResponseListener, Metadata, NetworkError}; use network_listener::PreInvoke; use parse::Parser; use script_runtime::ScriptChan; @@ -36,6 +36,7 @@ use std::cell::UnsafeCell; use std::default::Default; use std::ptr; use url::Url; +use util::resource_files::read_resource_file; #[must_root] #[derive(JSTraceable, HeapSizeOf)] @@ -239,12 +240,23 @@ impl ParserContext { } impl AsyncResponseListener for ParserContext { - fn headers_available(&mut self, metadata: Metadata) { - let content_type = metadata.content_type.clone(); - - let parser = ScriptThread::page_fetch_complete(self.id.clone(), self.subpage.clone(), - metadata); - let parser = match parser { + fn headers_available(&mut self, meta_result: Result) { + let mut is_ssl_error = false; + let metadata = match meta_result { + Ok(meta) => Some(meta), + Err(NetworkError::SslValidation(url)) => { + is_ssl_error = true; + let mut meta = Metadata::default(url); + let mime: Option = "text/html".parse().ok(); + meta.set_content_type(mime.as_ref()); + Some(meta) + }, + Err(_) => None, + }; + let content_type = metadata.clone().and_then(|meta| meta.content_type); + let parser = match ScriptThread::page_fetch_complete(self.id.clone(), + self.subpage.clone(), + metadata) { Some(parser) => parser, None => return, }; @@ -274,7 +286,15 @@ impl AsyncResponseListener for ParserContext { parser.parse_sync(); parser.set_plaintext_state(); }, - Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => {}, // Handle text/html + Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => { // Handle text/html + if is_ssl_error { + self.is_synthesized_document = true; + let page_bytes = read_resource_file("badcert.html").unwrap(); + let page = String::from_utf8(page_bytes).unwrap(); + parser.pending_input().borrow_mut().push(page); + parser.parse_sync(); + } + }, Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {}, // Handle text/xml Some(ContentType(Mime(toplevel, sublevel, _))) => { if toplevel.as_str() == "application" && sublevel.as_str() == "xhtml+xml" { @@ -308,7 +328,7 @@ impl AsyncResponseListener for ParserContext { } } - fn response_complete(&mut self, status: Result<(), String>) { + fn response_complete(&mut self, status: Result<(), NetworkError>) { let parser = match self.parser.as_ref() { Some(parser) => parser.root(), None => return, @@ -316,7 +336,7 @@ impl AsyncResponseListener for ParserContext { parser.r().document().finish_load(LoadType::PageSource(self.url.clone())); if let Err(err) = status { - debug!("Failed to load page URL {}, error: {}", self.url.serialize(), err); + debug!("Failed to load page URL {}, error: {:?}", self.url.serialize(), err); // TODO(Savago): we should send a notification to callers #5463. } diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 8d6716201af4..a381cae57b09 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -45,7 +45,7 @@ use js::jsapi::JS_ClearPendingException; use js::jsapi::{JSContext, JS_ParseJSON, RootedValue}; use js::jsval::{JSVal, NullValue, UndefinedValue}; use net_traits::ControlMsg::Load; -use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata}; +use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError}; use net_traits::{LoadConsumer, LoadContext, LoadData, ResourceCORSData, ResourceThread}; use network_listener::{NetworkListener, PreInvoke}; use parse::html::{ParseContext, parse_html}; @@ -254,7 +254,7 @@ impl XMLHttpRequest { resource_thread: ResourceThread, load_data: LoadData) { impl AsyncResponseListener for XHRContext { - fn headers_available(&mut self, metadata: Metadata) { + fn headers_available(&mut self, metadata: Result) { let xhr = self.xhr.root(); let rv = xhr.process_headers_available(self.cors_request.clone(), self.gen_id, @@ -269,7 +269,7 @@ impl XMLHttpRequest { self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone()); } - fn response_complete(&mut self, status: Result<(), String>) { + fn response_complete(&mut self, status: Result<(), NetworkError>) { let rv = self.xhr.root().process_response_complete(self.gen_id, status); *self.sync_status.borrow_mut() = Some(rv); } @@ -870,7 +870,15 @@ impl XMLHttpRequest { } fn process_headers_available(&self, cors_request: Option, - gen_id: GenerationId, metadata: Metadata) -> Result<(), Error> { + gen_id: GenerationId, metadata: Result) + -> Result<(), Error> { + let metadata = match metadata { + Ok(meta) => meta, + Err(_) => { + self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network)); + return Err(Error::Network); + }, + }; let bypass_cross_origin_check = { // We want to be able to do cross-origin requests in browser.html. @@ -904,9 +912,7 @@ impl XMLHttpRequest { *self.response_url.borrow_mut() = metadata.final_url.serialize_no_fragment(); // XXXManishearth Clear cache entries in case of a network error - self.process_partial_response(XHRProgress::HeadersReceived(gen_id, - metadata.headers, - metadata.status)); + self.process_partial_response(XHRProgress::HeadersReceived(gen_id, metadata.headers, metadata.status)); Ok(()) } @@ -914,7 +920,7 @@ impl XMLHttpRequest { self.process_partial_response(XHRProgress::Loading(gen_id, ByteString::new(payload))); } - fn process_response_complete(&self, gen_id: GenerationId, status: Result<(), String>) + fn process_response_complete(&self, gen_id: GenerationId, status: Result<(), NetworkError>) -> ErrorResult { match status { Ok(()) => { diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 78c9aefc4095..d076701a9f5c 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -492,7 +492,7 @@ pub unsafe extern "C" fn shadow_check_callback(_cx: *mut JSContext, } impl ScriptThread { - pub fn page_fetch_complete(id: PipelineId, subpage: Option, metadata: Metadata) + pub fn page_fetch_complete(id: PipelineId, subpage: Option, metadata: Option) -> Option { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.borrow().unwrap() }; @@ -1280,7 +1280,7 @@ impl ScriptThread { /// We have received notification that the response associated with a load has completed. /// Kick off the document and frame tree creation process using the result. fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option, - metadata: Metadata) -> Option { + metadata: Option) -> Option { let idx = self.incomplete_loads.borrow().iter().position(|load| { load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage }); @@ -1289,7 +1289,7 @@ impl ScriptThread { match idx { Some(idx) => { let load = self.incomplete_loads.borrow_mut().remove(idx); - Some(self.load(metadata, load)) + metadata.map(|meta| self.load(meta, load)) } None => { assert!(self.closed_pipelines.borrow().contains(&id));