Files
servo/components/net/protocols/file.rs
Narfinger 618f40540e net: Remove exterior mutability from FileManager (#42111)
This switches FileManager from being behind an Arc<Mutex<FileManager>>>
to just FileManager.
This saves us the Arc and Mutex and the compiler makes sure that we do
not have Race Conditions.

FileManager is already easily cloneable because it consist of store:
Arc<> and GenericEmbedderProxy.

Testing: Compilation and unit tests are the tests.

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
2026-01-27 01:22:02 +00:00

110 lines
4.2 KiB
Rust

/* 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/. */
use std::fs::File;
use std::future::{Future, ready};
use std::io::{BufReader, Seek, SeekFrom};
use std::pin::Pin;
use headers::{ContentType, HeaderMapExt, Range};
use http::Method;
use net_traits::request::Request;
use net_traits::response::{Response, ResponseBody};
use net_traits::{NetworkError, ResourceFetchTiming};
use tokio::sync::mpsc::unbounded_channel;
use crate::fetch::methods::{DoneChannel, FetchContext};
use crate::filemanager_thread::FILE_CHUNK_SIZE;
use crate::local_directory_listing;
use crate::protocols::{
ProtocolHandler, get_range_request_bounds, partial_content, range_not_satisfiable_error,
};
#[derive(Default)]
pub struct FileProtocolHander {}
impl ProtocolHandler for FileProtocolHander {
fn load<'a>(
&'a self,
request: &'a mut Request,
done_chan: &mut DoneChannel,
context: &FetchContext,
) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>> {
let url = request.current_url();
if request.method != Method::GET {
return Box::pin(ready(Response::network_error(NetworkError::InvalidMethod)));
}
let response = if let Ok(file_path) = url.to_file_path() {
if file_path.is_dir() {
return Box::pin(async move {
local_directory_listing::fetch(request, url, file_path).await
});
}
if let Ok(file) = File::open(file_path.clone()) {
// 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 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 Ok(range) = get_range_request_bounds(range_header, file_size.unwrap_or(0))
.get_final(file_size)
else {
range_not_satisfiable_error(&mut response);
return Box::pin(ready(response));
};
let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
return Box::pin(ready(Response::network_error(NetworkError::InvalidMethod)));
}
// 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 = mime_guess::from_path(file_path).first_or_octet_stream();
response.headers.typed_insert(ContentType::from(mime));
// Setup channel to receive cross-thread messages about the file fetch
// operation.
let (mut done_sender, done_receiver) = unbounded_channel();
*done_chan = Some((done_sender.clone(), done_receiver));
*response.body.lock() = ResponseBody::Receiving(vec![]);
context.filemanager.fetch_file_in_chunks(
&mut done_sender,
reader,
response.body.clone(),
context.cancellation_listener.clone(),
range,
);
response
} else {
Response::network_error(NetworkError::ResourceLoadError(
"Opening file failed".into(),
))
}
} else {
Response::network_error(NetworkError::ResourceLoadError(
"Constructing file path failed".into(),
))
};
Box::pin(ready(response))
}
}