diff options
Diffstat (limited to 'components/shared/net/lib.rs')
-rw-r--r-- | components/shared/net/lib.rs | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs new file mode 100644 index 00000000000..b8b32725a25 --- /dev/null +++ b/components/shared/net/lib.rs @@ -0,0 +1,859 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_code)] + +use cookie::Cookie; +use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader}; +use http::{Error as HttpError, HeaderMap, StatusCode}; +use hyper::Error as HyperError; +use hyper_serde::Serde; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::router::ROUTER; +use ipc_channel::Error as IpcError; +use lazy_static::lazy_static; +use log::warn; +use malloc_size_of::malloc_size_of_is_0; +use malloc_size_of_derive::MallocSizeOf; +use mime::Mime; +use msg::constellation_msg::HistoryStateId; +use rustls::Certificate; +use serde::{Deserialize, Serialize}; +use servo_rand::RngCore; +use servo_url::{ImmutableOrigin, ServoUrl}; +use time::precise_time_ns; +use webrender_api::{ImageData, ImageDescriptor, ImageKey}; + +use crate::filemanager_thread::FileManagerThreadMsg; +use crate::request::{Request, RequestBuilder}; +use crate::response::{HttpsState, Response, ResponseInit}; +use crate::storage_thread::StorageThreadMsg; + +pub mod blob_url_store; +pub mod filemanager_thread; +pub mod image_cache; +pub mod pub_domains; +pub mod quality; +pub mod request; +pub mod response; +pub mod storage_thread; + +/// Image handling. +/// +/// It may be surprising that this goes in the network crate as opposed to the graphics crate. +/// However, image handling is generally very integrated with the network stack (especially where +/// caching is involved) and as a result it must live in here. +pub mod image { + pub mod base; +} + +/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/) +pub mod fetch { + pub mod headers; +} + +/// A loading context, for context-specific sniffing, as defined in +/// <https://mimesniff.spec.whatwg.org/#context-specific-sniffing> +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum LoadContext { + Browsing, + Image, + AudioVideo, + Plugin, + Style, + Script, + Font, + TextTrack, + CacheManifest, +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct CustomResponse { + #[ignore_malloc_size_of = "Defined in hyper"] + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + pub headers: HeaderMap, + #[ignore_malloc_size_of = "Defined in hyper"] + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + pub raw_status: (StatusCode, String), + pub body: Vec<u8>, +} + +impl CustomResponse { + pub fn new( + headers: HeaderMap, + raw_status: (StatusCode, String), + body: Vec<u8>, + ) -> CustomResponse { + CustomResponse { + headers: headers, + raw_status: raw_status, + body: body, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CustomResponseMediator { + pub response_chan: IpcSender<Option<CustomResponse>>, + pub load_url: ServoUrl, +} + +/// [Policies](https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-states) +/// for providing a referrer header for a request +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum ReferrerPolicy { + /// "no-referrer" + NoReferrer, + /// "no-referrer-when-downgrade" + NoReferrerWhenDowngrade, + /// "origin" + Origin, + /// "same-origin" + SameOrigin, + /// "origin-when-cross-origin" + OriginWhenCrossOrigin, + /// "unsafe-url" + UnsafeUrl, + /// "strict-origin" + StrictOrigin, + /// "strict-origin-when-cross-origin" + StrictOriginWhenCrossOrigin, +} + +impl ToString for ReferrerPolicy { + fn to_string(&self) -> String { + match self { + ReferrerPolicy::NoReferrer => "no-referrer", + ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade", + ReferrerPolicy::Origin => "origin", + ReferrerPolicy::SameOrigin => "same-origin", + ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin", + ReferrerPolicy::UnsafeUrl => "unsafe-url", + ReferrerPolicy::StrictOrigin => "strict-origin", + ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin", + } + .to_string() + } +} + +impl From<ReferrerPolicyHeader> for ReferrerPolicy { + fn from(policy: ReferrerPolicyHeader) -> Self { + match policy { + ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer, + ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => { + ReferrerPolicy::NoReferrerWhenDowngrade + }, + ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin, + ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin, + ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin, + ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl, + ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin, + ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => { + ReferrerPolicy::StrictOriginWhenCrossOrigin + }, + } + } +} + +impl From<ReferrerPolicy> for ReferrerPolicyHeader { + fn from(referrer_policy: ReferrerPolicy) -> Self { + match referrer_policy { + ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER, + ReferrerPolicy::NoReferrerWhenDowngrade => { + ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE + }, + ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN, + ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN, + ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN, + ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL, + ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN, + ReferrerPolicy::StrictOriginWhenCrossOrigin => { + ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN + }, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum FetchResponseMsg { + // todo: should have fields for transmitted/total bytes + ProcessRequestBody, + ProcessRequestEOF, + // todo: send more info about the response (or perhaps the entire Response) + ProcessResponse(Result<FetchMetadata, NetworkError>), + ProcessResponseChunk(Vec<u8>), + ProcessResponseEOF(Result<ResourceFetchTiming, NetworkError>), +} + +pub trait FetchTaskTarget { + /// <https://fetch.spec.whatwg.org/#process-request-body> + /// + /// Fired when a chunk of the request body is transmitted + fn process_request_body(&mut self, request: &Request); + + /// <https://fetch.spec.whatwg.org/#process-request-end-of-file> + /// + /// Fired when the entire request finishes being transmitted + fn process_request_eof(&mut self, request: &Request); + + /// <https://fetch.spec.whatwg.org/#process-response> + /// + /// Fired when headers are received + fn process_response(&mut self, response: &Response); + + /// Fired when a chunk of response content is received + fn process_response_chunk(&mut self, chunk: Vec<u8>); + + /// <https://fetch.spec.whatwg.org/#process-response-end-of-file> + /// + /// Fired when the response is fully fetched + fn process_response_eof(&mut self, response: &Response); +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FilteredMetadata { + Basic(Metadata), + Cors(Metadata), + Opaque, + OpaqueRedirect(ServoUrl), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FetchMetadata { + Unfiltered(Metadata), + Filtered { + filtered: FilteredMetadata, + unsafe_: Metadata, + }, +} + +pub trait FetchResponseListener { + fn process_request_body(&mut self); + fn process_request_eof(&mut self); + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>); + fn process_response_chunk(&mut self, chunk: Vec<u8>); + fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>); + fn resource_timing(&self) -> &ResourceFetchTiming; + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming; + fn submit_resource_timing(&mut self); +} + +impl FetchTaskTarget for IpcSender<FetchResponseMsg> { + fn process_request_body(&mut self, _: &Request) { + let _ = self.send(FetchResponseMsg::ProcessRequestBody); + } + + fn process_request_eof(&mut self, _: &Request) { + let _ = self.send(FetchResponseMsg::ProcessRequestEOF); + } + + fn process_response(&mut self, response: &Response) { + let _ = self.send(FetchResponseMsg::ProcessResponse(response.metadata())); + } + + fn process_response_chunk(&mut self, chunk: Vec<u8>) { + let _ = self.send(FetchResponseMsg::ProcessResponseChunk(chunk)); + } + + fn process_response_eof(&mut self, response: &Response) { + if let Some(e) = response.get_network_error() { + let _ = self.send(FetchResponseMsg::ProcessResponseEOF(Err(e.clone()))); + } else { + let _ = self.send(FetchResponseMsg::ProcessResponseEOF(Ok(response + .get_resource_timing() + .lock() + .unwrap() + .clone()))); + } + } +} + +/// A fetch task that discards all data it's sent, +/// useful when speculatively prefetching data that we don't need right +/// now, but might need in the future. +pub struct DiscardFetch; + +impl FetchTaskTarget for DiscardFetch { + fn process_request_body(&mut self, _: &Request) {} + + fn process_request_eof(&mut self, _: &Request) {} + + fn process_response(&mut self, _: &Response) {} + + fn process_response_chunk(&mut self, _: Vec<u8>) {} + + fn process_response_eof(&mut self, _: &Response) {} +} + +pub trait Action<Listener> { + fn process(self, listener: &mut Listener); +} + +impl<T: FetchResponseListener> Action<T> for FetchResponseMsg { + /// Execute the default action on a provided listener. + fn process(self, listener: &mut T) { + match self { + FetchResponseMsg::ProcessRequestBody => listener.process_request_body(), + FetchResponseMsg::ProcessRequestEOF => listener.process_request_eof(), + FetchResponseMsg::ProcessResponse(meta) => listener.process_response(meta), + FetchResponseMsg::ProcessResponseChunk(data) => listener.process_response_chunk(data), + FetchResponseMsg::ProcessResponseEOF(data) => { + match data { + Ok(ref response_resource_timing) => { + // update listener with values from response + *listener.resource_timing_mut() = response_resource_timing.clone(); + listener.process_response_eof(Ok(response_resource_timing.clone())); + // TODO timing check https://w3c.github.io/resource-timing/#dfn-timing-allow-check + + listener.submit_resource_timing(); + }, + // TODO Resources for which the fetch was initiated, but was later aborted + // (e.g. due to a network error) MAY be included as PerformanceResourceTiming + // objects in the Performance Timeline and MUST contain initialized attribute + // values for processed substeps of the processing model. + Err(e) => listener.process_response_eof(Err(e)), + } + }, + } + } +} + +/// Handle to a resource thread +pub type CoreResourceThread = IpcSender<CoreResourceMsg>; + +pub type IpcSendResult = Result<(), IpcError>; + +/// Abstraction of the ability to send a particular type of message, +/// used by net_traits::ResourceThreads to ease the use its IpcSender sub-fields +/// XXX: If this trait will be used more in future, some auto derive might be appealing +pub trait IpcSend<T> +where + T: serde::Serialize + for<'de> serde::Deserialize<'de>, +{ + /// send message T + fn send(&self, _: T) -> IpcSendResult; + /// get underlying sender + fn sender(&self) -> IpcSender<T>; +} + +// FIXME: Originally we will construct an Arc<ResourceThread> from ResourceThread +// in script_thread to avoid some performance pitfall. Now we decide to deal with +// the "Arc" hack implicitly in future. +// See discussion: http://logs.glob.uno/?c=mozilla%23servo&s=16+May+2016&e=16+May+2016#c430412 +// See also: https://github.com/servo/servo/blob/735480/components/script/script_thread.rs#L313 +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ResourceThreads { + core_thread: CoreResourceThread, + storage_thread: IpcSender<StorageThreadMsg>, +} + +impl ResourceThreads { + pub fn new(c: CoreResourceThread, s: IpcSender<StorageThreadMsg>) -> ResourceThreads { + ResourceThreads { + core_thread: c, + storage_thread: s, + } + } + + pub fn clear_cache(&self) { + let _ = self.core_thread.send(CoreResourceMsg::ClearCache); + } +} + +impl IpcSend<CoreResourceMsg> for ResourceThreads { + fn send(&self, msg: CoreResourceMsg) -> IpcSendResult { + self.core_thread.send(msg) + } + + fn sender(&self) -> IpcSender<CoreResourceMsg> { + self.core_thread.clone() + } +} + +impl IpcSend<StorageThreadMsg> for ResourceThreads { + fn send(&self, msg: StorageThreadMsg) -> IpcSendResult { + self.storage_thread.send(msg) + } + + fn sender(&self) -> IpcSender<StorageThreadMsg> { + self.storage_thread.clone() + } +} + +// Ignore the sub-fields +malloc_size_of_is_0!(ResourceThreads); + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum IncludeSubdomains { + Included, + NotIncluded, +} + +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum MessageData { + Text(String), + Binary(Vec<u8>), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebSocketDomAction { + SendMessage(MessageData), + Close(Option<u16>, Option<String>), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebSocketNetworkEvent { + ConnectionEstablished { protocol_in_use: Option<String> }, + MessageReceived(MessageData), + Close(Option<u16>, String), + Fail, +} + +#[derive(Debug, Deserialize, Serialize)] +/// IPC channels to communicate with the script thread about network or DOM events. +pub enum FetchChannels { + ResponseMsg( + IpcSender<FetchResponseMsg>, + /* cancel_chan */ Option<IpcReceiver<()>>, + ), + WebSocket { + event_sender: IpcSender<WebSocketNetworkEvent>, + action_receiver: IpcReceiver<WebSocketDomAction>, + }, + /// If the fetch is just being done to populate the cache, + /// not because the data is needed now. + Prefetch, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum CoreResourceMsg { + Fetch(RequestBuilder, FetchChannels), + /// Initiate a fetch in response to processing a redirection + FetchRedirect( + RequestBuilder, + ResponseInit, + IpcSender<FetchResponseMsg>, + /* cancel_chan */ Option<IpcReceiver<()>>, + ), + /// Store a cookie for a given originating URL + SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource), + /// Store a set of cookies for a given originating URL + SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource), + /// Retrieve the stored cookies for a given URL + GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource), + /// Get a cookie by name for a given originating URL + GetCookiesDataForUrl( + ServoUrl, + IpcSender<Vec<Serde<Cookie<'static>>>>, + CookieSource, + ), + DeleteCookies(ServoUrl), + /// Get a history state by a given history state id + GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>), + /// Set a history state for a given history state id + SetHistoryState(HistoryStateId, Vec<u8>), + /// Removes history states for the given ids + RemoveHistoryStates(Vec<HistoryStateId>), + /// Synchronization message solely for knowing the state of the ResourceChannelManager loop + Synchronize(IpcSender<()>), + /// Clear the network cache. + ClearCache, + /// Send the service worker network mediator for an origin to CoreResourceThread + NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin), + /// Message forwarded to file manager's handler + ToFileManager(FileManagerThreadMsg), + /// Break the load handler loop, send a reply when done cleaning up local resources + /// and exit + Exit(IpcSender<()>), +} + +/// Instruct the resource thread to make a new request. +pub fn fetch_async<F>(request: RequestBuilder, core_resource_thread: &CoreResourceThread, f: F) +where + F: Fn(FetchResponseMsg) + Send + 'static, +{ + let (action_sender, action_receiver) = ipc::channel().unwrap(); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| f(message.to().unwrap())), + ); + core_resource_thread + .send(CoreResourceMsg::Fetch( + request, + FetchChannels::ResponseMsg(action_sender, None), + )) + .unwrap(); +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ResourceCorsData { + /// CORS Preflight flag + pub preflight: bool, + /// Origin of CORS Request + pub origin: ServoUrl, +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ResourceFetchTiming { + pub domain_lookup_start: u64, + pub timing_check_passed: bool, + pub timing_type: ResourceTimingType, + /// Number of redirects until final resource (currently limited to 20) + pub redirect_count: u16, + pub request_start: u64, + pub secure_connection_start: u64, + pub response_start: u64, + pub fetch_start: u64, + pub response_end: u64, + pub redirect_start: u64, + pub redirect_end: u64, + pub connect_start: u64, + pub connect_end: u64, + pub start_time: u64, +} + +pub enum RedirectStartValue { + #[allow(dead_code)] + Zero, + FetchStart, +} + +pub enum RedirectEndValue { + Zero, + ResponseEnd, +} + +// TODO: refactor existing code to use this enum for setting time attributes +// suggest using this with all time attributes in the future +pub enum ResourceTimeValue { + Zero, + Now, + FetchStart, + RedirectStart, +} + +pub enum ResourceAttribute { + RedirectCount(u16), + DomainLookupStart, + RequestStart, + ResponseStart, + RedirectStart(RedirectStartValue), + RedirectEnd(RedirectEndValue), + FetchStart, + ConnectStart(u64), + ConnectEnd(u64), + SecureConnectionStart, + ResponseEnd, + StartTime(ResourceTimeValue), +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum ResourceTimingType { + Resource, + Navigation, + Error, + None, +} + +impl ResourceFetchTiming { + pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming { + ResourceFetchTiming { + timing_type: timing_type, + timing_check_passed: true, + domain_lookup_start: 0, + redirect_count: 0, + secure_connection_start: 0, + request_start: 0, + response_start: 0, + fetch_start: 0, + redirect_start: 0, + redirect_end: 0, + connect_start: 0, + connect_end: 0, + response_end: 0, + start_time: 0, + } + } + + // TODO currently this is being set with precise time ns when it should be time since + // time origin (as described in Performance::now) + pub fn set_attribute(&mut self, attribute: ResourceAttribute) { + let should_attribute_always_be_updated = match attribute { + ResourceAttribute::FetchStart | + ResourceAttribute::ResponseEnd | + ResourceAttribute::StartTime(_) => true, + _ => false, + }; + if !self.timing_check_passed && !should_attribute_always_be_updated { + return; + } + match attribute { + ResourceAttribute::DomainLookupStart => self.domain_lookup_start = precise_time_ns(), + ResourceAttribute::RedirectCount(count) => self.redirect_count = count, + ResourceAttribute::RequestStart => self.request_start = precise_time_ns(), + ResourceAttribute::ResponseStart => self.response_start = precise_time_ns(), + ResourceAttribute::RedirectStart(val) => match val { + RedirectStartValue::Zero => self.redirect_start = 0, + RedirectStartValue::FetchStart => { + if self.redirect_start == 0 { + self.redirect_start = self.fetch_start + } + }, + }, + ResourceAttribute::RedirectEnd(val) => match val { + RedirectEndValue::Zero => self.redirect_end = 0, + RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end, + }, + ResourceAttribute::FetchStart => self.fetch_start = precise_time_ns(), + ResourceAttribute::ConnectStart(val) => self.connect_start = val, + ResourceAttribute::ConnectEnd(val) => self.connect_end = val, + ResourceAttribute::SecureConnectionStart => { + self.secure_connection_start = precise_time_ns() + }, + ResourceAttribute::ResponseEnd => self.response_end = precise_time_ns(), + ResourceAttribute::StartTime(val) => match val { + ResourceTimeValue::RedirectStart + if self.redirect_start == 0 || !self.timing_check_passed => {}, + _ => self.start_time = self.get_time_value(val), + }, + } + } + + fn get_time_value(&self, time: ResourceTimeValue) -> u64 { + match time { + ResourceTimeValue::Zero => 0, + ResourceTimeValue::Now => precise_time_ns(), + ResourceTimeValue::FetchStart => self.fetch_start, + ResourceTimeValue::RedirectStart => self.redirect_start, + } + } + + pub fn mark_timing_check_failed(&mut self) { + self.timing_check_passed = false; + self.domain_lookup_start = 0; + self.redirect_count = 0; + self.request_start = 0; + self.response_start = 0; + self.redirect_start = 0; + self.connect_start = 0; + self.connect_end = 0; + } +} + +/// Metadata about a loaded resource, such as is obtained from HTTP headers. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct Metadata { + /// Final URL after redirects. + pub final_url: ServoUrl, + + /// Location URL from the response headers. + pub location_url: Option<Result<ServoUrl, String>>, + + #[ignore_malloc_size_of = "Defined in hyper"] + /// MIME type / subtype. + pub content_type: Option<Serde<ContentType>>, + + /// Character set. + pub charset: Option<String>, + + #[ignore_malloc_size_of = "Defined in hyper"] + /// Headers + pub headers: Option<Serde<HeaderMap>>, + + /// HTTP Status + pub status: Option<(u16, Vec<u8>)>, + + /// Is successful HTTPS connection + pub https_state: HttpsState, + + /// Referrer Url + pub referrer: Option<ServoUrl>, + + /// Referrer Policy of the Request used to obtain Response + pub referrer_policy: Option<ReferrerPolicy>, + /// Performance information for navigation events + pub timing: Option<ResourceFetchTiming>, + /// True if the request comes from a redirection + pub redirected: bool, +} + +impl Metadata { + /// Metadata with defaults for everything optional. + pub fn default(url: ServoUrl) -> Self { + Metadata { + final_url: url, + location_url: None, + content_type: None, + charset: None, + headers: None, + // https://fetch.spec.whatwg.org/#concept-response-status-message + status: Some((200, b"".to_vec())), + https_state: HttpsState::None, + referrer: None, + referrer_policy: None, + timing: None, + redirected: false, + } + } + + /// Extract the parts of a Mime that we care about. + pub fn set_content_type(&mut self, content_type: Option<&Mime>) { + if self.headers.is_none() { + self.headers = Some(Serde(HeaderMap::new())); + } + + if let Some(mime) = content_type { + self.headers + .as_mut() + .unwrap() + .typed_insert(ContentType::from(mime.clone())); + if let Some(charset) = mime.get_param(mime::CHARSET) { + self.charset = Some(charset.to_string()); + } + self.content_type = Some(Serde(ContentType::from(mime.clone()))); + } + } + + /// Set the referrer policy associated with the loaded resource. + pub fn set_referrer_policy(&mut self, referrer_policy: Option<ReferrerPolicy>) { + if self.headers.is_none() { + self.headers = Some(Serde(HeaderMap::new())); + } + + self.referrer_policy = referrer_policy; + if let Some(referrer_policy) = referrer_policy { + self.headers + .as_mut() + .unwrap() + .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into()); + } + } +} + +/// The creator of a given cookie +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum CookieSource { + /// An HTTP API + HTTP, + /// A non-HTTP API + NonHTTP, +} + +/// Network errors that have to be exported out of the loaders +#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] +pub enum NetworkError { + /// Could be any of the internal errors, like unsupported scheme, connection errors, etc. + Internal(String), + LoadCancelled, + /// SSL validation error, to be converted to Resource::BadCertHTML in the HTML parser. + SslValidation(String, Vec<u8>), + /// Crash error, to be converted to Resource::Crash in the HTML parser. + Crash(String), +} + +impl NetworkError { + pub fn from_hyper_error(error: &HyperError, certificate: Option<Certificate>) -> Self { + let error_string = error.to_string(); + match certificate { + Some(certificate) => NetworkError::SslValidation(error_string, certificate.0), + _ => NetworkError::Internal(error_string), + } + } + + pub fn from_http_error(error: &HttpError) -> Self { + NetworkError::Internal(error.to_string()) + } +} + +/// Normalize `slice`, as defined by +/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize). +pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] { + const HTTP_WS_BYTES: &'static [u8] = b"\x09\x0A\x0D\x20"; + + loop { + match slice.split_first() { + Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder, + _ => break, + } + } + + loop { + match slice.split_last() { + Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder, + _ => break, + } + } + + slice +} + +pub fn http_percent_encode(bytes: &[u8]) -> String { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS + .add(b' ') + .add(b'"') + .add(b'%') + .add(b'\'') + .add(b'(') + .add(b')') + .add(b'*') + .add(b',') + .add(b'/') + .add(b':') + .add(b';') + .add(b'<') + .add(b'-') + .add(b'>') + .add(b'?') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'{') + .add(b'}'); + + percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string() +} + +#[derive(Deserialize, Serialize)] +pub enum NetToCompositorMsg { + AddImage(ImageKey, ImageDescriptor, ImageData), + GenerateImageKey(IpcSender<ImageKey>), +} + +#[derive(Clone, Deserialize, Serialize)] +pub struct WebrenderIpcSender(IpcSender<NetToCompositorMsg>); + +impl WebrenderIpcSender { + pub fn new(sender: IpcSender<NetToCompositorMsg>) -> Self { + Self(sender) + } + + pub fn generate_image_key(&self) -> ImageKey { + let (sender, receiver) = ipc::channel().unwrap(); + self.0 + .send(NetToCompositorMsg::GenerateImageKey(sender)) + .expect("error sending image key generation"); + receiver.recv().expect("error receiving image key result") + } + + pub fn add_image(&self, key: ImageKey, descriptor: ImageDescriptor, data: ImageData) { + if let Err(e) = self + .0 + .send(NetToCompositorMsg::AddImage(key, descriptor, data)) + { + warn!("Error sending image update: {}", e); + } + } +} + +lazy_static! { + pub static ref PRIVILEGED_SECRET: u32 = servo_rand::ServoRng::new().next_u32(); +} |