diff --git a/nix-rust/src/error.rs b/nix-rust/src/error.rs index 9d6fa4c51..5717a7a47 100644 --- a/nix-rust/src/error.rs +++ b/nix-rust/src/error.rs @@ -1,9 +1,13 @@ +use std::fmt; + #[derive(Debug)] pub enum Error { InvalidPath(crate::store::StorePath), BadStorePath(std::path::PathBuf), BadNarInfo, BadBase32, + StorePathNameTooLong, + BadStorePathName, IOError(std::io::Error), HttpError(reqwest::Error), Misc(String), @@ -22,19 +26,30 @@ impl From for Error { } } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidPath(_) => write!(f, "invalid path"), + Error::BadNarInfo => write!(f, ".narinfo file is corrupt"), + Error::BadStorePath(path) => write!(f, "path '{}' is not a store path", path.display()), + Error::BadBase32 => write!(f, "invalid base32 string"), + Error::StorePathNameTooLong => { + write!(f, "store path name is longer than 211 characters") + } + Error::BadStorePathName => write!(f, "store path name contains forbidden character"), + Error::IOError(err) => write!(f, "I/O error: {}", err), + Error::HttpError(err) => write!(f, "HTTP error: {}", err), + Error::Foreign(_) => write!(f, ""), // FIXME + Error::Misc(s) => write!(f, "{}", s), + } + } +} + impl From for CppException { fn from(err: Error) -> Self { match err { - Error::InvalidPath(_) => unsafe { make_error("invalid path") }, // FIXME - Error::BadNarInfo => unsafe { make_error(".narinfo file is corrupt") }, // FIXME - Error::BadStorePath(path) => unsafe { - make_error(&format!("path '{}' is not a store path", path.display())) - }, // FIXME - Error::BadBase32 => unsafe { make_error("invalid base32 string") }, // FIXME - Error::IOError(err) => unsafe { make_error(&err.to_string()) }, - Error::HttpError(err) => unsafe { make_error(&err.to_string()) }, Error::Foreign(ex) => ex, - Error::Misc(s) => unsafe { make_error(&s) }, + _ => unsafe { make_error(&err.to_string()) }, } } } diff --git a/nix-rust/src/store/mod.rs b/nix-rust/src/store/mod.rs index a0d1c72ab..85355b594 100644 --- a/nix-rust/src/store/mod.rs +++ b/nix-rust/src/store/mod.rs @@ -1,7 +1,9 @@ mod binary_cache_store; +mod path; mod path_info; mod store; pub use binary_cache_store::BinaryCacheStore; +pub use path::{StorePath, StorePathHash, StorePathName}; pub use path_info::PathInfo; -pub use store::{Store, StorePath}; +pub use store::Store; diff --git a/nix-rust/src/store/path.rs b/nix-rust/src/store/path.rs new file mode 100644 index 000000000..4b866f9b4 --- /dev/null +++ b/nix-rust/src/store/path.rs @@ -0,0 +1,100 @@ +use crate::error::Error; +use crate::util::base32; +use std::fmt; +use std::path::Path; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct StorePath { + pub hash: StorePathHash, + pub name: StorePathName, +} + +pub const STORE_PATH_HASH_BYTES: usize = 20; +pub const STORE_PATH_HASH_CHARS: usize = 32; + +impl StorePath { + pub fn new(path: &Path, _store_dir: &str) -> Result { + // FIXME: check store_dir + Self::new_from_base_name( + path.file_name() + .ok_or(Error::BadStorePath(path.into()))? + .to_str() + .ok_or(Error::BadStorePath(path.into()))?, + ) + } + + pub fn new_from_base_name(base_name: &str) -> Result { + if base_name.len() < STORE_PATH_HASH_CHARS + 2 + || base_name.as_bytes()[STORE_PATH_HASH_CHARS] != '-' as u8 + { + return Err(Error::BadStorePath(base_name.into())); + } + + Ok(StorePath { + hash: StorePathHash::new(&base_name[0..STORE_PATH_HASH_CHARS])?, + name: StorePathName::new(&base_name[STORE_PATH_HASH_CHARS + 1..])?, + }) + } +} + +impl fmt::Display for StorePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}-{}", self.hash, self.name) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct StorePathHash([u8; STORE_PATH_HASH_BYTES]); + +impl StorePathHash { + pub fn new(s: &str) -> Result { + assert_eq!(s.len(), STORE_PATH_HASH_CHARS); + let v = base32::decode(s)?; + assert_eq!(v.len(), STORE_PATH_HASH_BYTES); + let mut bytes: [u8; 20] = Default::default(); + bytes.copy_from_slice(&v[0..STORE_PATH_HASH_BYTES]); + Ok(Self(bytes)) + } +} + +impl fmt::Display for StorePathHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&base32::encode(&self.0)) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct StorePathName(String); + +impl StorePathName { + pub fn new(s: &str) -> Result { + if s.len() > 211 { + return Err(Error::StorePathNameTooLong); + } + + if s.starts_with('.') + || !s.chars().all(|c| { + c.is_ascii_alphabetic() + || c.is_ascii_digit() + || c == '+' + || c == '-' + || c == '.' + || c == '_' + || c == '?' + || c == '=' + }) + { + return Err(Error::BadStorePathName); + } + + Ok(Self(s.to_string())) + } +} + +impl fmt::Display for StorePathName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +// FIXME: add tests diff --git a/nix-rust/src/store/path_info.rs b/nix-rust/src/store/path_info.rs index 2759e03d4..c2903ed29 100644 --- a/nix-rust/src/store/path_info.rs +++ b/nix-rust/src/store/path_info.rs @@ -43,11 +43,11 @@ impl PathInfo { } else if name == "References" { if !value.is_empty() { for r in value.split(' ') { - references.insert(StorePath::new_short(r)?); + references.insert(StorePath::new_from_base_name(r)?); } } } else if name == "Deriver" { - deriver = Some(StorePath::new_short(value)?); + deriver = Some(StorePath::new_from_base_name(value)?); } else if name == "URL" { url = Some(value.into()); } else if name == "Compression" { diff --git a/nix-rust/src/store/store.rs b/nix-rust/src/store/store.rs index 32949a078..c33dc4a90 100644 --- a/nix-rust/src/store/store.rs +++ b/nix-rust/src/store/store.rs @@ -1,56 +1,8 @@ -use super::PathInfo; +use super::{PathInfo, StorePath}; use crate::Error; use std::collections::{BTreeMap, BTreeSet}; use std::path::Path; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct StorePath { - pub hash: String, - pub name: String, -} - -pub const STORE_PATH_HASH_CHARS: usize = 32; - -impl StorePath { - pub fn new(path: &Path, _store_dir: &str) -> Result { - // FIXME: check store_dir - Self::new_short( - path.file_name() - .ok_or(Error::BadStorePath(path.into()))? - .to_str() - .ok_or(Error::BadStorePath(path.into()))?, - ) - } - - pub fn new_short(base_name: &str) -> Result { - if base_name.len() < STORE_PATH_HASH_CHARS + 2 - || base_name.as_bytes()[STORE_PATH_HASH_CHARS] != '-' as u8 - { - return Err(Error::BadStorePath(base_name.into())); - } - - // FIXME: validate name - - Ok(StorePath { - hash: base_name[0..STORE_PATH_HASH_CHARS].to_string(), - name: base_name[STORE_PATH_HASH_CHARS + 1..].to_string(), - }) - } -} - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct StorePathHash { - bytes: [u8; 20], -} - -/* -impl StorePathHash { - pub fn to_base32(&self) -> String { - "7h7qgvs4kgzsn8a6rb273saxyqh4jxlz".to_string() - } -} -*/ - pub trait Store: Send + Sync { fn store_dir(&self) -> &str { "/nix/store"