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"
+        );
+    }
+}


Reply via email to