net/protocols/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::collections::hash_map::Entry;
6use std::future::Future;
7use std::ops::Bound;
8use std::pin::Pin;
9
10use headers::Range;
11use http::StatusCode;
12use log::error;
13use net_traits::filemanager_thread::RelativePos;
14use net_traits::request::Request;
15use net_traits::response::Response;
16use rustc_hash::FxHashMap;
17use servo_url::ServoUrl;
18
19use crate::fetch::methods::{DoneChannel, FetchContext, RangeRequestBounds};
20
21mod blob;
22mod data;
23mod file;
24
25use blob::BlobProtocolHander;
26use data::DataProtocolHander;
27use file::FileProtocolHander;
28
29// The set of schemes that can't be registered.
30static FORBIDDEN_SCHEMES: [&str; 4] = ["http", "https", "chrome", "about"];
31
32pub trait ProtocolHandler: Send + Sync {
33    /// A list of schema-less URLs that can be resolved against this handler's
34    /// scheme. These URLs will be granted access to the `navigator.servo`
35    /// interface to perform privileged operations that manipulate Servo internals.
36    fn privileged_paths(&self) -> &'static [&'static str] {
37        &[]
38    }
39
40    /// Triggers the load of a resource for this protocol and returns a future
41    /// that will produce a Response. Even if the protocol is not backed by a
42    /// http endpoint, it is recommended to a least provide:
43    /// - A relevant status code.
44    /// - A Content Type.
45    fn load(
46        &self,
47        request: &mut Request,
48        done_chan: &mut DoneChannel,
49        context: &FetchContext,
50    ) -> Pin<Box<dyn Future<Output = Response> + Send>>;
51
52    /// Specify if resources served by that protocol can be retrieved
53    /// with `fetch()` without no-cors mode to allow the caller direct
54    /// access to the resource content.
55    fn is_fetchable(&self) -> bool {
56        false
57    }
58
59    /// Specify if this custom protocol can be used in a [secure context]
60    ///
61    /// Note: this only works for bypassing mixed content checks right now
62    ///
63    /// [secure context]: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts
64    fn is_secure(&self) -> bool {
65        false
66    }
67}
68
69#[derive(Default)]
70pub struct ProtocolRegistry {
71    pub(crate) handlers: FxHashMap<String, Box<dyn ProtocolHandler>>, // Maps scheme -> handler
72}
73
74#[derive(Clone, Copy, Debug)]
75pub enum ProtocolRegisterError {
76    ForbiddenScheme,
77    SchemeAlreadyRegistered,
78}
79
80impl ProtocolRegistry {
81    pub fn with_internal_protocols() -> Self {
82        let mut registry = Self::default();
83        // We just created a new registry, and know that we aren't using
84        // any forbidden schemes, so this should never panic.
85        registry
86            .register("data", DataProtocolHander::default())
87            .expect("Infallible");
88        registry
89            .register("blob", BlobProtocolHander::default())
90            .expect("Infallible");
91        registry
92            .register("file", FileProtocolHander::default())
93            .expect("Infallible");
94        registry
95    }
96
97    /// Do not allow users to enter an arbitrary protocol as this can lead to slowdowns.
98    pub fn register(
99        &mut self,
100        scheme: &str,
101        handler: impl ProtocolHandler + 'static,
102    ) -> Result<(), ProtocolRegisterError> {
103        if FORBIDDEN_SCHEMES.contains(&scheme) {
104            error!("Protocol handler for '{scheme}' is not allowed to be registered.");
105            return Err(ProtocolRegisterError::ForbiddenScheme);
106        }
107
108        if let Entry::Vacant(entry) = self.handlers.entry(scheme.into()) {
109            entry.insert(Box::new(handler));
110            Ok(())
111        } else {
112            error!("Protocol handler for '{scheme}' is already registered.");
113            Err(ProtocolRegisterError::SchemeAlreadyRegistered)
114        }
115    }
116
117    pub fn get(&self, scheme: &str) -> Option<&dyn ProtocolHandler> {
118        self.handlers.get(scheme).map(|e| e.as_ref())
119    }
120
121    pub fn merge(&mut self, mut other: ProtocolRegistry) {
122        for (scheme, handler) in other.handlers.drain() {
123            if FORBIDDEN_SCHEMES.contains(&scheme.as_str()) {
124                error!("Protocol handler for '{scheme}' is not allowed to be registered.");
125                continue;
126            }
127
128            self.handlers.entry(scheme).or_insert(handler);
129        }
130    }
131
132    pub fn is_fetchable(&self, scheme: &str) -> bool {
133        self.handlers
134            .get(scheme)
135            .is_some_and(|handler| handler.is_fetchable())
136    }
137
138    pub fn is_secure(&self, scheme: &str) -> bool {
139        self.handlers
140            .get(scheme)
141            .is_some_and(|handler| handler.is_secure())
142    }
143
144    pub fn privileged_urls(&self) -> Vec<ServoUrl> {
145        self.handlers
146            .iter()
147            .flat_map(|(scheme, handler)| {
148                let paths = handler.privileged_paths();
149                paths
150                    .iter()
151                    .filter_map(move |path| ServoUrl::parse(&format!("{scheme}:{path}")).ok())
152            })
153            .collect()
154    }
155}
156
157/// Test if the URL is potentially trustworthy or the custom protocol is registered as secure
158pub fn is_url_potentially_trustworthy(
159    protocol_registry: &ProtocolRegistry,
160    url: &ServoUrl,
161) -> bool {
162    url.is_potentially_trustworthy() || protocol_registry.is_secure(url.scheme())
163}
164
165pub fn range_not_satisfiable_error(response: &mut Response) {
166    response.status = StatusCode::RANGE_NOT_SATISFIABLE.into();
167}
168
169/// Get the range bounds if the `Range` header is present.
170pub fn get_range_request_bounds(range: Option<Range>, len: u64) -> RangeRequestBounds {
171    if let Some(ref range) = range {
172        let (start, end) = match range.satisfiable_ranges(len).next() {
173            Some((Bound::Included(start), Bound::Unbounded)) => (start, None),
174            Some((Bound::Included(start), Bound::Included(end))) => {
175                // `end` should be less or equal to `start`.
176                (start, Some(i64::max(start as i64, end as i64)))
177            },
178            Some((Bound::Unbounded, Bound::Included(offset))) => {
179                return RangeRequestBounds::Pending(offset);
180            },
181            _ => (0, None),
182        };
183        RangeRequestBounds::Final(RelativePos::from_opts(Some(start as i64), end))
184    } else {
185        RangeRequestBounds::Final(RelativePos::from_opts(Some(0), None))
186    }
187}
188
189pub fn partial_content(response: &mut Response) {
190    response.status = StatusCode::PARTIAL_CONTENT.into();
191}