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 // Ensure `FsScopeEntry` and `scope::EntryRaw` is kept in sync
impl From<FsScopeEntry> for scope::EntryRaw { fn _f() {
fn from(value: FsScopeEntry) -> Self { match scope::EntryRaw::Value(PathBuf::new()) {
match value { scope::EntryRaw::Value(path) => FsScopeEntry::Value(path),
FsScopeEntry::Value(path) => scope::EntryRaw::Value(path), scope::EntryRaw::Object { path } => FsScopeEntry::Object { path },
FsScopeEntry::Object { path } => scope::EntryRaw::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] = &[ const BASE_DIR_VARS: &[&str] = &[

@ -13,10 +13,9 @@ use std::{
use serde::Deserialize; use serde::Deserialize;
#[doc(hidden)]
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum EntryRaw { pub(crate) enum EntryRaw {
Value(PathBuf), Value(PathBuf),
Object { path: PathBuf }, Object { path: PathBuf },
} }

@ -47,23 +47,16 @@ enum HttpScopeEntry {
}, },
} }
// Ensure scope entry is kept up to date // Ensure `HttpScopeEntry` and `scope::EntryRaw` is kept in sync
impl From<HttpScopeEntry> for scope::Entry { fn _f() {
fn from(value: HttpScopeEntry) -> Self { match scope::EntryRaw::Value(String::new()) {
let url = match value { scope::EntryRaw::Value(url) => HttpScopeEntry::Value(url),
HttpScopeEntry::Value(url) => url, scope::EntryRaw::Object { url } => HttpScopeEntry::Object { url },
HttpScopeEntry::Object { url } => url, };
}; match HttpScopeEntry::Value(String::new()) {
HttpScopeEntry::Value(url) => scope::EntryRaw::Value(url),
scope::Entry { HttpScopeEntry::Object { url } => scope::EntryRaw::Object { url },
url: urlpattern::UrlPattern::parse( };
urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(&url, None)
.unwrap(),
Default::default(),
)
.unwrap(),
}
}
} }
fn main() { fn main() {

@ -33,18 +33,18 @@ fn parse_url_pattern(s: &str) -> Result<UrlPattern, urlpattern::quirks::Error> {
UrlPattern::parse(init, Default::default()) UrlPattern::parse(init, Default::default())
} }
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum EntryRaw {
Value(String),
Object { url: String },
}
impl<'de> Deserialize<'de> for Entry { impl<'de> Deserialize<'de> for Entry {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
#[derive(Deserialize)]
#[serde(untagged)]
enum EntryRaw {
Value(String),
Object { url: String },
}
EntryRaw::deserialize(deserializer).and_then(|raw| { EntryRaw::deserialize(deserializer).and_then(|raw| {
let url = match raw { let url = match raw {
EntryRaw::Value(url) => url, EntryRaw::Value(url) => url,

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

@ -2,15 +2,173 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::path::PathBuf;
use schemars::JsonSchema;
#[path = "src/scope_entry.rs"] #[path = "src/scope_entry.rs"]
mod scope_entry; 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"]; const COMMANDS: &[&str] = &["execute", "spawn", "stdin_write", "kill", "open"];
fn main() { fn main() {
tauri_plugin::Builder::new(COMMANDS) tauri_plugin::Builder::new(COMMANDS)
.global_api_script_path("./api-iife.js") .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") .android_path("android")
.ios_path("ios") .ios_path("ios")
.build(); .build();

@ -7,29 +7,23 @@ use serde::{de::Error as DeError, Deserialize, Deserializer};
use std::path::PathBuf; use std::path::PathBuf;
/// A command allowed to be executed by the webview API. /// A command allowed to be executed by the webview API.
#[derive(Debug, Clone, PartialEq, Eq, Hash, schemars::JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Entry { pub(crate) struct Entry {
/// The name for this allowed shell command configuration. pub(crate) name: String,
/// pub(crate) command: PathBuf,
/// This name will be used inside of the webview API to call this command along with pub(crate) args: ShellAllowedArgs,
/// any specified arguments. pub(crate) sidecar: bool,
pub name: String, }
/// The command name. #[derive(Deserialize)]
/// It can start with a variable that resolves to a system base directory. pub(crate) struct EntryRaw {
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, pub(crate) name: String,
/// `$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")] #[serde(rename = "cmd")]
pub command: PathBuf, pub(crate) command: Option<PathBuf>,
#[serde(default)]
/// The allowed arguments for the command execution. pub(crate) args: ShellAllowedArgs,
pub args: ShellAllowedArgs, #[serde(default)]
pub(crate) sidecar: bool,
/// If this command is a sidecar command.
pub sidecar: bool,
} }
impl<'de> Deserialize<'de> for Entry { impl<'de> Deserialize<'de> for Entry {
@ -37,18 +31,7 @@ impl<'de> Deserialize<'de> for Entry {
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
#[derive(Deserialize)] let config = EntryRaw::deserialize(deserializer)?;
struct InnerEntry {
name: String,
#[serde(rename = "cmd")]
command: Option<PathBuf>,
#[serde(default)]
args: ShellAllowedArgs,
#[serde(default)]
sidecar: bool,
}
let config = InnerEntry::deserialize(deserializer)?;
if !config.sidecar && config.command.is_none() { if !config.sidecar && config.command.is_none() {
return Err(DeError::custom( 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. #[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize)]
///
/// 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)]
#[serde(untagged, deny_unknown_fields)] #[serde(untagged, deny_unknown_fields)]
#[non_exhaustive] #[non_exhaustive]
pub enum ShellAllowedArgs { pub enum ShellAllowedArgs {
/// Use a simple boolean to allow all or disable all arguments to this command configuration.
Flag(bool), Flag(bool),
/// A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.
List(Vec<ShellAllowedArg>), 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)]
#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, schemars::JsonSchema)]
#[serde(untagged, deny_unknown_fields)] #[serde(untagged, deny_unknown_fields)]
#[non_exhaustive] #[non_exhaustive]
pub enum ShellAllowedArg { pub enum ShellAllowedArg {
/// A non-configurable argument that is passed to the command in the order it was specified.
Fixed(String), Fixed(String),
/// A variable that is set while calling the command from the webview API.
///
Var { 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, 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)] #[serde(default)]
raw: bool, raw: bool,
}, },

@ -221,7 +221,7 @@ fn update_app() {
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext { let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
format!("{bundle_updater_ext}.{updater_zip_ext}") format!("{bundle_updater_ext}.{updater_zip_ext}")
} else { } else {
format!("{bundle_updater_ext}") bundle_updater_ext
}; };
let signature_extension = format!("{updater_extension}.sig"); let signature_extension = format!("{updater_extension}.sig");
let signature_path = out_bundle_path.with_extension(signature_extension); let signature_path = out_bundle_path.with_extension(signature_extension);

@ -413,7 +413,7 @@ fn update_app() {
), ),
2 => ( 2 => (
v2_config.version, 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)>>, as Box<dyn Fn() -> Vec<(BundleTarget, PathBuf)>>,
"-v2", "-v2",
), ),

Loading…
Cancel
Save