diff --git a/.changes/fix-remote-domain-url.md b/.changes/fix-remote-domain-url.md new file mode 100644 index 000000000..d21f99c67 --- /dev/null +++ b/.changes/fix-remote-domain-url.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:bug +--- + +Fixes capability remote domain not allowing subpaths, query parameters and hash when those values are empty. diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index 6ac555b27..646ab5a14 100644 --- a/core/tauri-config-schema/schema.json +++ b/core/tauri-config-schema/schema.json @@ -1141,7 +1141,7 @@ ], "properties": { "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).", + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n# Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", "type": "array", "items": { "type": "string" diff --git a/core/tauri-utils/src/acl/capability.rs b/core/tauri-utils/src/acl/capability.rs index af42da4ce..25ed11163 100644 --- a/core/tauri-utils/src/acl/capability.rs +++ b/core/tauri-utils/src/acl/capability.rs @@ -90,6 +90,11 @@ fn default_capability_local() -> bool { #[serde(rename_all = "camelCase")] pub struct CapabilityRemote { /// Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/). + /// + /// # Examples + /// + /// - "https://*.mydomain.dev": allows subdomains of mydomain.dev + /// - "https://mydomain.dev/api/*": allows any subpath of mydomain.dev/api pub urls: Vec, } diff --git a/core/tauri-utils/src/acl/mod.rs b/core/tauri-utils/src/acl/mod.rs index 93db0a4ca..82405649f 100644 --- a/core/tauri-utils/src/acl/mod.rs +++ b/core/tauri-utils/src/acl/mod.rs @@ -202,7 +202,21 @@ impl FromStr for RemoteUrlPattern { type Err = urlpattern::quirks::Error; fn from_str(s: &str) -> std::result::Result { - let init = urlpattern::UrlPatternInit::parse_constructor_string::(s, None)?; + let mut init = urlpattern::UrlPatternInit::parse_constructor_string::(s, None)?; + if init.search.as_ref().map(|p| p.is_empty()).unwrap_or(true) { + init.search.replace("*".to_string()); + } + if init.hash.as_ref().map(|p| p.is_empty()).unwrap_or(true) { + init.hash.replace("*".to_string()); + } + if init + .pathname + .as_ref() + .map(|p| p.is_empty() || p == "/") + .unwrap_or(true) + { + init.pathname.replace("*".to_string()); + } let pattern = urlpattern::UrlPattern::parse(init)?; Ok(Self(Arc::new(pattern), s.to_string())) } @@ -251,6 +265,46 @@ pub enum ExecutionContext { }, } +#[cfg(test)] +mod tests { + use crate::acl::RemoteUrlPattern; + + #[test] + fn url_pattern_domain_wildcard() { + let pattern: RemoteUrlPattern = "http://*".parse().unwrap(); + + assert!(pattern.test(&"http://tauri.app/path".parse().unwrap())); + assert!(pattern.test(&"http://tauri.app/path?q=1".parse().unwrap())); + + assert!(pattern.test(&"http://localhost/path".parse().unwrap())); + assert!(pattern.test(&"http://localhost/path?q=1".parse().unwrap())); + + let pattern: RemoteUrlPattern = "http://*.tauri.app".parse().unwrap(); + + assert!(!pattern.test(&"http://tauri.app/path".parse().unwrap())); + assert!(!pattern.test(&"http://tauri.app/path?q=1".parse().unwrap())); + assert!(pattern.test(&"http://api.tauri.app/path".parse().unwrap())); + assert!(pattern.test(&"http://api.tauri.app/path?q=1".parse().unwrap())); + assert!(!pattern.test(&"http://localhost/path".parse().unwrap())); + assert!(!pattern.test(&"http://localhost/path?q=1".parse().unwrap())); + } + + #[test] + fn url_pattern_path_wildcard() { + let pattern: RemoteUrlPattern = "http://localhost/*".parse().unwrap(); + assert!(pattern.test(&"http://localhost/path".parse().unwrap())); + assert!(pattern.test(&"http://localhost/path?q=1".parse().unwrap())); + } + + #[test] + fn url_pattern_scheme_wildcard() { + let pattern: RemoteUrlPattern = "*://localhost".parse().unwrap(); + assert!(pattern.test(&"http://localhost/path".parse().unwrap())); + assert!(pattern.test(&"https://localhost/path?q=1".parse().unwrap())); + assert!(pattern.test(&"custom://localhost/path".parse().unwrap())); + } +} + #[cfg(feature = "build")] mod build_ { use std::convert::identity; diff --git a/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap b/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap index 994f5f61d..cf1f7ea44 100644 --- a/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap +++ b/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap @@ -90,50 +90,59 @@ Resolved { }, }, pathname: Component { - pattern_string: "/", + pattern_string: "*", regexp: Ok( Regex( - "^/$", + "^(.*)$", ), ), - group_name_list: [], + group_name_list: [ + "0", + ], matcher: Matcher { prefix: "", suffix: "", - inner: Literal { - literal: "/", + inner: SingleCapture { + filter: None, + allow_empty: true, }, }, }, search: Component { - pattern_string: "", + pattern_string: "*", regexp: Ok( Regex( - "^$", + "^(.*)$", ), ), - group_name_list: [], + group_name_list: [ + "0", + ], matcher: Matcher { prefix: "", suffix: "", - inner: Literal { - literal: "", + inner: SingleCapture { + filter: None, + allow_empty: true, }, }, }, hash: Component { - pattern_string: "", + pattern_string: "*", regexp: Ok( Regex( - "^$", + "^(.*)$", ), ), - group_name_list: [], + group_name_list: [ + "0", + ], matcher: Matcher { prefix: "", suffix: "", - inner: Literal { - literal: "", + inner: SingleCapture { + filter: None, + allow_empty: true, }, }, }, @@ -251,50 +260,59 @@ Resolved { }, }, pathname: Component { - pattern_string: "/", + pattern_string: "*", regexp: Ok( Regex( - "^/$", + "^(.*)$", ), ), - group_name_list: [], + group_name_list: [ + "0", + ], matcher: Matcher { prefix: "", suffix: "", - inner: Literal { - literal: "/", + inner: SingleCapture { + filter: None, + allow_empty: true, }, }, }, search: Component { - pattern_string: "", + pattern_string: "*", regexp: Ok( Regex( - "^$", + "^(.*)$", ), ), - group_name_list: [], + group_name_list: [ + "0", + ], matcher: Matcher { prefix: "", suffix: "", - inner: Literal { - literal: "", + inner: SingleCapture { + filter: None, + allow_empty: true, }, }, }, hash: Component { - pattern_string: "", + pattern_string: "*", regexp: Ok( Regex( - "^$", + "^(.*)$", ), ), - group_name_list: [], + group_name_list: [ + "0", + ], matcher: Matcher { prefix: "", suffix: "", - inner: Literal { - literal: "", + inner: SingleCapture { + filter: None, + allow_empty: true, }, }, }, diff --git a/examples/api/src-tauri/capabilities/run-app.json b/examples/api/src-tauri/capabilities/run-app.json index c2150e890..ea0a2952a 100644 --- a/examples/api/src-tauri/capabilities/run-app.json +++ b/examples/api/src-tauri/capabilities/run-app.json @@ -2,7 +2,10 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "run-app", "description": "permissions to run the app", - "windows": ["main", "main-*"], + "windows": [ + "main", + "main-*" + ], "permissions": [ { "identifier": "allow-log-operation", @@ -96,4 +99,4 @@ "tray:allow-set-icon-as-template", "tray:allow-set-show-menu-on-left-click" ] -} +} \ No newline at end of file diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 6ac555b27..646ab5a14 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1141,7 +1141,7 @@ ], "properties": { "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).", + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n# Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", "type": "array", "items": { "type": "string"