Skip to content

Commit a05d03c

Browse files
committed
⚡ Add EntryName::sanitize and EntryName::from_{utf8,path,path_lossy}_preserve_root
1 parent 63433b6 commit a05d03c

1 file changed

Lines changed: 115 additions & 10 deletions

File tree

lib/src/entry/name.rs

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,7 @@ pub struct EntryName(String);
2525

2626
impl EntryName {
2727
fn new_from_utf8path(path: &Utf8Path) -> Self {
28-
let path = normalize_utf8path(path);
29-
let iter = path.components().filter_map(|c| match c {
30-
Utf8Component::Prefix(_)
31-
| Utf8Component::RootDir
32-
| Utf8Component::CurDir
33-
| Utf8Component::ParentDir => None,
34-
Utf8Component::Normal(p) => Some(p),
35-
});
36-
Self(join_with_capacity(iter, "/", path.as_str().len()))
28+
Self::new_from_utf8path_preserve_root(path).sanitize()
3729
}
3830

3931
#[inline]
@@ -71,7 +63,120 @@ impl EntryName {
7163

7264
#[inline]
7365
fn from_path_lossy(p: &Path) -> Self {
74-
Self::new_from_utf8(&p.to_string_lossy())
66+
Self::from_path_lossy_preserve_root(p).sanitize()
67+
}
68+
69+
/// Creates an [EntryName] from a path, preserving absolute path components.
70+
///
71+
/// This method is similar to the `From` implementations for path-like types, but preserves absolute path components.
72+
///
73+
/// # Examples
74+
///
75+
/// ```rust
76+
/// use libpna::EntryName;
77+
///
78+
/// assert_eq!("foo.txt", EntryName::from_utf8_preserve_root("foo.txt"));
79+
/// #[cfg(windows)]
80+
/// assert_eq!("\\foo.txt", EntryName::from_utf8_preserve_root("/foo.txt"));
81+
/// #[cfg(unix)]
82+
/// assert_eq!("/foo.txt", EntryName::from_utf8_preserve_root("/foo.txt"));
83+
/// assert_eq!("foo.txt", EntryName::from_utf8_preserve_root("./foo.txt"));
84+
/// #[cfg(windows)]
85+
/// assert_eq!("..\\foo.txt", EntryName::from_utf8_preserve_root("../foo.txt"));
86+
/// #[cfg(unix)]
87+
/// assert_eq!("../foo.txt", EntryName::from_utf8_preserve_root("../foo.txt"));
88+
/// assert_eq!("foo.txt", EntryName::from_utf8_preserve_root("bar/../foo.txt"));
89+
/// ```
90+
#[inline]
91+
pub fn from_utf8_preserve_root(path: &str) -> Self {
92+
Self::new_from_utf8path_preserve_root(Utf8Path::new(path))
93+
}
94+
95+
#[inline]
96+
fn new_from_utf8path_preserve_root(path: &Utf8Path) -> Self {
97+
let path = normalize_utf8path(path);
98+
Self(path.into_string())
99+
}
100+
101+
/// Creates an [EntryName] from a path, preserving absolute path components.
102+
///
103+
/// This method is similar to the `From` implementations for path-like types, but preserves absolute path components.
104+
///
105+
/// # Examples
106+
///
107+
/// ```rust
108+
/// use libpna::EntryName;
109+
///
110+
/// assert_eq!("foo.txt", EntryName::from_path_preserve_root("foo.txt".as_ref()).unwrap());
111+
/// #[cfg(windows)]
112+
/// assert_eq!("\\foo.txt", EntryName::from_path_preserve_root("/foo.txt".as_ref()).unwrap());
113+
/// #[cfg(unix)]
114+
/// assert_eq!("/foo.txt", EntryName::from_path_preserve_root("/foo.txt".as_ref()).unwrap());
115+
/// assert_eq!("foo.txt", EntryName::from_path_preserve_root("./foo.txt".as_ref()).unwrap());
116+
/// #[cfg(windows)]
117+
/// assert_eq!("..\\foo.txt", EntryName::from_path_preserve_root("../foo.txt".as_ref()).unwrap());
118+
/// #[cfg(unix)]
119+
/// assert_eq!("../foo.txt", EntryName::from_path_preserve_root("../foo.txt".as_ref()).unwrap());
120+
/// assert_eq!("foo.txt", EntryName::from_path_preserve_root("bar/../foo.txt".as_ref()).unwrap());
121+
/// ```
122+
#[inline]
123+
pub fn from_path_preserve_root(name: &Path) -> Result<Self, EntryNameError> {
124+
let path = str::from_utf8(name.as_os_str().as_encoded_bytes())?;
125+
Ok(Self::new_from_utf8path_preserve_root(Utf8Path::new(path)))
126+
}
127+
128+
/// Creates an [EntryName] from a path, preserving absolute path components.
129+
///
130+
/// This method is similar to the `From` implementations for path-like types, but preserves absolute path components.
131+
///
132+
/// # Errors
133+
///
134+
/// Returns an [`EntryNameError`] if it cannot be represented as valid UTF-8.
135+
///
136+
/// # Examples
137+
///
138+
/// ```rust
139+
/// use libpna::EntryName;
140+
///
141+
/// assert_eq!("foo.txt", EntryName::from_path_lossy_preserve_root("foo.txt".as_ref()));
142+
/// #[cfg(windows)]
143+
/// assert_eq!("\\foo.txt", EntryName::from_path_lossy_preserve_root("/foo.txt".as_ref()));
144+
/// #[cfg(unix)]
145+
/// assert_eq!("/foo.txt", EntryName::from_path_lossy_preserve_root("/foo.txt".as_ref()));
146+
/// assert_eq!("foo.txt", EntryName::from_path_lossy_preserve_root("./foo.txt".as_ref()));
147+
/// #[cfg(windows)]
148+
/// assert_eq!("..\\foo.txt", EntryName::from_path_lossy_preserve_root("../foo.txt".as_ref()));
149+
/// #[cfg(unix)]
150+
/// assert_eq!("../foo.txt", EntryName::from_path_lossy_preserve_root("../foo.txt".as_ref()));
151+
/// assert_eq!("foo.txt", EntryName::from_path_lossy_preserve_root("bar/../foo.txt".as_ref()));
152+
/// ```
153+
#[inline]
154+
pub fn from_path_lossy_preserve_root(name: &Path) -> Self {
155+
Self::new_from_utf8path_preserve_root(Utf8Path::new(&name.to_string_lossy()))
156+
}
157+
158+
/// Returns a sanitized copy of this entry name that contains only normal components.
159+
///
160+
/// Sanitization discards prefixes, root separators, `.` and `..` segments so the
161+
/// resulting entry name is always relative and safe to embed in an archive.
162+
///
163+
/// # Examples
164+
///
165+
/// ```rust
166+
/// use libpna::EntryName;
167+
///
168+
/// let name = EntryName::from_utf8_preserve_root("/var/../tmp/./log");
169+
/// assert_eq!("tmp/log", name.sanitize());
170+
/// ```
171+
#[inline]
172+
pub fn sanitize(&self) -> Self {
173+
let path = Utf8Path::new(&self.0);
174+
Self(join_with_capacity(
175+
path.components()
176+
.filter(|c| matches!(c, Utf8Component::Normal(_))),
177+
"/",
178+
path.as_str().len(),
179+
))
75180
}
76181

77182
#[inline]

0 commit comments

Comments
 (0)