Auto merge of #13750 - servo:fetch-blob, r=Manishearth

Implement blob url support in the fetch stack.

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/13750)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-10-14 07:20:05 -05:00 committed by GitHub
commit d692cf1085
11 changed files with 237 additions and 64 deletions

View file

@ -18,12 +18,13 @@ use resource_thread::{send_error, start_sending_sniffed_opt};
use resource_thread::CancellationListener;
use std::boxed::FnBox;
use std::sync::Arc;
use url::Url;
use util::thread::spawn_named;
// TODO: Check on GET
// https://w3c.github.io/FileAPI/#requestResponseModel
pub fn factory<UI: 'static + UIProvider>(filemanager: Arc<FileManager<UI>>)
pub fn factory<UI: 'static + UIProvider>(filemanager: FileManager<UI>)
-> Box<FnBox(LoadData, LoadConsumer, Arc<MimeClassifier>, CancellationListener) + Send> {
box move |load_data: LoadData, start_chan, classifier, cancel_listener| {
spawn_named(format!("blob loader for {}", load_data.url), move || {
@ -35,7 +36,7 @@ pub fn factory<UI: 'static + UIProvider>(filemanager: Arc<FileManager<UI>>)
fn load_blob<UI: 'static + UIProvider>
(load_data: LoadData, start_chan: LoadConsumer,
classifier: Arc<MimeClassifier>,
filemanager: Arc<FileManager<UI>>,
filemanager: FileManager<UI>,
cancel_listener: CancellationListener) {
let (chan, recv) = ipc::channel().unwrap();
if let Ok((id, origin, _fragment)) = parse_blob_url(&load_data.url.clone()) {
@ -119,3 +120,72 @@ fn load_blob<UI: 'static + UIProvider>
send_error(load_data.url.clone(), format_err, start_chan);
}
}
/// https://fetch.spec.whatwg.org/#concept-basic-fetch (partial)
// TODO: make async.
pub fn load_blob_sync<UI: 'static + UIProvider>
(url: Url,
filemanager: FileManager<UI>)
-> Result<(Headers, Vec<u8>), NetworkError> {
let (id, origin) = match parse_blob_url(&url) {
Ok((id, origin, _fragment)) => (id, origin),
Err(()) => {
let e = format!("Invalid blob URL format {:?}", url);
return Err(NetworkError::Internal(e));
}
};
let (sender, receiver) = ipc::channel().unwrap();
let check_url_validity = true;
let msg = FileManagerThreadMsg::ReadFile(sender, id, check_url_validity, origin);
let _ = filemanager.handle(msg, None);
let blob_buf = match receiver.recv().unwrap() {
Ok(ReadFileProgress::Meta(blob_buf)) => blob_buf,
Ok(_) => {
return Err(NetworkError::Internal("Invalid filemanager reply".to_string()));
}
Err(e) => {
return Err(NetworkError::Internal(format!("{:?}", e)));
}
};
let content_type: Mime = blob_buf.type_string.parse().unwrap_or(mime!(Text / Plain));
let charset = content_type.get_param(Attr::Charset);
let mut headers = Headers::new();
if let Some(name) = blob_buf.filename {
let charset = charset.and_then(|c| c.as_str().parse().ok());
headers.set(ContentDisposition {
disposition: DispositionType::Inline,
parameters: vec![
DispositionParam::Filename(charset.unwrap_or(Charset::Us_Ascii),
None, name.as_bytes().to_vec())
]
});
}
// Basic fetch, Step 4.
headers.set(ContentLength(blob_buf.size as u64));
// Basic fetch, Step 5.
headers.set(ContentType(content_type.clone()));
let mut bytes = blob_buf.bytes;
loop {
match receiver.recv().unwrap() {
Ok(ReadFileProgress::Partial(ref mut new_bytes)) => {
bytes.append(new_bytes);
}
Ok(ReadFileProgress::EOF) => {
return Ok((headers, bytes));
}
Ok(_) => {
return Err(NetworkError::Internal("Invalid filemanager reply".to_string()));
}
Err(e) => {
return Err(NetworkError::Internal(format!("{:?}", e)));
}
}
}
}

View file

@ -2,10 +2,12 @@
* 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 blob_loader::load_blob_sync;
use connector::create_http_connector;
use data_loader::decode;
use devtools_traits::DevtoolsControlMsg;
use fetch::cors_cache::CORSCache;
use filemanager_thread::{FileManager, UIProvider};
use http_loader::{HttpState, set_default_accept_encoding, set_request_cookies};
use http_loader::{NetworkHttpRequestFactory, ReadResult, StreamedResponse, obtain_response, read_block};
use http_loader::{auth_from_cache, determine_request_referrer};
@ -50,23 +52,28 @@ enum Data {
Done,
}
pub struct FetchContext {
pub struct FetchContext<UI: 'static + UIProvider> {
pub state: HttpState,
pub user_agent: Cow<'static, str>,
pub devtools_chan: Option<Sender<DevtoolsControlMsg>>,
pub filemanager: FileManager<UI>,
}
type DoneChannel = Option<(Sender<Data>, Receiver<Data>)>;
/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
pub fn fetch(request: Rc<Request>, target: &mut Target, context: FetchContext) -> Response {
pub fn fetch<UI: 'static + UIProvider>(request: Rc<Request>,
target: &mut Target,
context: FetchContext<UI>)
-> Response {
fetch_with_cors_cache(request, &mut CORSCache::new(), target, context)
}
pub fn fetch_with_cors_cache(request: Rc<Request>,
cache: &mut CORSCache,
target: &mut Target,
context: FetchContext) -> Response {
pub fn fetch_with_cors_cache<UI: 'static + UIProvider>(request: Rc<Request>,
cache: &mut CORSCache,
target: &mut Target,
context: FetchContext<UI>)
-> Response {
// Step 1
if request.window.get() == Window::Client {
// TODO: Set window to request's client object if client is a Window object
@ -131,9 +138,14 @@ pub fn fetch_with_cors_cache(request: Rc<Request>,
}
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
fn main_fetch(request: Rc<Request>, cache: &mut CORSCache, cors_flag: bool,
recursive_flag: bool, target: &mut Target, done_chan: &mut DoneChannel,
context: &FetchContext) -> Response {
fn main_fetch<UI: 'static + UIProvider>(request: Rc<Request>,
cache: &mut CORSCache,
cors_flag: bool,
recursive_flag: bool,
target: &mut Target,
done_chan: &mut DoneChannel,
context: &FetchContext<UI>)
-> Response {
// TODO: Implement main fetch spec
// Step 1
@ -389,9 +401,12 @@ fn main_fetch(request: Rc<Request>, cache: &mut CORSCache, cors_flag: bool,
}
/// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
fn basic_fetch(request: Rc<Request>, cache: &mut CORSCache,
target: &mut Target, done_chan: &mut DoneChannel,
context: &FetchContext) -> Response {
fn basic_fetch<UI: 'static + UIProvider>(request: Rc<Request>,
cache: &mut CORSCache,
target: &mut Target,
done_chan: &mut DoneChannel,
context: &FetchContext<UI>)
-> Response {
let url = request.current_url();
match url.scheme() {
@ -450,7 +465,29 @@ fn basic_fetch(request: Rc<Request>, cache: &mut CORSCache,
}
},
"blob" | "ftp" => {
"blob" => {
println!("Loading blob {}", url.as_str());
// Step 2.
if *request.method.borrow() != Method::Get {
return Response::network_error();
}
match load_blob_sync(url.clone(), context.filemanager.clone()) {
Ok((headers, bytes)) => {
let mut response = Response::new();
response.url = Some(url.clone());
response.headers = headers;
*response.body.lock().unwrap() = ResponseBody::Done(bytes);
response
},
Err(e) => {
debug!("Failed to load {}: {:?}", url, e);
Response::network_error()
},
}
},
"ftp" => {
// XXXManishearth handle these
panic!("Unimplemented scheme for Fetch")
},
@ -460,14 +497,15 @@ fn basic_fetch(request: Rc<Request>, cache: &mut CORSCache,
}
/// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch)
fn http_fetch(request: Rc<Request>,
cache: &mut CORSCache,
cors_flag: bool,
cors_preflight_flag: bool,
authentication_fetch_flag: bool,
target: &mut Target,
done_chan: &mut DoneChannel,
context: &FetchContext) -> Response {
fn http_fetch<UI: 'static + UIProvider>(request: Rc<Request>,
cache: &mut CORSCache,
cors_flag: bool,
cors_preflight_flag: bool,
authentication_fetch_flag: bool,
target: &mut Target,
done_chan: &mut DoneChannel,
context: &FetchContext<UI>)
-> Response {
// This is a new async fetch, reset the channel we are waiting on
*done_chan = None;
// Step 1
@ -631,13 +669,14 @@ fn http_fetch(request: Rc<Request>,
}
/// [HTTP redirect fetch](https://fetch.spec.whatwg.org#http-redirect-fetch)
fn http_redirect_fetch(request: Rc<Request>,
cache: &mut CORSCache,
response: Rc<Response>,
cors_flag: bool,
target: &mut Target,
done_chan: &mut DoneChannel,
context: &FetchContext) -> Response {
fn http_redirect_fetch<UI: 'static + UIProvider>(request: Rc<Request>,
cache: &mut CORSCache,
response: Rc<Response>,
cors_flag: bool,
target: &mut Target,
done_chan: &mut DoneChannel,
context: &FetchContext<UI>)
-> Response {
// Step 1
assert_eq!(response.return_internal.get(), true);
@ -711,11 +750,12 @@ fn http_redirect_fetch(request: Rc<Request>,
}
/// [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch)
fn http_network_or_cache_fetch(request: Rc<Request>,
credentials_flag: bool,
authentication_fetch_flag: bool,
done_chan: &mut DoneChannel,
context: &FetchContext) -> Response {
fn http_network_or_cache_fetch<UI: 'static + UIProvider>(request: Rc<Request>,
credentials_flag: bool,
authentication_fetch_flag: bool,
done_chan: &mut DoneChannel,
context: &FetchContext<UI>)
-> Response {
// TODO: Implement Window enum for Request
let request_has_no_window = true;
@ -1108,8 +1148,10 @@ fn http_network_fetch(request: Rc<Request>,
}
/// [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch)
fn cors_preflight_fetch(request: Rc<Request>, cache: &mut CORSCache,
context: &FetchContext) -> Response {
fn cors_preflight_fetch<UI: 'static + UIProvider>(request: Rc<Request>,
cache: &mut CORSCache,
context: &FetchContext<UI>)
-> Response {
// Step 1
let mut preflight = Request::new(request.current_url(), Some(request.origin.borrow().clone()),
request.is_service_worker_global_scope, request.pipeline_id.get());

View file

@ -116,6 +116,15 @@ pub struct FileManager<UI: 'static + UIProvider> {
store: Arc<FileManagerStore<UI>>,
}
// Not derived to avoid an unnecessary `UI: Clone` bound.
impl<UI: 'static + UIProvider> Clone for FileManager<UI> {
fn clone(&self) -> Self {
FileManager {
store: self.store.clone(),
}
}
}
impl<UI: 'static + UIProvider> FileManager<UI> {
pub fn new(ui: &'static UI) -> FileManager<UI> {
FileManager {
@ -139,7 +148,7 @@ impl<UI: 'static + UIProvider> FileManager<UI> {
}
FileManagerThreadMsg::ReadFile(sender, id, check_url_validity, origin) => {
spawn_named("read file".to_owned(), move || {
if let Err(e) = store.try_read_file(sender.clone(), id, check_url_validity,
if let Err(e) = store.try_read_file(&sender, id, check_url_validity,
origin, cancel_listener) {
let _ = sender.send(Err(FileManagerThreadError::BlobURLStoreError(e)));
}
@ -151,24 +160,16 @@ impl<UI: 'static + UIProvider> FileManager<UI> {
})
}
FileManagerThreadMsg::AddSlicedURLEntry(id, rel_pos, sender, origin) =>{
spawn_named("add sliced URL entry".to_owned(), move || {
store.add_sliced_url_entry(id, rel_pos, sender, origin);
})
store.add_sliced_url_entry(id, rel_pos, sender, origin);
}
FileManagerThreadMsg::DecRef(id, origin, sender) => {
spawn_named("dec ref".to_owned(), move || {
let _ = sender.send(store.dec_ref(&id, &origin));
})
let _ = sender.send(store.dec_ref(&id, &origin));
}
FileManagerThreadMsg::RevokeBlobURL(id, origin, sender) => {
spawn_named("revoke blob url".to_owned(), move || {
let _ = sender.send(store.set_blob_url_validity(false, &id, &origin));
})
let _ = sender.send(store.set_blob_url_validity(false, &id, &origin));
}
FileManagerThreadMsg::ActivateBlobURL(id, sender, origin) => {
spawn_named("activate blob url".to_owned(), move || {
let _ = sender.send(store.set_blob_url_validity(true, &id, &origin));
});
let _ = sender.send(store.set_blob_url_validity(true, &id, &origin));
}
}
}
@ -365,7 +366,7 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> {
})
}
fn get_blob_buf(&self, sender: IpcSender<FileManagerResult<ReadFileProgress>>,
fn get_blob_buf(&self, sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
id: &Uuid, origin_in: &FileOrigin, rel_pos: RelativePos,
check_url_validity: bool,
cancel_listener: Option<CancellationListener>) -> Result<(), BlobURLStoreError> {
@ -428,7 +429,7 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> {
}
// Convenient wrapper over get_blob_buf
fn try_read_file(&self, sender: IpcSender<FileManagerResult<ReadFileProgress>>,
fn try_read_file(&self, sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
id: Uuid, check_url_validity: bool, origin_in: FileOrigin,
cancel_listener: Option<CancellationListener>) -> Result<(), BlobURLStoreError> {
self.get_blob_buf(sender, &id, &origin_in, RelativePos::full_range(), check_url_validity, cancel_listener)
@ -541,7 +542,7 @@ fn select_files_pref_enabled() -> bool {
const CHUNK_SIZE: usize = 8192;
fn chunked_read(sender: IpcSender<FileManagerResult<ReadFileProgress>>,
fn chunked_read(sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
file: &mut File, size: usize, opt_filename: Option<String>,
type_string: String, cancel_listener: Option<CancellationListener>) {
// First chunk

View file

@ -475,7 +475,7 @@ pub struct CoreResourceManager {
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
swmanager_chan: Option<IpcSender<CustomResponseMediator>>,
profiler_chan: ProfilerChan,
filemanager: Arc<FileManager<TFDProvider>>,
filemanager: FileManager<TFDProvider>,
cancel_load_map: HashMap<ResourceId, Sender<()>>,
next_resource_id: ResourceId,
}
@ -490,7 +490,7 @@ impl CoreResourceManager {
devtools_chan: devtools_channel,
swmanager_chan: None,
profiler_chan: profiler_chan,
filemanager: Arc::new(FileManager::new(TFD_PROVIDER)),
filemanager: FileManager::new(TFD_PROVIDER),
cancel_load_map: HashMap::new(),
next_resource_id: ResourceId(0),
}
@ -592,6 +592,7 @@ impl CoreResourceManager {
};
let ua = self.user_agent.clone();
let dc = self.devtools_chan.clone();
let filemanager = self.filemanager.clone();
spawn_named(format!("fetch thread for {}", init.url), move || {
let request = Request::from_init(init);
// XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed)
@ -599,7 +600,12 @@ impl CoreResourceManager {
// todo referrer policy?
// todo service worker stuff
let mut target = Some(Box::new(sender) as Box<FetchTaskTarget + Send + 'static>);
let context = FetchContext { state: http_state, user_agent: ua, devtools_chan: dc };
let context = FetchContext {
state: http_state,
user_agent: ua,
devtools_chan: dc,
filemanager: filemanager,
};
fetch(Rc::new(request), &mut target, context);
})
}

View file

@ -366,7 +366,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// Step 11 - abort existing requests
self.terminate_ongoing_fetch();
// TODO(izgzhen): In the WPT test: FileAPI/blob/Blob-XHR-revoke.html,
// FIXME(#13767): In the WPT test: FileAPI/blob/Blob-XHR-revoke.html,
// the xhr.open(url) is expected to hold a reference to the URL,
// thus renders following revocations invalid. Though we won't
// implement this for now, if ever needed, we should check blob

View file

@ -5,6 +5,7 @@
use devtools_traits::DevtoolsControlMsg;
use devtools_traits::HttpRequest as DevtoolsHttpRequest;
use devtools_traits::HttpResponse as DevtoolsHttpResponse;
use filemanager_thread::{TestProvider, TEST_PROVIDER};
use http_loader::{expect_devtools_http_request, expect_devtools_http_response};
use hyper::LanguageTag;
use hyper::header::{Accept, AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowOrigin};
@ -22,6 +23,7 @@ use hyper::uri::RequestUri;
use msg::constellation_msg::{ReferrerPolicy, TEST_PIPELINE_ID};
use net::fetch::cors_cache::CORSCache;
use net::fetch::methods::{FetchContext, fetch, fetch_with_cors_cache};
use net::filemanager_thread::FileManager;
use net::http_loader::HttpState;
use net_traits::FetchTaskTarget;
use net_traits::request::{Origin, RedirectMode, Referrer, Request, RequestMode};
@ -46,11 +48,12 @@ struct FetchResponseCollector {
sender: Sender<Response>,
}
fn new_fetch_context(dc: Option<Sender<DevtoolsControlMsg>>) -> FetchContext {
fn new_fetch_context(dc: Option<Sender<DevtoolsControlMsg>>) -> FetchContext<TestProvider> {
FetchContext {
state: HttpState::new(),
user_agent: DEFAULT_USER_AGENT.into(),
devtools_chan: dc,
filemanager: FileManager::new(TEST_PROVIDER),
}
}
impl FetchTaskTarget for FetchResponseCollector {
@ -164,6 +167,48 @@ fn test_fetch_data() {
}
}
#[test]
fn test_fetch_blob() {
use ipc_channel::ipc;
use net_traits::blob_url_store::BlobBuf;
use net_traits::filemanager_thread::FileManagerThreadMsg;
let context = new_fetch_context(None);
let bytes = b"content";
let blob_buf = BlobBuf {
filename: Some("test.txt".into()),
type_string: "text/plain".into(),
size: bytes.len() as u64,
bytes: bytes.to_vec(),
};
let origin = Url::parse("http://www.example.org/").unwrap();
let (sender, receiver) = ipc::channel().unwrap();
let message = FileManagerThreadMsg::PromoteMemory(blob_buf, true, sender, "http://www.example.org".into());
context.filemanager.handle(message, None);
let id = receiver.recv().unwrap().unwrap();
let url = Url::parse(&format!("blob:{}{}", origin.as_str(), id.simple())).unwrap();
let request = Request::new(url, Some(Origin::Origin(origin.origin())), false, None);
let fetch_response = fetch(Rc::new(request), &mut None, context);
assert!(!fetch_response.is_network_error());
assert_eq!(fetch_response.headers.len(), 2);
let content_type: &ContentType = fetch_response.headers.get().unwrap();
assert_eq!(**content_type, Mime(TopLevel::Text, SubLevel::Plain, vec![]));
let content_length: &ContentLength = fetch_response.headers.get().unwrap();
assert_eq!(**content_length, bytes.len() as u64);
assert_eq!(*fetch_response.body.lock().unwrap(),
ResponseBody::Done(bytes.to_vec()));
}
#[test]
fn test_fetch_file() {
let mut path = resources_dir_path().expect("Cannot find resource dir");

View file

@ -10,9 +10,9 @@ use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
const TEST_PROVIDER: &'static TestProvider = &TestProvider;
pub const TEST_PROVIDER: &'static TestProvider = &TestProvider;
struct TestProvider;
pub struct TestProvider;
impl UIProvider for TestProvider {
fn open_file_dialog(&self, _path: &str, _patterns: Vec<FilterPattern>) -> Option<String> {

View file

@ -1,4 +1,6 @@
[Blob-XHR-revoke.html]
type: testharness
expected: CRASH
bug: https://github.com/servo/servo/issues/10539
bug: https://github.com/servo/servo/issues/13767
[Revoking blob URL used with XMLHttpRequest]
expected: FAIL

View file

@ -1,3 +1,6 @@
[scheme-blob-worker.html]
type: testharness
expected: CRASH
[Fetching [GET\] URL.createObjectURL(blob) is OK]
bug: https://github.com/servo/servo/issues/13766
expected: FAIL

View file

@ -1,3 +1,6 @@
[scheme-blob.html]
type: testharness
expected: CRASH
[Fetching [GET\] URL.createObjectURL(blob) is OK]
bug: https://github.com/servo/servo/issues/13766
expected: FAIL

View file

@ -1,2 +1,3 @@
[uniformOutOfBounds.html]
type: testharness
disabled: https://github.com/servo/servo/issues/13662