diff --git a/core/tauri/src/api/file/extract.rs b/core/tauri/src/api/file/extract.rs
index 572a994f3..31b869370 100644
--- a/core/tauri/src/api/file/extract.rs
+++ b/core/tauri/src/api/file/extract.rs
@@ -6,7 +6,7 @@ use std::{
borrow::Cow,
fs,
io::{self, Cursor, Read, Seek},
- path::{self, Path, PathBuf},
+ path::{self, Component, Path, PathBuf},
};
/// The archive reader.
@@ -84,10 +84,22 @@ impl<'a, R: Read> Entry<'a, R> {
/// Extract this entry into `into_path`.
/// If it's a directory, the target will be created, if it's a file, it'll be extracted at this location.
+ /// If it's a symlink, it will be created.
/// Note: You need to include the complete path, with file name and extension.
pub fn extract(self, into_path: &path::Path) -> crate::api::Result<()> {
match self {
Self::Tar(mut entry) => {
+ // validate path
+ let path = entry.path()?;
+ if path.components().any(|c| matches!(c, Component::ParentDir)) {
+ return Err(
+ std::io::Error::new(
+ std::io::ErrorKind::InvalidInput,
+ "cannot extract path with parent dir component",
+ )
+ .into(),
+ );
+ }
// determine if it's a file or a directory
if entry.header().entry_type() == tar::EntryType::Directory {
// this is a directory, lets create it
@@ -100,13 +112,8 @@ impl<'a, R: Read> Entry<'a, R> {
}
}
} else {
- let mut out_file = fs::File::create(into_path)?;
- io::copy(&mut entry, &mut out_file)?;
-
- // make sure we set permissions
- if let Ok(mode) = entry.header().mode() {
- set_perms(into_path, Some(&mut out_file), mode, true)?;
- }
+ // handle files, symlinks, hard links, etc. and set permissions
+ entry.unpack(into_path)?;
}
}
Self::Zip(entry) => {
@@ -270,59 +277,3 @@ impl<'a, R: Read + Seek> Extract<'a, R> {
Ok(())
}
}
-
-fn set_perms(
- dst: &Path,
- f: Option<&mut std::fs::File>,
- mode: u32,
- preserve: bool,
-) -> crate::api::Result<()> {
- _set_perms(dst, f, mode, preserve).map_err(|_| {
- crate::api::Error::Extract(format!(
- "failed to set permissions to {mode:o} \
- for `{}`",
- dst.display()
- ))
- })
-}
-
-#[cfg(unix)]
-fn _set_perms(
- dst: &Path,
- f: Option<&mut std::fs::File>,
- mode: u32,
- preserve: bool,
-) -> io::Result<()> {
- use std::os::unix::prelude::*;
-
- let mode = if preserve { mode } else { mode & 0o777 };
- let perm = fs::Permissions::from_mode(mode as _);
- match f {
- Some(f) => f.set_permissions(perm),
- None => fs::set_permissions(dst, perm),
- }
-}
-
-#[cfg(windows)]
-fn _set_perms(
- dst: &Path,
- f: Option<&mut std::fs::File>,
- mode: u32,
- _preserve: bool,
-) -> io::Result<()> {
- if mode & 0o200 == 0o200 {
- return Ok(());
- }
- match f {
- Some(f) => {
- let mut perm = f.metadata()?.permissions();
- perm.set_readonly(true);
- f.set_permissions(perm)
- }
- None => {
- let mut perm = fs::metadata(dst)?.permissions();
- perm.set_readonly(true);
- fs::set_permissions(dst, perm)
- }
- }
-}
diff --git a/core/tests/app-updater/frameworks/test.framework/Headers b/core/tests/app-updater/frameworks/test.framework/Headers
new file mode 120000
index 000000000..a177d2a6b
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/Headers
@@ -0,0 +1 @@
+Versions/Current/Headers
\ No newline at end of file
diff --git a/core/tests/app-updater/frameworks/test.framework/Resources b/core/tests/app-updater/frameworks/test.framework/Resources
new file mode 120000
index 000000000..953ee36f3
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/Resources
@@ -0,0 +1 @@
+Versions/Current/Resources
\ No newline at end of file
diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h b/core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h
new file mode 100644
index 000000000..fed3381b2
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h
@@ -0,0 +1 @@
+// Testing that a header can be included
diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist b/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist
new file mode 100644
index 000000000..593ddefdb
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ test
+ CFBundleIdentifier
+ com.tauri.test.framework
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ test
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleVersion
+ 1.0.0
+ CFBundleSignature
+ ????
+ LSMinimumSystemVersion
+ 10.15
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+ NSPrincipalClass
+
+
+
diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/LICENSE b/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/LICENSE
new file mode 100644
index 000000000..eb609b5b8
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/LICENSE
@@ -0,0 +1 @@
+Test that a LICENSE file can be included
diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/test b/core/tests/app-updater/frameworks/test.framework/Versions/A/test
new file mode 100755
index 000000000..5b0be073c
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/test
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+
+echo "hello"
diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/Current b/core/tests/app-updater/frameworks/test.framework/Versions/Current
new file mode 120000
index 000000000..8c7e5a667
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/Versions/Current
@@ -0,0 +1 @@
+A
\ No newline at end of file
diff --git a/core/tests/app-updater/frameworks/test.framework/test b/core/tests/app-updater/frameworks/test.framework/test
new file mode 120000
index 000000000..2e6ce0ecf
--- /dev/null
+++ b/core/tests/app-updater/frameworks/test.framework/test
@@ -0,0 +1 @@
+Versions/Current/test
\ No newline at end of file
diff --git a/core/tests/app-updater/tauri.conf.json b/core/tests/app-updater/tauri.conf.json
index 8c3351c63..1de6eb67e 100644
--- a/core/tests/app-updater/tauri.conf.json
+++ b/core/tests/app-updater/tauri.conf.json
@@ -17,6 +17,11 @@
"../../../examples/.icons/icon.ico"
],
"category": "DeveloperTool",
+ "macOS": {
+ "frameworks": [
+ "./frameworks/test.framework"
+ ]
+ },
"windows": {
"wix": {
"skipWebviewInstall": true
diff --git a/core/tests/app-updater/tests/update.rs b/core/tests/app-updater/tests/update.rs
index fc32c3dfe..aab499136 100644
--- a/core/tests/app-updater/tests/update.rs
+++ b/core/tests/app-updater/tests/update.rs
@@ -310,6 +310,23 @@ fn update_app() {
let status = binary_cmd.status().expect("failed to run app");
+ // Verify the framework extracted symlinks correctly
+ #[cfg(target_os = "macos")]
+ {
+ let meta = std::fs::symlink_metadata(
+ bundle_paths(&root_dir, "0.1.0")
+ .first()
+ .unwrap()
+ .1
+ .join("Contents/Frameworks/test.framework/test"),
+ )
+ .expect("test.framework/test metadata");
+ assert!(
+ meta.file_type().is_symlink(),
+ "test.framework/test should be a symlink"
+ );
+ }
+
if !status.success() {
panic!("failed to run app");
}
diff --git a/tooling/bundler/src/bundle/updater_bundle.rs b/tooling/bundler/src/bundle/updater_bundle.rs
index 6e4708712..0cb8d3b02 100644
--- a/tooling/bundler/src/bundle/updater_bundle.rs
+++ b/tooling/bundler/src/bundle/updater_bundle.rs
@@ -238,12 +238,22 @@ fn create_tar(src_dir: &Path, dest_path: &Path) -> crate::Result {
let gzip_encoder = libflate::gzip::Encoder::new(dest_file)?;
let gzip_encoder = create_tar_from_src(src_dir, gzip_encoder)?;
+
let mut dest_file = gzip_encoder.finish().into_result()?;
dest_file.flush()?;
Ok(dest_path.to_owned())
}
-#[cfg(not(target_os = "windows"))]
+#[cfg(target_os = "macos")]
+fn create_tar_from_src, W: Write>(src_dir: P, dest_file: W) -> crate::Result {
+ let src_dir = src_dir.as_ref();
+ let mut builder = tar::Builder::new(dest_file);
+ builder.follow_symlinks(false);
+ builder.append_dir_all(src_dir.file_name().expect("Path has no file_name"), src_dir)?;
+ builder.into_inner().map_err(Into::into)
+}
+
+#[cfg(target_os = "linux")]
fn create_tar_from_src, W: Write>(src_dir: P, dest_file: W) -> crate::Result {
let src_dir = src_dir.as_ref();
let mut tar_builder = tar::Builder::new(dest_file);