mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 03:21:53 +00:00
@@ -588,7 +588,8 @@ impl WebDavClient {
|
||||
async fn ensure_parent_collections(&self, remote_path: &str) -> Result<(), String> {
|
||||
let method = Method::from_bytes(b"MKCOL").map_err(|e| e.to_string())?;
|
||||
for parent in parent_collection_paths(remote_path) {
|
||||
let response = self.request(method.clone(), &parent)?.send().await.map_err(|e| e.to_string())?;
|
||||
let collection_path = format!("{parent}/");
|
||||
let response = self.request(method.clone(), &collection_path)?.send().await.map_err(|e| e.to_string())?;
|
||||
let status = response.status();
|
||||
if status.is_success() || status == StatusCode::METHOD_NOT_ALLOWED {
|
||||
continue;
|
||||
@@ -1608,7 +1609,7 @@ mod tests {
|
||||
save_webdav_sync_secrets_preference, scrub_connection_secrets, snapshot_for_snippet_upload,
|
||||
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,
|
||||
SnippetProvider, SnippetSyncClient, SnippetSyncConfig, WebDavClient, WebDavConfig, DEFAULT_SNIPPET_FILE_NAME,
|
||||
};
|
||||
use crate::ai::{AiApiStyle, AiAuthMethod, AiConfig, AiConfigItem};
|
||||
use crate::connection_secrets::NACOS_AUTH_PASSWORD_KEY;
|
||||
@@ -1690,6 +1691,48 @@ mod tests {
|
||||
(format!("http://{address}"), server)
|
||||
}
|
||||
|
||||
async fn spawn_webdav_server(
|
||||
responses: Vec<u16>,
|
||||
reject_non_collection_mkcol: bool,
|
||||
) -> (String, tokio::task::JoinHandle<Vec<String>>) {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let server = tokio::spawn(async move {
|
||||
let mut request_lines = Vec::new();
|
||||
for configured_status in responses {
|
||||
let (mut socket, _) = listener.accept().await.unwrap();
|
||||
let mut request = Vec::new();
|
||||
let mut chunk = [0_u8; 4096];
|
||||
while !request.windows(4).any(|window| window == b"\r\n\r\n") {
|
||||
let read = socket.read(&mut chunk).await.unwrap();
|
||||
assert!(read > 0, "request ended before headers were complete");
|
||||
request.extend_from_slice(&chunk[..read]);
|
||||
}
|
||||
let request = String::from_utf8(request).unwrap();
|
||||
let request_line = request.lines().next().unwrap().to_string();
|
||||
let invalid_nginx_collection_uri = reject_non_collection_mkcol
|
||||
&& request_line.starts_with("MKCOL ")
|
||||
&& request_line.split_whitespace().nth(1).is_some_and(|target| !target.ends_with('/'));
|
||||
let status = if invalid_nginx_collection_uri { 409 } else { configured_status };
|
||||
let reason = match status {
|
||||
200 => "OK",
|
||||
201 => "Created",
|
||||
207 => "Multi-Status",
|
||||
405 => "Method Not Allowed",
|
||||
409 => "Conflict",
|
||||
_ => "Test Response",
|
||||
};
|
||||
request_lines.push(request_line);
|
||||
let response = format!("HTTP/1.1 {status} {reason}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
|
||||
socket.write_all(response.as_bytes()).await.unwrap();
|
||||
}
|
||||
request_lines
|
||||
});
|
||||
(format!("http://{address}/"), server)
|
||||
}
|
||||
|
||||
fn github_snippet_response(content: &str) -> String {
|
||||
serde_json::json!({
|
||||
"files": { DEFAULT_SNIPPET_FILE_NAME: { "content": content } }
|
||||
@@ -1897,6 +1940,104 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn webdav_upload_terminates_collection_request_paths() {
|
||||
let storage = Storage::open(&temp_db_path("webdav-collection-paths")).await.unwrap();
|
||||
let snapshot = build_sync_snapshot(&storage, "test-version", None, None).await.unwrap();
|
||||
let (endpoint, server) = spawn_webdav_server(vec![201, 201, 200], true).await;
|
||||
let client = WebDavClient::new(WebDavConfig {
|
||||
endpoint,
|
||||
username: None,
|
||||
password: None,
|
||||
remote_path: Some("DBX-home/sync/snapshot.json".to_string()),
|
||||
});
|
||||
|
||||
client.put_snapshot(&snapshot).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
server.await.unwrap(),
|
||||
vec![
|
||||
"MKCOL /DBX-home/ HTTP/1.1",
|
||||
"MKCOL /DBX-home/sync/ HTTP/1.1",
|
||||
"PUT /DBX-home/sync/snapshot.json HTTP/1.1",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn webdav_upload_accepts_existing_collections() {
|
||||
let storage = Storage::open(&temp_db_path("webdav-existing-collections")).await.unwrap();
|
||||
let snapshot = build_sync_snapshot(&storage, "test-version", None, None).await.unwrap();
|
||||
let (endpoint, server) = spawn_webdav_server(vec![405, 405, 200], false).await;
|
||||
let client = WebDavClient::new(WebDavConfig {
|
||||
endpoint,
|
||||
username: None,
|
||||
password: None,
|
||||
remote_path: Some("DBX-home/sync/snapshot.json".to_string()),
|
||||
});
|
||||
|
||||
client.put_snapshot(&snapshot).await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
server.await.unwrap(),
|
||||
vec![
|
||||
"MKCOL /DBX-home/ HTTP/1.1",
|
||||
"MKCOL /DBX-home/sync/ HTTP/1.1",
|
||||
"PUT /DBX-home/sync/snapshot.json HTTP/1.1",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn webdav_upload_preserves_collection_conflicts() {
|
||||
let storage = Storage::open(&temp_db_path("webdav-collection-conflict")).await.unwrap();
|
||||
let snapshot = build_sync_snapshot(&storage, "test-version", None, None).await.unwrap();
|
||||
let (endpoint, server) = spawn_webdav_server(vec![409], false).await;
|
||||
let client = WebDavClient::new(WebDavConfig {
|
||||
endpoint,
|
||||
username: None,
|
||||
password: None,
|
||||
remote_path: Some("DBX-home/sync/snapshot.json".to_string()),
|
||||
});
|
||||
|
||||
let error = client.put_snapshot(&snapshot).await.unwrap_err();
|
||||
|
||||
assert_eq!(error, "Failed to create WebDAV collection 'DBX-home' with HTTP 409 Conflict");
|
||||
assert_eq!(server.await.unwrap(), vec!["MKCOL /DBX-home/ HTTP/1.1"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn webdav_upload_skips_collection_requests_for_single_file() {
|
||||
let storage = Storage::open(&temp_db_path("webdav-single-file")).await.unwrap();
|
||||
let snapshot = build_sync_snapshot(&storage, "test-version", None, None).await.unwrap();
|
||||
let (endpoint, server) = spawn_webdav_server(vec![200], false).await;
|
||||
let client = WebDavClient::new(WebDavConfig {
|
||||
endpoint,
|
||||
username: None,
|
||||
password: None,
|
||||
remote_path: Some("snapshot.json".to_string()),
|
||||
});
|
||||
|
||||
client.put_snapshot(&snapshot).await.unwrap();
|
||||
|
||||
assert_eq!(server.await.unwrap(), vec!["PUT /snapshot.json HTTP/1.1"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn webdav_test_keeps_propfind_request() {
|
||||
let (endpoint, server) = spawn_webdav_server(vec![207], false).await;
|
||||
let client = WebDavClient::new(WebDavConfig {
|
||||
endpoint,
|
||||
username: None,
|
||||
password: None,
|
||||
remote_path: Some("DBX-home/sync/snapshot.json".to_string()),
|
||||
});
|
||||
|
||||
client.test().await.unwrap();
|
||||
|
||||
assert_eq!(server.await.unwrap(), vec!["PROPFIND / HTTP/1.1"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrubs_connection_secret_fields() {
|
||||
let mut config = ConnectionConfig {
|
||||
|
||||
Reference in New Issue
Block a user