Skip to main content

deno_web/
blob.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::fmt::Debug;
6use std::rc::Rc;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use deno_core::JsBuffer;
11use deno_core::OpState;
12use deno_core::convert::Uint8Array;
13use deno_core::op2;
14use deno_core::parking_lot::Mutex;
15use deno_core::url::Url;
16use serde::Deserialize;
17use serde::Serialize;
18use uuid::Uuid;
19
20#[derive(Debug, thiserror::Error, deno_error::JsError)]
21pub enum BlobError {
22  #[class(type)]
23  #[error("Blob part not found")]
24  BlobPartNotFound,
25  #[class(type)]
26  #[error("start + len can not be larger than blob part size")]
27  SizeLargerThanBlobPart,
28  #[class(type)]
29  #[error("Blob URLs are not supported in this context")]
30  BlobURLsNotSupported,
31  #[class(generic)]
32  #[error(transparent)]
33  Url(#[from] deno_core::url::ParseError),
34}
35
36use crate::Location;
37
38pub type PartMap = HashMap<Uuid, Arc<dyn BlobPart + Send + Sync>>;
39
40#[derive(Default, Debug)]
41pub struct BlobStore {
42  parts: Mutex<PartMap>,
43  object_urls: Mutex<HashMap<Url, Arc<Blob>>>,
44}
45
46impl BlobStore {
47  pub fn insert_part(&self, part: Arc<dyn BlobPart + Send + Sync>) -> Uuid {
48    let id = Uuid::new_v4();
49    let mut parts = self.parts.lock();
50    parts.insert(id, part);
51    id
52  }
53
54  pub fn get_part(&self, id: &Uuid) -> Option<Arc<dyn BlobPart + Send + Sync>> {
55    let parts = self.parts.lock();
56    let part = parts.get(id);
57    part.cloned()
58  }
59
60  pub fn remove_part(
61    &self,
62    id: &Uuid,
63  ) -> Option<Arc<dyn BlobPart + Send + Sync>> {
64    let mut parts = self.parts.lock();
65    parts.remove(id)
66  }
67
68  pub fn get_object_url(&self, mut url: Url) -> Option<Arc<Blob>> {
69    let blob_store = self.object_urls.lock();
70    url.set_fragment(None);
71    blob_store.get(&url).cloned()
72  }
73
74  pub fn insert_object_url(
75    &self,
76    blob: Blob,
77    maybe_location: Option<Url>,
78  ) -> Url {
79    let origin = if let Some(location) = maybe_location {
80      location.origin().ascii_serialization()
81    } else {
82      "null".to_string()
83    };
84    let id = Uuid::new_v4();
85    let url = Url::parse(&format!("blob:{origin}/{id}")).unwrap();
86
87    let mut blob_store = self.object_urls.lock();
88    blob_store.insert(url.clone(), Arc::new(blob));
89
90    url
91  }
92
93  pub fn remove_object_url(&self, url: &Url) {
94    let mut blob_store = self.object_urls.lock();
95    blob_store.remove(url);
96  }
97
98  pub fn clear(&self) {
99    self.parts.lock().clear();
100    self.object_urls.lock().clear();
101  }
102}
103
104#[derive(Debug)]
105pub struct Blob {
106  pub media_type: String,
107
108  pub parts: Vec<Arc<dyn BlobPart + Send + Sync>>,
109}
110
111impl Blob {
112  // TODO(lucacsonato): this should be a stream!
113  pub async fn read_all(&self) -> Vec<u8> {
114    let size = self.size();
115    let mut bytes = Vec::with_capacity(size);
116
117    for part in &self.parts {
118      let chunk = part.read().await;
119      bytes.extend_from_slice(chunk);
120    }
121
122    assert_eq!(bytes.len(), size);
123
124    bytes
125  }
126
127  fn size(&self) -> usize {
128    let mut total = 0;
129    for part in &self.parts {
130      total += part.size()
131    }
132    total
133  }
134}
135
136#[async_trait]
137pub trait BlobPart: Debug {
138  // TODO(lucacsonato): this should be a stream!
139  async fn read<'a>(&'a self) -> &'a [u8];
140  fn size(&self) -> usize;
141}
142
143#[derive(Debug)]
144pub struct InMemoryBlobPart(Vec<u8>);
145
146impl From<Vec<u8>> for InMemoryBlobPart {
147  fn from(vec: Vec<u8>) -> Self {
148    Self(vec)
149  }
150}
151
152#[async_trait]
153impl BlobPart for InMemoryBlobPart {
154  async fn read<'a>(&'a self) -> &'a [u8] {
155    &self.0
156  }
157
158  fn size(&self) -> usize {
159    self.0.len()
160  }
161}
162
163#[derive(Debug)]
164pub struct SlicedBlobPart {
165  part: Arc<dyn BlobPart + Send + Sync>,
166  start: usize,
167  len: usize,
168}
169
170#[async_trait]
171impl BlobPart for SlicedBlobPart {
172  async fn read<'a>(&'a self) -> &'a [u8] {
173    let original = self.part.read().await;
174    &original[self.start..self.start + self.len]
175  }
176
177  fn size(&self) -> usize {
178    self.len
179  }
180}
181
182#[op2]
183#[serde]
184pub fn op_blob_create_part(
185  state: &mut OpState,
186  #[buffer] data: JsBuffer,
187) -> Uuid {
188  let blob_store = state.borrow::<Arc<BlobStore>>();
189  let part = InMemoryBlobPart(data.to_vec());
190  blob_store.insert_part(Arc::new(part))
191}
192
193#[derive(Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct SliceOptions {
196  start: usize,
197  len: usize,
198}
199
200#[op2]
201#[serde]
202pub fn op_blob_slice_part(
203  state: &mut OpState,
204  #[serde] id: Uuid,
205  #[serde] options: SliceOptions,
206) -> Result<Uuid, BlobError> {
207  let blob_store = state.borrow::<Arc<BlobStore>>();
208  let part = blob_store
209    .get_part(&id)
210    .ok_or(BlobError::BlobPartNotFound)?;
211
212  let SliceOptions { start, len } = options;
213
214  let size = part.size();
215  if start + len > size {
216    return Err(BlobError::SizeLargerThanBlobPart);
217  }
218
219  let sliced_part = SlicedBlobPart { part, start, len };
220  let id = blob_store.insert_part(Arc::new(sliced_part));
221
222  Ok(id)
223}
224
225#[op2]
226pub async fn op_blob_read_part(
227  state: Rc<RefCell<OpState>>,
228  #[serde] id: Uuid,
229) -> Result<Uint8Array, BlobError> {
230  let part = {
231    let state = state.borrow();
232    let blob_store = state.borrow::<Arc<BlobStore>>();
233    blob_store.get_part(&id)
234  }
235  .ok_or(BlobError::BlobPartNotFound)?;
236  let buf = part.read().await;
237  Ok(Uint8Array::from(buf.to_vec()))
238}
239
240#[op2]
241pub fn op_blob_remove_part(state: &mut OpState, #[serde] id: Uuid) {
242  let blob_store = state.borrow::<Arc<BlobStore>>();
243  blob_store.remove_part(&id);
244}
245
246#[op2]
247#[string]
248pub fn op_blob_create_object_url(
249  state: &mut OpState,
250  #[string] media_type: String,
251  #[serde] part_ids: Vec<Uuid>,
252) -> Result<String, BlobError> {
253  let mut parts = Vec::with_capacity(part_ids.len());
254  let blob_store = state.borrow::<Arc<BlobStore>>();
255  for part_id in part_ids {
256    let part = blob_store
257      .get_part(&part_id)
258      .ok_or(BlobError::BlobPartNotFound)?;
259    parts.push(part);
260  }
261
262  let blob = Blob { media_type, parts };
263
264  let maybe_location = state.try_borrow::<Location>();
265  let blob_store = state.borrow::<Arc<BlobStore>>();
266
267  let url = blob_store
268    .insert_object_url(blob, maybe_location.map(|location| location.0.clone()));
269
270  Ok(url.into())
271}
272
273#[op2(fast)]
274pub fn op_blob_revoke_object_url(
275  state: &mut OpState,
276  #[string] url: &str,
277) -> Result<(), BlobError> {
278  let url = Url::parse(url)?;
279  let blob_store = state.borrow::<Arc<BlobStore>>();
280  blob_store.remove_object_url(&url);
281  Ok(())
282}
283
284#[derive(Serialize)]
285pub struct ReturnBlob {
286  pub media_type: String,
287  pub parts: Vec<ReturnBlobPart>,
288}
289
290#[derive(Serialize)]
291pub struct ReturnBlobPart {
292  pub uuid: Uuid,
293  pub size: usize,
294}
295
296#[op2]
297#[serde]
298pub fn op_blob_from_object_url(
299  state: &mut OpState,
300  #[string] url: String,
301) -> Result<Option<ReturnBlob>, BlobError> {
302  let url = Url::parse(&url)?;
303  if url.scheme() != "blob" {
304    return Ok(None);
305  }
306
307  let blob_store = state
308    .try_borrow::<Arc<BlobStore>>()
309    .ok_or(BlobError::BlobURLsNotSupported)?;
310  match blob_store.get_object_url(url) {
311    Some(blob) => {
312      let parts = blob
313        .parts
314        .iter()
315        .map(|part| ReturnBlobPart {
316          uuid: blob_store.insert_part(part.clone()),
317          size: part.size(),
318        })
319        .collect();
320      Ok(Some(ReturnBlob {
321        media_type: blob.media_type.clone(),
322        parts,
323      }))
324    }
325    _ => Ok(None),
326  }
327}