1use std::fmt::Debug;
19use std::str::FromStr;
20use std::sync::Arc;
21
22use http::StatusCode;
23use log::debug;
24
25use super::WEBDAV_SCHEME;
26use super::config::WebdavConfig;
27use super::core::parse_error;
28use super::core::*;
29use super::deleter::WebdavDeleter;
30use super::lister::WebdavLister;
31use super::reader::*;
32use super::writer::WebdavWriter;
33use opendal_core::raw::oio;
34use opendal_core::raw::*;
35use opendal_core::*;
36
37#[doc = include_str!("docs.md")]
39#[derive(Default)]
40pub struct WebdavBuilder {
41 pub(super) config: WebdavConfig,
42}
43
44impl Debug for WebdavBuilder {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("WebdavBuilder")
47 .field("config", &self.config)
48 .finish_non_exhaustive()
49 }
50}
51
52impl WebdavBuilder {
53 pub fn endpoint(mut self, endpoint: &str) -> Self {
57 self.config.endpoint = if endpoint.is_empty() {
58 None
59 } else {
60 Some(endpoint.to_string())
61 };
62
63 self
64 }
65
66 pub fn username(mut self, username: &str) -> Self {
70 if !username.is_empty() {
71 self.config.username = Some(username.to_owned());
72 }
73 self
74 }
75
76 pub fn password(mut self, password: &str) -> Self {
80 if !password.is_empty() {
81 self.config.password = Some(password.to_owned());
82 }
83 self
84 }
85
86 pub fn token(mut self, token: &str) -> Self {
90 if !token.is_empty() {
91 self.config.token = Some(token.to_string());
92 }
93 self
94 }
95
96 pub fn root(mut self, root: &str) -> Self {
98 self.config.root = if root.is_empty() {
99 None
100 } else {
101 Some(root.to_string())
102 };
103
104 self
105 }
106
107 pub fn disable_create_dir(mut self, disable: bool) -> Self {
118 self.config.disable_create_dir = disable;
119 self
120 }
121
122 #[deprecated(
124 since = "0.57.0",
125 note = "WebDAV user metadata capability is enabled by default. Use CapabilityOverrideLayer to override write_with_user_metadata for endpoints without PROPPATCH support."
126 )]
127 pub fn enable_user_metadata(self, _enable: bool) -> Self {
128 self
129 }
130
131 pub fn user_metadata_prefix(mut self, prefix: &str) -> Self {
138 if !prefix.is_empty() {
139 self.config.user_metadata_prefix = Some(prefix.to_string());
140 }
141 self
142 }
143
144 pub fn user_metadata_uri(mut self, uri: &str) -> Self {
151 if !uri.is_empty() {
152 self.config.user_metadata_uri = Some(uri.to_string());
153 }
154 self
155 }
156
157 pub fn enable_conditional_read(mut self, enable: bool) -> Self {
171 self.config.enable_conditional_read = enable;
172 self
173 }
174}
175
176impl Builder for WebdavBuilder {
177 type Config = WebdavConfig;
178
179 fn build(self) -> Result<impl Service> {
180 debug!("backend build started: {:?}", self);
181
182 let endpoint = match &self.config.endpoint {
183 Some(v) => v,
184 None => {
185 return Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
186 .with_context("service", WEBDAV_SCHEME));
187 }
188 };
189 let server_path = http::Uri::from_str(endpoint)
191 .map_err(|err| {
192 Error::new(ErrorKind::ConfigInvalid, "endpoint is invalid")
193 .with_context("service", WEBDAV_SCHEME)
194 .set_source(err)
195 })?
196 .path()
197 .trim_end_matches('/')
198 .to_string();
199
200 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
201 debug!("backend use root {root}");
202
203 let mut authorization = None;
204 if let Some(username) = &self.config.username {
205 authorization = Some(format_authorization_by_basic(
206 username,
207 self.config.password.as_deref().unwrap_or_default(),
208 )?);
209 }
210 if let Some(token) = &self.config.token {
211 authorization = Some(format_authorization_by_bearer(token)?)
212 }
213
214 let conditional_read = self.config.enable_conditional_read;
215
216 let core = Arc::new(WebdavCore {
217 info: ServiceInfo::new(WEBDAV_SCHEME, &root, ""),
218 capability: Capability {
219 stat: true,
220
221 read: true,
222 read_with_suffix: true,
223 read_with_if_match: conditional_read,
224 read_with_if_none_match: conditional_read,
225 read_with_if_modified_since: conditional_read,
226 read_with_if_unmodified_since: conditional_read,
227
228 write: true,
229 write_can_empty: true,
230 write_with_user_metadata: true,
231
232 create_dir: true,
233 delete: true,
234
235 copy: true,
236
237 rename: true,
238
239 list: true,
240
241 shared: true,
244
245 ..Default::default()
246 },
247 endpoint: endpoint.to_string(),
248 server_path,
249 authorization,
250 root,
251 user_metadata_prefix: self
252 .config
253 .user_metadata_prefix
254 .unwrap_or_else(|| DEFAULT_USER_METADATA_PREFIX.to_string()),
255 user_metadata_uri: self
256 .config
257 .user_metadata_uri
258 .unwrap_or_else(|| DEFAULT_USER_METADATA_URI.to_string()),
259 disable_create_dir: self.config.disable_create_dir,
260 });
261 Ok(WebdavBackend { core })
262 }
263}
264
265#[derive(Clone, Debug)]
266pub struct WebdavBackend {
267 pub(crate) core: Arc<WebdavCore>,
268}
269
270impl Service for WebdavBackend {
271 type Reader = oio::StreamReader<WebdavReader>;
272 type Writer = oio::OneShotWriter<WebdavWriter>;
273 type Lister = oio::PageLister<WebdavLister>;
274 type Deleter = oio::OneShotDeleter<WebdavDeleter>;
275 type Copier = oio::OneShotCopier;
276
277 fn info(&self) -> ServiceInfo {
278 self.core.info.clone()
279 }
280
281 fn capability(&self) -> Capability {
282 self.core.capability
283 }
284
285 async fn create_dir(
286 &self,
287 ctx: &OperationContext,
288 path: &str,
289 _: OpCreateDir,
290 ) -> Result<RpCreateDir> {
291 self.core.webdav_mkcol(ctx, path).await?;
292 Ok(RpCreateDir::default())
293 }
294
295 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
296 let metadata = self.core.webdav_stat(ctx, path).await?;
297 Ok(RpStat::new(metadata))
298 }
299 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
300 let output: oio::StreamReader<WebdavReader> = {
301 Ok(oio::StreamReader::new(WebdavReader::new(
302 self.clone(),
303 ctx.clone(),
304 path,
305 args,
306 )))
307 }?;
308
309 Ok(output)
310 }
311
312 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
313 let output: oio::OneShotWriter<WebdavWriter> = {
314 Ok(oio::OneShotWriter::new(WebdavWriter::new(
315 self.core.clone(),
316 ctx.clone(),
317 args,
318 path.to_string(),
319 )))
320 }?;
321
322 Ok(output)
323 }
324
325 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
326 let output: oio::OneShotDeleter<WebdavDeleter> = {
327 Ok(oio::OneShotDeleter::new(WebdavDeleter::new(
328 self.core.clone(),
329 ctx.clone(),
330 )))
331 }?;
332
333 Ok(output)
334 }
335
336 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
337 let output: oio::PageLister<WebdavLister> = {
338 Ok(oio::PageLister::new(WebdavLister::new(
339 self.core.clone(),
340 ctx.clone(),
341 path,
342 args,
343 )))
344 }?;
345
346 Ok(output)
347 }
348
349 fn copy(
350 &self,
351 ctx: &OperationContext,
352 from: &str,
353 to: &str,
354 _args: OpCopy,
355 _opts: OpCopier,
356 ) -> Result<Self::Copier> {
357 let core = self.core.clone();
358 let ctx = ctx.clone();
359 let from = from.to_string();
360 let to = to.to_string();
361
362 Ok(oio::OneShotCopier::new_with(move || {
363 let core = core.clone();
364 let ctx = ctx.clone();
365 let from = from.clone();
366 let to = to.clone();
367
368 async move {
369 let resp = core.webdav_copy(&ctx, &from, &to).await?;
370 let status = resp.status();
371
372 match status {
373 StatusCode::CREATED | StatusCode::NO_CONTENT => Ok(Metadata::default()),
374 _ => Err(parse_error(resp)),
375 }
376 }
377 }))
378 }
379
380 async fn rename(
381 &self,
382 ctx: &OperationContext,
383 from: &str,
384 to: &str,
385 _args: OpRename,
386 ) -> Result<RpRename> {
387 let resp = self.core.webdav_move(ctx, from, to).await?;
388
389 let status = resp.status();
390 match status {
391 StatusCode::CREATED | StatusCode::NO_CONTENT | StatusCode::OK => {
392 Ok(RpRename::default())
393 }
394 _ => Err(parse_error(resp)),
395 }
396 }
397
398 async fn presign(
399 &self,
400 _ctx: &OperationContext,
401 _path: &str,
402 _args: OpPresign,
403 ) -> Result<RpPresign> {
404 Err(Error::new(
405 ErrorKind::Unsupported,
406 "operation is not supported",
407 ))
408 }
409}