Skip to main content

net/
filemanager_thread.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::fs::File;
6use std::io::{BufRead, BufReader, Seek, SeekFrom};
7use std::ops::Index;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::sync::atomic::{self, AtomicBool, AtomicUsize, Ordering};
11
12use embedder_traits::{
13    EmbedderControlId, EmbedderControlResponse, FilePickerRequest, GenericEmbedderProxy,
14    SelectedFile,
15};
16use headers::{ContentLength, ContentRange, ContentType, HeaderMap, HeaderMapExt, Range};
17use ipc_channel::ipc::IpcSender;
18use log::warn;
19use mime::Mime;
20use net_traits::blob_url_store::{BlobBuf, BlobTokenCommunicator, BlobURLStoreError};
21use net_traits::filemanager_thread::{
22    FileManagerResult, FileManagerThreadError, FileManagerThreadMsg, FileTokenCheck,
23    GetTokenForFileReply, ReadFileProgress, RelativePos,
24};
25use net_traits::response::{Response, ResponseBody};
26use parking_lot::{Mutex, RwLock};
27use rustc_hash::{FxHashMap, FxHashSet};
28use servo_arc::Arc as ServoArc;
29use servo_base::generic_channel::GenericSender;
30use servo_url::ImmutableOrigin;
31use tokio::io::{AsyncReadExt, AsyncSeekExt};
32use tokio::sync::mpsc::UnboundedSender as TokioSender;
33use tokio::task::yield_now;
34use uuid::Uuid;
35
36use crate::async_runtime::spawn_task;
37use crate::embedder::NetToEmbedderMsg;
38use crate::fetch::methods::{CancellationListener, Data, RangeRequestBounds};
39use crate::protocols::get_range_request_bounds;
40
41pub const FILE_CHUNK_SIZE: usize = 32768; // 32 KB
42
43/// FileManagerStore's entry
44struct FileStoreEntry {
45    /// Origin of the entry's "creator"
46    origin: ImmutableOrigin,
47    /// Backend implementation
48    file_impl: FileImpl,
49    /// Number of FileID holders that the ID is used to
50    /// index this entry in `FileManagerStore`.
51    /// Reference holders include a FileStoreEntry or
52    /// a script-side File-based Blob
53    refs: AtomicUsize,
54    /// UUIDs only become valid blob URIs when explicitly requested
55    /// by the user with createObjectURL. Validity can be revoked as well.
56    /// (The UUID is the one that maps to this entry in `FileManagerStore`)
57    is_valid_url: AtomicBool,
58    /// UUIDs of fetch instances that acquired an interest in this file,
59    /// when the url was still valid.
60    outstanding_tokens: FxHashSet<Uuid>,
61}
62
63#[derive(Clone)]
64struct FileMetaData {
65    path: PathBuf,
66    size: u64,
67}
68
69/// File backend implementation
70#[derive(Clone)]
71enum FileImpl {
72    /// Metadata of on-disk file
73    MetaDataOnly(FileMetaData),
74    /// In-memory Blob buffer object
75    Memory(BlobBuf),
76    /// A reference to parent entry in `FileManagerStore`,
77    /// representing a sliced version of the parent entry data
78    Sliced(Uuid, RelativePos),
79}
80
81#[derive(Clone)]
82pub struct FileManager {
83    embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
84    store: Arc<FileManagerStore>,
85    blob_token_communicator: Arc<Mutex<BlobTokenCommunicator>>,
86}
87
88impl FileManager {
89    pub fn new(
90        embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
91        blob_token_communicator: Arc<Mutex<BlobTokenCommunicator>>,
92    ) -> FileManager {
93        FileManager {
94            embedder_proxy,
95            store: Arc::new(FileManagerStore::new()),
96            blob_token_communicator,
97        }
98    }
99
100    fn read_file(
101        &self,
102        sender: IpcSender<FileManagerResult<ReadFileProgress>>,
103        id: Uuid,
104        origin: ImmutableOrigin,
105    ) {
106        let store = self.store.clone();
107        spawn_task(async move {
108            if let Err(e) = store.try_read_file(&sender, id, origin).await {
109                let _ = sender.send(Err(FileManagerThreadError::BlobURLStoreError(e)));
110            }
111        });
112    }
113
114    pub(crate) fn get_token_for_file(&self, file_id: &Uuid, allow_revoked: bool) -> FileTokenCheck {
115        self.store.get_token_for_file(file_id, allow_revoked)
116    }
117
118    pub(crate) fn invalidate_token(&self, token: &FileTokenCheck, file_id: &Uuid) {
119        self.store.invalidate_token(token, file_id);
120    }
121
122    /// Read a file for the Fetch implementation.
123    /// It gets the required headers synchronously and reads the actual content
124    /// in a separate thread.
125    #[expect(clippy::too_many_arguments)]
126    pub(crate) fn fetch_file(
127        &self,
128        done_sender: &mut TokioSender<Data>,
129        cancellation_listener: Arc<CancellationListener>,
130        id: Uuid,
131        file_token: &FileTokenCheck,
132        origin: ImmutableOrigin,
133        response: &mut Response,
134        range: Option<Range>,
135    ) -> Result<(), BlobURLStoreError> {
136        self.fetch_blob_buf(
137            done_sender,
138            cancellation_listener,
139            &id,
140            file_token,
141            &origin,
142            BlobBounds::Unresolved(range),
143            response,
144        )
145    }
146
147    pub fn promote_memory(
148        &self,
149        id: Uuid,
150        blob_buf: BlobBuf,
151        set_valid: bool,
152        origin: ImmutableOrigin,
153    ) {
154        self.store.promote_memory(id, blob_buf, set_valid, origin);
155    }
156
157    /// Message handler
158    pub fn handle(&self, msg: FileManagerThreadMsg) {
159        match msg {
160            FileManagerThreadMsg::SelectFiles(control_id, file_picker_request, response_sender) => {
161                let store = self.store.clone();
162                let embedder = self.embedder_proxy.clone();
163                spawn_task(async move {
164                    let embedder_control_msg = store
165                        .select_files(control_id, file_picker_request, embedder)
166                        .await;
167                    response_sender.send(embedder_control_msg).unwrap();
168                });
169            },
170            FileManagerThreadMsg::ReadFile(sender, id, origin) => {
171                self.read_file(sender, id, origin);
172            },
173            FileManagerThreadMsg::PromoteMemory(id, blob_buf, set_valid, origin) => {
174                self.promote_memory(id, blob_buf, set_valid, origin);
175            },
176            FileManagerThreadMsg::AddSlicedURLEntry(id, rel_pos, sender, origin) => {
177                self.store.add_sliced_url_entry(id, rel_pos, sender, origin);
178            },
179            FileManagerThreadMsg::DecRef(id, origin, sender) => {
180                let _ = sender.send(self.store.dec_ref(&id, &origin));
181            },
182            FileManagerThreadMsg::RevokeBlobURL(id, origin, sender) => {
183                let _ = sender.send(self.store.set_blob_url_validity(false, &id, &origin));
184            },
185            FileManagerThreadMsg::ActivateBlobURL(id, sender, origin) => {
186                let _ = sender.send(self.store.set_blob_url_validity(true, &id, &origin));
187            },
188            FileManagerThreadMsg::GetTokenForFile(id, _origin, sender) => {
189                let token = match self.get_token_for_file(&id, false) {
190                    FileTokenCheck::Required(token) => Some(token),
191                    _ => None,
192                };
193
194                let communicator = self.blob_token_communicator.lock();
195                let _ = sender.send(GetTokenForFileReply {
196                    token,
197                    revoke_sender: communicator.revoke_sender.clone(),
198                    refresh_sender: communicator.refresh_token_sender.clone(),
199                });
200            },
201            FileManagerThreadMsg::RevokeTokenForFile(token, id) => {
202                self.invalidate_token(&FileTokenCheck::Required(token), &id);
203            },
204        }
205    }
206
207    pub fn fetch_file_in_chunks(
208        &self,
209        done_sender: &mut TokioSender<Data>,
210        mut reader: BufReader<File>,
211        res_body: ServoArc<Mutex<ResponseBody>>,
212        cancellation_listener: Arc<CancellationListener>,
213        range: RelativePos,
214    ) {
215        let done_sender = done_sender.clone();
216        spawn_task(async move {
217            loop {
218                if cancellation_listener.cancelled() {
219                    *res_body.lock() = ResponseBody::Done(vec![]);
220                    let _ = done_sender.send(Data::Cancelled);
221                    return;
222                }
223                let length = {
224                    let buffer = reader.fill_buf().unwrap().to_vec();
225                    let mut buffer_len = buffer.len();
226                    if let ResponseBody::Receiving(ref mut body) = *res_body.lock() {
227                        let offset = usize::min(
228                            {
229                                if let Some(end) = range.end {
230                                    // HTTP Range requests are specified with closed ranges,
231                                    // while Rust uses half-open ranges. We add +1 here so
232                                    // we don't skip the last requested byte.
233                                    let remaining_bytes =
234                                        end as usize - range.start as usize - body.len() + 1;
235                                    if remaining_bytes <= FILE_CHUNK_SIZE {
236                                        // This is the last chunk so we set buffer
237                                        // len to 0 to break the reading loop.
238                                        buffer_len = 0;
239                                        remaining_bytes
240                                    } else {
241                                        FILE_CHUNK_SIZE
242                                    }
243                                } else {
244                                    FILE_CHUNK_SIZE
245                                }
246                            },
247                            buffer.len(),
248                        );
249                        let chunk = &buffer[0..offset];
250                        body.extend_from_slice(chunk);
251                        let _ = done_sender.send(Data::Payload(chunk.to_vec()));
252                    }
253                    buffer_len
254                };
255                if length == 0 {
256                    let mut body = res_body.lock();
257                    let completed_body = match *body {
258                        ResponseBody::Receiving(ref mut body) => std::mem::take(body),
259                        _ => vec![],
260                    };
261                    *body = ResponseBody::Done(completed_body);
262                    let _ = done_sender.send(Data::Done);
263                    break;
264                }
265                reader.consume(length);
266                yield_now().await
267            }
268        });
269    }
270
271    #[expect(clippy::too_many_arguments)]
272    fn fetch_blob_buf(
273        &self,
274        done_sender: &mut TokioSender<Data>,
275        cancellation_listener: Arc<CancellationListener>,
276        id: &Uuid,
277        file_token: &FileTokenCheck,
278        origin_in: &ImmutableOrigin,
279        bounds: BlobBounds,
280        response: &mut Response,
281    ) -> Result<(), BlobURLStoreError> {
282        let file_impl = self.store.get_impl(id, file_token, origin_in)?;
283        /*
284           Only Fetch Blob Range Request would have unresolved range, and only in that case we care about range header.
285        */
286        let mut is_range_requested = false;
287        match file_impl {
288            FileImpl::Memory(buf) => {
289                let bounds = match bounds {
290                    BlobBounds::Unresolved(range) => {
291                        if range.is_some() {
292                            is_range_requested = true;
293                        }
294                        get_range_request_bounds(range, buf.size)
295                    },
296                    BlobBounds::Resolved(bounds) => bounds,
297                };
298                let range = bounds
299                    .get_final(Some(buf.size))
300                    .map_err(|_| BlobURLStoreError::InvalidRange)?;
301
302                let range = range.to_abs_blob_range(buf.size as usize);
303                let len = range.len() as u64;
304                let content_range = if is_range_requested {
305                    ContentRange::bytes(range.start as u64..range.end as u64, buf.size).ok()
306                } else {
307                    None
308                };
309
310                set_blob_response_headers(
311                    &mut response.headers,
312                    len,
313                    buf.type_string.parse().unwrap_or(mime::TEXT_PLAIN),
314                    content_range,
315                );
316
317                let mut bytes = vec![];
318                bytes.extend_from_slice(buf.bytes.index(range));
319
320                let _ = done_sender.send(Data::Payload(bytes));
321                let _ = done_sender.send(Data::Done);
322
323                Ok(())
324            },
325            FileImpl::MetaDataOnly(metadata) => {
326                /* XXX: Snapshot state check (optional) https://w3c.github.io/FileAPI/#snapshot-state.
327                        Concretely, here we create another file, and this file might not
328                        has the same underlying file state (meta-info plus content) as the time
329                        create_entry is called.
330                */
331
332                let file = File::open(&metadata.path)
333                    .map_err(|e| BlobURLStoreError::External(e.to_string()))?;
334                let mut is_range_requested = false;
335                let bounds = match bounds {
336                    BlobBounds::Unresolved(range) => {
337                        if range.is_some() {
338                            is_range_requested = true;
339                        }
340                        get_range_request_bounds(range, metadata.size)
341                    },
342                    BlobBounds::Resolved(bounds) => bounds,
343                };
344                let range = bounds
345                    .get_final(Some(metadata.size))
346                    .map_err(|_| BlobURLStoreError::InvalidRange)?;
347
348                let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
349                if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
350                    return Err(BlobURLStoreError::External(
351                        "Unexpected method for blob".into(),
352                    ));
353                }
354
355                let content_range = if is_range_requested {
356                    let abs_range = range.to_abs_blob_range(metadata.size as usize);
357                    ContentRange::bytes(abs_range.start as u64..abs_range.end as u64, metadata.size)
358                        .ok()
359                } else {
360                    None
361                };
362                set_blob_response_headers(
363                    &mut response.headers,
364                    metadata.size,
365                    mime_guess::from_path(metadata.path)
366                        .first()
367                        .unwrap_or(mime::TEXT_PLAIN),
368                    content_range,
369                );
370
371                self.fetch_file_in_chunks(
372                    &mut done_sender.clone(),
373                    reader,
374                    response.body.clone(),
375                    cancellation_listener,
376                    range,
377                );
378
379                Ok(())
380            },
381            FileImpl::Sliced(parent_id, inner_rel_pos) => {
382                // Next time we don't need to check validity since
383                // we have already done that for requesting URL if necessary.
384                let bounds = RangeRequestBounds::Final(
385                    RelativePos::full_range().slice_inner(&inner_rel_pos),
386                );
387                self.fetch_blob_buf(
388                    done_sender,
389                    cancellation_listener,
390                    &parent_id,
391                    file_token,
392                    origin_in,
393                    BlobBounds::Resolved(bounds),
394                    response,
395                )
396            },
397        }
398    }
399}
400
401enum BlobBounds {
402    Unresolved(Option<Range>),
403    Resolved(RangeRequestBounds),
404}
405
406/// File manager's data store. It maintains a thread-safe mapping
407/// from FileID to FileStoreEntry which might have different backend implementation.
408/// Access to the content is encapsulated as methods of this struct.
409struct FileManagerStore {
410    entries: RwLock<FxHashMap<Uuid, FileStoreEntry>>,
411}
412
413impl FileManagerStore {
414    fn new() -> Self {
415        FileManagerStore {
416            entries: RwLock::new(FxHashMap::default()),
417        }
418    }
419
420    /// Copy out the file backend implementation content
421    fn get_impl(
422        &self,
423        id: &Uuid,
424        file_token: &FileTokenCheck,
425        origin_in: &ImmutableOrigin,
426    ) -> Result<FileImpl, BlobURLStoreError> {
427        match self.entries.read().get(id) {
428            Some(entry) => {
429                if *origin_in != entry.origin {
430                    Err(BlobURLStoreError::InvalidOrigin)
431                } else {
432                    match file_token {
433                        FileTokenCheck::NotRequired => Ok(entry.file_impl.clone()),
434                        FileTokenCheck::Required(token) => {
435                            if entry.outstanding_tokens.contains(token) {
436                                return Ok(entry.file_impl.clone());
437                            }
438                            Err(BlobURLStoreError::InvalidFileID)
439                        },
440                        FileTokenCheck::ShouldFail => Err(BlobURLStoreError::InvalidFileID),
441                    }
442                }
443            },
444            None => Err(BlobURLStoreError::InvalidFileID),
445        }
446    }
447
448    fn invalidate_token(&self, token: &FileTokenCheck, file_id: &Uuid) {
449        if let FileTokenCheck::Required(token) = token {
450            let mut entries = self.entries.write();
451            if let Some(entry) = entries.get_mut(file_id) {
452                entry.outstanding_tokens.remove(token);
453
454                // Check if there are references left.
455                let zero_refs = entry.refs.load(Ordering::Acquire) == 0;
456
457                // Check if no other fetch has acquired a token for this file.
458                let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
459
460                // Check if there is still a blob URL outstanding.
461                let valid = entry.is_valid_url.load(Ordering::Acquire);
462
463                // Can we remove this file?
464                let do_remove = zero_refs && no_outstanding_tokens && !valid;
465
466                if do_remove {
467                    entries.remove(file_id);
468                }
469            }
470        }
471    }
472
473    pub(crate) fn get_token_for_file(&self, file_id: &Uuid, allow_revoked: bool) -> FileTokenCheck {
474        let mut entries = self.entries.write();
475        let parent_id = match entries.get(file_id) {
476            Some(entry) => {
477                if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl {
478                    Some(*parent_id)
479                } else {
480                    None
481                }
482            },
483            None => return FileTokenCheck::ShouldFail,
484        };
485        let file_id = parent_id.as_ref().unwrap_or(file_id);
486
487        if let Some(entry) = entries.get_mut(file_id) {
488            if !allow_revoked && !entry.is_valid_url.load(Ordering::Acquire) {
489                log::warn!("Refusing to grant token for revoked blob url: {file_id:?}");
490                return FileTokenCheck::ShouldFail;
491            }
492            let token = Uuid::new_v4();
493            entry.outstanding_tokens.insert(token);
494            return FileTokenCheck::Required(token);
495        }
496        FileTokenCheck::ShouldFail
497    }
498
499    fn insert(&self, id: Uuid, entry: FileStoreEntry) {
500        self.entries.write().insert(id, entry);
501    }
502
503    fn remove(&self, id: &Uuid) {
504        self.entries.write().remove(id);
505    }
506
507    fn inc_ref(&self, id: &Uuid, origin_in: &ImmutableOrigin) -> Result<(), BlobURLStoreError> {
508        match self.entries.read().get(id) {
509            Some(entry) => {
510                if entry.origin == *origin_in {
511                    entry.refs.fetch_add(1, Ordering::Relaxed);
512                    Ok(())
513                } else {
514                    Err(BlobURLStoreError::InvalidOrigin)
515                }
516            },
517            None => Err(BlobURLStoreError::InvalidFileID),
518        }
519    }
520
521    fn add_sliced_url_entry(
522        &self,
523        parent_id: Uuid,
524        rel_pos: RelativePos,
525        sender: GenericSender<Result<Uuid, BlobURLStoreError>>,
526        origin_in: ImmutableOrigin,
527    ) {
528        match self.inc_ref(&parent_id, &origin_in) {
529            Ok(_) => {
530                let new_id = Uuid::new_v4();
531                self.insert(
532                    new_id,
533                    FileStoreEntry {
534                        origin: origin_in,
535                        file_impl: FileImpl::Sliced(parent_id, rel_pos),
536                        refs: AtomicUsize::new(1),
537                        // Valid here since AddSlicedURLEntry implies URL creation
538                        // from a BlobImpl::Sliced
539                        is_valid_url: AtomicBool::new(true),
540                        outstanding_tokens: Default::default(),
541                    },
542                );
543
544                // We assume that the returned id will be held by BlobImpl::File
545                let _ = sender.send(Ok(new_id));
546            },
547            Err(e) => {
548                let _ = sender.send(Err(e));
549            },
550        }
551    }
552
553    async fn select_files(
554        &self,
555        control_id: EmbedderControlId,
556        file_picker_request: FilePickerRequest,
557        embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
558    ) -> EmbedderControlResponse {
559        let (sender, receiver) = tokio::sync::oneshot::channel();
560
561        let origin = file_picker_request.origin.clone();
562        embedder_proxy.send(NetToEmbedderMsg::SelectFiles(
563            control_id,
564            file_picker_request,
565            sender,
566        ));
567
568        let paths = match receiver.await {
569            Ok(Some(result)) => result,
570            Ok(None) => {
571                return EmbedderControlResponse::FilePicker(None);
572            },
573            Err(error) => {
574                warn!("Failed to receive files from embedder ({:?}).", error);
575                return EmbedderControlResponse::FilePicker(None);
576            },
577        };
578
579        let mut failed = false;
580        let files: Vec<_> = paths
581            .into_iter()
582            .filter_map(|path| match self.create_entry(&path, origin.clone()) {
583                Ok(entry) => Some(entry),
584                Err(error) => {
585                    failed = true;
586                    warn!("Failed to create entry for selected file: {error:?}");
587                    None
588                },
589            })
590            .collect();
591
592        // From <https://w3c.github.io/webdriver/#dfn-element-send-keys>:
593        //
594        // > Step 8.5: Verify that each file given by the user exists. If any do not,
595        // > return error with error code invalid argument.
596        //
597        // WebDriver expects that if any of the files isn't found we don't select any files.
598        if failed {
599            for file in files.iter() {
600                self.remove(&file.id);
601            }
602            return EmbedderControlResponse::FilePicker(Some(Vec::new()));
603        }
604
605        EmbedderControlResponse::FilePicker(Some(files))
606    }
607
608    fn create_entry(
609        &self,
610        file_path: &Path,
611        origin: ImmutableOrigin,
612    ) -> Result<SelectedFile, FileManagerThreadError> {
613        use net_traits::filemanager_thread::FileManagerThreadError::FileSystemError;
614
615        let file = File::open(file_path).map_err(|e| FileSystemError(e.to_string()))?;
616        let metadata = file
617            .metadata()
618            .map_err(|e| FileSystemError(e.to_string()))?;
619        let modified = metadata
620            .modified()
621            .map_err(|e| FileSystemError(e.to_string()))?;
622        let file_size = metadata.len();
623        let file_name = file_path
624            .file_name()
625            .ok_or(FileSystemError("Invalid filepath".to_string()))?;
626
627        let file_impl = FileImpl::MetaDataOnly(FileMetaData {
628            path: file_path.to_path_buf(),
629            size: file_size,
630        });
631
632        let id = Uuid::new_v4();
633
634        self.insert(
635            id,
636            FileStoreEntry {
637                origin,
638                file_impl,
639                refs: AtomicUsize::new(1),
640                // Invalid here since create_entry is called by file selection
641                is_valid_url: AtomicBool::new(false),
642                outstanding_tokens: Default::default(),
643            },
644        );
645
646        let filename_path = Path::new(file_name);
647        let type_string = match mime_guess::from_path(filename_path).first() {
648            Some(x) => format!("{}", x),
649            None => "".to_string(),
650        };
651
652        Ok(SelectedFile {
653            id,
654            filename: filename_path.to_path_buf(),
655            modified,
656            size: file_size,
657            type_string,
658        })
659    }
660
661    async fn get_blob_buf(
662        &self,
663        sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
664        id: &Uuid,
665        file_token: &FileTokenCheck,
666        origin_in: &ImmutableOrigin,
667        rel_pos: RelativePos,
668    ) -> Result<(), BlobURLStoreError> {
669        let file_impl = self.get_impl(id, file_token, origin_in)?;
670        match file_impl {
671            FileImpl::Memory(buf) => {
672                let range = rel_pos.to_abs_range(buf.size as usize);
673                let buf = BlobBuf {
674                    filename: None,
675                    type_string: buf.type_string,
676                    size: range.len() as u64,
677                    bytes: buf.bytes.index(range).to_vec(),
678                };
679
680                let _ = sender.send(Ok(ReadFileProgress::Meta(buf)));
681                let _ = sender.send(Ok(ReadFileProgress::EOF));
682
683                Ok(())
684            },
685            FileImpl::MetaDataOnly(metadata) => {
686                /* XXX: Snapshot state check (optional) https://w3c.github.io/FileAPI/#snapshot-state.
687                        Concretely, here we create another file, and this file might not
688                        has the same underlying file state (meta-info plus content) as the time
689                        create_entry is called.
690                */
691
692                let opt_filename = metadata
693                    .path
694                    .file_name()
695                    .and_then(|osstr| osstr.to_str())
696                    .map(|s| s.to_string());
697
698                let mime = mime_guess::from_path(metadata.path.clone()).first();
699                let range = rel_pos.to_abs_range(metadata.size as usize);
700
701                let mut file = tokio::fs::File::open(&metadata.path)
702                    .await
703                    .map_err(|e| BlobURLStoreError::External(e.to_string()))?;
704                let seeked_start = file
705                    .seek(SeekFrom::Start(range.start as u64))
706                    .await
707                    .map_err(|e| BlobURLStoreError::External(e.to_string()))?;
708
709                if seeked_start == (range.start as u64) {
710                    let type_string = match mime {
711                        Some(x) => format!("{}", x),
712                        None => "".to_string(),
713                    };
714
715                    read_file_in_chunks(sender, file, range.len(), opt_filename, type_string).await;
716                    Ok(())
717                } else {
718                    Err(BlobURLStoreError::InvalidEntry)
719                }
720            },
721            FileImpl::Sliced(parent_id, inner_rel_pos) => {
722                // Next time we don't need to check validity since
723                // we have already done that for requesting URL if necessary
724                Box::pin(self.get_blob_buf(
725                    sender,
726                    &parent_id,
727                    file_token,
728                    origin_in,
729                    rel_pos.slice_inner(&inner_rel_pos),
730                ))
731                .await
732            },
733        }
734    }
735
736    // Convenient wrapper over get_blob_buf
737    async fn try_read_file(
738        &self,
739        sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
740        id: Uuid,
741        origin_in: ImmutableOrigin,
742    ) -> Result<(), BlobURLStoreError> {
743        self.get_blob_buf(
744            sender,
745            &id,
746            &FileTokenCheck::NotRequired,
747            &origin_in,
748            RelativePos::full_range(),
749        )
750        .await
751    }
752
753    fn dec_ref(&self, id: &Uuid, origin_in: &ImmutableOrigin) -> Result<(), BlobURLStoreError> {
754        let (do_remove, opt_parent_id) = match self.entries.read().get(id) {
755            Some(entry) => {
756                if entry.origin == *origin_in {
757                    let old_refs = entry.refs.fetch_sub(1, Ordering::Release);
758
759                    if old_refs > 1 {
760                        // not the last reference, no need to touch parent
761                        (false, None)
762                    } else {
763                        // last reference, and if it has a reference to parent id
764                        // dec_ref on parent later if necessary
765                        let is_valid = entry.is_valid_url.load(Ordering::Acquire);
766
767                        // Check if no fetch has acquired a token for this file.
768                        let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
769
770                        // Can we remove this file?
771                        let do_remove = !is_valid && no_outstanding_tokens;
772
773                        if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl {
774                            (do_remove, Some(*parent_id))
775                        } else {
776                            (do_remove, None)
777                        }
778                    }
779                } else {
780                    return Err(BlobURLStoreError::InvalidOrigin);
781                }
782            },
783            None => return Err(BlobURLStoreError::InvalidFileID),
784        };
785
786        // Trigger removing if its last reference is gone and it is
787        // not a part of a valid Blob URL
788        if do_remove {
789            atomic::fence(Ordering::Acquire);
790            self.remove(id);
791
792            if let Some(parent_id) = opt_parent_id {
793                return self.dec_ref(&parent_id, origin_in);
794            }
795        }
796
797        Ok(())
798    }
799
800    fn promote_memory(
801        &self,
802        id: Uuid,
803        blob_buf: BlobBuf,
804        set_valid: bool,
805        origin: ImmutableOrigin,
806    ) {
807        self.insert(
808            id,
809            FileStoreEntry {
810                origin,
811                file_impl: FileImpl::Memory(blob_buf),
812                refs: AtomicUsize::new(1),
813                is_valid_url: AtomicBool::new(set_valid),
814                outstanding_tokens: Default::default(),
815            },
816        );
817    }
818
819    fn set_blob_url_validity(
820        &self,
821        validity: bool,
822        id: &Uuid,
823        origin_in: &ImmutableOrigin,
824    ) -> Result<(), BlobURLStoreError> {
825        let (do_remove, opt_parent_id, res) = match self.entries.read().get(id) {
826            Some(entry) => {
827                if entry.origin == *origin_in {
828                    entry.is_valid_url.store(validity, Ordering::Release);
829
830                    if !validity {
831                        // Check if it is the last possible reference
832                        // since refs only accounts for blob id holders
833                        // and store entry id holders
834                        let zero_refs = entry.refs.load(Ordering::Acquire) == 0;
835
836                        // Check if no fetch has acquired a token for this file.
837                        let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
838
839                        // Can we remove this file?
840                        let do_remove = zero_refs && no_outstanding_tokens;
841
842                        if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl {
843                            (do_remove, Some(*parent_id), Ok(()))
844                        } else {
845                            (do_remove, None, Ok(()))
846                        }
847                    } else {
848                        (false, None, Ok(()))
849                    }
850                } else {
851                    (false, None, Err(BlobURLStoreError::InvalidOrigin))
852                }
853            },
854            None => (false, None, Err(BlobURLStoreError::InvalidFileID)),
855        };
856
857        if do_remove {
858            atomic::fence(Ordering::Acquire);
859            self.remove(id);
860
861            if let Some(parent_id) = opt_parent_id {
862                return self.dec_ref(&parent_id, origin_in);
863            }
864        }
865        res
866    }
867}
868
869async fn read_file_in_chunks(
870    sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
871    mut file: tokio::fs::File,
872    size: usize,
873    opt_filename: Option<String>,
874    type_string: String,
875) {
876    // First chunk
877    let mut buf = vec![0; FILE_CHUNK_SIZE];
878    match file.read(&mut buf).await {
879        Ok(n) => {
880            buf.truncate(n);
881            let blob_buf = BlobBuf {
882                filename: opt_filename,
883                type_string,
884                size: size as u64,
885                bytes: buf,
886            };
887            let _ = sender.send(Ok(ReadFileProgress::Meta(blob_buf)));
888        },
889        Err(e) => {
890            let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
891            return;
892        },
893    }
894
895    // Send the remaining chunks
896    loop {
897        let mut buf = vec![0; FILE_CHUNK_SIZE];
898        match file.read(&mut buf).await {
899            Ok(0) => {
900                let _ = sender.send(Ok(ReadFileProgress::EOF));
901                return;
902            },
903            Ok(n) => {
904                buf.truncate(n);
905                let _ = sender.send(Ok(ReadFileProgress::Partial(buf)));
906            },
907            Err(e) => {
908                let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
909                return;
910            },
911        }
912    }
913}
914
915fn set_blob_response_headers(
916    headers: &mut HeaderMap,
917    content_length: u64,
918    mime: Mime,
919    content_range: Option<ContentRange>,
920) {
921    headers.typed_insert(ContentLength(content_length));
922    if let Some(content_range) = content_range {
923        headers.typed_insert(content_range);
924    }
925    headers.typed_insert(ContentType::from(mime));
926}