Explorar el Código

Merge remote-tracking branch 'origin/dev' into dev

Lucas Nogueira hace 3 años
padre
commit
9e31dd5ccb

+ 5 - 0
.changes/window-request-handler.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Added `WindowBuilder::on_web_resource_request`, which allows customizing the tauri custom protocol response.

+ 27 - 6
core/tauri-runtime-wry/src/lib.rs

@@ -219,10 +219,10 @@ struct HttpRequestWrapper(HttpRequest);
 
 impl From<&WryHttpRequest> for HttpRequestWrapper {
   fn from(req: &WryHttpRequest) -> Self {
-    Self(HttpRequest {
-      body: req.body.clone(),
-      head: HttpRequestPartsWrapper::from(req.head.clone()).0,
-    })
+    Self(HttpRequest::new_internal(
+      HttpRequestPartsWrapper::from(req.head.clone()).0,
+      req.body.clone(),
+    ))
   }
 }
 
@@ -242,9 +242,10 @@ impl From<HttpResponseParts> for HttpResponsePartsWrapper {
 struct HttpResponseWrapper(WryHttpResponse);
 impl From<HttpResponse> for HttpResponseWrapper {
   fn from(response: HttpResponse) -> Self {
+    let (parts, body) = response.into_parts();
     Self(WryHttpResponse {
-      body: response.body,
-      head: HttpResponsePartsWrapper::from(response.head).0,
+      body,
+      head: HttpResponsePartsWrapper::from(parts).0,
     })
   }
 }
@@ -1466,6 +1467,26 @@ pub struct Wry {
   tray_context: TrayContext,
 }
 
+impl fmt::Debug for Wry {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    let mut d = f.debug_struct("Wry");
+    d.field("main_thread_id", &self.main_thread_id)
+      .field("global_shortcut_manager", &self.global_shortcut_manager)
+      .field(
+        "global_shortcut_manager_handle",
+        &self.global_shortcut_manager_handle,
+      )
+      .field("clipboard_manager", &self.clipboard_manager)
+      .field("clipboard_manager_handle", &self.clipboard_manager_handle)
+      .field("event_loop", &self.event_loop)
+      .field("windows", &self.windows)
+      .field("web_context", &self.web_context);
+    #[cfg(feature = "system-tray")]
+    d.field("tray_context", &self.tray_context);
+    d.finish()
+  }
+}
+
 /// A handle to the Wry runtime.
 #[derive(Debug, Clone)]
 pub struct WryHandle {

+ 17 - 2
core/tauri-runtime/src/http/request.rs

@@ -17,8 +17,8 @@ use super::{
 ///
 /// - **Linux:** Headers are not exposed.
 pub struct Request {
-  pub head: RequestParts,
-  pub body: Vec<u8>,
+  head: RequestParts,
+  body: Vec<u8>,
 }
 
 /// Component parts of an HTTP `Request`
@@ -47,6 +47,17 @@ impl Request {
     }
   }
 
+  /// Creates a new `Request` with the given head and body.
+  ///
+  /// # Stability
+  ///
+  /// This API is used internally. It may have breaking changes in the future.
+  #[inline]
+  #[doc(hidden)]
+  pub fn new_internal(head: RequestParts, body: Vec<u8>) -> Request {
+    Request { head, body }
+  }
+
   /// Returns a reference to the associated HTTP method.
   #[inline]
   pub fn method(&self) -> &Method {
@@ -72,6 +83,10 @@ impl Request {
   }
 
   /// Consumes the request returning the head and body RequestParts.
+  ///
+  /// # Stability
+  ///
+  /// This API is used internally. It may have breaking changes in the future.
   #[inline]
   pub fn into_parts(self) -> (RequestParts, Vec<u8>) {
     (self.head, self.body)

+ 44 - 9
core/tauri-runtime/src/http/response.rs

@@ -32,8 +32,8 @@ type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
 /// ```
 ///
 pub struct Response {
-  pub head: ResponseParts,
-  pub body: Vec<u8>,
+  head: ResponseParts,
+  body: Vec<u8>,
 }
 
 /// Component parts of an HTTP `Response`
@@ -42,16 +42,16 @@ pub struct Response {
 /// header fields.
 #[derive(Clone)]
 pub struct ResponseParts {
-  /// The response's status
+  /// The response's status.
   pub status: StatusCode,
 
-  /// The response's version
+  /// The response's version.
   pub version: Version,
 
-  /// The response's headers
+  /// The response's headers.
   pub headers: HeaderMap<HeaderValue>,
 
-  /// The response's mimetype type
+  /// The response's mimetype type.
   pub mimetype: Option<String>,
 }
 
@@ -74,16 +74,39 @@ impl Response {
     }
   }
 
-  /// Returns the `StatusCode`.
+  /// Consumes the response returning the head and body ResponseParts.
+  ///
+  /// # Stability
+  ///
+  /// This API is used internally. It may have breaking changes in the future.
+  #[inline]
+  #[doc(hidden)]
+  pub fn into_parts(self) -> (ResponseParts, Vec<u8>) {
+    (self.head, self.body)
+  }
+
+  /// Sets the status code.
+  #[inline]
+  pub fn set_status(&mut self, status: StatusCode) {
+    self.head.status = status;
+  }
+
+  /// Returns the [`StatusCode`].
   #[inline]
   pub fn status(&self) -> StatusCode {
     self.head.status
   }
 
+  /// Sets the mimetype.
+  #[inline]
+  pub fn set_mimetype(&mut self, mimetype: Option<String>) {
+    self.head.mimetype = mimetype;
+  }
+
   /// Returns a reference to the mime type.
   #[inline]
-  pub fn mimetype(&self) -> Option<String> {
-    self.head.mimetype.clone()
+  pub fn mimetype(&self) -> Option<&String> {
+    self.head.mimetype.as_ref()
   }
 
   /// Returns a reference to the associated version.
@@ -92,12 +115,24 @@ impl Response {
     self.head.version
   }
 
+  /// Returns a mutable reference to the associated header field map.
+  #[inline]
+  pub fn headers_mut(&mut self) -> &mut HeaderMap<HeaderValue> {
+    &mut self.head.headers
+  }
+
   /// Returns a reference to the associated header field map.
   #[inline]
   pub fn headers(&self) -> &HeaderMap<HeaderValue> {
     &self.head.headers
   }
 
+  /// Returns a mutable reference to the associated HTTP body.
+  #[inline]
+  pub fn body_mut(&mut self) -> &mut Vec<u8> {
+    &mut self.body
+  }
+
   /// Returns a reference to the associated HTTP body.
   #[inline]
   pub fn body(&self) -> &Vec<u8> {

+ 1 - 1
core/tauri-runtime/src/lib.rs

@@ -311,7 +311,7 @@ pub trait ClipboardManager: Debug + Clone + Send + Sync {
 }
 
 /// The webview runtime interface.
-pub trait Runtime: Sized + 'static {
+pub trait Runtime: Debug + Sized + 'static {
   /// The message dispatcher.
   type Dispatcher: Dispatch<Runtime = Self>;
   /// The runtime handle type.

+ 11 - 12
core/tauri/src/app.rs

@@ -23,6 +23,7 @@ use crate::{
   sealed::{ManagerBase, RuntimeOrDispatch},
   utils::config::{Config, WindowUrl},
   utils::{assets::Assets, Env},
+  window::WindowBuilder,
   Context, Invoke, InvokeError, InvokeResponse, Manager, Scopes, StateManager, Window,
 };
 
@@ -386,15 +387,12 @@ macro_rules! shared_app_impl {
           WebviewAttributes,
         ),
       {
-        let (window_builder, webview_attributes) = setup(
-          <R::Dispatcher as Dispatch>::WindowBuilder::new(),
-          WebviewAttributes::new(url),
-        );
-        self.create_new_window(PendingWindow::new(
-          window_builder,
-          webview_attributes,
-          label,
-        )?)
+        let mut builder = WindowBuilder::<R>::new(self, label, url);
+        let (window_builder, webview_attributes) =
+          setup(builder.window_builder, builder.webview_attributes);
+        builder.window_builder = window_builder;
+        builder.webview_attributes = webview_attributes;
+        builder.build()
       }
 
       #[cfg(feature = "system-tray")]
@@ -1310,9 +1308,10 @@ impl<R: Runtime> Builder<R> {
     let mut main_window = None;
 
     for pending in self.pending_windows {
-      let pending = app
-        .manager
-        .prepare_window(app.handle.clone(), pending, &window_labels)?;
+      let pending =
+        app
+          .manager
+          .prepare_window(app.handle.clone(), pending, &window_labels, None)?;
       let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
       let _window = app.manager.attach_window(app.handle(), detached);
       #[cfg(feature = "updater")]

+ 1 - 41
core/tauri/src/lib.rs

@@ -174,7 +174,6 @@ pub type Result<T> = std::result::Result<T, Error>;
 /// A task to run on the main thread.
 pub type SyncTask = Box<dyn FnOnce() + Send>;
 
-use crate::runtime::window::PendingWindow;
 use serde::Serialize;
 use std::{collections::HashMap, fmt, sync::Arc};
 
@@ -600,7 +599,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
 /// Prevent implementation details from leaking out of the [`Manager`] trait.
 pub(crate) mod sealed {
   use crate::{app::AppHandle, manager::WindowManager};
-  use tauri_runtime::{Runtime, RuntimeHandle};
+  use tauri_runtime::Runtime;
 
   /// A running [`Runtime`] or a dispatcher to it.
   pub enum RuntimeOrDispatch<'r, R: Runtime> {
@@ -614,51 +613,12 @@ pub(crate) mod sealed {
     Dispatch(R::Dispatcher),
   }
 
-  #[derive(Clone, serde::Serialize)]
-  struct WindowCreatedEvent {
-    label: String,
-  }
-
   /// Managed handle to the application runtime.
   pub trait ManagerBase<R: Runtime> {
     /// The manager behind the [`Managed`] item.
     fn manager(&self) -> &WindowManager<R>;
-
     fn runtime(&self) -> RuntimeOrDispatch<'_, R>;
     fn managed_app_handle(&self) -> AppHandle<R>;
-
-    /// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
-    fn create_new_window(
-      &self,
-      pending: crate::PendingWindow<R>,
-    ) -> crate::Result<crate::Window<R>> {
-      use crate::runtime::Dispatch;
-      let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
-      let pending = self
-        .manager()
-        .prepare_window(self.managed_app_handle(), pending, &labels)?;
-      let window = match self.runtime() {
-        RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
-        RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending),
-        RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending),
-      }
-      .map(|window| {
-        self
-          .manager()
-          .attach_window(self.managed_app_handle(), window)
-      })?;
-
-      self.manager().emit_filter(
-        "tauri://window-created",
-        None,
-        Some(WindowCreatedEvent {
-          label: window.label().into(),
-        }),
-        |w| w != &window,
-      )?;
-
-      Ok(window)
-    }
   }
 }
 

+ 47 - 7
core/tauri/src/manager.rs

@@ -129,11 +129,16 @@ fn set_csp<R: Runtime>(
   let csp = Csp::DirectiveMap(csp).to_string();
   #[cfg(target_os = "linux")]
   {
-    *asset = asset.replacen(tauri_utils::html::CSP_TOKEN, &csp, 1);
+    *asset = set_html_csp(asset, &csp);
   }
   csp
 }
 
+#[cfg(target_os = "linux")]
+fn set_html_csp(html: &str, csp: &str) -> String {
+  html.replacen(tauri_utils::html::CSP_TOKEN, csp, 1)
+}
+
 // inspired by https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/alloc/src/str.rs#L260-L297
 fn replace_with_callback<F: FnMut() -> String>(
   original: &str,
@@ -383,6 +388,9 @@ impl<R: Runtime> WindowManager<R> {
     label: &str,
     window_labels: &[String],
     app_handle: AppHandle<R>,
+    web_resource_request_handler: Option<
+      Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>,
+    >,
   ) -> crate::Result<PendingWindow<R>> {
     let is_init_global = self.inner.config.build.with_global_tauri;
     let plugin_init = self
@@ -470,7 +478,10 @@ impl<R: Runtime> WindowManager<R> {
     }
 
     if !registered_scheme_protocols.contains(&"tauri".into()) {
-      pending.register_uri_scheme_protocol("tauri", self.prepare_uri_scheme_protocol());
+      pending.register_uri_scheme_protocol(
+        "tauri",
+        self.prepare_uri_scheme_protocol(web_resource_request_handler),
+      );
       registered_scheme_protocols.push("tauri".into());
     }
 
@@ -788,6 +799,9 @@ impl<R: Runtime> WindowManager<R> {
   #[allow(clippy::type_complexity)]
   fn prepare_uri_scheme_protocol(
     &self,
+    web_resource_request_handler: Option<
+      Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>,
+    >,
   ) -> Box<dyn Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync>
   {
     let manager = self.clone();
@@ -801,11 +815,28 @@ impl<R: Runtime> WindowManager<R> {
         .to_string()
         .replace("tauri://localhost", "");
       let asset = manager.get_asset(path)?;
-      let mut response = HttpResponseBuilder::new().mimetype(&asset.mime_type);
-      if let Some(csp) = asset.csp_header {
-        response = response.header("Content-Security-Policy", csp);
+      let mut builder = HttpResponseBuilder::new().mimetype(&asset.mime_type);
+      if let Some(csp) = &asset.csp_header {
+        builder = builder.header("Content-Security-Policy", csp);
+      }
+      let mut response = builder.body(asset.bytes)?;
+      if let Some(handler) = &web_resource_request_handler {
+        handler(request, &mut response);
+
+        // if it's an HTML file, we need to set the CSP meta tag on Linux
+        #[cfg(target_os = "linux")]
+        if let (Some(original_csp), Some(response_csp)) = (
+          asset.csp_header,
+          response.headers().get("Content-Security_Policy"),
+        ) {
+          let response_csp = String::from_utf8_lossy(response_csp.as_bytes());
+          if response_csp != original_csp {
+            let body = set_html_csp(&String::from_utf8_lossy(response.body()), &response_csp);
+            *response.body_mut() = body.as_bytes().to_vec();
+          }
+        }
       }
-      response.body(asset.bytes)
+      Ok(response)
     })
   }
 
@@ -990,6 +1021,9 @@ impl<R: Runtime> WindowManager<R> {
     app_handle: AppHandle<R>,
     mut pending: PendingWindow<R>,
     window_labels: &[String],
+    web_resource_request_handler: Option<
+      Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>,
+    >,
   ) -> crate::Result<PendingWindow<R>> {
     if self.windows_lock().contains_key(&pending.label) {
       return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
@@ -1043,7 +1077,13 @@ impl<R: Runtime> WindowManager<R> {
 
     if is_local {
       let label = pending.label.clone();
-      pending = self.prepare_pending_window(pending, &label, window_labels, app_handle.clone())?;
+      pending = self.prepare_pending_window(
+        pending,
+        &label,
+        window_labels,
+        app_handle.clone(),
+        web_resource_request_handler,
+      )?;
       pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle.clone()));
     }
 

+ 1 - 0
core/tauri/src/test/mock_runtime.rs

@@ -489,6 +489,7 @@ impl TrayHandle for MockTrayHandler {
   }
 }
 
+#[derive(Debug)]
 pub struct MockRuntime {
   pub context: RuntimeContext,
   global_shortcut_manager: MockGlobalShortcutManager,

+ 102 - 6
core/tauri/src/window.rs

@@ -15,6 +15,7 @@ use crate::{
   hooks::{InvokePayload, InvokeResponder},
   manager::WindowManager,
   runtime::{
+    http::{Request as HttpRequest, Response as HttpResponse},
     menu::Menu,
     monitor::Monitor as RuntimeMonitor,
     webview::{WebviewAttributes, WindowBuilder as _},
@@ -22,7 +23,7 @@ use crate::{
       dpi::{PhysicalPosition, PhysicalSize, Position, Size},
       DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
     },
-    Dispatch, Runtime, UserAttentionType,
+    Dispatch, Runtime, RuntimeHandle, UserAttentionType,
   },
   sealed::ManagerBase,
   sealed::RuntimeOrDispatch,
@@ -37,11 +38,17 @@ use windows::Win32::Foundation::HWND;
 use tauri_macros::default_runtime;
 
 use std::{
+  fmt,
   hash::{Hash, Hasher},
   path::PathBuf,
   sync::Arc,
 };
 
+#[derive(Clone, Serialize)]
+struct WindowCreatedEvent {
+  label: String,
+}
+
 /// Monitor descriptor.
 #[derive(Debug, Clone, Serialize)]
 #[serde(rename_all = "camelCase")]
@@ -98,14 +105,27 @@ pub enum RuntimeHandleOrDispatch<R: Runtime> {
 
 /// A builder for a webview window managed by Tauri.
 #[default_runtime(crate::Wry, wry)]
-#[derive(Debug)]
 pub struct WindowBuilder<R: Runtime> {
   manager: WindowManager<R>,
   runtime: RuntimeHandleOrDispatch<R>,
   app_handle: AppHandle<R>,
   label: String,
   pub(crate) window_builder: <R::Dispatcher as Dispatch>::WindowBuilder,
-  webview_attributes: WebviewAttributes,
+  pub(crate) webview_attributes: WebviewAttributes,
+  web_resource_request_handler: Option<Box<dyn Fn(&HttpRequest, &mut HttpResponse) + Send + Sync>>,
+}
+
+impl<R: Runtime> fmt::Debug for WindowBuilder<R> {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    f.debug_struct("WindowBuilder")
+      .field("manager", &self.manager)
+      .field("runtime", &self.runtime)
+      .field("app_handle", &self.app_handle)
+      .field("label", &self.label)
+      .field("window_builder", &self.window_builder)
+      .field("webview_attributes", &self.webview_attributes)
+      .finish()
+  }
 }
 
 impl<R: Runtime> ManagerBase<R> for WindowBuilder<R> {
@@ -141,16 +161,92 @@ impl<R: Runtime> WindowBuilder<R> {
       label: label.into(),
       window_builder: <R::Dispatcher as Dispatch>::WindowBuilder::new(),
       webview_attributes: WebviewAttributes::new(url),
+      web_resource_request_handler: None,
     }
   }
 
+  /// Defines a closure to be executed when the webview makes an HTTP request for a web resource, allowing you to modify the response.
+  ///
+  /// Currently only implemented for the `tauri` URI protocol.
+  ///
+  /// **NOTE:** Currently this is **not** executed when using external URLs such as a development server,
+  /// but it might be implemented in the future. **Always** check the request URL.
+  ///
+  /// # Examples
+  ///
+  /// ```rust,no_run
+  /// use tauri::{
+  ///   utils::config::{Csp, CspDirectiveSources, WindowUrl},
+  ///   runtime::http::header::HeaderValue,
+  ///   window::WindowBuilder,
+  /// };
+  /// use std::collections::HashMap;
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     WindowBuilder::new(app, "core", WindowUrl::App("index.html".into()))
+  ///       .on_web_resource_request(|request, response| {
+  ///         if request.uri().starts_with("tauri://") {
+  ///           // if we have a CSP header, Tauri is loading an HTML file
+  ///           //  for this example, let's dynamically change the CSP
+  ///           if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") {
+  ///             // use the tauri helper to parse the CSP policy to a map
+  ///             let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();
+  ///             csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'");
+  ///             // use the tauri helper to get a CSP string from the map
+  ///             let csp_string = Csp::from(csp_map).to_string();
+  ///             *csp = HeaderValue::from_str(&csp_string).unwrap();
+  ///           }
+  ///         }
+  ///       })
+  ///       .build()
+  ///       .unwrap();
+  ///     Ok(())
+  ///   });
+  /// ```
+  pub fn on_web_resource_request<F: Fn(&HttpRequest, &mut HttpResponse) + Send + Sync + 'static>(
+    mut self,
+    f: F,
+  ) -> Self {
+    self.web_resource_request_handler.replace(Box::new(f));
+    self
+  }
+
   /// Creates a new webview window.
-  pub fn build(self) -> crate::Result<Window<R>> {
-    self.create_new_window(PendingWindow::new(
+  pub fn build(mut self) -> crate::Result<Window<R>> {
+    let web_resource_request_handler = self.web_resource_request_handler.take();
+    let pending = PendingWindow::new(
       self.window_builder.clone(),
       self.webview_attributes.clone(),
       self.label.clone(),
-    )?)
+    )?;
+    let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
+    let pending = self.manager().prepare_window(
+      self.managed_app_handle(),
+      pending,
+      &labels,
+      web_resource_request_handler,
+    )?;
+    let window = match self.runtime() {
+      RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
+      RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending),
+      RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending),
+    }
+    .map(|window| {
+      self
+        .manager()
+        .attach_window(self.managed_app_handle(), window)
+    })?;
+
+    self.manager().emit_filter(
+      "tauri://window-created",
+      None,
+      Some(WindowCreatedEvent {
+        label: window.label().into(),
+      }),
+      |w| w != &window,
+    )?;
+
+    Ok(window)
   }
 
   // --------------------------------------------- Window builder ---------------------------------------------