split into openUrl and openPath

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

1
Cargo.lock generated

@ -6574,7 +6574,6 @@ dependencies = [
"tauri",
"tauri-plugin",
"thiserror 2.0.3",
"url",
"windows 0.58.0",
"zbus",
]

@ -34,7 +34,6 @@ serde_json = { workspace = true }
tauri = { workspace = true }
thiserror = { workspace = true }
open = { version = "5", features = ["shellexecute-on-windows"] }
url = { workspace = true }
glob = { workspace = true }
[target."cfg(windows)".dependencies]

@ -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.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{path:n})},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.openPath=async function(n,_){await e("plugin:opener|open_path",{path:n,with:_})},n.openUrl=async function(n,_){await e("plugin:opener|open_url",{url:n,with:_})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{path:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}

@ -51,7 +51,7 @@ fn _f() {
};
}
const COMMANDS: &[&str] = &["open", "reveal_item_in_dir"];
const COMMANDS: &[&str] = &["open_url", "open_path", "reveal_item_in_dir"];
fn main() {
tauri_plugin::Builder::new(COMMANDS)

@ -34,31 +34,57 @@ export type Program =
| 'wslview'
/**
* Opens a path or URL with the system's default app,
* or the one specified with `openWith`.
*
* The `openWith` value must be one of `firefox`, `google chrome`, `chromium` `safari`,
* `open`, `start`, `xdg-open`, `gio`, `gnome-open`, `kde-open` or `wslview`.
* Opens a url with the system's default app, or the one specified with {@linkcode openWith}.
*
* @example
* ```typescript
* import { open } from '@tauri-apps/plugin-opener';
* import { openUrl } from '@tauri-apps/plugin-opener';
*
* // opens the given URL on the default browser:
* await open('https://github.com/tauri-apps/tauri');
* await openUrl('https://github.com/tauri-apps/tauri');
* // opens the given URL using `firefox`:
* await open('https://github.com/tauri-apps/tauri', 'firefox');
* await openUrl('https://github.com/tauri-apps/tauri', 'firefox');
* ```
*
* @param url The URL to open.
* @param openWith The app to open the URL with.
* Must be one of `firefox`, `google chrome`, `chromium` `safari`, `open`, `start`, `xdg-open`, `gio`, `gnome-open`, `kde-open` or `wslview`.
* If not specified, defaults to the system default application for the specified url type.
*
* @since 2.0.0
*/
export async function openUrl(url: string, openWith?: Program): Promise<void> {
await invoke('plugin:opener|open_url', {
url,
with: openWith
})
}
/**
* Opens a path with the system's default app, or the one specified with {@linkcode openWith}.
*
* @example
* ```typescript
* import { openPath } from '@tauri-apps/plugin-opener';
*
* // opens a file using the default program:
* await open('/path/to/file');
* await openPath('/path/to/file');
* // opens a file using `start` command on Windows.
* await openPath('C:/path/to/file', 'start');
* ```
*
* @param path The path or URL to open.
* @param openWith The app to open the file or URL with.
* Defaults to the system default application for the specified path type.
* @param path The path to open.
* @param openWith The app to open the path with.
* Must be one of `firefox`, `google chrome`, `chromium` `safari`, `open`, `start`, `xdg-open`, `gio`, `gnome-open`, `kde-open` or `wslview`.
* If not specified, defaults to the system default application for the specified path type.
*
* @since 2.0.0
*/
export async function open(path: string, openWith?: Program): Promise<void> {
await invoke('plugin:opener|open', {
export async function openPath(
path: string,
openWith?: Program
): Promise<void> {
await invoke('plugin:opener|open_path', {
path,
with: openWith
})

@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-open-path"
description = "Enables the open_path command without any pre-configured scope."
commands.allow = ["open_path"]
[[permission]]
identifier = "deny-open-path"
description = "Denies the open_path command without any pre-configured scope."
commands.deny = ["open_path"]

@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-open-url"
description = "Enables the open_url command without any pre-configured scope."
commands.allow = ["open_url"]
[[permission]]
identifier = "deny-open-url"
description = "Denies the open_url command without any pre-configured scope."
commands.deny = ["open_url"]

@ -3,7 +3,7 @@
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-open-url`
- `allow-reveal-item-in-dir`
- `allow-default-urls`
@ -58,6 +58,58 @@ Denies the open command without any pre-configured scope.
<tr>
<td>
`opener:allow-open-path`
</td>
<td>
Enables the open_path command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`opener:deny-open-path`
</td>
<td>
Denies the open_path command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`opener:allow-open-url`
</td>
<td>
Enables the open_url command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`opener:deny-open-url`
</td>
<td>
Denies the open_url command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`opener:allow-reveal-item-in-dir`
</td>

@ -3,4 +3,8 @@
[default]
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"]
permissions = [
"allow-open-url",
"allow-reveal-item-in-dir",
"allow-default-urls",
]

@ -309,6 +309,26 @@
"type": "string",
"const": "deny-open"
},
{
"description": "Enables the open_path command without any pre-configured scope.",
"type": "string",
"const": "allow-open-path"
},
{
"description": "Denies the open_path command without any pre-configured scope.",
"type": "string",
"const": "deny-open-path"
},
{
"description": "Enables the open_url command without any pre-configured scope.",
"type": "string",
"const": "allow-open-url"
},
{
"description": "Denies the open_url command without any pre-configured scope.",
"type": "string",
"const": "deny-open-url"
},
{
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use tauri::{
ipc::{CommandScope, GlobalScope},
@ -12,7 +12,7 @@ use tauri::{
use crate::{open::Program, scope::Scope, Error};
#[tauri::command]
pub async fn open<R: Runtime>(
pub async fn open_url<R: Runtime>(
app: AppHandle<R>,
command_scope: CommandScope<crate::scope::Entry>,
global_scope: GlobalScope<crate::scope::Entry>,
@ -33,10 +33,39 @@ pub async fn open<R: Runtime>(
.collect(),
);
if scope.is_allowed(&path)? {
crate::open(path, with)
if scope.is_url_allowed(&path) {
crate::open_url(path, with)
} else {
Err(Error::NotAllowed(path))
Err(Error::ForbiddenUrl(path))
}
}
#[tauri::command]
pub async fn open_path<R: Runtime>(
app: AppHandle<R>,
command_scope: CommandScope<crate::scope::Entry>,
global_scope: GlobalScope<crate::scope::Entry>,
path: String,
with: Option<Program>,
) -> crate::Result<()> {
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_path_allowed(Path::new(&path))? {
crate::open_path(path, with)
} else {
Err(Error::ForbiddenPath(path))
}
}

@ -20,8 +20,10 @@ pub enum Error {
Json(#[from] serde_json::Error),
#[error("unknown program {0}")]
UnknownProgramName(String),
#[error("Not allowed to open forbidden path or url: {0}")]
NotAllowed(String),
#[error("Not allowed to open forbidden path: {0}")]
ForbiddenPath(String),
#[error("Not allowed to open forbidden url: {0}")]
ForbiddenUrl(String),
#[error("API not supported on the current platform")]
UnsupportedPlatform,
#[error(transparent)]

@ -26,7 +26,7 @@ mod scope_entry;
pub use error::Error;
type Result<T> = std::result::Result<T, Error>;
pub use open::{open, Program};
pub use open::{open_path, open_url, Program};
pub use reveal_item_in_dir::reveal_item_in_dir;
pub struct Opener<R: Runtime> {
@ -37,15 +37,29 @@ pub struct Opener<R: Runtime> {
}
impl<R: Runtime> Opener<R> {
/// Open a url with a default or specific browser opening 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)
}
/// Open a url with a default or specific browser opening program.
#[cfg(mobile)]
pub fn open_url(&self, url: impl Into<String>, _with: Option<open::Program>) -> 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.
#[cfg(desktop)]
pub fn open(&self, path: impl Into<String>, with: Option<open::Program>) -> Result<()> {
pub fn open_path(&self, path: impl Into<String>, with: Option<open::Program>) -> Result<()> {
open::open(path.into(), with).map_err(Into::into)
}
/// Open a (url) path with a default or specific browser opening program.
#[cfg(mobile)]
pub fn open(&self, path: impl Into<String>, _with: Option<open::Program>) -> Result<()> {
pub fn open_path(&self, path: impl Into<String>, _with: Option<open::Program>) -> Result<()> {
self.mobile_plugin_handle
.run_mobile_plugin("open", path.into())
.map_err(Into::into)
@ -85,7 +99,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
Ok(())
})
.invoke_handler(tauri::generate_handler![
commands::open,
commands::open_url,
commands::open_path,
commands::reveal_item_in_dir
])
.build()

@ -6,7 +6,7 @@
use serde::{Deserialize, Deserializer};
use std::{fmt::Display, str::FromStr};
use std::{ffi::OsStr, fmt::Display, path::Path, str::FromStr};
/// Program to use on the [`open()`] call.
#[derive(Debug)]
@ -123,40 +123,44 @@ impl Program {
}
}
/// Opens path or URL with the program specified in `with`, or system default if `None`.
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),
}
.map_err(Into::into)
}
/// Opens URL with the program specified in `with`, or system default if `None`.
///
/// # Examples
///
/// The path will be matched against the shell open validation regex, defaulting to `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`.
/// A custom validation regex may be supplied in the config in `plugins > shell > scope > open`.
/// ```rust,no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// // open the given URL on the system default browser
/// tauri_plugin_opener::open_url("https://github.com/tauri-apps/tauri", None)?;
/// Ok(())
/// });
/// ```
pub fn open_url<P: AsRef<str>>(url: P, with: Option<Program>) -> crate::Result<()> {
let url = url.as_ref();
open(url, with)
}
/// Opens path with the program specified in `with`, or system default if `None`.
///
/// # Examples
///
/// ```rust,no_run
/// use tauri_plugin_shell::ShellExt;
/// tauri::Builder::default()
/// .setup(|app| {
/// // open the given URL on the system default browser
/// app.shell().open("https://github.com/tauri-apps/tauri", None)?;
/// tauri_plugin_opener::open_path("/path/to/file", None)?;
/// Ok(())
/// });
/// ```
pub fn open<P: AsRef<str>>(path: P, with: Option<Program>) -> crate::Result<()> {
pub fn open_path<P: AsRef<Path>>(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(),
// });
// }
// }
// 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`.
match with.map(Program::name) {
Some(program) => ::open::with_detached(path, program),
None => ::open::that_detached(path),
}
.map_err(Into::into)
open(path, with)
}

@ -2,12 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{marker::PhantomData, path::PathBuf, sync::Arc};
use std::{
marker::PhantomData,
path::{Path, PathBuf},
sync::Arc,
};
use tauri::{ipc::ScopeObject, utils::acl::Value, AppHandle, Manager, Runtime};
use url::Url;
use crate::{scope_entry::EntryRaw, Error};
#[derive(Debug)]
@ -70,30 +72,22 @@ impl<'a, R: Runtime, M: Manager<R>> Scope<'a, R, M> {
}
}
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 {
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.as_str()),
Entry::Url(url_pattern) => url_pattern.matches(url),
Entry::Path { .. } => false,
});
if denied {
false
} else {
self.allowed.iter().any(|entry| match entry.as_ref() {
Entry::Url(url_pattern) => url_pattern.matches(url.as_str()),
Entry::Url(url_pattern) => url_pattern.matches(url),
Entry::Path { .. } => false,
})
}
}
pub fn is_path_allowed(&self, path: &str) -> crate::Result<bool> {
pub fn is_path_allowed(&self, path: &Path) -> crate::Result<bool> {
let fs_scope = tauri::fs::Scope::new(
self.manager,
&tauri::utils::config::FsScope::Scope {

Loading…
Cancel
Save