parent
38369c832d
commit
42ab44cd5f
@ -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__})}
|
||||
|
@ -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://*:*"
|
@ -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,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"]
|
||||
|
@ -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(®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)
|
||||
}
|
||||
}
|
@ -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 },
|
||||
}
|
Loading…
Reference in new issue