This is an automated email from the ASF dual-hosted git repository. xuanwo pushed a commit to branch add-nextcloud-test-for-webdav in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
commit 29c07d796b6ebc90223601956675e2a03add5d65 Author: Xuanwo <[email protected]> AuthorDate: Thu Jul 13 21:58:50 2023 +0800 Save work Signed-off-by: Xuanwo <[email protected]> --- core/src/raw/path.rs | 1 - core/src/services/webdav/backend.rs | 79 ++--- core/src/services/webdav/list_response.rs | 500 ------------------------------ core/src/services/webdav/mod.rs | 1 - core/src/services/webdav/pager.rs | 488 ++++++++++++++++++++++++++++- 5 files changed, 508 insertions(+), 561 deletions(-) diff --git a/core/src/raw/path.rs b/core/src/raw/path.rs index 652938806..cdc88d032 100644 --- a/core/src/raw/path.rs +++ b/core/src/raw/path.rs @@ -176,7 +176,6 @@ pub fn get_basename(path: &str) -> &str { } /// Get parent from path. -#[allow(dead_code)] pub fn get_parent(path: &str) -> &str { if path == "/" { return "/"; diff --git a/core/src/services/webdav/backend.rs b/core/src/services/webdav/backend.rs index 9288b25c8..3a1ff789b 100644 --- a/core/src/services/webdav/backend.rs +++ b/core/src/services/webdav/backend.rs @@ -30,7 +30,7 @@ use http::StatusCode; use log::debug; use super::error::parse_error; -use super::list_response::Multistatus; +use super::pager::Multistatus; use super::pager::WebdavPager; use super::writer::WebdavWriter; use crate::raw::*; @@ -298,29 +298,9 @@ impl Accessor for WebdavBackend { async fn create_dir(&self, path: &str, _: OpCreateDir) -> Result<RpCreateDir> { self.ensure_parent_path(path).await?; + self.create_dir_internal(path).await?; - let abs_path = build_abs_path(&self.root, path); - - let resp = self - .webdav_mkcol(&abs_path, None, None, AsyncBody::Empty) - .await?; - - let status = resp.status(); - - match status { - StatusCode::CREATED - | StatusCode::OK - // `File exists` will return `Method Not Allowed` - | StatusCode::METHOD_NOT_ALLOWED - // create existing dir will return conflict - | StatusCode::CONFLICT - // create existing file will return no_content - | StatusCode::NO_CONTENT => { - resp.into_body().consume().await?; - Ok(RpCreateDir::default()) - } - _ => Err(parse_error(resp).await?), - } + Ok(RpCreateDir::default()) } async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> { @@ -529,29 +509,19 @@ impl WebdavBackend { self.client.send(req).await } - async fn webdav_mkcol( - &self, - abs_path: &str, - content_type: Option<&str>, - content_disposition: Option<&str>, - body: AsyncBody, - ) -> Result<Response<IncomingAsyncBody>> { - let url = format!("{}/{}", self.endpoint, percent_encode_path(abs_path)); + async fn webdav_mkcol(&self, path: &str) -> Result<Response<IncomingAsyncBody>> { + let p = build_abs_path(&self.root, path); + + let url = format!("{}/{}", self.endpoint, percent_encode_path(&p)); let mut req = Request::builder().method("MKCOL").uri(&url); if let Some(auth) = &self.authorization { req = req.header(header::AUTHORIZATION, auth); } - if let Some(mime) = content_type { - req = req.header(header::CONTENT_TYPE, mime) - } - - if let Some(cd) = content_disposition { - req = req.header(header::CONTENT_DISPOSITION, cd) - } - - let req = req.body(body).map_err(new_request_build_error)?; + let req = req + .body(AsyncBody::Empty) + .map_err(new_request_build_error)?; self.client.send(req).await } @@ -664,11 +634,7 @@ impl WebdavBackend { } async fn create_dir_internal(&self, path: &str) -> Result<()> { - let abs_path = build_abs_path(&self.root, path); - - let resp = self - .webdav_mkcol(&abs_path, None, None, AsyncBody::Empty) - .await?; + let resp = self.webdav_mkcol(path).await?; let status = resp.status(); @@ -680,7 +646,9 @@ impl WebdavBackend { // create existing dir will return conflict | StatusCode::CONFLICT // create existing file will return no_content - | StatusCode::NO_CONTENT => { + | StatusCode::NO_CONTENT + // ALlow mutiple status + | StatusCode::MULTI_STATUS=> { resp.into_body().consume().await?; Ok(()) } @@ -694,13 +662,24 @@ impl WebdavBackend { while path != "/" { // check path first. let parent = get_parent(path); - match self.stat(path, OpStat::default()).await { - Ok(_) => break, - Err(err) if err.kind() != ErrorKind::NotFound => return Err(err), - _ => { + log::info!("parent: {}", parent); + if parent == "/" { + break; + } + + let mut header_map = HeaderMap::new(); + // not include children + header_map.insert("Depth", "0".parse().unwrap()); + header_map.insert(header::ACCEPT, "application/xml".parse().unwrap()); + + let resp = self.webdav_propfind(path, Some(header_map)).await?; + match resp.status() { + StatusCode::OK | StatusCode::MULTI_STATUS => break, + StatusCode::NOT_FOUND => { dirs.push_front(path); path = parent } + _ => return Err(parse_error(resp).await?), } } diff --git a/core/src/services/webdav/list_response.rs b/core/src/services/webdav/list_response.rs deleted file mode 100644 index e8b5c7000..000000000 --- a/core/src/services/webdav/list_response.rs +++ /dev/null @@ -1,500 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use serde::Deserialize; - -use crate::raw::parse_datetime_from_rfc2822; -use crate::EntryMode; -use crate::Error; -use crate::ErrorKind; -use crate::Metadata; -use crate::Result; - -#[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct Multistatus { - pub response: Vec<ListOpResponse>, -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct ListOpResponse { - pub href: String, - pub propstat: Propstat, -} - -impl ListOpResponse { - pub fn parse_into_metadata(&self) -> Result<Metadata> { - let ListOpResponse { - href, - propstat: - Propstat { - prop: - Prop { - getlastmodified, - getcontentlength, - getcontenttype, - getetag, - .. - }, - status, - }, - } = self; - if let [_, code, text] = status.split(' ').collect::<Vec<_>>()[..3] { - // As defined in https://tools.ietf.org/html/rfc2068#section-6.1 - let code = code.parse::<u16>().unwrap(); - if code >= 400 { - return Err(Error::new( - ErrorKind::Unexpected, - &format!("Invalid response: {} {}", code, text), - )); - } - } - - let mode = if href.ends_with('/') { - EntryMode::DIR - } else { - EntryMode::FILE - }; - let mut m = Metadata::new(mode); - - if let Some(v) = getcontentlength { - m.set_content_length(v.parse::<u64>().unwrap()); - } - - if let Some(v) = getcontenttype { - m.set_content_type(v); - } - - if let Some(v) = getetag { - m.set_etag(v); - } - // https://www.rfc-editor.org/rfc/rfc4918#section-14.18 - m.set_last_modified(parse_datetime_from_rfc2822(getlastmodified)?); - Ok(m) - } -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct Propstat { - pub prop: Prop, - pub status: String, -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct Prop { - #[serde(default)] - pub displayname: String, - pub getlastmodified: String, - pub getetag: Option<String>, - pub getcontentlength: Option<String>, - pub getcontenttype: Option<String>, - pub resourcetype: ResourceTypeContainer, -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct ResourceTypeContainer { - #[serde(rename = "$value")] - pub value: Option<ResourceType>, -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum ResourceType { - Collection, -} - -#[cfg(test)] -mod tests { - use quick_xml::de::from_str; - - use super::*; - - #[test] - fn test_propstat() { - let xml = r#"<D:propstat> - <D:prop> - <D:displayname>/</D:displayname> - <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> - <D:resourcetype><D:collection/></D:resourcetype> - <D:lockdiscovery/> - <D:supportedlock> - <D:lockentry> - <D:lockscope><D:exclusive/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat>"#; - - let propstat = from_str::<Propstat>(xml).unwrap(); - assert_eq!( - propstat.prop.getlastmodified, - "Tue, 01 May 2022 06:39:47 GMT" - ); - assert_eq!( - propstat.prop.resourcetype.value.unwrap(), - ResourceType::Collection - ); - - assert_eq!(propstat.status, "HTTP/1.1 200 OK"); - } - - #[test] - fn test_response_simple() { - let xml = r#"<D:response> - <D:href>/</D:href> - <D:propstat> - <D:prop> - <D:displayname>/</D:displayname> - <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> - <D:resourcetype><D:collection/></D:resourcetype> - <D:lockdiscovery/> - <D:supportedlock> - <D:lockentry> - <D:lockscope><D:exclusive/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response>"#; - - let response = from_str::<ListOpResponse>(xml).unwrap(); - assert_eq!(response.href, "/"); - - assert_eq!(response.propstat.prop.displayname, "/"); - - assert_eq!( - response.propstat.prop.getlastmodified, - "Tue, 01 May 2022 06:39:47 GMT" - ); - assert_eq!( - response.propstat.prop.resourcetype.value.unwrap(), - ResourceType::Collection - ); - assert_eq!(response.propstat.status, "HTTP/1.1 200 OK"); - } - - #[test] - fn test_response_file() { - let xml = r#"<D:response> - <D:href>/test_file</D:href> - <D:propstat> - <D:prop> - <D:displayname>test_file</D:displayname> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Tue, 07 May 2022 05:52:22 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - <D:lockdiscovery /> - <D:supportedlock> - <D:lockentry> - <D:lockscope> - <D:exclusive /> - </D:lockscope> - <D:locktype> - <D:write /> - </D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response>"#; - - let response = from_str::<ListOpResponse>(xml).unwrap(); - assert_eq!(response.href, "/test_file"); - assert_eq!( - response.propstat.prop.getlastmodified, - "Tue, 07 May 2022 05:52:22 GMT" - ); - assert_eq!(response.propstat.prop.getcontentlength.unwrap(), "1"); - assert_eq!(response.propstat.prop.resourcetype.value, None); - assert_eq!(response.propstat.status, "HTTP/1.1 200 OK"); - } - - #[test] - fn test_with_multiple_items_simple() { - let xml = r#"<D:multistatus xmlns:D="DAV:"> - <D:response> - <D:href>/</D:href> - <D:propstat> - <D:prop> - <D:displayname>/</D:displayname> - <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> - <D:resourcetype><D:collection/></D:resourcetype> - <D:lockdiscovery/> - <D:supportedlock> - <D:lockentry> - <D:lockscope><D:exclusive/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/</D:href> - <D:propstat> - <D:prop> - <D:displayname>/</D:displayname> - <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> - <D:resourcetype><D:collection/></D:resourcetype> - <D:lockdiscovery/> - <D:supportedlock> - <D:lockentry> - <D:lockscope><D:exclusive/></D:lockscope> - <D:locktype><D:write/></D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - </D:multistatus>"#; - - let multistatus = from_str::<Multistatus>(xml).unwrap(); - assert_eq!(multistatus.response.len(), 2); - assert_eq!(multistatus.response[0].href, "/"); - assert_eq!( - multistatus.response[0].propstat.prop.getlastmodified, - "Tue, 01 May 2022 06:39:47 GMT" - ); - } - - #[test] - fn test_with_multiple_items_mixed() { - let xml = r#"<?xml version="1.0" encoding="utf-8"?> - <D:multistatus xmlns:D="DAV:"> - <D:response> - <D:href>/</D:href> - <D:propstat> - <D:prop> - <D:displayname>/</D:displayname> - <D:getlastmodified>Tue, 07 May 2022 06:39:47 GMT</D:getlastmodified> - <D:resourcetype> - <D:collection /> - </D:resourcetype> - <D:lockdiscovery /> - <D:supportedlock> - <D:lockentry> - <D:lockscope> - <D:exclusive /> - </D:lockscope> - <D:locktype> - <D:write /> - </D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/testdir/</D:href> - <D:propstat> - <D:prop> - <D:displayname>testdir</D:displayname> - <D:getlastmodified>Tue, 07 May 2022 06:40:10 GMT</D:getlastmodified> - <D:resourcetype> - <D:collection /> - </D:resourcetype> - <D:lockdiscovery /> - <D:supportedlock> - <D:lockentry> - <D:lockscope> - <D:exclusive /> - </D:lockscope> - <D:locktype> - <D:write /> - </D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file</D:href> - <D:propstat> - <D:prop> - <D:displayname>test_file</D:displayname> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Tue, 07 May 2022 05:52:22 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - <D:lockdiscovery /> - <D:supportedlock> - <D:lockentry> - <D:lockscope> - <D:exclusive /> - </D:lockscope> - <D:locktype> - <D:write /> - </D:locktype> - </D:lockentry> - </D:supportedlock> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - </D:multistatus>"#; - - let multistatus = from_str::<Multistatus>(xml).unwrap(); - - assert_eq!(multistatus.response.len(), 3); - let first_response = &multistatus.response[0]; - assert_eq!(first_response.href, "/"); - assert_eq!( - first_response.propstat.prop.getlastmodified, - "Tue, 07 May 2022 06:39:47 GMT" - ); - - let second_response = &multistatus.response[1]; - assert_eq!(second_response.href, "/testdir/"); - assert_eq!( - second_response.propstat.prop.getlastmodified, - "Tue, 07 May 2022 06:40:10 GMT" - ); - - let third_response = &multistatus.response[2]; - assert_eq!(third_response.href, "/test_file"); - assert_eq!( - third_response.propstat.prop.getlastmodified, - "Tue, 07 May 2022 05:52:22 GMT" - ); - } - - #[test] - fn test_with_multiple_items_mixed_nginx() { - let xml = r#"<?xml version="1.0" encoding="utf-8"?> - <D:multistatus xmlns:D="DAV:"> - <D:response> - <D:href>/</D:href> - <D:propstat> - <D:prop> - <D:getlastmodified>Fri, 17 Feb 2023 03:37:22 GMT</D:getlastmodified> - <D:resourcetype> - <D:collection /> - </D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_75</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_36</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_38</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_59</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_9</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_93</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_43</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - <D:response> - <D:href>/test_file_95</D:href> - <D:propstat> - <D:prop> - <D:getcontentlength>1</D:getcontentlength> - <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> - <D:resourcetype></D:resourcetype> - </D:prop> - <D:status>HTTP/1.1 200 OK</D:status> - </D:propstat> - </D:response> - </D:multistatus> - "#; - - let multistatus: Multistatus = from_str(xml).unwrap(); - - assert_eq!(multistatus.response.len(), 9); - - let first_response = &multistatus.response[0]; - assert_eq!(first_response.href, "/"); - assert_eq!( - first_response.propstat.prop.getlastmodified, - "Fri, 17 Feb 2023 03:37:22 GMT" - ); - } -} diff --git a/core/src/services/webdav/mod.rs b/core/src/services/webdav/mod.rs index 63d00511b..2b46724bb 100644 --- a/core/src/services/webdav/mod.rs +++ b/core/src/services/webdav/mod.rs @@ -19,6 +19,5 @@ mod backend; pub use backend::WebdavBuilder as Webdav; mod error; -mod list_response; mod pager; mod writer; diff --git a/core/src/services/webdav/pager.rs b/core/src/services/webdav/pager.rs index 3cd8ebb81..4bbf1e135 100644 --- a/core/src/services/webdav/pager.rs +++ b/core/src/services/webdav/pager.rs @@ -18,14 +18,10 @@ use std::mem; use async_trait::async_trait; +use serde::Deserialize; -use super::list_response::Multistatus; -use crate::raw::build_rel_path; -use crate::raw::oio; -use crate::EntryMode; -use crate::Metadata; -use crate::Result; - +use crate::raw::*; +use crate::*; pub struct WebdavPager { root: String, path: String, @@ -66,8 +62,7 @@ impl oio::Page for WebdavPager { return None; } - let entry = if de.propstat.prop.resourcetype.value - == Some(super::list_response::ResourceType::Collection) + let entry = if de.propstat.prop.resourcetype.value == Some(ResourceType::Collection) { oio::Entry::new(&normalized_path, Metadata::new(EntryMode::DIR)) } else { @@ -81,3 +76,478 @@ impl oio::Page for WebdavPager { Ok(Some(oes)) } } + +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct Multistatus { + pub response: Vec<ListOpResponse>, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct ListOpResponse { + pub href: String, + pub propstat: Propstat, +} + +impl ListOpResponse { + pub fn parse_into_metadata(&self) -> Result<Metadata> { + let ListOpResponse { + href, + propstat: + Propstat { + prop: + Prop { + getlastmodified, + getcontentlength, + getcontenttype, + getetag, + .. + }, + status, + }, + } = self; + if let [_, code, text] = status.split(' ').collect::<Vec<_>>()[..3] { + // As defined in https://tools.ietf.org/html/rfc2068#section-6.1 + let code = code.parse::<u16>().unwrap(); + if code >= 400 { + return Err(Error::new( + ErrorKind::Unexpected, + &format!("Invalid response: {} {}", code, text), + )); + } + } + + let mode = if href.ends_with('/') { + EntryMode::DIR + } else { + EntryMode::FILE + }; + let mut m = Metadata::new(mode); + + if let Some(v) = getcontentlength { + m.set_content_length(v.parse::<u64>().unwrap()); + } + + if let Some(v) = getcontenttype { + m.set_content_type(v); + } + + if let Some(v) = getetag { + m.set_etag(v); + } + // https://www.rfc-editor.org/rfc/rfc4918#section-14.18 + m.set_last_modified(parse_datetime_from_rfc2822(getlastmodified)?); + Ok(m) + } +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct Propstat { + pub prop: Prop, + pub status: String, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct Prop { + #[serde(default)] + pub displayname: String, + pub getlastmodified: String, + pub getetag: Option<String>, + pub getcontentlength: Option<String>, + pub getcontenttype: Option<String>, + pub resourcetype: ResourceTypeContainer, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct ResourceTypeContainer { + #[serde(rename = "$value")] + pub value: Option<ResourceType>, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ResourceType { + Collection, +} + +#[cfg(test)] +mod tests { + use quick_xml::de::from_str; + + use super::*; + + #[test] + fn test_propstat() { + let xml = r#"<D:propstat> + <D:prop> + <D:displayname>/</D:displayname> + <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> + <D:resourcetype><D:collection/></D:resourcetype> + <D:lockdiscovery/> + <D:supportedlock> + <D:lockentry> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat>"#; + + let propstat = from_str::<Propstat>(xml).unwrap(); + assert_eq!( + propstat.prop.getlastmodified, + "Tue, 01 May 2022 06:39:47 GMT" + ); + assert_eq!( + propstat.prop.resourcetype.value.unwrap(), + ResourceType::Collection + ); + + assert_eq!(propstat.status, "HTTP/1.1 200 OK"); + } + + #[test] + fn test_response_simple() { + let xml = r#"<D:response> + <D:href>/</D:href> + <D:propstat> + <D:prop> + <D:displayname>/</D:displayname> + <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> + <D:resourcetype><D:collection/></D:resourcetype> + <D:lockdiscovery/> + <D:supportedlock> + <D:lockentry> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response>"#; + + let response = from_str::<ListOpResponse>(xml).unwrap(); + assert_eq!(response.href, "/"); + + assert_eq!(response.propstat.prop.displayname, "/"); + + assert_eq!( + response.propstat.prop.getlastmodified, + "Tue, 01 May 2022 06:39:47 GMT" + ); + assert_eq!( + response.propstat.prop.resourcetype.value.unwrap(), + ResourceType::Collection + ); + assert_eq!(response.propstat.status, "HTTP/1.1 200 OK"); + } + + #[test] + fn test_response_file() { + let xml = r#"<D:response> + <D:href>/test_file</D:href> + <D:propstat> + <D:prop> + <D:displayname>test_file</D:displayname> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Tue, 07 May 2022 05:52:22 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + <D:lockdiscovery /> + <D:supportedlock> + <D:lockentry> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response>"#; + + let response = from_str::<ListOpResponse>(xml).unwrap(); + assert_eq!(response.href, "/test_file"); + assert_eq!( + response.propstat.prop.getlastmodified, + "Tue, 07 May 2022 05:52:22 GMT" + ); + assert_eq!(response.propstat.prop.getcontentlength.unwrap(), "1"); + assert_eq!(response.propstat.prop.resourcetype.value, None); + assert_eq!(response.propstat.status, "HTTP/1.1 200 OK"); + } + + #[test] + fn test_with_multiple_items_simple() { + let xml = r#"<D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>/</D:href> + <D:propstat> + <D:prop> + <D:displayname>/</D:displayname> + <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> + <D:resourcetype><D:collection/></D:resourcetype> + <D:lockdiscovery/> + <D:supportedlock> + <D:lockentry> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/</D:href> + <D:propstat> + <D:prop> + <D:displayname>/</D:displayname> + <D:getlastmodified>Tue, 01 May 2022 06:39:47 GMT</D:getlastmodified> + <D:resourcetype><D:collection/></D:resourcetype> + <D:lockdiscovery/> + <D:supportedlock> + <D:lockentry> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + </D:multistatus>"#; + + let multistatus = from_str::<Multistatus>(xml).unwrap(); + assert_eq!(multistatus.response.len(), 2); + assert_eq!(multistatus.response[0].href, "/"); + assert_eq!( + multistatus.response[0].propstat.prop.getlastmodified, + "Tue, 01 May 2022 06:39:47 GMT" + ); + } + + #[test] + fn test_with_multiple_items_mixed() { + let xml = r#"<?xml version="1.0" encoding="utf-8"?> + <D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>/</D:href> + <D:propstat> + <D:prop> + <D:displayname>/</D:displayname> + <D:getlastmodified>Tue, 07 May 2022 06:39:47 GMT</D:getlastmodified> + <D:resourcetype> + <D:collection /> + </D:resourcetype> + <D:lockdiscovery /> + <D:supportedlock> + <D:lockentry> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/testdir/</D:href> + <D:propstat> + <D:prop> + <D:displayname>testdir</D:displayname> + <D:getlastmodified>Tue, 07 May 2022 06:40:10 GMT</D:getlastmodified> + <D:resourcetype> + <D:collection /> + </D:resourcetype> + <D:lockdiscovery /> + <D:supportedlock> + <D:lockentry> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file</D:href> + <D:propstat> + <D:prop> + <D:displayname>test_file</D:displayname> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Tue, 07 May 2022 05:52:22 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + <D:lockdiscovery /> + <D:supportedlock> + <D:lockentry> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + </D:lockentry> + </D:supportedlock> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + </D:multistatus>"#; + + let multistatus = from_str::<Multistatus>(xml).unwrap(); + + assert_eq!(multistatus.response.len(), 3); + let first_response = &multistatus.response[0]; + assert_eq!(first_response.href, "/"); + assert_eq!( + first_response.propstat.prop.getlastmodified, + "Tue, 07 May 2022 06:39:47 GMT" + ); + + let second_response = &multistatus.response[1]; + assert_eq!(second_response.href, "/testdir/"); + assert_eq!( + second_response.propstat.prop.getlastmodified, + "Tue, 07 May 2022 06:40:10 GMT" + ); + + let third_response = &multistatus.response[2]; + assert_eq!(third_response.href, "/test_file"); + assert_eq!( + third_response.propstat.prop.getlastmodified, + "Tue, 07 May 2022 05:52:22 GMT" + ); + } + + #[test] + fn test_with_multiple_items_mixed_nginx() { + let xml = r#"<?xml version="1.0" encoding="utf-8"?> + <D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>/</D:href> + <D:propstat> + <D:prop> + <D:getlastmodified>Fri, 17 Feb 2023 03:37:22 GMT</D:getlastmodified> + <D:resourcetype> + <D:collection /> + </D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_75</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_36</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_38</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_59</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_9</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_93</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_43</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + <D:response> + <D:href>/test_file_95</D:href> + <D:propstat> + <D:prop> + <D:getcontentlength>1</D:getcontentlength> + <D:getlastmodified>Fri, 17 Feb 2023 03:36:54 GMT</D:getlastmodified> + <D:resourcetype></D:resourcetype> + </D:prop> + <D:status>HTTP/1.1 200 OK</D:status> + </D:propstat> + </D:response> + </D:multistatus> + "#; + + let multistatus: Multistatus = from_str(xml).unwrap(); + + assert_eq!(multistatus.response.len(), 9); + + let first_response = &multistatus.response[0]; + assert_eq!(first_response.href, "/"); + assert_eq!( + first_response.propstat.prop.getlastmodified, + "Fri, 17 Feb 2023 03:37:22 GMT" + ); + } +}
