servo/ports/servoshell/desktop/protocols/resource.rs
webbeef 1b48bd18aa
Basic tab strip for the minibrowser (#33100)
This implements a simple tab system for servoshell:
- The egui part uses the built-in SelectableLabels components and
  display the full tab title on hover.
- WebView structs now hold all the state for each WebView. When we
  need "global" state, we return the focused WebView state, eg.
  for the load status since it's still global in the UI.
- New keyboard shortcut: [Cmd-or-Ctrl]+[W] to close the current tab.
- New keyboard shortcut: [Cmd-or-Ctrl]+[T] to create a new tab.
- The new tab content is loaded from the 'servo:newtab' url using a
  couple of custom protocol handlers.

Signed-off-by: webbeef <me@webbeef.org>
2024-08-27 20:17:33 +00:00

108 lines
3.7 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/. */
//! This protocol handler loads files from the <resources_dir_path()>/protocol/resource directory,
//! sanitizing the path to prevent path escape attacks.
//! For security reasons, loads are only allowed if the referrer has a 'resource' or
//! 'servo' scheme.
use std::fs::File;
use std::future::Future;
use std::io::BufReader;
use std::pin::Pin;
use headers::{ContentType, HeaderMapExt};
use net::fetch::methods::{DoneChannel, FetchContext};
use net::filemanager_thread::FILE_CHUNK_SIZE;
use net::protocols::ProtocolHandler;
use net_traits::filemanager_thread::RelativePos;
use net_traits::request::Request;
use net_traits::response::{Response, ResponseBody};
use net_traits::ResourceFetchTiming;
use tokio::sync::mpsc::unbounded_channel;
#[derive(Default)]
pub struct ResourceProtocolHander {}
impl ResourceProtocolHander {
pub fn response_for_path(
request: &mut Request,
done_chan: &mut DoneChannel,
context: &FetchContext,
path: &str,
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
if path.contains("..") || !path.starts_with("/") {
return Box::pin(std::future::ready(Response::network_internal_error(
"Invalid path",
)));
}
let path = if let Some(path) = path.strip_prefix("/") {
path
} else {
return Box::pin(std::future::ready(Response::network_internal_error(
"Invalid path",
)));
};
let file_path = crate::resources::resources_dir_path()
.join("resource_protocol")
.join(path);
if !file_path.exists() || file_path.is_dir() {
return Box::pin(std::future::ready(Response::network_internal_error(
"Invalid path",
)));
}
let response = if let Ok(file) = File::open(file_path.clone()) {
let mut response = Response::new(
request.current_url(),
ResourceFetchTiming::new(request.timing_type()),
);
let reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
// 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(),
RelativePos::full_range(),
);
response
} else {
Response::network_internal_error("Opening file failed")
};
Box::pin(std::future::ready(response))
}
}
impl ProtocolHandler for ResourceProtocolHander {
fn load(
&self,
request: &mut Request,
done_chan: &mut DoneChannel,
context: &FetchContext,
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
let url = request.current_url();
// TODO: Check referrer.
// We unexpectedly get `NoReferrer` for all requests from the newtab page.
Self::response_for_path(request, done_chan, context, url.path())
}
}