diff --git a/Cargo.lock b/Cargo.lock index be5daf8fd..2518cc92b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2095,6 +2095,7 @@ dependencies = [ "thiserror", "tokio", "ureq", + "url", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index 7894cc98c..09171118a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,3 +45,4 @@ tokio = { version = "1", features = ["rt-multi-thread"] } thiserror = "1.0" ureq = { version = "^2.9.0", features = ["tls"] } uuid = { version = "^1.8.0", features = ["serde", "v4"] } +url = { version = "2" } diff --git a/doc/man/task-sync.5.in b/doc/man/task-sync.5.in index 54129343d..a8cf66d5f 100644 --- a/doc/man/task-sync.5.in +++ b/doc/man/task-sync.5.in @@ -73,6 +73,8 @@ Configure Taskwarrior with these details: $ task config sync.server.origin $ task config sync.server.client_id +Note that the origin must include the scheme, such as 'http://' or 'https://'. + .SS Google Cloud Platform To synchronize your tasks to GCP, use the GCP Console to create a new project, diff --git a/taskchampion/integration-tests/src/bindings_tests/replica.c b/taskchampion/integration-tests/src/bindings_tests/replica.c index 58c0e5f29..0126791ff 100644 --- a/taskchampion/integration-tests/src/bindings_tests/replica.c +++ b/taskchampion/integration-tests/src/bindings_tests/replica.c @@ -186,7 +186,7 @@ static void test_replica_sync_local(void) { static void test_replica_remote_server(void) { TCString err; TCServer *server = tc_server_new_sync( - tc_string_borrow("tc.freecinc.com"), + tc_string_borrow("http://tc.freecinc.com"), tc_uuid_new_v4(), tc_string_borrow("\xf0\x28\x8c\x28"), // NOTE: not utf-8 &err); diff --git a/taskchampion/taskchampion/Cargo.toml b/taskchampion/taskchampion/Cargo.toml index ad0b55b68..7ff582dd7 100644 --- a/taskchampion/taskchampion/Cargo.toml +++ b/taskchampion/taskchampion/Cargo.toml @@ -15,7 +15,7 @@ rust-version = "1.70.0" default = ["server-sync", "server-gcp"] # Support for sync to a server -server-sync = ["encryption", "dep:ureq"] +server-sync = ["encryption", "dep:ureq", "dep:url"] # Support for sync to GCP server-gcp = ["cloud", "encryption", "dep:google-cloud-storage", "dep:tokio"] # (private) Support for sync protocol encryption @@ -43,10 +43,12 @@ byteorder.workspace = true ring.workspace = true google-cloud-storage.workspace = true tokio.workspace = true +url.workspace = true google-cloud-storage.optional = true tokio.optional = true ureq.optional = true +url.optional = true ring.optional = true [dev-dependencies] diff --git a/taskchampion/taskchampion/src/server/sync/mod.rs b/taskchampion/taskchampion/src/server/sync/mod.rs index c42db7499..ecd118dbe 100644 --- a/taskchampion/taskchampion/src/server/sync/mod.rs +++ b/taskchampion/taskchampion/src/server/sync/mod.rs @@ -4,6 +4,7 @@ use crate::server::{ VersionId, }; use std::time::Duration; +use url::Url; use uuid::Uuid; use super::encryption::{Cryptor, Sealed, Secret, Unsealed}; @@ -28,8 +29,16 @@ impl SyncServer { /// identify this client to the server. Multiple replicas synchronizing the same task history /// should use the same client_id. pub fn new(origin: String, client_id: Uuid, encryption_secret: Vec) -> Result { + let origin = Url::parse(&origin) + .map_err(|_| Error::Server(format!("Could not parse {} as a URL", origin)))?; + if origin.path() != "/" { + return Err(Error::Server(format!( + "Server origin must have an empty path; got {}", + origin + ))); + } Ok(SyncServer { - origin, + origin: origin.to_string(), client_id, cryptor: Cryptor::new(client_id, &Secret(encryption_secret.to_vec()))?, agent: ureq::AgentBuilder::new()