|
@@ -18,6 +18,8 @@ use std::{
|
|
|
time::{SystemTime, UNIX_EPOCH},
|
|
|
};
|
|
|
|
|
|
+#[cfg(target_os = "macos")]
|
|
|
+use std::fs::rename;
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
|
use std::process::Command;
|
|
|
|
|
@@ -230,7 +232,7 @@ impl<'a> UpdateBuilder<'a> {
|
|
|
} else {
|
|
|
// we expect it to fail if we can't find the executable path
|
|
|
// without this path we can't continue the update process.
|
|
|
- env::current_exe().expect("Can't access current executable path.")
|
|
|
+ env::current_exe()?
|
|
|
};
|
|
|
|
|
|
// Did the target is provided by the config?
|
|
@@ -479,17 +481,16 @@ impl Update {
|
|
|
// We should have an AppImage already installed to be able to copy and install
|
|
|
// the extract_path is the current AppImage path
|
|
|
// tmp_dir is where our new AppImage is found
|
|
|
-
|
|
|
#[cfg(target_os = "linux")]
|
|
|
fn copy_files_and_run(tmp_dir: tempfile::TempDir, extract_path: PathBuf) -> Result {
|
|
|
// we delete our current AppImage (we'll create a new one later)
|
|
|
remove_file(&extract_path)?;
|
|
|
|
|
|
// In our tempdir we expect 1 directory (should be the <app>.app)
|
|
|
- let paths = read_dir(&tmp_dir).unwrap();
|
|
|
+ let paths = read_dir(&tmp_dir)?;
|
|
|
|
|
|
for path in paths {
|
|
|
- let found_path = path.expect("Unable to extract").path();
|
|
|
+ let found_path = path?.path();
|
|
|
// make sure it's our .AppImage
|
|
|
if found_path.extension() == Some(OsStr::new("AppImage")) {
|
|
|
// Simply overwrite our AppImage (we use the command)
|
|
@@ -522,16 +523,15 @@ fn copy_files_and_run(tmp_dir: tempfile::TempDir, extract_path: PathBuf) -> Resu
|
|
|
|
|
|
// ## EXE
|
|
|
// Update server can provide a custom EXE (installer) who can run any task.
|
|
|
-
|
|
|
#[cfg(target_os = "windows")]
|
|
|
#[allow(clippy::unnecessary_wraps)]
|
|
|
fn copy_files_and_run(tmp_dir: tempfile::TempDir, _extract_path: PathBuf) -> Result {
|
|
|
- let paths = read_dir(&tmp_dir).unwrap();
|
|
|
+ let paths = read_dir(&tmp_dir)?;
|
|
|
// This consumes the TempDir without deleting directory on the filesystem,
|
|
|
// meaning that the directory will no longer be automatically deleted.
|
|
|
tmp_dir.into_path();
|
|
|
for path in paths {
|
|
|
- let found_path = path.expect("Unable to extract").path();
|
|
|
+ let found_path = path?.path();
|
|
|
// we support 2 type of files exe & msi for now
|
|
|
// If it's an `exe` we expect an installer not a runtime.
|
|
|
if found_path.extension() == Some(OsStr::new("exe")) {
|
|
@@ -558,27 +558,57 @@ fn copy_files_and_run(tmp_dir: tempfile::TempDir, _extract_path: PathBuf) -> Res
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
-// MacOS
|
|
|
+// Get the current app name in the path
|
|
|
+// Example; `/Applications/updater-example.app/Contents/MacOS/updater-example`
|
|
|
+// Should return; `updater-example.app`
|
|
|
+#[cfg(target_os = "macos")]
|
|
|
+fn macos_app_name_in_path(extract_path: &PathBuf) -> String {
|
|
|
+ let components = extract_path.components();
|
|
|
+ let app_name = components.last().unwrap();
|
|
|
+ let app_name = app_name.as_os_str().to_str().unwrap();
|
|
|
+ app_name.to_string()
|
|
|
+}
|
|
|
|
|
|
+// MacOS
|
|
|
// ### Expected structure:
|
|
|
// ├── [AppName]_[version]_x64.app.tar.gz # GZ generated by tauri-bundler
|
|
|
// │ └──[AppName].app # Main application
|
|
|
// │ └── Contents # Application contents...
|
|
|
// │ └── ...
|
|
|
// └── ...
|
|
|
-
|
|
|
#[cfg(target_os = "macos")]
|
|
|
fn copy_files_and_run(tmp_dir: tempfile::TempDir, extract_path: PathBuf) -> Result {
|
|
|
// In our tempdir we expect 1 directory (should be the <app>.app)
|
|
|
- let paths = read_dir(&tmp_dir).unwrap();
|
|
|
+ let paths = read_dir(&tmp_dir)?;
|
|
|
+
|
|
|
+ // current app name in /Applications/<app>.app
|
|
|
+ let app_name = macos_app_name_in_path(&extract_path);
|
|
|
|
|
|
for path in paths {
|
|
|
- let found_path = path.expect("Unable to extract").path();
|
|
|
+ let mut found_path = path?.path();
|
|
|
// make sure it's our .app
|
|
|
if found_path.extension() == Some(OsStr::new("app")) {
|
|
|
- // Walk the temp dir and copy all files by replacing existing files only
|
|
|
- // and creating directories if needed
|
|
|
- Move::from_source(&found_path).walk_to_dest(&extract_path)?;
|
|
|
+ let found_app_name = macos_app_name_in_path(&found_path);
|
|
|
+ // make sure the app name in the archive matche the installed app name on path
|
|
|
+ if found_app_name != app_name {
|
|
|
+ // we need to replace the app name in the updater archive to match
|
|
|
+ // installed app name
|
|
|
+ let new_path = found_path.parent().unwrap().join(app_name);
|
|
|
+ rename(&found_path, &new_path)?;
|
|
|
+
|
|
|
+ found_path = new_path;
|
|
|
+ }
|
|
|
+
|
|
|
+ let sandbox_app_path = tempfile::Builder::new()
|
|
|
+ .prefix("tauri_current_app_sandbox")
|
|
|
+ .tempdir()?;
|
|
|
+
|
|
|
+ // Replace the whole application to make sure the
|
|
|
+ // code signature is following
|
|
|
+ Move::from_source(&found_path)
|
|
|
+ .replace_using_temp(sandbox_app_path.path())
|
|
|
+ .to_dest(&extract_path)?;
|
|
|
+
|
|
|
// early finish we have everything we need here
|
|
|
return Ok(());
|
|
|
}
|
|
@@ -619,7 +649,7 @@ pub fn extract_path_from_executable(executable_path: &Path) -> PathBuf {
|
|
|
.expect("Can't determine extract path");
|
|
|
|
|
|
// MacOS example binary is in /Applications/TestApp.app/Contents/MacOS/myApp
|
|
|
- // We need to get /Applications/TestApp.app
|
|
|
+ // We need to get /Applications/<app>.app
|
|
|
// todo(lemarier): Need a better way here
|
|
|
// Maybe we could search for <*.app> to get the right path
|
|
|
#[cfg(target_os = "macos")]
|
|
@@ -691,22 +721,16 @@ pub fn verify_signature(
|
|
|
let public_key = PublicKey::decode(pub_key_decoded)?;
|
|
|
let signature_base64_decoded = base64_to_string(&release_signature)?;
|
|
|
|
|
|
- let signature =
|
|
|
- Signature::decode(&signature_base64_decoded).expect("Something wrong with the signature");
|
|
|
+ let signature = Signature::decode(&signature_base64_decoded)?;
|
|
|
|
|
|
// We need to open the file and extract the datas to make sure its not corrupted
|
|
|
- let file_open = OpenOptions::new()
|
|
|
- .read(true)
|
|
|
- .open(&archive_path)
|
|
|
- .expect("Can't open our archive to validate signature");
|
|
|
+ let file_open = OpenOptions::new().read(true).open(&archive_path)?;
|
|
|
|
|
|
let mut file_buff: BufReader<File> = BufReader::new(file_open);
|
|
|
|
|
|
// read all bytes since EOF in the buffer
|
|
|
let mut data = vec![];
|
|
|
- file_buff
|
|
|
- .read_to_end(&mut data)
|
|
|
- .expect("Can't read buffer to validate signature");
|
|
|
+ file_buff.read_to_end(&mut data)?;
|
|
|
|
|
|
// Validate signature or bail out
|
|
|
public_key.verify(&data, &signature)?;
|
|
@@ -779,6 +803,17 @@ mod test {
|
|
|
}"#.into()
|
|
|
}
|
|
|
|
|
|
+ #[cfg(target_os = "macos")]
|
|
|
+ #[test]
|
|
|
+ fn test_app_name_in_path() {
|
|
|
+ let executable = extract_path_from_executable(Path::new(
|
|
|
+ "/Applications/updater-example.app/Contents/MacOS/updater-example",
|
|
|
+ ));
|
|
|
+ let app_name = macos_app_name_in_path(&executable);
|
|
|
+ assert!(executable.ends_with("updater-example.app"));
|
|
|
+ assert_eq!(app_name, "updater-example.app".to_string());
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
fn simple_http_updater() {
|
|
|
let _m = mockito::mock("GET", "/")
|