Skip to content

Commit

Permalink
fix(cli): handle symlinks in updater bundler, closes #3933 (#3934)
Browse files Browse the repository at this point in the history
Co-authored-by: chip <chip@chip.sh>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
  • Loading branch information
4 people authored Jul 13, 2023
1 parent 290e366 commit ef962c4
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 65 deletions.
79 changes: 15 additions & 64 deletions core/tauri/src/api/file/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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) => {
Expand Down Expand Up @@ -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)
}
}
}
1 change: 1 addition & 0 deletions core/tests/app-updater/frameworks/test.framework/Headers
1 change: 1 addition & 0 deletions core/tests/app-updater/frameworks/test.framework/Resources
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Testing that a header can be included
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>test</string>
<key>CFBundleIdentifier</key>
<string>com.tauri.test.framework</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>test</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test that a LICENSE file can be included
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env sh

echo "hello"
1 change: 1 addition & 0 deletions core/tests/app-updater/frameworks/test.framework/test
5 changes: 5 additions & 0 deletions core/tests/app-updater/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
"../../../examples/.icons/icon.ico"
],
"category": "DeveloperTool",
"macOS": {
"frameworks": [
"./frameworks/test.framework"
]
},
"windows": {
"wix": {
"skipWebviewInstall": true
Expand Down
17 changes: 17 additions & 0 deletions core/tests/app-updater/tests/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
12 changes: 11 additions & 1 deletion tooling/bundler/src/bundle/updater_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,22 @@ fn create_tar(src_dir: &Path, dest_path: &Path) -> crate::Result<PathBuf> {
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<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {
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<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {
let src_dir = src_dir.as_ref();
let mut tar_builder = tar::Builder::new(dest_file);
Expand Down

0 comments on commit ef962c4

Please sign in to comment.