1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// 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 std::sync::Arc;

use http::StatusCode;

use super::core::*;
use super::error::*;
use crate::raw::*;
use crate::*;

pub struct WebdavLister {
    core: Arc<WebdavCore>,

    path: String,
    args: OpList,
}

impl WebdavLister {
    pub fn new(core: Arc<WebdavCore>, path: &str, args: OpList) -> Self {
        Self {
            core,
            path: path.to_string(),
            args,
        }
    }
}

impl oio::PageList for WebdavLister {
    async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
        let resp = self.core.webdav_list(&self.path, &self.args).await?;

        // jfrog artifactory's webdav services have some strange behavior.
        // We add this flag to check if the server is jfrog artifactory.
        //
        // Example: `"x-jfrog-version": "Artifactory/7.77.5 77705900"`
        let is_jfrog_artifactory = if let Some(v) = resp.headers().get("x-jfrog-version") {
            v.to_str().unwrap_or_default().starts_with("Artifactory")
        } else {
            false
        };

        let bs = if resp.status().is_success() {
            resp.into_body()
        } else if resp.status() == StatusCode::NOT_FOUND && self.path.ends_with('/') {
            ctx.done = true;
            return Ok(());
        } else {
            return Err(parse_error(resp));
        };

        let result: Multistatus = deserialize_multistatus(&bs.to_bytes())?;

        for res in result.response {
            let mut path = res
                .href
                .strip_prefix(&self.core.server_path)
                .unwrap_or(&res.href)
                .to_string();

            let meta = parse_propstat(&res.propstat)?;

            // Append `/` to path if it's a dir
            if !path.ends_with('/') && meta.is_dir() {
                path += "/"
            }

            let decoded_path = percent_decode_path(&path);

            // Ignore the root path itself.
            if self.core.root == decoded_path {
                continue;
            }

            let normalized_path = build_rel_path(&self.core.root, &decoded_path);

            // HACKS! HACKS! HACKS!
            //
            // jfrog artifactory will generate a virtual checksum file for each file.
            // The checksum file can't be stated, but can be listed and read.
            // We ignore the checksum files to avoid listing unexpected files.
            if is_jfrog_artifactory && meta.content_type() == Some("application/x-checksum") {
                continue;
            }

            ctx.entries
                .push_back(oio::Entry::new(&normalized_path, meta))
        }
        ctx.done = true;

        Ok(())
    }
}