nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at devShellTools-shell 128 lines 3.1 kB view raw
1use data_encoding::BASE64; 2use digest::{Digest, Update}; 3use serde::{Deserialize, Serialize}; 4use sha1::Sha1; 5use sha2::{Sha256, Sha512}; 6use std::{ 7 fmt::Write as FmtWrite, 8 fs::{self, File}, 9 io::Write, 10 path::PathBuf, 11}; 12use url::Url; 13 14#[allow(clippy::struct_field_names)] 15#[derive(Serialize, Deserialize)] 16pub(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, 22} 23 24#[derive(Serialize, Deserialize)] 25pub(super) struct Metadata { 26 pub(super) url: Url, 27 pub(super) options: Options, 28} 29 30#[derive(Serialize, Deserialize)] 31pub(super) struct Options { 32 pub(super) compress: bool, 33} 34 35pub struct Cache(PathBuf); 36 37fn 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..]); 41} 42 43impl Cache { 44 pub fn new(path: PathBuf) -> Cache { 45 Cache(path) 46 } 47 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"))?; 51 52 Ok(()) 53 } 54 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"); 66 67 (algo.to_string(), BASE64.decode(hash.as_bytes())?, integrity) 68 } else { 69 let hash = Sha512::new().chain(data).finalize(); 70 71 ( 72 String::from("sha512"), 73 hash.to_vec(), 74 format!("sha512-{}", BASE64.encode(&hash)), 75 ) 76 }; 77 78 let content_path = { 79 let mut p = self.0.join("content-v2"); 80 81 p.push(algo); 82 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 ); 90 91 p 92 }; 93 94 fs::create_dir_all(content_path.parent().unwrap())?; 95 96 fs::write(content_path, data)?; 97 98 let index_path = { 99 let mut p = self.0.join("index-v5"); 100 101 push_hash_segments( 102 &mut p, 103 &format!("{:x}", Sha256::new().chain(&key).finalize()), 104 ); 105 106 p 107 }; 108 109 fs::create_dir_all(index_path.parent().unwrap())?; 110 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 })?; 121 122 let mut file = File::options().append(true).create(true).open(index_path)?; 123 124 write!(file, "{:x}\t{data}", Sha1::new().chain(&data).finalize())?; 125 126 Ok(()) 127 } 128}