mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 08:13:34 +00:00
@@ -9,6 +9,7 @@ use reqwest::{header, Client, Method, StatusCode, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use crate::ai::AiConfigItem;
|
||||
use crate::connection_secrets::{
|
||||
@@ -521,7 +522,11 @@ pub async fn resolve_webdav_sync_secrets_passphrase(storage: &Storage) -> Result
|
||||
|
||||
impl WebDavClient {
|
||||
pub fn new(config: WebDavConfig) -> Self {
|
||||
Self { http: Client::new(), config }
|
||||
let builder = Client::builder();
|
||||
let builder =
|
||||
if webdav_endpoint_uses_direct_connection(&config.endpoint) { builder.no_proxy() } else { builder };
|
||||
let http = builder.build().expect("failed to build WebDAV HTTP client");
|
||||
Self { http, config }
|
||||
}
|
||||
|
||||
pub fn remote_path(&self) -> String {
|
||||
@@ -1529,6 +1534,42 @@ fn normalized_remote_path(value: Option<&str>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn webdav_endpoint_uses_direct_connection(endpoint: &str) -> bool {
|
||||
let Ok(url) = Url::parse(endpoint.trim()) else {
|
||||
return false;
|
||||
};
|
||||
let Some(host) = url.host_str() else {
|
||||
return false;
|
||||
};
|
||||
let host = host.strip_prefix('[').and_then(|host| host.strip_suffix(']')).unwrap_or(host);
|
||||
if host.rsplit('.').next().is_some_and(|label| label.eq_ignore_ascii_case("localhost")) {
|
||||
return true;
|
||||
}
|
||||
host.parse::<IpAddr>().is_ok_and(webdav_ip_uses_direct_connection)
|
||||
}
|
||||
|
||||
fn webdav_ip_uses_direct_connection(address: IpAddr) -> bool {
|
||||
match address {
|
||||
IpAddr::V4(address) => webdav_ipv4_uses_direct_connection(address),
|
||||
IpAddr::V6(address) => {
|
||||
address.is_loopback()
|
||||
|| address.is_unspecified()
|
||||
|| address.is_unique_local()
|
||||
|| address.is_unicast_link_local()
|
||||
|| address.to_ipv4_mapped().is_some_and(webdav_ipv4_uses_direct_connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn webdav_ipv4_uses_direct_connection(address: Ipv4Addr) -> bool {
|
||||
let [first, second, _, _] = address.octets();
|
||||
address.is_private()
|
||||
|| address.is_loopback()
|
||||
|| address.is_link_local()
|
||||
|| address.is_unspecified()
|
||||
|| (first == 100 && (64..=127).contains(&second))
|
||||
}
|
||||
|
||||
fn parent_collection_paths(remote_path: &str) -> Vec<String> {
|
||||
let parts = remote_path.trim_matches('/').split('/').filter(|part| !part.is_empty()).collect::<Vec<_>>();
|
||||
if parts.len() <= 1 {
|
||||
@@ -1551,9 +1592,9 @@ mod tests {
|
||||
parent_collection_paths, parse_legacy_dbx_snapshot, parse_snippet_snapshot, prepare_legacy_snippet_snapshot,
|
||||
resolve_webdav_sync_secrets_passphrase, retry_pending_snippet_cleanup, save_snippet_sync_id,
|
||||
save_webdav_sync_secrets_preference, scrub_connection_secrets, snapshot_for_snippet_upload,
|
||||
snippet_file_content, snippet_response_id, snippet_sync_settings, webdav_sync_secrets_status,
|
||||
ApplySnapshotOptions, ConnectionSecretSnapshot, SensitiveSyncPayload, SnippetProvider, SnippetSyncClient,
|
||||
SnippetSyncConfig, DEFAULT_SNIPPET_FILE_NAME,
|
||||
snippet_file_content, snippet_response_id, snippet_sync_settings, webdav_endpoint_uses_direct_connection,
|
||||
webdav_sync_secrets_status, ApplySnapshotOptions, ConnectionSecretSnapshot, SensitiveSyncPayload,
|
||||
SnippetProvider, SnippetSyncClient, SnippetSyncConfig, DEFAULT_SNIPPET_FILE_NAME,
|
||||
};
|
||||
use crate::ai::{AiApiStyle, AiAuthMethod, AiConfig, AiConfigItem};
|
||||
use crate::connection_secrets::NACOS_AUTH_PASSWORD_KEY;
|
||||
@@ -1794,6 +1835,41 @@ mod tests {
|
||||
assert_eq!(normalized_remote_path(Some("DBX/sync/../snapshot.json")), "DBX/snapshot.json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bypasses_proxy_for_local_webdav_endpoints() {
|
||||
for endpoint in [
|
||||
"http://172.27.31.29:8088/dbx/",
|
||||
"https://10.0.0.8/webdav",
|
||||
"http://192.168.1.9/",
|
||||
"http://100.64.0.1/",
|
||||
"http://127.0.0.1:8080/",
|
||||
"http://169.254.1.2/",
|
||||
"http://localhost:8080/",
|
||||
"http://dbx.localhost/",
|
||||
"http://[::1]/",
|
||||
"http://[fd00::1]/",
|
||||
"http://[fe80::1]/",
|
||||
"http://[::ffff:172.27.31.29]/",
|
||||
] {
|
||||
assert!(webdav_endpoint_uses_direct_connection(endpoint), "expected direct WebDAV connection: {endpoint}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_proxy_for_public_webdav_endpoints() {
|
||||
for endpoint in [
|
||||
"https://dav.example.com/remote.php/dav/files/user/",
|
||||
"http://8.8.8.8/webdav/",
|
||||
"http://[2606:4700:4700::1111]/webdav/",
|
||||
"not a URL",
|
||||
] {
|
||||
assert!(
|
||||
!webdav_endpoint_uses_direct_connection(endpoint),
|
||||
"expected configured proxy behavior: {endpoint}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_parent_collection_paths_from_leaf() {
|
||||
assert_eq!(parent_collection_paths("dbx/sync/snapshot.json"), vec!["dbx".to_string(), "dbx/sync".to_string()]);
|
||||
|
||||
Reference in New Issue
Block a user