1use std::error::Error;
4use std::ffi::OsStr;
5#[cfg(target_os = "windows")]
6use std::ffi::OsString;
7#[cfg(target_family = "unix")]
8use std::os::unix::ffi::OsStrExt;
9use std::path::{Path, PathBuf};
10
11use mime_guess;
12use percent_encoding as pct;
13
14use crate::DavError;
15
16const PATH_ENCODE_SET: &pct::AsciiSet = &pct::NON_ALPHANUMERIC
19 .remove(b'-')
20 .remove(b'_')
21 .remove(b'.')
22 .remove(b'~')
23 .remove(b'/');
24
25#[derive(Clone)]
27pub struct DavPath {
28 fullpath: Vec<u8>,
29 pfxlen: Option<usize>,
30}
31
32pub struct DavPathRef {
35 fullpath: [u8],
36}
37
38impl std::fmt::Display for DavPath {
39 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
40 write!(f, "{}", self.as_pathbuf().display())
41 }
42}
43
44impl std::fmt::Debug for DavPath {
45 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
46 write!(f, "{:?}", &self.as_url_string_with_prefix_debug())
47 }
48}
49
50#[derive(Debug)]
52pub enum ParseError {
53 InvalidPath,
55 PrefixMismatch,
57 ForbiddenPath,
59}
60
61impl Error for ParseError {
62 fn description(&self) -> &str {
63 "DavPath parse error"
64 }
65 fn cause(&self) -> Option<&dyn Error> {
66 None
67 }
68}
69
70impl std::fmt::Display for ParseError {
71 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
72 write!(f, "{:?}", self)
73 }
74}
75
76impl From<ParseError> for DavError {
77 fn from(e: ParseError) -> Self {
78 match e {
79 ParseError::InvalidPath => DavError::InvalidPath,
80 ParseError::PrefixMismatch => DavError::IllegalPath,
81 ParseError::ForbiddenPath => DavError::ForbiddenPath,
82 }
83 }
84}
85
86fn encode_path(src: &[u8]) -> Vec<u8> {
88 pct::percent_encode(src, PATH_ENCODE_SET)
89 .to_string()
90 .into_bytes()
91}
92
93fn normalize_path(rp: &[u8]) -> Result<Vec<u8>, ParseError> {
102 if rp.iter().any(|&x| !(32..=126).contains(&x)) {
104 return Err(ParseError::InvalidPath);
105 }
106
107 let mut rawpath = rp;
109 if let Some(pos) = rawpath.iter().position(|&x| x == b'?' || x == b'#') {
110 if rawpath[pos] == b'#' {
111 return Err(ParseError::InvalidPath);
112 }
113 rawpath = &rawpath[..pos];
114 }
115
116 if rawpath.is_empty() || rawpath[0] != b'/' {
118 return Err(ParseError::InvalidPath);
119 }
120
121 let isdir = matches!(rawpath.last(), Some(x) if *x == b'/');
123 let segments: Vec<Vec<u8>> = rawpath
124 .split(|c| *c == b'/')
125 .map(|segment| pct::percent_decode(segment).collect())
126 .collect();
127 let mut v: Vec<&[u8]> = Vec::new();
128 for segment in &segments {
129 match &segment[..] {
130 b"." | b"" => {}
131 b".." => {
132 if v.len() < 2 {
133 return Err(ParseError::ForbiddenPath);
134 }
135 v.pop();
136 v.pop();
137 }
138 s => {
139 if s.iter().any(|x| *x == 0 || *x == b'/') {
141 return Err(ParseError::InvalidPath);
142 }
143 v.push(b"/");
144 v.push(s);
145 }
146 }
147 }
148 if isdir || v.is_empty() {
149 v.push(b"/");
150 }
151 Ok(v.join(&b""[..]))
152}
153
154impl PartialEq for DavPath {
156 fn eq(&self, rhs: &DavPath) -> bool {
157 let mut a = self.fullpath.as_slice();
158 if a.len() > 1 && a.ends_with(b"/") {
159 a = &a[..a.len() - 1];
160 }
161 let mut b = rhs.fullpath.as_slice();
162 if b.len() > 1 && b.ends_with(b"/") {
163 b = &b[..b.len() - 1];
164 }
165 a == b
166 }
167}
168
169impl DavPath {
170 pub fn new(src: &str) -> Result<DavPath, ParseError> {
172 let path = normalize_path(src.as_bytes())?;
173 Ok(DavPath {
174 fullpath: path.to_vec(),
175 pfxlen: None,
176 })
177 }
178
179 pub fn set_prefix(&mut self, prefix: &str) -> Result<(), ParseError> {
181 let path = &mut self.fullpath;
182 let prefix = prefix.as_bytes();
183 if !path.starts_with(prefix) {
184 return Err(ParseError::PrefixMismatch);
185 }
186 let mut pfxlen = prefix.len();
187 if prefix.ends_with(b"/") {
188 pfxlen -= 1;
189 if path[pfxlen] != b'/' {
190 return Err(ParseError::PrefixMismatch);
191 }
192 } else if path.len() == pfxlen {
193 path.push(b'/');
194 }
195 self.pfxlen = Some(pfxlen);
196 Ok(())
197 }
198
199 pub fn with_prefix(&self) -> &DavPathRef {
201 DavPathRef::new(&self.fullpath)
202 }
203
204 pub(crate) fn from_str_and_prefix(src: &str, prefix: &str) -> Result<DavPath, ParseError> {
206 let path = normalize_path(src.as_bytes())?;
207 let mut davpath = DavPath {
208 fullpath: path.to_vec(),
209 pfxlen: None,
210 };
211 davpath.set_prefix(prefix)?;
212 Ok(davpath)
213 }
214
215 pub(crate) fn from_uri_and_prefix(
217 uri: &http::uri::Uri,
218 prefix: &str,
219 ) -> Result<Self, ParseError> {
220 match uri.path() {
221 "*" => Ok(DavPath {
222 fullpath: b"*".to_vec(),
223 pfxlen: None,
224 }),
225 path if path.starts_with('/') => DavPath::from_str_and_prefix(path, prefix),
226 _ => Err(ParseError::InvalidPath),
227 }
228 }
229
230 pub fn from_uri(uri: &http::uri::Uri) -> Result<Self, ParseError> {
232 Ok(DavPath {
233 fullpath: uri.path().as_bytes().to_vec(),
234 pfxlen: None,
235 })
236 }
237
238 pub(crate) fn add_slash(&mut self) {
240 if !self.is_collection() {
241 self.fullpath.push(b'/');
242 }
243 }
244
245 pub(crate) fn add_slash_if(&mut self, b: bool) {
247 if b && !self.is_collection() {
248 self.fullpath.push(b'/');
249 }
250 }
251
252 pub(crate) fn push_segment(&mut self, b: &[u8]) {
254 if !self.is_collection() {
255 self.fullpath.push(b'/');
256 }
257 self.fullpath.extend_from_slice(b);
258 }
259
260 pub(crate) fn as_url_string_with_prefix_debug(&self) -> String {
262 let mut p = encode_path(self.get_path());
263 if !self.get_prefix().is_empty() {
264 let mut u = encode_path(self.get_prefix());
265 u.extend_from_slice(b"[");
266 u.extend_from_slice(&p);
267 u.extend_from_slice(b"]");
268 p = u;
269 }
270 std::string::String::from_utf8(p).unwrap()
271 }
272
273 fn get_prefix(&self) -> &[u8] {
275 &self.fullpath[..self.pfxlen.unwrap_or(0)]
276 }
277
278 pub fn prefix(&self) -> &str {
280 std::str::from_utf8(self.get_prefix()).unwrap()
281 }
282
283 pub fn parent(&self) -> DavPath {
285 let mut segs = self
286 .fullpath
287 .split(|&c| c == b'/')
288 .filter(|e| !e.is_empty())
289 .collect::<Vec<&[u8]>>();
290 segs.pop();
291 if !segs.is_empty() {
292 segs.push(b"");
293 }
294 segs.insert(0, b"");
295 DavPath {
296 pfxlen: self.pfxlen,
297 fullpath: segs.join(&b'/').to_vec(),
298 }
299 }
300}
301
302impl std::ops::Deref for DavPath {
303 type Target = DavPathRef;
304
305 fn deref(&self) -> &DavPathRef {
306 let pfxlen = self.pfxlen.unwrap_or(0);
307 DavPathRef::new(&self.fullpath[pfxlen..])
308 }
309}
310
311impl DavPathRef {
312 fn new(path: &[u8]) -> &DavPathRef {
315 unsafe { &*(path as *const [u8] as *const DavPathRef) }
316 }
317
318 pub fn as_bytes(&self) -> &[u8] {
320 self.get_path()
321 }
322
323 pub fn as_pathbuf(&self) -> PathBuf {
325 let mut b = self.get_path();
326 if b.len() > 1 && b.ends_with(b"/") {
327 b = &b[..b.len() - 1];
328 }
329 #[cfg(not(target_os = "windows"))]
330 let os_string = OsStr::from_bytes(b).to_owned();
331 #[cfg(target_os = "windows")]
332 let os_string = OsString::from(String::from_utf8(b.to_vec()).unwrap());
333 PathBuf::from(os_string)
334 }
335
336 pub fn as_url_string(&self) -> String {
338 let p = encode_path(self.get_path());
339 std::string::String::from_utf8(p).unwrap()
340 }
341
342 pub fn is_collection(&self) -> bool {
344 self.get_path().ends_with(b"/")
345 }
346
347 fn get_path(&self) -> &[u8] {
352 &self.fullpath
353 }
354
355 pub(crate) fn is_star(&self) -> bool {
357 self.get_path() == b"*"
358 }
359
360 pub fn as_rel_ospath(&self) -> &Path {
364 let spath = self.get_path();
365 let mut path = if !spath.is_empty() {
366 &spath[1..]
367 } else {
368 spath
369 };
370 if path.ends_with(b"/") {
371 path = &path[..path.len() - 1];
372 }
373 #[cfg(not(target_os = "windows"))]
374 let os_string = OsStr::from_bytes(path);
375 #[cfg(target_os = "windows")]
376 let os_string: &OsStr = std::str::from_utf8(path).unwrap().as_ref();
377 Path::new(os_string)
378 }
379
380 #[allow(dead_code)]
382 pub fn parent(&self) -> &DavPathRef {
383 let path = self.get_path();
384
385 let mut end = path.len();
386 while end > 0 {
387 end -= 1;
388 if path[end] == b'/' {
389 if end == 0 {
390 end = 1;
391 }
392 break;
393 }
394 }
395 DavPathRef::new(&path[..end])
396 }
397
398 pub fn file_name_bytes(&self) -> &[u8] {
400 let segs = self
401 .get_path()
402 .split(|&c| c == b'/')
403 .filter(|e| !e.is_empty())
404 .collect::<Vec<&[u8]>>();
405 if !segs.is_empty() {
406 segs[segs.len() - 1]
407 } else {
408 b""
409 }
410 }
411
412 pub fn file_name(&self) -> Option<&str> {
414 let name = self.file_name_bytes();
415 if name.is_empty() {
416 None
417 } else {
418 std::str::from_utf8(name).ok()
419 }
420 }
421
422 pub(crate) fn get_mime_type_str(&self) -> &'static str {
423 let name = self.file_name_bytes();
424 let d = name.rsplitn(2, |&c| c == b'.').collect::<Vec<&[u8]>>();
425 if d.len() > 1 {
426 if let Ok(ext) = std::str::from_utf8(d[0]) {
427 if let Some(t) = mime_guess::from_ext(ext).first_raw() {
428 return t;
429 }
430 }
431 }
432 "application/octet-stream"
433 }
434}