net_traits/
blob_url_store.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::fmt;
6use std::ops::{Deref, DerefMut};
7use std::str::FromStr;
8use std::sync::Arc;
9
10use malloc_size_of_derive::MallocSizeOf;
11use parking_lot::Mutex;
12use serde::{Deserialize, Serialize};
13use servo_base::generic_channel::{self, GenericSend, GenericSender};
14use servo_url::{ImmutableOrigin, ServoUrl};
15use url::Url;
16use uuid::Uuid;
17
18use crate::{
19    BlobTokenRefreshRequest, BlobTokenRevocationRequest, CoreResourceMsg, FileManagerThreadMsg,
20    ResourceThreads,
21};
22
23/// Errors returned to Blob URL Store request
24#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
25pub enum BlobURLStoreError {
26    /// Invalid File UUID
27    InvalidFileID,
28    /// Invalid URL origin
29    InvalidOrigin,
30    /// Invalid entry content
31    InvalidEntry,
32    /// Invalid range
33    InvalidRange,
34    /// External error, from like file system, I/O etc.
35    External(String),
36}
37
38/// Standalone blob buffer object
39#[derive(Clone, Debug, Deserialize, Serialize)]
40pub struct BlobBuf {
41    pub filename: Option<String>,
42    /// MIME type string
43    pub type_string: String,
44    /// Size of content in bytes
45    pub size: u64,
46    /// Content of blob
47    pub bytes: Vec<u8>,
48}
49
50/// Parse URL as Blob URL scheme's definition
51///
52/// <https://w3c.github.io/FileAPI/#url-intro>
53///
54/// FIXME: This function should never be used to obtain the origin of a blob url, because
55/// it doesn't consider [blob URL entries].
56///
57/// [blob URL entries]: https://url.spec.whatwg.org/#concept-url-blob-entry
58pub fn parse_blob_url(url: &ServoUrl) -> Result<(Uuid, ImmutableOrigin), &'static str> {
59    if url.query().is_some() {
60        return Err("URL should not contain a query");
61    }
62
63    let Some((_, uuid)) = url.path().rsplit_once('/') else {
64        return Err("Failed to split origin from uuid");
65    };
66
67    // See https://url.spec.whatwg.org/#origin - "blob" case
68    let origin = Url::parse(url.path())
69        .ok()
70        .filter(|url| matches!(url.scheme(), "http" | "https" | "file"))
71        .map(|url| ImmutableOrigin::new(&url))
72        .unwrap_or(ImmutableOrigin::new_opaque());
73
74    let id = Uuid::from_str(uuid).map_err(|_| "Failed to parse UUID from path segment")?;
75
76    Ok((id, origin))
77}
78
79/// This type upholds the variant that if the URL is a valid `blob` URL, then it has
80/// a token. Violating this invariant causes logic errors, but no unsafety.
81#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
82pub struct UrlWithBlobClaim {
83    url: ServoUrl,
84    token: Option<TokenSerializationGuard>,
85}
86
87impl UrlWithBlobClaim {
88    pub fn new(url: ServoUrl, token: Option<TokenSerializationGuard>) -> Self {
89        Self { url, token }
90    }
91
92    pub fn token(&self) -> Option<&BlobToken> {
93        self.token.as_ref().map(|guard| guard.token.as_ref())
94    }
95
96    pub fn blob_id(&self) -> Option<Uuid> {
97        self.token.as_ref().map(|guard| guard.token.file_id)
98    }
99
100    pub fn origin(&self) -> ImmutableOrigin {
101        if let Some(guard) = self.token.as_ref() {
102            return guard.token.origin.clone();
103        }
104
105        self.url.origin()
106    }
107
108    /// Constructs a [UrlWithBlobClaim] for URLs that are not `blob` URLs
109    /// (Such URLs don't need to claim anything).
110    ///
111    /// Returns an `Err` containing the original URL if it's a `blob` URL,
112    /// so it can be reused without cloning.
113    pub fn for_url(url: ServoUrl) -> Result<Self, ServoUrl> {
114        if url.scheme() == "blob" {
115            return Err(url);
116        }
117
118        Ok(Self { url, token: None })
119    }
120
121    /// This method should only exist temporarily, and all callers should either
122    /// claim the blob or guarantee that the URL is not a `blob` URL.
123    pub fn from_url_without_having_claimed_blob(url: ServoUrl) -> Self {
124        if url.scheme() == "blob" {
125            // See https://github.com/servo/servo/issues/25226 for more details
126            log::warn!(
127                "Creating blob URL without claiming its associated blob entry. This might cause race conditions if the URL is revoked."
128            );
129        }
130        Self { url, token: None }
131    }
132
133    pub fn url(&self) -> ServoUrl {
134        self.url.clone()
135    }
136}
137
138impl Deref for UrlWithBlobClaim {
139    type Target = ServoUrl;
140
141    fn deref(&self) -> &Self::Target {
142        &self.url
143    }
144}
145
146impl DerefMut for UrlWithBlobClaim {
147    fn deref_mut(&mut self) -> &mut Self::Target {
148        &mut self.url
149    }
150}
151
152/// Guarantees that blob entries kept alive the contained token are not deallocated even
153/// if this token is serialized, dropped, and then later deserialized (possibly in a different thread).
154#[derive(Clone, Debug, MallocSizeOf)]
155pub struct TokenSerializationGuard {
156    #[conditional_malloc_size_of]
157    token: Arc<BlobToken>,
158}
159
160impl serde::Serialize for TokenSerializationGuard {
161    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162    where
163        S: serde::Serializer,
164    {
165        let mut new_token = self.token.refresh();
166        let result = new_token.serialize(serializer);
167        if result.is_ok() {
168            // This token belongs to whoever receives the serialized message, so don't free it.
169            new_token.disown();
170        }
171        result
172    }
173}
174
175impl<'a> serde::Deserialize<'a> for TokenSerializationGuard {
176    fn deserialize<D>(de: D) -> Result<Self, <D as serde::Deserializer<'a>>::Error>
177    where
178        D: serde::Deserializer<'a>,
179    {
180        struct TokenGuardVisitor;
181
182        impl<'de> serde::de::Visitor<'de> for TokenGuardVisitor {
183            type Value = TokenSerializationGuard;
184
185            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
186                write!(formatter, "a TokenSerializationGuard")
187            }
188
189            fn visit_newtype_struct<D>(
190                self,
191                deserializer: D,
192            ) -> Result<Self::Value, <D as serde::Deserializer<'de>>::Error>
193            where
194                D: serde::Deserializer<'de>,
195            {
196                Ok(TokenSerializationGuard {
197                    token: Arc::new(BlobToken::deserialize(deserializer)?),
198                })
199            }
200        }
201
202        de.deserialize_newtype_struct("TokenSerializationGuard", TokenGuardVisitor)
203    }
204}
205
206#[derive(Clone, MallocSizeOf)]
207pub struct BlobResolver<'a> {
208    pub origin: ImmutableOrigin,
209    pub resource_threads: &'a ResourceThreads,
210}
211
212#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
213/// A reference to a blob URL that will revoke the blob when dropped,
214/// unless the `disown` method is invoked.
215pub struct BlobToken {
216    pub token: Uuid,
217    pub file_id: Uuid,
218    pub disowned: bool,
219    pub origin: ImmutableOrigin,
220    // We need a mutex here because BlobTokens are shared among threads, and accessing
221    // `GenericSender<CoreResourceMsg>` from different threads is not safe.
222    //
223    // We need a Arc because the Communicator is shared among different BlobTokens.
224    #[conditional_malloc_size_of]
225    pub communicator: Arc<Mutex<BlobTokenCommunicator>>,
226}
227
228#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
229pub struct BlobTokenCommunicator {
230    pub revoke_sender: GenericSender<CoreResourceMsg>,
231    pub refresh_token_sender: GenericSender<CoreResourceMsg>,
232}
233
234impl BlobTokenCommunicator {
235    pub fn stub_for_testing() -> Arc<Mutex<Self>> {
236        Arc::new(Mutex::new(Self {
237            revoke_sender: generic_channel::channel().unwrap().0,
238            refresh_token_sender: generic_channel::channel().unwrap().0,
239        }))
240    }
241}
242
243impl BlobToken {
244    fn refresh(&self) -> Self {
245        let (new_token_sender, new_token_receiver) = generic_channel::channel().unwrap();
246        let refresh_request = BlobTokenRefreshRequest {
247            blob_id: self.file_id,
248            new_token_sender,
249        };
250        self.communicator
251            .lock()
252            .refresh_token_sender
253            .send(CoreResourceMsg::RefreshTokenForFile(refresh_request))
254            .unwrap();
255        let new_token = new_token_receiver.recv().unwrap();
256
257        BlobToken {
258            token: new_token,
259            file_id: self.file_id,
260            communicator: self.communicator.clone(),
261            disowned: false,
262            origin: self.origin.clone(),
263        }
264    }
265
266    /// Prevents this token from revoking itself when it is dropped.
267    fn disown(&mut self) {
268        self.disowned = true;
269    }
270}
271
272impl<'a> BlobResolver<'a> {
273    pub fn acquire_blob_token_for(&self, url: &ServoUrl) -> Option<TokenSerializationGuard> {
274        if url.scheme() != "blob" {
275            return None;
276        }
277        let (file_id, origin) = parse_blob_url(url)
278            .inspect_err(|error| log::warn!("Failed to acquire token for {url}: {error}"))
279            .ok()?;
280        let (sender, receiver) = generic_channel::channel().unwrap();
281        self.resource_threads
282            .send(CoreResourceMsg::ToFileManager(
283                FileManagerThreadMsg::GetTokenForFile(file_id, origin, sender),
284            ))
285            .ok()?;
286        let reply = receiver.recv().ok()?;
287        reply.token.map(|token_id| {
288            let token = BlobToken {
289                token: token_id,
290                file_id,
291                communicator: Arc::new(Mutex::new(BlobTokenCommunicator {
292                    revoke_sender: reply.revoke_sender,
293                    refresh_token_sender: reply.refresh_sender,
294                })),
295                disowned: false,
296                origin: self.origin.clone(),
297            };
298
299            TokenSerializationGuard {
300                token: Arc::new(token),
301            }
302        })
303    }
304}
305
306impl Drop for BlobToken {
307    fn drop(&mut self) {
308        if self.disowned {
309            return;
310        }
311
312        let revocation_request = BlobTokenRevocationRequest {
313            token: self.token,
314            blob_id: self.file_id,
315        };
316        let _ = self
317            .communicator
318            .lock()
319            .revoke_sender
320            .send(CoreResourceMsg::RevokeTokenForFile(revocation_request));
321    }
322}
323
324impl fmt::Debug for BlobToken {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        f.debug_struct("BlobToken")
327            .field("token", &self.token)
328            .field("file_id", &self.file_id)
329            .field("disowned", &self.disowned)
330            .field("origin", &self.origin)
331            .finish()
332    }
333}