fix(shell): fix schema requiring `sidecar` property even though it is optional (#1839)

* fix(shell): fix schema requiring `sidecar` property even though it is optional

* fix clippy

* make `cmd` and `sidecar` exclusive

* make args optional

* cleanup

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
pull/1859/head
Amr Bashir 8 months ago committed by GitHub
parent 2f7e32b5e0
commit 44273b9889
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,6 @@
---
"shell": "patch"
---
Fix the plugin schema requiring to set `sidecar` property when it is in fact optional.

@ -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] = &[

@ -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 },
}

@ -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() {

@ -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,

@ -27,7 +27,6 @@ serde = { workspace = true }
[dependencies]
serde = { workspace = true }
schemars = { workspace = true }
serde_json = { workspace = true }
tauri = { workspace = true }
tokio = { version = "1", features = ["time"] }

@ -2,15 +2,173 @@
// 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)]
#[serde(untagged, deny_unknown_fields)]
#[allow(unused)]
pub(crate) enum ShellScopeEntry {
Command {
/// 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: PathBuf,
/// The allowed arguments for the command execution.
#[serde(default)]
args: ShellScopeEntryAllowedArgs,
},
Sidecar {
/// 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 allowed arguments for the command execution.
#[serde(default)]
args: ShellScopeEntryAllowedArgs,
/// If this command is a sidecar command.
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() {
match (ShellScopeEntry::Sidecar {
name: String::new(),
args: ShellScopeEntryAllowedArgs::Flag(false),
sidecar: true,
}) {
ShellScopeEntry::Command {
name,
command,
args,
} => scope_entry::EntryRaw {
name,
command: Some(command),
args: match args {
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(),
),
},
sidecar: false,
},
ShellScopeEntry::Sidecar {
name,
args,
sidecar,
} => scope_entry::EntryRaw {
name,
command: None,
args: match args {
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(),
),
},
sidecar,
},
};
}
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();

@ -7,29 +7,23 @@ 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 {
@ -37,18 +31,7 @@ impl<'de> Deserialize<'de> for Entry {
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(
@ -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>),
}
@ -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,
},

@ -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);

@ -413,7 +413,7 @@ fn update_app() {
),
2 => (
v2_config.version,
Box::new(|| v2::bundle_paths(&root_dir, &v2_config.version))
Box::new(|| v2::bundle_paths(&root_dir, v2_config.version))
as Box<dyn Fn() -> Vec<(BundleTarget, PathBuf)>>,
"-v2",
),

Loading…
Cancel
Save