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::HashMap;
6use std::collections::hash_map::Entry;
7use std::future::Future;
8use std::ops::Bound;
9use std::pin::Pin;
10
11use headers::Range;
12use http::StatusCode;
13use log::error;
14use net_traits::filemanager_thread::RelativePos;
15use net_traits::request::Request;
16use net_traits::response::Response;
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: HashMap<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    pub fn register(
98        &mut self,
99        scheme: &str,
100        handler: impl ProtocolHandler + 'static,
101    ) -> Result<(), ProtocolRegisterError> {
102        if FORBIDDEN_SCHEMES.contains(&scheme) {
103            error!("Protocol handler for '{scheme}' is not allowed to be registered.");
104            return Err(ProtocolRegisterError::ForbiddenScheme);
105        }
106
107        if let Entry::Vacant(entry) = self.handlers.entry(scheme.into()) {
108            entry.insert(Box::new(handler));
109            Ok(())
110        } else {
111            error!("Protocol handler for '{scheme}' is already registered.");
112            Err(ProtocolRegisterError::SchemeAlreadyRegistered)
113        }
114    }
115
116    pub fn get(&self, scheme: &str) -> Option<&dyn ProtocolHandler> {
117        self.handlers.get(scheme).map(|e| e.as_ref())
118    }
119
120    pub fn merge(&mut self, mut other: ProtocolRegistry) {
121        for (scheme, handler) in other.handlers.drain() {
122            if FORBIDDEN_SCHEMES.contains(&scheme.as_str()) {
123                error!("Protocol handler for '{scheme}' is not allowed to be registered.");
124                continue;
125            }
126
127            self.handlers.entry(scheme).or_insert(handler);
128        }
129    }
130
131    pub fn is_fetchable(&self, scheme: &str) -> bool {
132        self.handlers
133            .get(scheme)
134            .is_some_and(|handler| handler.is_fetchable())
135    }
136
137    pub fn is_secure(&self, scheme: &str) -> bool {
138        self.handlers
139            .get(scheme)
140            .is_some_and(|handler| handler.is_secure())
141    }
142
143    pub fn privileged_urls(&self) -> Vec<ServoUrl> {
144        self.handlers
145            .iter()
146            .flat_map(|(scheme, handler)| {
147                let paths = handler.privileged_paths();
148                paths
149                    .iter()
150                    .filter_map(move |path| ServoUrl::parse(&format!("{scheme}:{path}")).ok())
151            })
152            .collect()
153    }
154}
155
156/// Test if the URL is potentially trustworthy or the custom protocol is registered as secure
157pub fn is_url_potentially_trustworthy(
158    protocol_registry: &ProtocolRegistry,
159    url: &ServoUrl,
160) -> bool {
161    url.is_potentially_trustworthy() || protocol_registry.is_secure(url.scheme())
162}
163
164pub fn range_not_satisfiable_error(response: &mut Response) {
165    response.status = StatusCode::RANGE_NOT_SATISFIABLE.into();
166}
167
168/// Get the range bounds if the `Range` header is present.
169pub fn get_range_request_bounds(range: Option<Range>, len: u64) -> RangeRequestBounds {
170    if let Some(ref range) = range {
171        let (start, end) = match range.satisfiable_ranges(len).next() {
172            Some((Bound::Included(start), Bound::Unbounded)) => (start, None),
173            Some((Bound::Included(start), Bound::Included(end))) => {
174                // `end` should be less or equal to `start`.
175                (start, Some(i64::max(start as i64, end as i64)))
176            },
177            Some((Bound::Unbounded, Bound::Included(offset))) => {
178                return RangeRequestBounds::Pending(offset);
179            },
180            _ => (0, None),
181        };
182        RangeRequestBounds::Final(RelativePos::from_opts(Some(start as i64), end))
183    } else {
184        RangeRequestBounds::Final(RelativePos::from_opts(Some(0), None))
185    }
186}
187
188pub fn partial_content(response: &mut Response) {
189    response.status = StatusCode::PARTIAL_CONTENT.into();
190}