From 3fd84cb3c9aca7560ad6db8dbebda564308a3bfa Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 13 Jun 2024 18:31:27 +0300 Subject: [PATCH] fix(NSIS): rewrite license file with BOM (#10049) * fix(NSIS): rewrite license file with BOM closes #9031 This also includes a change to NSIS bundler to use UTF8 by default * refactor and cleanup --- .changes/bundler-nsis-license-file-bom.md | 5 + tooling/bundler/src/bundle/windows/nsis.rs | 170 +++++++++++---------- tooling/bundler/src/bundle/windows/sign.rs | 2 +- 3 files changed, 93 insertions(+), 84 deletions(-) create mode 100644 .changes/bundler-nsis-license-file-bom.md diff --git a/.changes/bundler-nsis-license-file-bom.md b/.changes/bundler-nsis-license-file-bom.md new file mode 100644 index 000000000..544661f9e --- /dev/null +++ b/.changes/bundler-nsis-license-file-bom.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'patch:bug' +--- + +Fix encoding of NSIS license page when using a license file without a BOM. diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index e3480d6f7..f5aaf74cb 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -21,7 +21,7 @@ use handlebars::{to_json, Handlebars}; use tauri_utils::config::{NSISInstallerMode, NsisCompression, WebviewInstallMode}; use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, fs, path::{Path, PathBuf}, process::Command, @@ -213,31 +213,35 @@ fn build_nsis_app_installer( to_json(settings.windows().allow_downgrades), ); - if let Some(license) = settings.license_file() { - data.insert("license", to_json(dunce::canonicalize(license)?)); + if let Some(license_file) = settings.license_file() { + let license_file = dunce::canonicalize(license_file)?; + let license_file_with_bom = output_path.join("license_file"); + let content = std::fs::read(license_file)?; + write_utf8_with_bom(&license_file_with_bom, content)?; + data.insert("license", to_json(license_file_with_bom)); } - let mut install_mode = NSISInstallerMode::CurrentUser; - let mut languages = vec!["English".into()]; - let mut custom_template_path = None; - let mut custom_language_files = None; - if let Some(nsis) = &settings.windows().nsis { - custom_template_path.clone_from(&nsis.template); - custom_language_files.clone_from(&nsis.custom_language_files); - install_mode = nsis.install_mode; - if let Some(langs) = &nsis.languages { - languages.clear(); - languages.extend_from_slice(langs); - } + let nsis = settings.windows().nsis.as_ref(); + + let custom_template_path = nsis.as_ref().and_then(|n| n.template.clone()); + + let install_mode = nsis + .as_ref() + .map(|n| n.install_mode) + .unwrap_or(NSISInstallerMode::CurrentUser); + + if let Some(nsis) = nsis { if let Some(installer_icon) = &nsis.installer_icon { data.insert( "installer_icon", to_json(dunce::canonicalize(installer_icon)?), ); } + if let Some(header_image) = &nsis.header_image { data.insert("header_image", to_json(dunce::canonicalize(header_image)?)); } + if let Some(sidebar_image) = &nsis.sidebar_image { data.insert( "sidebar_image", @@ -245,11 +249,6 @@ fn build_nsis_app_installer( ); } - data.insert( - "display_language_selector", - to_json(nsis.display_language_selector && languages.len() > 1), - ); - if let Some(installer_hooks) = &nsis.installer_hooks { let installer_hooks = dunce::canonicalize(installer_hooks)?; data.insert("installer_hooks", to_json(installer_hooks)); @@ -281,26 +280,48 @@ fn build_nsis_app_installer( }), ); - let mut languages_data = Vec::new(); - for lang in &languages { - if let Some(data) = get_lang_data(lang, custom_language_files.as_ref())? { - languages_data.push(data); - } else { - log::warn!("Custom tauri messages for {lang} are not translated.\nIf it is a valid language listed on , please open a Tauri feature request\n or you can provide a custom language file for it in `tauri.conf.json > bundle > windows > nsis > custom_language_files`"); - } - } - + let languages = nsis + .and_then(|nsis| nsis.languages.clone()) + .unwrap_or_else(|| vec!["English".into()]); data.insert("languages", to_json(languages.clone())); + data.insert( - "language_files", + "display_language_selector", to_json( - languages_data - .iter() - .map(|d| d.0.clone()) - .collect::>(), + nsis + .map(|nsis| nsis.display_language_selector && languages.len() > 1) + .unwrap_or(false), ), ); + let custom_language_files = nsis.and_then(|nsis| nsis.custom_language_files.clone()); + + let mut language_files_paths = Vec::new(); + for lang in &languages { + // if user provided a custom lang file, we rewrite it with BOM + if let Some(path) = custom_language_files.as_ref().and_then(|h| h.get(lang)) { + let path = dunce::canonicalize(path)?; + let path_with_bom = path + .file_name() + .map(|f| output_path.join(f)) + .unwrap_or_else(|| output_path.join(format!("{lang}_custom.nsh"))); + let content = std::fs::read(path)?; + write_utf8_with_bom(&path_with_bom, content)?; + language_files_paths.push(path_with_bom); + } else { + // if user has not provided a custom lang file, + // we check our translated languages + if let Some((file_name, content)) = get_lang_data(lang) { + let path = output_path.join(file_name); + write_utf8_with_bom(&path, content)?; + language_files_paths.push(path); + } else { + log::warn!("Custom tauri messages for {lang} are not translated.\nIf it is a valid language listed on , please open a Tauri feature request\n or you can provide a custom language file for it in `tauri.conf.json > bundle > windows > nsis > custom_language_files`"); + } + } + } + data.insert("language_files", to_json(language_files_paths)); + let main_binary = settings .binaries() .iter() @@ -463,27 +484,21 @@ fn build_nsis_app_installer( .expect("Failed to setup handlebar template"); } - write_ut16_le_with_bom( + write_utf8_with_bom( output_path.join("FileAssociation.nsh"), - include_str!("./templates/FileAssociation.nsh"), + include_bytes!("./templates/FileAssociation.nsh"), )?; - write_ut16_le_with_bom( + write_utf8_with_bom( output_path.join("utils.nsh"), - include_str!("./templates/utils.nsh"), + include_bytes!("./templates/utils.nsh"), )?; let installer_nsi_path = output_path.join("installer.nsi"); - write_ut16_le_with_bom( + write_utf8_with_bom( &installer_nsi_path, - handlebars.render("installer.nsi", &data)?.as_str(), + handlebars.render("installer.nsi", &data)?, )?; - for (lang, data) in languages_data.iter() { - if let Some(content) = data { - write_ut16_le_with_bom(output_path.join(lang).with_extension("nsh"), content)?; - } - } - let package_base_name = format!( "{}_{}_{}-setup", settings.product_name(), @@ -511,6 +526,7 @@ fn build_nsis_app_installer( let mut nsis_cmd = Command::new("makensis"); nsis_cmd + .args(["-INPUTCHARSET", "UTF8", "-OUTPUTCHARSET", "UTF8"]) .arg(match settings.log_level() { log::Level::Error => "-V1", log::Level::Warn => "-V2", @@ -662,50 +678,38 @@ fn generate_estimated_size( Ok(format!("{size:#08x}")) } -fn get_lang_data( - lang: &str, - custom_lang_files: Option<&HashMap>, -) -> crate::Result)>> { - if let Some(path) = custom_lang_files.and_then(|h| h.get(lang)) { - return Ok(Some((dunce::canonicalize(path)?, None))); - } - - let lang_path = PathBuf::from(format!("{lang}.nsh")); - let lang_content = match lang.to_lowercase().as_str() { - "arabic" => Some(include_str!("./templates/nsis-languages/Arabic.nsh")), - "bulgarian" => Some(include_str!("./templates/nsis-languages/Bulgarian.nsh")), - "dutch" => Some(include_str!("./templates/nsis-languages/Dutch.nsh")), - "english" => Some(include_str!("./templates/nsis-languages/English.nsh")), - "german" => Some(include_str!("./templates/nsis-languages/German.nsh")), - "japanese" => Some(include_str!("./templates/nsis-languages/Japanese.nsh")), - "korean" => Some(include_str!("./templates/nsis-languages/Korean.nsh")), - "portuguesebr" => Some(include_str!("./templates/nsis-languages/PortugueseBR.nsh")), - "russian" => Some(include_str!("./templates/nsis-languages/Russian.nsh")), - "tradchinese" => Some(include_str!("./templates/nsis-languages/TradChinese.nsh")), - "simpchinese" => Some(include_str!("./templates/nsis-languages/SimpChinese.nsh")), - "french" => Some(include_str!("./templates/nsis-languages/French.nsh")), - "spanish" => Some(include_str!("./templates/nsis-languages/Spanish.nsh")), - "spanishinternational" => Some(include_str!( - "./templates/nsis-languages/SpanishInternational.nsh" - )), - "persian" => Some(include_str!("./templates/nsis-languages/Persian.nsh")), - "turkish" => Some(include_str!("./templates/nsis-languages/Turkish.nsh")), - "swedish" => Some(include_str!("./templates/nsis-languages/Swedish.nsh")), - _ => return Ok(None), +fn get_lang_data(lang: &str) -> Option<(String, &[u8])> { + let path = format!("{lang}.nsh"); + let content: &[u8] = match lang.to_lowercase().as_str() { + "arabic" => include_bytes!("./templates/nsis-languages/Arabic.nsh"), + "bulgarian" => include_bytes!("./templates/nsis-languages/Bulgarian.nsh"), + "dutch" => include_bytes!("./templates/nsis-languages/Dutch.nsh"), + "english" => include_bytes!("./templates/nsis-languages/English.nsh"), + "german" => include_bytes!("./templates/nsis-languages/German.nsh"), + "japanese" => include_bytes!("./templates/nsis-languages/Japanese.nsh"), + "korean" => include_bytes!("./templates/nsis-languages/Korean.nsh"), + "portuguesebr" => include_bytes!("./templates/nsis-languages/PortugueseBR.nsh"), + "russian" => include_bytes!("./templates/nsis-languages/Russian.nsh"), + "tradchinese" => include_bytes!("./templates/nsis-languages/TradChinese.nsh"), + "simpchinese" => include_bytes!("./templates/nsis-languages/SimpChinese.nsh"), + "french" => include_bytes!("./templates/nsis-languages/French.nsh"), + "spanish" => include_bytes!("./templates/nsis-languages/Spanish.nsh"), + "spanishinternational" => include_bytes!("./templates/nsis-languages/SpanishInternational.nsh"), + "persian" => include_bytes!("./templates/nsis-languages/Persian.nsh"), + "turkish" => include_bytes!("./templates/nsis-languages/Turkish.nsh"), + "swedish" => include_bytes!("./templates/nsis-languages/Swedish.nsh"), + _ => return None, }; - - Ok(Some((lang_path, lang_content))) + Some((path, content)) } -fn write_ut16_le_with_bom>(path: P, content: &str) -> crate::Result<()> { +fn write_utf8_with_bom, C: AsRef<[u8]>>(path: P, content: C) -> crate::Result<()> { use std::fs::File; use std::io::{BufWriter, Write}; let file = File::create(path)?; let mut output = BufWriter::new(file); - output.write_all(&[0xFF, 0xFE])?; // the BOM part - for utf16 in content.encode_utf16() { - output.write_all(&utf16.to_le_bytes())?; - } + output.write_all(&[0xEF, 0xBB, 0xBF])?; // UTF-8 BOM + output.write_all(content.as_ref())?; Ok(()) } diff --git a/tooling/bundler/src/bundle/windows/sign.rs b/tooling/bundler/src/bundle/windows/sign.rs index 0ecdd2ee4..c77e3ca36 100644 --- a/tooling/bundler/src/bundle/windows/sign.rs +++ b/tooling/bundler/src/bundle/windows/sign.rs @@ -157,7 +157,7 @@ pub fn sign_command_default>( ) -> crate::Result { let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?; - let mut cmd = Command::new(&signtool); + let mut cmd = Command::new(signtool); cmd.arg("sign"); cmd.args(["/fd", ¶ms.digest_algorithm]); cmd.args(["/sha1", ¶ms.certificate_thumbprint]);