9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
147 lines
5.9 KiB
Rust
147 lines
5.9 KiB
Rust
//! Linux-only: enable media capture (`getUserMedia`) in the WebKitGTK webview.
|
|
//!
|
|
//! On macOS (WKWebView) and Windows (WebView2) the media-permission prompt is
|
|
//! routed to the OS automatically, so microphone/camera capture "just works".
|
|
//! WebKitGTK is different on two counts, and both must be handled or capture
|
|
//! fails on Linux only:
|
|
//!
|
|
//! * `enable-media-stream` is **off by default**, so `navigator.mediaDevices`
|
|
//! never exposes a working `getUserMedia`; and
|
|
//! * the default `permission-request` handler **denies every request**, so even
|
|
//! with media-stream on, the call rejects with `NotAllowedError`.
|
|
//!
|
|
//! This module reaches the underlying `webkit2gtk::WebView` via
|
|
//! [`tauri::Webview::with_webview`], enables media-stream, and installs a
|
|
//! `permission-request` handler that is **deny-by-default**: a `UserMedia`
|
|
//! request is allowed only when it comes from a trusted app origin and asks for
|
|
//! an audio and/or video device. Tauri does not restrict navigation by default,
|
|
//! so without the origin check any document that ended up in this webview would
|
|
//! inherit silent mic/camera access for the process lifetime.
|
|
//!
|
|
//! Buzz's AppImage pins `GDK_BACKEND=x11` (see [`crate::webkit_rendering`]),
|
|
//! which is the backend WebKitGTK media capture is reliable on.
|
|
|
|
/// The origin Tauri serves the packaged app from on Linux.
|
|
/// Consumed only by linux-gated [`enable_media_capture`]; kept compiling on all
|
|
/// platforms so the unit tests run everywhere.
|
|
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
|
const PROD_ORIGIN: &str = "tauri://localhost";
|
|
|
|
/// The Vite dev-server origin (`devUrl` in `tauri.conf.json`, `strictPort`
|
|
/// 1420 in `vite.config.ts`). Only trusted in debug builds.
|
|
#[cfg(debug_assertions)]
|
|
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
|
const DEV_ORIGIN: &str = "http://localhost:1420";
|
|
|
|
/// Whether `uri` (the webview's current document URI) is a trusted app origin
|
|
/// allowed to use mic/camera. Matches the origin exactly or as a path prefix so
|
|
/// `tauri://localhost.evil.com` and `http://localhost:14200` do not slip
|
|
/// through. Pure and platform-independent so it can be unit-tested everywhere.
|
|
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
|
fn is_trusted_media_origin(uri: &str) -> bool {
|
|
fn matches(uri: &str, origin: &str) -> bool {
|
|
uri == origin
|
|
|| uri
|
|
.strip_prefix(origin)
|
|
.is_some_and(|rest| rest.starts_with('/'))
|
|
}
|
|
|
|
if matches(uri, PROD_ORIGIN) {
|
|
return true;
|
|
}
|
|
#[cfg(debug_assertions)]
|
|
if matches(uri, DEV_ORIGIN) {
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Enable microphone/camera capture for `webview` if it is running on
|
|
/// WebKitGTK. A no-op on every non-Linux target, so callers can invoke it
|
|
/// unconditionally from shared startup code.
|
|
#[cfg(target_os = "linux")]
|
|
pub fn enable_media_capture<R: tauri::Runtime>(webview: &tauri::Webview<R>) {
|
|
use webkit2gtk::{
|
|
glib::prelude::Cast, PermissionRequestExt, SettingsExt, UserMediaPermissionRequest,
|
|
UserMediaPermissionRequestExt, WebViewExt,
|
|
};
|
|
|
|
// `with_webview` runs the closure on the UI thread, which GTK calls
|
|
// require. It errors only if the platform webview is unavailable.
|
|
let result = webview.with_webview(|platform_webview| {
|
|
// On Linux this is the underlying `webkit2gtk::WebView`.
|
|
let webview = platform_webview.inner();
|
|
|
|
if let Some(settings) = WebViewExt::settings(&webview) {
|
|
settings.set_enable_media_stream(true);
|
|
}
|
|
|
|
// Deny-by-default: allow only mic/camera requests from a trusted app
|
|
// origin; deny everything else (still returning `true` so WebKit's
|
|
// auto-deny default does not also run). Non-`UserMedia` requests return
|
|
// `false` and keep their default handling.
|
|
webview.connect_permission_request(|wv, request| {
|
|
let Some(request) = request.downcast_ref::<UserMediaPermissionRequest>() else {
|
|
return false;
|
|
};
|
|
|
|
let uri = wv.uri().map(|u| u.to_string()).unwrap_or_default();
|
|
let for_device = request.is_for_audio_device() || request.is_for_video_device();
|
|
|
|
if for_device && is_trusted_media_origin(&uri) {
|
|
request.allow();
|
|
} else {
|
|
request.deny();
|
|
}
|
|
true
|
|
});
|
|
});
|
|
|
|
if let Err(error) = result {
|
|
eprintln!("buzz-desktop: could not enable WebKitGTK media capture: {error}");
|
|
}
|
|
}
|
|
|
|
/// No-op stub so shared startup code can call [`enable_media_capture`] on every
|
|
/// platform. macOS and Windows route media permissions through the OS.
|
|
#[cfg(not(target_os = "linux"))]
|
|
pub fn enable_media_capture<R: tauri::Runtime>(_webview: &tauri::Webview<R>) {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::is_trusted_media_origin;
|
|
|
|
#[test]
|
|
fn allows_production_app_origin() {
|
|
assert!(is_trusted_media_origin("tauri://localhost"));
|
|
assert!(is_trusted_media_origin(
|
|
"tauri://localhost/channels/general"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn denies_untrusted_origins() {
|
|
assert!(!is_trusted_media_origin(""));
|
|
assert!(!is_trusted_media_origin("https://evil.example.com"));
|
|
// Prefix look-alikes must not slip through.
|
|
assert!(!is_trusted_media_origin("tauri://localhost.evil.com"));
|
|
assert!(!is_trusted_media_origin("tauri://localhostfoo"));
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
#[test]
|
|
fn allows_dev_origin_in_debug_only() {
|
|
assert!(is_trusted_media_origin("http://localhost:1420"));
|
|
assert!(is_trusted_media_origin("http://localhost:1420/"));
|
|
// A different localhost port is still untrusted.
|
|
assert!(!is_trusted_media_origin("http://localhost:14200"));
|
|
assert!(!is_trusted_media_origin("http://localhost:3000"));
|
|
}
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
#[test]
|
|
fn denies_dev_origin_in_release() {
|
|
assert!(!is_trusted_media_origin("http://localhost:1420"));
|
|
}
|
|
}
|