diff options
author | Florian Klink <flokli@flokli.de> | 2024-08-21T08·06+0300 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2024-08-21T09·40+0000 |
commit | e03ea11badbf40971d6bf87eede33fe3b046c98b (patch) | |
tree | d520dedadeb8193b2da323adb2623a269217951c /tvix/nix-compat/src | |
parent | 2357079891819c679821ea58a7715de7be431aaa (diff) |
feat(nix-compat/nix_http): init parse_nar[info]_str r/8547
This moves the URL component parsing code we had in nar-bridge to nix-compat. We change the function signature to return an Option, not a Result<_, StatusCode>. This allows returning more appropriate error codes, as we can ok_or(…) at the callsite, which we now do: on an upload to an invalid path, we now return "unauthorized", while on a GET/HEAD, we return "not found". This also adds support to parse compression suffixes. While not supported in nar-bridge, other users of nix-compat might very well want to parse these paths. Also fix the error message when parsing NAR urls, it mentioned 32, not 52, which is a copypasta error from the narinfo URL parsing code. Change-Id: Id1be9a8044814b54ce68b125c52dfe933c9c4f74 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12260 Reviewed-by: raitobezarius <tvl@lahfa.xyz> Autosubmit: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/nix-compat/src')
-rw-r--r-- | tvix/nix-compat/src/lib.rs | 1 | ||||
-rw-r--r-- | tvix/nix-compat/src/nix_http/mod.rs | 108 |
2 files changed, 109 insertions, 0 deletions
diff --git a/tvix/nix-compat/src/lib.rs b/tvix/nix-compat/src/lib.rs index cc1ee082be38..1410a8264240 100644 --- a/tvix/nix-compat/src/lib.rs +++ b/tvix/nix-compat/src/lib.rs @@ -2,6 +2,7 @@ pub(crate) mod aterm; pub mod derivation; pub mod nar; pub mod narinfo; +pub mod nix_http; pub mod nixbase32; pub mod nixcpp; pub mod nixhash; diff --git a/tvix/nix-compat/src/nix_http/mod.rs b/tvix/nix-compat/src/nix_http/mod.rs new file mode 100644 index 000000000000..cbb629784612 --- /dev/null +++ b/tvix/nix-compat/src/nix_http/mod.rs @@ -0,0 +1,108 @@ +use tracing::trace; + +use crate::nixbase32; + +/// Parses a `14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar` +/// string and returns the nixbase32-decoded digest, as well as the compression +/// suffix (which might be empty). +pub fn parse_nar_str(s: &str) -> Option<([u8; 32], &str)> { + if !s.is_char_boundary(52) { + trace!("invalid string, no char boundary at 52"); + return None; + } + + let (hash_str, suffix) = s.split_at(52); + + // we know hash_str is 52 bytes, so it's ok to unwrap here. + let hash_str_fixed: [u8; 52] = hash_str.as_bytes().try_into().unwrap(); + + match suffix.strip_prefix(".nar") { + Some(compression_suffix) => match nixbase32::decode_fixed(hash_str_fixed) { + Err(e) => { + trace!(err=%e, "invalid nixbase32 encoding"); + None + } + Ok(digest) => Some((digest, compression_suffix)), + }, + None => { + trace!("no .nar suffix"); + None + } + } +} + +/// Parses a `3mzh8lvgbynm9daj7c82k2sfsfhrsfsy.narinfo` string and returns the +/// nixbase32-decoded digest. +pub fn parse_narinfo_str(s: &str) -> Option<[u8; 20]> { + if !s.is_char_boundary(32) { + trace!("invalid string, no char boundary at 32"); + return None; + } + + match s.split_at(32) { + (hash_str, ".narinfo") => { + // we know this is 32 bytes, so it's ok to unwrap here. + let hash_str_fixed: [u8; 32] = hash_str.as_bytes().try_into().unwrap(); + + match nixbase32::decode_fixed(hash_str_fixed) { + Err(e) => { + trace!(err=%e, "invalid nixbase32 encoding"); + None + } + Ok(digest) => Some(digest), + } + } + _ => { + trace!("invalid string, no .narinfo suffix"); + None + } + } +} + +#[cfg(test)] +mod test { + use super::{parse_nar_str, parse_narinfo_str}; + use hex_literal::hex; + + #[test] + fn parse_nar_str_success() { + assert_eq!( + ( + hex!("13a8cf7ca57f68a9f1752acee36a72a55187d3a954443c112818926f26109d91"), + "" + ), + parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar").unwrap() + ); + + assert_eq!( + ( + hex!("13a8cf7ca57f68a9f1752acee36a72a55187d3a954443c112818926f26109d91"), + ".xz" + ), + parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0k.nar.xz").unwrap() + ) + } + + #[test] + fn parse_nar_str_failure() { + assert!(parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0").is_none()); + assert!( + parse_nar_str("14cx20k6z4hq508kqi2lm79qfld5f9mf7kiafpqsjs3zlmycza0🦊.nar").is_none() + ) + } + #[test] + fn parse_narinfo_str_success() { + assert_eq!( + hex!("8a12321522fd91efbd60ebb2481af88580f61600"), + parse_narinfo_str("00bgd045z0d4icpbc2yyz4gx48ak44la.narinfo").unwrap() + ); + } + + #[test] + fn parse_narinfo_str_failure() { + assert!(parse_narinfo_str("00bgd045z0d4icpbc2yyz4gx48ak44la").is_none()); + assert!(parse_narinfo_str("/00bgd045z0d4icpbc2yyz4gx48ak44la").is_none()); + assert!(parse_narinfo_str("000000").is_none()); + assert!(parse_narinfo_str("00bgd045z0d4icpbc2yyz4gx48ak44l🦊.narinfo").is_none()); + } +} |