diff --git a/components/net/cookie.rs b/components/net/cookie.rs index 0f77a8d6f2c8..f5c021e2b91a 100644 --- a/components/net/cookie.rs +++ b/components/net/cookie.rs @@ -8,6 +8,7 @@ use cookie_rs; use net_traits::CookieSource; use pub_domains::PUB_DOMAINS; +use rustc_serialize::{Encodable, Encoder}; use std::borrow::ToOwned; use std::net::{Ipv4Addr, Ipv6Addr}; use time::{Tm, now, at, Duration}; @@ -175,3 +176,65 @@ impl Cookie { true } } + +impl Encodable for Cookie { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + s.emit_struct("Cookie", 6, |e| { + try!(e.emit_struct_field("cookie", 0, |e| RsCookie(self.cookie.clone()).encode(e))); + try!(e.emit_struct_field("host_only", 1, |e| self.host_only.encode(e))); + try!(e.emit_struct_field("persistent", 2, |e| self.persistent.encode(e))); + try!(e.emit_struct_field("creation_time", 3, |e| Time(self.creation_time).encode(e))); + try!(e.emit_struct_field("last_access", 4, |e| Time(self.last_access).encode(e))); + match self.expiry_time { + Some(time) => try!(e.emit_struct_field("expiry_time", 5, |e| Time(time).encode(e))), + None => {}, + } + Ok(()) + }) + } +} + +struct Time(Tm); + +impl Encodable for Time { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + let Time(time) = *self; + s.emit_struct("Time", 11, |e| { + try!(e.emit_struct_field("tm_sec", 0, |e| time.tm_sec.encode(e))); + try!(e.emit_struct_field("tm_min", 1, |e| time.tm_min.encode(e))); + try!(e.emit_struct_field("tm_hour", 2, |e| time.tm_hour.encode(e))); + try!(e.emit_struct_field("tm_mday", 3, |e| time.tm_mday.encode(e))); + try!(e.emit_struct_field("tm_mon", 4, |e| time.tm_mon.encode(e))); + try!(e.emit_struct_field("tm_year", 5, |e| time.tm_year.encode(e))); + try!(e.emit_struct_field("tm_wday", 6, |e| time.tm_wday.encode(e))); + try!(e.emit_struct_field("tm_yday", 7, |e| time.tm_yday.encode(e))); + try!(e.emit_struct_field("tm_isdst", 8, |e| time.tm_isdst.encode(e))); + try!(e.emit_struct_field("tm_utcoff", 9, |e| time.tm_utcoff.encode(e))); + try!(e.emit_struct_field("tm_nsec", 10, |e| time.tm_nsec.encode(e))); + Ok(()) + }) + } +} + +struct RsCookie(cookie_rs::Cookie); + +impl Encodable for RsCookie { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + let RsCookie(ref rs_cookie) = *self; + s.emit_struct("RsCookie", 9, |e| { + try!(e.emit_struct_field("name", 0, |e| rs_cookie.name.encode(e))); + try!(e.emit_struct_field("value", 1, |e| rs_cookie.value.encode(e))); + match rs_cookie.expires { + Some(time) => try!(e.emit_struct_field("expires", 2, |e| Time(time).encode(e))), + None => {}, + } + try!(e.emit_struct_field("max_age", 3, |e| rs_cookie.max_age.encode(e))); + try!(e.emit_struct_field("domain", 4, |e| rs_cookie.domain.encode(e))); + try!(e.emit_struct_field("path", 5, |e| rs_cookie.path.encode(e))); + try!(e.emit_struct_field("secure", 6, |e| rs_cookie.secure.encode(e))); + try!(e.emit_struct_field("httponly", 7, |e| rs_cookie.httponly.encode(e))); + try!(e.emit_struct_field("custom", 8, |e| rs_cookie.custom.encode(e))); + Ok(()) + }) + } +} diff --git a/components/net/cookie_storage.rs b/components/net/cookie_storage.rs index 4179d3ceda73..e71664f9611a 100644 --- a/components/net/cookie_storage.rs +++ b/components/net/cookie_storage.rs @@ -7,16 +7,20 @@ use cookie::Cookie; use net_traits::CookieSource; +use rustc_serialize::{Encodable, Encoder}; use std::cmp::Ordering; use url::Url; +#[derive(RustcEncodable, Clone)] pub struct CookieStorage { + version: u32, cookies: Vec } impl CookieStorage { pub fn new() -> CookieStorage { CookieStorage { + version: 1, cookies: Vec::new() } } diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index b9caf61446ad..6b5646ae29b9 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -31,10 +31,10 @@ use net_traits::{CookieSource, IncludeSubdomains, LoadConsumer, LoadContext, Loa 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}; +use resource_thread::{CancellationListener, send_error, start_sending_sniffed_opt, AuthCache, AuthCacheEntry}; use std::borrow::ToOwned; use std::boxed::FnBox; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::error::Error; use std::io::{self, Read, Write}; use std::sync::mpsc::Sender; @@ -128,7 +128,7 @@ fn inner_url(url: &Url) -> Url { pub struct HttpState { pub hsts_list: Arc>, pub cookie_jar: Arc>, - pub auth_cache: Arc>>, + pub auth_cache: Arc>, } impl HttpState { @@ -136,7 +136,7 @@ impl HttpState { HttpState { hsts_list: Arc::new(RwLock::new(HstsList::new())), cookie_jar: Arc::new(RwLock::new(CookieStorage::new())), - auth_cache: Arc::new(RwLock::new(HashMap::new())), + auth_cache: Arc::new(RwLock::new(AuthCache::new())), } } } @@ -522,7 +522,7 @@ pub fn modify_request_headers(headers: &mut Headers, url: &Url, user_agent: &str, cookie_jar: &Arc>, - auth_cache: &Arc>>, + auth_cache: &Arc>, load_data: &LoadData) { // Ensure that the host header is set from the original url let host = Host { @@ -555,13 +555,13 @@ pub fn modify_request_headers(headers: &mut Headers, fn set_auth_header(headers: &mut Headers, url: &Url, - auth_cache: &Arc>>) { + auth_cache: &Arc>) { if !headers.has::>() { if let Some(auth) = auth_from_url(url) { headers.set(auth); } else { - if let Some(ref auth_entry) = auth_cache.read().unwrap().get(url) { + if let Some(ref auth_entry) = auth_cache.read().unwrap().entries.get(url) { auth_from_entry(&auth_entry, headers); } } @@ -820,7 +820,7 @@ pub fn load(load_data: &LoadData, password: auth_header.password.to_owned().unwrap(), }; - http_state.auth_cache.write().unwrap().insert(doc_url.clone(), auth_entry); + http_state.auth_cache.write().unwrap().entries.insert(doc_url.clone(), auth_entry); } } diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 5d51b48206d3..ba1f2bb90325 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! A thread that takes a URL and streams back the binary data. - use about_loader; use chrome_loader; use cookie; @@ -23,13 +22,20 @@ 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::{NetworkError, WebSocketCommunicate, WebSocketConnectData}; +use rustc_serialize::Encodable; +use rustc_serialize::json; use std::borrow::ToOwned; use std::boxed::FnBox; use std::cell::Cell; use std::collections::HashMap; +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; use std::sync::mpsc::{Receiver, Sender, channel}; use std::sync::{Arc, RwLock}; use url::Url; +use util::opts; use util::prefs; use util::thread::spawn_named; use websocket_loader; @@ -190,12 +196,54 @@ impl ResourceChannelManager { ControlMsg::Synchronize(sender) => { let _ = sender.send(()); } - ControlMsg::Exit => break, + ControlMsg::Exit => { + if let Some(ref profile_dir) = opts::get().profile_dir { + match self.resource_manager.auth_cache.read() { + Ok(auth_cache) => write_json_to_file(&*auth_cache, profile_dir, "auth_cache.json"), + Err(_) => warn!("Error writing auth cache to disk"), + } + match self.resource_manager.cookie_jar.read() { + Ok(jar) => write_json_to_file(&*jar, profile_dir, "cookie_jar.json"), + Err(_) => warn!("Error writing cookie jar to disk"), + } + match self.resource_manager.hsts_list.read() { + Ok(hsts) => write_json_to_file(&*hsts, profile_dir, "hsts_list.json"), + Err(_) => warn!("Error writing hsts list to disk"), + } + } + break; + } + } } } } +pub fn write_json_to_file(data: &T, profile_dir: &str, filename: &str) { + let json_encoded: String; + match json::encode(&data) { + Ok(d) => json_encoded = d, + Err(_) => return, + } + let path = Path::new(profile_dir).join(filename); + let display = path.display(); + + let mut file = match File::create(&path) { + Err(why) => panic!("couldn't create {}: {}", + display, + Error::description(&why)), + Ok(file) => file, + }; + + match file.write_all(json_encoded.as_bytes()) { + Err(why) => { + panic!("couldn't write to {}: {}", display, + Error::description(&why)) + }, + Ok(_) => println!("successfully wrote to {}", display), + } +} + /// The optional resources required by the `CancellationListener` pub struct CancellableResource { /// The receiver which receives a message on load cancellation @@ -259,15 +307,32 @@ impl Drop for CancellationListener { } } +#[derive(RustcDecodable, RustcEncodable, Clone)] pub struct AuthCacheEntry { pub user_name: String, pub password: String, } +impl AuthCache { + + pub fn new() -> AuthCache { + AuthCache { + version: 1, + entries: HashMap::new() + } + } +} + +#[derive(RustcDecodable, RustcEncodable, Clone)] +pub struct AuthCache { + pub version: u32, + pub entries: HashMap, +} + pub struct ResourceManager { user_agent: String, cookie_jar: Arc>, - auth_cache: Arc>>, + auth_cache: Arc>, mime_classifier: Arc, devtools_chan: Option>, hsts_list: Arc>, @@ -283,7 +348,7 @@ impl ResourceManager { ResourceManager { user_agent: user_agent, cookie_jar: Arc::new(RwLock::new(CookieStorage::new())), - auth_cache: Arc::new(RwLock::new(HashMap::new())), + auth_cache: Arc::new(RwLock::new(AuthCache::new())), mime_classifier: Arc::new(MIMEClassifier::new()), devtools_chan: devtools_channel, hsts_list: Arc::new(RwLock::new(hsts_list)), diff --git a/components/net/storage_thread.rs b/components/net/storage_thread.rs index 746cce7963cd..6c2ca89ee6cf 100644 --- a/components/net/storage_thread.rs +++ b/components/net/storage_thread.rs @@ -4,10 +4,12 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use net_traits::storage_thread::{StorageThread, StorageThreadMsg, StorageType}; +use resource_thread; use std::borrow::ToOwned; use std::collections::BTreeMap; use std::collections::HashMap; use url::Url; +use util::opts; use util::thread::spawn_named; const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024; @@ -69,6 +71,9 @@ impl StorageManager { self.clear(sender, url, storage_type) } StorageThreadMsg::Exit => { + if let Some(ref profile_dir) = opts::get().profile_dir { + resource_thread::write_json_to_file(&self.local_data, profile_dir, "local_data.json"); + } break } } diff --git a/tests/unit/net/http_loader.rs b/tests/unit/net/http_loader.rs index 06ce33abcc33..833787a40072 100644 --- a/tests/unit/net/http_loader.rs +++ b/tests/unit/net/http_loader.rs @@ -1382,7 +1382,7 @@ fn test_if_auth_creds_not_in_url_but_in_cache_it_sets_it() { password: "test".to_owned(), }; - http_state.auth_cache.write().unwrap().insert(url.clone(), auth_entry); + http_state.auth_cache.write().unwrap().entries.insert(url.clone(), auth_entry); let mut load_data = LoadData::new(LoadContext::Browsing, url, None); load_data.credentials_flag = true;