diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 9beb2d069..fed84a162 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -1168,13 +1168,13 @@ ] }, "urls": { - "description": "Only allow URLs matching the given pattern list when set.", + "description": "Only allow URLs matching the given pattern list when set.\n\n By default it is a glob pattern, but can use the full [URLPattern] spec if the `url-pattern` feature is enabled.\n\n [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern", "type": [ "array", "null" ], "items": { - "$ref": "#/definitions/UrlPattern" + "$ref": "#/definitions/UrlScope" } } }, @@ -1194,13 +1194,13 @@ ] }, "urls": { - "description": "Only allow URLs matching the given pattern list when set.", + "description": "Only allow URLs matching the given pattern list when set.\n\n By default it is a glob pattern, but can use the full [URLPattern] spec if the `url-pattern` feature is enabled.\n\n [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern", "type": [ "array", "null" ], "items": { - "$ref": "#/definitions/UrlPattern" + "$ref": "#/definitions/UrlScope" } } }, @@ -1224,7 +1224,20 @@ } ] }, - "UrlPattern": { + "UrlScope": { + "description": "A scope to match URLs.", + "anyOf": [ + { + "description": "A [`GlobPattern`] to match URLs.", + "allOf": [ + { + "$ref": "#/definitions/GlobPattern" + } + ] + } + ] + }, + "GlobPattern": { "type": "string" }, "SecurityConfig": { diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 9beb2d069..fed84a162 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -1168,13 +1168,13 @@ ] }, "urls": { - "description": "Only allow URLs matching the given pattern list when set.", + "description": "Only allow URLs matching the given pattern list when set.\n\n By default it is a glob pattern, but can use the full [URLPattern] spec if the `url-pattern` feature is enabled.\n\n [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern", "type": [ "array", "null" ], "items": { - "$ref": "#/definitions/UrlPattern" + "$ref": "#/definitions/UrlScope" } } }, @@ -1194,13 +1194,13 @@ ] }, "urls": { - "description": "Only allow URLs matching the given pattern list when set.", + "description": "Only allow URLs matching the given pattern list when set.\n\n By default it is a glob pattern, but can use the full [URLPattern] spec if the `url-pattern` feature is enabled.\n\n [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern", "type": [ "array", "null" ], "items": { - "$ref": "#/definitions/UrlPattern" + "$ref": "#/definitions/UrlScope" } } }, @@ -1224,7 +1224,20 @@ } ] }, - "UrlPattern": { + "UrlScope": { + "description": "A scope to match URLs.", + "anyOf": [ + { + "description": "A [`GlobPattern`] to match URLs.", + "allOf": [ + { + "$ref": "#/definitions/GlobPattern" + } + ] + } + ] + }, + "GlobPattern": { "type": "string" }, "SecurityConfig": { diff --git a/crates/tauri-utils/Cargo.toml b/crates/tauri-utils/Cargo.toml index 939cb93ed..468b49506 100644 --- a/crates/tauri-utils/Cargo.toml +++ b/crates/tauri-utils/Cargo.toml @@ -75,3 +75,4 @@ config-json5 = ["json5"] config-toml = [] resources = ["walkdir"] html-manipulation = ["dep:html5ever", "dep:kuchiki"] +url-pattern = [] diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index b56c17a02..022ed8899 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -62,7 +62,7 @@ fn add_description(schema: Schema, description: impl Into) -> Schema { pub mod parse; use crate::{ - acl::capability::Capability, url::UrlPattern, TitleBarStyle, WindowEffect, WindowEffectState, + acl::capability::Capability, url::UrlScope, TitleBarStyle, WindowEffect, WindowEffectState, }; pub use self::parse::parse; @@ -1658,12 +1658,20 @@ pub enum OnNewWindow { /// Allow the window to be created using the default webview implementation. AllowDefault { /// Only allow URLs matching the given pattern list when set. - urls: Option>, + /// + /// By default it is a glob pattern, but can use the full [URLPattern] spec if the `url-pattern` feature is enabled. + /// + /// [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern + urls: Option>, }, /// Allow the window to be created using a Tauri window. AllowTauriWindow { /// Only allow URLs matching the given pattern list when set. - urls: Option>, + /// + /// By default it is a glob pattern, but can use the full [URLPattern] spec if the `url-pattern` feature is enabled. + /// + /// [URLPattern]: https://developer.mozilla.org/en-US/docs/Web/API/URLPattern + urls: Option>, }, /// Deny the window from being created. #[default] @@ -3557,11 +3565,11 @@ mod build { tokens.append_all(match self { Self::AllowDefault { urls } => { - let urls = opt_vec_lit(urls.as_ref(), url_pattern_lit); + let urls = opt_vec_lit(urls.as_ref(), url_scope_lit); quote! { #prefix::AllowDefault { urls: #urls } } } Self::AllowTauriWindow { urls } => { - let urls = opt_vec_lit(urls.as_ref(), url_pattern_lit); + let urls = opt_vec_lit(urls.as_ref(), url_scope_lit); quote! { #prefix::AllowTauriWindow { urls: #urls } } } Self::Deny => quote! { #prefix::Deny }, diff --git a/crates/tauri-utils/src/tokens.rs b/crates/tauri-utils/src/tokens.rs index b1fb41cca..8b237b5db 100644 --- a/crates/tauri-utils/src/tokens.rs +++ b/crates/tauri-utils/src/tokens.rs @@ -11,7 +11,7 @@ use quote::{quote, ToTokens}; use serde_json::Value as JsonValue; use url::Url; -use crate::url::UrlPattern; +use crate::url::{GlobPattern, UrlPattern, UrlScope}; /// Write a `TokenStream` of the `$struct`'s fields to the `$tokens`. /// @@ -100,6 +100,29 @@ pub fn url_pattern_lit(url: &UrlPattern) -> TokenStream { quote! { #url.parse().unwrap() } } +/// Creates a [`GlobPattern`] constructor `TokenStream`. +pub fn glob_pattern_lit(pattern: &GlobPattern) -> TokenStream { + let pattern = pattern.0.as_str(); + quote! { #pattern.parse().unwrap() } +} + +/// Creates a [`UrlScope`] constructor `TokenStream`. +pub fn url_scope_lit(url: &UrlScope) -> TokenStream { + let prefix = quote! { ::tauri::utils::url::UrlScope }; + match url { + #[cfg(feature = "url-pattern")] + UrlScope::UrlPattern(url) => { + let url = url.as_str(); + quote! { #prefix::UrlPattern(#url.parse().unwrap()) } + } + #[cfg(not(feature = "url-pattern"))] + UrlScope::Glob(glob) => { + let pattern = glob.0.as_str(); + quote! { #prefix::Glob(#pattern.parse().unwrap()) } + } + } +} + /// Create a map constructor, mapping keys and values with other `TokenStream`s. /// /// This function is pretty generic because the types of keys AND values get transformed. diff --git a/crates/tauri-utils/src/url.rs b/crates/tauri-utils/src/url.rs index 8f4340ab1..c8e38d142 100644 --- a/crates/tauri-utils/src/url.rs +++ b/crates/tauri-utils/src/url.rs @@ -9,6 +9,77 @@ use std::{str::FromStr, sync::Arc}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use url::Url; +/// A scope to match URLs. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(untagged)] +pub enum UrlScope { + /// A [`UrlPattern`] to match URLs. + /// + /// This is only available if the `url-pattern` feature is enabled. + #[cfg(feature = "url-pattern")] + UrlPattern(UrlPattern), + /// A [`GlobPattern`] to match URLs. + #[cfg(not(feature = "url-pattern"))] + Glob(GlobPattern), +} + +impl UrlScope { + /// Test if a given URL is matched by the scope. + pub fn test(&self, url: &Url) -> bool { + match self { + #[cfg(feature = "url-pattern")] + Self::UrlPattern(pattern) => pattern.test(url), + #[cfg(not(feature = "url-pattern"))] + Self::Glob(pattern) => pattern.0.matches(url.as_str()), + } + } +} + +/// A [`glob::Pattern`] to match URLs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GlobPattern(pub(crate) glob::Pattern); + +#[cfg(feature = "schema")] +impl schemars::JsonSchema for GlobPattern { + fn schema_name() -> String { + "GlobPattern".to_string() + } + + fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + String::json_schema(_gen) + } +} + +impl FromStr for GlobPattern { + type Err = glob::PatternError; + + fn from_str(s: &str) -> std::result::Result { + glob::Pattern::new(s).map(Self) + } +} + +impl Serialize for GlobPattern { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(&self.0.as_str()) + } +} + +impl<'de> Deserialize<'de> for GlobPattern { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + glob::Pattern::new(&s) + .map(Self) + .map_err(serde::de::Error::custom) + } +} + /// UrlPattern to match URLs. #[derive(Debug, Clone)] pub struct UrlPattern(Arc, String); @@ -73,7 +144,7 @@ impl UrlPattern { &self.1 } - /// Test if a given URL matches the pattern. + /// Test if a given URL is matched by the pattern. pub fn test(&self, url: &Url) -> bool { self .0 diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index 7d453f8e7..fd7146ed4 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -221,6 +221,7 @@ image-png = ["image/png"] macos-proxy = ["tauri-runtime-wry?/macos-proxy"] dynamic-acl = [] specta = ["dep:specta"] +url-pattern = ["tauri-utils/url-pattern"] [[example]] name = "commands" diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 68a58edcc..965fd72ac 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -37,6 +37,7 @@ //! - **macos-proxy**: Adds support for [`WebviewBuilder::proxy_url`] on macOS. Requires macOS 14+. //! - **specta**: Add support for [`specta::specta`](https://docs.rs/specta/%5E2.0.0-rc.9/specta/attr.specta.html) with Tauri arguments such as [`State`](crate::State), [`Window`](crate::Window) and [`AppHandle`](crate::AppHandle) //! - **dynamic-acl** *(enabled by default)*: Enables you to add ACLs at runtime, notably it enables the [`Manager::add_capability`] function. +//! - **url-pattern**: Enables using the [URLPattern] spec for URL scopes on JavaScript APIs. //! //! ## Cargo allowlist features //! diff --git a/packages/api/src/webview.ts b/packages/api/src/webview.ts index d770213cc..68ed230a6 100644 --- a/packages/api/src/webview.ts +++ b/packages/api/src/webview.ts @@ -906,14 +906,14 @@ interface WebviewOptions { * * 'allowDefault' lets the webview open the URL using the native implementation. * 'allowTauriWindow' creates a Tauri window to load the URL. - * Additionally you can provide a list of filters to only allow URLs matching certain {@link https://developer.mozilla.org/en-US/docs/Web/API/URLPattern|URL patterns}. + * Additionally you can provide a list of filters to only allow URLs matching certain glob patterns. + * It can leverage the {@link https://developer.mozilla.org/en-US/docs/Web/API/URLPattern|URL pattern spec} if the `url-pattern` Cargo feature is enabled. * * A new window is requested to be opened by the {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open|window.open API}. * * ## Platform-specific * * - **Android / iOS**: Not supported. - * * */ onNewWindow?: OnNewWindow }