webdav_handler/
davpath.rs1use std::error::Error;
4use std::ffi::OsStr;
5use std::os::unix::ffi::OsStrExt;
6use std::path::{Path, PathBuf};
7
8use mime_guess;
9use percent_encoding as pct;
10
11use crate::DavError;
12
13#[derive(Clone)]
15pub struct DavPath {
16 fullpath: Vec<u8>,
17 pfxlen: Option<usize>,
18}
19
20pub struct DavPathRef {
23 fullpath: [u8],
24}
25
26#[derive(Copy, Clone, Debug)]
27#[allow(non_camel_case_types)]
28struct ENCODE_SET;
29
30impl pct::EncodeSet for ENCODE_SET {
31 #[inline]
34 fn contains(&self, byte: u8) -> bool {
35 let unreserved = (byte >= b'A' && byte <= b'Z') ||
36 (byte >= b'a' && byte <= b'z') ||
37 (byte >= b'0' && byte <= b'9') ||
38 byte == b'-' ||
39 byte == b'_' ||
40 byte == b'.' ||
41 byte == b'~';
42 !unreserved && byte != b'/'
43 }
44}
45
46impl std::fmt::Display for DavPath {
47 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
48 write!(f, "{:?}", &self.as_url_string_with_prefix_debug())
49 }
50}
51
52impl std::fmt::Debug for DavPath {
53 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54 write!(f, "{:?}", &self.as_url_string_with_prefix_debug())
55 }
56}
57
58#[derive(Debug)]
60pub enum ParseError {
61 InvalidPath,
63 PrefixMismatch,
65 ForbiddenPath,
67}
68
69impl Error for ParseError {
70 fn description(&self) -> &str {
71 "DavPath parse error"
72 }
73 fn cause(&self) -> Option<&dyn Error> {
74 None
75 }
76}
77
78impl std::fmt::Display for ParseError {
79 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
80 write!(f, "{:?}", self)
81 }
82}
83
84impl From<ParseError> for DavError {
85 fn from(e: ParseError) -> Self {
86 match e {
87 ParseError::InvalidPath => DavError::InvalidPath,
88 ParseError::PrefixMismatch => DavError::IllegalPath,
89 ParseError::ForbiddenPath => DavError::ForbiddenPath,
90 }
91 }
92}
93
94fn valid_segment(src: &[u8]) -> Result<(), ParseError> {
96 let mut p = pct::percent_decode(src);
97 if p.any(|x| x == 0 || x == b'/') {
98 return Err(ParseError::InvalidPath);
99 }
100 Ok(())
101}
102
103fn encode_path(src: &[u8]) -> Vec<u8> {
105 pct::percent_encode(src, ENCODE_SET).to_string().into_bytes()
106}
107
108fn normalize_path(rp: &[u8]) -> Result<Vec<u8>, ParseError> {
117 if rp.iter().any(|&x| x < 32 || x > 126) {
119 Err(ParseError::InvalidPath)?;
120 }
121
122 let mut rawpath = rp;
124 if let Some(pos) = rawpath.iter().position(|&x| x == b'?' || x == b'#') {
125 if rawpath[pos] == b'#' {
126 Err(ParseError::InvalidPath)?;
127 }
128 rawpath = &rawpath[..pos];
129 }
130
131 if rawpath.is_empty() || rawpath[0] != b'/' {
133 Err(ParseError::InvalidPath)?;
134 }
135
136 let isdir = match rawpath.last() {
138 Some(x) if *x == b'/' => true,
139 _ => false,
140 };
141 let segments = rawpath.split(|c| *c == b'/');
142 let mut v: Vec<&[u8]> = Vec::new();
143 for segment in segments {
144 match segment {
145 b"." | b"" => {},
146 b".." => {
147 if v.len() < 2 {
148 return Err(ParseError::ForbiddenPath);
149 }
150 v.pop();
151 v.pop();
152 },
153 s => {
154 if let Err(e) = valid_segment(s) {
155 Err(e)?;
156 }
157 v.push(b"/");
158 v.push(s);
159 },
160 }
161 }
162 if isdir || v.is_empty() {
163 v.push(b"/");
164 }
165 Ok(v.iter().flat_map(|s| pct::percent_decode(s)).collect())
166}
167
168impl PartialEq for DavPath {
170 fn eq(&self, rhs: &DavPath) -> bool {
171 let mut a = self.fullpath.as_slice();
172 if a.len() > 1 && a.ends_with(b"/") {
173 a = &a[..a.len() - 1];
174 }
175 let mut b = rhs.fullpath.as_slice();
176 if b.len() > 1 && b.ends_with(b"/") {
177 b = &b[..b.len() - 1];
178 }
179 a == b
180 }
181}
182
183impl DavPath {
184 pub fn new(src: &str) -> Result<DavPath, ParseError> {
186 let path = normalize_path(src.as_bytes())?;
187 Ok(DavPath {
188 fullpath: path.to_vec(),
189 pfxlen: None,
190 })
191 }
192
193 pub fn set_prefix(&mut self, prefix: &str) -> Result<(), ParseError> {
195 let path = &mut self.fullpath;
196 let prefix = prefix.as_bytes();
197 if !path.starts_with(prefix) {
198 return Err(ParseError::PrefixMismatch);
199 }
200 let mut pfxlen = prefix.len();
201 if prefix.ends_with(b"/") {
202 pfxlen -= 1;
203 if path[pfxlen] != b'/' {
204 return Err(ParseError::PrefixMismatch);
205 }
206 } else if path.len() == pfxlen {
207 path.push(b'/');
208 }
209 self.pfxlen = Some(pfxlen);
210 Ok(())
211 }
212
213 pub fn with_prefix(&self) -> &DavPathRef {
215 DavPathRef::new(&self.fullpath)
216 }
217
218 pub(crate) fn from_str_and_prefix(src: &str, prefix: &str) -> Result<DavPath, ParseError> {
220 let path = normalize_path(src.as_bytes())?;
221 let mut davpath = DavPath {
222 fullpath: path.to_vec(),
223 pfxlen: None,
224 };
225 davpath.set_prefix(prefix)?;
226 Ok(davpath)
227 }
228
229 pub(crate) fn from_uri_and_prefix(uri: &http::uri::Uri, prefix: &str) -> Result<Self, ParseError> {
231 match uri.path() {
232 "*" => {
233 Ok(DavPath {
234 fullpath: b"*".to_vec(),
235 pfxlen: None,
236 })
237 },
238 path if path.starts_with("/") => DavPath::from_str_and_prefix(path, prefix),
239 _ => Err(ParseError::InvalidPath),
240 }
241 }
242
243 pub fn from_uri(uri: &http::uri::Uri) -> Result<Self, ParseError> {
245 Ok(DavPath {
246 fullpath: uri.path().as_bytes().to_vec(),
247 pfxlen: None,
248 })
249 }
250
251 pub(crate) fn add_slash(&mut self) {
253 if !self.is_collection() {
254 self.fullpath.push(b'/');
255 }
256 }
257
258 pub(crate) fn add_slash_if(&mut self, b: bool) {
260 if b && !self.is_collection() {
261 self.fullpath.push(b'/');
262 }
263 }
264
265 pub(crate) fn push_segment(&mut self, b: &[u8]) {
267 if !self.is_collection() {
268 self.fullpath.push(b'/');
269 }
270 self.fullpath.extend_from_slice(b);
271 }
272
273 pub(crate) fn as_url_string_with_prefix_debug(&self) -> String {
275 let mut p = encode_path(self.get_path());
276 if self.get_prefix().len() > 0 {
277 let mut u = encode_path(self.get_prefix());
278 u.extend_from_slice(b"[");
279 u.extend_from_slice(&p);
280 u.extend_from_slice(b"]");
281 p = u;
282 }
283 std::string::String::from_utf8(p).unwrap()
284 }
285
286 fn get_prefix(&self) -> &[u8] {
288 &self.fullpath[..self.pfxlen.unwrap_or(0)]
289 }
290
291 pub fn prefix(&self) -> &str {
293 std::str::from_utf8(self.get_prefix()).unwrap()
294 }
295
296 pub(crate) fn parent(&self) -> DavPath {
298 let mut segs = self
299 .fullpath
300 .split(|&c| c == b'/')
301 .filter(|e| e.len() > 0)
302 .collect::<Vec<&[u8]>>();
303 segs.pop();
304 if segs.len() > 0 {
305 segs.push(b"");
306 }
307 segs.insert(0, b"");
308 DavPath {
309 pfxlen: self.pfxlen,
310 fullpath: segs.join(&b'/').to_vec(),
311 }
312 }
313}
314
315impl std::ops::Deref for DavPath {
316 type Target = DavPathRef;
317
318 fn deref(&self) -> &DavPathRef {
319 let pfxlen = self.pfxlen.unwrap_or(0);
320 DavPathRef::new(&self.fullpath[pfxlen..])
321 }
322}
323
324impl DavPathRef {
325 fn new(path: &[u8]) -> &DavPathRef {
328 unsafe { &*(path as *const [u8] as *const DavPathRef) }
329 }
330
331 pub fn as_bytes(&self) -> &[u8] {
333 self.get_path()
334 }
335
336 pub fn as_pathbuf(&self) -> PathBuf {
338 let mut b = self.get_path();
339 if b.len() > 1 && b.ends_with(b"/") {
340 b = &b[..b.len() - 1];
341 }
342 let os_string = OsStr::from_bytes(b).to_owned();
343 PathBuf::from(os_string)
344 }
345
346 pub fn as_url_string(&self) -> String {
348 let p = encode_path(self.get_path());
349 std::string::String::from_utf8(p).unwrap()
350 }
351
352 pub fn is_collection(&self) -> bool {
354 self.get_path().ends_with(b"/")
355 }
356
357 fn get_path(&self) -> &[u8] {
362 &self.fullpath
363 }
364
365 pub(crate) fn is_star(&self) -> bool {
367 self.get_path() == b"*"
368 }
369
370 pub fn as_rel_ospath(&self) -> &Path {
374 let spath = self.get_path();
375 let mut path = if spath.len() > 0 { &spath[1..] } else { spath };
376 if path.ends_with(b"/") {
377 path = &path[..path.len() - 1];
378 }
379 let os_string = OsStr::from_bytes(path);
380 Path::new(os_string)
381 }
382
383 #[allow(dead_code)]
385 pub(crate) fn parent(&self) -> &DavPathRef {
386 let path = self.get_path();
387
388 let mut end = path.len();
389 while end > 0 {
390 end -= 1;
391 if path[end] == b'/' {
392 if end == 0 {
393 end = 1;
394 }
395 break;
396 }
397 }
398 DavPathRef::new(&path[..end])
399 }
400
401 pub(crate) fn file_name(&self) -> &[u8] {
403 let segs = self
404 .get_path()
405 .split(|&c| c == b'/')
406 .filter(|e| e.len() > 0)
407 .collect::<Vec<&[u8]>>();
408 if segs.len() > 0 {
409 segs[segs.len() - 1]
410 } else {
411 b""
412 }
413 }
414
415 pub(crate) fn get_mime_type_str(&self) -> &'static str {
416 let name = self.file_name();
417 let d = name.rsplitn(2, |&c| c == b'.').collect::<Vec<&[u8]>>();
418 if d.len() > 1 {
419 if let Ok(ext) = std::str::from_utf8(d[0]) {
420 if let Some(t) = mime_guess::from_ext(ext).first_raw() {
421 return t;
422 }
423 }
424 }
425 "application/octet-stream"
426 }
427}