Skip to content

Commit

Permalink
Set response status for range requests to file and blob urls
Browse files Browse the repository at this point in the history
  • Loading branch information
ferjm committed Nov 26, 2018
1 parent b96e568 commit 79d27cb
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 37 deletions.
70 changes: 59 additions & 11 deletions components/net/fetch/methods.rs
Expand Up @@ -18,7 +18,7 @@ use hyper::StatusCode;
use ipc_channel::ipc::IpcReceiver;
use mime::{self, Mime};
use mime_guess::guess_mime_type;
use net_traits::blob_url_store::parse_blob_url;
use net_traits::blob_url_store::{parse_blob_url, BlobURLStoreError};
use net_traits::filemanager_thread::RelativePos;
use net_traits::request::{CredentialsMode, Destination, Referrer, Request, RequestMode};
use net_traits::request::{Origin, ResponseTainting, Window};
Expand Down Expand Up @@ -500,17 +500,24 @@ pub enum RangeRequestBounds {
}

impl RangeRequestBounds {
pub fn get_final(&self, len: Option<u64>) -> RelativePos {
pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, ()> {
match self {
RangeRequestBounds::Final(pos) => pos.clone(),
RangeRequestBounds::Pending(offset) => RelativePos::from_opts(
RangeRequestBounds::Final(pos) => {
if let Some(len) = len {
if pos.start <= len as i64 {
return Ok(pos.clone());
}
}
Err(())
},
RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
if let Some(len) = len {
Some((len - u64::min(len, *offset)) as i64)
} else {
Some(0)
},
None,
),
)),
}
}
}
Expand Down Expand Up @@ -539,6 +546,18 @@ fn get_range_request_bounds(range: Option<Range>) -> RangeRequestBounds {
}
}

fn partial_content(response: &mut Response) {
let reason = "Partial Content".to_owned();
response.status = Some((StatusCode::PARTIAL_CONTENT, reason.clone()));
response.raw_status = Some((StatusCode::PARTIAL_CONTENT.as_u16(), reason.into()));
}

fn range_not_satisfiable_error(response: &mut Response) {
let reason = "Range Not Satisfiable".to_owned();
response.status = Some((StatusCode::RANGE_NOT_SATISFIABLE, reason.clone()));
response.raw_status = Some((StatusCode::RANGE_NOT_SATISFIABLE.as_u16(), reason.into()));
}

/// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch)
fn scheme_fetch(
request: &mut Request,
Expand Down Expand Up @@ -584,23 +603,37 @@ fn scheme_fetch(
}
if let Ok(file_path) = url.to_file_path() {
if let Ok(file) = File::open(file_path.clone()) {
let mut response = Response::new(url, ResourceFetchTiming::new(request.timing_type()));

// Get range bounds (if any) and try to seek to the requested offset.
// If seeking fails, bail out with a NetworkError.
let file_size = match file.metadata() {
Ok(metadata) => Some(metadata.len()),
Err(_) => None,
};
let range = get_range_request_bounds(request.headers.typed_get::<Range>());
let range = range.get_final(file_size);

let mut response = Response::new(url, ResourceFetchTiming::new(request.timing_type()));

let range_header = request.headers.typed_get::<Range>();
let is_range_request = range_header.is_some();
let range = match get_range_request_bounds(range_header).get_final(file_size) {
Ok(range) => range,
Err(_) => {
range_not_satisfiable_error(&mut response);
return response;
},
};
let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
return Response::network_error(NetworkError::Internal(
"Unexpected method for file".into(),
));
}

// Set response status to 206 if Range header is present.
// At this point we should have already validated the header.
if is_range_request {
partial_content(&mut response);
}

// Set Content-Type header.
let mime = guess_mime_type(file_path);
response.headers.typed_insert(ContentType::from(mime));
Expand Down Expand Up @@ -631,15 +664,19 @@ fn scheme_fetch(
},

"blob" => {
println!("Loading blob {}", url.as_str());
debug!("Loading blob {}", url.as_str());
// Step 2.
if request.method != Method::GET {
return Response::network_error(NetworkError::Internal(
"Unexpected method for blob".into(),
));
}

let range = get_range_request_bounds(request.headers.typed_get::<Range>());
let range_header = request.headers.typed_get::<Range>();
let is_range_request = range_header.is_some();
// We will get a final version of this range once we have
// the length of the data backing the blob.
let range = get_range_request_bounds(range_header);

let (id, origin) = match parse_blob_url(&url) {
Ok((id, origin)) => (id, origin),
Expand All @@ -651,6 +688,10 @@ fn scheme_fetch(
};

let mut response = Response::new(url);
if is_range_request {
partial_content(&mut response);
}

let (done_sender, done_receiver) = channel();
*done_chan = Some((done_sender.clone(), done_receiver));
*response.body.lock().unwrap() = ResponseBody::Receiving(vec![]);
Expand All @@ -665,6 +706,13 @@ fn scheme_fetch(
range,
) {
let _ = done_sender.send(Data::Done);
let err = match err {
BlobURLStoreError::InvalidRange => {
range_not_satisfiable_error(&mut response);
return response;
},
_ => format!("{:?}", err),
};
return Response::network_error(NetworkError::Internal(err));
};

Expand Down
42 changes: 27 additions & 15 deletions components/net/filemanager_thread.rs
Expand Up @@ -114,18 +114,16 @@ impl FileManager {
origin: FileOrigin,
response: &mut Response,
range: RangeRequestBounds,
) -> Result<(), String> {
self.store
.fetch_blob_buf(
done_sender,
cancellation_listener,
&id,
&origin,
range,
check_url_validity,
response,
)
.map_err(|e| format!("{:?}", e))
) -> Result<(), BlobURLStoreError> {
self.store.fetch_blob_buf(
done_sender,
cancellation_listener,
&id,
&origin,
range,
check_url_validity,
response,
)
}

pub fn promote_memory(
Expand Down Expand Up @@ -539,7 +537,13 @@ impl FileManagerStore {
let file_impl = self.get_impl(id, origin_in, check_url_validity)?;
match file_impl {
FileImpl::Memory(buf) => {
let range = range.get_final(Some(buf.size));
let range = match range.get_final(Some(buf.size)) {
Ok(range) => range,
Err(_) => {
return Err(BlobURLStoreError::InvalidRange);
},
};

let range = range.to_abs_range(buf.size as usize);
let len = range.len() as u64;

Expand Down Expand Up @@ -568,7 +572,13 @@ impl FileManagerStore {
let file = File::open(&metadata.path)
.map_err(|e| BlobURLStoreError::External(e.to_string()))?;

let range = range.get_final(Some(metadata.size));
let range = match range.get_final(Some(metadata.size)) {
Ok(range) => range,
Err(_) => {
return Err(BlobURLStoreError::InvalidRange);
},
};

let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
return Err(BlobURLStoreError::External(
Expand Down Expand Up @@ -607,7 +617,9 @@ impl FileManagerStore {
cancellation_listener,
&parent_id,
origin_in,
RangeRequestBounds::Final(range.get_final(None).slice_inner(&inner_rel_pos)),
RangeRequestBounds::Final(
RelativePos::full_range().slice_inner(&inner_rel_pos),
),
false,
response,
);
Expand Down
4 changes: 3 additions & 1 deletion components/net_traits/blob_url_store.rs
Expand Up @@ -9,14 +9,16 @@ use url::Url;
use uuid::Uuid;

/// Errors returned to Blob URL Store request
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum BlobURLStoreError {
/// Invalid File UUID
InvalidFileID,
/// Invalid URL origin
InvalidOrigin,
/// Invalid entry content
InvalidEntry,
/// Invalid range
InvalidRange,
/// External error, from like file system, I/O etc.
External(String),
}
Expand Down
2 changes: 1 addition & 1 deletion tests/wpt/mozilla/meta/MANIFEST.json
Expand Up @@ -27246,7 +27246,7 @@
"testharness"
],
"mozilla/range_request_blob_url.html": [
"b41f0b5382e3994db35f38b2c358a41b75551fe2",
"075397620e989dafc814c0ed2bca46bd476bccf6",
"testharness"
],
"mozilla/range_request_file_url.html": [
Expand Down
15 changes: 6 additions & 9 deletions tests/wpt/mozilla/tests/mozilla/range_request_blob_url.html
Expand Up @@ -23,12 +23,10 @@
range: "bytes=0-100",
status: 206,
expected: "abcdefghijklmnopqrstuvwxyz"
/*}, {
XXX(ferjm): Range requests with an out of bounds start throw an exception
rather than responding with 416.
}, {
range: "bytes=100-",
status: 416,
expected: ""*/
expected: ""
}, {
range: "bytes=-100",
status: 206,
Expand All @@ -43,11 +41,10 @@
}
})
.then(response => {
// XXX(ferjm): Responses to range requests to blob urls have status 200 rather than 206.
// assert_equals(response.status, test.status);
// if (response.status != 206) {
// return "";
// }
assert_equals(response.status, test.status);
if (response.status != 206) {
return "";
}
return response.text();
})
.then(response => {
Expand Down

0 comments on commit 79d27cb

Please sign in to comment.