nixos/filesystems: don't silently ignore label when device is set (#361418)
[NixPkgs.git] / pkgs / build-support / node / fetch-npm-deps / src / cacache.rs
blob403c909dee115750057b7aaaaf916d56fcb1e718
1 use data_encoding::BASE64;
2 use digest::{Digest, Update};
3 use serde::{Deserialize, Serialize};
4 use sha1::Sha1;
5 use sha2::{Sha256, Sha512};
6 use std::{
7     fmt::Write as FmtWrite,
8     fs::{self, File},
9     io::Write,
10     path::PathBuf,
12 use url::Url;
14 #[allow(clippy::struct_field_names)]
15 #[derive(Serialize, Deserialize)]
16 pub(super) struct Key {
17     pub(super) key: String,
18     pub(super) integrity: String,
19     pub(super) time: u8,
20     pub(super) size: usize,
21     pub(super) metadata: Metadata,
24 #[derive(Serialize, Deserialize)]
25 pub(super) struct Metadata {
26     pub(super) url: Url,
27     pub(super) options: Options,
30 #[derive(Serialize, Deserialize)]
31 pub(super) struct Options {
32     pub(super) compress: bool,
35 pub struct Cache(PathBuf);
37 fn push_hash_segments(path: &mut PathBuf, hash: &str) {
38     path.push(&hash[0..2]);
39     path.push(&hash[2..4]);
40     path.push(&hash[4..]);
43 impl Cache {
44     pub fn new(path: PathBuf) -> Cache {
45         Cache(path)
46     }
48     pub fn init(&self) -> anyhow::Result<()> {
49         fs::create_dir_all(self.0.join("content-v2"))?;
50         fs::create_dir_all(self.0.join("index-v5"))?;
52         Ok(())
53     }
55     pub fn put(
56         &self,
57         key: String,
58         url: Url,
59         data: &[u8],
60         integrity: Option<String>,
61     ) -> anyhow::Result<()> {
62         let (algo, hash, integrity) = if let Some(integrity) = integrity {
63             let (algo, hash) = integrity
64                 .split_once('-')
65                 .expect("hash should be SRI format");
67             (algo.to_string(), BASE64.decode(hash.as_bytes())?, integrity)
68         } else {
69             let hash = Sha512::new().chain(data).finalize();
71             (
72                 String::from("sha512"),
73                 hash.to_vec(),
74                 format!("sha512-{}", BASE64.encode(&hash)),
75             )
76         };
78         let content_path = {
79             let mut p = self.0.join("content-v2");
81             p.push(algo);
83             push_hash_segments(
84                 &mut p,
85                 &hash.into_iter().fold(String::new(), |mut out, n| {
86                     let _ = write!(out, "{n:02x}");
87                     out
88                 }),
89             );
91             p
92         };
94         fs::create_dir_all(content_path.parent().unwrap())?;
96         fs::write(content_path, data)?;
98         let index_path = {
99             let mut p = self.0.join("index-v5");
101             push_hash_segments(
102                 &mut p,
103                 &format!("{:x}", Sha256::new().chain(&key).finalize()),
104             );
106             p
107         };
109         fs::create_dir_all(index_path.parent().unwrap())?;
111         let data = serde_json::to_string(&Key {
112             key,
113             integrity,
114             time: 0,
115             size: data.len(),
116             metadata: Metadata {
117                 url,
118                 options: Options { compress: true },
119             },
120         })?;
122         let mut file = File::options().append(true).create(true).open(index_path)?;
124         write!(file, "{:x}\t{data}", Sha1::new().chain(&data).finalize())?;
126         Ok(())
127     }