Compare commits

...

12 Commits
api-v2 ... 1.1

Author SHA1 Message Date
Lucas Nogueira
1023326269 fix(ci): update getPublishedVersion script 2023-05-03 13:02:44 -03:00
Lucas Nogueira
1cc51a2195 fix(ci): update NPM token 2023-05-03 12:59:48 -03:00
Lucas Nogueira
d9f6ddccac fix(ci): remove covector filterPackages 2023-05-03 12:33:48 -03:00
Lucas Nogueira
b6e866c6d2 chore: trigger release 2023-05-03 12:32:07 -03:00
Lucas Nogueira
6f07f907de chore: remove println 2023-05-02 10:15:15 -03:00
Lucas Nogueira
b352f69a05 fix(core): IPC remote domain check bypassed by isolation iframe usage (#6691) 2023-05-02 10:14:32 -03:00
Lucas Nogueira
58ea0b4526 feat(core): block remote URLs from accessing the IPC
This was cherry picked from ee71c31fd0, keeping only the logic to block remote URLs from using the IPC.
PR: #5918
2023-04-12 11:50:38 -03:00
Lucas Nogueira
e4159d4a1b run covector version 2022-12-22 10:59:18 -03:00
Amr Bashir
2654c0f49d Merge pull request from GHSA-6mv3-wm7j-h4w5
* fix(core): use `require_literal_separator` when matching paths

* document the need for `require_literal_separator`

* use `require_literal_leading_dot`
2022-12-22 10:58:43 -03:00
Lucas Nogueira
7b64529f4e run covector version 2022-11-08 12:07:17 -03:00
Lucas Nogueira
598efcc9cb prepare CI for hotfix publishes 2022-11-08 12:06:35 -03:00
Amr Bashir
e4dc5bedbb fix(core): escape glob characters in drop/dialogs , closes #5234 (#5237)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
2022-11-08 12:05:52 -03:00
22 changed files with 479 additions and 92 deletions

View File

@@ -5,7 +5,7 @@
"rust": {
"errorOnVersionRange": "^2.0.0-0",
"version": true,
"getPublishedVersion": "cargo search ${ pkgFile.pkg.package.name } --limit 1 | sed -nE \"s/^[^\\\"]*\\\"//; s/\\\".*//1p\"",
"getPublishedVersion": "node ../../.scripts/covector/package-latest-version.js cargo ${ pkgFile.pkg.package.name } ${ pkgFile.pkg.package.version }",
"prepublish": [
"sudo apt-get update",
"sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libayatana-appindicator3-dev librsvg2-dev patchelf",
@@ -56,7 +56,6 @@
}
],
"postpublish": [
"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f",
"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f",
"git push --tags -f"
],
@@ -70,7 +69,7 @@
"javascript": {
"errorOnVersionRange": "^2.0.0-0",
"version": true,
"getPublishedVersion": "npm view ${ pkgFile.pkg.name } version",
"getPublishedVersion": "node ../../.scripts/covector/package-latest-version.js npm ${ pkgFile.pkg.name } ${ pkgFile.pkg.version }",
"prepublish": [
{
"command": "yarn",
@@ -116,7 +115,6 @@
}
],
"postpublish": [
"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f",
"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f",
"git push --tags -f"
]
@@ -231,7 +229,7 @@
"cli.js": {
"path": "./tooling/cli/node",
"manager": "javascript",
"dependencies": ["cli.rs"],
"getPublishedVersion": "node ../../../.scripts/covector/package-latest-version.js npm ${ pkgFile.pkg.name } ${ pkgFile.pkg.version }",
"postversion": "node ../../../.scripts/covector/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }",
"prepublish": [],
"publish": [],

View File

@@ -136,7 +136,7 @@ jobs:
uses: jbolda/covector/packages/action@covector-v0
id: covector
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -396,5 +396,5 @@ jobs:
npm publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
RELEASE_ID: ${{ github.event.client_payload.releaseId }}

58
.github/workflows/publish-hotfix.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
# Copyright 2019-2021 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: version or publish
on:
push:
branches:
- '1.*'
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 65
outputs:
change: ${{ steps.covector.outputs.change }}
commandRan: ${{ steps.covector.outputs.commandRan }}
successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: 14
registry-url: 'https://registry.npmjs.org'
cache: yarn
cache-dependency-path: tooling/*/yarn.lock
- name: cargo login
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
- name: git config
run: |
git config --global user.name "${{ github.event.pusher.name }}"
git config --global user.email "${{ github.event.pusher.email }}"
- name: covector version or publish (publish when no change files present)
uses: jbolda/covector/packages/action@covector-v0
id: covector
env:
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
command: 'version-or-publish'
createRelease: true
- name: Trigger cli.js publishing workflow
if: |
steps.covector.outputs.successfulPublish == 'true' &&
contains(steps.covector.outputs.packagesPublished, 'cli.rs')
uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.TAURI_BOT_PAT }}
repository: tauri-apps/tauri
event-type: publish-clijs

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
// Copyright 2019-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
/*
This script is solely intended to be run as part of the `covector publish` step to
check the latest version of a crate, considering the current minor version.
*/
const https = require('https')
const kind = process.argv[2]
const packageName = process.argv[3]
const packageVersion = process.argv[4]
const target = packageVersion.substring(0, packageVersion.lastIndexOf('.'))
let url = null
switch (kind) {
case 'cargo':
url = `https://crates.io/api/v1/crates/${packageName}`
break;
case 'npm':
url = `https://registry.npmjs.org/${packageName}`
break;
default:
throw new Error('unexpected kind ' + kind)
}
const options = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'tauri (https://github.com/tauri-apps/tauri)'
}
}
https.get(url, options, (response) => {
let chunks = []
response.on('data', function (chunk) {
chunks.push(chunk)
})
response.on('end', function () {
const data = JSON.parse(chunks.join(''))
if (kind === 'cargo') {
const versions = data.versions.filter(v => v.num.startsWith(target))
console.log(versions.length ? versions[0].num : '0.0.0')
} else if (kind === 'npm') {
const versions = Object.keys(data.versions).filter(v => v.startsWith(target))
console.log(versions[versions.length - 1] || '0.0.0')
}
})
})

View File

@@ -54,5 +54,5 @@ fn override_msvcrt_lib() {
f.write_all(bytes).unwrap();
}
// Add the output directory to the native library path.
println!("cargo:rustc-link-search=native={}", out_dir);
println!("cargo:rustc-link-search=native={out_dir}");
}

