feat: update to tauri beta, add permissions (#862)

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Co-authored-by: Lucas Nogueira <lucas@crabnebula.dev>
This commit is contained in:
Tillmann
2024-02-04 03:14:41 +09:00
committed by GitHub
parent 506ce4835b
commit d198c01486
387 changed files with 21883 additions and 943 deletions
+176 -17
View File
@@ -6,7 +6,9 @@
use serde::{Deserialize, Serialize, Serializer};
use serde_repr::{Deserialize_repr, Serialize_repr};
use tauri::{
ipc::{CommandScope, GlobalScope},
path::{BaseDirectory, SafePathBuf},
utils::config::FsScope,
AppHandle, Manager, Resource, ResourceId, Runtime,
};
@@ -18,7 +20,7 @@ use std::{
time::{SystemTime, UNIX_EPOCH},
};
use crate::{Error, FsExt};
use crate::{scope::Entry, Error, FsExt};
#[derive(Debug, thiserror::Error)]
pub enum CommandError {
@@ -71,10 +73,18 @@ pub struct BaseOptions {
#[tauri::command]
pub fn create<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<ResourceId> {
let resolved_path = resolve_path(&app, path, options.and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.and_then(|o| o.base_dir),
)?;
let file = File::create(&resolved_path).map_err(|e| {
format!(
"failed to create file at path: {} with error: {e}",
@@ -113,10 +123,18 @@ fn default_true() -> bool {
#[tauri::command]
pub fn open<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<OpenOptions>,
) -> CommandResult<ResourceId> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base.base_dir),
)?;
let mut opts = std::fs::OpenOptions::new();
// default to read-only
@@ -166,17 +184,23 @@ pub struct CopyFileOptions {
#[tauri::command]
pub fn copy_file<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
from_path: SafePathBuf,
to_path: SafePathBuf,
options: Option<CopyFileOptions>,
) -> CommandResult<()> {
let resolved_from_path = resolve_path(
&app,
&global_scope,
&command_scope,
from_path,
options.as_ref().and_then(|o| o.from_path_base_dir),
)?;
let resolved_to_path = resolve_path(
&app,
&global_scope,
&command_scope,
to_path,
options.as_ref().and_then(|o| o.to_path_base_dir),
)?;
@@ -202,10 +226,18 @@ pub struct MkdirOptions {
#[tauri::command]
pub fn mkdir<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<MkdirOptions>,
) -> CommandResult<()> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base.base_dir),
)?;
let mut builder = std::fs::DirBuilder::new();
builder.recursive(options.as_ref().and_then(|o| o.recursive).unwrap_or(false));
@@ -261,10 +293,18 @@ fn read_dir_inner<P: AsRef<Path>>(path: P) -> crate::Result<Vec<DirEntry>> {
#[tauri::command]
pub fn read_dir<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<Vec<DirEntry>> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
read_dir_inner(&resolved_path)
.map_err(|e| {
@@ -292,10 +332,18 @@ pub fn read<R: Runtime>(
#[tauri::command]
pub fn read_file<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<Vec<u8>> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
std::fs::read(&resolved_path)
.map_err(|e| {
format!(
@@ -309,10 +357,18 @@ pub fn read_file<R: Runtime>(
#[tauri::command]
pub fn read_text_file<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<String> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
std::fs::read_to_string(&resolved_path)
.map_err(|e| {
format!(
@@ -326,12 +382,20 @@ pub fn read_text_file<R: Runtime>(
#[tauri::command]
pub fn read_text_file_lines<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<ResourceId> {
use std::io::BufRead;
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
let file = File::open(&resolved_path).map_err(|e| {
format!(
@@ -374,10 +438,18 @@ pub struct RemoveOptions {
#[tauri::command]
pub fn remove<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<RemoveOptions>,
) -> CommandResult<()> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base.base_dir),
)?;
let metadata = std::fs::symlink_metadata(&resolved_path).map_err(|e| {
format!(
@@ -434,17 +506,23 @@ pub struct RenameOptions {
#[tauri::command]
pub fn rename<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
old_path: SafePathBuf,
new_path: SafePathBuf,
options: Option<RenameOptions>,
) -> CommandResult<()> {
let resolved_old_path = resolve_path(
&app,
&global_scope,
&command_scope,
old_path,
options.as_ref().and_then(|o| o.old_path_base_dir),
)?;
let resolved_new_path = resolve_path(
&app,
&global_scope,
&command_scope,
new_path,
options.as_ref().and_then(|o| o.new_path_base_dir),
)?;
@@ -490,10 +568,18 @@ pub fn seek<R: Runtime>(
#[tauri::command]
pub fn stat<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<FileInfo> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
let metadata = std::fs::metadata(&resolved_path).map_err(|e| {
format!(
"failed to get metadata of path: {} with error: {e}",
@@ -506,10 +592,18 @@ pub fn stat<R: Runtime>(
#[tauri::command]
pub fn lstat<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<FileInfo> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
let metadata = std::fs::symlink_metadata(&resolved_path).map_err(|e| {
format!(
"failed to get metadata of path: {} with error: {e}",
@@ -530,11 +624,19 @@ pub fn fstat<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> CommandResult<Fi
#[tauri::command]
pub fn truncate<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
len: Option<u64>,
options: Option<BaseOptions>,
) -> CommandResult<()> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
let f = std::fs::OpenOptions::new()
.write(true)
.open(&resolved_path)
@@ -599,11 +701,19 @@ fn default_create_value() -> bool {
fn write_file_inner<R: Runtime>(
app: AppHandle<R>,
global_scope: &GlobalScope<'_, Entry>,
command_scope: &CommandScope<'_, Entry>,
path: SafePathBuf,
data: &[u8],
options: Option<WriteFileOptions>,
) -> CommandResult<()> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
let resolved_path = resolve_path(
&app,
global_scope,
command_scope,
path,
options.as_ref().and_then(|o| o.base.base_dir),
)?;
let mut opts = std::fs::OpenOptions::new();
// defaults
@@ -644,35 +754,56 @@ fn write_file_inner<R: Runtime>(
#[tauri::command]
pub fn write_file<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
data: Vec<u8>,
options: Option<WriteFileOptions>,
) -> CommandResult<()> {
write_file_inner(app, path, &data, options)
write_file_inner(app, &global_scope, &command_scope, path, &data, options)
}
#[tauri::command]
pub fn write_text_file<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
data: String,
options: Option<WriteFileOptions>,
) -> CommandResult<()> {
write_file_inner(app, path, data.as_bytes(), options)
write_file_inner(
app,
&global_scope,
&command_scope,
path,
data.as_bytes(),
options,
)
}
#[tauri::command]
pub fn exists<R: Runtime>(
app: AppHandle<R>,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
path: SafePathBuf,
options: Option<BaseOptions>,
) -> CommandResult<bool> {
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
let resolved_path = resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.as_ref().and_then(|o| o.base_dir),
)?;
Ok(resolved_path.exists())
}
pub fn resolve_path<R: Runtime>(
app: &AppHandle<R>,
global_scope: &GlobalScope<'_, Entry>,
command_scope: &CommandScope<'_, Entry>,
path: SafePathBuf,
base_dir: Option<BaseDirectory>,
) -> CommandResult<PathBuf> {
@@ -684,7 +815,35 @@ pub fn resolve_path<R: Runtime>(
} else {
path.as_ref().to_path_buf()
};
if app.fs_scope().is_allowed(&path) {
let scope = tauri::scope::fs::Scope::new(
app,
&FsScope::Scope {
allow: app
.fs_scope()
.allowed
.lock()
.unwrap()
.clone()
.into_iter()
.chain(global_scope.allows().iter().map(|e| e.path.clone()))
.chain(command_scope.allows().iter().map(|e| e.path.clone()))
.collect(),
deny: app
.fs_scope()
.denied
.lock()
.unwrap()
.clone()
.into_iter()
.chain(global_scope.denies().iter().map(|e| e.path.clone()))
.chain(command_scope.denies().iter().map(|e| e.path.clone()))
.collect(),
require_literal_leading_dot: None,
},
)?;
if scope.is_allowed(&path) {
Ok(path)
} else {
Err(CommandError::Plugin(Error::PathForbidden(path)))
+10 -3
View File
@@ -3,9 +3,16 @@
// SPDX-License-Identifier: MIT
use serde::Deserialize;
use tauri::utils::config::FsScope;
#[derive(Debug, Deserialize)]
#[derive(Deserialize)]
pub struct Config {
pub scope: FsScope,
/// Whether or not paths that contain components that start with a `.`
/// will require that `.` appears literally in the pattern; `*`, `?`, `**`,
/// or `[...]` will not match. This is useful because such files are
/// conventionally considered hidden on Unix systems and it might be
/// desirable to skip them when listing files.
///
/// Defaults to `true` on Unix systems and `false` on Windows
// dotfiles are not supposed to be exposed by default on unix
pub require_literal_leading_dot: Option<bool>,
}
+4
View File
@@ -8,6 +8,10 @@ use serde::{Serialize, Serializer};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Tauri(#[from] tauri::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("forbidden path: {0}")]
+38 -18
View File
@@ -11,21 +11,25 @@
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
)]
use std::path::PathBuf;
use serde::Deserialize;
use tauri::{
ipc::ScopeObject,
plugin::{Builder as PluginBuilder, TauriPlugin},
scope::fs::Scope,
utils::config::FsScope,
FileDropEvent, Manager, RunEvent, Runtime, WindowEvent,
utils::acl::Value,
AppHandle, FileDropEvent, Manager, RunEvent, Runtime, WindowEvent,
};
mod commands;
mod config;
mod error;
mod scope;
#[cfg(feature = "watch")]
mod watcher;
pub use config::Config;
pub use error::Error;
pub use scope::{Event as ScopeEvent, Scope};
type Result<T> = std::result::Result<T, Error>;
@@ -44,8 +48,27 @@ impl<R: Runtime, T: Manager<R>> FsExt<R> for T {
}
}
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
PluginBuilder::<R, Option<Config>>::new("fs")
impl ScopeObject for scope::Entry {
type Error = Error;
fn deserialize<R: Runtime>(
app: &AppHandle<R>,
raw: Value,
) -> std::result::Result<Self, Self::Error> {
#[derive(Deserialize)]
struct EntryRaw {
path: PathBuf,
}
let entry = serde_json::from_value::<EntryRaw>(raw.into())?;
Ok(Self {
path: app.path().parse(entry.path)?,
})
}
}
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
PluginBuilder::<R, Option<config::Config>>::new("fs")
.js_init_script(include_str!("api-iife.js").to_string())
.invoke_handler(tauri::generate_handler![
commands::create,
@@ -76,16 +99,13 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
#[cfg(feature = "watch")]
watcher::unwatch
])
.setup(|app: &tauri::AppHandle<R>, api| {
let default_scope = FsScope::default();
app.manage(Scope::new(
app,
api.config()
.as_ref()
.map(|c| &c.scope)
.unwrap_or(&default_scope),
)?);
.setup(|app, api| {
let mut scope = Scope::default();
scope.require_literal_leading_dot = api
.config()
.as_ref()
.and_then(|c| c.require_literal_leading_dot);
app.manage(scope);
Ok(())
})
.on_event(|app, event| {
@@ -98,9 +118,9 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
let scope = app.fs_scope();
for path in paths {
if path.is_file() {
let _ = scope.allow_file(path);
scope.allow_file(path);
} else {
let _ = scope.allow_directory(path, false);
scope.allow_directory(path, false);
}
}
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{
atomic::{AtomicU32, Ordering},
Mutex,
},
};
#[derive(Debug, schemars::JsonSchema)]
pub struct Entry {
pub path: PathBuf,
}
pub type EventId = u32;
type EventListener = Box<dyn Fn(&Event) + Send>;
/// Scope change event.
#[derive(Debug, Clone)]
pub enum Event {
/// A path has been allowed.
PathAllowed(PathBuf),
/// A path has been forbidden.
PathForbidden(PathBuf),
}
#[derive(Default)]
pub struct Scope {
pub(crate) allowed: Mutex<Vec<PathBuf>>,
pub(crate) denied: Mutex<Vec<PathBuf>>,
event_listeners: Mutex<HashMap<EventId, EventListener>>,
next_event_id: AtomicU32,
pub(crate) require_literal_leading_dot: Option<bool>,
}
impl Scope {
/// Extend the allowed patterns with the given directory.
///
/// After this function has been called, the frontend will be able to use the Tauri API to read
/// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too.
pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref();
let mut allowed = self.allowed.lock().unwrap();
allowed.push(path.to_path_buf());
allowed.push(path.join(if recursive { "**" } else { "*" }));
self.emit(Event::PathAllowed(path.to_path_buf()));
}
/// Extend the allowed patterns with the given file path.
///
/// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
pub fn allow_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref();
self.allowed.lock().unwrap().push(path.to_path_buf());
self.emit(Event::PathAllowed(path.to_path_buf()));
}
/// Set the given directory path to be forbidden by this scope.
///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref();
let mut denied = self.denied.lock().unwrap();
denied.push(path.to_path_buf());
denied.push(path.join(if recursive { "**" } else { "*" }));
self.emit(Event::PathForbidden(path.to_path_buf()));
}
/// Set the given file path to be forbidden by this scope.
///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref();
self.denied.lock().unwrap().push(path.to_path_buf());
self.emit(Event::PathForbidden(path.to_path_buf()));
}
/// List of allowed paths.
pub fn allowed(&self) -> Vec<PathBuf> {
self.allowed.lock().unwrap().clone()
}
/// List of forbidden paths.
pub fn forbidden(&self) -> Vec<PathBuf> {
self.denied.lock().unwrap().clone()
}
fn next_event_id(&self) -> u32 {
self.next_event_id.fetch_add(1, Ordering::Relaxed)
}
fn emit(&self, event: Event) {
let listeners = self.event_listeners.lock().unwrap();
let handlers = listeners.values();
for listener in handlers {
listener(&event);
}
}
/// Listen to an event on this scope.
pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> EventId {
let id = self.next_event_id();
self.event_listeners.lock().unwrap().insert(id, Box::new(f));
id
}
}
+14 -3
View File
@@ -6,7 +6,7 @@ use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, FileIdMap};
use serde::Deserialize;
use tauri::{
ipc::Channel,
ipc::{Channel, CommandScope, GlobalScope},
path::{BaseDirectory, SafePathBuf},
AppHandle, Manager, Resource, ResourceId, Runtime,
};
@@ -21,7 +21,10 @@ use std::{
time::Duration,
};
use crate::commands::{resolve_path, CommandResult};
use crate::{
commands::{resolve_path, CommandResult},
scope::Entry,
};
struct InnerWatcher {
pub kind: WatcherKind,
@@ -83,10 +86,18 @@ pub async fn watch<R: Runtime>(
paths: Vec<SafePathBuf>,
options: WatchOptions,
on_event: Channel,
global_scope: GlobalScope<'_, Entry>,
command_scope: CommandScope<'_, Entry>,
) -> CommandResult<ResourceId> {
let mut resolved_paths = Vec::with_capacity(paths.capacity());
for path in paths {
resolved_paths.push(resolve_path(&app, path, options.dir)?);
resolved_paths.push(resolve_path(
&app,
&global_scope,
&command_scope,
path,
options.dir,
)?);
}
let mode = if options.recursive {