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)]
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.
#[derive(schemars::JsonSchema)]
#[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/"
url: String,
/// An application to open this url with, for example: firefox.
#[serde(default)]
app: Application,
},
Path {
/// 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`,
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
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
fn _f() {
match (scope::EntryRaw::Url { url: String::new() }) {
scope::EntryRaw::Url { url } => OpenerScopeEntry::Url { url },
scope::EntryRaw::Path { path } => OpenerScopeEntry::Path { path },
match (scope::EntryRaw::Url {
url: String::new(),
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() }) {
OpenerScopeEntry::Url { url } => scope::EntryRaw::Url { url },
OpenerScopeEntry::Path { path } => scope::EntryRaw::Path { path },
match (OpenerScopeEntry::Url {
url: String::new(),
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
window.addEventListener('click', function (evt) {
console.log(evt.button)
// return early if
if (
// event was prevented

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

@ -20,10 +20,10 @@ pub enum Error {
Json(#[from] serde_json::Error),
#[error("unknown program {0}")]
UnknownProgramName(String),
#[error("Not allowed to open forbidden path: {0}")]
ForbiddenPath(String),
#[error("Not allowed to open forbidden url: {0}")]
ForbiddenUrl(String),
#[error("Not allowed to open path {0}{}", .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())]
ForbiddenPath { path: String, with: Option<String> },
#[error("Not allowed to open url {0}{}", .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())]
ForbiddenUrl { url: String, with: Option<String> },
#[error("API not supported on the current platform")]
UnsupportedPlatform,
#[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;
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 struct Opener<R: Runtime> {
@ -37,36 +37,60 @@ pub struct Opener<R: Runtime> {
}
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)]
pub fn open_url(&self, url: impl Into<String>, with: Option<open::Program>) -> Result<()> {
open::open(url.into(), with).map_err(Into::into)
pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> {
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)]
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
.run_mobile_plugin("open", url.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)]
pub fn open_path(&self, path: impl Into<String>, with: Option<open::Program>) -> Result<()> {
open::open(path.into(), with).map_err(Into::into)
pub fn open_path(
&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)]
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
.run_mobile_plugin("open", path.into())
.map_err(Into::into)
}
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.
use serde::{Deserialize, Deserializer};
use std::{ffi::OsStr, path::Path};
use std::{ffi::OsStr, fmt::Display, path::Path, str::FromStr};
/// Program to use on the [`open()`] call.
#[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),
pub(crate) fn open<P: AsRef<OsStr>, S: AsRef<str>>(path: P, with: Option<S>) -> crate::Result<()> {
match with {
Some(program) => ::open::with_detached(path, program.as_ref()),
None => ::open::that_detached(path),
}
.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`.
///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
///
/// # Examples
///
/// ```rust,no_run
@ -143,13 +30,17 @@ pub(crate) fn open<P: AsRef<OsStr>>(path: P, with: Option<Program>) -> crate::Re
/// 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();
open(url, with)
}
/// Opens path with the program specified in `with`, or system default if `None`.
///
/// ## Platform-specific:
///
/// - **Android / iOS**: Always opens using default program.
///
/// # Examples
///
/// ```rust,no_run
@ -160,7 +51,7 @@ pub fn open_url<P: AsRef<str>>(url: P, with: Option<Program>) -> crate::Result<(
/// 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();
open(path, with)
}

@ -12,35 +12,44 @@ use tauri::{ipc::ScopeObject, utils::acl::Value, AppHandle, Manager, Runtime};
use crate::{scope_entry::EntryRaw, Error};
pub use crate::scope_entry::Application;
#[derive(Debug)]
pub enum Entry {
Url(glob::Pattern),
Path(Option<PathBuf>),
Url {
url: glob::Pattern,
app: Application,
},
Path {
path: Option<PathBuf>,
app: Application,
},
}
impl ScopeObject for Entry {
type Error = Error;
fn deserialize<R: Runtime>(
app: &AppHandle<R>,
app_handle: &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(
glob::Pattern::new(&url)
EntryRaw::Url { url, app } => Entry::Url {
url: glob::Pattern::new(&url)
.map_err(|e| serde::de::Error::custom(e.to_string()))?,
),
EntryRaw::Path { path } => {
let path = match app.path().parse(path) {
app,
},
EntryRaw::Path { path, app } => {
let path = match app_handle.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)
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)]
pub struct Scope<'a, R: Runtime, M: Manager<R>> {
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 {
let denied = self.denied.iter().any(|entry| match entry.as_ref() {
Entry::Url(url_pattern) => url_pattern.matches(url),
Entry::Path { .. } => false,
});
pub fn is_url_allowed(&self, url: &str, with: Option<&str>) -> bool {
let denied = self.denied.iter().any(|e| e.matches_url(url, with));
if denied {
false
} else {
self.allowed.iter().any(|entry| match entry.as_ref() {
Entry::Url(url_pattern) => url_pattern.matches(url),
Entry::Path { .. } => false,
})
self.allowed.iter().any(|e| e.matches_url(url, with))
}
}
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(
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(),
allow: self.allowed.iter().filter_map(|e| e.path()).collect(),
deny: self.denied.iter().filter_map(|e| e.path()).collect(),
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;
#[derive(Deserialize, Debug)]
pub enum Application {
Default,
Enable(bool),
App(String),
}
impl Default for Application {
fn default() -> Self {
Self::Default
}
}
#[derive(Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
#[allow(unused)]
pub(crate) enum EntryRaw {
Url { url: String },
Path { path: PathBuf },
Url {
url: String,
#[serde(default)]
app: Application,
},
Path {
path: PathBuf,
#[serde(default)]
app: Application,
},
}

Loading…
Cancel
Save