diff --git a/Cargo.lock b/Cargo.lock
index d94e7d16..dcbc2f30 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6586,11 +6586,14 @@ version = "2.0.0"
dependencies = [
"open",
"regex",
+ "schemars",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror",
+ "url",
+ "urlpattern",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index a17d33ea..d011d0ab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,8 @@ url = "2"
schemars = "0.8"
dunce = "1"
specta = "=2.0.0-rc.20"
+glob = "0.3"
+urlpattern = "0.3"
#tauri-specta = "=2.0.0-rc.11"
[workspace.package]
diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json
index c039c8e0..ae93aeef 100644
--- a/examples/api/src-tauri/capabilities/base.json
+++ b/examples/api/src-tauri/capabilities/base.json
@@ -16,7 +16,6 @@
},
"core:default",
"fs:default",
- "opener:default",
"core:window:allow-minimize",
"core:window:allow-maximize",
"core:window:allow-unmaximize",
@@ -81,6 +80,7 @@
],
"deny": ["$APPDATA/db/*.stronghold"]
},
- "store:default"
+ "store:default",
+ "opener:default"
]
}
diff --git a/examples/api/src/views/Opener.svelte b/examples/api/src/views/Opener.svelte
index cb5a2773..3a2afaab 100644
--- a/examples/api/src/views/Opener.svelte
+++ b/examples/api/src/views/Opener.svelte
@@ -27,7 +27,10 @@
diff --git a/plugins/fs/Cargo.toml b/plugins/fs/Cargo.toml
index 5d9a7efb..7546baef 100644
--- a/plugins/fs/Cargo.toml
+++ b/plugins/fs/Cargo.toml
@@ -34,7 +34,7 @@ thiserror = { workspace = true }
url = { workspace = true }
anyhow = "1"
uuid = { version = "1", features = ["v4"] }
-glob = "0.3"
+glob = { workspace = true }
# TODO: Remove `serialization-compat-6` in v3
notify = { version = "7", optional = true, features = [
"serde",
diff --git a/plugins/fs/build.rs b/plugins/fs/build.rs
index cb9d00da..935e0a81 100644
--- a/plugins/fs/build.rs
+++ b/plugins/fs/build.rs
@@ -16,10 +16,23 @@ mod scope;
#[serde(untagged)]
#[allow(unused)]
enum FsScopeEntry {
- /// FS scope path.
+ /// A path that can be accessed by the webview when using the fs APIs.
+ /// FS scope path pattern.
+ ///
+ /// The pattern can start with a variable that resolves to a system base directory.
+ /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
+ /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
+ /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
+ /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
Value(PathBuf),
Object {
- /// FS scope path.
+ /// A path that can be accessed by the webview when using the fs APIs.
+ ///
+ /// The pattern can start with a variable that resolves to a system base directory.
+ /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
+ /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
+ /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
+ /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
path: PathBuf,
},
}
diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml
index 8c1801a3..7b008c14 100644
--- a/plugins/http/Cargo.toml
+++ b/plugins/http/Cargo.toml
@@ -25,7 +25,7 @@ tauri-plugin = { workspace = true, features = ["build"] }
schemars = { workspace = true }
serde = { workspace = true }
url = { workspace = true }
-urlpattern = "0.3"
+urlpattern = { workspace = true }
regex = "1"
[dependencies]
@@ -35,7 +35,7 @@ tauri = { workspace = true }
thiserror = { workspace = true }
tokio = { version = "1", features = ["sync", "macros"] }
tauri-plugin-fs = { path = "../fs", version = "2.0.3" }
-urlpattern = "0.3"
+urlpattern = { workspace = true }
regex = "1"
http = "1"
reqwest = { version = "0.12", default-features = false }
diff --git a/plugins/opener/Cargo.toml b/plugins/opener/Cargo.toml
index 92aa4efa..f450e15d 100644
--- a/plugins/opener/Cargo.toml
+++ b/plugins/opener/Cargo.toml
@@ -24,13 +24,17 @@ ios = { level = "partial", notes = "Only allows to open URLs via `open`" }
[build-dependencies]
tauri-plugin = { workspace = true, features = ["build"] }
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+schemars = { workspace = true }
+serde = { workspace = true }
+urlpattern = { workspace = true }
+regex = "1"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
tauri = { workspace = true }
thiserror = { workspace = true }
-regex = "1"
open = { version = "5", features = ["shellexecute-on-windows"] }
+urlpattern = { workspace = true }
+regex = "1"
+url = { workspace = true }
diff --git a/plugins/opener/api-iife.js b/plugins/opener/api-iife.js
index 07852123..5b37b44e 100644
--- a/plugins/opener/api-iife.js
+++ b/plugins/opener/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},_){return window.__TAURI_INTERNALS__.invoke(n,e,_)}return"function"==typeof SuppressedError&&SuppressedError,n.open=async function(n,_){await e("plugin:opener|open",{path:n,with:_})},n.revealInDir=async function(){return e("plugin:opener|reveal_in_dir")},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
+if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},_){return window.__TAURI_INTERNALS__.invoke(n,e,_)}return"function"==typeof SuppressedError&&SuppressedError,n.open=async function(n,_){await e("plugin:opener|open",{path:n,with:_})},n.revealInDir=async function(){return e("plugin:opener|reveal_item_in_dir")},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
diff --git a/plugins/opener/build.rs b/plugins/opener/build.rs
index 6bf98b26..6bccbdd9 100644
--- a/plugins/opener/build.rs
+++ b/plugins/opener/build.rs
@@ -2,13 +2,66 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
-const COMMANDS: &[&str] = &["open", "reveal_in_dir"];
+use std::path::PathBuf;
+
+#[path = "src/scope_entry.rs"]
+#[allow(dead_code)]
+mod scope;
+
+/// Opener scope entry.
+#[derive(schemars::JsonSchema)]
+#[serde(untagged)]
+#[allow(unused)]
+enum OpenerScopeEntry {
+ Url {
+ /// A URL that can be opened by the webview when using the Opener APIs.
+ /// Wildcards can be used following the URL pattern standard.
+ ///
+ /// See [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.
+ ///
+ /// Examples:
+ ///
+ /// - "https://*" : allows all HTTPS origin on port 443
+ ///
+ /// - "https://*:*" : allows all HTTPS origin on any port
+ ///
+ /// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path
+ ///
+ /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/"
+ url: String,
+ },
+ Path {
+ /// A path that can be opened by the webview when using the Opener APIs.
+ ///
+ /// The pattern can start with a variable that resolves to a system base directory.
+ /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
+ /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
+ /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
+ /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
+ path: PathBuf,
+ },
+}
+
+// Ensure `OpenerScopeEntry` and `scope::EntryRaw` is kept in sync
+fn _f() {
+ match (scope::EntryRaw::Url { url: String::new() }) {
+ scope::EntryRaw::Url { url } => OpenerScopeEntry::Url { url },
+ scope::EntryRaw::Path { path } => OpenerScopeEntry::Path { path },
+ };
+ match (OpenerScopeEntry::Url { url: String::new() }) {
+ OpenerScopeEntry::Url { url } => scope::EntryRaw::Url { url },
+ OpenerScopeEntry::Path { path } => scope::EntryRaw::Path { path },
+ };
+}
+
+const COMMANDS: &[&str] = &["open", "reveal_item_in_dir"];
fn main() {
tauri_plugin::Builder::new(COMMANDS)
.global_api_script_path("./api-iife.js")
.android_path("android")
.ios_path("ios")
+ .global_scope_schema(schemars::schema_for!(OpenerScopeEntry))
.build();
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
diff --git a/plugins/opener/guest-js/index.ts b/plugins/opener/guest-js/index.ts
index 82c04738..0435fcc4 100644
--- a/plugins/opener/guest-js/index.ts
+++ b/plugins/opener/guest-js/index.ts
@@ -53,5 +53,5 @@ export async function open(path: string, openWith?: string): Promise {
})
}
export async function revealInDir() {
- return invoke('plugin:opener|reveal_in_dir')
+ return invoke('plugin:opener|reveal_item_in_dir')
}
diff --git a/plugins/opener/permissions/allow-default-urls.toml b/plugins/opener/permissions/allow-default-urls.toml
new file mode 100644
index 00000000..cad02770
--- /dev/null
+++ b/plugins/opener/permissions/allow-default-urls.toml
@@ -0,0 +1,17 @@
+"$schema" = "schemas/schema.json"
+
+[[permission]]
+identifier = "allow-default-urls"
+description = "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application."
+
+[[permission.scope.allow]]
+url = "mailto:*"
+
+[[permission.scope.allow]]
+url = "tel:*"
+
+[[permission.scope.allow]]
+url = "http://*:*"
+
+[[permission.scope.allow]]
+url = "https://*:*"
diff --git a/plugins/opener/permissions/autogenerated/commands/reveal_in_dir.toml b/plugins/opener/permissions/autogenerated/commands/reveal_in_dir.toml
index a5c79836..e669620f 100644
--- a/plugins/opener/permissions/autogenerated/commands/reveal_in_dir.toml
+++ b/plugins/opener/permissions/autogenerated/commands/reveal_in_dir.toml
@@ -3,11 +3,11 @@
"$schema" = "../../schemas/schema.json"
[[permission]]
-identifier = "allow-reveal-in-dir"
-description = "Enables the reveal_in_dir command without any pre-configured scope."
-commands.allow = ["reveal_in_dir"]
+identifier = "allow-reveal-item-in-dir"
+description = "Enables the reveal_item_in_dir command without any pre-configured scope."
+commands.allow = ["reveal_item_in_dir"]
[[permission]]
-identifier = "deny-reveal-in-dir"
-description = "Denies the reveal_in_dir command without any pre-configured scope."
-commands.deny = ["reveal_in_dir"]
+identifier = "deny-reveal-item-in-dir"
+description = "Denies the reveal_item_in_dir command without any pre-configured scope."
+commands.deny = ["reveal_item_in_dir"]
diff --git a/plugins/opener/permissions/autogenerated/commands/reveal_item_in_dir.toml b/plugins/opener/permissions/autogenerated/commands/reveal_item_in_dir.toml
new file mode 100644
index 00000000..e669620f
--- /dev/null
+++ b/plugins/opener/permissions/autogenerated/commands/reveal_item_in_dir.toml
@@ -0,0 +1,13 @@
+# Automatically generated - DO NOT EDIT!
+
+"$schema" = "../../schemas/schema.json"
+
+[[permission]]
+identifier = "allow-reveal-item-in-dir"
+description = "Enables the reveal_item_in_dir command without any pre-configured scope."
+commands.allow = ["reveal_item_in_dir"]
+
+[[permission]]
+identifier = "deny-reveal-item-in-dir"
+description = "Denies the reveal_item_in_dir command without any pre-configured scope."
+commands.deny = ["reveal_item_in_dir"]
diff --git a/plugins/opener/permissions/autogenerated/reference.md b/plugins/opener/permissions/autogenerated/reference.md
index a03c50e6..0a795ef7 100644
--- a/plugins/opener/permissions/autogenerated/reference.md
+++ b/plugins/opener/permissions/autogenerated/reference.md
@@ -1,9 +1,11 @@
## Default Permission
-
+This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application
+as well as reveal file in directories using default file explorer
- `allow-open`
-- `allow-reveal-in-dir`
+- `allow-reveal-item-in-dir`
+- `default-urls`
## Permission Table
@@ -43,12 +45,51 @@ Denies the open command without any pre-configured scope.
-`opener:allow-reveal-in-dir`
+`opener:allow-reveal-item-in-dir`
+
+ |
+
+
+Enables the reveal_item_in_dir command without any pre-configured scope.
+
+ |
+
+
+
+
+
+`opener:deny-reveal-item-in-dir`
+
+ |
+
+
+Denies the reveal_item_in_dir command without any pre-configured scope.
+
+ |
+
+
+
+
+
+`opener:allow-reveal-item-in-dir`
+
+ |
+
+
+Enables the reveal_item_in_dir command without any pre-configured scope.
+
+ |
+
+
+
+
+
+`opener:deny-reveal-item-in-dir`
|
-Enables the reveal_in_dir command without any pre-configured scope.
+Denies the reveal_item_in_dir command without any pre-configured scope.
|
@@ -56,12 +97,12 @@ Enables the reveal_in_dir command without any pre-configured scope.
-`opener:deny-reveal-in-dir`
+`opener:default-urls`
|
-Denies the reveal_in_dir command without any pre-configured scope.
+This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.
|
diff --git a/plugins/opener/permissions/default.toml b/plugins/opener/permissions/default.toml
index 7286f06c..f9e3184d 100644
--- a/plugins/opener/permissions/default.toml
+++ b/plugins/opener/permissions/default.toml
@@ -1,4 +1,6 @@
"$schema" = "schemas/schema.json"
[default]
-permissions = ["allow-open", "allow-reveal-in-dir"]
+description = """This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application
+as well as reveal file in directories using default file explorer"""
+permissions = ["allow-open", "allow-reveal-item-in-dir", "allow-default-urls"]
diff --git a/plugins/opener/permissions/schemas/schema.json b/plugins/opener/permissions/schemas/schema.json
index 8078fbbc..94891e8f 100644
--- a/plugins/opener/permissions/schemas/schema.json
+++ b/plugins/opener/permissions/schemas/schema.json
@@ -305,16 +305,32 @@
"const": "deny-open"
},
{
- "description": "Enables the reveal_in_dir command without any pre-configured scope.",
+ "description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
- "const": "allow-reveal-in-dir"
+ "const": "allow-reveal-item-in-dir"
},
{
- "description": "Denies the reveal_in_dir command without any pre-configured scope.",
+ "description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
- "const": "deny-reveal-in-dir"
+ "const": "deny-reveal-item-in-dir"
},
{
+ "description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
+ "type": "string",
+ "const": "allow-reveal-item-in-dir"
+ },
+ {
+ "description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
+ "type": "string",
+ "const": "deny-reveal-item-in-dir"
+ },
+ {
+ "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
+ "type": "string",
+ "const": "default-urls"
+ },
+ {
+ "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer",
"type": "string",
"const": "default"
}
diff --git a/plugins/opener/src/commands.rs b/plugins/opener/src/commands.rs
index 6a6ba118..b3eec103 100644
--- a/plugins/opener/src/commands.rs
+++ b/plugins/opener/src/commands.rs
@@ -1,16 +1,38 @@
-use tauri::{AppHandle, Runtime, State};
+use tauri::{
+ ipc::{CommandScope, GlobalScope},
+ AppHandle, Runtime,
+};
-use crate::{open::Program, Opener};
+use crate::{open::Program, scope::Scope, Error};
#[tauri::command]
pub async fn open(
- _app: AppHandle,
- opener: State<'_, Opener>,
+ app: AppHandle,
+ command_scope: CommandScope,
+ global_scope: GlobalScope,
path: String,
with: Option,
) -> crate::Result<()> {
- opener.open(path, with)
+ let scope = Scope::new(
+ &app,
+ command_scope
+ .allows()
+ .iter()
+ .chain(global_scope.allows())
+ .collect(),
+ command_scope
+ .denies()
+ .iter()
+ .chain(global_scope.denies())
+ .collect(),
+ );
+
+ if scope.is_allowed(&path)? {
+ crate::open::open(path, with)
+ } else {
+ Err(Error::NotAllowed(path))
+ }
}
#[tauri::command]
-pub async fn reveal_in_dir() {}
+pub async fn reveal_item_in_dir() {}
diff --git a/plugins/opener/src/config.rs b/plugins/opener/src/config.rs
deleted file mode 100644
index 9ac28014..00000000
--- a/plugins/opener/src/config.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use regex::Regex;
-use serde::Deserialize;
-
-/// Scope for the open command
-pub struct OpenScope {
- /// The validation regex that `shell > open` paths must match against.
- pub open: Option,
-}
-
-/// Configuration for the shell plugin.
-#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)]
-#[serde(rename_all = "camelCase", deny_unknown_fields)]
-pub struct Config {
- /// Open URL with the user's default application.
- #[serde(default)]
- pub open: OpenConfig,
-}
-
-impl Config {
- pub fn open_scope(&self) -> OpenScope {
- let open = match &self.open {
- OpenConfig::Flag(false) => None,
- OpenConfig::Flag(true) => {
- Some(Regex::new(r"^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+").unwrap())
- }
- OpenConfig::Validate(validator) => {
- let regex = format!("^{validator}$");
- let validator =
- Regex::new(®ex).unwrap_or_else(|e| panic!("invalid regex {regex}: {e}"));
- Some(validator)
- }
- };
-
- OpenScope { open }
- }
-}
-
-/// Defines the `opener > open` api scope.
-#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
-#[serde(untagged, deny_unknown_fields)]
-#[non_exhaustive]
-pub enum OpenConfig {
- /// If the opener open API should be enabled.
- ///
- /// If enabled, the default validation regex (`^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`) is used.
- Flag(bool),
-
- /// Enable the opener open API, with a custom regex that the opened path must match against.
- ///
- /// The regex string is automatically surrounded by `^...$` to match the full string.
- /// For example the `https?://\w+` regex would be registered as `^https?://\w+$`.
- ///
- /// If using a custom regex to support a non-http(s) schema, care should be used to prevent values
- /// that allow flag-like strings to pass validation. e.g. `--enable-debugging`, `-i`, `/R`.
- Validate(String),
-}
-
-impl Default for OpenConfig {
- fn default() -> Self {
- Self::Flag(false)
- }
-}
diff --git a/plugins/opener/src/error.rs b/plugins/opener/src/error.rs
index 833e7a6f..943305d6 100644
--- a/plugins/opener/src/error.rs
+++ b/plugins/opener/src/error.rs
@@ -10,18 +10,15 @@ pub enum Error {
#[error(transparent)]
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
#[error(transparent)]
+ Tauri(#[from] tauri::Error),
+ #[error(transparent)]
Io(#[from] std::io::Error),
+ #[error(transparent)]
+ Json(#[from] serde_json::Error),
#[error("unknown program {0}")]
UnknownProgramName(String),
- /// At least one argument did not pass input validation.
- #[error("Scoped command argument at position {index} was found, but failed regex validation {validation}")]
- Validation {
- /// Index of the variable.
- index: usize,
-
- /// Regex that the variable value failed to match.
- validation: String,
- },
+ #[error("Not allowed to open {0}")]
+ NotAllowed(String),
}
impl Serialize for Error {
diff --git a/plugins/opener/src/lib.rs b/plugins/opener/src/lib.rs
index 8b75dbaf..94d06851 100644
--- a/plugins/opener/src/lib.rs
+++ b/plugins/opener/src/lib.rs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
-use config::OpenScope;
use tauri::{
plugin::{Builder, TauriPlugin},
AppHandle, Manager, Runtime,
@@ -16,9 +15,10 @@ const PLUGIN_IDENTIFIER: &str = "app.tauri.opener";
tauri::ios_plugin_binding!(init_plugin_opener);
mod commands;
-mod config;
mod error;
mod open;
+mod scope;
+mod scope_entry;
pub use error::Error;
type Result = std::result::Result;
@@ -28,21 +28,16 @@ pub struct Opener {
app: AppHandle,
#[cfg(mobile)]
mobile_plugin_handle: PluginHandle,
- open_scope: OpenScope,
}
impl Opener {
/// Open a (url) path with a default or specific browser opening program.
- ///
- /// See [`crate::open::open`] for how it handles security-related measures.
#[cfg(desktop)]
pub fn open(&self, path: impl Into, with: Option) -> Result<()> {
- open::open(&self.open_scope, path.into(), with).map_err(Into::into)
+ open::open(path.into(), with).map_err(Into::into)
}
/// Open a (url) path with a default or specific browser opening program.
- ///
- /// See [`crate::open::open`] for how it handles security-related measures.
#[cfg(mobile)]
pub fn open(&self, path: impl Into, _with: Option) -> Result<()> {
self.mobile_plugin_handle
@@ -63,13 +58,10 @@ impl> crate::OpenerExt for T {
}
/// Initializes the plugin.
-pub fn init() -> TauriPlugin> {
- Builder::>::new("opener")
+pub fn init() -> TauriPlugin {
+ Builder::new("opener")
.js_init_script(include_str!("init-iife.js").to_string())
- .setup(|app, api| {
- let default_config = config::Config::default();
- let config = api.config().as_ref().unwrap_or(&default_config);
-
+ .setup(|app, _api| {
#[cfg(target_os = "android")]
let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "OpenerPlugin")?;
#[cfg(target_os = "ios")]
@@ -77,7 +69,6 @@ pub fn init() -> TauriPlugin> {
app.manage(Opener {
app: app.clone(),
- open_scope: config.open_scope(),
#[cfg(mobile)]
mobile_plugin_handle: handle,
});
@@ -85,7 +76,7 @@ pub fn init() -> TauriPlugin> {
})
.invoke_handler(tauri::generate_handler![
commands::open,
- commands::reveal_in_dir
+ commands::reveal_item_in_dir
])
.build()
}
diff --git a/plugins/opener/src/open.rs b/plugins/opener/src/open.rs
index 33ddb0e3..b472cb34 100644
--- a/plugins/opener/src/open.rs
+++ b/plugins/opener/src/open.rs
@@ -6,11 +6,10 @@
use serde::{Deserialize, Deserializer};
-use std::str::FromStr;
-
-use crate::{config::OpenScope, Error};
+use std::{fmt::Display, str::FromStr};
/// Program to use on the [`open()`] call.
+#[derive(Debug)]
pub enum Program {
/// Use the `open` program.
Open,
@@ -36,6 +35,28 @@ pub enum Program {
Safari,
}
+impl Display for Program {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::Open => "open",
+ Self::Start => "start",
+ Self::XdgOpen => "xdg-open",
+ Self::Gio => "gio",
+ Self::GnomeOpen => "gnome-open",
+ Self::KdeOpen => "kde-open",
+ Self::WslView => "wslview",
+ Self::Firefox => "firefox",
+ Self::Chrome => "chrome",
+ Self::Chromium => "chromium",
+ Self::Safari => "safari",
+ }
+ )
+ }
+}
+
impl FromStr for Program {
type Err = super::Error;
@@ -118,18 +139,18 @@ impl Program {
/// Ok(())
/// });
/// ```
-pub fn open>(scope: &OpenScope, path: P, with: Option) -> crate::Result<()> {
+pub fn open>(path: P, with: Option) -> crate::Result<()> {
let path = path.as_ref();
- // ensure we pass validation if the configuration has one
- if let Some(regex) = &scope.open {
- if !regex.is_match(path) {
- return Err(Error::Validation {
- index: 0,
- validation: regex.as_str().into(),
- });
- }
- }
+ // // ensure we pass validation if the configuration has one
+ // if let Some(regex) = &scope.open {
+ // if !regex.is_match(path) {
+ // return Err(Error::Validation {
+ // index: 0,
+ // validation: regex.as_str().into(),
+ // });
+ // }
+ // }
// The prevention of argument escaping is handled by the usage of std::process::Command::arg by
// the `open` dependency. This behavior should be re-confirmed during upgrades of `open`.
diff --git a/plugins/opener/src/scope.rs b/plugins/opener/src/scope.rs
new file mode 100644
index 00000000..b26b910c
--- /dev/null
+++ b/plugins/opener/src/scope.rs
@@ -0,0 +1,146 @@
+use std::{marker::PhantomData, path::PathBuf, sync::Arc};
+
+use tauri::{ipc::ScopeObject, utils::acl::Value, AppHandle, Manager, Runtime};
+
+use url::Url;
+use urlpattern::UrlPatternMatchInput;
+
+use crate::{scope_entry::EntryRaw, Error};
+
+#[derive(Debug)]
+pub enum Entry {
+ Url(urlpattern::UrlPattern),
+ Path(Option),
+}
+
+fn parse_url_pattern(
+ s: &str,
+) -> std::result::Result {
+ 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());
+ }
+ urlpattern::UrlPattern::parse(init, Default::default())
+}
+
+impl ScopeObject for Entry {
+ type Error = Error;
+
+ fn deserialize(
+ app: &AppHandle,
+ raw: Value,
+ ) -> std::result::Result {
+ serde_json::from_value(raw.into())
+ .and_then(|raw| {
+ let entry = match raw {
+ EntryRaw::Url { url } => Entry::Url(parse_url_pattern(&url).map_err(|e| {
+ serde::de::Error::custom(format!(
+ "`{}` is not a valid URL pattern: {e}",
+ url
+ ))
+ })?),
+ EntryRaw::Path { path } => {
+ let path = match app.path().parse(path) {
+ Ok(path) => Some(path),
+ #[cfg(not(target_os = "android"))]
+ Err(tauri::Error::UnknownPath) => None,
+ Err(err) => return Err(serde::de::Error::custom(err.to_string())),
+ };
+
+ Entry::Path(path)
+ }
+ };
+
+ Ok(entry)
+ })
+ .map_err(Into::into)
+ }
+}
+
+#[derive(Debug)]
+pub struct Scope<'a, R: Runtime, M: Manager> {
+ allowed: Vec<&'a Arc>,
+ denied: Vec<&'a Arc>,
+ manager: &'a M,
+ _marker: PhantomData,
+}
+
+impl<'a, R: Runtime, M: Manager> Scope<'a, R, M> {
+ pub(crate) fn new(
+ manager: &'a M,
+ allowed: Vec<&'a Arc>,
+ denied: Vec<&'a Arc>,
+ ) -> Self {
+ Self {
+ manager,
+ allowed,
+ denied,
+ _marker: PhantomData,
+ }
+ }
+
+ pub fn is_allowed(&self, path_or_url: &str) -> crate::Result {
+ let url = Url::parse(path_or_url).ok();
+ match url {
+ Some(url) => Ok(self.is_url_allowed(url)),
+ None => self.is_path_allowed(path_or_url),
+ }
+ }
+
+ pub fn is_url_allowed(&self, url: Url) -> bool {
+ let denied = self.denied.iter().any(|entry| match entry.as_ref() {
+ Entry::Url(url_pattern) => url_pattern
+ .test(UrlPatternMatchInput::Url(url.clone()))
+ .unwrap_or_default(),
+ Entry::Path { .. } => false,
+ });
+ if denied {
+ false
+ } else {
+ self.allowed.iter().any(|entry| match entry.as_ref() {
+ Entry::Url(url_pattern) => url_pattern
+ .test(UrlPatternMatchInput::Url(url.clone()))
+ .unwrap_or_default(),
+ Entry::Path { .. } => false,
+ })
+ }
+ }
+
+ pub fn is_path_allowed(&self, path: &str) -> crate::Result {
+ let fs_scope = tauri::fs::Scope::new(
+ self.manager,
+ &tauri::utils::config::FsScope::Scope {
+ allow: self
+ .allowed
+ .iter()
+ .filter_map(|e| match e.as_ref() {
+ Entry::Path(path) => path.clone(),
+ _ => None,
+ })
+ .collect(),
+ deny: self
+ .denied
+ .iter()
+ .filter_map(|e| match e.as_ref() {
+ Entry::Path(path) => path.clone(),
+ _ => None,
+ })
+ .collect(),
+ require_literal_leading_dot: None,
+ },
+ )?;
+
+ Ok(fs_scope.is_allowed(path))
+ }
+}
diff --git a/plugins/opener/src/scope_entry.rs b/plugins/opener/src/scope_entry.rs
new file mode 100644
index 00000000..105e2d59
--- /dev/null
+++ b/plugins/opener/src/scope_entry.rs
@@ -0,0 +1,11 @@
+use std::path::PathBuf;
+
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+#[serde(untagged, rename_all = "camelCase")]
+#[allow(unused)]
+pub(crate) enum EntryRaw {
+ Url { url: String },
+ Path { path: PathBuf },
+}
diff --git a/plugins/shell/src/commands.rs b/plugins/shell/src/commands.rs
index 3345bb3a..5bee6b92 100644
--- a/plugins/shell/src/commands.rs
+++ b/plugins/shell/src/commands.rs
@@ -11,8 +11,9 @@ use tauri::{
Manager, Runtime, State, Window,
};
+#[allow(deprecated)]
+use crate::open::Program;
use crate::{
- open::Program,
process::{CommandEvent, TerminatedPayload},
scope::ExecuteArgs,
Shell,
@@ -302,6 +303,7 @@ pub fn kill(
Ok(())
}
+#[allow(deprecated)]
#[tauri::command]
pub async fn open(
_window: Window,
diff --git a/plugins/shell/src/lib.rs b/plugins/shell/src/lib.rs
index 9066da93..4e8b245f 100644
--- a/plugins/shell/src/lib.rs
+++ b/plugins/shell/src/lib.rs
@@ -29,6 +29,7 @@ mod commands;
mod config;
mod error;
#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
+#[allow(deprecated)]
pub mod open;
pub mod process;
mod scope;
@@ -74,6 +75,7 @@ impl Shell {
/// See [`crate::open::open`] for how it handles security-related measures.
#[cfg(desktop)]
#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
+ #[allow(deprecated)]
pub fn open(&self, path: impl Into, with: Option) -> Result<()> {
open::open(&self.open_scope, path.into(), with).map_err(Into::into)
}
diff --git a/plugins/shell/src/scope.rs b/plugins/shell/src/scope.rs
index 8178ab10..9e76931a 100644
--- a/plugins/shell/src/scope.rs
+++ b/plugins/shell/src/scope.rs
@@ -4,6 +4,7 @@
use std::sync::Arc;
+#[allow(deprecated)]
use crate::open::Program;
use crate::process::Command;
@@ -201,6 +202,7 @@ impl OpenScope {
///
/// The path is validated against the `plugins > shell > open` validation regex, which
/// defaults to `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`.
+ #[allow(deprecated)]
pub fn open(&self, path: &str, with: Option) -> Result<(), Error> {
// ensure we pass validation if the configuration has one
if let Some(regex) = &self.open {