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 type Composer = ();
277
278 fn info(&self) -> ServiceInfo {
279 self.core.info.clone()
280 }
281
282 fn capability(&self) -> Capability {
283 self.core.capability
284 }
285
286 async fn create_dir(
287 &self,
288 ctx: &OperationContext,
289 path: &str,
290 _: OpCreateDir,
291 ) -> Result<RpCreateDir> {
292 self.core.webdav_mkcol(ctx, path).await?;
293 Ok(RpCreateDir::default())
294 }
295
296 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
297 let metadata = self.core.webdav_stat(ctx, path).await?;
298 Ok(RpStat::new(metadata))
299 }
300 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
301 let output: oio::StreamReader<WebdavReader> = {
302 Ok(oio::StreamReader::new(WebdavReader::new(
303 self.clone(),
304 ctx.clone(),
305 path,
306 args,
307 )))
308 }?;
309
310 Ok(output)
311 }
312
313 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
314 let output: oio::OneShotWriter<WebdavWriter> = {
315 Ok(oio::OneShotWriter::new(WebdavWriter::new(
316 self.core.clone(),
317 ctx.clone(),
318 args,
319 path.to_string(),
320 )))
321 }?;
322
323 Ok(output)
324 }
325
326 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
327 let output: oio::OneShotDeleter<WebdavDeleter> = {
328 Ok(oio::OneShotDeleter::new(WebdavDeleter::new(
329 self.core.clone(),
330 ctx.clone(),
331 )))
332 }?;
333
334 Ok(output)
335 }
336
337 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
338 let output: oio::PageLister<WebdavLister> = {
339 Ok(oio::PageLister::new(WebdavLister::new(
340 self.core.clone(),
341 ctx.clone(),
342 path,
343 args,
344 )))
345 }?;
346
347 Ok(output)
348 }
349
350 fn copy(
351 &self,
352 ctx: &OperationContext,
353 from: &str,
354 to: &str,
355 args: OpCopy,
356 ) -> Result<Self::Copier> {
357 let backend = self.clone();
358 let core = self.core.clone();
359 let ctx = ctx.clone();
360 let from = from.to_string();
361 let to = to.to_string();
362 let source_content_length_hint = args.source_content_length_hint();
363
364 Ok(oio::OneShotCopier::new_with(move || {
365 let backend = backend.clone();
366 let core = core.clone();
367 let ctx = ctx.clone();
368 let from = from.clone();
369 let to = to.clone();
370
371 async move {
372 let source_size = match source_content_length_hint {
373 Some(size) => size,
374 None => backend
375 .stat(&ctx, &from, OpStat::default())
376 .await?
377 .into_metadata()
378 .content_length(),
379 };
380
381 let resp = core.webdav_copy(&ctx, &from, &to).await?;
382 let status = resp.status();
383
384 match status {
385 StatusCode::CREATED | StatusCode::NO_CONTENT => {
386 Ok(MetadataBuilder::file(source_size).build())
387 }
388 _ => Err(parse_error(
389 ErrorContext::new(ServiceOperation("Copy")),
390 resp,
391 )),
392 }
393 }
394 }))
395 }
396
397 async fn rename(
398 &self,
399 ctx: &OperationContext,
400 from: &str,
401 to: &str,
402 _args: OpRename,
403 ) -> Result<RpRename> {
404 let resp = self.core.webdav_move(ctx, from, to).await?;
405
406 let status = resp.status();
407 match status {
408 StatusCode::CREATED | StatusCode::NO_CONTENT | StatusCode::OK => {
409 Ok(RpRename::default())
410 }
411 _ => Err(parse_error(
412 ErrorContext::new(ServiceOperation("Move")),
413 resp,
414 )),
415 }
416 }
417
418 async fn presign(
419 &self,
420 _ctx: &OperationContext,
421 _path: &str,
422 _args: OpPresign,
423 ) -> Result<RpPresign> {
424 Err(Error::new(
425 ErrorKind::Unsupported,
426 "operation is not supported",
427 ))
428 }
429}