diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 192be87e95a..22f9a9958c1 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -22,6 +22,7 @@ use ipc_channel::ipc; use libc::c_void; use msg::constellation_msg::{PipelineId, PipelineIndex, PipelineNamespaceId}; use net_traits::image::base::Image; +use net_traits::image_cache::CorsStatus; use num_traits::FromPrimitive; #[cfg(feature = "gl")] use pixels::PixelFormat; @@ -1384,6 +1385,7 @@ impl IOCompositor { format: PixelFormat::RGB8, bytes: ipc::IpcSharedMemory::from_bytes(&*img), id: None, + cors_status: CorsStatus::Safe, }) }, #[cfg(feature = "gl")] diff --git a/components/layout/context.rs b/components/layout/context.rs index 015cc9ac40d..5c373ac668d 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -18,7 +18,7 @@ use script_layout_interface::{PendingImage, PendingImageState}; use script_traits::Painter; use script_traits::UntrustedNodeAddress; use servo_atoms::Atom; -use servo_url::ServoUrl; +use servo_url::{ImmutableOrigin, ServoUrl}; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::hash::BuildHasherDefault; @@ -60,6 +60,9 @@ pub struct LayoutContext<'a> { /// The pipeline id of this LayoutContext. pub id: PipelineId, + /// The origin of this layout context. + pub origin: ImmutableOrigin, + /// Bits shared by the layout and style system. pub style_context: SharedStyleContext<'a>, @@ -120,9 +123,12 @@ impl<'a> LayoutContext<'a> { }; // See if the image is already available - let result = - self.image_cache - .find_image_or_metadata(url.clone(), use_placeholder, can_request); + let result = self.image_cache.find_image_or_metadata( + url.clone(), + self.origin.clone(), + use_placeholder, + can_request, + ); match result { Ok(image_or_metadata) => Some(image_or_metadata), // Image failed to load, so just return nothing diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index f03ca9f9d03..84ace1c1efe 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -659,6 +659,7 @@ impl LayoutThread { LayoutContext { id: self.id, + origin: self.url.origin(), style_context: SharedStyleContext { stylist: &self.stylist, options: GLOBAL_STYLE_DATA.options.clone(), diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index bfa87fc0687..89397483ee3 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -5,12 +5,12 @@ use embedder_traits::resources::{self, Resource}; use immeta::load_from_buf; use net_traits::image::base::{load_from_memory, Image, ImageMetadata}; -use net_traits::image_cache::{CanRequestImages, ImageCache, ImageResponder}; +use net_traits::image_cache::{CanRequestImages, CorsStatus, ImageCache, ImageResponder}; use net_traits::image_cache::{ImageOrMetadataAvailable, ImageResponse, ImageState}; use net_traits::image_cache::{PendingImageId, UsePlaceholder}; -use net_traits::{FetchMetadata, FetchResponseMsg, NetworkError}; +use net_traits::{FetchMetadata, FetchResponseMsg, FilteredMetadata, NetworkError}; use pixels::PixelFormat; -use servo_url::ServoUrl; +use servo_url::{ImmutableOrigin, ServoUrl}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::HashMap; use std::io; @@ -33,8 +33,8 @@ use webrender_api::units::DeviceIntSize; // Helper functions. // ====================================================================== -fn decode_bytes_sync(key: LoadKey, bytes: &[u8]) -> DecoderMsg { - let image = load_from_memory(bytes); +fn decode_bytes_sync(key: LoadKey, bytes: &[u8], cors: CorsStatus) -> DecoderMsg { + let image = load_from_memory(bytes, cors); DecoderMsg { key: key, image: image, @@ -45,7 +45,7 @@ fn get_placeholder_image( webrender_api: &webrender_api::RenderApi, data: &[u8], ) -> io::Result> { - let mut image = load_from_memory(&data).unwrap(); + let mut image = load_from_memory(&data, CorsStatus::Unsafe).unwrap(); set_webrender_image_key(webrender_api, &mut image); Ok(Arc::new(image)) } @@ -99,9 +99,9 @@ struct AllPendingLoads { // for performance reasons. loads: HashMap, - // Get a load key from its url. Used ony when starting and + // Get a load key from its url and requesting origin. Used ony when starting and // finishing a load or when adding a new listener. - url_to_load_key: HashMap, + url_to_load_key: HashMap<(ServoUrl, ImmutableOrigin), LoadKey>, // A counter used to generate instances of LoadKey keygen: LoadKeyGenerator, @@ -123,7 +123,9 @@ impl AllPendingLoads { fn remove(&mut self, key: &LoadKey) -> Option { self.loads.remove(key).and_then(|pending_load| { - self.url_to_load_key.remove(&pending_load.url).unwrap(); + self.url_to_load_key + .remove(&(pending_load.url.clone(), pending_load.load_origin.clone())) + .unwrap(); Some(pending_load) }) } @@ -131,9 +133,10 @@ impl AllPendingLoads { fn get_cached<'a>( &'a mut self, url: ServoUrl, + origin: ImmutableOrigin, can_request: CanRequestImages, ) -> CacheResult<'a> { - match self.url_to_load_key.entry(url.clone()) { + match self.url_to_load_key.entry((url.clone(), origin.clone())) { Occupied(url_entry) => { let load_key = url_entry.get(); CacheResult::Hit(*load_key, self.loads.get_mut(load_key).unwrap()) @@ -146,7 +149,7 @@ impl AllPendingLoads { let load_key = self.keygen.next(); url_entry.insert(load_key); - let pending_load = PendingLoad::new(url); + let pending_load = PendingLoad::new(url, origin); match self.loads.entry(load_key) { Occupied(_) => unreachable!(), Vacant(load_entry) => { @@ -251,33 +254,44 @@ enum LoadResult { /// Represents an image that is either being loaded /// by the resource thread, or decoded by a worker thread. struct PendingLoad { - // The bytes loaded so far. Reset to an empty vector once loading - // is complete and the buffer has been transmitted to the decoder. + /// The bytes loaded so far. Reset to an empty vector once loading + /// is complete and the buffer has been transmitted to the decoder. bytes: ImageBytes, - // Image metadata, if available. + /// Image metadata, if available. metadata: Option, - // Once loading is complete, the result of the operation. + /// Once loading is complete, the result of the operation. result: Option>, + + /// The listeners that are waiting for this response to complete. listeners: Vec, - // The url being loaded. Do not forget that this may be several Mb - // if we are loading a data: url. + /// The url being loaded. Do not forget that this may be several Mb + /// if we are loading a data: url. url: ServoUrl, + /// The origin that requested this load. + load_origin: ImmutableOrigin, + + /// The CORS status of this image response. + cors_status: CorsStatus, + + /// The URL of the final response that contains a body. final_url: Option, } impl PendingLoad { - fn new(url: ServoUrl) -> PendingLoad { + fn new(url: ServoUrl, load_origin: ImmutableOrigin) -> PendingLoad { PendingLoad { bytes: ImageBytes::InProgress(vec![]), metadata: None, result: None, listeners: vec![], url: url, + load_origin, final_url: None, + cors_status: CorsStatus::Unsafe, } } @@ -294,7 +308,7 @@ struct ImageCacheStore { pending_loads: AllPendingLoads, // Images that have finished loading (successful or not) - completed_loads: HashMap, + completed_loads: HashMap<(ServoUrl, ImmutableOrigin), CompletedLoad>, // The placeholder image used when an image fails to load placeholder_image: Option>, @@ -331,8 +345,10 @@ impl ImageCacheStore { }; let completed_load = CompletedLoad::new(image_response.clone(), key); - self.completed_loads - .insert(pending_load.url.into(), completed_load); + self.completed_loads.insert( + (pending_load.url.into(), pending_load.load_origin), + completed_load, + ); for listener in pending_load.listeners { listener.respond(image_response.clone()); @@ -343,20 +359,27 @@ impl ImageCacheStore { /// or the complete load is not fully decoded or is unavailable. fn get_completed_image_if_available( &self, - url: &ServoUrl, + url: ServoUrl, + origin: ImmutableOrigin, placeholder: UsePlaceholder, ) -> Option> { - self.completed_loads.get(url).map(|completed_load| { - match (&completed_load.image_response, placeholder) { - (&ImageResponse::Loaded(ref image, ref url), _) | - (&ImageResponse::PlaceholderLoaded(ref image, ref url), UsePlaceholder::Yes) => Ok( - ImageOrMetadataAvailable::ImageAvailable(image.clone(), url.clone()), - ), - (&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) | - (&ImageResponse::None, _) | - (&ImageResponse::MetadataLoaded(_), _) => Err(ImageState::LoadError), - } - }) + self.completed_loads + .get(&(url, origin)) + .map( + |completed_load| match (&completed_load.image_response, placeholder) { + (&ImageResponse::Loaded(ref image, ref url), _) | + ( + &ImageResponse::PlaceholderLoaded(ref image, ref url), + UsePlaceholder::Yes, + ) => Ok(ImageOrMetadataAvailable::ImageAvailable( + image.clone(), + url.clone(), + )), + (&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) | + (&ImageResponse::None, _) | + (&ImageResponse::MetadataLoaded(_), _) => Err(ImageState::LoadError), + }, + ) } /// Handle a message from one of the decoder worker threads or from a sync @@ -397,23 +420,28 @@ impl ImageCache for ImageCacheImpl { fn find_image_or_metadata( &self, url: ServoUrl, + origin: ImmutableOrigin, use_placeholder: UsePlaceholder, can_request: CanRequestImages, ) -> Result { - debug!("Find image or metadata for {}", url); + debug!("Find image or metadata for {} ({:?})", url, origin); let mut store = self.store.lock().unwrap(); - if let Some(result) = store.get_completed_image_if_available(&url, use_placeholder) { + if let Some(result) = + store.get_completed_image_if_available(url.clone(), origin.clone(), use_placeholder) + { debug!("{} is available", url); return result; } let decoded = { - let result = store.pending_loads.get_cached(url.clone(), can_request); + let result = store + .pending_loads + .get_cached(url.clone(), origin.clone(), can_request); match result { CacheResult::Hit(key, pl) => match (&pl.result, &pl.metadata) { (&Some(Ok(_)), _) => { debug!("Sync decoding {} ({:?})", url, key); - decode_bytes_sync(key, &pl.bytes.as_slice()) + decode_bytes_sync(key, &pl.bytes.as_slice(), pl.cors_status) }, (&None, &Some(ref meta)) => { debug!("Metadata available for {} ({:?})", url, key); @@ -440,7 +468,7 @@ impl ImageCache for ImageCacheImpl { // and ignore the async decode when it finishes later. // TODO: make this behaviour configurable according to the caller's needs. store.handle_decoder(decoded); - match store.get_completed_image_if_available(&url, use_placeholder) { + match store.get_completed_image_if_available(url, origin, use_placeholder) { Some(result) => result, None => Err(ImageState::LoadError), } @@ -472,15 +500,26 @@ impl ImageCache for ImageCacheImpl { (FetchResponseMsg::ProcessResponse(response), _) => { let mut store = self.store.lock().unwrap(); let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap(); - let metadata = match response { - Ok(meta) => Some(match meta { - FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_, - }), - Err(_) => None, + let (cors_status, metadata) = match response { + Ok(meta) => match meta { + FetchMetadata::Unfiltered(m) => (CorsStatus::Safe, Some(m)), + FetchMetadata::Filtered { unsafe_, filtered } => ( + match filtered { + FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_) => { + CorsStatus::Safe + }, + FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect => { + CorsStatus::Unsafe + }, + }, + Some(unsafe_), + ), + }, + Err(_) => (CorsStatus::Unsafe, None), }; let final_url = metadata.as_ref().map(|m| m.final_url.clone()); pending_load.final_url = final_url; + pending_load.cors_status = cors_status; }, (FetchResponseMsg::ProcessResponseChunk(data), _) => { debug!("Got some data for {:?}", id); @@ -506,17 +545,17 @@ impl ImageCache for ImageCacheImpl { debug!("Received EOF for {:?}", key); match result { Ok(_) => { - let bytes = { + let (bytes, cors_status) = { let mut store = self.store.lock().unwrap(); let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap(); pending_load.result = Some(Ok(())); debug!("Async decoding {} ({:?})", pending_load.url, key); - pending_load.bytes.mark_complete() + (pending_load.bytes.mark_complete(), pending_load.cors_status) }; let local_store = self.store.clone(); thread::spawn(move || { - let msg = decode_bytes_sync(key, &*bytes); + let msg = decode_bytes_sync(key, &*bytes, cors_status); debug!("Image decoded"); local_store.lock().unwrap().handle_decoder(msg); }); diff --git a/components/net_traits/image/base.rs b/components/net_traits/image/base.rs index 345f09551e2..262eaf476a7 100644 --- a/components/net_traits/image/base.rs +++ b/components/net_traits/image/base.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::image_cache::CorsStatus; use ipc_channel::ipc::IpcSharedMemory; use piston_image::{DynamicImage, ImageFormat}; use pixels::PixelFormat; @@ -16,6 +17,7 @@ pub struct Image { pub bytes: IpcSharedMemory, #[ignore_malloc_size_of = "Defined in webrender_api"] pub id: Option, + pub cors_status: CorsStatus, } impl fmt::Debug for Image { @@ -37,7 +39,7 @@ pub struct ImageMetadata { // FIXME: Images must not be copied every frame. Instead we should atomically // reference count them. -pub fn load_from_memory(buffer: &[u8]) -> Option { +pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option { if buffer.is_empty() { return None; } @@ -61,6 +63,7 @@ pub fn load_from_memory(buffer: &[u8]) -> Option { format: PixelFormat::BGRA8, bytes: IpcSharedMemory::from_bytes(&*rgba), id: None, + cors_status, }) }, Err(e) => { diff --git a/components/net_traits/image_cache.rs b/components/net_traits/image_cache.rs index 29b7cae972c..3126b37d8b5 100644 --- a/components/net_traits/image_cache.rs +++ b/components/net_traits/image_cache.rs @@ -5,7 +5,7 @@ use crate::image::base::{Image, ImageMetadata}; use crate::FetchResponseMsg; use ipc_channel::ipc::IpcSender; -use servo_url::ServoUrl; +use servo_url::{ImmutableOrigin, ServoUrl}; use std::sync::Arc; // ====================================================================== @@ -110,6 +110,7 @@ pub trait ImageCache: Sync + Send { fn find_image_or_metadata( &self, url: ServoUrl, + origin: ImmutableOrigin, use_placeholder: UsePlaceholder, can_request: CanRequestImages, ) -> Result; @@ -121,3 +122,14 @@ pub trait ImageCache: Sync + Send { /// Inform the image cache about a response for a pending request. fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg); } + +/// Whether this response passed any CORS checks, and is thus safe to read from +/// in cross-origin environments. +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum CorsStatus { + /// The response is either same-origin or cross-origin but passed CORS checks. + Safe, + /// The response is cross-origin and did not pass CORS checks. It is unsafe + /// to expose pixel data to the requesting environment. + Unsafe, +} diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 1faf9878b53..e2495816408 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -47,7 +47,7 @@ use net_traits::image_cache::UsePlaceholder; use pixels::PixelFormat; use profile_traits::ipc as profiled_ipc; use script_traits::ScriptMsg; -use servo_url::ServoUrl; +use servo_url::{ImmutableOrigin, ServoUrl}; use std::cell::Cell; use std::str::FromStr; use std::sync::Arc; @@ -126,6 +126,7 @@ pub struct CanvasState { /// The base URL for resolving CSS image URL values. /// Needed because of https://github.com/servo/servo/issues/17625 base_url: ServoUrl, + origin: ImmutableOrigin, /// Any missing image URLs. missing_image_urls: DomRefCell>, saved_states: DomRefCell>, @@ -152,6 +153,7 @@ impl CanvasState { base_url: global.api_base_url(), missing_image_urls: DomRefCell::new(Vec::new()), saved_states: DomRefCell::new(Vec::new()), + origin: global.origin().immutable().clone(), } } @@ -222,6 +224,7 @@ impl CanvasState { fn request_image_from_cache(&self, url: ServoUrl) -> ImageResponse { let response = self.image_cache.find_image_or_metadata( url.clone(), + self.origin.clone(), UsePlaceholder::No, CanRequestImages::No, ); diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 1c07a88cc1c..eb62d920ca9 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -448,6 +448,7 @@ pub mod utils { let image_cache = window.image_cache(); let response = image_cache.find_image_or_metadata( url.into(), + window.origin().immutable().clone(), UsePlaceholder::No, CanRequestImages::No, ); diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 14911650be6..95f00a2b007 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -56,7 +56,7 @@ use mime::{self, Mime}; use msg::constellation_msg::PipelineId; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache::UsePlaceholder; -use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable}; +use net_traits::image_cache::{CanRequestImages, CorsStatus, ImageCache, ImageOrMetadataAvailable}; use net_traits::image_cache::{ImageResponder, ImageResponse, ImageState, PendingImageId}; use net_traits::request::RequestBuilder; use net_traits::{FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError}; @@ -281,6 +281,7 @@ impl HTMLImageElement { let image_cache = window.image_cache(); let response = image_cache.find_image_or_metadata( img_url.clone().into(), + window.origin().immutable().clone(), UsePlaceholder::Yes, CanRequestImages::Yes, ); @@ -907,6 +908,7 @@ impl HTMLImageElement { let image_cache = window.image_cache(); let response = image_cache.find_image_or_metadata( img_url.clone().into(), + window.origin().immutable().clone(), UsePlaceholder::No, CanRequestImages::No, ); @@ -1062,6 +1064,7 @@ impl HTMLImageElement { // Step 14 let response = image_cache.find_image_or_metadata( img_url.clone().into(), + window.origin().immutable().clone(), UsePlaceholder::No, CanRequestImages::Yes, ); @@ -1268,6 +1271,10 @@ impl HTMLImageElement { } pub fn same_origin(&self, origin: &MutableOrigin) -> bool { + if let Some(ref image) = self.current_request.borrow().image { + return image.cors_status == CorsStatus::Safe; + } + self.current_request .borrow() .final_url diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index e95a2ca8ccd..4a1816b5cd4 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -132,6 +132,7 @@ impl HTMLVideoElement { let image_cache = window.image_cache(); let response = image_cache.find_image_or_metadata( poster_url.clone().into(), + window.origin().immutable().clone(), UsePlaceholder::No, CanRequestImages::Yes, ); diff --git a/components/url/origin.rs b/components/url/origin.rs index 1878e85a9d8..b272eb96e52 100644 --- a/components/url/origin.rs +++ b/components/url/origin.rs @@ -8,7 +8,7 @@ use url::{Host, Origin}; use uuid::Uuid; /// The origin of an URL -#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum ImmutableOrigin { /// A globally unique identifier Opaque(OpaqueOrigin), @@ -82,7 +82,7 @@ impl ImmutableOrigin { } /// Opaque identifier for URLs that have file or other schemes -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct OpaqueOrigin(Uuid); malloc_size_of_is_0!(OpaqueOrigin);