nix_compat/nix_http/
mod.rs

1use tracing::trace;
2
3use crate::nixbase32;
4
5/// The mime type used for NAR files, both compressed and uncompressed
6pub const MIME_TYPE_NAR: &str = "application/x-nix-nar";
7/// The mime type used for NARInfo files
8pub const MIME_TYPE_NARINFO: &str = "text/x-nix-narinfo";
9/// The mime type used for the `nix-cache-info` file
10pub const MIME_TYPE_CACHE_INFO: &str = "text/x-nix-cache-info";
11
12/// Parses a `14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar`
13/// string and returns the nixbase32-decoded digest, as well as the compression
14/// suffix (which might be empty).
15pub fn parse_nar_str(s: &str) -> Option<([u8; 32], &str)> {
16    if !s.is_char_boundary(52) {
17        trace!("invalid string, no char boundary at 52");
18        return None;
19    }
20
21    let (hash_str, suffix) = s.split_at(52);
22
23    // we know hash_str is 52 bytes, so it's ok to unwrap here.
24    let hash_str_fixed: [u8; 52] = hash_str.as_bytes().try_into().unwrap();
25
26    match suffix.strip_prefix(".nar") {
27        Some(compression_suffix) => match nixbase32::decode_fixed(hash_str_fixed) {
28            Err(e) => {
29                trace!(err=%e, "invalid nixbase32 encoding");
30                None
31            }
32            Ok(digest) => Some((digest, compression_suffix)),
33        },
34        None => {
35            trace!("no .nar suffix");
36            None
37        }
38    }
39}
40
41/// Parses a `3mzh8lvgbynm9daj7c82k2sfsfhrsfsy.narinfo` string and returns the
42/// nixbase32-decoded digest.
43pub fn parse_narinfo_str(s: &str) -> Option<[u8; 20]> {
44    if !s.is_char_boundary(32) {
45        trace!("invalid string, no char boundary at 32");
46        return None;
47    }
48
49    match s.split_at(32) {
50        (hash_str, ".narinfo") => {
51            // we know this is 32 bytes, so it's ok to unwrap here.
52            let hash_str_fixed: [u8; 32] = hash_str.as_bytes().try_into().unwrap();
53
54            match nixbase32::decode_fixed(hash_str_fixed) {
55                Err(e) => {
56                    trace!(err=%e, "invalid nixbase32 encoding");
57                    None
58                }
59                Ok(digest) => Some(digest),
60            }
61        }
62        _ => {
63            trace!("invalid string, no .narinfo suffix");
64            None
65        }
66    }
67}
68
69#[cfg(test)]
70mod test {
71    use super::{parse_nar_str, parse_narinfo_str};
72    use hex_literal::hex;
73
74    #[test]
75    fn parse_nar_str_success() {
76        assert_eq!(
77            (
78                hex!("13a8cf7ca57f68a9f1752acee36a72a55187d3a954443c112818926f26109d91"),
79                ""
80            ),
81            parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar").unwrap()
82        );
83
84        assert_eq!(
85            (
86                hex!("13a8cf7ca57f68a9f1752acee36a72a55187d3a954443c112818926f26109d91"),
87                ".xz"
88            ),
89            parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar.xz").unwrap()
90        )
91    }
92
93    #[test]
94    fn parse_nar_str_failure() {
95        assert!(parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0").is_none());
96        assert!(
97            parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0🦊.nar").is_none()
98        )
99    }
100    #[test]
101    fn parse_narinfo_str_success() {
102        assert_eq!(
103            hex!("8a12321522fd91efbd60ebb2481af88580f61600"),
104            parse_narinfo_str("00bgd045z0d4icpbc2yyz4gx48ak44la.narinfo").unwrap()
105        );
106    }
107
108    #[test]
109    fn parse_narinfo_str_failure() {
110        assert!(parse_narinfo_str("00bgd045z0d4icpbc2yyz4gx48ak44la").is_none());
111        assert!(parse_narinfo_str("/00bgd045z0d4icpbc2yyz4gx48ak44la").is_none());
112        assert!(parse_narinfo_str("000000").is_none());
113        assert!(parse_narinfo_str("00bgd045z0d4icpbc2yyz4gx48ak44l🦊.narinfo").is_none());
114    }
115}