View File

@@ -1,5 +1,10 @@
# Changelog
## \[0.11.2]
- Block remote URLs from accessing the IPC.
- [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12
## \[0.11.1]
- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-runtime-wry"
version = "0.11.1"
version = "0.11.2"
authors = [ "Tauri Programme within The Commons Conservancy" ]
categories = [ "gui", "web-programming" ]
license = "Apache-2.0 OR MIT"
@@ -14,11 +14,12 @@ readme = "README.md"
[dependencies]
wry = { version = "0.21", default-features = false, features = [ "file-drop", "protocol" ] }
tauri-runtime = { version = "0.11.1", path = "../tauri-runtime" }
tauri-runtime = { version = "0.11.2", path = "../tauri-runtime" }
tauri-utils = { version = "1.1.1", path = "../tauri-utils" }
uuid = { version = "1", features = [ "v4" ] }
rand = "0.8"
raw-window-handle = "0.5"
url = "2"
[target."cfg(windows)".dependencies]
webview2-com = "0.19.1"

View File

@@ -36,6 +36,7 @@ use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
use url::Url;
use uuid::Uuid;
use wry::{
application::{
@@ -210,6 +211,7 @@ impl<T: UserEvent> Context<T> {
impl<T: UserEvent> Context<T> {
fn create_webview(&self, pending: PendingWindow<T, Wry<T>>) -> Result<DetachedWindow<T, Wry<T>>> {
let label = pending.label.clone();
let current_url = pending.current_url.clone();
let menu_ids = pending.menu_ids.clone();
let js_event_listeners = pending.js_event_listeners.clone();
let context = self.clone();
@@ -231,6 +233,7 @@ impl<T: UserEvent> Context<T> {
};
Ok(DetachedWindow {
label,
current_url,
dispatcher,
menu_ids,
js_event_listeners,
@@ -1864,6 +1867,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>> {
let label = pending.label.clone();
let current_url = pending.current_url.clone();
let menu_ids = pending.menu_ids.clone();
let js_event_listeners = pending.js_event_listeners.clone();
let window_id = rand::random();
@@ -1890,6 +1894,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
Ok(DetachedWindow {
label,
current_url,
dispatcher,
menu_ids,
js_event_listeners,
@@ -2848,7 +2853,7 @@ fn create_webview<T: UserEvent>(
mut window_builder,
ipc_handler,
label,
url,
current_url,
menu_ids,
js_event_listeners,
..
@@ -2888,17 +2893,23 @@ fn create_webview<T: UserEvent>(
}
let mut webview_builder = WebViewBuilder::new(window)
.map_err(|e| Error::CreateWebview(Box::new(e)))?
.with_url(&url)
.with_url(current_url.lock().unwrap().as_str())
.unwrap() // safe to unwrap because we validate the URL beforehand
.with_transparent(is_window_transparent);
if webview_attributes.file_drop_handler_enabled {
webview_builder = webview_builder
.with_file_drop_handler(create_file_drop_handler(window_event_listeners.clone()));
}
if let Some(navigation_handler) = pending.navigation_handler {
webview_builder = webview_builder.with_navigation_handler(move |url| {
Url::parse(&url).map(&navigation_handler).unwrap_or(true)
});
}
if let Some(handler) = ipc_handler {
webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(
context,
label.clone(),
current_url,
menu_ids,
js_event_listeners,
handler,
@@ -3002,6 +3013,7 @@ fn create_webview<T: UserEvent>(
fn create_ipc_handler<T: UserEvent>(
context: Context<T>,
label: String,
current_url: Arc<Mutex<Url>>,
menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
handler: WebviewIpcHandler<T, Wry<T>>,
@@ -3010,6 +3022,7 @@ fn create_ipc_handler<T: UserEvent>(
let window_id = context.webview_id_map.get(&window.id()).unwrap();
handler(
DetachedWindow {
current_url: current_url.clone(),
dispatcher: WryDispatcher {
window_id,
context: context.clone(),

View File

@@ -1,5 +1,10 @@
# Changelog
## \[0.11.2]
- Block remote URLs from accessing the IPC.
- [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12
## \[0.11.1]
- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.

View File

@@ -1,6 +1,6 @@
[package]
name = "tauri-runtime"
version = "0.11.1"
version = "0.11.2"
authors = [ "Tauri Programme within The Commons Conservancy" ]
categories = [ "gui", "web-programming" ]
license = "Apache-2.0 OR MIT"
@@ -33,6 +33,7 @@ http-range = "0.1.4"
infer = "0.7"
raw-window-handle = "0.5"
rand = "0.8"
url = "2"
[target."cfg(windows)".dependencies]
webview2-com = "0.19.1"

View File

@@ -12,6 +12,7 @@ use crate::{
};
use serde::{Deserialize, Deserializer, Serialize};
use tauri_utils::{config::WindowConfig, Theme};
use url::Url;
use std::{
collections::{HashMap, HashSet},
@@ -224,14 +225,17 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
/// How to handle IPC calls on the webview window.
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
/// The resolved URL to load on the webview.
pub url: String,
/// Maps runtime id to a string menu id.
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
/// A HashMap mapping JS event names with associated listener ids.
pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
/// A handler to decide if incoming url is allowed to navigate.
pub navigation_handler: Option<Box<dyn Fn(Url) -> bool + Send>>,
/// The current webview URL.
pub current_url: Arc<Mutex<Url>>,
}
pub fn is_label_valid(label: &str) -> bool {
@@ -268,9 +272,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
uri_scheme_protocols: Default::default(),
label,
ipc_handler: None,
url: "tauri://localhost".to_string(),
menu_ids: Arc::new(Mutex::new(menu_ids)),
js_event_listeners: Default::default(),
navigation_handler: Default::default(),
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
})
}
}
@@ -297,9 +302,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
uri_scheme_protocols: Default::default(),
label,
ipc_handler: None,
url: "tauri://localhost".to_string(),
menu_ids: Arc::new(Mutex::new(menu_ids)),
js_event_listeners: Default::default(),
navigation_handler: Default::default(),
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
})
}
}
@@ -340,6 +346,9 @@ pub struct JsEventListenerKey {
/// A webview window that is not yet managed by Tauri.
#[derive(Debug)]
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
/// The current webview URL.
pub current_url: Arc<Mutex<Url>>,
/// Name of the window
pub label: String,
@@ -356,6 +365,7 @@ pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
fn clone(&self) -> Self {
Self {
current_url: self.current_url.clone(),
label: self.label.clone(),
dispatcher: self.dispatcher.clone(),
menu_ids: self.menu_ids.clone(),

View File

@@ -1,5 +1,20 @@
# Changelog
## \[1.1.4]
- Block remote URLs from accessing the IPC.
- [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12
## \[1.1.3]
- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`.
- [2654c0f4](https://www.github.com/tauri-apps/tauri/commit/2654c0f49da23434d36447d0908fa24e61ff5e4e) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22
## \[1.1.2]
- Escape glob special characters in files/directories when dropping files or using the open/save dialogs.
- [e4dc5bed](https://www.github.com/tauri-apps/tauri/commit/e4dc5bedbb54fbe6e06ab833d7fb7e0cacebad10) fix(core): escape glob characters in drop/dialogs , closes [#5234](https://www.github.com/tauri-apps/tauri/pull/5234) ([#5237](https://www.github.com/tauri-apps/tauri/pull/5237)) on 2022-10-05
## \[1.1.1]
- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.

View File

@@ -10,7 +10,7 @@ license = "Apache-2.0 OR MIT"
name = "tauri"
readme = "README.md"
repository = "https://github.com/tauri-apps/tauri"
version = "1.1.1"
version = "1.1.4"
[package.metadata.docs.rs]
no-default-features = true
@@ -49,10 +49,10 @@ url = { version = "2.3" }
anyhow = "1.0"
thiserror = "1.0"
once_cell = "1"
tauri-runtime = { version = "0.11.1", path = "../tauri-runtime" }
tauri-runtime = { version = "0.11.2", path = "../tauri-runtime" }
tauri-macros = { version = "1.1.1", path = "../tauri-macros" }
tauri-utils = { version = "1.1.1", features = [ "resources" ], path = "../tauri-utils" }
tauri-runtime-wry = { version = "0.11.1", path = "../tauri-runtime-wry", optional = true }
tauri-runtime-wry = { version = "0.11.2", path = "../tauri-runtime-wry", optional = true }
rand = "0.8"
semver = { version = "1.0", features = [ "serde" ] }
serde_repr = "0.1"

View File

@@ -948,7 +948,7 @@ impl<R: Runtime> Builder<R> {
#[cfg(any(windows, target_os = "linux"))]
runtime_any_thread: false,
setup: Box::new(|_| Ok(())),
invoke_handler: Box::new(|_| ()),
invoke_handler: Box::new(|invoke| invoke.resolver.reject("not implemented")),
invoke_responder: Arc::new(window_invoke_responder),
invoke_initialization_script:
"Object.defineProperty(window, '__TAURI_POST_MESSAGE__', { value: (message) => window.ipc.postMessage(JSON.stringify(message)) })".into(),

View File

@@ -25,10 +25,9 @@ use tauri_utils::{
html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
};
use crate::hooks::IpcJavascript;
#[cfg(feature = "isolation")]
use crate::hooks::IsolationJavascript;
use crate::pattern::{format_real_schema, PatternJavascript};
use crate::pattern::PatternJavascript;
use crate::{
app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
event::{assert_event_name_is_valid, Event, EventHandler, Listeners},
@@ -54,6 +53,7 @@ use crate::{
app::{GlobalMenuEventListener, WindowMenuEvent},
window::WebResourceRequestHandler,
};
use crate::{hooks::IpcJavascript, pattern::format_real_schema};
#[cfg(any(target_os = "linux", target_os = "windows"))]
use crate::api::path::{resolve_path, BaseDirectory};
@@ -139,7 +139,7 @@ fn set_csp<R: Runtime>(
let default_src = csp
.entry("default-src".into())
.or_insert_with(Default::default);
default_src.push(format_real_schema(schema));
default_src.push(crate::pattern::format_real_schema(schema));
}
Csp::DirectiveMap(csp).to_string()
@@ -231,7 +231,7 @@ pub struct InnerWindowManager<R: Runtime> {
/// The script that initializes the invoke system.
invoke_initialization_script: String,
/// Application pattern.
pattern: Pattern,
pub(crate) pattern: Pattern,
}
impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
@@ -367,9 +367,12 @@ impl<R: Runtime> WindowManager<R> {
/// Get the base URL to use for webview requests.
///
/// In dev mode, this will be based on the `devPath` configuration value.
fn get_url(&self) -> Cow<'_, Url> {
pub(crate) fn get_url(&self) -> Cow<'_, Url> {
match self.base_path() {
AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url),
#[cfg(windows)]
_ => Cow::Owned(Url::parse("https://tauri.localhost").unwrap()),
#[cfg(not(windows))]
_ => Cow::Owned(Url::parse("tauri://localhost").unwrap()),
}
}
@@ -477,7 +480,7 @@ impl<R: Runtime> WindowManager<R> {
});
}
let window_url = Url::parse(&pending.url).unwrap();
let window_url = pending.current_url.lock().unwrap().clone();
let window_origin =
if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" {
format!("https://{}.localhost", window_url.scheme())
@@ -1011,7 +1014,16 @@ mod test {
);
#[cfg(custom_protocol)]
assert_eq!(manager.get_url().to_string(), "tauri://localhost");
{
assert_eq!(
manager.get_url().to_string(),
if cfg!(windows) {
"https://tauri.localhost/"
} else {
"tauri://localhost"
}
);
}
#[cfg(dev)]
assert_eq!(manager.get_url().to_string(), "http://localhost:4000/");
@@ -1062,27 +1074,21 @@ impl<R: Runtime> WindowManager<R> {
return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
}
#[allow(unused_mut)] // mut url only for the data-url parsing
let (is_local, mut url) = match &pending.webview_attributes.url {
let mut url = match &pending.webview_attributes.url {
WindowUrl::App(path) => {
let url = self.get_url();
(
true,
// ignore "index.html" just to simplify the url
if path.to_str() != Some("index.html") {
url
.join(&*path.to_string_lossy())
.map_err(crate::Error::InvalidUrl)
// this will never fail
.unwrap()
} else {
url.into_owned()
},
)
}
WindowUrl::External(url) => {
let config_url = self.get_url();
(config_url.make_relative(url).is_some(), url.clone())
// ignore "index.html" just to simplify the url
if path.to_str() != Some("index.html") {
url
.join(&*path.to_string_lossy())
.map_err(crate::Error::InvalidUrl)
// this will never fail
.unwrap()
} else {
url.into_owned()
}
}
WindowUrl::External(url) => url.clone(),
_ => unimplemented!(),
};
@@ -1109,7 +1115,7 @@ impl<R: Runtime> WindowManager<R> {
}
}
pending.url = url.to_string();
*pending.current_url.lock().unwrap() = url;
if !pending.window_builder.has_icon() {
if let Some(default_window_icon) = self.inner.default_window_icon.clone() {
@@ -1125,17 +1131,15 @@ 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(),
web_resource_request_handler,
)?;
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
}
let label = pending.label.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));
// in `Windows`, we need to force a data_directory
// but we do respect user-specification
@@ -1160,6 +1164,28 @@ impl<R: Runtime> WindowManager<R> {
}
}
#[cfg(feature = "isolation")]
let pattern = self.pattern().clone();
let current_url_ = pending.current_url.clone();
let navigation_handler = pending.navigation_handler.take();
pending.navigation_handler = Some(Box::new(move |url| {
// always allow navigation events for the isolation iframe and do not emit them for consumers
#[cfg(feature = "isolation")]
if let Pattern::Isolation { schema, .. } = &pattern {
if url.scheme() == schema
&& url.domain() == Some(crate::pattern::ISOLATION_IFRAME_SRC_DOMAIN)
{
return true;
}
}
*current_url_.lock().unwrap() = url.clone();
if let Some(handler) = &navigation_handler {
handler(url)
} else {
true
}
}));
Ok(pending)
}

View File

@@ -11,8 +11,11 @@ use serialize_to_javascript::{default_template, Template};
use tauri_utils::assets::{Assets, EmbeddedAssets};
/// The domain of the isolation iframe source.
pub const ISOLATION_IFRAME_SRC_DOMAIN: &str = "localhost";
/// An application pattern.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum Pattern<A: Assets = EmbeddedAssets> {
/// The brownfield pattern.
Brownfield(PhantomData<A>),
@@ -35,6 +38,26 @@ pub enum Pattern<A: Assets = EmbeddedAssets> {
},
}
impl<A: Assets> Clone for Pattern<A> {
fn clone(&self) -> Self {
match self {
Self::Brownfield(a) => Self::Brownfield(*a),
#[cfg(feature = "isolation")]
Self::Isolation {
assets,
schema,
key,
crypto_keys,
} => Self::Isolation {
assets: assets.clone(),
schema: schema.clone(),
key: key.clone(),
crypto_keys: crypto_keys.clone(),
},
}
}
}
/// The shape of the JavaScript Pattern config
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase", tag = "pattern")]
@@ -87,8 +110,8 @@ pub(crate) struct PatternJavascript {
#[allow(dead_code)]
pub(crate) fn format_real_schema(schema: &str) -> String {
if cfg!(windows) {
format!("https://{}.localhost", schema)
format!("https://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}")
} else {
format!("{}://localhost", schema)
format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}")
}
}

View File

@@ -5,7 +5,7 @@
use std::{
collections::{HashMap, HashSet},
fmt,
path::{Path, PathBuf},
path::{Path, PathBuf, MAIN_SEPARATOR},
sync::{Arc, Mutex},
};
@@ -64,15 +64,19 @@ impl fmt::Debug for Scope {
}
}
fn push_pattern<P: AsRef<Path>>(list: &mut HashSet<Pattern>, pattern: P) -> crate::Result<()> {
fn push_pattern<P: AsRef<Path>, F: Fn(&str) -> Result<Pattern, glob::PatternError>>(
list: &mut HashSet<Pattern>,
pattern: P,
f: F,
) -> crate::Result<()> {
let path: PathBuf = pattern.as_ref().components().collect();
list.insert(Pattern::new(&path.to_string_lossy())?);
list.insert(f(&path.to_string_lossy())?);
#[cfg(windows)]
{
if let Ok(p) = std::fs::canonicalize(&path) {
list.insert(Pattern::new(&p.to_string_lossy())?);
list.insert(f(&p.to_string_lossy())?);
} else {
list.insert(Pattern::new(&format!("\\\\?\\{}", path.display()))?);
list.insert(f(&format!("\\\\?\\{}", path.display()))?);
}
}
Ok(())
@@ -89,7 +93,7 @@ impl Scope {
let mut allowed_patterns = HashSet::new();
for path in scope.allowed_paths() {
if let Ok(path) = parse_path(config, package_info, env, path) {
push_pattern(&mut allowed_patterns, path)?;
push_pattern(&mut allowed_patterns, path, Pattern::new)?;
}
}
@@ -97,7 +101,7 @@ impl Scope {
if let Some(forbidden_paths) = scope.forbidden_paths() {
for path in forbidden_paths {
if let Ok(path) = parse_path(config, package_info, env, path) {
push_pattern(&mut forbidden_patterns, path)?;
push_pattern(&mut forbidden_patterns, path, Pattern::new)?;
}
}
}
@@ -137,18 +141,20 @@ impl Scope {
/// Extend the allowed patterns with the given directory.
///
/// After this function has been called, the frontend will be able to use the Tauri API to read
/// the directory and all of its files and subdirectories.
/// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too.
pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) -> crate::Result<()> {
let path = path.as_ref().to_path_buf();
let path = path.as_ref();
{
let mut list = self.allowed_patterns.lock().unwrap();
// allow the directory to be read
push_pattern(&mut list, &path)?;
push_pattern(&mut list, &path, escaped_pattern)?;
// allow its files and subdirectories to be read
push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }))?;
push_pattern(&mut list, &path, |p| {
escaped_pattern_with(p, if recursive { "**" } else { "*" })
})?;
}
self.trigger(Event::PathAllowed(path));
self.trigger(Event::PathAllowed(path.to_path_buf()));
Ok(())
}
@@ -157,7 +163,11 @@ impl Scope {
/// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
pub fn allow_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
let path = path.as_ref();
push_pattern(&mut self.allowed_patterns.lock().unwrap(), &path)?;
push_pattern(
&mut self.allowed_patterns.lock().unwrap(),
&path,
escaped_pattern,
)?;
self.trigger(Event::PathAllowed(path.to_path_buf()));
Ok(())
}
@@ -166,16 +176,18 @@ impl Scope {
///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) -> crate::Result<()> {
let path = path.as_ref().to_path_buf();
let path = path.as_ref();
{
let mut list = self.forbidden_patterns.lock().unwrap();
// allow the directory to be read
push_pattern(&mut list, &path)?;
push_pattern(&mut list, &path, escaped_pattern)?;
// allow its files and subdirectories to be read
push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }))?;
push_pattern(&mut list, &path, |p| {
escaped_pattern_with(p, if recursive { "**" } else { "*" })
})?;
}
self.trigger(Event::PathForbidden(path));
self.trigger(Event::PathForbidden(path.to_path_buf()));
Ok(())
}
@@ -184,7 +196,11 @@ impl Scope {
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
let path = path.as_ref();
push_pattern(&mut self.forbidden_patterns.lock().unwrap(), &path)?;
push_pattern(
&mut self.forbidden_patterns.lock().unwrap(),
&path,
escaped_pattern,
)?;
self.trigger(Event::PathForbidden(path.to_path_buf()));
Ok(())
}
@@ -200,13 +216,22 @@ impl Scope {
if let Ok(path) = path {
let path: PathBuf = path.components().collect();
let options = glob::MatchOptions {
// this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
// see: https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5
require_literal_separator: true,
// dotfiles are not supposed to be exposed by default
#[cfg(unix)]
require_literal_leading_dot: true,
..Default::default()
};
let forbidden = self
.forbidden_patterns
.lock()
.unwrap()
.iter()
.any(|p| p.matches_path(&path));
.any(|p| p.matches_path_with(&path, options));
if forbidden {
false
@@ -216,7 +241,7 @@ impl Scope {
.lock()
.unwrap()
.iter()
.any(|p| p.matches_path(&path));
.any(|p| p.matches_path_with(&path, options));
allowed
}
} else {
@@ -224,3 +249,126 @@ impl Scope {
}
}
}
fn escaped_pattern(p: &str) -> Result<Pattern, glob::PatternError> {
Pattern::new(&glob::Pattern::escape(p))
}
fn escaped_pattern_with(p: &str, append: &str) -> Result<Pattern, glob::PatternError> {
Pattern::new(&format!(
"{}{}{}",
glob::Pattern::escape(p),
MAIN_SEPARATOR,
append
))
}
#[cfg(test)]
mod tests {
use super::Scope;
fn new_scope() -> Scope {
Scope {
allowed_patterns: Default::default(),
forbidden_patterns: Default::default(),
event_listeners: Default::default(),
}
}
#[test]
fn path_is_escaped() {
let scope = new_scope();
#[cfg(unix)]
{
scope.allow_directory("/home/tauri/**", false).unwrap();
assert!(scope.is_allowed("/home/tauri/**"));
assert!(scope.is_allowed("/home/tauri/**/file"));
assert!(!scope.is_allowed("/home/tauri/anyfile"));
}
#[cfg(windows)]
{
scope.allow_directory("C:\\home\\tauri\\**", false).unwrap();
assert!(scope.is_allowed("C:\\home\\tauri\\**"));
assert!(scope.is_allowed("C:\\home\\tauri\\**\\file"));
assert!(!scope.is_allowed("C:\\home\\tauri\\anyfile"));
}
let scope = new_scope();
#[cfg(unix)]
{
scope.allow_file("/home/tauri/**").unwrap();
assert!(scope.is_allowed("/home/tauri/**"));
assert!(!scope.is_allowed("/home/tauri/**/file"));
assert!(!scope.is_allowed("/home/tauri/anyfile"));
}
#[cfg(windows)]
{
scope.allow_file("C:\\home\\tauri\\**").unwrap();
assert!(scope.is_allowed("C:\\home\\tauri\\**"));
assert!(!scope.is_allowed("C:\\home\\tauri\\**\\file"));
assert!(!scope.is_allowed("C:\\home\\tauri\\anyfile"));
}
let scope = new_scope();
#[cfg(unix)]
{
scope.allow_directory("/home/tauri", true).unwrap();
scope.forbid_directory("/home/tauri/**", false).unwrap();
assert!(!scope.is_allowed("/home/tauri/**"));
assert!(!scope.is_allowed("/home/tauri/**/file"));
assert!(scope.is_allowed("/home/tauri/**/inner/file"));
assert!(scope.is_allowed("/home/tauri/inner/folder/anyfile"));
assert!(scope.is_allowed("/home/tauri/anyfile"));
}
#[cfg(windows)]
{
scope.allow_directory("C:\\home\\tauri", true).unwrap();
scope
.forbid_directory("C:\\home\\tauri\\**", false)
.unwrap();
assert!(!scope.is_allowed("C:\\home\\tauri\\**"));
assert!(!scope.is_allowed("C:\\home\\tauri\\**\\file"));
assert!(scope.is_allowed("C:\\home\\tauri\\**\\inner\\file"));
assert!(scope.is_allowed("C:\\home\\tauri\\inner\\folder\\anyfile"));
assert!(scope.is_allowed("C:\\home\\tauri\\anyfile"));
}
let scope = new_scope();
#[cfg(unix)]
{
scope.allow_directory("/home/tauri", true).unwrap();
scope.forbid_file("/home/tauri/**").unwrap();
assert!(!scope.is_allowed("/home/tauri/**"));
assert!(scope.is_allowed("/home/tauri/**/file"));
assert!(scope.is_allowed("/home/tauri/**/inner/file"));
assert!(scope.is_allowed("/home/tauri/anyfile"));
}
#[cfg(windows)]
{
scope.allow_directory("C:\\home\\tauri", true).unwrap();
scope.forbid_file("C:\\home\\tauri\\**").unwrap();
assert!(!scope.is_allowed("C:\\home\\tauri\\**"));
assert!(scope.is_allowed("C:\\home\\tauri\\**\\file"));
assert!(scope.is_allowed("C:\\home\\tauri\\**\\inner\\file"));
assert!(scope.is_allowed("C:\\home\\tauri\\anyfile"));
}
let scope = new_scope();
#[cfg(unix)]
{
scope.allow_directory("/home/tauri", false).unwrap();
assert!(scope.is_allowed("/home/tauri/**"));
assert!(!scope.is_allowed("/home/tauri/**/file"));
assert!(!scope.is_allowed("/home/tauri/**/inner/file"));
assert!(scope.is_allowed("/home/tauri/anyfile"));
}
#[cfg(windows)]
{
scope.allow_directory("C:\\home\\tauri", false).unwrap();
assert!(scope.is_allowed("C:\\home\\tauri\\**"));
assert!(!scope.is_allowed("C:\\home\\tauri\\**\\file"));
assert!(!scope.is_allowed("C:\\home\\tauri\\**\\inner\\file"));
assert!(scope.is_allowed("C:\\home\\tauri\\anyfile"));
}
}
}

View File

@@ -67,6 +67,7 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
) -> Result<DetachedWindow<T, Self::Runtime>> {
Ok(DetachedWindow {
label: pending.label,
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
dispatcher: MockDispatcher {
context: self.context.clone(),
},
@@ -627,6 +628,7 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>> {
Ok(DetachedWindow {
label: pending.label,
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
dispatcher: MockDispatcher {
context: self.context.clone(),
},

View File

@@ -33,6 +33,7 @@ use crate::{
};
use serde::Serialize;
use url::Url;
#[cfg(windows)]
use windows::Win32::Foundation::HWND;
@@ -510,7 +511,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
#[derive(Debug)]
pub struct Window<R: Runtime> {
/// The webview window created by the runtime.
window: DetachedWindow<EventLoopMessage, R>,
pub(crate) window: DetachedWindow<EventLoopMessage, R>,
/// The manager to associate this webview window with.
manager: WindowManager<R>,
pub(crate) app_handle: AppHandle<R>,
@@ -1184,9 +1185,27 @@ impl<R: Runtime> Window<R> {
/// Webview APIs.
impl<R: Runtime> Window<R> {
/// Returns the current url of the webview.
pub fn url(&self) -> Url {
self.window.current_url.lock().unwrap().clone()
}
/// Handles this window receiving an [`InvokeMessage`].
pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> {
let manager = self.manager.clone();
let current_url = self.url();
let config_url = manager.get_url();
#[allow(unused_mut)]
let mut is_local = config_url.make_relative(&current_url).is_some();
#[cfg(feature = "isolation")]
if let crate::Pattern::Isolation { schema, .. } = &self.manager.inner.pattern {
if current_url.scheme() == schema
&& current_url.domain() == Some(crate::pattern::ISOLATION_IFRAME_SRC_DOMAIN)
{
is_local = true;
}
}
match payload.cmd.as_str() {
"__initialized" => {
let payload: PageLoadPayload = serde_json::from_value(payload.inner)?;
@@ -1200,8 +1219,15 @@ impl<R: Runtime> Window<R> {
payload.inner,
);
let resolver = InvokeResolver::new(self, payload.callback, payload.error);
let invoke = Invoke { message, resolver };
if !is_local {
invoke
.resolver
.reject("Remote URLs are not allowed to access the IPC");
return Ok(());
}
if let Some(module) = &payload.tauri_module {
crate::endpoints::handle(
module.to_string(),

View File

@@ -3105,7 +3105,7 @@ dependencies = [
[[package]]
name = "tauri"
version = "1.0.5"
version = "1.1.3"
dependencies = [
"anyhow",
"attohttpc",
@@ -3167,7 +3167,7 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "1.0.4"
version = "1.1.1"
dependencies = [
"anyhow",
"cargo_toml",
@@ -3183,7 +3183,7 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "1.0.4"
version = "1.1.1"
dependencies = [
"base64",
"brotli",
@@ -3207,7 +3207,7 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "1.0.4"
version = "1.1.1"
dependencies = [
"heck 0.4.0",
"proc-macro2",
@@ -3219,7 +3219,7 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "0.10.2"
version = "0.11.1"
dependencies = [
"gtk",
"http",
@@ -3231,6 +3231,7 @@ dependencies = [
"serde_json",
"tauri-utils",
"thiserror",
"url",
"uuid 1.1.2",
"webview2-com",
"windows 0.39.0",
@@ -3238,7 +3239,7 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "0.10.2"
version = "0.11.1"
dependencies = [
"cocoa",
"gtk",
@@ -3247,6 +3248,7 @@ dependencies = [
"raw-window-handle",
"tauri-runtime",
"tauri-utils",
"url",
"uuid 1.1.2",
"webkit2gtk",
"webview2-com",
@@ -3256,7 +3258,7 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "1.0.3"
version = "1.1.1"
dependencies = [
"aes-gcm",
"brotli",

View File

@@ -3,6 +3,6 @@
"version": "1.1.1",
"node": ">= 10.0.0"
},
"tauri": "1.1.1",
"tauri": "1.1.4",
"tauri-build": "1.1.1"
}