Skip to main content

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