Xuanwo commented on code in PR #3980:
URL: 
https://github.com/apache/incubator-opendal/pull/3980#discussion_r1453144641


##########
.typos.toml:
##########
@@ -23,10 +23,13 @@
 "hellow" = "hellow"
 # Showed up in examples.
 "thw" = "thw"
+# Showed up in test.
+"hsa" = "hsa"
 
 [files]
 extend-exclude = [
   # Generated SSH Keys.
   "fixtures/sftp/test_ssh_key",
   "fixtures/sftp/test_ssh_key.pub",
+  "core/src/services/icloud/core.rs"

Review Comment:
   Please don't do this. Ignore `hsa` is enough.



##########
core/src/services/icloud/backend.rs:
##########
@@ -0,0 +1,336 @@
+// 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 async_trait::async_trait;
+use http::StatusCode;
+use log::debug;
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::fmt::{Debug, Formatter};
+use std::sync::Arc;
+use tokio::sync::Mutex;
+
+use crate::raw::*;
+use crate::services::icloud::core::{parse_error, IcloudSigner, SessionData};
+use crate::*;
+use crate::{Builder, Capability, Error, ErrorKind, Scheme};
+
+use super::client::Client;
+
+/// Config for icloud services support.
+#[derive(Default, Deserialize)]
+#[serde(default)]
+#[non_exhaustive]
+pub struct IcloudConfig {
+    /// root of this backend.
+    ///
+    /// All operations will happen under this root.
+    ///
+    /// default to `/` if not set.
+    pub root: Option<String>,
+    /// apple_id of this backend.
+    ///
+    /// apple_id must be full, mostly like `[email protected]`.
+    pub apple_id: Option<String>,
+    /// password of this backend.
+    ///
+    /// password must be full.
+    pub password: Option<String>,
+
+    /// Session
+    ///
+    /// token must be valid.
+    pub trust_token: Option<String>,
+    pub ds_web_auth_token: Option<String>,
+    /// China region `origin` Header needs to be set to 
"https://www.icloud.com.cn";.
+    ///
+    /// otherwise Apple server will return 302.
+    pub region: Option<String>,
+}
+
+impl Debug for IcloudConfig {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut d = f.debug_struct("iCloudBuilder");
+        d.field("root", &self.root);
+        d.field("trust_token", &self.trust_token);

Review Comment:
   Please don't print them in debug.



##########
core/src/services/icloud/core.rs:
##########
@@ -0,0 +1,648 @@
+// 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 bytes::Buf;
+use std::collections::{BTreeMap, HashMap};
+use std::fmt::{Debug, Formatter};
+
+use http::header;
+use http::Method;
+use http::Request;
+use http::Response;
+use http::StatusCode;
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::types::Result;
+use crate::{Error, ErrorKind};
+
+use crate::raw::{
+    new_json_deserialize_error, with_error_response_context, AsyncBody, 
HttpClient,
+    IncomingAsyncBody,
+};
+
+static ACCOUNT_COUNTRY_HEADER: &str = "X-Apple-ID-Account-Country";
+static OAUTH_STATE_HEADER: &str = "X-Apple-OAuth-State";
+
+static SESSION_ID_HEADER: &str = "X-Apple-ID-Session-Id";
+
+static SCNT_HEADER: &str = "scnt";
+
+static SESSION_TOKEN_HEADER: &str = "X-Apple-Session-Token";
+static AUTH_ENDPOINT: &str = "https://idmsa.apple.com/appleauth/auth";;
+static SETUP_ENDPOINT: &str = "https://setup.icloud.com/setup/ws/1";;
+
+static APPLE_RESPONSE_HEADER: &str = "X-Apple-I-Rscd";
+
+const AUTH_HEADERS: [(&str, &str); 7] = [
+    (
+        "X-Apple-OAuth-Client-Id",
+        "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d",
+    ),
+    ("X-Apple-OAuth-Client-Type", "firstPartyAuth"),
+    ("X-Apple-OAuth-Redirect-URI", "https://www.icloud.com";),
+    ("X-Apple-OAuth-Require-Grant-Code", "true"),
+    ("X-Apple-OAuth-Response-Mode", "web_message"),
+    ("X-Apple-OAuth-Response-Type", "code"),
+    (
+        "X-Apple-Widget-Key",
+        "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d",
+    ),
+];
+
+pub struct ServiceInfo {
+    pub url: String,
+}
+
+pub struct SessionData {
+    oauth_state: String,
+    session_id: Option<String>,
+    session_token: Option<String>,
+
+    scnt: Option<String>,
+    account_country: Option<String>,
+
+    cookies: BTreeMap<String, String>,
+    webservices: HashMap<String, ServiceInfo>,
+}
+
+impl SessionData {
+    pub fn new() -> SessionData {
+        Self {
+            oauth_state: format!("auth-{}", uuid::Uuid::new_v4()).to_string(),
+            session_id: None,
+            session_token: None,
+            scnt: None,
+            account_country: None,
+            cookies: BTreeMap::new(),
+            webservices: HashMap::new(),
+        }
+    }
+}
+
+pub struct IcloudSigner {
+    pub client: HttpClient,
+
+    pub data: SessionData,
+    pub apple_id: String,
+    pub password: String,
+
+    pub trust_token: Option<String>,
+    pub ds_web_auth_token: Option<String>,
+    pub region: Option<String>,
+}
+
+impl Debug for IcloudSigner {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut de = f.debug_struct("iCloud signer");
+        de.field("trust_token", &self.trust_token);
+        de.field("ds_web_auth_token", &self.ds_web_auth_token);
+        de.field("region", &self.region);
+        de.finish()
+    }
+}
+
+impl IcloudSigner {
+    pub fn get_service_info(&self, name: String) -> Option<&ServiceInfo> {
+        self.data.webservices.get(&name)
+    }
+
+    #[warn(dead_code)]
+    // create_dir could use client_id
+    pub fn get_client_id(&self) -> &String {
+        &self.data.oauth_state
+    }
+}
+
+impl IcloudSigner {
+    pub async fn signer(&mut self) -> Result<()> {
+        let body = json!({
+            "accountName" : self.apple_id,
+            "password" : self.password,
+            "rememberMe": true,
+            "trustTokens": [self.trust_token.clone().unwrap()],
+        })
+        .to_string();
+
+        let uri = format!("{}/signin?isRememberMeEnable=true", AUTH_ENDPOINT);
+
+        let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
+
+        let response = self.sign(Method::POST, uri, async_body).await?;
+
+        let status = response.status();
+
+        return match status {
+            StatusCode::OK => {
+                if let Some(rscd) = 
response.headers().get(APPLE_RESPONSE_HEADER) {
+                    let status_code = 
StatusCode::from_bytes(rscd.as_bytes()).unwrap();
+                    //409
+                    if status_code != StatusCode::CONFLICT {
+                        return Err(parse_error(response).await?);
+                    }
+                }
+                self.authenticate().await
+            }
+            _ => Err(parse_error(response).await?),
+        };
+    }
+
+    pub async fn authenticate(&mut self) -> Result<()> {
+        let body = json!({
+            "accountCountryCode": 
self.data.account_country.as_ref().unwrap_or(&String::new()),
+            
"dsWebAuthToken":self.ds_web_auth_token.as_ref().unwrap_or(&String::new()),
+                    "extended_login": true,
+                    "trustToken": 
self.trust_token.as_ref().unwrap_or(&String::new())
+        })
+        .to_string();
+
+        let uri = format!("{}/accountLogin", SETUP_ENDPOINT);
+
+        let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
+
+        let response = self.sign(Method::POST, uri, async_body).await?;
+
+        let status = response.status();
+
+        match status {
+            StatusCode::OK => {
+                let body = &response.into_body().bytes().await?;
+                let auth_info: IcloudWebservicesResponse =
+                    
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
+
+                if let Some(drivews_url) = &auth_info.webservices.drivews.url {
+                    self.data.webservices.insert(
+                        String::from("drive"),
+                        ServiceInfo {
+                            url: drivews_url.to_string(),
+                        },
+                    );
+                }
+                if let Some(docws_url) = &auth_info.webservices.docws.url {
+                    self.data.webservices.insert(
+                        String::from("docw"),
+                        ServiceInfo {
+                            url: docws_url.to_string(),
+                        },
+                    );
+                }
+
+                if auth_info.hsa_challenge_required {
+                    if auth_info.hsa_trusted_browser {
+                        Ok(())
+                    } else {
+                        Err(Error::new(ErrorKind::Unexpected, "Apple iCloud 
AuthenticationFailed:Unauthorized request:Needs two-factor authentication"))
+                    }
+                } else {
+                    Ok(())
+                }
+            }
+            _ => Err(Error::new(
+                ErrorKind::Unexpected,
+                "Apple iCloud AuthenticationFailed:Unauthorized:Invalid token",
+            )),
+        }
+    }
+}
+
+impl IcloudSigner {
+    pub async fn sign(
+        &mut self,
+        method: Method,
+        uri: String,
+        body: AsyncBody,
+    ) -> Result<Response<IncomingAsyncBody>> {
+        let mut request = Request::builder().method(method).uri(uri);
+
+        request = request.header(OAUTH_STATE_HEADER, 
self.data.oauth_state.clone());
+
+        if let Some(session_id) = &self.data.session_id {
+            request = request.header(SESSION_ID_HEADER, session_id);
+        }
+        if let Some(scnt) = &self.data.scnt {
+            request = request.header(SCNT_HEADER, scnt);
+        }
+
+        // China region
+        // ("Origin", "https://www.icloud.com.cn";)
+        // ("Referer", "https://www.icloud.com.cn/";)
+        if let Some(region) = &self.region {
+            request = request.header("Origin", region);
+            request = request.header("Referer", (region.to_string() + 
"/").as_str());
+        }
+
+        if !self.data.cookies.is_empty() {
+            let cookies: Vec<String> = self
+                .data
+                .cookies
+                .iter()
+                .map(|(k, v)| format!("{}={}", k, v))
+                .collect();
+            request = request.header(header::COOKIE, 
cookies.as_slice().join("; "));
+        }
+
+        if let Some(headers) = request.headers_mut() {
+            headers.insert("Content-Type", 
"application/json".parse().unwrap());
+            headers.insert("Accept", "*/*".parse().unwrap());
+            for (key, value) in AUTH_HEADERS {
+                headers.insert(key, value.parse().unwrap());
+            }
+        }
+
+        match self.client.send(request.body(body).unwrap()).await {
+            Ok(response) => {
+                if let Some(account_country) = 
response.headers().get(ACCOUNT_COUNTRY_HEADER) {
+                    self.data.account_country =
+                        Some(String::from(account_country.to_str().unwrap()));
+                }
+                if let Some(session_id) = 
response.headers().get(SESSION_ID_HEADER) {
+                    self.data.session_id = 
Some(String::from(session_id.to_str().unwrap()));
+                }
+                if let Some(session_token) = 
response.headers().get(SESSION_TOKEN_HEADER) {
+                    self.data.session_token = 
Some(String::from(session_token.to_str().unwrap()));
+                }
+
+                if let Some(scnt) = response.headers().get(SCNT_HEADER) {
+                    self.data.scnt = 
Some(String::from(scnt.to_str().unwrap()));
+                }
+
+                for (key, value) in response.headers() {
+                    if key == header::SET_COOKIE {
+                        #[warn(clippy::single_char_pattern)]

Review Comment:
   Please remove this warning and chaning `";"` into `';'` instead.



##########
core/src/services/icloud/backend.rs:
##########
@@ -0,0 +1,336 @@
+// 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 async_trait::async_trait;
+use http::StatusCode;
+use log::debug;
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::fmt::{Debug, Formatter};
+use std::sync::Arc;
+use tokio::sync::Mutex;
+
+use crate::raw::*;
+use crate::services::icloud::core::{parse_error, IcloudSigner, SessionData};
+use crate::*;
+use crate::{Builder, Capability, Error, ErrorKind, Scheme};
+
+use super::client::Client;
+
+/// Config for icloud services support.
+#[derive(Default, Deserialize)]
+#[serde(default)]
+#[non_exhaustive]
+pub struct IcloudConfig {
+    /// root of this backend.
+    ///
+    /// All operations will happen under this root.
+    ///
+    /// default to `/` if not set.
+    pub root: Option<String>,
+    /// apple_id of this backend.
+    ///
+    /// apple_id must be full, mostly like `[email protected]`.
+    pub apple_id: Option<String>,
+    /// password of this backend.
+    ///
+    /// password must be full.
+    pub password: Option<String>,
+
+    /// Session
+    ///
+    /// token must be valid.
+    pub trust_token: Option<String>,
+    pub ds_web_auth_token: Option<String>,
+    /// China region `origin` Header needs to be set to 
"https://www.icloud.com.cn";.
+    ///
+    /// otherwise Apple server will return 302.
+    pub region: Option<String>,
+}
+
+impl Debug for IcloudConfig {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut d = f.debug_struct("iCloudBuilder");

Review Comment:
   No `iCloud` please, thanks.



##########
core/src/services/icloud/core.rs:
##########
@@ -0,0 +1,648 @@
+// 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 bytes::Buf;
+use std::collections::{BTreeMap, HashMap};
+use std::fmt::{Debug, Formatter};
+
+use http::header;
+use http::Method;
+use http::Request;
+use http::Response;
+use http::StatusCode;
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::types::Result;
+use crate::{Error, ErrorKind};
+
+use crate::raw::{
+    new_json_deserialize_error, with_error_response_context, AsyncBody, 
HttpClient,
+    IncomingAsyncBody,
+};
+
+static ACCOUNT_COUNTRY_HEADER: &str = "X-Apple-ID-Account-Country";
+static OAUTH_STATE_HEADER: &str = "X-Apple-OAuth-State";
+
+static SESSION_ID_HEADER: &str = "X-Apple-ID-Session-Id";
+
+static SCNT_HEADER: &str = "scnt";
+
+static SESSION_TOKEN_HEADER: &str = "X-Apple-Session-Token";
+static AUTH_ENDPOINT: &str = "https://idmsa.apple.com/appleauth/auth";;
+static SETUP_ENDPOINT: &str = "https://setup.icloud.com/setup/ws/1";;
+
+static APPLE_RESPONSE_HEADER: &str = "X-Apple-I-Rscd";
+
+const AUTH_HEADERS: [(&str, &str); 7] = [
+    (
+        "X-Apple-OAuth-Client-Id",
+        "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d",
+    ),
+    ("X-Apple-OAuth-Client-Type", "firstPartyAuth"),
+    ("X-Apple-OAuth-Redirect-URI", "https://www.icloud.com";),
+    ("X-Apple-OAuth-Require-Grant-Code", "true"),
+    ("X-Apple-OAuth-Response-Mode", "web_message"),
+    ("X-Apple-OAuth-Response-Type", "code"),
+    (
+        "X-Apple-Widget-Key",
+        "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d",
+    ),
+];
+
+pub struct ServiceInfo {
+    pub url: String,
+}
+
+pub struct SessionData {
+    oauth_state: String,
+    session_id: Option<String>,
+    session_token: Option<String>,
+
+    scnt: Option<String>,
+    account_country: Option<String>,
+
+    cookies: BTreeMap<String, String>,
+    webservices: HashMap<String, ServiceInfo>,
+}
+
+impl SessionData {
+    pub fn new() -> SessionData {
+        Self {
+            oauth_state: format!("auth-{}", uuid::Uuid::new_v4()).to_string(),
+            session_id: None,
+            session_token: None,
+            scnt: None,
+            account_country: None,
+            cookies: BTreeMap::new(),
+            webservices: HashMap::new(),
+        }
+    }
+}
+
+pub struct IcloudSigner {
+    pub client: HttpClient,
+
+    pub data: SessionData,
+    pub apple_id: String,
+    pub password: String,
+
+    pub trust_token: Option<String>,
+    pub ds_web_auth_token: Option<String>,
+    pub region: Option<String>,
+}
+
+impl Debug for IcloudSigner {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut de = f.debug_struct("iCloud signer");
+        de.field("trust_token", &self.trust_token);
+        de.field("ds_web_auth_token", &self.ds_web_auth_token);
+        de.field("region", &self.region);
+        de.finish()
+    }
+}
+
+impl IcloudSigner {
+    pub fn get_service_info(&self, name: String) -> Option<&ServiceInfo> {
+        self.data.webservices.get(&name)
+    }
+
+    #[warn(dead_code)]
+    // create_dir could use client_id
+    pub fn get_client_id(&self) -> &String {
+        &self.data.oauth_state
+    }
+}
+
+impl IcloudSigner {
+    pub async fn signer(&mut self) -> Result<()> {
+        let body = json!({
+            "accountName" : self.apple_id,
+            "password" : self.password,
+            "rememberMe": true,
+            "trustTokens": [self.trust_token.clone().unwrap()],
+        })
+        .to_string();
+
+        let uri = format!("{}/signin?isRememberMeEnable=true", AUTH_ENDPOINT);
+
+        let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
+
+        let response = self.sign(Method::POST, uri, async_body).await?;
+
+        let status = response.status();
+
+        return match status {
+            StatusCode::OK => {
+                if let Some(rscd) = 
response.headers().get(APPLE_RESPONSE_HEADER) {
+                    let status_code = 
StatusCode::from_bytes(rscd.as_bytes()).unwrap();
+                    //409
+                    if status_code != StatusCode::CONFLICT {
+                        return Err(parse_error(response).await?);
+                    }
+                }
+                self.authenticate().await
+            }
+            _ => Err(parse_error(response).await?),
+        };
+    }
+
+    pub async fn authenticate(&mut self) -> Result<()> {
+        let body = json!({
+            "accountCountryCode": 
self.data.account_country.as_ref().unwrap_or(&String::new()),
+            
"dsWebAuthToken":self.ds_web_auth_token.as_ref().unwrap_or(&String::new()),
+                    "extended_login": true,
+                    "trustToken": 
self.trust_token.as_ref().unwrap_or(&String::new())
+        })
+        .to_string();
+
+        let uri = format!("{}/accountLogin", SETUP_ENDPOINT);
+
+        let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
+
+        let response = self.sign(Method::POST, uri, async_body).await?;
+
+        let status = response.status();
+
+        match status {
+            StatusCode::OK => {
+                let body = &response.into_body().bytes().await?;
+                let auth_info: IcloudWebservicesResponse =
+                    
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
+
+                if let Some(drivews_url) = &auth_info.webservices.drivews.url {
+                    self.data.webservices.insert(
+                        String::from("drive"),
+                        ServiceInfo {
+                            url: drivews_url.to_string(),
+                        },
+                    );
+                }
+                if let Some(docws_url) = &auth_info.webservices.docws.url {
+                    self.data.webservices.insert(
+                        String::from("docw"),
+                        ServiceInfo {
+                            url: docws_url.to_string(),
+                        },
+                    );
+                }
+
+                if auth_info.hsa_challenge_required {
+                    if auth_info.hsa_trusted_browser {
+                        Ok(())
+                    } else {
+                        Err(Error::new(ErrorKind::Unexpected, "Apple iCloud 
AuthenticationFailed:Unauthorized request:Needs two-factor authentication"))
+                    }
+                } else {
+                    Ok(())
+                }
+            }
+            _ => Err(Error::new(
+                ErrorKind::Unexpected,
+                "Apple iCloud AuthenticationFailed:Unauthorized:Invalid token",
+            )),
+        }
+    }
+}
+
+impl IcloudSigner {
+    pub async fn sign(
+        &mut self,
+        method: Method,
+        uri: String,
+        body: AsyncBody,
+    ) -> Result<Response<IncomingAsyncBody>> {
+        let mut request = Request::builder().method(method).uri(uri);
+
+        request = request.header(OAUTH_STATE_HEADER, 
self.data.oauth_state.clone());
+
+        if let Some(session_id) = &self.data.session_id {
+            request = request.header(SESSION_ID_HEADER, session_id);
+        }
+        if let Some(scnt) = &self.data.scnt {
+            request = request.header(SCNT_HEADER, scnt);
+        }
+
+        // China region
+        // ("Origin", "https://www.icloud.com.cn";)
+        // ("Referer", "https://www.icloud.com.cn/";)
+        if let Some(region) = &self.region {
+            request = request.header("Origin", region);
+            request = request.header("Referer", (region.to_string() + 
"/").as_str());
+        }
+
+        if !self.data.cookies.is_empty() {
+            let cookies: Vec<String> = self
+                .data
+                .cookies
+                .iter()
+                .map(|(k, v)| format!("{}={}", k, v))
+                .collect();
+            request = request.header(header::COOKIE, 
cookies.as_slice().join("; "));
+        }
+
+        if let Some(headers) = request.headers_mut() {
+            headers.insert("Content-Type", 
"application/json".parse().unwrap());
+            headers.insert("Accept", "*/*".parse().unwrap());
+            for (key, value) in AUTH_HEADERS {
+                headers.insert(key, value.parse().unwrap());
+            }
+        }
+
+        match self.client.send(request.body(body).unwrap()).await {
+            Ok(response) => {
+                if let Some(account_country) = 
response.headers().get(ACCOUNT_COUNTRY_HEADER) {
+                    self.data.account_country =
+                        Some(String::from(account_country.to_str().unwrap()));
+                }
+                if let Some(session_id) = 
response.headers().get(SESSION_ID_HEADER) {
+                    self.data.session_id = 
Some(String::from(session_id.to_str().unwrap()));
+                }
+                if let Some(session_token) = 
response.headers().get(SESSION_TOKEN_HEADER) {

Review Comment:
   Please use 
[`opendal::raw::parse_header_to_str`](https://opendal.apache.org/docs/rust/opendal/raw/fn.parse_header_to_str.html)



##########
core/src/services/icloud/backend.rs:
##########
@@ -0,0 +1,336 @@
+// 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 async_trait::async_trait;
+use http::StatusCode;
+use log::debug;
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::fmt::{Debug, Formatter};
+use std::sync::Arc;
+use tokio::sync::Mutex;
+
+use crate::raw::*;
+use crate::services::icloud::core::{parse_error, IcloudSigner, SessionData};
+use crate::*;
+use crate::{Builder, Capability, Error, ErrorKind, Scheme};
+
+use super::client::Client;
+
+/// Config for icloud services support.
+#[derive(Default, Deserialize)]
+#[serde(default)]
+#[non_exhaustive]
+pub struct IcloudConfig {
+    /// root of this backend.
+    ///
+    /// All operations will happen under this root.
+    ///
+    /// default to `/` if not set.
+    pub root: Option<String>,
+    /// apple_id of this backend.
+    ///
+    /// apple_id must be full, mostly like `[email protected]`.
+    pub apple_id: Option<String>,
+    /// password of this backend.
+    ///
+    /// password must be full.
+    pub password: Option<String>,
+
+    /// Session
+    ///
+    /// token must be valid.
+    pub trust_token: Option<String>,
+    pub ds_web_auth_token: Option<String>,
+    /// China region `origin` Header needs to be set to 
"https://www.icloud.com.cn";.
+    ///
+    /// otherwise Apple server will return 302.
+    pub region: Option<String>,
+}
+
+impl Debug for IcloudConfig {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut d = f.debug_struct("iCloudBuilder");
+        d.field("root", &self.root);
+        d.field("trust_token", &self.trust_token);
+        d.field("ds_web_auth_token", &self.ds_web_auth_token);
+        d.field("region", &self.region);
+
+        d.finish_non_exhaustive()
+    }
+}
+
+/// [IcloudDrive](https://www.icloud.com/iclouddrive/) service support.
+#[doc = include_str!("docs.md")]
+#[derive(Default)]
+pub struct IcloudBuilder {
+    pub config: IcloudConfig,
+    pub http_client: Option<HttpClient>,
+}
+
+impl Debug for IcloudBuilder {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut d = f.debug_struct("IcloudBuilder");
+
+        d.field("config", &self.config);
+        d.finish_non_exhaustive()
+    }
+}
+
+impl IcloudBuilder {
+    /// Set root of this backend.
+    ///
+    /// All operations will happen under this root.
+    pub fn root(&mut self, root: &str) -> &mut Self {
+        self.config.root = Some(root.to_string());
+        self
+    }
+
+    /// Your Apple id
+    ///
+    /// It is required. your Apple login email, e.g. `[email protected]`
+    pub fn apple_id(&mut self, apple_id: &str) -> &mut Self {
+        self.config.apple_id = if apple_id.is_empty() {
+            None
+        } else {
+            Some(apple_id.to_string())
+        };
+
+        self
+    }
+
+    /// Your Apple id password
+    ///
+    /// It is required. your icloud login password, e.g. `password`
+    pub fn password(&mut self, password: &str) -> &mut Self {
+        self.config.password = if password.is_empty() {
+            None
+        } else {
+            Some(password.to_string())
+        };
+
+        self
+    }
+
+    /// Trust token and ds_web_auth_token is used for temporary access to the 
IcloudDrive API.
+    /// Authenticate using session token
+    /// You can get more information from 
[pyicloud](https://github.com/picklepete/pyicloud/tree/master?tab=readme-ov-file#authentication)
+    /// or 
[iCloud-API](https://github.com/MauriceConrad/iCloud-API?tab=readme-ov-file#getting-started)
+

Review Comment:
   remove this line.



##########
core/src/services/icloud/backend.rs:
##########
@@ -0,0 +1,336 @@
+// 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 async_trait::async_trait;
+use http::StatusCode;
+use log::debug;
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::fmt::{Debug, Formatter};
+use std::sync::Arc;
+use tokio::sync::Mutex;
+
+use crate::raw::*;
+use crate::services::icloud::core::{parse_error, IcloudSigner, SessionData};
+use crate::*;
+use crate::{Builder, Capability, Error, ErrorKind, Scheme};
+
+use super::client::Client;
+
+/// Config for icloud services support.
+#[derive(Default, Deserialize)]
+#[serde(default)]
+#[non_exhaustive]
+pub struct IcloudConfig {
+    /// root of this backend.
+    ///
+    /// All operations will happen under this root.
+    ///
+    /// default to `/` if not set.
+    pub root: Option<String>,
+    /// apple_id of this backend.
+    ///
+    /// apple_id must be full, mostly like `[email protected]`.
+    pub apple_id: Option<String>,
+    /// password of this backend.
+    ///
+    /// password must be full.
+    pub password: Option<String>,
+
+    /// Session
+    ///
+    /// token must be valid.
+    pub trust_token: Option<String>,
+    pub ds_web_auth_token: Option<String>,
+    /// China region `origin` Header needs to be set to 
"https://www.icloud.com.cn";.
+    ///
+    /// otherwise Apple server will return 302.
+    pub region: Option<String>,

Review Comment:
   The term 'region' could be misleading in this context as it may be confused 
with the AWS S3 region setting. How about add a new config called 
`is_china_mainland`?
   
   See:
   
   - https://github.com/picklepete/pyicloud/pull/418
   - https://support.apple.com/en-us/111754



##########
core/src/services/icloud/drive.rs:
##########
@@ -0,0 +1,301 @@
+// 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 chrono::Utc;
+use http::header::{IF_MATCH, IF_NONE_MATCH};
+use http::{Method, Request, Response, StatusCode};
+use log::debug;
+use serde_json::json;
+use std::sync::Arc;
+use tokio::sync::Mutex;
+
+use crate::raw::oio::WriteBuf;
+use crate::raw::{new_json_deserialize_error, AsyncBody, IncomingAsyncBody, 
OpRead};
+use crate::{Error, ErrorKind, Result};
+
+use super::core::{
+    parse_error, IcloudCreateFolder, IcloudItem, IcloudObject, IcloudRoot, 
IcloudSigner,
+};
+
+#[derive(Clone)]
+pub struct File {
+    pub id: Option<String>,
+    pub name: String,
+    pub size: u64,
+    pub date_created: Option<chrono::DateTime<Utc>>,
+    pub date_modified: Option<chrono::DateTime<Utc>>,
+    pub mime_type: String,
+}
+
+// A directory in iCloud Drive.
+#[derive(Clone)]
+pub struct Folder {
+    pub id: Option<String>,
+    pub name: String,
+    pub date_created: Option<chrono::DateTime<Utc>>,
+    pub items: Vec<IcloudItem>,
+    pub mime_type: String,
+}
+
+pub struct FolderIter<'a> {
+    current: std::slice::Iter<'a, IcloudItem>,
+}
+
+impl<'a> Iterator for FolderIter<'a> {
+    type Item = &'a IcloudItem;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.current.next()
+    }
+}
+
+impl Folder {
+    #[warn(dead_code)]

Review Comment:
   This is not allowed. We can remove `Folder` struct if we don't need them for 
now



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to