1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
use std::borrow::Cow;
use std::io::Read;

use reqwest::{header, Body, Client as HttpClient, IntoUrl, Method, RequestBuilder, Response};
use url::Url;

use super::error::Result;
use super::header::{Depth, Destination};
use super::response::{parse_propfind_response, PropfindResponse};
use Error;

type CowStr = Cow<'static, str>;

/// The WebDAV client. Make a `Client` for each server.
#[derive(Debug)]
pub struct Client {
    http_client: HttpClient,
    webdav_url: Url,
    credentials: Option<Credentials>,
}

/// The credentials for authenticating with the WebDAV server.
#[derive(Default, Debug)]
pub struct Credentials {
    username: CowStr,
    password: CowStr,
}

/// The builder for the `Client`.
#[derive(Default, Debug)]
pub struct ClientBuilder {
    username: Option<CowStr>,
    password: Option<CowStr>,
}

impl ClientBuilder {
    /// Construct a new `ClientBuilder`.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the credentials for the server.
    pub fn credentials<S>(mut self, username: S, password: S) -> Self
    where
        S: Into<CowStr>,
    {
        self.username = Some(username.into());
        self.password = Some(password.into());
        self
    }

    /// Build the WebDAV `Client`.
    ///
    /// # Errors
    ///
    /// This methods fails if the passed url is invalid.
    pub fn build<U>(self, webdav_url: U) -> Result<Client>
    where
        U: IntoUrl,
    {
        let credentials = if let ClientBuilder {
            username: Some(u),
            password: Some(p),
        } = self
        {
            Some(Credentials {
                username: u,
                password: p,
            })
        } else {
            None
        };

        Ok(Client {
            http_client: HttpClient::new(),
            webdav_url: webdav_url.into_url()?,
            credentials,
        })
    }
}

impl Client {
    /// Constructs a new `ClientBuilder`.
    pub fn new() -> ClientBuilder {
        ClientBuilder::new()
    }

    /// Get a file from the WebDAV server.
    ///
    /// # Errors
    ///
    /// This method fails if the passed path doesn't exist on the WebDAV server.
    pub fn get<I>(&self, path: I) -> Result<Response>
    where
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let res = self.request(Method::Get, path).send()?;

        if !res.status().is_success() {
            Err(Error::FailedRequest(res.status()))?;
        }

        Ok(res)
    }

    /// Put a file on the WebDAV server, make sure the URL is pointing to the location where you
    /// want the file to be.
    ///
    /// # Errors
    ///
    /// This method fails if the passed path doesn't exist on the WebDAV server.
    pub fn put<R, I>(&self, body: R, path: I) -> Result<()>
    where
        R: Read + Send + 'static,
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let res = self
            .request(Method::Put, path)
            .body(Body::new(body))
            .send()?;

        if !res.status().is_success() {
            Err(Error::FailedRequest(res.status()))?;
        }

        Ok(())
    }

    /// Creates a directory on the WebDAV server.
    ///
    /// # Errors
    ///
    /// This methods fails if the path where you want to create the directory doesn't exist.
    pub fn mkcol<I>(&self, path: I) -> Result<()>
    where
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let res = self
            .request(Method::Extension("MKCOL".to_string()), path)
            .send()?;

        if !res.status().is_success() {
            Err(Error::FailedRequest(res.status()))?
        }

        Ok(())
    }

    /// Rename or move a directory or file on the WebDAV server.
    ///
    /// # Errors
    ///
    /// This method fails if from doesn't exist, also fails if the to path is invalid.
    pub fn mv<F, T>(&self, from: F, to: T) -> Result<()>
    where
        F: IntoIterator,
        F::Item: AsRef<str>,
        T: IntoIterator,
        T::Item: AsRef<str>,
    {
        let mut req = self.request(Method::Extension("MOVE".to_string()), from);
        let mut url = self.webdav_url.clone();
        url.path_segments_mut().unwrap().extend(to);

        req.header(Destination(url.to_string()));

        let res = req.send()?;

        if !res.status().is_success() {
            Err(Error::FailedRequest(res.status()))?;
        }

        Ok(())
    }

    /// List files in a directory on the WebDAV server.
    ///
    /// # Errors
    ///
    /// This method fails if the passed path doesn't exist on the WebDAV server.
    pub fn list<I>(&self, path: I) -> Result<Vec<PropfindResponse>>
    where
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let body = r#"<?xml version="1.0" encoding="utf-8" ?>
            <D:propfind xmlns:D="DAV:">
                <D:allprop/>
            </D:propfind>
        "#;

        let res = self
            .request(Method::Extension("PROPFIND".to_string()), path)
            .header(Depth("Infinity".into()))
            .body(body)
            .send()?;

        if !res.status().is_success() {
            Err(Error::FailedRequest(res.status()))?;
        }

        Ok(parse_propfind_response(res)?)
    }

    /// Prepare a `RequestBuilder` for use in a request to the WebDAV server.
    /// This can be used in case you need to customize something or want to do something which is
    /// still unsupported.
    pub fn request<I>(&self, method: Method, path: I) -> RequestBuilder
    where
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let mut url = self.webdav_url.clone();
        url.path_segments_mut().unwrap().extend(path);
        let mut request = self.http_client.request(method, url.as_str());

        if let Some(Credentials {
            ref username,
            ref password,
        }) = self.credentials
        {
            request.header(header::Authorization(header::Basic {
                username: username.to_string(),
                password: Some(password.to_string()),
            }));
        }

        request
    }
}