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