aboutsummaryrefslogtreecommitdiffstats
path: root/components/net/fetch
diff options
context:
space:
mode:
Diffstat (limited to 'components/net/fetch')
-rw-r--r--components/net/fetch/cors_cache.rs316
-rw-r--r--components/net/fetch/request.rs149
-rw-r--r--components/net/fetch/response.rs144
3 files changed, 609 insertions, 0 deletions
diff --git a/components/net/fetch/cors_cache.rs b/components/net/fetch/cors_cache.rs
new file mode 100644
index 00000000000..fb6676e8064
--- /dev/null
+++ b/components/net/fetch/cors_cache.rs
@@ -0,0 +1,316 @@
+/* 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/. */
+
+//! An implementation of the [CORS preflight cache](http://fetch.spec.whatwg.org/#cors-preflight-cache)
+//! For now this library is XHR-specific.
+//! For stuff involving `<img>`, `<iframe>`, `<form>`, etc please check what
+//! the request mode should be and compare with the fetch spec
+//! This library will eventually become the core of the Fetch crate
+//! with CORSRequest being expanded into FetchRequest (etc)
+
+use http::method::Method;
+use std::ascii::StrAsciiExt;
+use std::comm::{Sender, Receiver, channel};
+use time;
+use time::{now, Timespec};
+use url::Url;
+
+/// Union type for CORS cache entries
+///
+/// Each entry might pertain to a header or method
+#[deriving(Clone)]
+pub enum HeaderOrMethod {
+ HeaderData(String),
+ MethodData(Method)
+}
+
+impl HeaderOrMethod {
+ fn match_header(&self, header_name: &str) -> bool {
+ match *self {
+ HeaderData(ref s) => s.as_slice().eq_ignore_ascii_case(header_name),
+ _ => false
+ }
+ }
+
+ fn match_method(&self, method: &Method) -> bool {
+ match *self {
+ MethodData(ref m) => m == method,
+ _ => false
+ }
+ }
+}
+
+/// An entry in the CORS cache
+#[deriving(Clone)]
+pub struct CORSCacheEntry {
+ pub origin: Url,
+ pub url: Url,
+ pub max_age: uint,
+ pub credentials: bool,
+ pub header_or_method: HeaderOrMethod,
+ created: Timespec
+}
+
+impl CORSCacheEntry {
+ fn new (origin:Url, url: Url, max_age: uint, credentials: bool, header_or_method: HeaderOrMethod) -> CORSCacheEntry {
+ CORSCacheEntry {
+ origin: origin,
+ url: url,
+ max_age: max_age,
+ credentials: credentials,
+ header_or_method: header_or_method,
+ created: time::now().to_timespec()
+ }
+ }
+}
+
+/// Properties of Request required to cache match.
+pub struct CacheRequestDetails {
+ pub origin: Url,
+ pub destination: Url,
+ pub credentials: bool
+}
+
+/// Trait for a generic CORS Cache
+pub trait CORSCache {
+ /// [Clear the cache](http://fetch.spec.whatwg.org/#concept-cache-clear)
+ fn clear (&mut self, request: CacheRequestDetails);
+
+ /// Remove old entries
+ fn cleanup(&mut self);
+
+ /// Returns true if an entry with a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found
+ fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool;
+
+ /// Updates max age if an entry for a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
+ ///
+ /// If not, it will insert an equivalent entry
+ fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool;
+
+ /// Returns true if an entry with a [matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found
+ fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool;
+
+ /// Updates max age if an entry for [a matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
+ ///
+ /// If not, it will insert an equivalent entry
+ fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool;
+ /// Insert an entry
+ fn insert(&mut self, entry: CORSCacheEntry);
+}
+
+/// A simple, vector-based CORS Cache
+#[deriving(Clone)]
+#[unstable = "This might later be replaced with a HashMap-like entity, though that requires a separate Origin struct"]
+pub struct BasicCORSCache(Vec<CORSCacheEntry>);
+
+impl BasicCORSCache {
+ fn find_entry_by_header<'a>(&'a mut self, request: &CacheRequestDetails, header_name: &str) -> Option<&'a mut CORSCacheEntry> {
+ self.cleanup();
+ let BasicCORSCache(ref mut buf) = *self;
+ let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
+ e.origin.host() == request.origin.host() &&
+ e.origin.port() == request.origin.port() &&
+ e.url == request.destination &&
+ e.credentials == request.credentials &&
+ e.header_or_method.match_header(header_name));
+ entry
+ }
+
+ fn find_entry_by_method<'a>(&'a mut self, request: &CacheRequestDetails, method: Method) -> Option<&'a mut CORSCacheEntry> {
+ // we can take the method from CORSRequest itself
+ self.cleanup();
+ let BasicCORSCache(ref mut buf) = *self;
+ let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
+ e.origin.host() == request.origin.host() &&
+ e.origin.port() == request.origin.port() &&
+ e.url == request.destination &&
+ e.credentials == request.credentials &&
+ e.header_or_method.match_method(&method));
+ entry
+ }
+}
+
+impl CORSCache for BasicCORSCache {
+ /// http://fetch.spec.whatwg.org/#concept-cache-clear
+ #[allow(dead_code)]
+ fn clear (&mut self, request: CacheRequestDetails) {
+ let BasicCORSCache(buf) = self.clone();
+ let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect();
+ *self = BasicCORSCache(new_buf);
+ }
+
+ // Remove old entries
+ fn cleanup(&mut self) {
+ let BasicCORSCache(buf) = self.clone();
+ let now = time::now().to_timespec();
+ let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| now.sec > e.created.sec + e.max_age as i64).collect();
+ *self = BasicCORSCache(new_buf);
+ }
+
+ fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
+ self.find_entry_by_header(&request, header_name).is_some()
+ }
+
+ fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
+ match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) {
+ Some(_) => true,
+ None => {
+ self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
+ request.credentials, HeaderData(header_name.to_string())));
+ false
+ }
+ }
+ }
+
+ fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
+ self.find_entry_by_method(&request, method).is_some()
+ }
+
+ fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
+ match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) {
+ Some(_) => true,
+ None => {
+ self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
+ request.credentials, MethodData(method)));
+ false
+ }
+ }
+ }
+
+ fn insert(&mut self, entry: CORSCacheEntry) {
+ self.cleanup();
+ let BasicCORSCache(ref mut buf) = *self;
+ buf.push(entry);
+ }
+}
+
+/// Various messages that can be sent to a CORSCacheTask
+pub enum CORSCacheTaskMsg {
+ Clear(CacheRequestDetails, Sender<()>),
+ Cleanup(Sender<()>),
+ MatchHeader(CacheRequestDetails, String, Sender<bool>),
+ MatchHeaderUpdate(CacheRequestDetails, String, uint, Sender<bool>),
+ MatchMethod(CacheRequestDetails, Method, Sender<bool>),
+ MatchMethodUpdate(CacheRequestDetails, Method, uint, Sender<bool>),
+ Insert(CORSCacheEntry, Sender<()>),
+ ExitMsg
+}
+
+/// A Sender to a CORSCacheTask
+///
+/// This can be used as a CORS Cache.
+/// The methods on this type block until they can run, and it behaves similar to a mutex
+pub type CORSCacheSender = Sender<CORSCacheTaskMsg>;
+
+impl CORSCache for CORSCacheSender {
+ fn clear (&mut self, request: CacheRequestDetails) {
+ let (tx, rx) = channel();
+ self.send(Clear(request, tx));
+ let _ = rx.recv_opt();
+ }
+
+ fn cleanup(&mut self) {
+ let (tx, rx) = channel();
+ self.send(Cleanup(tx));
+ let _ = rx.recv_opt();
+ }
+
+ fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchHeader(request, header_name.to_string(), tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchHeaderUpdate(request, header_name.to_string(), new_max_age, tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchMethod(request, method, tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchMethodUpdate(request, method, new_max_age, tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn insert(&mut self, entry: CORSCacheEntry) {
+ let (tx, rx) = channel();
+ self.send(Insert(entry, tx));
+ let _ = rx.recv_opt();
+ }
+}
+
+/// A simple task-based CORS Cache that can be sent messages
+///
+/// #Example
+/// ```
+/// let task = CORSCacheTask::new();
+/// let builder = TaskBuilder::new().named("XHRTask");
+/// let mut sender = task.get_sender();
+/// builder.spawn(proc() { task.run() });
+/// sender.insert(CORSCacheEntry::new(/* parameters here */));
+/// ```
+pub struct CORSCacheTask {
+ receiver: Receiver<CORSCacheTaskMsg>,
+ cache: BasicCORSCache,
+ sender: CORSCacheSender
+}
+
+impl CORSCacheTask {
+ pub fn new() -> CORSCacheTask {
+ let (tx, rx) = channel();
+ CORSCacheTask {
+ receiver: rx,
+ cache: BasicCORSCache(vec![]),
+ sender: tx
+ }
+ }
+
+ /// Provides a sender to the cache task
+ pub fn get_sender(&self) -> CORSCacheSender {
+ self.sender.clone()
+ }
+
+ /// Runs the cache task
+ /// This blocks the current task, so it is advised
+ /// to spawn a new task for this
+ /// Send ExitMsg to the associated Sender to exit
+ pub fn run(&mut self) {
+ loop {
+ match self.receiver.recv() {
+ Clear(request, tx) => {
+ self.cache.clear(request);
+ tx.send(());
+ },
+ Cleanup(tx) => {
+ self.cache.cleanup();
+ tx.send(());
+ },
+ MatchHeader(request, header, tx) => {
+ tx.send(self.cache.match_header(request, header.as_slice()));
+ },
+ MatchHeaderUpdate(request, header, new_max_age, tx) => {
+ tx.send(self.cache.match_header_and_update(request, header.as_slice(), new_max_age));
+ },
+ MatchMethod(request, method, tx) => {
+ tx.send(self.cache.match_method(request, method));
+ },
+ MatchMethodUpdate(request, method, new_max_age, tx) => {
+ tx.send(self.cache.match_method_and_update(request, method, new_max_age));
+ },
+ Insert(entry, tx) => {
+ self.cache.insert(entry);
+ tx.send(());
+ },
+ ExitMsg => break
+ }
+ }
+ }
+}
diff --git a/components/net/fetch/request.rs b/components/net/fetch/request.rs
new file mode 100644
index 00000000000..c14efe9c59e
--- /dev/null
+++ b/components/net/fetch/request.rs
@@ -0,0 +1,149 @@
+/* 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 url::Url;
+use http::method::{Get, Method};
+use http::headers::request::HeaderCollection;
+use fetch::cors_cache::CORSCache;
+use fetch::response::Response;
+
+/// A [request context](http://fetch.spec.whatwg.org/#concept-request-context)
+pub enum Context {
+ Audio, Beacon, CSPreport, Download, Embed, Eventsource,
+ Favicon, Fetch, Font, Form, Frame, Hyperlink, IFrame, Image,
+ ImageSet, Import, Internal, Location, Manifest, Object, Ping,
+ Plugin, Prefetch, Script, ServiceWorker, SharedWorker, Subresource,
+ Style, Track, Video, Worker, XMLHttpRequest, XSLT
+}
+
+/// A [request context frame type](http://fetch.spec.whatwg.org/#concept-request-context-frame-type)
+pub enum ContextFrameType {
+ Auxiliary,
+ TopLevel,
+ Nested,
+ ContextNone
+}
+
+/// A [referer](http://fetch.spec.whatwg.org/#concept-request-referrer)
+pub enum Referer {
+ RefererNone,
+ Client,
+ RefererUrl(Url)
+}
+
+/// A [request mode](http://fetch.spec.whatwg.org/#concept-request-mode)
+pub enum RequestMode {
+ SameOrigin,
+ NoCORS,
+ CORSMode,
+ ForcedPreflightMode
+}
+
+/// Request [credentials mode](http://fetch.spec.whatwg.org/#concept-request-credentials-mode)
+pub enum CredentialsMode {
+ Omit,
+ CredentialsSameOrigin,
+ Include
+}
+
+/// [Response tainting](http://fetch.spec.whatwg.org/#concept-request-response-tainting)
+pub enum ResponseTainting {
+ Basic,
+ CORSTainting,
+ Opaque
+}
+
+/// A [Request](http://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
+pub struct Request {
+ pub method: Method,
+ pub url: Url,
+ pub headers: HeaderCollection,
+ pub unsafe_request: bool,
+ pub body: Option<Vec<u8>>,
+ pub preserve_content_codings: bool,
+ // pub client: GlobalRef, // XXXManishearth copy over only the relevant fields of the global scope,
+ // not the entire scope to avoid the libscript dependency
+ pub skip_service_worker: bool,
+ pub context: Context,
+ pub context_frame_type: ContextFrameType,
+ pub origin: Option<Url>,
+ pub force_origin_header: bool,
+ pub same_origin_data: bool,
+ pub referer: Referer,
+ pub authentication: bool,
+ pub sync: bool,
+ pub mode: RequestMode,
+ pub credentials_mode: CredentialsMode,
+ pub use_url_credentials: bool,
+ pub manual_redirect: bool,
+ pub redirect_count: uint,
+ pub response_tainting: ResponseTainting,
+ pub cache: Option<Box<CORSCache>>
+}
+
+impl Request {
+ pub fn new(url: Url, context: Context) -> Request {
+ Request {
+ method: Get,
+ url: url,
+ headers: HeaderCollection::new(),
+ unsafe_request: false,
+ body: None,
+ preserve_content_codings: false,
+ skip_service_worker: false,
+ context: context,
+ context_frame_type: ContextNone,
+ origin: None,
+ force_origin_header: false,
+ same_origin_data: false,
+ referer: Client,
+ authentication: false,
+ sync: false,
+ mode: NoCORS,
+ credentials_mode: Omit,
+ use_url_credentials: false,
+ manual_redirect: false,
+ redirect_count: 0,
+ response_tainting: Basic,
+ cache: None
+ }
+ }
+
+ /// [Basic fetch](http://fetch.spec.whatwg.org#basic-fetch)
+ pub fn basic_fetch(&mut self) -> Response {
+ match self.url.scheme.as_slice() {
+ "about" => match self.url.non_relative_scheme_data() {
+ Some(s) if s.as_slice() == "blank" => {
+ let mut response = Response::new();
+ let _ = response.headers.insert_raw("Content-Type".to_string(), b"text/html;charset=utf-8");
+ response
+ },
+ _ => Response::network_error()
+ },
+ "http" | "https" => {
+ self.http_fetch(false, false, false)
+ },
+ "blob" | "data" | "file" | "ftp" => {
+ // XXXManishearth handle these
+ fail!("Unimplemented scheme for Fetch")
+ },
+
+ _ => Response::network_error()
+ }
+ }
+
+ // [HTTP fetch](http://fetch.spec.whatwg.org#http-fetch)
+ pub fn http_fetch(&mut self, _cors_flag: bool, cors_preflight_flag: bool, _authentication_fetch_flag: bool) -> Response {
+ let response = Response::new();
+ // TODO: Service worker fetch
+ // Step 3
+ // Substep 1
+ self.skip_service_worker = true;
+ // Substep 2
+ if cors_preflight_flag {
+ // XXXManishearth stuff goes here
+ }
+ response
+ }
+}
diff --git a/components/net/fetch/response.rs b/components/net/fetch/response.rs
new file mode 100644
index 00000000000..359ec6aa394
--- /dev/null
+++ b/components/net/fetch/response.rs
@@ -0,0 +1,144 @@
+/* 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 url::Url;
+use http::status::{Status, UnregisteredStatus};
+use StatusOk = http::status::Ok;
+use http::headers::HeaderEnum;
+use http::headers::response::HeaderCollection;
+use std::ascii::OwnedStrAsciiExt;
+use std::comm::Receiver;
+
+/// [Response type](http://fetch.spec.whatwg.org/#concept-response-type)
+#[deriving(Clone, PartialEq)]
+pub enum ResponseType {
+ Basic,
+ CORS,
+ Default,
+ Error,
+ Opaque
+}
+
+/// [Response termination reason](http://fetch.spec.whatwg.org/#concept-response-termination-reason)
+#[deriving(Clone)]
+pub enum TerminationReason {
+ EndUserAbort,
+ Fatal,
+ Timeout
+}
+
+/// The response body can still be pushed to after fetch
+/// This provides a way to store unfinished response bodies
+#[unstable = "I haven't yet decided exactly how the interface for this will be"]
+#[deriving(Clone)]
+pub enum ResponseBody {
+ Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
+ Receiving(Vec<u8>),
+ Done(Vec<u8>),
+}
+
+#[unstable = "I haven't yet decided exactly how the interface for this will be"]
+pub enum ResponseMsg {
+ Chunk(Vec<u8>),
+ Finished,
+ Errored
+}
+
+#[unstable = "I haven't yet decided exactly how the interface for this will be"]
+pub struct ResponseLoader {
+ response: Response,
+ chan: Receiver<ResponseMsg>
+}
+
+/// A [Response](http://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
+#[deriving(Clone)]
+pub struct Response {
+ pub response_type: ResponseType,
+ pub termination_reason: Option<TerminationReason>,
+ pub url: Option<Url>,
+ pub status: Status,
+ pub headers: HeaderCollection,
+ pub body: ResponseBody,
+ /// [Internal response](http://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response is a filtered response
+ pub internal_response: Option<Box<Response>>,
+}
+
+impl Response {
+ pub fn new() -> Response {
+ Response {
+ response_type: Default,
+ termination_reason: None,
+ url: None,
+ status: StatusOk,
+ headers: HeaderCollection::new(),
+ body: Empty,
+ internal_response: None
+ }
+ }
+
+ pub fn network_error() -> Response {
+ Response {
+ response_type: Error,
+ termination_reason: None,
+ url: None,
+ status: UnregisteredStatus(0, "".to_string()),
+ headers: HeaderCollection::new(),
+ body: Empty,
+ internal_response: None
+ }
+ }
+
+ pub fn is_network_error(&self) -> bool {
+ match self.response_type {
+ Error => true,
+ _ => false
+ }
+ }
+
+ /// Convert to a filtered response, of type `filter_type`.
+ /// Do not use with type Error or Default
+ pub fn to_filtered(self, filter_type: ResponseType) -> Response {
+ assert!(filter_type != Error);
+ assert!(filter_type != Default);
+ if self.is_network_error() {
+ return self;
+ }
+ let old_headers = self.headers.clone();
+ let mut response = self.clone();
+ response.internal_response = Some(box self);
+ match filter_type {
+ Default | Error => unreachable!(),
+ Basic => {
+ let mut headers = HeaderCollection::new();
+ for h in old_headers.iter() {
+ match h.header_name().into_ascii_lower().as_slice() {
+ "set-cookie" | "set-cookie2" => {},
+ _ => headers.insert(h)
+ }
+ }
+ response.headers = headers;
+ response.response_type = filter_type;
+ },
+ CORS => {
+ let mut headers = HeaderCollection::new();
+ for h in old_headers.iter() {
+ match h.header_name().into_ascii_lower().as_slice() {
+ "cache-control" | "content-language" |
+ "content-type" | "expires" | "last-modified" | "Pragma" => {},
+ // XXXManishearth handle Access-Control-Expose-Headers
+ _ => headers.insert(h)
+ }
+ }
+ response.headers = headers;
+ response.response_type = filter_type;
+ },
+ Opaque => {
+ response.headers = HeaderCollection::new();
+ response.status = UnregisteredStatus(0, "".to_string());
+ response.body = Empty;
+ }
+ }
+ response
+ }
+}