Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/shell-schema-required-sidcar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"shell": "patch"
---

Fix the plugin schema requiring to set `sidecar` property when it is in fact optional.

18 changes: 10 additions & 8 deletions plugins/fs/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ enum FsScopeEntry {
},
}

// Ensure scope entry is kept up to date
impl From<FsScopeEntry> for scope::EntryRaw {
fn from(value: FsScopeEntry) -> Self {
match value {
FsScopeEntry::Value(path) => scope::EntryRaw::Value(path),
FsScopeEntry::Object { path } => scope::EntryRaw::Object { path },
}
}
// Ensure `FsScopeEntry` and `scope::EntryRaw` is kept in sync
fn _f() {
match scope::EntryRaw::Value(PathBuf::new()) {
scope::EntryRaw::Value(path) => FsScopeEntry::Value(path),
scope::EntryRaw::Object { path } => FsScopeEntry::Object { path },
};
match FsScopeEntry::Value(PathBuf::new()) {
FsScopeEntry::Value(path) => scope::EntryRaw::Value(path),
FsScopeEntry::Object { path } => scope::EntryRaw::Object { path },
};
}

const BASE_DIR_VARS: &[&str] = &[
Expand Down
3 changes: 1 addition & 2 deletions plugins/fs/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ use std::{

use serde::Deserialize;

#[doc(hidden)]
#[derive(Deserialize)]
#[serde(untagged)]
pub enum EntryRaw {
pub(crate) enum EntryRaw {
Value(PathBuf),
Object { path: PathBuf },
}
Expand Down
27 changes: 10 additions & 17 deletions plugins/http/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,16 @@ enum HttpScopeEntry {
},
}

// Ensure scope entry is kept up to date
impl From<HttpScopeEntry> for scope::Entry {
fn from(value: HttpScopeEntry) -> Self {
let url = match value {
HttpScopeEntry::Value(url) => url,
HttpScopeEntry::Object { url } => url,
};

scope::Entry {
url: urlpattern::UrlPattern::parse(
urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(&url, None)
.unwrap(),
Default::default(),
)
.unwrap(),
}
}
// Ensure `HttpScopeEntry` and `scope::EntryRaw` is kept in sync
fn _f() {
match scope::EntryRaw::Value(String::new()) {
scope::EntryRaw::Value(url) => HttpScopeEntry::Value(url),
scope::EntryRaw::Object { url } => HttpScopeEntry::Object { url },
};
match HttpScopeEntry::Value(String::new()) {
HttpScopeEntry::Value(url) => scope::EntryRaw::Value(url),
HttpScopeEntry::Object { url } => scope::EntryRaw::Object { url },
};
}

fn main() {
Expand Down
14 changes: 7 additions & 7 deletions plugins/http/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ fn parse_url_pattern(s: &str) -> Result<UrlPattern, urlpattern::quirks::Error> {
UrlPattern::parse(init, Default::default())
}

#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum EntryRaw {
Value(String),
Object { url: String },
}

impl<'de> Deserialize<'de> for Entry {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum EntryRaw {
Value(String),
Object { url: String },
}

EntryRaw::deserialize(deserializer).and_then(|raw| {
let url = match raw {
EntryRaw::Value(url) => url,
Expand Down
1 change: 0 additions & 1 deletion plugins/shell/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ serde = { workspace = true }

[dependencies]
serde = { workspace = true }
schemars = { workspace = true }
serde_json = { workspace = true }
tauri = { workspace = true }
tokio = { version = "1", features = ["time"] }
Expand Down
137 changes: 136 additions & 1 deletion plugins/shell/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,150 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::path::PathBuf;

use schemars::JsonSchema;

#[path = "src/scope_entry.rs"]
mod scope_entry;

/// A command argument allowed to be executed by the webview API.
#[derive(Debug, PartialEq, Eq, Clone, Hash, schemars::JsonSchema)]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum ShellScopeEntryAllowedArg {
/// A non-configurable argument that is passed to the command in the order it was specified.
Fixed(String),

/// A variable that is set while calling the command from the webview API.
///
Var {
/// [regex] validator to require passed values to conform to an expected input.
///
/// This will require the argument value passed to this variable to match the `validator` regex
/// before it will be executed.
///
/// The regex string is by default surrounded by `^...$` to match the full string.
/// For example the `https?://\w+` regex would be registered as `^https?://\w+$`.
///
/// [regex]: <https://docs.rs/regex/latest/regex/#syntax>
validator: String,

/// Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.
///
/// This means the regex will not match on the entire string by default, which might
/// be exploited if your regex allow unexpected input to be considered valid.
/// When using this option, make sure your regex is correct.
#[serde(default)]
raw: bool,
},
}

/// A set of command arguments allowed to be executed by the webview API.
///
/// A value of `true` will allow any arguments to be passed to the command. `false` will disable all
/// arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to
/// be passed to the attached command configuration.
#[derive(Debug, PartialEq, Eq, Clone, Hash, JsonSchema)]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum ShellScopeEntryAllowedArgs {
/// Use a simple boolean to allow all or disable all arguments to this command configuration.
Flag(bool),

/// A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.
List(Vec<ShellScopeEntryAllowedArg>),
}

impl Default for ShellScopeEntryAllowedArgs {
fn default() -> Self {
Self::Flag(false)
}
}

/// Shell scope entry.
#[derive(JsonSchema)]
#[allow(unused)]
pub(crate) struct ShellScopeEntry {
/// The name for this allowed shell command configuration.
///
/// This name will be used inside of the webview API to call this command along with
/// any specified arguments.
name: String,
/// The command name.
/// It can start with a variable that resolves to a system base directory.
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
// use default just so the schema doesn't flag it as required
#[serde(rename = "cmd")]
command: Option<PathBuf>,
/// The allowed arguments for the command execution.
#[serde(default)]
args: ShellScopeEntryAllowedArgs,
/// If this command is a sidecar command.
#[serde(default)]
sidecar: bool,
}

// Ensure `ShellScopeEntry` and `scope_entry::EntryRaw`
// and `ShellScopeEntryAllowedArg` and `ShellAllowedArg`
// and `ShellScopeEntryAllowedArgs` and `ShellAllowedArgs`
// are kept in sync
#[allow(clippy::unnecessary_operation)]
fn _f() {
let v = scope_entry::EntryRaw {
name: String::new(),
command: None,
args: scope_entry::ShellAllowedArgs::default(),
sidecar: false,
};

ShellScopeEntry {
name: v.name,
command: v.command,
args: match v.args {
scope_entry::ShellAllowedArgs::Flag(flag) => ShellScopeEntryAllowedArgs::Flag(flag),
scope_entry::ShellAllowedArgs::List(vec) => ShellScopeEntryAllowedArgs::List(
vec.into_iter()
.map(|s| match s {
scope_entry::ShellAllowedArg::Fixed(fixed) => {
ShellScopeEntryAllowedArg::Fixed(fixed)
}
scope_entry::ShellAllowedArg::Var { validator, raw } => {
ShellScopeEntryAllowedArg::Var { validator, raw }
}
})
.collect(),
),
},
sidecar: v.sidecar,
};

match ShellScopeEntryAllowedArgs::Flag(false) {
ShellScopeEntryAllowedArgs::Flag(flag) => scope_entry::ShellAllowedArgs::Flag(flag),
ShellScopeEntryAllowedArgs::List(vec) => scope_entry::ShellAllowedArgs::List(
vec.into_iter()
.map(|s| match s {
ShellScopeEntryAllowedArg::Fixed(fixed) => {
scope_entry::ShellAllowedArg::Fixed(fixed)
}
ShellScopeEntryAllowedArg::Var { validator, raw } => {
scope_entry::ShellAllowedArg::Var { validator, raw }
}
})
.collect(),
),
};
}

const COMMANDS: &[&str] = &["execute", "spawn", "stdin_write", "kill", "open"];

fn main() {
tauri_plugin::Builder::new(COMMANDS)
.global_api_script_path("./api-iife.js")
.global_scope_schema(schemars::schema_for!(scope_entry::Entry))
.global_scope_schema(schemars::schema_for!(ShellScopeEntry))
.android_path("android")
.ios_path("ios")
.build();
Expand Down
81 changes: 18 additions & 63 deletions plugins/shell/src/scope_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,31 @@ use serde::{de::Error as DeError, Deserialize, Deserializer};
use std::path::PathBuf;

/// A command allowed to be executed by the webview API.
#[derive(Debug, Clone, PartialEq, Eq, Hash, schemars::JsonSchema)]
pub struct Entry {
/// The name for this allowed shell command configuration.
///
/// This name will be used inside of the webview API to call this command along with
/// any specified arguments.
pub name: String,
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct Entry {
pub(crate) name: String,
pub(crate) command: PathBuf,
pub(crate) args: ShellAllowedArgs,
pub(crate) sidecar: bool,
}

/// The command name.
/// It can start with a variable that resolves to a system base directory.
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
// use default just so the schema doesn't flag it as required
#[derive(Deserialize)]
pub(crate) struct EntryRaw {
pub(crate) name: String,
#[serde(rename = "cmd")]
pub command: PathBuf,

/// The allowed arguments for the command execution.
pub args: ShellAllowedArgs,

/// If this command is a sidecar command.
pub sidecar: bool,
pub(crate) command: Option<PathBuf>,
#[serde(default)]
pub(crate) args: ShellAllowedArgs,
#[serde(default)]
pub(crate) sidecar: bool,
}

impl<'de> Deserialize<'de> for Entry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct InnerEntry {
name: String,
#[serde(rename = "cmd")]
command: Option<PathBuf>,
#[serde(default)]
args: ShellAllowedArgs,
#[serde(default)]
sidecar: bool,
}

let config = InnerEntry::deserialize(deserializer)?;
let config = EntryRaw::deserialize(deserializer)?;

if !config.sidecar && config.command.is_none() {
return Err(DeError::custom(
Expand All @@ -65,19 +48,11 @@ impl<'de> Deserialize<'de> for Entry {
}
}

/// A set of command arguments allowed to be executed by the webview API.
///
/// A value of `true` will allow any arguments to be passed to the command. `false` will disable all
/// arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to
/// be passed to the attached command configuration.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, schemars::JsonSchema)]
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum ShellAllowedArgs {
/// Use a simple boolean to allow all or disable all arguments to this command configuration.
Flag(bool),

/// A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.
List(Vec<ShellAllowedArg>),
}

Expand All @@ -87,33 +62,13 @@ impl Default for ShellAllowedArgs {
}
}

/// A command argument allowed to be executed by the webview API.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, schemars::JsonSchema)]
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum ShellAllowedArg {
/// A non-configurable argument that is passed to the command in the order it was specified.
Fixed(String),

/// A variable that is set while calling the command from the webview API.
///
Var {
/// [regex] validator to require passed values to conform to an expected input.
///
/// This will require the argument value passed to this variable to match the `validator` regex
/// before it will be executed.
///
/// The regex string is by default surrounded by `^...$` to match the full string.
/// For example the `https?://\w+` regex would be registered as `^https?://\w+$`.
///
/// [regex]: <https://docs.rs/regex/latest/regex/#syntax>
validator: String,

/// Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.
///
/// This means the regex will not match on the entire string by default, which might
/// be exploited if your regex allow unexpected input to be considered valid.
/// When using this option, make sure your regex is correct.
#[serde(default)]
raw: bool,
},
Expand Down
2 changes: 1 addition & 1 deletion plugins/updater/tests/app-updater/tests/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ fn update_app() {
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
format!("{bundle_updater_ext}.{updater_zip_ext}")
} else {
format!("{bundle_updater_ext}")
bundle_updater_ext
};
let signature_extension = format!("{updater_extension}.sig");
let signature_path = out_bundle_path.with_extension(signature_extension);
Expand Down
Loading
Loading