opendal/services/webdav/
backend.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::str::FromStr;
21use std::sync::Arc;
22
23use http::Response;
24use http::StatusCode;
25use log::debug;
26
27use super::core::*;
28use super::delete::WebdavDeleter;
29use super::error::parse_error;
30use super::lister::WebdavLister;
31use super::writer::WebdavWriter;
32use crate::raw::*;
33use crate::services::WebdavConfig;
34use crate::*;
35
36impl Configurator for WebdavConfig {
37    type Builder = WebdavBuilder;
38
39    #[allow(deprecated)]
40    fn into_builder(self) -> Self::Builder {
41        WebdavBuilder {
42            config: self,
43            http_client: None,
44        }
45    }
46}
47
48/// [WebDAV](https://datatracker.ietf.org/doc/html/rfc4918) backend support.
49#[doc = include_str!("docs.md")]
50#[derive(Default)]
51pub struct WebdavBuilder {
52    config: WebdavConfig,
53
54    #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
55    http_client: Option<HttpClient>,
56}
57
58impl Debug for WebdavBuilder {
59    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
60        let mut d = f.debug_struct("WebdavBuilder");
61
62        d.field("config", &self.config);
63
64        d.finish_non_exhaustive()
65    }
66}
67
68impl WebdavBuilder {
69    /// Set endpoint for http backend.
70    ///
71    /// For example: `https://example.com`
72    pub fn endpoint(mut self, endpoint: &str) -> Self {
73        self.config.endpoint = if endpoint.is_empty() {
74            None
75        } else {
76            Some(endpoint.to_string())
77        };
78
79        self
80    }
81
82    /// set the username for Webdav
83    ///
84    /// default: no username
85    pub fn username(mut self, username: &str) -> Self {
86        if !username.is_empty() {
87            self.config.username = Some(username.to_owned());
88        }
89        self
90    }
91
92    /// set the password for Webdav
93    ///
94    /// default: no password
95    pub fn password(mut self, password: &str) -> Self {
96        if !password.is_empty() {
97            self.config.password = Some(password.to_owned());
98        }
99        self
100    }
101
102    /// set the bearer token for Webdav
103    ///
104    /// default: no access token
105    pub fn token(mut self, token: &str) -> Self {
106        if !token.is_empty() {
107            self.config.token = Some(token.to_string());
108        }
109        self
110    }
111
112    /// Set root path of http backend.
113    pub fn root(mut self, root: &str) -> Self {
114        self.config.root = if root.is_empty() {
115            None
116        } else {
117            Some(root.to_string())
118        };
119
120        self
121    }
122
123    /// Specify the http client that used by this service.
124    ///
125    /// # Notes
126    ///
127    /// This API is part of OpenDAL's Raw API. `HttpClient` could be changed
128    /// during minor updates.
129    #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
130    #[allow(deprecated)]
131    pub fn http_client(mut self, client: HttpClient) -> Self {
132        self.http_client = Some(client);
133        self
134    }
135}
136
137impl Builder for WebdavBuilder {
138    const SCHEME: Scheme = Scheme::Webdav;
139    type Config = WebdavConfig;
140
141    fn build(self) -> Result<impl Access> {
142        debug!("backend build started: {:?}", &self);
143
144        let endpoint = match &self.config.endpoint {
145            Some(v) => v,
146            None => {
147                return Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
148                    .with_context("service", Scheme::Webdav));
149            }
150        };
151        // Some services might return the path with suffix `/remote.php/webdav/`, we need to trim them.
152        let server_path = http::Uri::from_str(endpoint)
153            .map_err(|err| {
154                Error::new(ErrorKind::ConfigInvalid, "endpoint is invalid")
155                    .with_context("service", Scheme::Webdav)
156                    .set_source(err)
157            })?
158            .path()
159            .trim_end_matches('/')
160            .to_string();
161
162        let root = normalize_root(&self.config.root.clone().unwrap_or_default());
163        debug!("backend use root {root}");
164
165        let mut authorization = None;
166        if let Some(username) = &self.config.username {
167            authorization = Some(format_authorization_by_basic(
168                username,
169                self.config.password.as_deref().unwrap_or_default(),
170            )?);
171        }
172        if let Some(token) = &self.config.token {
173            authorization = Some(format_authorization_by_bearer(token)?)
174        }
175
176        let core = Arc::new(WebdavCore {
177            info: {
178                let am = AccessorInfo::default();
179                am.set_scheme(Scheme::Webdav)
180                    .set_root(&root)
181                    .set_native_capability(Capability {
182                        stat: true,
183
184                        read: true,
185
186                        write: true,
187                        write_can_empty: true,
188
189                        create_dir: true,
190                        delete: true,
191
192                        copy: !self.config.disable_copy,
193
194                        rename: true,
195
196                        list: true,
197
198                        // We already support recursive list but some details still need to polish.
199                        // list_with_recursive: true,
200                        shared: true,
201
202                        ..Default::default()
203                    });
204
205                // allow deprecated api here for compatibility
206                #[allow(deprecated)]
207                if let Some(client) = self.http_client {
208                    am.update_http_client(|_| client);
209                }
210
211                am.into()
212            },
213            endpoint: endpoint.to_string(),
214            server_path,
215            authorization,
216            root,
217        });
218        Ok(WebdavBackend { core })
219    }
220}
221
222/// Backend is used to serve `Accessor` support for http.
223#[derive(Clone)]
224pub struct WebdavBackend {
225    core: Arc<WebdavCore>,
226}
227
228impl Debug for WebdavBackend {
229    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
230        f.debug_struct("WebdavBackend")
231            .field("core", &self.core)
232            .finish()
233    }
234}
235
236impl Access for WebdavBackend {
237    type Reader = HttpBody;
238    type Writer = oio::OneShotWriter<WebdavWriter>;
239    type Lister = oio::PageLister<WebdavLister>;
240    type Deleter = oio::OneShotDeleter<WebdavDeleter>;
241
242    fn info(&self) -> Arc<AccessorInfo> {
243        self.core.info.clone()
244    }
245
246    async fn create_dir(&self, path: &str, _: OpCreateDir) -> Result<RpCreateDir> {
247        self.core.webdav_mkcol(path).await?;
248        Ok(RpCreateDir::default())
249    }
250
251    async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
252        let metadata = self.core.webdav_stat(path).await?;
253        Ok(RpStat::new(metadata))
254    }
255
256    async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
257        let resp = self.core.webdav_get(path, args.range(), &args).await?;
258
259        let status = resp.status();
260
261        match status {
262            StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
263                Ok((RpRead::default(), resp.into_body()))
264            }
265            _ => {
266                let (part, mut body) = resp.into_parts();
267                let buf = body.to_buffer().await?;
268                Err(parse_error(Response::from_parts(part, buf)))
269            }
270        }
271    }
272
273    async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
274        // Ensure parent path exists
275        self.core.webdav_mkcol(get_parent(path)).await?;
276
277        Ok((
278            RpWrite::default(),
279            oio::OneShotWriter::new(WebdavWriter::new(self.core.clone(), args, path.to_string())),
280        ))
281    }
282
283    async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
284        Ok((
285            RpDelete::default(),
286            oio::OneShotDeleter::new(WebdavDeleter::new(self.core.clone())),
287        ))
288    }
289
290    async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
291        Ok((
292            RpList::default(),
293            oio::PageLister::new(WebdavLister::new(self.core.clone(), path, args)),
294        ))
295    }
296
297    async fn copy(&self, from: &str, to: &str, _args: OpCopy) -> Result<RpCopy> {
298        let resp = self.core.webdav_copy(from, to).await?;
299
300        let status = resp.status();
301
302        match status {
303            StatusCode::CREATED | StatusCode::NO_CONTENT => Ok(RpCopy::default()),
304            _ => Err(parse_error(resp)),
305        }
306    }
307
308    async fn rename(&self, from: &str, to: &str, _args: OpRename) -> Result<RpRename> {
309        let resp = self.core.webdav_move(from, to).await?;
310
311        let status = resp.status();
312        match status {
313            StatusCode::CREATED | StatusCode::NO_CONTENT | StatusCode::OK => {
314                Ok(RpRename::default())
315            }
316            _ => Err(parse_error(resp)),
317        }
318    }
319}