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| url.origin())
72        .map(ImmutableOrigin::new)
73        .unwrap_or(ImmutableOrigin::new_opaque());
74
75    let id = Uuid::from_str(uuid).map_err(|_| "Failed to parse UUID from path segment")?;
76
77    Ok((id, origin))
78}
79
80/// This type upholds the variant that if the URL is a valid `blob` URL, then it has
81/// a token. Violating this invariant causes logic errors, but no unsafety.
82#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
83pub struct UrlWithBlobClaim {
84    url: ServoUrl,
85    token: Option<TokenSerializationGuard>,
86}
87
88impl UrlWithBlobClaim {
89    pub fn new(url: ServoUrl, token: Option<TokenSerializationGuard>) -> Self {
90        Self { url, token }
91    }
92
93    pub fn token(&self) -> Option<&BlobToken> {
94        self.token.as_ref().map(|guard| guard.token.as_ref())
95    }
96
97    pub fn blob_id(&self) -> Option<Uuid> {
98        self.token.as_ref().map(|guard| guard.token.file_id)
99    }
100
101    pub fn origin(&self) -> ImmutableOrigin {
102        if let Some(guard) = self.token.as_ref() {
103            return guard.token.origin.clone();
104        }
105
106        self.url.origin()
107    }
108
109    /// Constructs a [UrlWithBlobClaim] for URLs that are not `blob` URLs
110    /// (Such URLs don't need to claim anything).
111    ///
112    /// Returns an `Err` containing the original URL if it's a `blob` URL,
113    /// so it can be reused without cloning.
114    pub fn for_url(url: ServoUrl) -> Result<Self, ServoUrl> {
115        if url.scheme() == "blob" {
116            return Err(url);
117        }
118
119        Ok(Self { url, token: None })
120    }
121
122    /// This method should only exist temporarily, and all callers should either
123    /// claim the blob or guarantee that the URL is not a `blob` URL.
124    pub fn from_url_without_having_claimed_blob(url: ServoUrl) -> Self {
125        if url.scheme() == "blob" {
126            // See https://github.com/servo/servo/issues/25226 for more details
127            log::warn!(
128                "Creating blob URL without claiming its associated blob entry. This might cause race conditions if the URL is revoked."
129            );
130        }
131        Self { url, token: None }
132    }
133
134    pub fn url(&self) -> ServoUrl {
135        self.url.clone()
136    }
137}
138
139impl Deref for UrlWithBlobClaim {
140    type Target = ServoUrl;
141
142    fn deref(&self) -> &Self::Target {
143        &self.url
144    }
145}
146
147impl DerefMut for UrlWithBlobClaim {
148    fn deref_mut(&mut self) -> &mut Self::Target {
149        &mut self.url
150    }
151}
152
153/// Guarantees that blob entries kept alive the contained token are not deallocated even
154/// if this token is serialized, dropped, and then later deserialized (possibly in a different thread).
155#[derive(Clone, Debug, MallocSizeOf)]
156pub struct TokenSerializationGuard {
157    #[conditional_malloc_size_of]
158    token: Arc<BlobToken>,
159}
160
161impl serde::Serialize for TokenSerializationGuard {
162    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
163    where
164        S: serde::Serializer,
165    {
166        let mut new_token = self.token.refresh();
167        let result = new_token.serialize(serializer);
168        if result.is_ok() {
169            // This token belongs to whoever receives the serialized message, so don't free it.
170            new_token.disown();
171        }
172        result
173    }
174}
175
176impl<'a> serde::Deserialize<'a> for TokenSerializationGuard {
177    fn deserialize<D>(de: D) -> Result<Self, <D as serde::Deserializer<'a>>::Error>
178    where
179        D: serde::Deserializer<'a>,
180    {
181        struct TokenGuardVisitor;
182
183        impl<'de> serde::de::Visitor<'de> for TokenGuardVisitor {
184            type Value = TokenSerializationGuard;
185
186            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
187                write!(formatter, "a TokenSerializationGuard")
188            }
189
190            fn visit_newtype_struct<D>(
191                self,
192                deserializer: D,
193            ) -> Result<Self::Value, <D as serde::Deserializer<'de>>::Error>
194            where
195                D: serde::Deserializer<'de>,
196            {
197                Ok(TokenSerializationGuard {
198                    token: Arc::new(BlobToken::deserialize(deserializer)?),
199                })
200            }
201        }
202
203        de.deserialize_newtype_struct("TokenSerializationGuard", TokenGuardVisitor)
204    }
205}
206
207#[derive(Clone, MallocSizeOf)]
208pub struct BlobResolver<'a> {
209    pub origin: ImmutableOrigin,
210    pub resource_threads: &'a ResourceThreads,
211}
212
213#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
214/// A reference to a blob URL that will revoke the blob when dropped,
215/// unless the `disown` method is invoked.
216pub struct BlobToken {
217    pub token: Uuid,
218    pub file_id: Uuid,
219    pub disowned: bool,
220    pub origin: ImmutableOrigin,
221    // We need a mutex here because BlobTokens are shared among threads, and accessing
222    // `GenericSender<CoreResourceMsg>` from different threads is not safe.
223    //
224    // We need a Arc because the Communicator is shared among different BlobTokens.
225    #[conditional_malloc_size_of]
226    pub communicator: Arc<Mutex<BlobTokenCommunicator>>,
227}
228
229#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
230pub struct BlobTokenCommunicator {
231    pub revoke_sender: GenericSender<CoreResourceMsg>,
232    pub refresh_token_sender: GenericSender<CoreResourceMsg>,
233}
234
235impl BlobTokenCommunicator {
236    pub fn stub_for_testing() -> Arc<Mutex<Self>> {
237        Arc::new(Mutex::new(Self {
238            revoke_sender: generic_channel::channel().unwrap().0,
239            refresh_token_sender: generic_channel::channel().unwrap().0,
240        }))
241    }
242}
243
244impl BlobToken {
245    fn refresh(&self) -> Self {
246        let (new_token_sender, new_token_receiver) = generic_channel::channel().unwrap();
247        let refresh_request = BlobTokenRefreshRequest {
248            blob_id: self.file_id,
249            new_token_sender,
250        };
251        self.communicator
252            .lock()
253            .refresh_token_sender
254            .send(CoreResourceMsg::RefreshTokenForFile(refresh_request))
255            .unwrap();
256        let new_token = new_token_receiver.recv().unwrap();
257
258        BlobToken {
259            token: new_token,
260            file_id: self.file_id,
261            communicator: self.communicator.clone(),
262            disowned: false,
263            origin: self.origin.clone(),
264        }
265    }
266
267    /// Prevents this token from revoking itself when it is dropped.
268    fn disown(&mut self) {
269        self.disowned = true;
270    }
271}
272
273impl<'a> BlobResolver<'a> {
274    pub fn acquire_blob_token_for(&self, url: &ServoUrl) -> Option<TokenSerializationGuard> {
275        if url.scheme() != "blob" {
276            return None;
277        }
278        let (file_id, origin) = parse_blob_url(url)
279            .inspect_err(|error| log::warn!("Failed to acquire token for {url}: {error}"))
280            .ok()?;
281        let (sender, receiver) = generic_channel::channel().unwrap();
282        self.resource_threads
283            .send(CoreResourceMsg::ToFileManager(
284                FileManagerThreadMsg::GetTokenForFile(file_id, origin, sender),
285            ))
286            .ok()?;
287        let reply = receiver.recv().ok()?;
288        reply.token.map(|token_id| {
289            let token = BlobToken {
290                token: token_id,
291                file_id,
292                communicator: Arc::new(Mutex::new(BlobTokenCommunicator {
293                    revoke_sender: reply.revoke_sender,
294                    refresh_token_sender: reply.refresh_sender,
295                })),
296                disowned: false,
297                origin: self.origin.clone(),
298            };
299
300            TokenSerializationGuard {
301                token: Arc::new(token),
302            }
303        })
304    }
305}
306
307impl Drop for BlobToken {
308    fn drop(&mut self) {
309        if self.disowned {
310            return;
311        }
312
313        let revocation_request = BlobTokenRevocationRequest {
314            token: self.token,
315            blob_id: self.file_id,
316        };
317        let _ = self
318            .communicator
319            .lock()
320            .revoke_sender
321            .send(CoreResourceMsg::RevokeTokenForFile(revocation_request));
322    }
323}
324
325impl fmt::Debug for BlobToken {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        f.debug_struct("BlobToken")
328            .field("token", &self.token)
329            .field("file_id", &self.file_id)
330            .field("disowned", &self.disowned)
331            .field("origin", &self.origin)
332            .finish()
333    }
334}