1use 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#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
25pub enum BlobURLStoreError {
26 InvalidFileID,
28 InvalidOrigin,
30 InvalidEntry,
32 InvalidRange,
34 External(String),
36}
37
38#[derive(Clone, Debug, Deserialize, Serialize)]
40pub struct BlobBuf {
41 pub filename: Option<String>,
42 pub type_string: String,
44 pub size: u64,
46 pub bytes: Vec<u8>,
48}
49
50pub 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 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#[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 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 pub fn from_url_without_having_claimed_blob(url: ServoUrl) -> Self {
124 if url.scheme() == "blob" {
125 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#[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 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)]
213pub struct BlobToken {
216 pub token: Uuid,
217 pub file_id: Uuid,
218 pub disowned: bool,
219 pub origin: ImmutableOrigin,
220 #[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 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}