Skip to main content

turbopack_core/
data_uri_source.rs

1use anyhow::{Result, bail};
2use turbo_rcstr::RcStr;
3use turbo_tasks::{ResolvedVc, Vc};
4use turbo_tasks_fs::{File, FileContent, FileSystemPath, rope::Rope};
5use turbo_tasks_hash::{encode_hex, hash_xxh3_hash64};
6
7use crate::{
8    asset::{Asset, AssetContent},
9    ident::AssetIdent,
10    source::Source,
11};
12
13/// The raw [Source]. It represents raw content from a path without any
14/// references to other [Source]s.
15#[turbo_tasks::value]
16pub struct DataUriSource {
17    media_type: RcStr,
18    encoding: RcStr,
19    data: ResolvedVc<RcStr>,
20    lookup_path: FileSystemPath,
21}
22
23#[turbo_tasks::value_impl]
24impl DataUriSource {
25    #[turbo_tasks::function]
26    pub fn new(
27        media_type: RcStr,
28        encoding: RcStr,
29        data: ResolvedVc<RcStr>,
30        lookup_path: FileSystemPath,
31    ) -> Vc<Self> {
32        Self::cell(DataUriSource {
33            media_type,
34            encoding,
35            data,
36            lookup_path,
37        })
38    }
39}
40
41#[turbo_tasks::value_impl]
42impl Source for DataUriSource {
43    #[turbo_tasks::function]
44    async fn description(&self) -> Result<Vc<RcStr>> {
45        let media_type = &self.media_type;
46        let encoding = &self.encoding;
47        let data = self.data.await?;
48        // Build the data URI prefix for identification; data URIs can be very
49        // long so we cap the total display at 50 characters.
50        let sep = if encoding.is_empty() { "" } else { ";" };
51        let full_with_data = format!("data:{media_type}{sep}{encoding},{data}");
52        let prefix: String = full_with_data.chars().take(50).collect();
53        let ellipsis = if full_with_data.len() > 50 { "..." } else { "" };
54        Ok(Vc::cell(
55            format!("data URI content ({prefix}{ellipsis})").into(),
56        ))
57    }
58
59    #[turbo_tasks::function]
60    async fn ident(&self) -> Result<Vc<AssetIdent>> {
61        let content_type = self.media_type.split(";").next().unwrap().into();
62        let filename = format!(
63            "data:{}",
64            &encode_hex(hash_xxh3_hash64((
65                &*self.data.await?,
66                &self.media_type,
67                &self.encoding
68            )))[0..6]
69        );
70        Ok(
71            AssetIdent::from_path(self.lookup_path.join(&filename)?)
72                .with_content_type(content_type),
73        )
74    }
75}
76
77#[turbo_tasks::value_impl]
78impl Asset for DataUriSource {
79    #[turbo_tasks::function]
80    async fn content(&self) -> Result<Vc<AssetContent>> {
81        let data = self.data.await?;
82        let rope = if self.encoding == "base64" {
83            let decoded = data_encoding::BASE64.decode(data.as_bytes())?;
84            // TODO this should read self.media_type and potentially use a different encoding
85            Rope::from(decoded)
86        } else if self.encoding.is_empty() {
87            let decoded = urlencoding::decode(data.as_str())?.into_owned();
88            Rope::from(decoded)
89        } else {
90            bail!("Unsupported data URL encoding: {}", self.encoding);
91        };
92        Ok(AssetContent::file(
93            FileContent::from(File::from(rope)).cell(),
94        ))
95    }
96}