/* 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::{ready, Future}; 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::{ get_range_request_bounds, partial_content, range_not_satisfiable_error, ProtocolHandler, }; #[derive(Default)] pub struct FileProtocolHander {} impl ProtocolHandler for FileProtocolHander { fn load( &self, request: &mut Request, done_chan: &mut DoneChannel, context: &FetchContext, ) -> Pin + Send>> { let url = request.current_url(); if request.method != Method::GET { return Box::pin(ready(Response::network_error(NetworkError::Internal( "Unexpected method for file".into(), )))); } let response = if let Ok(file_path) = url.to_file_path() { if file_path.is_dir() { return Box::pin(ready(local_directory_listing::fetch( request, url, file_path, ))); } 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::(); 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::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 = 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().unwrap() = ResponseBody::Receiving(vec![]); context.filemanager.lock().unwrap().fetch_file_in_chunks( &mut done_sender, reader, response.body.clone(), context.cancellation_listener.clone(), range, ); response } else { Response::network_error(NetworkError::Internal("Opening file failed".into())) } } else { Response::network_error(NetworkError::Internal( "Constructing file path failed".into(), )) }; Box::pin(ready(response)) } }