Ver Fonte

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
Amr Bashir há 1 ano atrás
pai
commit
3fd84cb3c9

+ 5 - 0
.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.

+ 87 - 83
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 <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files>, 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::<Vec<_>>(),
+      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 <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files>, 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<String, PathBuf>>,
-) -> crate::Result<Option<(PathBuf, Option<&'static str>)>> {
-  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<P: AsRef<Path>>(path: P, content: &str) -> crate::Result<()> {
+fn write_utf8_with_bom<P: AsRef<Path>, 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(())
 }

+ 1 - 1
tooling/bundler/src/bundle/windows/sign.rs

@@ -157,7 +157,7 @@ pub fn sign_command_default<P: AsRef<Path>>(
 ) -> crate::Result<Command> {
   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", &params.digest_algorithm]);
   cmd.args(["/sha1", &params.certificate_thumbprint]);