remove `Program` enum

pull/2019/head
amrbashir 8 months ago
parent b696024611
commit 768e3ecbcf
No known key found for this signature in database
GPG Key ID: BBD7A47A2003FF33

@ -8,6 +8,25 @@ use std::path::PathBuf;
#[allow(dead_code)] #[allow(dead_code)]
mod scope; mod scope;
/// Opener scope application.
#[derive(schemars::JsonSchema)]
#[serde(untagged)]
#[allow(unused)]
enum Application {
/// Open in default application.
Default,
/// If true, allow open with any application.
Enable(bool),
/// Allow specific application to open with.
App(String),
}
impl Default for Application {
fn default() -> Self {
Self::Default
}
}
/// Opener scope entry. /// Opener scope entry.
#[derive(schemars::JsonSchema)] #[derive(schemars::JsonSchema)]
#[serde(untagged)] #[serde(untagged)]
@ -26,6 +45,9 @@ enum OpenerScopeEntry {
/// ///
/// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/"
url: String, url: String,
/// An application to open this url with, for example: firefox.
#[serde(default)]
app: Application,
}, },
Path { Path {
/// A path that can be opened by the webview when using the Opener APIs. /// A path that can be opened by the webview when using the Opener APIs.
@ -36,18 +58,55 @@ enum OpenerScopeEntry {
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
path: PathBuf, path: PathBuf,
/// An application to open this path with, for example: xdg-open.
#[serde(default)]
app: Application,
}, },
} }
// Ensure `OpenerScopeEntry` and `scope::EntryRaw` is kept in sync // Ensure `OpenerScopeEntry` and `scope::EntryRaw` is kept in sync
fn _f() { fn _f() {
match (scope::EntryRaw::Url { url: String::new() }) { match (scope::EntryRaw::Url {
scope::EntryRaw::Url { url } => OpenerScopeEntry::Url { url }, url: String::new(),
scope::EntryRaw::Path { path } => OpenerScopeEntry::Path { path }, app: scope::Application::Enable(true),
}) {
scope::EntryRaw::Url { url, app } => OpenerScopeEntry::Url {
url,
app: match app {
scope::Application::Enable(p) => Application::Enable(p),
scope::Application::App(p) => Application::App(p),
scope::Application::Default => Application::Default,
},
},
scope::EntryRaw::Path { path, app } => OpenerScopeEntry::Path {
path,
app: match app {
scope::Application::Enable(p) => Application::Enable(p),
scope::Application::App(p) => Application::App(p),
scope::Application::Default => Application::Default,
},
},
}; };
match (OpenerScopeEntry::Url { url: String::new() }) { match (OpenerScopeEntry::Url {
OpenerScopeEntry::Url { url } => scope::EntryRaw::Url { url }, url: String::new(),
OpenerScopeEntry::Path { path } => scope::EntryRaw::Path { path }, app: Application::Enable(true),
}) {
OpenerScopeEntry::Url { url, app } => scope::EntryRaw::Url {
url,
app: match app {
Application::Enable(p) => scope::Application::Enable(p),
Application::App(p) => scope::Application::App(p),
Application::Default => scope::Application::Default,
},
},
OpenerScopeEntry::Path { path, app } => scope::EntryRaw::Path {
path,
app: match app {
Application::Enable(p) => scope::Application::Enable(p),
Application::App(p) => scope::Application::App(p),
Application::Default => scope::Application::Default,
},
},
}; };
} }

