Browse Source

refactor(acl): use URLPattern instead of glob for remote URLs (#9116)

Lucas Fernandes Nogueira 1 year ago
parent
commit
4ef17d0833

+ 6 - 0
.changes/acl-urlpattern.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch:breaking
+"tauri-utils": patch:breaking
+---
+
+The ACL configuration for remote URLs now uses the URLPattern standard instead of glob patterns.

+ 57 - 0
Cargo.lock

@@ -3642,6 +3642,7 @@ dependencies = [
  "tracing",
  "tray-icon",
  "url",
+ "urlpattern",
  "uuid",
  "webkit2gtk",
  "webview2-com",
@@ -3796,6 +3797,7 @@ dependencies = [
  "phf 0.11.2",
  "proc-macro2",
  "quote",
+ "regex",
  "schemars",
  "semver",
  "serde",
@@ -3806,6 +3808,7 @@ dependencies = [
  "thiserror",
  "toml 0.8.2",
  "url",
+ "urlpattern",
  "walkdir",
 ]
 
@@ -4179,6 +4182,47 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
 
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-ucd-ident"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.15"
@@ -4234,6 +4278,19 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "urlpattern"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609"
+dependencies = [
+ "derive_more",
+ "regex",
+ "serde",
+ "unic-ucd-ident",
+ "url",
+]
+
 [[package]]
 name = "utf-8"
 version = "0.7.6"

+ 1 - 1
core/tauri-config-schema/schema.json

@@ -1146,7 +1146,7 @@
       ],
       "properties": {
         "urls": {
-          "description": "Remote domains this capability refers to. Can use glob patterns.",
+          "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).",
           "type": "array",
           "items": {
             "type": "string"

+ 2 - 0
core/tauri-utils/Cargo.toml

@@ -33,6 +33,8 @@ json5 = { version = "0.4", optional = true }
 toml = { version = "0.8", features = [ "parse" ] }
 json-patch = "1.2"
 glob = "0.3"
+urlpattern = "0.2"
+regex = "1"
 walkdir = { version = "2", optional = true }
 memchr = "2"
 semver = "1"

+ 1 - 1
core/tauri-utils/src/acl/capability.rs

@@ -98,7 +98,7 @@ fn default_platforms() -> Vec<Target> {
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase")]
 pub struct CapabilityRemote {
-  /// Remote domains this capability refers to. Can use glob patterns.
+  /// Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).
   pub urls: Vec<String>,
 }
 

+ 49 - 5
core/tauri-utils/src/acl/mod.rs

@@ -4,10 +4,10 @@
 
 //! Access Control List types.
 
-use glob::Pattern;
 use serde::{Deserialize, Serialize};
-use std::num::NonZeroU64;
+use std::{num::NonZeroU64, str::FromStr, sync::Arc};
 use thiserror::Error;
+use url::Url;
 
 use crate::platform::Target;
 
@@ -204,16 +204,60 @@ pub struct PermissionSet {
   pub permissions: Vec<String>,
 }
 
+/// UrlPattern for [`ExecutionContext::Remote`].
+#[derive(Debug, Clone)]
+pub struct RemoteUrlPattern(Arc<urlpattern::UrlPattern>, String);
+
+impl FromStr for RemoteUrlPattern {
+  type Err = urlpattern::quirks::Error;
+
+  fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+    let init = urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(s, None)?;
+    let pattern = urlpattern::UrlPattern::parse(init)?;
+    Ok(Self(Arc::new(pattern), s.to_string()))
+  }
+}
+
+impl RemoteUrlPattern {
+  #[doc(hidden)]
+  pub fn as_str(&self) -> &str {
+    &self.1
+  }
+
+  /// Test if a given URL matches the pattern.
+  pub fn test(&self, url: &Url) -> bool {
+    self
+      .0
+      .test(urlpattern::UrlPatternMatchInput::Url(url.clone()))
+      .unwrap_or_default()
+  }
+}
+
+impl PartialEq for RemoteUrlPattern {
+  fn eq(&self, other: &Self) -> bool {
+    self.0.protocol() == other.0.protocol()
+      && self.0.username() == other.0.username()
+      && self.0.password() == other.0.password()
+      && self.0.hostname() == other.0.hostname()
+      && self.0.port() == other.0.port()
+      && self.0.pathname() == other.0.pathname()
+      && self.0.search() == other.0.search()
+      && self.0.hash() == other.0.hash()
+  }
+}
+
+impl Eq for RemoteUrlPattern {}
+
 /// Execution context of an IPC call.
-#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
+#[derive(Debug, Default, Clone, Eq, PartialEq)]
 pub enum ExecutionContext {
   /// A local URL is used (the Tauri app URL).
   #[default]
   Local,
   /// Remote URL is tring to use the IPC.
   Remote {
-    /// The URL trying to access the IPC (glob pattern).
-    url: Pattern,
+    /// The URL trying to access the IPC (URL pattern).
+    url: RemoteUrlPattern,
   },
 }
 

+ 3 - 4
core/tauri-utils/src/acl/resolved.rs

@@ -6,8 +6,6 @@
 
 use std::{collections::BTreeMap, fmt};
 
-use glob::Pattern;
-
 use crate::platform::Target;
 
 use super::{
@@ -292,8 +290,9 @@ fn resolve_command(
   if let Some(remote) = &capability.remote {
     contexts.extend(remote.urls.iter().map(|url| {
       ExecutionContext::Remote {
-        url: Pattern::new(url)
-          .unwrap_or_else(|e| panic!("invalid glob pattern for remote URL {url}: {e}")),
+        url: url
+          .parse()
+          .unwrap_or_else(|e| panic!("invalid URL pattern for remote URL {url}: {e}")),
       }
     }));
   }

+ 1 - 0
core/tauri/Cargo.toml

@@ -64,6 +64,7 @@ reqwest = { version = "0.11", default-features = false, features = [ "json", "st
 bytes = { version = "1", features = [ "serde" ] }
 raw-window-handle = "0.6"
 glob = "0.3"
+urlpattern = "0.2"
 mime = "0.3"
 data-url = { version = "0.3", optional = true }
 serialize-to-javascript = "=0.1.1"

+ 11 - 7
core/tauri/src/ipc/authority.rs

@@ -20,6 +20,8 @@ use tauri_utils::acl::{
   ExecutionContext, Scopes,
 };
 
+use url::Url;
+
 use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
 use crate::{AppHandle, Manager};
 
@@ -40,7 +42,7 @@ pub enum Origin {
   /// Remote origin.
   Remote {
     /// Remote URL.
-    url: String,
+    url: Url,
   },
 }
 
@@ -58,7 +60,7 @@ impl Origin {
     match (self, context) {
       (Self::Local, ExecutionContext::Local) => true,
       (Self::Remote { url }, ExecutionContext::Remote { url: url_pattern }) => {
-        url_pattern.matches(url)
+        url_pattern.test(url)
       }
       _ => false,
     }
@@ -816,7 +818,7 @@ mod tests {
     let resolved_cmd = vec![ResolvedCommand {
       windows: vec![Pattern::new(window).unwrap()],
       context: ExecutionContext::Remote {
-        url: Pattern::new(url).unwrap(),
+        url: url.parse().unwrap(),
       },
       ..Default::default()
     }];
@@ -837,7 +839,9 @@ mod tests {
         command,
         window,
         webview,
-        &Origin::Remote { url: url.into() }
+        &Origin::Remote {
+          url: url.parse().unwrap()
+        }
       ),
       Some(resolved_cmd)
     );
@@ -853,7 +857,7 @@ mod tests {
     let resolved_cmd = vec![ResolvedCommand {
       windows: vec![Pattern::new(window).unwrap()],
       context: ExecutionContext::Remote {
-        url: Pattern::new(url).unwrap(),
+        url: url.parse().unwrap(),
       },
       ..Default::default()
     }];
@@ -875,7 +879,7 @@ mod tests {
         window,
         webview,
         &Origin::Remote {
-          url: url.replace('*', "studio")
+          url: url.replace('*', "studio").parse().unwrap()
         }
       ),
       Some(resolved_cmd)
@@ -908,7 +912,7 @@ mod tests {
         window,
         webview,
         &Origin::Remote {
-          url: "https://tauri.app".into()
+          url: "https://tauri.app".parse().unwrap()
         }
       )
       .is_none());

+ 1 - 1
core/tauri/src/webview/mod.rs

@@ -1137,7 +1137,7 @@ fn main() {
       Origin::Local
     } else {
       Origin::Remote {
-        url: current_url.to_string(),
+        url: current_url.clone(),
       }
     };
     let (resolved_acl, has_app_acl_manifest) = {

+ 266 - 114
core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap

@@ -7,63 +7,139 @@ Resolved {
         "plugin:fs|read_dir": [
             ResolvedCommand {
                 context: Remote {
-                    url: Pattern {
-                        original: "https://tauri.app",
-                        tokens: [
-                            Char(
-                                'h',
-                            ),
-                            Char(
-                                't',
-                            ),
-                            Char(
-                                't',
-                            ),
-                            Char(
-                                'p',
-                            ),
-                            Char(
-                                's',
-                            ),
-                            Char(
-                                ':',
-                            ),
-                            Char(
-                                '/',
-                            ),
-                            Char(
-                                '/',
-                            ),
-                            Char(
-                                't',
-                            ),
-                            Char(
-                                'a',
-                            ),
-                            Char(
-                                'u',
-                            ),
-                            Char(
-                                'r',
-                            ),
-                            Char(
-                                'i',
-                            ),
-                            Char(
-                                '.',
-                            ),
-                            Char(
-                                'a',
-                            ),
-                            Char(
-                                'p',
-                            ),
-                            Char(
-                                'p',
-                            ),
-                        ],
-                        is_recursive: false,
-                    },
+                    url: RemoteUrlPattern(
+                        UrlPattern {
+                            protocol: Component {
+                                pattern_string: "https",
+                                regexp: Ok(
+                                    Regex(
+                                        "^https$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "https",
+                                    },
+                                },
+                            },
+                            username: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            password: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            hostname: Component {
+                                pattern_string: "tauri.app",
+                                regexp: Ok(
+                                    Regex(
+                                        "^tauri\\.app$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "tauri.app",
+                                    },
+                                },
+                            },
+                            port: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            pathname: Component {
+                                pattern_string: "/",
+                                regexp: Ok(
+                                    Regex(
+                                        "^/$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "/",
+                                    },
+                                },
+                            },
+                            search: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            hash: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                        },
+                        "https://tauri.app",
+                    ),
                 },
                 windows: [
                     Pattern {
@@ -92,63 +168,139 @@ Resolved {
         "plugin:fs|read_file": [
             ResolvedCommand {
                 context: Remote {
-                    url: Pattern {
-                        original: "https://tauri.app",
-                        tokens: [
-                            Char(
-                                'h',
-                            ),
-                            Char(
-                                't',
-                            ),
-                            Char(
-                                't',
-                            ),
-                            Char(
-                                'p',
-                            ),
-                            Char(
-                                's',
-                            ),
-                            Char(
-                                ':',
-                            ),
-                            Char(
-                                '/',
-                            ),
-                            Char(
-                                '/',
-                            ),
-                            Char(
-                                't',
-                            ),
-                            Char(
-                                'a',
-                            ),
-                            Char(
-                                'u',
-                            ),
-                            Char(
-                                'r',
-                            ),
-                            Char(
-                                'i',
-                            ),
-                            Char(
-                                '.',
-                            ),
-                            Char(
-                                'a',
-                            ),
-                            Char(
-                                'p',
-                            ),
-                            Char(
-                                'p',
-                            ),
-                        ],
-                        is_recursive: false,
-                    },
+                    url: RemoteUrlPattern(
+                        UrlPattern {
+                            protocol: Component {
+                                pattern_string: "https",
+                                regexp: Ok(
+                                    Regex(
+                                        "^https$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "https",
+                                    },
+                                },
+                            },
+                            username: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            password: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            hostname: Component {
+                                pattern_string: "tauri.app",
+                                regexp: Ok(
+                                    Regex(
+                                        "^tauri\\.app$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "tauri.app",
+                                    },
+                                },
+                            },
+                            port: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            pathname: Component {
+                                pattern_string: "/",
+                                regexp: Ok(
+                                    Regex(
+                                        "^/$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "/",
+                                    },
+                                },
+                            },
+                            search: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                            hash: Component {
+                                pattern_string: "",
+                                regexp: Ok(
+                                    Regex(
+                                        "^$",
+                                    ),
+                                ),
+                                group_name_list: [],
+                                matcher: Matcher {
+                                    prefix: "",
+                                    suffix: "",
+                                    inner: Literal {
+                                        literal: "",
+                                    },
+                                },
+                            },
+                        },
+                        "https://tauri.app",
+                    ),
                 },
                 windows: [
                     Pattern {

+ 56 - 0
tooling/cli/Cargo.lock

@@ -4936,6 +4936,7 @@ dependencies = [
  "log",
  "memchr",
  "phf 0.11.2",
+ "regex",
  "schemars",
  "semver",
  "serde",
@@ -4945,6 +4946,7 @@ dependencies = [
  "thiserror",
  "toml 0.8.10",
  "url",
+ "urlpattern",
  "walkdir",
 ]
 
@@ -5359,6 +5361,47 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-ucd-ident"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.15"
@@ -5475,6 +5518,19 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "urlpattern"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609"
+dependencies = [
+ "derive_more",
+ "regex",
+ "serde",
+ "unic-ucd-ident",
+ "url",
+]
+
 [[package]]
 name = "usvg"
 version = "0.40.0"

+ 1 - 1
tooling/cli/schema.json

@@ -1146,7 +1146,7 @@
       ],
       "properties": {
         "urls": {
-          "description": "Remote domains this capability refers to. Can use glob patterns.",
+          "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).",
           "type": "array",
           "items": {
             "type": "string"