perf: completely remove endpoints if none of its APIs is allowlisted (#3958)

This commit is contained in:
Lucas Fernandes Nogueira
2022-04-24 15:18:22 -07:00
committed by GitHub
parent 24e4ff208e
commit ed467c275b
8 changed files with 364 additions and 168 deletions

View File

@@ -141,8 +141,10 @@ impl WebviewIdStore {
#[macro_export]
macro_rules! getter {
($self: ident, $rx: expr, $message: expr) => {{
crate::send_user_message(&$self.context, $message)?;
$rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage)
$crate::send_user_message(&$self.context, $message)?;
$rx
.recv()
.map_err(|_| $crate::Error::FailedToReceiveMessage)
}};
}

View File

@@ -104,7 +104,8 @@ version = "0.30.0"
features = [ "Win32_Foundation" ]
[build-dependencies]
cfg_aliases = "0.1.1"
heck = "0.4"
once_cell = "1.10"
[dev-dependencies]
mockito = "0.31"

View File

@@ -2,108 +2,160 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use cfg_aliases::cfg_aliases;
use heck::ToSnakeCase;
use once_cell::sync::OnceCell;
fn main() {
cfg_aliases! {
custom_protocol: { feature = "custom-protocol" },
dev: { not(feature = "custom-protocol") },
updater: { any(feature = "updater", feature = "__updater-docs") },
use std::{path::Path, sync::Mutex};
api_all: { feature = "api-all" },
static CHECKED_FEATURES: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
// fs
fs_all: { any(api_all, feature = "fs-all") },
fs_read_file: { any(fs_all, feature = "fs-read-file") },
fs_write_file: { any(fs_all, feature = "fs-write-file") },
fs_write_binary_file: { any(fs_all, feature = "fs-write-binary-file") },
fs_read_dir: { any(fs_all, feature = "fs-read-dir") },
fs_copy_file: { any(fs_all, feature = "fs-copy-file") },
fs_create_dir: { any(fs_all, feature = "fs-create_dir") },
fs_remove_dir: { any(fs_all, feature = "fs-remove-dir") },
fs_remove_file: { any(fs_all, feature = "fs-remove-file") },
fs_rename_file: { any(fs_all, feature = "fs-rename-file") },
// checks if the given Cargo feature is enabled.
fn has_feature(feature: &str) -> bool {
CHECKED_FEATURES
.get_or_init(Default::default)
.lock()
.unwrap()
.push(feature.to_string());
// window
window_all: { any(api_all, feature = "window-all") },
window_create: { any(window_all, feature = "window-create") },
window_center: { any(window_all, feature = "window-center") },
window_request_user_attention: { any(window_all, feature = "window-request-user-attention") },
window_set_resizable: { any(window_all, feature = "window-set-resizable") },
window_set_title: { any(window_all, feature = "window-set-title") },
window_maximize: { any(window_all, feature = "window-maximize") },
window_unmaximize: { any(window_all, feature = "window-unmaximize") },
window_minimize: { any(window_all, feature = "window-minimize") },
window_unminimize: { any(window_all, feature = "window-unminimize") },
window_show: { any(window_all, feature = "window-show") },
window_hide: { any(window_all, feature = "window-hide") },
window_close: { any(window_all, feature = "window-close") },
window_set_decorations: { any(window_all, feature = "window-set-decorations") },
window_set_always_on_top: { any(window_all, feature = "window-set-always-on-top") },
window_set_size: { any(window_all, feature = "window-set-size") },
window_set_min_size: { any(window_all, feature = "window-set-min-size") },
window_set_max_size: { any(window_all, feature = "window-set-max-size") },
window_set_position: { any(window_all, feature = "window-set-position") },
window_set_fullscreen: { any(window_all, feature = "window-set-fullscreen") },
window_set_focus: { any(window_all, feature = "window-set-focus") },
window_set_icon: { any(window_all, feature = "window-set-icon") },
window_set_skip_taskbar: { any(window_all, feature = "window-set-skip-taskbar") },
window_set_cursor_grab: { any(window_all, feature = "window-set-cursor-grab") },
window_set_cursor_visible: { any(window_all, feature = "window-set-cursor-visible") },
window_set_cursor_icon: { any(window_all, feature = "window-set-cursor-icon") },
window_set_cursor_position: { any(window_all, feature = "window-set-cursor-position") },
window_start_dragging: { any(window_all, feature = "window-start-dragging") },
window_print: { any(window_all, feature = "window-print") },
// when a feature is enabled, Cargo sets the `CARGO_FEATURE_<name` env var to 1
// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
std::env::var(format!(
"CARGO_FEATURE_{}",
feature.to_uppercase().to_snake_case()
))
.map(|x| x == "1")
.unwrap_or(false)
}
// shell
shell_all: { any(api_all, feature = "shell-all") },
shell_execute: { any(shell_all, feature = "shell-execute") },
shell_sidecar: { any(shell_all, feature = "shell-sidecar") },
shell_open: { any(shell_all, feature = "shell-open") },
// helper for the command module macro
shell_script: { any(shell_execute, shell_sidecar) },
// helper for the shell scope functionality
shell_scope: { any(shell_execute, shell_sidecar, feature = "shell-open-api") },
// dialog
dialog_all: { any(api_all, feature = "dialog-all") },
dialog_open: { any(dialog_all, feature = "dialog-open") },
dialog_save: { any(dialog_all, feature = "dialog-save") },
dialog_message: { any(dialog_all, feature = "dialog-message") },
dialog_ask: { any(dialog_all, feature = "dialog-ask") },
dialog_confirm: { any(dialog_all, feature = "dialog-confirm") },
// http
http_all: { any(api_all, feature = "http-all") },
http_request: { any(http_all, feature = "http-request") },
// cli
cli: { feature = "cli" },
// notification
notification_all: { any(api_all, feature = "notification-all") },
// global shortcut
global_shortcut_all: { any(api_all, feature = "global_shortcut-all") },
// os
os_all: { any(api_all, feature = "os-all") },
// path
path_all: { any(api_all, feature = "path-all") },
// protocol
protocol_all: { any(api_all, feature = "protocol-all") },
protocol_asset: { any(protocol_all, feature = "protocol-asset") },
// process
process_all: { any(api_all, feature = "process-all") },
process_relaunch: { any(protocol_all, feature = "process-relaunch") },
process_exit: { any(protocol_all, feature = "process-exit") },
// clipboard
clipboard_all: { any(api_all, feature = "clipboard-all") },
clipboard_write_text: { any(protocol_all, feature = "clipboard-write-text") },
clipboard_read_text: { any(protocol_all, feature = "clipboard-read-text") },
// creates a cfg alias if `has_feature` is true.
// `alias` must be a snake case string.
fn alias(alias: &str, has_feature: bool) {
if has_feature {
println!("cargo:rustc-cfg={}", alias);
}
}
fn main() {
alias("custom_protocol", has_feature("custom-protocol"));
alias("dev", !has_feature("custom-protocol"));
alias(
"updater",
has_feature("updater") || has_feature("__updater-docs"),
);
let api_all = has_feature("api-all");
alias("api_all", api_all);
alias_module(
"fs",
&[
"read-file",
"write-file",
"read-dir",
"copy-file",
"create-dir",
"remove-dir",
"remove-file",
"rename-file",
],
api_all,
);
alias_module(
"window",
&[
"create",
"center",
"request-user-attention",
"set-resizable",
"set-title",
"maximize",
"unmaximize",
"minimize",
"unminimize",
"show",
"hide",
"close",
"set-decorations",
"set-always-on-top",
"set-size",
"set-min-size",
"set-max-size",
"set-position",
"set-fullscreen",
"set-focus",
"set-icon",
"set-skip-taskbar",
"set-cursor-grab",
"set-cursor-visible",
"set-cursor-icon",
"set-cursor-position",
"start-dragging",
"print",
],
api_all,
);
alias_module("shell", &["execute", "sidecar", "open"], api_all);
// helper for the command module macro
let shell_script = has_feature("shell-execute") || has_feature("shell-sidecar");
alias("shell_script", shell_script);
alias("shell_scope", shell_script || has_feature("shell-open-api"));
alias_module(
"dialog",
&["open", "save", "message", "ask", "confirm"],
api_all,
);
alias_module("http", &["request"], api_all);
alias("cli", has_feature("cli"));
alias_module("notification", &[], api_all);
alias_module("global-shortcut", &[], api_all);
alias_module("os", &[], api_all);
alias_module("path", &[], api_all);
alias_module("protocol", &["asset"], api_all);
alias_module("process", &["relaunch", "exit"], api_all);
alias_module("clipboard", &["write-text", "read-text"], api_all);
let checked_features_out_path =
Path::new(&std::env::var("OUT_DIR").unwrap()).join("checked_features");
std::fs::write(
&checked_features_out_path,
&CHECKED_FEATURES.get().unwrap().lock().unwrap().join(","),
)
.expect("failed to write checked_features file");
}
// create aliases for the given module with its apis.
// each api is translated into a feature flag in the format of `<module>-<api>`
// and aliased as `<module_snake_case>_<api_snake_case>`.
//
// The `<module>-all` feature is also aliased to `<module>_all`.
//
// If any of the features is enabled, the `<module_snake_case>_any` alias is created.
//
// Note that both `module` and `apis` strings must be written in kebab case.
fn alias_module(module: &str, apis: &[&str], api_all: bool) {
let all_feature_name = format!("{}-all", module);
let all = api_all || has_feature(&all_feature_name);
alias(&all_feature_name.to_snake_case(), all);
let mut any = all;
for api in apis {
let has = all || has_feature(&format!("{}-{}", module, api));
alias(
&format!("{}_{}", module.to_snake_case(), api.to_snake_case()),
has,
);
any = any || has;
}
alias(&format!("{}_any", module.to_snake_case()), any);
}

View File

@@ -34,10 +34,12 @@ impl SafePathBuf {
}
}
#[allow(dead_code)]
pub unsafe fn new_unchecked(path: std::path::PathBuf) -> Self {
Self(path)
}
#[allow(dead_code)]
pub fn display(&self) -> Display<'_> {
self.0.display()
}

View File

@@ -13,17 +13,27 @@ use serde_json::Value as JsonValue;
use std::sync::Arc;
mod app;
#[cfg(cli)]
mod cli;
#[cfg(clipboard_any)]
mod clipboard;
#[cfg(dialog_any)]
mod dialog;
mod event;
#[cfg(fs_any)]
mod file_system;
#[cfg(global_shortcut_any)]
mod global_shortcut;
#[cfg(http_any)]
mod http;
mod notification;
#[cfg(os_any)]
mod operating_system;
#[cfg(path_any)]
mod path;
#[cfg(process_any)]
mod process;
#[cfg(shell_any)]
mod shell;
mod window;
@@ -62,18 +72,28 @@ impl<T: Serialize> From<T> for InvokeResponse {
#[serde(tag = "module", content = "message")]
enum Module {
App(app::Cmd),
#[cfg(process_any)]
Process(process::Cmd),
#[cfg(fs_any)]
Fs(file_system::Cmd),
#[cfg(os_any)]
Os(operating_system::Cmd),
#[cfg(path_any)]
Path(path::Cmd),
Window(Box<window::Cmd>),
#[cfg(shell_any)]
Shell(shell::Cmd),
Event(event::Cmd),
#[cfg(dialog_any)]
Dialog(dialog::Cmd),
#[cfg(cli)]
Cli(cli::Cmd),
Notification(notification::Cmd),
#[cfg(http_any)]
Http(http::Cmd),
#[cfg(global_shortcut_any)]
GlobalShortcut(global_shortcut::Cmd),
#[cfg(clipboard_any)]
Clipboard(clipboard::Cmd),
}
@@ -97,24 +117,28 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(process_any)]
Self::Process(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(fs_any)]
Self::Fs(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(path_any)]
Self::Path(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(os_any)]
Self::Os(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@@ -128,6 +152,7 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(shell_any)]
Self::Shell(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@@ -140,12 +165,14 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(dialog_any)]
Self::Dialog(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(cli)]
Self::Cli(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@@ -158,6 +185,7 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(http_any)]
Self::Http(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@@ -165,12 +193,14 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(global_shortcut_any)]
Self::GlobalShortcut(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(clipboard_any)]
Self::Clipboard(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@@ -195,11 +225,28 @@ pub(crate) fn handle<R: Runtime>(
} = message;
if let JsonValue::Object(ref mut obj) = payload {
obj.insert("module".to_string(), JsonValue::String(module));
obj.insert("module".to_string(), JsonValue::String(module.clone()));
}
match serde_json::from_value::<Module>(payload) {
Ok(module) => module.run(window, resolver, config, package_info.clone()),
Err(e) => resolver.reject(e.to_string()),
Err(e) => {
let message = e.to_string();
if message.starts_with("unknown variant") {
let mut s = message.split('`');
s.next();
if let Some(unknown_variant_name) = s.next() {
if unknown_variant_name == module {
return resolver.reject(format!(
"The `{}` module is not enabled. You must enable one of its APIs in the allowlist.",
module
));
} else if module == "Window" {
return resolver.reject(window::into_allowlist_error(unknown_variant_name).to_string());
}
}
}
resolver.reject(message);
}
}
}

View File

@@ -72,38 +72,67 @@ pub enum WindowManagerCmd {
AvailableMonitors,
Theme,
// Setters
#[cfg(window_center)]
Center,
#[cfg(window_request_user_attention)]
RequestUserAttention(Option<UserAttentionType>),
#[cfg(window_set_resizable)]
SetResizable(bool),
#[cfg(window_set_title)]
SetTitle(String),
#[cfg(window_maximize)]
Maximize,
#[cfg(window_unmaximize)]
Unmaximize,
#[cfg(all(window_maximize, window_unmaximize))]
ToggleMaximize,
#[cfg(window_minimize)]
Minimize,
#[cfg(window_unminimize)]
Unminimize,
#[cfg(window_show)]
Show,
#[cfg(window_hide)]
Hide,
#[cfg(window_close)]
Close,
#[cfg(window_set_decorations)]
SetDecorations(bool),
#[cfg(window_set_always_on_top)]
#[serde(rename_all = "camelCase")]
SetAlwaysOnTop(bool),
#[cfg(window_set_size)]
SetSize(Size),
#[cfg(window_set_min_size)]
SetMinSize(Option<Size>),
#[cfg(window_set_max_size)]
SetMaxSize(Option<Size>),
#[cfg(window_set_position)]
SetPosition(Position),
#[cfg(window_set_fullscreen)]
SetFullscreen(bool),
#[cfg(window_set_focus)]
SetFocus,
#[cfg(window_set_icon)]
SetIcon {
icon: IconDto,
},
#[cfg(window_set_skip_taskbar)]
SetSkipTaskbar(bool),
#[cfg(window_set_cursor_grab)]
SetCursorGrab(bool),
#[cfg(window_set_cursor_visible)]
SetCursorVisible(bool),
#[cfg(window_set_cursor_icon)]
SetCursorIcon(CursorIcon),
#[cfg(window_set_cursor_position)]
SetCursorPosition(Position),
#[cfg(window_start_dragging)]
StartDragging,
#[cfg(window_print)]
Print,
// internals
#[cfg(all(window_maximize, window_unmaximize))]
#[serde(rename = "__toggleMaximize")]
InternalToggleMaximize,
#[cfg(any(debug_assertions, feature = "devtools"))]
@@ -111,61 +140,45 @@ pub enum WindowManagerCmd {
InternalToggleDevtools,
}
impl WindowManagerCmd {
fn into_allowlist_error(self) -> crate::Error {
match self {
Self::Center => crate::Error::ApiNotAllowlisted("window > center".to_string()),
Self::RequestUserAttention(_) => {
crate::Error::ApiNotAllowlisted("window > requestUserAttention".to_string())
}
Self::SetResizable(_) => crate::Error::ApiNotAllowlisted("window > setResizable".to_string()),
Self::SetTitle(_) => crate::Error::ApiNotAllowlisted("window > setTitle".to_string()),
Self::Maximize => crate::Error::ApiNotAllowlisted("window > maximize".to_string()),
Self::Unmaximize => crate::Error::ApiNotAllowlisted("window > unmaximize".to_string()),
Self::ToggleMaximize => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
Self::Minimize => crate::Error::ApiNotAllowlisted("window > minimize".to_string()),
Self::Unminimize => crate::Error::ApiNotAllowlisted("window > unminimize".to_string()),
Self::Show => crate::Error::ApiNotAllowlisted("window > show".to_string()),
Self::Hide => crate::Error::ApiNotAllowlisted("window > hide".to_string()),
Self::Close => crate::Error::ApiNotAllowlisted("window > close".to_string()),
Self::SetDecorations(_) => {
crate::Error::ApiNotAllowlisted("window > setDecorations".to_string())
}
Self::SetAlwaysOnTop(_) => {
crate::Error::ApiNotAllowlisted("window > setAlwaysOnTop".to_string())
}
Self::SetSize(_) => crate::Error::ApiNotAllowlisted("window > setSize".to_string()),
Self::SetMinSize(_) => crate::Error::ApiNotAllowlisted("window > setMinSize".to_string()),
Self::SetMaxSize(_) => crate::Error::ApiNotAllowlisted("window > setMaxSize".to_string()),
Self::SetPosition(_) => crate::Error::ApiNotAllowlisted("window > setPosition".to_string()),
Self::SetFullscreen(_) => {
crate::Error::ApiNotAllowlisted("window > setFullscreen".to_string())
}
Self::SetIcon { .. } => crate::Error::ApiNotAllowlisted("window > setIcon".to_string()),
Self::SetSkipTaskbar(_) => {
crate::Error::ApiNotAllowlisted("window > setSkipTaskbar".to_string())
}
Self::SetCursorGrab(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorGrab".to_string())
}
Self::SetCursorVisible(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorVisible".to_string())
}
Self::SetCursorIcon(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorIcon".to_string())
}
Self::SetCursorPosition(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorPosition".to_string())
}
Self::StartDragging => crate::Error::ApiNotAllowlisted("window > startDragging".to_string()),
Self::Print => crate::Error::ApiNotAllowlisted("window > print".to_string()),
Self::InternalToggleMaximize => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
_ => crate::Error::ApiNotAllowlisted("window > all".to_string()),
pub fn into_allowlist_error(variant: &str) -> crate::Error {
match variant {
"center" => crate::Error::ApiNotAllowlisted("window > center".to_string()),
"requestUserAttention" => {
crate::Error::ApiNotAllowlisted("window > requestUserAttention".to_string())
}
"setResizable" => crate::Error::ApiNotAllowlisted("window > setResizable".to_string()),
"setTitle" => crate::Error::ApiNotAllowlisted("window > setTitle".to_string()),
"maximize" => crate::Error::ApiNotAllowlisted("window > maximize".to_string()),
"unmaximize" => crate::Error::ApiNotAllowlisted("window > unmaximize".to_string()),
"toggleMaximize" => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
"minimize" => crate::Error::ApiNotAllowlisted("window > minimize".to_string()),
"nnminimize" => crate::Error::ApiNotAllowlisted("window > unminimize".to_string()),
"show" => crate::Error::ApiNotAllowlisted("window > show".to_string()),
"hide" => crate::Error::ApiNotAllowlisted("window > hide".to_string()),
"close" => crate::Error::ApiNotAllowlisted("window > close".to_string()),
"setDecorations" => crate::Error::ApiNotAllowlisted("window > setDecorations".to_string()),
"setAlwaysOnTop" => crate::Error::ApiNotAllowlisted("window > setAlwaysOnTop".to_string()),
"setSize" => crate::Error::ApiNotAllowlisted("window > setSize".to_string()),
"setMinSize" => crate::Error::ApiNotAllowlisted("window > setMinSize".to_string()),
"setMaxSize" => crate::Error::ApiNotAllowlisted("window > setMaxSize".to_string()),
"setPosition" => crate::Error::ApiNotAllowlisted("window > setPosition".to_string()),
"setFullscreen" => crate::Error::ApiNotAllowlisted("window > setFullscreen".to_string()),
"setIcon" => crate::Error::ApiNotAllowlisted("window > setIcon".to_string()),
"setSkipTaskbar" => crate::Error::ApiNotAllowlisted("window > setSkipTaskbar".to_string()),
"setCursorGrab" => crate::Error::ApiNotAllowlisted("window > setCursorGrab".to_string()),
"setCursorVisible" => crate::Error::ApiNotAllowlisted("window > setCursorVisible".to_string()),
"setCursorIcon" => crate::Error::ApiNotAllowlisted("window > setCursorIcon".to_string()),
"setCursorPosition" => {
crate::Error::ApiNotAllowlisted("window > setCursorPosition".to_string())
}
"startDragging" => crate::Error::ApiNotAllowlisted("window > startDragging".to_string()),
"print" => crate::Error::ApiNotAllowlisted("window > print".to_string()),
"internalToggleMaximize" => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
_ => crate::Error::ApiNotAllowlisted("window".to_string()),
}
}
@@ -318,8 +331,6 @@ impl Cmd {
window.open_devtools();
}
}
#[allow(unreachable_patterns)]
_ => return Err(cmd.into_allowlist_error()),
}
#[allow(unreachable_code)]
Ok(().into())

View File

@@ -803,23 +803,100 @@ pub mod test;
#[cfg(test)]
mod tests {
use cargo_toml::Manifest;
use once_cell::sync::OnceCell;
use std::{env::var, fs::read_to_string, path::PathBuf};
static MANIFEST: OnceCell<Manifest> = OnceCell::new();
const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features"));
fn get_manifest() -> &'static Manifest {
MANIFEST.get_or_init(|| {
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
let manifest = Manifest::from_path(manifest_dir.join("Cargo.toml"))
.expect("failed to parse Cargo manifest");
manifest
})
}
#[test]
fn features_are_documented() {
use cargo_toml::Manifest;
use std::{env::var, fs::read_to_string, path::PathBuf};
// this env var is always set by Cargo
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
let manifest =
Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest");
let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
for (f, _) in manifest.features {
for (f, _) in &get_manifest().features {
if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{}**", f))) {
panic!("Feature {} is not documented", f);
}
}
}
#[test]
fn aliased_features_exist() {
let checked_features = CHECKED_FEATURES.split(',');
let manifest = get_manifest();
for checked_feature in checked_features {
if !manifest.features.iter().any(|(f, _)| f == checked_feature) {
panic!(
"Feature {} was checked in the alias build step but it does not exist in core/tauri/Cargo.toml",
checked_feature
);
}
}
}
#[test]
fn all_allowlist_features_are_aliased() {
let manifest = get_manifest();
let all_modules = manifest
.features
.iter()
.find(|(f, _)| f.as_str() == "api-all")
.map(|(_, enabled)| enabled)
.expect("api-all feature must exist");
let checked_features = CHECKED_FEATURES.split(',').collect::<Vec<&str>>();
assert!(
checked_features.contains(&"api-all"),
"`api-all` is not aliased"
);
// features that look like an allowlist feature, but are not
let allowed = [
"fs-extract-api",
"http-api",
"http-multipart",
"process-command-api",
"process-relaunch-dangerous-allow-symlink-macos",
"window-data-url",
];
for module_all_feature in all_modules {
let module = module_all_feature.replace("-all", "");
assert!(
checked_features.contains(&module_all_feature.as_str()),
"`{}` is not aliased",
module
);
let module_prefix = format!("{}-", module);
// we assume that module features are the ones that start with `<module>-`
// though it's not 100% accurate, we have an allowed list to fix it
let module_features = manifest
.features
.iter()
.map(|(f, _)| f)
.filter(|f| f.starts_with(&module_prefix));
for module_feature in module_features {
assert!(
allowed.contains(&module_feature.as_str())
|| checked_features.contains(&module_feature.as_str()),
"`{}` is not aliased",
module_feature
);
}
}
}
}
#[cfg(test)]

View File

@@ -605,8 +605,12 @@ impl<R: Runtime> Window<R> {
let invoke = Invoke { message, resolver };
if let Some(module) = &payload.tauri_module {
let module = module.to_string();
crate::endpoints::handle(module, invoke, manager.config(), manager.package_info());
crate::endpoints::handle(
module.to_string(),
invoke,
manager.config(),
manager.package_info(),
);
} else if payload.cmd.starts_with("plugin:") {
manager.extend_api(invoke);
} else {