@ -6,8 +6,6 @@ import { invoke } from '@tauri-apps/api/core'
// open <a href="..."> links with the API // open <a href="..."> links with the API
window.addEventListener('click', function (evt) { window.addEventListener('click', function (evt) {
console.log(evt.button)
// return early if // return early if
if ( if (
// event was prevented // event was prevented

@ -9,7 +9,7 @@ use tauri::{
AppHandle, Runtime, AppHandle, Runtime,
}; };
use crate::{open::Program, scope::Scope, Error}; use crate::{scope::Scope, Error};
#[tauri::command] #[tauri::command]
pub async fn open_url<R: Runtime>( pub async fn open_url<R: Runtime>(
@ -17,7 +17,7 @@ pub async fn open_url<R: Runtime>(
command_scope: CommandScope<crate::scope::Entry>, command_scope: CommandScope<crate::scope::Entry>,
global_scope: GlobalScope<crate::scope::Entry>, global_scope: GlobalScope<crate::scope::Entry>,
url: String, url: String,
with: Option<Program>, with: Option<String>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let scope = Scope::new( let scope = Scope::new(
&app, &app,
@ -33,10 +33,10 @@ pub async fn open_url<R: Runtime>(
.collect(), .collect(),
); );
if scope.is_url_allowed(&url) { if scope.is_url_allowed(&url, with.as_deref()) {
crate::open_url(url, with) crate::open_url(url, with)
} else { } else {
Err(Error::ForbiddenUrl(url)) Err(Error::ForbiddenUrl { url, with })
} }
} }
@ -46,7 +46,7 @@ pub async fn open_path<R: Runtime>(
command_scope: CommandScope<crate::scope::Entry>, command_scope: CommandScope<crate::scope::Entry>,
global_scope: GlobalScope<crate::scope::Entry>, global_scope: GlobalScope<crate::scope::Entry>,
path: String, path: String,
with: Option<Program>, with: Option<String>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let scope = Scope::new( let scope = Scope::new(
&app, &app,
@ -62,10 +62,10 @@ pub async fn open_path<R: Runtime>(
.collect(), .collect(),
); );
if scope.is_path_allowed(Path::new(&path))? { if scope.is_path_allowed(Path::new(&path), with.as_deref())? {
crate::open_path(path, with) crate::open_path(path, with)
} else { } else {
Err(Error::ForbiddenPath(path)) Err(Error::ForbiddenPath { path, with })
} }
} }

@ -20,10 +20,10 @@ pub enum Error {
Json(#[from] serde_json::Error), Json(#[from] serde_json::Error),
#[error("unknown program {0}")] #[error("unknown program {0}")]
UnknownProgramName(String), UnknownProgramName(String),
#[error("Not allowed to open forbidden path: {0}")] #[error("Not allowed to open path {0}{}", .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())]
ForbiddenPath(String), ForbiddenPath { path: String, with: Option<String> },
#[error("Not allowed to open forbidden url: {0}")] #[error("Not allowed to open url {0}{}", .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())]
ForbiddenUrl(String), ForbiddenUrl { url: String, with: Option<String> },
#[error("API not supported on the current platform")] #[error("API not supported on the current platform")]
UnsupportedPlatform, UnsupportedPlatform,
#[error(transparent)] #[error(transparent)]

@ -1 +1 @@
!function(){"use strict";"function"==typeof SuppressedError&&SuppressedError,window.addEventListener("click",(function(e){if(console.log(e.button),e.defaultPrevented||0!==e.button||e.metaKey||e.altKey)return;const t=e.composedPath().find((e=>e instanceof Node&&"A"===e.nodeName.toUpperCase()));if(!t||!t.href||"_blank"!==t.target&&!e.ctrlKey&&!e.shiftKey)return;const n=new URL(t.href);n.origin===window.location.origin||["http:","https:","mailto:","tel:"].every((e=>n.protocol!==e))||(e.preventDefault(),async function(e,t={},n){window.__TAURI_INTERNALS__.invoke(e,t,n)}("plugin:opener|open_url",{url:n}))}))}(); !function(){"use strict";"function"==typeof SuppressedError&&SuppressedError,window.addEventListener("click",(function(e){if(e.defaultPrevented||0!==e.button||e.metaKey||e.altKey)return;const t=e.composedPath().find((e=>e instanceof Node&&"A"===e.nodeName.toUpperCase()));if(!t||!t.href||"_blank"!==t.target&&!e.ctrlKey&&!e.shiftKey)return;const n=new URL(t.href);n.origin===window.location.origin||["http:","https:","mailto:","tel:"].every((e=>n.protocol!==e))||(e.preventDefault(),async function(e,t={},n){window.__TAURI_INTERNALS__.invoke(e,t,n)}("plugin:opener|open_url",{url:n}))}))}();

@ -26,7 +26,7 @@ mod scope_entry;
pub use error::Error; pub use error::Error;
type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
pub use open::{open_path, open_url, Program}; pub use open::{open_path, open_url};
pub use reveal_item_in_dir::reveal_item_in_dir; pub use reveal_item_in_dir::reveal_item_in_dir;
pub struct Opener<R: Runtime> { pub struct Opener<R: Runtime> {
@ -37,36 +37,60 @@ pub struct Opener<R: Runtime> {
} }
impl<R: Runtime> Opener<R> { impl<R: Runtime> Opener<R> {
/// Open a url with a default or specific browser opening program. /// Open a url with a default or specific program.
///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
#[cfg(desktop)] #[cfg(desktop)]
pub fn open_url(&self, url: impl Into<String>, with: Option<open::Program>) -> Result<()> { pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> {
open::open(url.into(), with).map_err(Into::into) crate::open::open(url.into(), with.map(Into::into)).map_err(Into::into)
} }
/// Open a url with a default or specific browser opening program. /// Open a url with a default or specific program.
///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
#[cfg(mobile)] #[cfg(mobile)]
pub fn open_url(&self, url: impl Into<String>, _with: Option<open::Program>) -> Result<()> { pub fn open_url(&self, url: impl Into<String>, _with: Option<impl Into<String>>) -> Result<()> {
self.mobile_plugin_handle self.mobile_plugin_handle
.run_mobile_plugin("open", url.into()) .run_mobile_plugin("open", url.into())
.map_err(Into::into) .map_err(Into::into)
} }
/// Open a (url) path with a default or specific browser opening program. /// Open a path with a default or specific program.
///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
#[cfg(desktop)] #[cfg(desktop)]
pub fn open_path(&self, path: impl Into<String>, with: Option<open::Program>) -> Result<()> { pub fn open_path(
open::open(path.into(), with).map_err(Into::into) &self,
path: impl Into<String>,
with: Option<impl Into<String>>,
) -> Result<()> {
crate::open::open(path.into(), with.map(Into::into)).map_err(Into::into)
} }
/// Open a (url) path with a default or specific browser opening program. /// Open a path with a default or specific program.
///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
#[cfg(mobile)] #[cfg(mobile)]
pub fn open_path(&self, path: impl Into<String>, _with: Option<open::Program>) -> Result<()> { pub fn open_path(
&self,
path: impl Into<String>,
_with: Option<impl Into<String>>,
) -> Result<()> {
self.mobile_plugin_handle self.mobile_plugin_handle
.run_mobile_plugin("open", path.into()) .run_mobile_plugin("open", path.into())
.map_err(Into::into) .map_err(Into::into)
} }
pub fn reveal_item_in_dir<P: AsRef<Path>>(&self, p: P) -> Result<()> { pub fn reveal_item_in_dir<P: AsRef<Path>>(&self, p: P) -> Result<()> {
reveal_item_in_dir::reveal_item_in_dir(p) crate::reveal_item_in_dir::reveal_item_in_dir(p)
} }
} }

@ -4,128 +4,11 @@
//! Types and functions related to shell. //! Types and functions related to shell.
use serde::{Deserialize, Deserializer}; use std::{ffi::OsStr, path::Path};
use std::{ffi::OsStr, fmt::Display, path::Path, str::FromStr}; pub(crate) fn open<P: AsRef<OsStr>, S: AsRef<str>>(path: P, with: Option<S>) -> crate::Result<()> {
match with {
/// Program to use on the [`open()`] call. Some(program) => ::open::with_detached(path, program.as_ref()),
#[derive(Debug)]
pub enum Program {
/// Use the `open` program.
Open,
/// Use the `start` program.
Start,
/// Use the `xdg-open` program.
XdgOpen,
/// Use the `gio` program.
Gio,
/// Use the `gnome-open` program.
GnomeOpen,
/// Use the `kde-open` program.
KdeOpen,
/// Use the `wslview` program.
WslView,
/// Use the `Firefox` program.
Firefox,
/// Use the `Google Chrome` program.
Chrome,
/// Use the `Chromium` program.
Chromium,
/// Use the `Safari` 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;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let p = match s.to_lowercase().as_str() {
"open" => Self::Open,
"start" => Self::Start,
"xdg-open" => Self::XdgOpen,
"gio" => Self::Gio,
"gnome-open" => Self::GnomeOpen,
"kde-open" => Self::KdeOpen,
"wslview" => Self::WslView,
"firefox" => Self::Firefox,
"chrome" | "google chrome" => Self::Chrome,
"chromium" => Self::Chromium,
"safari" => Self::Safari,
_ => return Err(crate::Error::UnknownProgramName(s.to_string())),
};
Ok(p)
}
}
impl<'de> Deserialize<'de> for Program {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Program::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string()))
}
}
impl Program {
pub(crate) fn name(self) -> &'static str {
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",
#[cfg(target_os = "macos")]
Self::Firefox => "Firefox",
#[cfg(not(target_os = "macos"))]
Self::Firefox => "firefox",
#[cfg(target_os = "macos")]
Self::Chrome => "Google Chrome",
#[cfg(not(target_os = "macos"))]
Self::Chrome => "google-chrome",
#[cfg(target_os = "macos")]
Self::Chromium => "Chromium",
#[cfg(not(target_os = "macos"))]
Self::Chromium => "chromium",
#[cfg(target_os = "macos")]
Self::Safari => "Safari",
#[cfg(not(target_os = "macos"))]
Self::Safari => "safari",
}
}
}
pub(crate) fn open<P: AsRef<OsStr>>(path: P, with: Option<Program>) -> crate::Result<()> {
match with.map(Program::name) {
Some(program) => ::open::with_detached(path, program),
None => ::open::that_detached(path), None => ::open::that_detached(path),
} }
.map_err(Into::into) .map_err(Into::into)
@ -133,6 +16,10 @@ pub(crate) fn open<P: AsRef<OsStr>>(path: P, with: Option<Program>) -> crate::Re
/// Opens URL with the program specified in `with`, or system default if `None`. /// Opens URL with the program specified in `with`, or system default if `None`.
/// ///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
///
/// # Examples /// # Examples
/// ///
/// ```rust,no_run /// ```rust,no_run
@ -143,13 +30,17 @@ pub(crate) fn open<P: AsRef<OsStr>>(path: P, with: Option<Program>) -> crate::Re
/// Ok(()) /// Ok(())
/// }); /// });
/// ``` /// ```
pub fn open_url<P: AsRef<str>>(url: P, with: Option<Program>) -> crate::Result<()> { pub fn open_url<P: AsRef<str>, S: AsRef<str>>(url: P, with: Option<S>) -> crate::Result<()> {
let url = url.as_ref(); let url = url.as_ref();
open(url, with) open(url, with)
} }
/// Opens path with the program specified in `with`, or system default if `None`. /// Opens path with the program specified in `with`, or system default if `None`.
/// ///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
///
/// # Examples /// # Examples
/// ///
/// ```rust,no_run /// ```rust,no_run
@ -160,7 +51,7 @@ pub fn open_url<P: AsRef<str>>(url: P, with: Option<Program>) -> crate::Result<(
/// Ok(()) /// Ok(())
/// }); /// });
/// ``` /// ```
pub fn open_path<P: AsRef<Path>>(path: P, with: Option<Program>) -> crate::Result<()> { pub fn open_path<P: AsRef<Path>, S: AsRef<str>>(path: P, with: Option<S>) -> crate::Result<()> {
let path = path.as_ref(); let path = path.as_ref();
open(path, with) open(path, with)
} }

@ -12,35 +12,44 @@ use tauri::{ipc::ScopeObject, utils::acl::Value, AppHandle, Manager, Runtime};
use crate::{scope_entry::EntryRaw, Error}; use crate::{scope_entry::EntryRaw, Error};
pub use crate::scope_entry::Application;
#[derive(Debug)] #[derive(Debug)]
pub enum Entry { pub enum Entry {
Url(glob::Pattern), Url {
Path(Option<PathBuf>), url: glob::Pattern,
app: Application,
},
Path {
path: Option<PathBuf>,
app: Application,
},
} }
impl ScopeObject for Entry { impl ScopeObject for Entry {
type Error = Error; type Error = Error;
fn deserialize<R: Runtime>( fn deserialize<R: Runtime>(
app: &AppHandle<R>, app_handle: &AppHandle<R>,
raw: Value, raw: Value,
) -> std::result::Result<Self, Self::Error> { ) -> std::result::Result<Self, Self::Error> {
serde_json::from_value(raw.into()) serde_json::from_value(raw.into())
.and_then(|raw| { .and_then(|raw| {
let entry = match raw { let entry = match raw {
EntryRaw::Url { url } => Entry::Url( EntryRaw::Url { url, app } => Entry::Url {
glob::Pattern::new(&url) url: glob::Pattern::new(&url)
.map_err(|e| serde::de::Error::custom(e.to_string()))?, .map_err(|e| serde::de::Error::custom(e.to_string()))?,
), app,
EntryRaw::Path { path } => { },
let path = match app.path().parse(path) { EntryRaw::Path { path, app } => {
let path = match app_handle.path().parse(path) {
Ok(path) => Some(path), Ok(path) => Some(path),
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
Err(tauri::Error::UnknownPath) => None, Err(tauri::Error::UnknownPath) => None,
Err(err) => return Err(serde::de::Error::custom(err.to_string())), Err(err) => return Err(serde::de::Error::custom(err.to_string())),
}; };
Entry::Path(path) Entry::Path { path, app }
} }
}; };
@ -50,6 +59,39 @@ impl ScopeObject for Entry {
} }
} }
impl Application {
fn matches(&self, a: Option<&str>) -> bool {
match self {
Self::Default => a.is_none(),
Self::Enable(enable) => *enable,
Self::App(program) => Some(program.as_str()) == a,
}
}
}
impl Entry {
fn path(&self) -> Option<PathBuf> {
match self {
Self::Url { .. } => None,
Self::Path { path, .. } => path.clone(),
}
}
fn matches_url(&self, u: &str, a: Option<&str>) -> bool {
match self {
Self::Url { url, app } => url.matches(u) && app.matches(a),
Self::Path { .. } => false,
}
}
fn matches_path_program(&self, a: Option<&str>) -> bool {
match self {
Self::Url { .. } => false,
Self::Path { app, .. } => app.matches(a),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Scope<'a, R: Runtime, M: Manager<R>> { pub struct Scope<'a, R: Runtime, M: Manager<R>> {
allowed: Vec<&'a Arc<Entry>>, allowed: Vec<&'a Arc<Entry>>,
@ -72,45 +114,25 @@ impl<'a, R: Runtime, M: Manager<R>> Scope<'a, R, M> {
} }
} }
pub fn is_url_allowed(&self, url: &str) -> bool { pub fn is_url_allowed(&self, url: &str, with: Option<&str>) -> bool {
let denied = self.denied.iter().any(|entry| match entry.as_ref() { let denied = self.denied.iter().any(|e| e.matches_url(url, with));
Entry::Url(url_pattern) => url_pattern.matches(url),
Entry::Path { .. } => false,
});
if denied { if denied {
false false
} else { } else {
self.allowed.iter().any(|entry| match entry.as_ref() { self.allowed.iter().any(|e| e.matches_url(url, with))
Entry::Url(url_pattern) => url_pattern.matches(url),
Entry::Path { .. } => false,
})
} }
} }
pub fn is_path_allowed(&self, path: &Path) -> crate::Result<bool> { pub fn is_path_allowed(&self, path: &Path, with: Option<&str>) -> crate::Result<bool> {
let fs_scope = tauri::fs::Scope::new( let fs_scope = tauri::fs::Scope::new(
self.manager, self.manager,
&tauri::utils::config::FsScope::Scope { &tauri::utils::config::FsScope::Scope {
allow: self allow: self.allowed.iter().filter_map(|e| e.path()).collect(),
.allowed deny: self.denied.iter().filter_map(|e| e.path()).collect(),
.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, require_literal_leading_dot: None,
}, },
)?; )?;
Ok(fs_scope.is_allowed(path)) Ok(fs_scope.is_allowed(path) && self.allowed.iter().any(|e| e.matches_path_program(with)))
} }
} }

@ -6,10 +6,30 @@ use std::path::PathBuf;
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub enum Application {
Default,
Enable(bool),
App(String),
}
impl Default for Application {
fn default() -> Self {
Self::Default
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged, rename_all = "camelCase")] #[serde(untagged, rename_all = "camelCase")]
#[allow(unused)]
pub(crate) enum EntryRaw { pub(crate) enum EntryRaw {
Url { url: String }, Url {
Path { path: PathBuf }, url: String,
#[serde(default)]
app: Application,
},
Path {
path: PathBuf,
#[serde(default)]
app: Application,
},
} }

Loading…
Cancel
Save