Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182use std::fmt;
use unicode_normalization::UnicodeNormalization;
/// A validated, NFC-preserving virtual filename. It is never interpreted as a URL.#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]pub struct CanonicalPath(String);
impl CanonicalPath { pub fn parse(input: &str) -> Result<Self, PathDiagnostic> { if input.is_empty() || input.starts_with('/') || input.ends_with('/') { return Err(PathDiagnostic::Slash); } if input.contains('%') { return Err(PathDiagnostic::Percent); } if input.contains('\\') { return Err(PathDiagnostic::Backslash); } if input.contains('?') || input.contains('#') { return Err(PathDiagnostic::QueryOrFragment); } if input.chars().any(|c| c.is_control() || c == ':') { return Err(PathDiagnostic::ControlOrColon); } if input.nfc().collect::<String>() != input { return Err(PathDiagnostic::NotNfc); } for segment in input.split('/') { if segment.is_empty() || segment == "." || segment == ".." { return Err(PathDiagnostic::Segment); } } Ok(Self(input.to_owned())) }
pub fn as_str(&self) -> &str { &self.0 }
pub fn segments(&self) -> impl Iterator<Item = &str> { self.0.split('/') }}
impl fmt::Display for CanonicalPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]pub enum RootId { Mpd, Manifest, Models, Parts, P, OfficialParts, OfficialP,}
impl RootId { pub fn as_str(self) -> &'static str { match self { Self::Mpd => "mpd", Self::Manifest => "manifest", Self::Models => "models", Self::Parts => "parts", Self::P => "p", Self::OfficialParts => "official-parts", Self::OfficialP => "official-p", } }
pub fn case_insensitive(self) -> bool { !matches!(self, Self::Mpd | Self::Manifest) }}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum PathDiagnostic { Slash, Percent, Backslash, QueryOrFragment, ControlOrColon, NotNfc, Segment,}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]pub struct LookupEntry { pub root: RootId, pub path: CanonicalPath, pub target: String,}
/// Resolve according to PM-75's fixed source precedence./// The maps contain already validated entries and only perform filename lookup.pub fn resolve_precedence( path: &CanonicalPath, mpd: &[LookupEntry], manifest: &[LookupEntry], uploaded: &[LookupEntry], official: &[LookupEntry],) -> Option<LookupEntry> { exact(path, mpd) .or_else(|| exact(path, manifest)) .or_else(|| roots(path, uploaded, &[RootId::Models, RootId::Parts, RootId::P])) .or_else(|| roots(path, official, &[RootId::OfficialParts, RootId::OfficialP]))}
fn exact(path: &CanonicalPath, entries: &[LookupEntry]) -> Option<LookupEntry> { entries.iter().find(|e| e.path == *path).cloned()}
fn roots(path: &CanonicalPath, entries: &[LookupEntry], roots: &[RootId]) -> Option<LookupEntry> { roots.iter().find_map(|root| { entries .iter() .filter(|e| e.root == *root) .find(|e| { e.path == *path || (root.case_insensitive() && e.path.as_str().eq_ignore_ascii_case(path.as_str())) }) .cloned() })}
#[cfg(test)]mod tests { use super::*;
#[test] fn rejects_unsafe_and_non_nfc_paths() { for value in [ "", "/a", "a/", "a//b", "a/../b", "a/./b", "a%2Fb", "a\\b", "a:b", "a?b", "a#b", "a\u{0000}b", ] { assert!(CanonicalPath::parse(value).is_err(), "{value:?}"); } assert!(CanonicalPath::parse("cafe\u{301}").is_err()); assert_eq!(CanonicalPath::parse("café").unwrap().as_str(), "café"); }
#[test] fn precedence_and_ascii_case_policy_are_stable() { let path = CanonicalPath::parse("parts/3001.dat").unwrap(); let entry = |root, target: &str| LookupEntry { root, path: path.clone(), target: target.into(), }; let found = resolve_precedence(&path, &[], &[], &[entry(RootId::Models, "model")], &[]).unwrap(); assert_eq!(found.target, "model"); let upper = CanonicalPath::parse("PARTS/3001.DAT").unwrap(); assert!( resolve_precedence(&upper, &[], &[], &[entry(RootId::Models, "model")], &[]).is_some() ); let mpd = entry(RootId::Mpd, "mpd"); assert_eq!( resolve_precedence(&path, &[mpd], &[], &[], &[]) .unwrap() .target, "mpd" ); }}