migrate to using scope

pull/2019/head
amrbashir 9 months ago
parent 38369c832d
commit 42ab44cd5f
No known key found for this signature in database
GPG Key ID: BBD7A47A2003FF33

3
Cargo.lock generated

@ -6586,11 +6586,14 @@ version = "2.0.0"
dependencies = [
"open",
"regex",
"schemars",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror",
"url",
"urlpattern",
]
[[package]]

@ -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]

@ -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"
]
}

@ -27,7 +27,10 @@
</script>
<div class="flex flex-col">
<div class="flex flex-row gap-2 items-center">
<form
class="flex flex-row gap-2 items-center"
on:submit|preventDefault={openPath}
>
<input
class="input grow"
placeholder="Type the path to watch..."
@ -42,6 +45,6 @@
{/each}
</select>
<button class="btn" on:click={openPath}>Open</button>
</div>
<button class="btn" type="submit">Open</button>
</form>
</div>

@ -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",

@ -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,
},
}

@ -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 }

@ -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 }

@ -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__})}

@ -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();

@ -53,5 +53,5 @@ export async function open(path: string, openWith?: string): Promise<void> {
})
}
export async function revealInDir() {
return invoke('plugin:opener|reveal_in_dir')
return invoke('plugin:opener|reveal_item_in_dir')
}

@ -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://*:*"

@ -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"]

@ -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"]

@ -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.
<tr>
<td>
`opener:allow-reveal-in-dir`
`opener:allow-reveal-item-in-dir`
</td>
<td>
Enables the reveal_item_in_dir command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`opener:deny-reveal-item-in-dir`
</td>
<td>
Denies the reveal_item_in_dir command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`opener:allow-reveal-item-in-dir`
</td>
<td>
Enables the reveal_item_in_dir command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`opener:deny-reveal-item-in-dir`
</td>
<td>
Enables the reveal_in_dir command without any pre-configured scope.
Denies the reveal_item_in_dir command without any pre-configured scope.
</td>
</tr>
@ -56,12 +97,12 @@ Enables the reveal_in_dir command without any pre-configured scope.
<tr>
<td>
`opener:deny-reveal-in-dir`
`opener:default-urls`
</td>
<td>
Denies the reveal_in_dir command without any pre-configured scope.
This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.
</td>
</tr>

@ -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"]

@ -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"
}

@ -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<R: Runtime>(
_app: AppHandle<R>,
opener: State<'_, Opener<R>>,
app: AppHandle<R>,
command_scope: CommandScope<crate::scope::Entry>,
global_scope: GlobalScope<crate::scope::Entry>,
path: String,
with: Option<Program>,
) -> 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() {}

@ -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<Regex>,
}
/// 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(&regex).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)
}
}

@ -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 {

@ -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<T> = std::result::Result<T, Error>;
@ -28,21 +28,16 @@ pub struct Opener<R: Runtime> {
app: AppHandle<R>,
#[cfg(mobile)]
mobile_plugin_handle: PluginHandle<R>,
open_scope: OpenScope,
}
impl<R: Runtime> Opener<R> {
/// 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<String>, with: Option<open::Program>) -> 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<String>, _with: Option<open::Program>) -> Result<()> {
self.mobile_plugin_handle
@ -63,13 +58,10 @@ impl<R: Runtime, T: Manager<R>> crate::OpenerExt<R> for T {
}
/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
Builder::<R, Option<config::Config>>::new("opener")
pub fn init<R: Runtime>() -> TauriPlugin<R> {
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<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
app.manage(Opener {
app: app.clone(),
open_scope: config.open_scope(),
#[cfg(mobile)]
mobile_plugin_handle: handle,
});
@ -85,7 +76,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
})
.invoke_handler(tauri::generate_handler![
commands::open,
commands::reveal_in_dir
commands::reveal_item_in_dir
])
.build()
}

@ -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<P: AsRef<str>>(scope: &OpenScope, path: P, with: Option<Program>) -> crate::Result<()> {
pub fn open<P: AsRef<str>>(path: P, with: Option<Program>) -> 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`.

@ -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<PathBuf>),
}
fn parse_url_pattern(
s: &str,
) -> std::result::Result<urlpattern::UrlPattern, urlpattern::quirks::Error> {
let mut init = urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(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<R: Runtime>(
app: &AppHandle<R>,
raw: Value,
) -> std::result::Result<Self, Self::Error> {
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<R>> {
allowed: Vec<&'a Arc<Entry>>,
denied: Vec<&'a Arc<Entry>>,
manager: &'a M,
_marker: PhantomData<R>,
}
impl<'a, R: Runtime, M: Manager<R>> Scope<'a, R, M> {
pub(crate) fn new(
manager: &'a M,
allowed: Vec<&'a Arc<Entry>>,
denied: Vec<&'a Arc<Entry>>,
) -> Self {
Self {
manager,
allowed,
denied,
_marker: PhantomData,
}
}
pub fn is_allowed(&self, path_or_url: &str) -> crate::Result<bool> {
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<bool> {
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))
}
}

@ -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 },
}

@ -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<R: Runtime>(
Ok(())
}
#[allow(deprecated)]
#[tauri::command]
pub async fn open<R: Runtime>(
_window: Window<R>,

@ -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<R: Runtime> Shell<R> {
/// 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<String>, with: Option<open::Program>) -> Result<()> {
open::open(&self.open_scope, path.into(), with).map_err(Into::into)
}

@ -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<Program>) -> Result<(), Error> {
// ensure we pass validation if the configuration has one
if let Some(regex) = &self.open {

Loading…
Cancel
Save