nix_compat/nix_http/
mod.rs1use bstr::ByteSlice;
2use tracing::trace;
3
4use crate::nixbase32;
5
6pub const MIME_TYPE_NAR: &str = "application/x-nix-nar";
8pub const MIME_TYPE_NARINFO: &str = "text/x-nix-narinfo";
10pub const MIME_TYPE_CACHE_INFO: &str = "text/x-nix-cache-info";
12pub const MIME_TYPE_NAR_LISTING: &str = "application/json";
14
15pub fn parse_nar_str<S>(s: &S) -> Option<([u8; 32], &[u8])>
19where
20 S: AsRef<[u8]> + ?Sized,
21{
22 if s.as_ref().len() < 52 + 4 {
23 trace!("nar_str too short");
24 return None;
25 }
26 let (hash_str, suffix) = s.as_ref().split_at(52);
27 let hash_str_fixed: [u8; 52] = hash_str.try_into().unwrap();
29
30 let compression_suffix = suffix
32 .as_bstr()
33 .strip_prefix(b".nar")
34 .ok_or_else(|| {
35 trace!("suffix does not start with .nar");
36 })
37 .ok()?;
38
39 let digest = nixbase32::decode_fixed(hash_str_fixed)
40 .inspect_err(|err| {
41 trace!(%err, "invalid nixbase32 encoding");
42 })
43 .ok()?;
44
45 Some((digest, compression_suffix))
46}
47
48#[derive(Debug, PartialEq, Eq)]
49pub enum RequestType {
50 Narinfo,
51 Listing,
52}
53
54pub fn parse_outhash_str(s: impl AsRef<[u8]>) -> Option<([u8; 20], RequestType)> {
57 if s.as_ref().len() < 32 + 3 {
58 trace!("outhash_str too short");
59 return None;
60 }
61
62 let (hash_str, suffix) = s.as_ref().split_at(32);
63 let hash_str_fixed: [u8; 32] = hash_str.try_into().unwrap();
65
66 let request_type = match suffix {
67 b".narinfo" => RequestType::Narinfo,
68 b".ls" => RequestType::Listing,
69 _ => {
70 trace!("invalid string, no .narinfo or .ls suffix");
71 return None;
72 }
73 };
74
75 let digest = nixbase32::decode_fixed(hash_str_fixed)
76 .inspect_err(|err| {
77 trace!(%err, "invalid nixbase32 encoding");
78 })
79 .ok()?;
80
81 Some((digest, request_type))
82}
83
84#[cfg(test)]
85mod test {
86 use crate::nix_http::RequestType;
87
88 use super::{parse_nar_str, parse_outhash_str};
89 use hex_literal::hex;
90
91 #[test]
92 fn parse_nar_str_success() {
93 assert_eq!(
94 (
95 hex!("13a8cf7ca57f68a9f1752acee36a72a55187d3a954443c112818926f26109d91"),
96 "".as_bytes()
97 ),
98 parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar").unwrap()
99 );
100
101 assert_eq!(
102 (
103 hex!("13a8cf7ca57f68a9f1752acee36a72a55187d3a954443c112818926f26109d91"),
104 ".xz".as_bytes()
105 ),
106 parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar.xz").unwrap()
107 )
108 }
109
110 #[test]
111 fn parse_nar_str_failure() {
112 assert!(parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0").is_none());
113 assert!(
114 parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0š¦.nar").is_none()
115 )
116 }
117 #[test]
118 fn parse_outhash_str_success() {
119 assert_eq!(
120 (
121 hex!("8a12321522fd91efbd60ebb2481af88580f61600"),
122 RequestType::Narinfo
123 ),
124 parse_outhash_str("00bgd045z0d4icpbc2yyz4gx48ak44la.narinfo").unwrap()
125 );
126 assert_eq!(
127 (
128 hex!("8a12321522fd91efbd60ebb2481af88580f61600"),
129 RequestType::Listing
130 ),
131 parse_outhash_str("00bgd045z0d4icpbc2yyz4gx48ak44la.ls").unwrap()
132 );
133 }
134
135 #[test]
136 fn parse_outhash_str_failure() {
137 assert!(parse_outhash_str("00bgd045z0d4icpbc2yyz4gx48ak44la").is_none());
138 assert!(parse_outhash_str("/00bgd045z0d4icpbc2yyz4gx48ak44la").is_none());
139 assert!(parse_outhash_str("000000").is_none());
140 assert!(parse_outhash_str("00bgd045z0d4icpbc2yyz4gx48ak44lš¦.narinfo").is_none());
141 assert!(parse_outhash_str("00bgd045z0d4icpbc2yyz4gx48ak44la.nah").is_none());
142 }
143}