1use 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; struct FileStoreEntry {
47 origin: ImmutableOrigin,
49 file_impl: FileImpl,
51 refs: AtomicUsize,
56 is_valid_url: AtomicBool,
60 outstanding_tokens: FxHashSet<Uuid>,
63}
64
65#[derive(Clone)]
66struct FileMetaData {
67 path: PathBuf,
68 size: u64,
69}
70
71#[derive(Clone)]
73enum FileImpl {
74 MetaDataOnly(FileMetaData),
76 Memory(BlobBuf),
78 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 #[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 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 let remaining_bytes =
215 end as usize - range.start as usize - body.len() + 1;
216 if remaining_bytes <= FILE_CHUNK_SIZE {
217 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 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 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 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 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
395struct 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 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 let zero_refs = entry.refs.load(Ordering::Acquire) == 0;
445
446 let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
448
449 let valid = entry.is_valid_url.load(Ordering::Acquire);
451
452 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 is_valid_url: AtomicBool::new(true),
530 outstanding_tokens: Default::default(),
531 },
532 );
533
534 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 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 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 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 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 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 (false, None)
752 } else {
753 let is_valid = entry.is_valid_url.load(Ordering::Acquire);
756
757 let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
759
760 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 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 let zero_refs = entry.refs.load(Ordering::Acquire) == 0;
825
826 let no_outstanding_tokens = entry.outstanding_tokens.is_empty();
828
829 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 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 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 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}