From ef962c43afcd86dfaf69dbcc349e4bbed60eda98 Mon Sep 17 00:00:00 2001 From: Dave Ceddia Date: Thu, 13 Jul 2023 13:35:22 -0400 Subject: [PATCH] fix(cli): handle symlinks in updater bundler, closes #3933 (#3934) Co-authored-by: chip Co-authored-by: Lucas Nogueira Co-authored-by: Lucas Fernandes Nogueira --- core/tauri/src/api/file/extract.rs | 79 ++++--------------- .../frameworks/test.framework/Headers | 1 + .../frameworks/test.framework/Resources | 1 + .../test.framework/Versions/A/Headers/test.h | 1 + .../Versions/A/Resources/Info.plist | 32 ++++++++ .../Versions/A/Resources/LICENSE | 1 + .../frameworks/test.framework/Versions/A/test | 3 + .../test.framework/Versions/Current | 1 + .../frameworks/test.framework/test | 1 + core/tests/app-updater/tauri.conf.json | 5 ++ core/tests/app-updater/tests/update.rs | 17 ++++ tooling/bundler/src/bundle/updater_bundle.rs | 12 ++- 12 files changed, 89 insertions(+), 65 deletions(-) create mode 120000 core/tests/app-updater/frameworks/test.framework/Headers create mode 120000 core/tests/app-updater/frameworks/test.framework/Resources create mode 100644 core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h create mode 100644 core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist create mode 100644 core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/LICENSE create mode 100755 core/tests/app-updater/frameworks/test.framework/Versions/A/test create mode 120000 core/tests/app-updater/frameworks/test.framework/Versions/Current create mode 120000 core/tests/app-updater/frameworks/test.framework/test 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);