rightkit_tauri_shell/
harden.rs1use serde::Serialize;
11use tauri::{Runtime, Webview};
12
13use crate::devtools::WebviewDecision;
14
15#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
18pub enum HardeningScript {
19 #[default]
21 AllFrames,
22 TopFrameOnly,
24 Off,
26}
27
28#[derive(Debug, Clone)]
29pub struct Hardening {
30 pub block_context_menu: bool,
32 pub allow_editable_menu: bool,
35 pub block_reload: bool,
37 pub block_zoom: bool,
39 pub block_history_nav: bool,
41 pub block_drop_navigation: bool,
43 pub intercept_external_links: bool,
45}
46
47impl Default for Hardening {
48 fn default() -> Self {
49 Self {
50 block_context_menu: true,
51 allow_editable_menu: true,
52 block_reload: true,
53 block_zoom: true,
54 block_history_nav: true,
55 block_drop_navigation: true,
56 intercept_external_links: true,
57 }
58 }
59}
60
61#[derive(Serialize)]
62#[serde(rename_all = "camelCase")]
63struct ScriptConfig {
64 block_context_menu: bool,
65 allow_editable_menu: bool,
66 block_reload: bool,
67 block_zoom: bool,
68 block_history_nav: bool,
69 content_devtools_labels: Vec<String>,
70 block_drop_navigation: bool,
71 intercept_external_links: bool,
72}
73
74const SCRIPT_BODY: &str = include_str!("harden.js");
75
76impl Hardening {
77 pub fn script(&self) -> String {
80 self.script_for(&[])
81 }
82
83 pub fn script_for(&self, content_labels: &[String]) -> String {
88 let cfg = ScriptConfig {
89 block_context_menu: self.block_context_menu,
90 allow_editable_menu: self.allow_editable_menu,
91 block_reload: self.block_reload,
92 block_zoom: self.block_zoom,
93 block_history_nav: self.block_history_nav,
94 content_devtools_labels: content_labels.to_vec(),
95 block_drop_navigation: self.block_drop_navigation,
96 intercept_external_links: self.intercept_external_links,
97 };
98 let json = serde_json::to_string(&cfg).expect("static config serialises");
99 SCRIPT_BODY.replace("__RK_SHELL_CONFIG__", &json)
100 }
101}
102
103pub fn apply_native<R: Runtime>(webview: &Webview<R>, h: &Hardening, decision: WebviewDecision) {
107 #[cfg(target_os = "macos")]
108 macos::apply(webview, h);
109 #[cfg(windows)]
110 windows_impl::apply(webview, h, decision);
111 #[cfg(not(windows))]
112 let _ = decision;
113 #[cfg(not(any(target_os = "macos", windows)))]
114 let _ = (webview, h);
115}
116
117#[cfg(target_os = "macos")]
118mod macos {
119 use super::*;
120 use objc2::msg_send;
121 use objc2::runtime::AnyObject;
122
123 pub fn apply<R: Runtime>(webview: &Webview<R>, h: &Hardening) {
124 let zoom_off = h.block_zoom;
125 let nav_off = h.block_history_nav;
126 let _ = webview.with_webview(move |platform| unsafe {
127 let wk = &*(platform.inner() as *mut AnyObject);
130 if zoom_off {
131 let (): () = msg_send![wk, setAllowsMagnification: false];
132 }
133 if nav_off {
134 let (): () = msg_send![wk, setAllowsBackForwardNavigationGestures: false];
135 }
136 });
137 }
138}
139
140#[cfg(windows)]
141mod windows_impl {
142 use super::*;
143 use crate::winapi::webview2_com::ContextMenuRequestedEventHandler;
144 use crate::winapi::webview2_com::Microsoft::Web::WebView2::Win32::{
145 ICoreWebView2Settings5, ICoreWebView2Settings6, ICoreWebView2_11,
146 };
147 use crate::winapi::windows::core::Interface;
148 use crate::winapi::windows::core::BOOL;
149
150 pub fn apply<R: Runtime>(webview: &Webview<R>, h: &Hardening, decision: WebviewDecision) {
153 let h = h.clone();
154 let content = decision != WebviewDecision::Deny;
155 crate::devtools::set_hardening_keys(
159 webview.label(),
160 h.block_reload,
161 h.block_history_nav,
162 h.block_zoom,
163 );
164 let _ = webview.with_webview(move |platform| unsafe {
165 let Ok(core) = platform.controller().CoreWebView2() else {
166 return;
167 };
168 let Ok(settings) = core.Settings() else {
169 return;
170 };
171 let _ = settings.SetIsStatusBarEnabled(false);
172 if h.block_zoom {
173 let _ = settings.SetIsZoomControlEnabled(false);
174 if let Ok(s5) = settings.cast::<ICoreWebView2Settings5>() {
175 let _ = s5.SetIsPinchZoomEnabled(false);
176 }
177 }
178 if h.block_history_nav {
179 if let Ok(s6) = settings.cast::<ICoreWebView2Settings6>() {
180 let _ = s6.SetIsSwipeNavigationEnabled(false);
181 }
182 }
183 if h.block_context_menu && !content {
184 let allow_editable = h.allow_editable_menu;
187 if let Ok(wv11) = core.cast::<ICoreWebView2_11>() {
188 let handler =
189 ContextMenuRequestedEventHandler::create(Box::new(move |_sender, args| {
190 let Some(args) = args else { return Ok(()) };
191 let target = args.ContextMenuTarget()?;
192 let mut editable = BOOL(0);
193 let _ = target.IsEditable(&mut editable);
194 if !(allow_editable && editable.as_bool()) {
195 args.SetHandled(true)?;
196 }
197 Ok(())
198 }));
199 let mut token: i64 = 0;
200 let _ = wv11.add_ContextMenuRequested(&handler, &mut token);
201 }
202 }
203 });
204 }
205}