Skip to main content

nextcloud_goa/
url.rs

1//! URL parsing helpers for GNOME Online Accounts WebDAV endpoints.
2
3use nextcloud_sdk_core::{ServerUrl, UrlError};
4
5use crate::GoaError;
6
7/// Parses a GNOME Online Accounts WebDAV URI into a Nextcloud base [`ServerUrl`].
8///
9/// GOA typically configures Nextcloud/ownCloud WebDAV URIs in formats such as:
10/// - `https://cloud.example.com/remote.php/webdav/`
11/// - `https://cloud.example.com/remote.php/dav/files/username/`
12/// - `davs://cloud.example.com/remote.php/webdav/`
13/// - `https://example.com/nextcloud/remote.php/webdav/`
14///
15/// This function normalises desktop-specific schemes (`dav://` and `davs://`),
16/// strips Nextcloud's `/remote.php` routing endpoints, and produces a validated
17/// base [`ServerUrl`].
18pub fn parse_webdav_url(uri: &str) -> Result<ServerUrl, GoaError> {
19    let uri = uri.trim();
20    if uri.is_empty() {
21        return Err(GoaError::Url(UrlError::MissingHost));
22    }
23
24    // Convert GNOME dav(s):// schemes to standard http(s)://
25    let normalized = if let Some(rest) = uri.strip_prefix("davs://") {
26        format!("https://{rest}")
27    } else if let Some(rest) = uri.strip_prefix("dav://") {
28        format!("http://{rest}")
29    } else {
30        uri.to_owned()
31    };
32
33    // Strip `/remote.php` and any subsequent endpoint paths
34    let base = match normalized.find("/remote.php") {
35        Some(idx) => &normalized[..idx],
36        None => &normalized,
37    };
38
39    let trimmed = base.trim_end_matches('/');
40    let server_url = ServerUrl::parse(trimmed)?;
41    Ok(server_url)
42}
43
44#[cfg(test)]
45mod tests {
46    use super::parse_webdav_url;
47    use crate::GoaError;
48
49    #[test]
50    fn parses_standard_webdav_endpoint() {
51        let parsed = parse_webdav_url("https://cloud.example.com/remote.php/webdav/").unwrap();
52        assert_eq!(parsed.as_str(), "https://cloud.example.com");
53        assert_eq!(parsed.authority(), "cloud.example.com");
54        assert_eq!(parsed.path_prefix(), "");
55    }
56
57    #[test]
58    fn parses_dav_files_endpoint() {
59        let parsed =
60            parse_webdav_url("https://cloud.example.com/remote.php/dav/files/alice/").unwrap();
61        assert_eq!(parsed.as_str(), "https://cloud.example.com");
62    }
63
64    #[test]
65    fn parses_davs_scheme() {
66        let parsed = parse_webdav_url("davs://cloud.example.com/remote.php/webdav/").unwrap();
67        assert_eq!(parsed.as_str(), "https://cloud.example.com");
68        assert!(parsed.is_https());
69    }
70
71    #[test]
72    fn parses_dav_insecure_scheme() {
73        let parsed = parse_webdav_url("dav://insecure.example.com/remote.php/webdav/").unwrap();
74        assert_eq!(parsed.as_str(), "http://insecure.example.com");
75        assert!(!parsed.is_https());
76    }
77
78    #[test]
79    fn parses_custom_port_and_subdirectory() {
80        let parsed = parse_webdav_url("http://localhost:8080/nextcloud/remote.php/webdav").unwrap();
81        assert_eq!(parsed.as_str(), "http://localhost:8080/nextcloud");
82        assert_eq!(parsed.authority(), "localhost:8080");
83        assert_eq!(parsed.path_prefix(), "/nextcloud");
84    }
85
86    #[test]
87    fn parses_root_urls_without_remote_php() {
88        let parsed1 = parse_webdav_url("https://cloud.example.com/").unwrap();
89        assert_eq!(parsed1.as_str(), "https://cloud.example.com");
90
91        let parsed2 = parse_webdav_url("https://cloud.example.com").unwrap();
92        assert_eq!(parsed2.as_str(), "https://cloud.example.com");
93    }
94
95    #[test]
96    fn rejects_invalid_urls() {
97        assert!(matches!(parse_webdav_url(""), Err(GoaError::Url(_))));
98        assert!(matches!(
99            parse_webdav_url("cloud.example.com"),
100            Err(GoaError::Url(_))
101        ));
102        assert!(matches!(
103            parse_webdav_url("ftp://cloud.example.com"),
104            Err(GoaError::Url(_))
105        ));
106    }
107}