Skip to content

Commit 3fd84cb

Browse files
authored
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
1 parent b2ff840 commit 3fd84cb

File tree

3 files changed

+93
-84
lines changed

3 files changed

+93
-84
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tauri-bundler': 'patch:bug'
3+
---
4+
5+
Fix encoding of NSIS license page when using a license file without a BOM.

tooling/bundler/src/bundle/windows/nsis.rs

Lines changed: 87 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use handlebars::{to_json, Handlebars};
2121
use tauri_utils::config::{NSISInstallerMode, NsisCompression, WebviewInstallMode};
2222

2323
use std::{
24-
collections::{BTreeMap, HashMap},
24+
collections::BTreeMap,
2525
fs,
2626
path::{Path, PathBuf},
2727
process::Command,
@@ -213,43 +213,42 @@ fn build_nsis_app_installer(
213213
to_json(settings.windows().allow_downgrades),
214214
);
215215

216-
if let Some(license) = settings.license_file() {
217-
data.insert("license", to_json(dunce::canonicalize(license)?));
216+
if let Some(license_file) = settings.license_file() {
217+
let license_file = dunce::canonicalize(license_file)?;
218+
let license_file_with_bom = output_path.join("license_file");
219+
let content = std::fs::read(license_file)?;
220+
write_utf8_with_bom(&license_file_with_bom, content)?;
221+
data.insert("license", to_json(license_file_with_bom));
218222
}
219223

220-
let mut install_mode = NSISInstallerMode::CurrentUser;
221-
let mut languages = vec!["English".into()];
222-
let mut custom_template_path = None;
223-
let mut custom_language_files = None;
224-
if let Some(nsis) = &settings.windows().nsis {
225-
custom_template_path.clone_from(&nsis.template);
226-
custom_language_files.clone_from(&nsis.custom_language_files);
227-
install_mode = nsis.install_mode;
228-
if let Some(langs) = &nsis.languages {
229-
languages.clear();
230-
languages.extend_from_slice(langs);
231-
}
224+
let nsis = settings.windows().nsis.as_ref();
225+
226+
let custom_template_path = nsis.as_ref().and_then(|n| n.template.clone());
227+
228+
let install_mode = nsis
229+
.as_ref()
230+
.map(|n| n.install_mode)
231+
.unwrap_or(NSISInstallerMode::CurrentUser);
232+
233+
if let Some(nsis) = nsis {
232234
if let Some(installer_icon) = &nsis.installer_icon {
233235
data.insert(
234236
"installer_icon",
235237
to_json(dunce::canonicalize(installer_icon)?),
236238
);
237239
}
240+
238241
if let Some(header_image) = &nsis.header_image {
239242
data.insert("header_image", to_json(dunce::canonicalize(header_image)?));
240243
}
244+
241245
if let Some(sidebar_image) = &nsis.sidebar_image {
242246
data.insert(
243247
"sidebar_image",
244248
to_json(dunce::canonicalize(sidebar_image)?),
245249
);
246250
}
247251

248-
data.insert(
249-
"display_language_selector",
250-
to_json(nsis.display_language_selector && languages.len() > 1),
251-
);
252-
253252
if let Some(installer_hooks) = &nsis.installer_hooks {
254253
let installer_hooks = dunce::canonicalize(installer_hooks)?;
255254
data.insert("installer_hooks", to_json(installer_hooks));
@@ -281,26 +280,48 @@ fn build_nsis_app_installer(
281280
}),
282281
);
283282

284-
let mut languages_data = Vec::new();
285-
for lang in &languages {
286-
if let Some(data) = get_lang_data(lang, custom_language_files.as_ref())? {
287-
languages_data.push(data);
288-
} else {
289-
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`");
290-
}
291-
}
292-
283+
let languages = nsis
284+
.and_then(|nsis| nsis.languages.clone())
285+
.unwrap_or_else(|| vec!["English".into()]);
293286
data.insert("languages", to_json(languages.clone()));
287+
294288
data.insert(
295-
"language_files",
289+
"display_language_selector",
296290
to_json(
297-
languages_data
298-
.iter()
299-
.map(|d| d.0.clone())
300-
.collect::<Vec<_>>(),
291+
nsis
292+
.map(|nsis| nsis.display_language_selector && languages.len() > 1)
293+
.unwrap_or(false),
301294
),
302295
);
303296

297+
let custom_language_files = nsis.and_then(|nsis| nsis.custom_language_files.clone());
298+
299+
let mut language_files_paths = Vec::new();
300+
for lang in &languages {
301+
// if user provided a custom lang file, we rewrite it with BOM
302+
if let Some(path) = custom_language_files.as_ref().and_then(|h| h.get(lang)) {
303+
let path = dunce::canonicalize(path)?;
304+
let path_with_bom = path
305+
.file_name()
306+
.map(|f| output_path.join(f))
307+
.unwrap_or_else(|| output_path.join(format!("{lang}_custom.nsh")));
308+
let content = std::fs::read(path)?;
309+
write_utf8_with_bom(&path_with_bom, content)?;
310+
language_files_paths.push(path_with_bom);
311+
} else {
312+
// if user has not provided a custom lang file,
313+
// we check our translated languages
314+
if let Some((file_name, content)) = get_lang_data(lang) {
315+
let path = output_path.join(file_name);
316+
write_utf8_with_bom(&path, content)?;
317+
language_files_paths.push(path);
318+
} else {
319+
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`");
320+
}
321+
}
322+
}
323+
data.insert("language_files", to_json(language_files_paths));
324+
304325
let main_binary = settings
305326
.binaries()
306327
.iter()
@@ -463,27 +484,21 @@ fn build_nsis_app_installer(
463484
.expect("Failed to setup handlebar template");
464485
}
465486

466-
write_ut16_le_with_bom(
487+
write_utf8_with_bom(
467488
output_path.join("FileAssociation.nsh"),
468-
include_str!("./templates/FileAssociation.nsh"),
489+
include_bytes!("./templates/FileAssociation.nsh"),
469490
)?;
470-
write_ut16_le_with_bom(
491+
write_utf8_with_bom(
471492
output_path.join("utils.nsh"),
472-
include_str!("./templates/utils.nsh"),
493+
include_bytes!("./templates/utils.nsh"),
473494
)?;
474495

475496
let installer_nsi_path = output_path.join("installer.nsi");
476-
write_ut16_le_with_bom(
497+
write_utf8_with_bom(
477498
&installer_nsi_path,
478-
handlebars.render("installer.nsi", &data)?.as_str(),
499+
handlebars.render("installer.nsi", &data)?,
479500
)?;
480501

481-
for (lang, data) in languages_data.iter() {
482-
if let Some(content) = data {
483-
write_ut16_le_with_bom(output_path.join(lang).with_extension("nsh"), content)?;
484-
}
485-
}
486-
487502
let package_base_name = format!(
488503
"{}_{}_{}-setup",
489504
settings.product_name(),
@@ -511,6 +526,7 @@ fn build_nsis_app_installer(
511526
let mut nsis_cmd = Command::new("makensis");
512527

513528
nsis_cmd
529+
.args(["-INPUTCHARSET", "UTF8", "-OUTPUTCHARSET", "UTF8"])
514530
.arg(match settings.log_level() {
515531
log::Level::Error => "-V1",
516532
log::Level::Warn => "-V2",
@@ -662,50 +678,38 @@ fn generate_estimated_size(
662678
Ok(format!("{size:#08x}"))
663679
}
664680

665-
fn get_lang_data(
666-
lang: &str,
667-
custom_lang_files: Option<&HashMap<String, PathBuf>>,
668-
) -> crate::Result<Option<(PathBuf, Option<&'static str>)>> {
669-
if let Some(path) = custom_lang_files.and_then(|h| h.get(lang)) {
670-
return Ok(Some((dunce::canonicalize(path)?, None)));
671-
}
672-
673-
let lang_path = PathBuf::from(format!("{lang}.nsh"));
674-
let lang_content = match lang.to_lowercase().as_str() {
675-
"arabic" => Some(include_str!("./templates/nsis-languages/Arabic.nsh")),
676-
"bulgarian" => Some(include_str!("./templates/nsis-languages/Bulgarian.nsh")),
677-
"dutch" => Some(include_str!("./templates/nsis-languages/Dutch.nsh")),
678-
"english" => Some(include_str!("./templates/nsis-languages/English.nsh")),
679-
"german" => Some(include_str!("./templates/nsis-languages/German.nsh")),
680-
"japanese" => Some(include_str!("./templates/nsis-languages/Japanese.nsh")),
681-
"korean" => Some(include_str!("./templates/nsis-languages/Korean.nsh")),
682-
"portuguesebr" => Some(include_str!("./templates/nsis-languages/PortugueseBR.nsh")),
683-
"russian" => Some(include_str!("./templates/nsis-languages/Russian.nsh")),
684-
"tradchinese" => Some(include_str!("./templates/nsis-languages/TradChinese.nsh")),
685-
"simpchinese" => Some(include_str!("./templates/nsis-languages/SimpChinese.nsh")),
686-
"french" => Some(include_str!("./templates/nsis-languages/French.nsh")),
687-
"spanish" => Some(include_str!("./templates/nsis-languages/Spanish.nsh")),
688-
"spanishinternational" => Some(include_str!(
689-
"./templates/nsis-languages/SpanishInternational.nsh"
690-
)),
691-
"persian" => Some(include_str!("./templates/nsis-languages/Persian.nsh")),
692-
"turkish" => Some(include_str!("./templates/nsis-languages/Turkish.nsh")),
693-
"swedish" => Some(include_str!("./templates/nsis-languages/Swedish.nsh")),
694-
_ => return Ok(None),
681+
fn get_lang_data(lang: &str) -> Option<(String, &[u8])> {
682+
let path = format!("{lang}.nsh");
683+
let content: &[u8] = match lang.to_lowercase().as_str() {
684+
"arabic" => include_bytes!("./templates/nsis-languages/Arabic.nsh"),
685+
"bulgarian" => include_bytes!("./templates/nsis-languages/Bulgarian.nsh"),
686+
"dutch" => include_bytes!("./templates/nsis-languages/Dutch.nsh"),
687+
"english" => include_bytes!("./templates/nsis-languages/English.nsh"),
688+
"german" => include_bytes!("./templates/nsis-languages/German.nsh"),
689+
"japanese" => include_bytes!("./templates/nsis-languages/Japanese.nsh"),
690+
"korean" => include_bytes!("./templates/nsis-languages/Korean.nsh"),
691+
"portuguesebr" => include_bytes!("./templates/nsis-languages/PortugueseBR.nsh"),
692+
"russian" => include_bytes!("./templates/nsis-languages/Russian.nsh"),
693+
"tradchinese" => include_bytes!("./templates/nsis-languages/TradChinese.nsh"),
694+
"simpchinese" => include_bytes!("./templates/nsis-languages/SimpChinese.nsh"),
695+
"french" => include_bytes!("./templates/nsis-languages/French.nsh"),
696+
"spanish" => include_bytes!("./templates/nsis-languages/Spanish.nsh"),
697+
"spanishinternational" => include_bytes!("./templates/nsis-languages/SpanishInternational.nsh"),
698+
"persian" => include_bytes!("./templates/nsis-languages/Persian.nsh"),
699+
"turkish" => include_bytes!("./templates/nsis-languages/Turkish.nsh"),
700+
"swedish" => include_bytes!("./templates/nsis-languages/Swedish.nsh"),
701+
_ => return None,
695702
};
696-
697-
Ok(Some((lang_path, lang_content)))
703+
Some((path, content))
698704
}
699705

700-
fn write_ut16_le_with_bom<P: AsRef<Path>>(path: P, content: &str) -> crate::Result<()> {
706+
fn write_utf8_with_bom<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, content: C) -> crate::Result<()> {
701707
use std::fs::File;
702708
use std::io::{BufWriter, Write};
703709

704710
let file = File::create(path)?;
705711
let mut output = BufWriter::new(file);
706-
output.write_all(&[0xFF, 0xFE])?; // the BOM part
707-
for utf16 in content.encode_utf16() {
708-
output.write_all(&utf16.to_le_bytes())?;
709-
}
712+
output.write_all(&[0xEF, 0xBB, 0xBF])?; // UTF-8 BOM
713+
output.write_all(content.as_ref())?;
710714
Ok(())
711715
}

tooling/bundler/src/bundle/windows/sign.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ pub fn sign_command_default<P: AsRef<Path>>(
157157
) -> crate::Result<Command> {
158158
let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;
159159

160-
let mut cmd = Command::new(&signtool);
160+
let mut cmd = Command::new(signtool);
161161
cmd.arg("sign");
162162
cmd.args(["/fd", &params.digest_algorithm]);
163163
cmd.args(["/sha1", &params.certificate_thumbprint]);

0 commit comments

Comments
 (0)