hyperdav_server/
lib.rs

1//! WebDAV server as a hyper server handler
2//!
3//!```no_run
4//!extern crate hyper;
5//!extern crate hyperdav_server;
6//!
7//!let server = hyper::server::Server::http("0.0.0.0:8080").unwrap();
8//!server
9//!    .handle(hyperdav_server::Server::new("", std::path::Path::new("/")))
10//!    .unwrap();
11//!```
12extern crate chrono;
13extern crate hyper;
14#[macro_use]
15extern crate log;
16extern crate url;
17extern crate xml;
18
19use std::borrow::{Borrow, Cow};
20use std::time::{UNIX_EPOCH, SystemTime};
21use std::io::{self, Read, Write, ErrorKind};
22use std::fs::{self, Metadata, read_dir, File};
23use std::path::{Path, PathBuf};
24
25use hyper::header::ContentLength;
26use hyper::method::Method;
27use hyper::server::{Handler, Request, Response};
28use hyper::status::StatusCode;
29use hyper::uri::RequestUri;
30use xml::{EmitterConfig, ParserConfig};
31use xml::common::XmlVersion;
32use xml::name::{Name, OwnedName};
33use xml::reader::XmlEvent;
34use xml::writer::EventWriter;
35use xml::writer::XmlEvent as XmlWEvent;
36
37struct ServerPath {
38    // HTTP path on the server representing the root directory
39    url_prefix: Cow<'static, str>,
40    // Root file system directory of the server
41    srv_root: Cow<'static, Path>,
42}
43
44impl ServerPath {
45    // Ex. url_prefix = "/dav", srv_root = "/srv/dav/"
46    fn new<U, R>(url_prefix: U, srv_root: R) -> Self
47        where U: Into<Cow<'static, str>>, R: Into<Cow<'static, Path>> {
48        let url_prefix = url_prefix.into();
49        let srv_root = srv_root.into();
50
51        assert_eq!(url_prefix.trim_right_matches("/"), url_prefix);
52        assert!(srv_root.ends_with("/"));
53
54        ServerPath {
55            url_prefix: url_prefix,
56            srv_root: srv_root,
57        }
58    }
59
60    fn file_to_url<P: AsRef<Path>>(&self, path: P) -> String {
61        let path = path.as_ref()
62            .strip_prefix(&self.srv_root)
63            .expect("file_to_url");
64        self.url_prefix.clone().into_owned() + "/" + path.to_str().expect("file_to_url")
65    }
66
67    fn url_to_file<'a>(&'a self, url: &'a str) -> Option<PathBuf> {
68        if url.starts_with(self.url_prefix.borrow() as &str) {
69            let subpath = &url[self.url_prefix.len()..]
70                               .trim_left_matches("/")
71                               .trim_right_matches("/");
72            let mut ret = self.srv_root.clone().into_owned();
73            ret.push(subpath);
74            Some(ret)
75        } else {
76            None
77        }
78    }
79}
80
81#[test]
82fn test_serverpath() {
83    let s = ServerPath::new("/dav", Path::new("/"));
84    assert_eq!(s.url_to_file("/dav/foo").unwrap().to_str().unwrap(), "/foo");
85    assert_eq!(s.url_to_file("/dav/foo/").unwrap().to_str().unwrap(),
86               "/foo");
87    assert_eq!(s.url_to_file("/dav/foo//").unwrap().to_str().unwrap(),
88               "/foo");
89    assert_eq!(s.url_to_file("/dav//foo//").unwrap().to_str().unwrap(),
90               "/foo");
91    assert_eq!(&s.file_to_url("/foo"), "/dav/foo");
92    assert_eq!(&s.file_to_url("/"), "/dav/");
93}
94
95pub struct Server {
96    serverpath: ServerPath,
97}
98
99impl Server {
100    /// Create a WebDAV handler
101    ///
102    /// * `url_prefix` - the path on the server that maps to the WebDAV root. It
103    /// must not end with trailing slashes.
104    ///
105    /// * `srv_root` - must be a directory on the host and must end with a trailing slash.
106    ///
107    /// Panics if the above requirements are not met.
108    /// These requirements are desired to consistently map between server URLs
109    /// and host file system paths. Since the server returns URLs for files,
110    /// the mapping must be consistent in both directions.
111    ///
112    /// Ex. url_prefix = "/dav", srv_root = Path::new("/srv/dav/")
113    pub fn new<U, R>(url_prefix: U, srv_root: R) -> Self
114        where U: Into<Cow<'static, str>>, R: Into<Cow<'static, Path>> {
115        Server { serverpath: ServerPath::new(url_prefix, srv_root) }
116    }
117}
118
119#[derive(Debug)]
120enum RequestType {
121    Options,
122    Propfind,
123    Get,
124    Copy,
125    Move,
126    Delete,
127    Put,
128    Mkdir,
129}
130
131#[derive(Debug)]
132enum Error {
133    ParseError,
134    BadPath,
135    XmlReader(xml::reader::Error),
136    XmlWriter(xml::writer::Error),
137    Io(io::Error),
138}
139
140impl From<xml::reader::Error> for Error {
141    fn from(e: xml::reader::Error) -> Self {
142        Error::XmlReader(e)
143    }
144}
145
146impl From<xml::writer::Error> for Error {
147    fn from(e: xml::writer::Error) -> Self {
148        Error::XmlWriter(e)
149    }
150}
151
152impl From<io::Error> for Error {
153    fn from(e: io::Error) -> Self {
154        Error::Io(e)
155    }
156}
157
158fn parse_propfind<R: Read, F: FnMut(OwnedName) -> ()>(mut xml: xml::reader::EventReader<R>,
159                                                      mut f: F)
160                                                      -> Result<(), Error> {
161    enum State {
162        Start,
163        PropFind,
164        Prop,
165        InProp,
166    }
167
168    let mut state = State::Start;
169
170    loop {
171        let event = xml.next()?;
172        match state {
173            State::Start => {
174                match event {
175                    XmlEvent::StartDocument { .. } => (),
176                    XmlEvent::StartElement { ref name, .. } if name.local_name == "propfind" => {
177                        state = State::PropFind;
178                    }
179                    _ => return Err(Error::ParseError),
180                }
181            }
182            State::PropFind => {
183                match event {
184                    XmlEvent::StartElement { ref name, .. } if name.local_name == "prop" => {
185                        state = State::Prop;
186                    }
187                    _ => return Err(Error::ParseError),
188                }
189            }
190            State::Prop => {
191                match event {
192                    XmlEvent::StartElement { name, .. } => {
193                        state = State::InProp;
194                        f(name);
195                    }
196                    XmlEvent::EndElement { .. } => {
197                        return Ok(());
198                    }
199                    _ => return Err(Error::ParseError),
200                }
201            }
202            State::InProp => {
203                match event {
204                    XmlEvent::EndElement { .. } => {
205                        state = State::Prop;
206                    }
207                    _ => return Err(Error::ParseError),
208                }
209            }
210        }
211    }
212}
213
214fn write_client_prop<W: Write>(xmlwriter: &mut EventWriter<W>,
215                               prop: Name)
216                               -> Result<(), xml::writer::Error> {
217    if let Some(namespace) = prop.namespace {
218        if let Some(prefix) = prop.prefix {
219            // Remap the client's prefix if it overlaps with our DAV: prefix
220            if prefix == "D" && namespace != "DAV:" {
221                let newname = Name {
222                    local_name: prop.local_name,
223                    namespace: Some(namespace),
224                    prefix: Some("U"),
225                };
226                return xmlwriter.write(XmlWEvent::start_element(newname).ns("U", namespace));
227            }
228        }
229    }
230    xmlwriter.write(XmlWEvent::start_element(prop))
231}
232
233fn systime_to_format(time: SystemTime) -> String {
234    use chrono::datetime::DateTime;
235    use chrono::naive::datetime::NaiveDateTime;
236    use chrono::offset::utc::UTC;
237
238    let unix = time.duration_since(UNIX_EPOCH).unwrap();
239    let time = DateTime::<UTC>::from_utc(NaiveDateTime::from_timestamp(unix.as_secs() as i64,
240                                                                       unix.subsec_nanos()),
241                                         UTC);
242    time.to_rfc3339()
243}
244
245fn handle_prop_path<W: Write>(xmlwriter: &mut EventWriter<W>,
246                              meta: &Metadata,
247                              prop: Name)
248                              -> Result<bool, Error> {
249    match (prop.namespace, prop.local_name) {
250        (Some("DAV:"), "resourcetype") => {
251            xmlwriter.write(XmlWEvent::start_element("D:resourcetype"))?;
252            if meta.is_dir() {
253                xmlwriter.write(XmlWEvent::start_element("D:collection"))?;
254                xmlwriter.write(XmlWEvent::end_element())?;
255            }
256            xmlwriter.write(XmlWEvent::end_element())?;
257            Ok(true)
258        }
259        (Some("DAV:"), "creationdate") => {
260            if let Ok(time) = meta.created() {
261                xmlwriter.write(XmlWEvent::start_element("D:creationdate"))?;
262                xmlwriter
263                    .write(XmlWEvent::characters(&systime_to_format(time)))?;
264                xmlwriter.write(XmlWEvent::end_element())?;
265                Ok(true)
266            } else {
267                Ok(false)
268            }
269        }
270        (Some("DAV:"), "getlastmodified") => {
271            if let Ok(time) = meta.modified() {
272                xmlwriter
273                    .write(XmlWEvent::start_element("D:getlastmodified"))?;
274                xmlwriter
275                    .write(XmlWEvent::characters(&systime_to_format(time)))?;
276                xmlwriter.write(XmlWEvent::end_element())?;
277                Ok(true)
278            } else {
279                Ok(false)
280            }
281        }
282        (Some("DAV:"), "getcontentlength") => {
283            xmlwriter
284                .write(XmlWEvent::start_element("D:getcontentlength"))?;
285            xmlwriter
286                .write(XmlWEvent::characters(&meta.len().to_string()))?;
287            xmlwriter.write(XmlWEvent::end_element())?;
288            Ok(true)
289        }
290        (Some("DAV:"), "getcontenttype") => {
291            xmlwriter
292                .write(XmlWEvent::start_element("D:getcontenttype"))?;
293            if meta.is_dir() {
294                xmlwriter
295                    .write(XmlWEvent::characters("httpd/unix-directory"))?;
296            } else {
297                xmlwriter.write(XmlWEvent::characters("text/plain"))?;
298            }
299            xmlwriter.write(XmlWEvent::end_element())?;
300            Ok(true)
301        }
302        _ => Ok(false),
303    }
304}
305
306fn handle_propfind_path<W: Write>(xmlwriter: &mut EventWriter<W>,
307                                  url: &str,
308                                  meta: &Metadata,
309                                  props: &[OwnedName])
310                                  -> Result<(), Error> {
311    xmlwriter.write(XmlWEvent::start_element("D:response"))?;
312
313    xmlwriter.write(XmlWEvent::start_element("D:href"))?;
314    xmlwriter.write(XmlWEvent::characters(url))?;
315    xmlwriter.write(XmlWEvent::end_element())?; // href
316
317    let mut failed_props = Vec::with_capacity(props.len());
318    xmlwriter.write(XmlWEvent::start_element("D:propstat"))?;
319    xmlwriter.write(XmlWEvent::start_element("D:prop"))?;
320    for prop in props {
321        if !handle_prop_path(xmlwriter, meta, prop.borrow())? {
322            failed_props.push(prop);
323        }
324    }
325    xmlwriter.write(XmlWEvent::end_element())?; // prop
326    xmlwriter.write(XmlWEvent::start_element("D:status"))?;
327    if failed_props.len() >= props.len() {
328        // If they all failed, make this a failure response and return
329        xmlwriter
330            .write(XmlWEvent::characters("HTTP/1.1 404 Not Found"))?;
331        xmlwriter.write(XmlWEvent::end_element())?; // status
332        xmlwriter.write(XmlWEvent::end_element())?; // propstat
333        xmlwriter.write(XmlWEvent::end_element())?; // response
334        return Ok(());
335    }
336    xmlwriter.write(XmlWEvent::characters("HTTP/1.1 200 OK"))?;
337    xmlwriter.write(XmlWEvent::end_element())?; // status
338    xmlwriter.write(XmlWEvent::end_element())?; // propstat
339
340    // Handle the failed properties
341    xmlwriter.write(XmlWEvent::start_element("D:propstat"))?;
342    xmlwriter.write(XmlWEvent::start_element("D:prop"))?;
343    for prop in failed_props {
344        write_client_prop(xmlwriter, prop.borrow())?;
345        xmlwriter.write(XmlWEvent::end_element())?;
346    }
347    xmlwriter.write(XmlWEvent::end_element())?; // prop
348    xmlwriter.write(XmlWEvent::start_element("D:status"))?;
349    xmlwriter
350        .write(XmlWEvent::characters("HTTP/1.1 404 Not Found"))?;
351    xmlwriter.write(XmlWEvent::end_element())?; // status
352    xmlwriter.write(XmlWEvent::end_element())?; // propstat
353    xmlwriter.write(XmlWEvent::end_element())?; // response
354    Ok(())
355}
356
357fn io_error_to_status(e: io::Error, res: &mut Response<hyper::net::Fresh>) -> io::Error {
358    if e.kind() == ErrorKind::NotFound {
359        *res.status_mut() = StatusCode::NotFound;
360    } else {
361        *res.status_mut() = StatusCode::InternalServerError;
362    }
363    e
364}
365
366impl Server {
367    fn handle_propfind_path_recursive<W: Write>(&self,
368                                                path: &Path,
369                                                depth: u32,
370                                                xmlwriter: &mut EventWriter<W>,
371                                                props: &[OwnedName])
372                                                -> Result<(), Error> {
373        if depth == 0 {
374            return Ok(());
375        }
376        for f in read_dir(path)? {
377            let f = match f {
378                Ok(f) => f,
379                Err(e) => {
380                    error!("Read dir error. Skipping {:?}", e);
381                    continue;
382                }
383            };
384            let path = f.path();
385            let meta = match f.metadata() {
386                Ok(meta) => meta,
387                Err(e) => {
388                    error!("Metadata error on {:?}. Skipping {:?}", path, e);
389                    continue;
390                }
391            };
392            handle_propfind_path(xmlwriter, &self.serverpath.file_to_url(&path), &meta, props)?;
393            // Ignore errors in order to try the other files. This could fail for
394            // connection reasons (not file I/O), but those should retrigger and
395            // get passed up on subsequent xml writes
396            let _ = self.handle_propfind_path_recursive(&path, depth - 1, xmlwriter, props);
397        }
398        Ok(())
399    }
400
401    fn uri_to_path(&self,
402                   req: &Request,
403                   res: &mut Response<hyper::net::Fresh>)
404                   -> Result<PathBuf, Error> {
405        if let RequestUri::AbsolutePath(ref s) = req.uri {
406                // Unwrap should hopefully be safe since we just came from a string
407                let s = url::percent_encoding::percent_decode(s.as_bytes())
408                    .decode_utf8()
409                    .expect("percent decode");
410                self.serverpath.url_to_file(s.borrow())
411            } else {
412                None
413            }
414            .ok_or_else(|| {
415                            *res.status_mut() = StatusCode::NotFound;
416                            Error::BadPath
417                        })
418    }
419
420    fn uri_to_src_dst(&self,
421                      req: &Request,
422                      res: &mut Response<hyper::net::Fresh>)
423                      -> Result<(PathBuf, PathBuf), Error> {
424        // Get the source
425        let src = self.uri_to_path(req, res)?;
426
427        // Get the destination
428        let dst = req.headers
429            .get_raw("Destination")
430            .and_then(|vec| vec.get(0))
431            .and_then(|vec| std::str::from_utf8(vec).ok())
432            .and_then(|s| url::Url::parse(s).ok())
433            .ok_or(Error::BadPath)
434            .map_err(|e| {
435                         *res.status_mut() = StatusCode::BadRequest;
436                         e
437                     })?;
438        let dst = url::percent_encoding::percent_decode(dst.path().as_bytes())
439            .decode_utf8()
440            .map_err(|_| Error::BadPath)
441            .and_then(|dst| {
442                          self.serverpath
443                              .url_to_file(dst.borrow())
444                              .ok_or(Error::BadPath)
445                      })
446            .map_err(|e| {
447                         *res.status_mut() = StatusCode::BadRequest;
448                         e
449                     })?;
450
451        if src == dst {
452            *res.status_mut() == StatusCode::Forbidden;
453            return Err(Error::BadPath);
454        }
455
456        Ok((src, dst))
457    }
458
459    fn handle_propfind(&self,
460                       mut req: Request,
461                       mut res: Response<hyper::net::Fresh>)
462                       -> Result<(), Error> {
463        // Get the file
464        let path = self.uri_to_path(&req, &mut res)?;
465
466        // Get the depth
467        let depth = req.headers
468            .get_raw("Depth")
469            .and_then(|vec| vec.get(0))
470            .and_then(|vec| std::str::from_utf8(vec).ok())
471            .and_then(|s| s.parse::<u32>().ok())
472            .unwrap_or(0);
473
474        let xml = xml::reader::EventReader::new_with_config(&mut req,
475                                                            ParserConfig {
476                                                                trim_whitespace: true,
477                                                                ..Default::default()
478                                                            });
479        let mut props = Vec::new();
480        if let Err(e) = parse_propfind(xml, |prop| { props.push(prop); }) {
481            *res.status_mut() = StatusCode::BadRequest;
482            return Err(e);
483        }
484
485        debug!("Propfind {:?} {:?}", path, props);
486
487        let meta = path.metadata()
488            .map_err(|e| io_error_to_status(e, &mut res))?;
489        *res.status_mut() = StatusCode::MultiStatus;
490
491        let mut xmlwriter = EventWriter::new_with_config(res.start()?,
492                                                         EmitterConfig {
493                                                             perform_indent: true,
494                                                             ..Default::default()
495                                                         });
496        xmlwriter
497            .write(XmlWEvent::StartDocument {
498                       version: XmlVersion::Version10,
499                       encoding: Some("utf-8"),
500                       standalone: None,
501                   })?;
502        xmlwriter
503            .write(XmlWEvent::start_element("D:multistatus").ns("D", "DAV:"))?;
504
505        handle_propfind_path(&mut xmlwriter,
506                             &self.serverpath.file_to_url(&path),
507                             &meta,
508                             &props)?;
509
510        if meta.is_dir() {
511            self.handle_propfind_path_recursive(&path, depth, &mut xmlwriter, &props)?;
512        }
513
514        xmlwriter.write(XmlWEvent::end_element())?;
515        Ok(())
516    }
517
518    fn handle_get(&self, req: Request, mut res: Response<hyper::net::Fresh>) -> Result<(), Error> {
519        // Get the file
520        let path = self.uri_to_path(&req, &mut res)?;
521        let mut file = File::open(path)
522            .map_err(|e| io_error_to_status(e, &mut res))?;
523        let size = file.metadata()
524            .map(|m| m.len())
525            .map_err(|e| io_error_to_status(e, &mut res))?;
526
527        // Ignore size = 0 to hopefully work reasonably with special files
528        if size > 0 {
529            res.headers_mut().set(ContentLength(size))
530        }
531
532        // TODO: byte ranges (Accept-Ranges: bytes)
533        io::copy(&mut file, &mut res.start()?)?;
534        Ok(())
535    }
536
537    fn handle_put(&self,
538                  mut req: Request,
539                  mut res: Response<hyper::net::Fresh>)
540                  -> Result<(), Error> {
541        let path = self.uri_to_path(&req, &mut res)?;
542        let mut file = File::create(path)
543            .map_err(|e| io_error_to_status(e, &mut res))?;
544        io::copy(&mut req, &mut file)?;
545        Ok(())
546    }
547
548    fn handle_copy(&self, req: Request, mut res: Response<hyper::net::Fresh>) -> Result<(), Error> {
549        let (src, dst) = self.uri_to_src_dst(&req, &mut res)?;
550        debug!("Copy {:?} -> {:?}", src, dst);
551
552        // TODO: handle overwrite flags and directory copies
553        // TODO: proper error for out of space
554        fs::copy(src, dst)
555            .map_err(|e| io_error_to_status(e, &mut res))?;
556        *res.status_mut() == StatusCode::Created;
557        Ok(())
558    }
559
560    fn handle_move(&self, req: Request, mut res: Response<hyper::net::Fresh>) -> Result<(), Error> {
561        let (src, dst) = self.uri_to_src_dst(&req, &mut res)?;
562        debug!("Move {:?} -> {:?}", src, dst);
563
564        // TODO: handle overwrite flags
565        fs::rename(src, dst)
566            .map_err(|e| io_error_to_status(e, &mut res))?;
567        *res.status_mut() == StatusCode::Created;
568        Ok(())
569    }
570
571    fn handle_delete(&self,
572                     req: Request,
573                     mut res: Response<hyper::net::Fresh>)
574                     -> Result<(), Error> {
575        // Get the file
576        let path = self.uri_to_path(&req, &mut res)?;
577        let meta = path.metadata()
578            .map_err(|e| io_error_to_status(e, &mut res))?;
579        if meta.is_dir() {
580                fs::remove_dir_all(path)
581            } else {
582                fs::remove_file(path)
583            }
584            .map_err(|e| io_error_to_status(e, &mut res))?;
585        Ok(())
586    }
587
588    fn handle_mkdir(&self,
589                    req: Request,
590                    mut res: Response<hyper::net::Fresh>)
591                    -> Result<(), Error> {
592        let path = self.uri_to_path(&req, &mut res)?;
593        let ret = fs::create_dir(path);
594        match ret {
595            Ok(_) => *res.status_mut() = StatusCode::Created,
596            Err(ref e) if e.kind() == ErrorKind::NotFound => {
597                *res.status_mut() = StatusCode::Conflict;
598            }
599            Err(_) => *res.status_mut() = StatusCode::InternalServerError,
600        };
601        ret.map_err(Into::into)
602    }
603}
604
605impl Handler for Server {
606    fn handle<'a, 'k>(&'a self, req: Request<'a, 'k>, mut res: Response<'a, hyper::net::Fresh>) {
607        debug!("Request {:?}", req.method);
608
609        let reqtype = match req.method {
610            Method::Options => RequestType::Options,
611            Method::Get => RequestType::Get,
612            Method::Put => RequestType::Put,
613            Method::Delete => RequestType::Delete,
614            Method::Extension(ref s) if s == "PROPFIND" => RequestType::Propfind,
615            Method::Extension(ref s) if s == "COPY" => RequestType::Copy,
616            Method::Extension(ref s) if s == "MOVE" => RequestType::Move,
617            Method::Extension(ref s) if s == "MKCOL" => RequestType::Mkdir,
618            _ => {
619                *res.status_mut() = StatusCode::BadRequest;
620                return;
621            }
622        };
623
624        if let Err(e) = match reqtype {
625               RequestType::Options => {
626                   res.headers_mut()
627                       .set(hyper::header::Allow(vec![Method::Options,
628                                                      Method::Get,
629                                                      Method::Put,
630                                                      Method::Delete,
631                                                      Method::Extension("PROPFIND".into()),
632                                                      Method::Extension("COPY".into()),
633                                                      Method::Extension("MOVE".into()),
634                                                      Method::Extension("MKCOL".into())]));
635                   res.headers_mut().set_raw("DAV", vec![b"1".to_vec()]);
636                   Ok(())
637               }
638               RequestType::Propfind => self.handle_propfind(req, res),
639               RequestType::Get => self.handle_get(req, res),
640               RequestType::Put => self.handle_put(req, res),
641               RequestType::Copy => self.handle_copy(req, res),
642               RequestType::Move => self.handle_move(req, res),
643               RequestType::Delete => self.handle_delete(req, res),
644               RequestType::Mkdir => self.handle_mkdir(req, res),
645           } {
646            error!("Request error {:?}", e)
647        }
648    }
649}