8236129: Exe installers have wrong properties

Reviewed-by: herrick, almatvee
This commit is contained in:
Alexey Semenyuk 2020-04-24 16:13:23 -04:00
parent 33d9178ebd
commit 88f3861cb7
21 changed files with 959 additions and 776 deletions

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -137,6 +137,10 @@ class DottedVersion implements Comparable<String> {
return value;
}
int[] getComponents() {
return components;
}
final private int[] components;
final private String value;
final private boolean greedy;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,18 +24,28 @@
*/
package jdk.incubator.jpackage.internal;
import java.io.*;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
import jdk.incubator.jpackage.internal.resources.ResourceLocator;
/**
* Resource file that may have the default value supplied by jpackage. It can be
* overridden by a file from resource directory set with {@code --resource-dir}
@ -134,13 +144,39 @@ final class OverridableResource {
return setExternal(toPath(v));
}
Source saveToFile(Path dest) throws IOException {
for (var source: sources) {
if (source.getValue().apply(dest)) {
return source.getKey();
}
Source saveToStream(OutputStream dest) throws IOException {
if (dest == null) {
return sendToConsumer(null);
}
return null;
return sendToConsumer(new ResourceConsumer() {
@Override
public Path publicName() {
throw new UnsupportedOperationException();
}
@Override
public void consume(InputStream in) throws IOException {
in.transferTo(dest);
}
});
}
Source saveToFile(Path dest) throws IOException {
if (dest == null) {
return sendToConsumer(null);
}
return sendToConsumer(new ResourceConsumer() {
@Override
public Path publicName() {
return dest.getFileName();
}
@Override
public void consume(InputStream in) throws IOException {
Files.createDirectories(dest.getParent());
Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
}
});
}
Source saveToFile(File dest) throws IOException {
@ -157,6 +193,15 @@ final class OverridableResource {
RESOURCE_DIR.fetchFrom(params));
}
private Source sendToConsumer(ResourceConsumer consumer) throws IOException {
for (var source: sources) {
if (source.getValue().apply(consumer)) {
return source.getKey();
}
}
return null;
}
private String getPrintableCategory() {
if (category != null) {
return String.format("[%s]", category);
@ -164,7 +209,7 @@ final class OverridableResource {
return "";
}
private boolean useExternal(Path dest) throws IOException {
private boolean useExternal(ResourceConsumer dest) throws IOException {
boolean used = externalPath != null && Files.exists(externalPath);
if (used && dest != null) {
Log.verbose(MessageFormat.format(I18N.getString(
@ -179,7 +224,7 @@ final class OverridableResource {
return used;
}
private boolean useResourceDir(Path dest) throws IOException {
private boolean useResourceDir(ResourceConsumer dest) throws IOException {
boolean used = false;
if (dest == null && publicName == null) {
@ -187,7 +232,7 @@ final class OverridableResource {
}
final Path resourceName = Optional.ofNullable(publicName).orElseGet(
() -> dest.getFileName());
() -> dest.publicName());
if (resourceDir != null) {
final Path customResource = resourceDir.resolve(resourceName);
@ -213,14 +258,14 @@ final class OverridableResource {
return used;
}
private boolean useDefault(Path dest) throws IOException {
private boolean useDefault(ResourceConsumer dest) throws IOException {
boolean used = defaultName != null;
if (used && dest != null) {
final Path resourceName = Optional
.ofNullable(logPublicName)
.orElse(Optional
.ofNullable(publicName)
.orElseGet(() -> dest.getFileName()));
.orElseGet(() -> dest.publicName()));
Log.verbose(MessageFormat.format(
I18N.getString("message.using-default-resource"),
defaultName, getPrintableCategory(), resourceName));
@ -232,7 +277,7 @@ final class OverridableResource {
return used;
}
private static List<String> substitute(Stream<String> lines,
private static Stream<String> substitute(Stream<String> lines,
Map<String, String> substitutionData) {
return lines.map(line -> {
String result = line;
@ -241,7 +286,7 @@ final class OverridableResource {
entry.getValue()).orElse(""));
}
return result;
}).collect(Collectors.toList());
});
}
private static Path toPath(File v) {
@ -251,17 +296,20 @@ final class OverridableResource {
return null;
}
private void processResourceStream(InputStream rawResource, Path dest)
throws IOException {
private void processResourceStream(InputStream rawResource,
ResourceConsumer dest) throws IOException {
if (substitutionData == null) {
Files.createDirectories(dest.getParent());
Files.copy(rawResource, dest, StandardCopyOption.REPLACE_EXISTING);
dest.consume(rawResource);
} else {
// Utf8 in and out
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(rawResource, StandardCharsets.UTF_8))) {
Files.createDirectories(dest.getParent());
Files.write(dest, substitute(reader.lines(), substitutionData));
String data = substitute(reader.lines(), substitutionData).collect(
Collectors.joining("\n"));
try (InputStream in = new ByteArrayInputStream(data.getBytes(
StandardCharsets.UTF_8))) {
dest.consume(in);
}
}
}
}
@ -292,7 +340,12 @@ final class OverridableResource {
private List<Map.Entry<Source, SourceHandler>> sources;
@FunctionalInterface
static interface SourceHandler {
public boolean apply(Path dest) throws IOException;
private static interface SourceHandler {
public boolean apply(ResourceConsumer dest) throws IOException;
}
private static interface ResourceConsumer {
public Path publicName();
public void consume(InputStream in) throws IOException;
}
}

@ -0,0 +1,240 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.incubator.jpackage.internal;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.ResourceBundle;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.COPYRIGHT;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.DESCRIPTION;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.ICON;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.TEMP_ROOT;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VENDOR;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
final class ExecutableRebrander {
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.WinResources");
private static final String LAUNCHER_PROPERTIES_TEMPLATE =
"WinLauncher.template";
private static final String INSTALLER_PROPERTIES_TEMPLATE =
"WinInstaller.template";
private static final String INSTALLER_PROPERTIES_RESOURE_DIR_ID =
"WinInstaller.properties";
void rebrandInstaller(Map<String, ? super Object> params, Path target)
throws IOException {
if (!target.isAbsolute()) {
rebrandInstaller(params, target.toAbsolutePath());
return;
}
rebrandExecutable(params, target, (resourceLock) -> {
rebrandProperties(resourceLock, createResource(
INSTALLER_PROPERTIES_TEMPLATE, params).setPublicName(
INSTALLER_PROPERTIES_RESOURE_DIR_ID),
createSubstituteData(params), target);
});
}
void rebrandLauncher(Map<String, ? super Object> params, Path icon,
Path target) throws IOException {
if (!target.isAbsolute() || (icon != null && !icon.isAbsolute())) {
Path absIcon = null;
if (icon != null) {
absIcon = icon.toAbsolutePath();
}
rebrandLauncher(params, absIcon, target.toAbsolutePath());
return;
}
rebrandExecutable(params, target, (resourceLock) -> {
rebrandProperties(resourceLock, createResource(
LAUNCHER_PROPERTIES_TEMPLATE, params).setPublicName(
APP_NAME.fetchFrom(params) + ".properties"),
createSubstituteData(params), target);
if (icon != null) {
iconSwap(resourceLock, icon.toString());
}
});
}
ExecutableRebrander addAction(UpdateResourceAction action) {
if (extraActions == null) {
extraActions = new ArrayList<>();
}
extraActions.add(action);
return this;
}
private void rebrandExecutable(Map<String, ? super Object> params,
Path target, UpdateResourceAction action) throws IOException {
try {
String tempDirectory = TEMP_ROOT.fetchFrom(params).getAbsolutePath();
if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
tempDirectory)) {
Log.verbose(MessageFormat.format(I18N.getString(
"message.potential.windows.defender.issue"),
tempDirectory));
}
target.toFile().setWritable(true, true);
long resourceLock = lockResource(target.toString());
if (resourceLock == 0) {
throw new RuntimeException(MessageFormat.format(
I18N.getString("error.lock-resource"), target));
}
try {
action.editResource(resourceLock);
if (extraActions != null) {
for (UpdateResourceAction extraAction: extraActions) {
extraAction.editResource(resourceLock);
}
}
} finally {
if (resourceLock != 0) {
unlockResource(resourceLock);
}
}
} finally {
target.toFile().setReadOnly();
}
}
@FunctionalInterface
static interface UpdateResourceAction {
public void editResource(long resourceLock) throws IOException;
}
private static String getFixedFileVersion(String value) {
int[] versionComponents = DottedVersion.greedy(value).getComponents();
int addComponentsCount = 4 - versionComponents.length;
if (addComponentsCount > 0) {
StringBuilder sb = new StringBuilder(value);
do {
sb.append('.');
sb.append(0);
} while (--addComponentsCount > 0);
return sb.toString();
}
return value;
}
private Map<String, String> createSubstituteData(
Map<String, ? super Object> params) {
Map<String, String> data = new HashMap<>();
String fixedFileVersion = getFixedFileVersion(VERSION.fetchFrom(params));
// mapping Java parameters in strings for version resource
validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
validateValueAndPut(data, "FILE_VERSION", VERSION, params);
validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
data.put("FIXEDFILEINFO_FILE_VERSION", fixedFileVersion);
return data;
}
private void rebrandProperties(long resourceLock, OverridableResource properties,
Map<String, String> data, Path target) throws IOException {
String targetExecutableName = target.getFileName().toString();
data.put("INTERNAL_NAME", targetExecutableName);
data.put("ORIGINAL_FILENAME", targetExecutableName);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
properties
.setSubstitutionData(data)
.setCategory(I18N.getString("resource.executable-properties-template"))
.saveToStream(buffer);
final List<String> propList = new ArrayList<>();
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(
buffer.toByteArray()), StandardCharsets.UTF_8)) {
final Properties configProp = new Properties();
configProp.load(reader);
configProp.forEach((k, v) -> {
propList.add(k.toString());
propList.add(v.toString());
});
}
if (versionSwap(resourceLock, propList.toArray(String[]::new)) != 0) {
throw new RuntimeException(MessageFormat.format(
I18N.getString("error.version-swap"), target));
}
}
private static void validateValueAndPut(
Map<String, String> data, String key,
BundlerParamInfo<String> param,
Map<String, ? super Object> params) {
String value = param.fetchFrom(params);
if (value.contains("\r") || value.contains("\n")) {
Log.error("Configuration Parameter " + param.getID()
+ " contains multiple lines of text, ignore it");
data.put(key, "");
return;
}
data.put(key, value);
}
private List<UpdateResourceAction> extraActions;
static {
System.loadLibrary("jpackage");
}
private static native long lockResource(String executable);
private static native void unlockResource(long resourceLock);
private static native int iconSwap(long resourceLock, String iconTarget);
private static native int versionSwap(long resourceLock, String[] executableProperties);
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -64,7 +64,7 @@ public class WinExeBundler extends AbstractBundler {
@Override
public String getName() {
return getString("exe.bundler.name");
return I18N.getString("exe.bundler.name");
}
@Override
@ -117,48 +117,45 @@ public class WinExeBundler extends AbstractBundler {
.setEnvironmentVariable("JpMsiFile", msi.getAbsolutePath().toString())
.run(params);
return buildEXE(msi, outdir);
return buildEXE(params, msi, outdir);
} catch (IOException ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
private File buildEXE(File msi, File outdir)
throws IOException {
private File buildEXE(Map<String, ? super Object> params, File msi,
File outdir) throws IOException {
Log.verbose(MessageFormat.format(
getString("message.outputting-to-location"),
I18N.getString("message.outputting-to-location"),
outdir.getAbsolutePath()));
// Copy template msi wrapper next to msi file
String exePath = msi.getAbsolutePath();
exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe";
final Path exePath = IOUtils.replaceSuffix(msi.toPath(), ".exe");
try (InputStream is = OverridableResource.readDefault(EXE_WRAPPER_NAME)) {
Files.copy(is, Path.of(exePath));
Files.copy(is, exePath);
}
// Embed msi in msi wrapper exe.
embedMSI(exePath, msi.getAbsolutePath());
new ExecutableRebrander().addAction((resourceLock) -> {
// Embed msi in msi wrapper exe.
embedMSI(resourceLock, msi.getAbsolutePath());
}).rebrandInstaller(params, exePath);
Path dstExePath = Paths.get(outdir.getAbsolutePath(),
Path.of(exePath).getFileName().toString());
exePath.getFileName().toString());
Files.deleteIfExists(dstExePath);
Files.copy(Path.of(exePath), dstExePath);
Files.copy(exePath, dstExePath);
Log.verbose(MessageFormat.format(
getString("message.output-location"),
I18N.getString("message.output-location"),
outdir.getAbsolutePath()));
return dstExePath.toFile();
}
private static String getString(String key)
throws MissingResourceException {
return I18N.getString(key);
}
private final WinMsiBundler msiBundler = new WinMsiBundler();
private static native int embedMSI(String exePath, String msiPath);
private static native int embedMSI(long resourceLock, String msiPath);
}

@ -26,49 +26,28 @@
package jdk.incubator.jpackage.internal;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.nio.charset.StandardCharsets;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermission;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
static {
System.loadLibrary("jpackage");
}
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.WinResources");
private final static String LIBRARY_NAME = "applauncher.dll";
private final static String TEMPLATE_APP_ICON ="java48.ico";
private static final String EXECUTABLE_PROPERTIES_TEMPLATE =
"WinLauncher.template";
private static final String TEMPLATE_APP_ICON ="java48.ico";
private final Path root;
private final Path appDir;
@ -77,13 +56,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
private final Path mdir;
private final Path binDir;
public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE =
new WindowsBundlerParam<>(
"win.launcher.rebrand",
Boolean.class,
params -> Boolean.TRUE,
(s, p) -> Boolean.valueOf(s));
public static final BundlerParamInfo<File> ICON_ICO =
new StandardBundlerParam<>(
"icon.ico",
@ -150,16 +122,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
return "app/" + APP_NAME.fetchFrom(params) +".cfg";
}
private File getConfig_ExecutableProperties(
Map<String, ? super Object> params) {
return new File(getConfigRoot(params),
APP_NAME.fetchFrom(params) + ".properties");
}
File getConfigRoot(Map<String, ? super Object> params) {
return CONFIG_ROOT.fetchFrom(params);
}
@Override
public Path getAppDir() {
return appDir;
@ -200,41 +162,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
public void prepareJreFiles(Map<String, ? super Object> params)
throws IOException {}
private void validateValueAndPut(
Map<String, String> data, String key,
BundlerParamInfo<String> param,
Map<String, ? super Object> params) {
String value = param.fetchFrom(params);
if (value.contains("\r") || value.contains("\n")) {
Log.error("Configuration Parameter " + param.getID()
+ " contains multiple lines of text, ignore it");
data.put(key, "");
return;
}
data.put(key, value);
}
protected void prepareExecutableProperties(
Map<String, ? super Object> params) throws IOException {
Map<String, String> data = new HashMap<>();
// mapping Java parameters in strings for version resource
validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
validateValueAndPut(data, "FILE_VERSION", VERSION, params);
data.put("INTERNAL_NAME", getLauncherName(params));
validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
data.put("ORIGINAL_FILENAME", getLauncherName(params));
validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params);
createResource(EXECUTABLE_PROPERTIES_TEMPLATE, params)
.setCategory(I18N.getString("resource.executable-properties-template"))
.setSubstitutionData(data)
.saveToFile(getConfig_ExecutableProperties(params));
}
private void createLauncherForEntryPoint(Map<String, ? super Object> params,
Map<String, ? super Object> mainParams) throws IOException {
@ -251,8 +178,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
writeCfgFile(params, root.resolve(
getLauncherCfgName(params)).toFile());
prepareExecutableProperties(params);
// Copy executable to bin folder
Path executableFile = binDir.resolve(getLauncherName(params));
@ -261,47 +186,11 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
writeEntry(is_launcher, executableFile);
}
File launcher = executableFile.toFile();
launcher.setWritable(true, true);
// Update branding of launcher executable
new ExecutableRebrander().rebrandLauncher(params, iconTarget, executableFile);
// Update branding of EXE file
if (REBRAND_EXECUTABLE.fetchFrom(params)) {
try {
String tempDirectory = WindowsDefender.getUserTempDirectory();
if (Arguments.CLIOptions.context().userProvidedBuildRoot) {
tempDirectory =
TEMP_ROOT.fetchFrom(params).getAbsolutePath();
}
if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
tempDirectory)) {
Log.verbose(MessageFormat.format(I18N.getString(
"message.potential.windows.defender.issue"),
tempDirectory));
}
launcher.setWritable(true);
if (iconTarget != null) {
iconSwap(iconTarget.toAbsolutePath().toString(),
launcher.getAbsolutePath());
}
File executableProperties =
getConfig_ExecutableProperties(params);
if (executableProperties.exists()) {
if (versionSwap(executableProperties.getAbsolutePath(),
launcher.getAbsolutePath()) != 0) {
throw new RuntimeException(MessageFormat.format(
I18N.getString("error.version-swap"),
executableProperties.getAbsolutePath()));
}
}
} finally {
executableFile.toFile().setExecutable(true);
executableFile.toFile().setReadOnly();
}
}
executableFile.toFile().setExecutable(true);
executableFile.toFile().setReadOnly();
}
private void copyApplication(Map<String, ? super Object> params)
@ -321,10 +210,4 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
}
}
}
private static native int iconSwap(String iconTarget, String launcher);
private static native int versionSwap(String executableProperties,
String launcher);
}

@ -0,0 +1,10 @@
FIXEDFILEINFO_FileVersion=FIXEDFILEINFO_FILE_VERSION
FIXEDFILEINFO_ProductVersion=FIXEDFILEINFO_FILE_VERSION
CompanyName=COMPANY_NAME
FileDescription=Installer of FILE_DESCRIPTION
FileVersion=FILE_VERSION
InternalName=INTERNAL_NAME
LegalCopyright=LEGAL_COPYRIGHT
OriginalFilename=ORIGINAL_FILENAME
ProductName=PRODUCT_NAME Installer
ProductVersion=FILE_VERSION

@ -1,3 +1,5 @@
FIXEDFILEINFO_FileVersion=FIXEDFILEINFO_FILE_VERSION
FIXEDFILEINFO_ProductVersion=FIXEDFILEINFO_FILE_VERSION
CompanyName=COMPANY_NAME
FileDescription=FILE_DESCRIPTION
FileVersion=FILE_VERSION
@ -5,4 +7,4 @@ InternalName=INTERNAL_NAME
LegalCopyright=LEGAL_COPYRIGHT
OriginalFilename=ORIGINAL_FILENAME
ProductName=PRODUCT_NAME
ProductVersion=PRODUCT_VERSION
ProductVersion=FILE_VERSION

@ -1,75 +0,0 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "ByteBuffer.h"
#include <stdio.h>
ByteBuffer::ByteBuffer() {
buffer.reserve(1024);
}
ByteBuffer::~ByteBuffer() {
}
LPBYTE ByteBuffer::getPtr() {
return &buffer[0];
}
size_t ByteBuffer::getPos() {
return buffer.size();
}
void ByteBuffer::AppendString(wstring str) {
size_t len = (str.size() + 1) * sizeof (WCHAR);
AppendBytes((BYTE*) str.c_str(), len);
}
void ByteBuffer::AppendWORD(WORD word) {
AppendBytes((BYTE*) & word, sizeof (WORD));
}
void ByteBuffer::Align(size_t bytesNumber) {
size_t pos = getPos();
if (pos % bytesNumber) {
DWORD dwNull = 0;
size_t len = bytesNumber - pos % bytesNumber;
AppendBytes((BYTE*) & dwNull, len);
}
}
void ByteBuffer::AppendBytes(BYTE* ptr, size_t len) {
buffer.insert(buffer.end(), ptr, ptr + len);
}
void ByteBuffer::ReplaceWORD(size_t offset, WORD word) {
ReplaceBytes(offset, (BYTE*) & word, sizeof (WORD));
}
void ByteBuffer::ReplaceBytes(size_t offset, BYTE* ptr, size_t len) {
for (size_t i = 0; i < len; i++) {
buffer[offset + i] = *(ptr + i);
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -96,7 +96,7 @@ void PrintError() {
// Note: We do not check here that iconTarget is valid icon.
// Java code will already do this for us.
bool ChangeIcon(wstring iconTarget, wstring launcher) {
bool ChangeIcon(HANDLE update, const wstring& iconTarget) {
WORD language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);
HANDLE icon = CreateFile(iconTarget.c_str(), GENERIC_READ, 0, NULL,
@ -154,15 +154,6 @@ bool ChangeIcon(wstring iconTarget, wstring launcher) {
}
// Store images in .EXE
HANDLE update = BeginUpdateResource(launcher.c_str(), FALSE);
if (update == NULL) {
free(lpid);
free(lpgid);
CloseHandle(icon);
PrintError();
return false;
}
for (int i = 0; i < lpid->idCount; i++) {
LPBYTE lpBuffer = (LPBYTE) malloc(lpid->idEntries[i].dwBytesInRes);
SetFilePointer(icon, lpid->idEntries[i].dwImageOffset,
@ -195,10 +186,5 @@ bool ChangeIcon(wstring iconTarget, wstring launcher) {
free(lpgid);
if (EndUpdateResource(update, FALSE) == FALSE) {
PrintError();
return false;
}
return true;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,9 +28,7 @@
#include <string>
using namespace std;
bool ChangeIcon(wstring iconTarget, wstring launcher);
bool ChangeIcon(HANDLE update, const std::wstring& iconTarget);
#endif // ICONSWAP_H

@ -0,0 +1,83 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "JniUtils.h"
#include "ErrorHandling.h"
#include "Toolbox.h"
namespace jni {
void JniObjWithEnv::LocalRefDeleter::operator()(pointer v) {
if (v.env && v.obj) {
v.env->DeleteLocalRef(v.obj);
}
}
#ifdef TSTRINGS_WITH_WCHAR
std::wstring toUnicodeString(JNIEnv *env, jstring val) {
const jchar* chars = env->GetStringChars(val, 0);
if (!chars) {
JP_THROW("GetStringChars() failed");
}
const auto releaseStringChars =
runAtEndOfScope([env, val, chars]() -> void {
env->ReleaseStringChars(val, chars);
});
const jsize len = env->GetStringLength(val);
return std::wstring(reinterpret_cast<const wchar_t*>(chars), len);
}
jstring toJString(JNIEnv *env, const std::wstring& val) {
jstring result = env->NewString(
reinterpret_cast<const jchar*>(val.c_str()), jsize(val.size()));
if (!result) {
JP_THROW("NewString() failed");
}
return result;
}
#endif
tstring_array toUnicodeStringArray(JNIEnv *env, jobjectArray val) {
tstring_array result;
const jsize len = env->GetArrayLength(val);
for (int i = 0; i < len; ++i) {
LocalRef localRef(JniObjWithEnv(env,
env->GetObjectArrayElement(val, i)));
result.push_back(toUnicodeString(env,
static_cast<jstring>(localRef.get().obj)));
}
return result;
}
} // namespace jni

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,31 +23,49 @@
* questions.
*/
#ifndef VERSIONINFOSWAP_H
#define VERSIONINFOSWAP_H
#ifndef JNIUTILS_H
#define JNIUTILS_H
#include "ByteBuffer.h"
#include <map>
#include "jni.h"
#include "tstrings.h"
using namespace std;
class VersionInfoSwap {
public:
VersionInfoSwap(wstring executableProperties, wstring launcher);
namespace jni {
bool PatchExecutable();
struct JniObjWithEnv {
JniObjWithEnv(): env(0), obj(0) {
private:
wstring m_executableProperties;
wstring m_launcher;
}
map<wstring, wstring> m_props;
JniObjWithEnv(JNIEnv *env, jobject obj) : env(env), obj(obj) {
}
bool LoadFromPropertyFile();
bool CreateNewResource(ByteBuffer *buf);
bool UpdateResource(LPVOID lpResLock, DWORD size);
bool FillFixedFileInfo(VS_FIXEDFILEINFO *fxi);
bool operator == (const JniObjWithEnv& other) const {
return env == other.env && obj == other.obj;
}
bool operator != (const JniObjWithEnv& other) const {
return ! operator == (other);
}
JNIEnv *env;
jobject obj;
struct LocalRefDeleter {
typedef JniObjWithEnv pointer;
void operator()(pointer v);
};
};
#endif // VERSIONINFOSWAP_H
typedef std::unique_ptr<JniObjWithEnv, JniObjWithEnv::LocalRefDeleter> LocalRef;
tstring toUnicodeString(JNIEnv *env, jstring val);
jstring toJString(JNIEnv *env, const tstring& val);
tstring_array toUnicodeStringArray(JNIEnv *env, jobjectArray val);
} // namespace jni
#endif // JNIUTILS_H

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -37,12 +37,19 @@ ResourceEditor::FileLock::FileLock(const std::wstring& binaryPath) {
<< binaryPath << ") failed", BeginUpdateResource));
}
ownHandle(true);
discard(false);
}
ResourceEditor::FileLock::FileLock(HANDLE h): h(h) {
ownHandle(false);
discard(false);
}
ResourceEditor::FileLock::~FileLock() {
if (!EndUpdateResource(h, theDiscard)) {
if (theOwnHandle && !EndUpdateResource(h, theDiscard)) {
JP_NO_THROW(JP_THROW(SysError(tstrings::any()
<< "EndUpdateResource(" << h << ") failed.", EndUpdateResource)));
}
@ -85,8 +92,8 @@ ResourceEditor& ResourceEditor::id(LPCWSTR v) {
theId = printer.str();
} else {
theId = v;
theIdPtr = theId.c_str();
}
theIdPtr = v;
return *this;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,7 +35,8 @@ class ResourceEditor {
public:
class FileLock {
public:
FileLock(const std::wstring& binaryPath);
explicit FileLock(const std::wstring& binaryPath);
explicit FileLock(HANDLE h);
~FileLock();
HANDLE get() const {
@ -46,11 +47,17 @@ public:
theDiscard = v;
}
FileLock& ownHandle(bool v) {
theOwnHandle = v;
return *this;
}
private:
FileLock(const FileLock&);
FileLock& operator=(const FileLock&);
private:
HANDLE h;
bool theOwnHandle;
bool theDiscard;
};

@ -1,82 +0,0 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "Windows.h"
#include "Utils.h"
#define BUFFER_SIZE 4096
wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr) {
const jchar *pJChars = pEnv->GetStringChars(jstr, NULL);
if (pJChars == NULL) {
return wstring(L"");
}
wstring wstr(pJChars);
pEnv->ReleaseStringChars(jstr, pJChars);
return wstr;
}
jstring GetJStringFromString(JNIEnv *pEnv,
const jchar *unicodeChars, jsize len) {
return pEnv->NewString(unicodeChars, len);
}
wstring GetLongPath(wstring path) {
wstring result(L"");
size_t len = path.length();
if (len > 1) {
if (path.at(len - 1) == '\\') {
path.erase(len - 1);
}
}
TCHAR *pBuffer = new TCHAR[BUFFER_SIZE];
if (pBuffer != NULL) {
DWORD dwResult = GetLongPathName(path.c_str(), pBuffer, BUFFER_SIZE);
if (dwResult > 0 && dwResult < BUFFER_SIZE) {
result = wstring(pBuffer);
} else {
delete [] pBuffer;
pBuffer = new TCHAR[dwResult];
if (pBuffer != NULL) {
DWORD dwResult2 =
GetLongPathName(path.c_str(), pBuffer, dwResult);
if (dwResult2 == (dwResult - 1)) {
result = wstring(pBuffer);
}
}
}
if (pBuffer != NULL) {
delete [] pBuffer;
}
}
return result;
}

@ -1,40 +0,0 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef UTILS_H
#define UTILS_H
#include <string>
#include "jni.h"
using namespace std;
wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr);
jstring GetJStringFromString(JNIEnv *pEnv, const jchar *unicodeChars,
jsize len);
wstring GetLongPath(wstring path);
#endif // UTILS_H

@ -0,0 +1,276 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <sstream>
#include <iomanip>
#include "VersionInfo.h"
#include "ResourceEditor.h"
#include "ErrorHandling.h"
#include "Toolbox.h"
VersionInfo::VersionInfo() {
}
namespace {
class FixedFileVersion {
public:
FixedFileVersion(const std::wstring& value) {
if (4 != swscanf_s(value.c_str(), L"%d.%d.%d.%d", components + 0,
components + 1, components + 2, components + 3)) {
JP_THROW(tstrings::any()
<< "Malformed file version value: ["
<< value
<< "]");
forEach(components, [&value](int component) -> void {
if (USHRT_MAX < component) {
JP_THROW(tstrings::any()
<< "Invalid file version value: ["
<< value
<< "]");
}
});
}
}
void apply(DWORD& ms, DWORD& ls) const {
ms = MAKELONG(components[1], components[0]);
ls = MAKELONG(components[3], components[2]);
}
private:
int components[4];
};
std::ostream& writeWORD(std::ostream& cout, size_t v) {
if (USHRT_MAX < v) {
JP_THROW("Invalid WORD value");
}
return cout.write(reinterpret_cast<const char*>(&v), sizeof(WORD));
}
std::ostream& writeDWORD(std::ostream& cout, size_t v) {
if (UINT_MAX < v) {
JP_THROW("Invalid DWORD value");
}
return cout.write(reinterpret_cast<const char*>(&v), sizeof(DWORD));
}
std::ostream& write(std::ostream& cout, const VS_FIXEDFILEINFO& v) {
return cout.write(reinterpret_cast<const char*>(&v), sizeof(v));
}
std::ostream& write(std::ostream& cout, const std::wstring& s) {
return cout.write(reinterpret_cast<const char*>(s.c_str()),
(s.size() + 1 /* trailing 0 */) * sizeof(wchar_t));
}
void add32bitPadding(std::ostream& cout) {
enum { WordAlign = 2 };
const std::streampos pos = cout.tellp();
if (pos % 2) {
JP_THROW("Invalid data written in the stream");
}
const int padding = WordAlign - (pos / 2) % WordAlign;
if (WordAlign != padding) {
for (int i = 0; i < padding; ++i) {
writeWORD(cout, 0);
}
}
}
class StreamSize {
public:
StreamSize(std::ostream& out): stream(out), anchor(out.tellp()) {
writeWORD(stream, 0); // placeholder
}
~StreamSize() {
JP_TRY;
const std::streampos curPos = stream.tellp();
const std::streampos size = curPos - anchor;
stream.seekp(anchor);
writeWORD(stream, size);
stream.seekp(curPos);
JP_CATCH_ALL;
}
private:
std::ostream& stream;
std::streampos anchor;
};
} // namespace
VersionInfo& VersionInfo::setProperty(
const std::wstring& id, const std::wstring& value) {
props[id] = value;
if (id == L"FIXEDFILEINFO_FileVersion") {
// Validate input
const ::FixedFileVersion validate(value);
}
return *this;
}
const VersionInfo& VersionInfo::apply(
const ResourceEditor::FileLock& fileLock) const {
if (props.find(L"FIXEDFILEINFO_FileVersion") == props.end()) {
JP_THROW("Missing mandatory FILEVERSION property");
}
std::stringstream buf(
std::stringstream::in | std::stringstream::out | std::stringstream::binary);
buf.exceptions(std::ios::failbit | std::ios::badbit);
fillBuffer(buf);
buf.seekg(0);
ResourceEditor()
.id(MAKEINTRESOURCE(VS_VERSION_INFO))
.type(RT_VERSION)
.apply(fileLock, buf);
return *this;
}
void VersionInfo::fillBuffer(std::ostream& buf) const {
// Fill VS_VERSIONINFO pseudo structure
StreamSize versionInfoLength(buf); // wLength
writeWORD(buf, sizeof(VS_FIXEDFILEINFO)); // wValueLength
writeWORD(buf, 0); // wType
write(buf, L"VS_VERSION_INFO"); // szKey
add32bitPadding(buf); // Padding1
write(buf, createFIXEDFILEINFO()); // Value
add32bitPadding(buf); // Padding2
const DWORD neutralLangId = (0x04b0 | MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) << 16);
const DWORD engLangId = (0x04b0 | MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16);
do {
// Fill StringFileInfo pseudo structure
StreamSize stringFileInfoLength(buf); // wLength
writeWORD(buf, 0); // wValueLength
writeWORD(buf, 1); // wType
write(buf, L"StringFileInfo"); // szKey
add32bitPadding(buf); // Padding
// Fill StringTable pseudo structure
StreamSize stringTableLength(buf); // wLength
writeWORD(buf, 0); // wValueLength
writeWORD(buf, 1); // wType
const std::wstring strLangId = (std::wstringstream()
<< std::uppercase
<< std::hex
<< std::setw(8)
<< std::setfill(L'0')
<< engLangId).str();
write(buf, strLangId); // szKey
add32bitPadding(buf); // Padding
forEach(props, [&buf](const PropertyMap::value_type& entry) -> void {
if (entry.first.rfind(L"FIXEDFILEINFO_", 0) == 0) {
// Ignore properties to be used to initialize data in
// VS_FIXEDFILEINFO structure.
return;
}
// Fill String pseudo structure
StreamSize stringLength(buf); // wLength
writeWORD(buf, entry.second.size()); // wValueLength
writeWORD(buf, 1); // wType
write(buf, entry.first); // wKey
add32bitPadding(buf); // Padding1
write(buf, entry.second); // Value
add32bitPadding(buf); // Padding2
});
} while (0);
// Fill VarFileInfo pseudo structure
StreamSize varFileInfoLength(buf); // wLength
writeWORD(buf, 0); // wValueLength
writeWORD(buf, 1); // wType
write(buf, L"VarFileInfo"); // szKey
add32bitPadding(buf); // Padding
// Fill Var pseudo structure
StreamSize varLength(buf); // wLength
writeWORD(buf, sizeof(DWORD)); // wValueLength
writeWORD(buf, 0); // wType
write(buf, L"Translation"); // szKey
add32bitPadding(buf); // Padding
writeDWORD(buf, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); // Value
}
VS_FIXEDFILEINFO VersionInfo::createFIXEDFILEINFO() const {
const ::FixedFileVersion fileVersion(props.find(
L"FIXEDFILEINFO_FileVersion")->second);
VS_FIXEDFILEINFO result;
ZeroMemory(&result, sizeof(result));
result.dwSignature = 0xFEEF04BD;
result.dwStrucVersion = 0x00010000;
result.dwFileOS = VOS_NT_WINDOWS32;
result.dwFileType = VFT_APP;
fileVersion.apply(result.dwFileVersionMS, result.dwFileVersionLS);
PropertyMap::const_iterator entry = props.find(
L"FIXEDFILEINFO_ProductVersion");
if (entry == props.end()) {
fileVersion.apply(result.dwProductVersionMS, result.dwProductVersionLS);
} else {
bool fatalError = false;
try {
const ::FixedFileVersion productVersion(entry->second);
fatalError = true;
productVersion.apply(result.dwProductVersionMS,
result.dwProductVersionLS);
} catch (const std::exception&) {
if (fatalError) {
throw;
}
// Failed to parse product version as four component version string.
fileVersion.apply(result.dwProductVersionMS,
result.dwProductVersionLS);
}
}
return result;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,35 +23,40 @@
* questions.
*/
#ifndef BYTEBUFFER_H
#define BYTEBUFFER_H
#ifndef VERSIONINFO_H
#define VERSIONINFO_H
#include <windows.h>
#include <vector>
#include <map>
#include <string>
#include "ResourceEditor.h"
using namespace std;
class ByteBuffer {
class VersionInfo {
public:
ByteBuffer();
~ByteBuffer();
VersionInfo();
LPBYTE getPtr();
size_t getPos();
VersionInfo& setProperty(const std::wstring& id, const std::wstring& value);
void AppendString(wstring str);
void AppendWORD(WORD word);
void AppendBytes(BYTE* ptr, size_t len);
/**
* Replaces existing VS_VERSIONINFO structure in the file locked
* with the passed in ResourceEditor::FileLock instance with data
* configured for this instance.
*/
const VersionInfo& apply(const ResourceEditor::FileLock& fileLock) const;
void ReplaceWORD(size_t offset, WORD word);
void ReplaceBytes(size_t offset, BYTE* ptr, size_t len);
void Align(size_t bytesNumber);
VersionInfo& apply(const ResourceEditor::FileLock& fileLock) {
static_cast<const VersionInfo&>(*this).apply(fileLock);
return *this;
}
private:
vector<BYTE> buffer;
void fillBuffer(std::ostream& buf) const;
VS_FIXEDFILEINFO createFIXEDFILEINFO() const;
typedef std::map<std::wstring, std::wstring> PropertyMap;
PropertyMap props;
};
#endif // BYTEBUFFER_H
#endif // #ifndef VERSIONINFO_H

@ -1,285 +0,0 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "VersionInfoSwap.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <Strsafe.h>
#include <fstream>
#include <locale>
#include <codecvt>
using namespace std;
/*
* [Property file] contains key/value pairs
* The swap tool uses these pairs to create new version resource
*
* See MSDN docs for VS_VERSIONINFO structure that
* depicts organization of data in this version resource
* https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
*
* The swap tool makes changes in [Executable file]
* The tool assumes that the executable file has no version resource
* and it adds new resource in the executable file.
* If the executable file has an existing version resource, then
* the existing version resource will be replaced with new one.
*/
VersionInfoSwap::VersionInfoSwap(wstring executableProperties,
wstring launcher) {
m_executableProperties = executableProperties;
m_launcher = launcher;
}
bool VersionInfoSwap::PatchExecutable() {
bool b = LoadFromPropertyFile();
if (!b) {
return false;
}
ByteBuffer buf;
b = CreateNewResource(&buf);
if (!b) {
return false;
}
b = this->UpdateResource(buf.getPtr(), static_cast<DWORD> (buf.getPos()));
if (!b) {
return false;
}
return true;
}
bool VersionInfoSwap::LoadFromPropertyFile() {
wifstream stream(m_executableProperties.c_str());
const locale empty_locale = locale::empty();
const locale utf8_locale =
locale(empty_locale, new codecvt_utf8<wchar_t>());
stream.imbue(utf8_locale);
if (stream.is_open() == true) {
int lineNumber = 1;
while (stream.eof() == false) {
wstring line;
getline(stream, line);
// # at the first character will comment out the line.
if (line.empty() == false && line[0] != '#') {
wstring::size_type pos = line.find('=');
if (pos != wstring::npos) {
wstring name = line.substr(0, pos);
wstring value = line.substr(pos + 1);
m_props[name] = value;
}
}
lineNumber++;
}
return true;
}
return false;
}
/*
* Creates new version resource
*
* MSND docs for VS_VERSION_INFO structure
* https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
*/
bool VersionInfoSwap::CreateNewResource(ByteBuffer *buf) {
size_t versionInfoStart = buf->getPos();
buf->AppendWORD(0);
buf->AppendWORD(sizeof VS_FIXEDFILEINFO);
buf->AppendWORD(0);
buf->AppendString(TEXT("VS_VERSION_INFO"));
buf->Align(4);
VS_FIXEDFILEINFO fxi;
if (!FillFixedFileInfo(&fxi)) {
return false;
}
buf->AppendBytes((BYTE*) & fxi, sizeof (VS_FIXEDFILEINFO));
buf->Align(4);
// String File Info
size_t stringFileInfoStart = buf->getPos();
buf->AppendWORD(0);
buf->AppendWORD(0);
buf->AppendWORD(1);
buf->AppendString(TEXT("StringFileInfo"));
buf->Align(4);
// String Table
size_t stringTableStart = buf->getPos();
buf->AppendWORD(0);
buf->AppendWORD(0);
buf->AppendWORD(1);
// "040904B0" = LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP
buf->AppendString(TEXT("040904B0"));
buf->Align(4);
// Strings
vector<wstring> keys;
for (map<wstring, wstring>::const_iterator it =
m_props.begin(); it != m_props.end(); ++it) {
keys.push_back(it->first);
}
for (size_t index = 0; index < keys.size(); index++) {
wstring name = keys[index];
wstring value = m_props[name];
size_t stringStart = buf->getPos();
buf->AppendWORD(0);
buf->AppendWORD(static_cast<WORD> (value.length()));
buf->AppendWORD(1);
buf->AppendString(name);
buf->Align(4);
buf->AppendString(value);
buf->ReplaceWORD(stringStart,
static_cast<WORD> (buf->getPos() - stringStart));
buf->Align(4);
}
buf->ReplaceWORD(stringTableStart,
static_cast<WORD> (buf->getPos() - stringTableStart));
buf->ReplaceWORD(stringFileInfoStart,
static_cast<WORD> (buf->getPos() - stringFileInfoStart));
// VarFileInfo
size_t varFileInfoStart = buf->getPos();
buf->AppendWORD(1);
buf->AppendWORD(0);
buf->AppendWORD(1);
buf->AppendString(TEXT("VarFileInfo"));
buf->Align(4);
buf->AppendWORD(0x24);
buf->AppendWORD(0x04);
buf->AppendWORD(0x00);
buf->AppendString(TEXT("Translation"));
buf->Align(4);
// "000004B0" = LANG_NEUTRAL/SUBLANG_ENGLISH_US, Unicode CP
buf->AppendWORD(0x0000);
buf->AppendWORD(0x04B0);
buf->ReplaceWORD(varFileInfoStart,
static_cast<WORD> (buf->getPos() - varFileInfoStart));
buf->ReplaceWORD(versionInfoStart,
static_cast<WORD> (buf->getPos() - versionInfoStart));
return true;
}
bool VersionInfoSwap::FillFixedFileInfo(VS_FIXEDFILEINFO *fxi) {
wstring fileVersion;
wstring productVersion;
int ret;
fileVersion = m_props[TEXT("FileVersion")];
productVersion = m_props[TEXT("ProductVersion")];
unsigned fv_1 = 0, fv_2 = 0, fv_3 = 0, fv_4 = 0;
unsigned pv_1 = 0, pv_2 = 0, pv_3 = 0, pv_4 = 0;
ret = _stscanf_s(fileVersion.c_str(),
TEXT("%d.%d.%d.%d"), &fv_1, &fv_2, &fv_3, &fv_4);
if (ret <= 0 || ret > 4) {
return false;
}
ret = _stscanf_s(productVersion.c_str(),
TEXT("%d.%d.%d.%d"), &pv_1, &pv_2, &pv_3, &pv_4);
if (ret <= 0 || ret > 4) {
return false;
}
fxi->dwSignature = 0xFEEF04BD;
fxi->dwStrucVersion = 0x00010000;
fxi->dwFileVersionMS = MAKELONG(fv_2, fv_1);
fxi->dwFileVersionLS = MAKELONG(fv_4, fv_3);
fxi->dwProductVersionMS = MAKELONG(pv_2, pv_1);
fxi->dwProductVersionLS = MAKELONG(pv_4, pv_3);
fxi->dwFileFlagsMask = 0;
fxi->dwFileFlags = 0;
fxi->dwFileOS = VOS_NT_WINDOWS32;
wstring exeExt =
m_launcher.substr(m_launcher.find_last_of(TEXT(".")));
if (exeExt == TEXT(".exe")) {
fxi->dwFileType = VFT_APP;
} else if (exeExt == TEXT(".dll")) {
fxi->dwFileType = VFT_DLL;
} else {
fxi->dwFileType = VFT_UNKNOWN;
}
fxi->dwFileSubtype = 0;
fxi->dwFileDateLS = 0;
fxi->dwFileDateMS = 0;
return true;
}
/*
* Adds new resource in the executable
*/
bool VersionInfoSwap::UpdateResource(LPVOID lpResLock, DWORD size) {
HANDLE hUpdateRes;
BOOL r;
hUpdateRes = ::BeginUpdateResource(m_launcher.c_str(), FALSE);
if (hUpdateRes == NULL) {
return false;
}
r = ::UpdateResource(hUpdateRes,
RT_VERSION,
MAKEINTRESOURCE(VS_VERSION_INFO),
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
lpResLock,
size);
if (!r) {
return false;
}
if (!::EndUpdateResource(hUpdateRes, FALSE)) {
return false;
}
return true;
}

@ -23,17 +23,50 @@
* questions.
*/
#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <jni.h>
#include "Utils.h"
#include "JniUtils.h"
#include "FileUtils.h"
#include "ErrorHandling.h"
#pragma comment(lib, "advapi32")
// Max value name size per MSDN plus NULL
#define VALUE_NAME_SIZE 16384
namespace {
std::wstring GetLongPath(const std::wstring& path) {
const std::wstring cleanPath = FileUtils::removeTrailingSlash(path);
if (cleanPath.size() != path.size()) {
return GetLongPath(cleanPath);
}
enum { BUFFER_SIZE = 4096 };
std::wstring result;
TCHAR *pBuffer = new TCHAR[BUFFER_SIZE];
if (pBuffer != NULL) {
DWORD dwResult = GetLongPathName(path.c_str(), pBuffer, BUFFER_SIZE);
if (dwResult > 0 && dwResult < BUFFER_SIZE) {
result = std::wstring(pBuffer);
} else {
delete [] pBuffer;
pBuffer = new TCHAR[dwResult];
if (pBuffer != NULL) {
DWORD dwResult2 =
GetLongPathName(path.c_str(), pBuffer, dwResult);
if (dwResult2 == (dwResult - 1)) {
result = std::wstring(pBuffer);
}
}
}
if (pBuffer != NULL) {
delete [] pBuffer;
}
}
return result;
}
} // namespace
#ifdef __cplusplus
extern "C" {
@ -52,12 +85,14 @@ extern "C" {
jstring jValue, jint defaultValue) {
jint jResult = defaultValue;
JP_TRY;
if (key != jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
return jResult;
JP_THROW("Inavlid Windows registry key id");
}
wstring subKey = GetStringFromJString(pEnv, jSubKey);
wstring value = GetStringFromJString(pEnv, jValue);
const std::wstring subKey = jni::toUnicodeString(pEnv, jSubKey);
const std::wstring value = jni::toUnicodeString(pEnv, jValue);
HKEY hSubKey = NULL;
LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
@ -74,6 +109,8 @@ extern "C" {
RegCloseKey(hSubKey);
}
JP_CATCH_ALL;
return jResult;
}
@ -85,11 +122,14 @@ extern "C" {
JNIEXPORT jlong JNICALL
Java_jdk_incubator_jpackage_internal_WindowsRegistry_openRegistryKey(
JNIEnv *pEnv, jclass c, jint key, jstring jSubKey) {
JP_TRY;
if (key != jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
return 0;
JP_THROW("Inavlid Windows registry key id");
}
wstring subKey = GetStringFromJString(pEnv, jSubKey);
const std::wstring subKey = jni::toUnicodeString(pEnv, jSubKey);
HKEY hSubKey = NULL;
LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
KEY_QUERY_VALUE, &hSubKey);
@ -97,6 +137,8 @@ extern "C" {
return (jlong)hSubKey;
}
JP_CATCH_ALL;
return 0;
}
@ -108,6 +150,12 @@ extern "C" {
JNIEXPORT jstring JNICALL
Java_jdk_incubator_jpackage_internal_WindowsRegistry_enumRegistryValue(
JNIEnv *pEnv, jclass c, jlong lKey, jint jIndex) {
JP_TRY;
// Max value name size per MSDN plus NULL
enum { VALUE_NAME_SIZE = 16384 };
HKEY hKey = (HKEY)lKey;
TCHAR valueName[VALUE_NAME_SIZE] = {0}; // Max size per MSDN plus NULL
DWORD cchValueName = VALUE_NAME_SIZE;
@ -117,10 +165,12 @@ extern "C" {
size_t chLength = 0;
if (StringCchLength(valueName, VALUE_NAME_SIZE, &chLength)
== S_OK) {
return GetJStringFromString(pEnv, valueName, (jsize)chLength);
return jni::toJString(pEnv, std::wstring(valueName, chLength));
}
}
JP_CATCH_ALL;
return NULL;
}
@ -144,24 +194,25 @@ extern "C" {
JNIEXPORT jboolean JNICALL
Java_jdk_incubator_jpackage_internal_WindowsRegistry_comparePaths(
JNIEnv *pEnv, jclass c, jstring jPath1, jstring jPath2) {
wstring path1 = GetStringFromJString(pEnv, jPath1);
wstring path2 = GetStringFromJString(pEnv, jPath2);
JP_TRY;
std::wstring path1 = jni::toUnicodeString(pEnv, jPath1);
std::wstring path2 = jni::toUnicodeString(pEnv, jPath2);
path1 = GetLongPath(path1);
path2 = GetLongPath(path2);
if (path1.length() == 0 || path2.length() == 0) {
if (path1.empty() || path2.empty()) {
return JNI_FALSE;
}
if (path1.length() != path2.length()) {
return JNI_FALSE;
}
if (_tcsnicmp(path1.c_str(), path2.c_str(), path1.length()) == 0) {
if (tstrings::equals(path1, path2, tstrings::IGNORE_CASE)) {
return JNI_TRUE;
}
JP_CATCH_ALL;
return JNI_FALSE;
}

@ -23,80 +23,125 @@
* questions.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <windows.h>
#include "ResourceEditor.h"
#include "WinErrorHandling.h"
#include "ErrorHandling.h"
#include "IconSwap.h"
#include "VersionInfoSwap.h"
#include "Utils.h"
using namespace std;
#include "VersionInfo.h"
#include "JniUtils.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jdk_incubator_jpackage_internal_WindowsAppImageBuilder
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
* Method: lockResource
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_lockResource(
JNIEnv *pEnv, jclass c, jstring jExecutable) {
JP_TRY;
const std::wstring executable = jni::toUnicodeString(pEnv, jExecutable);
return reinterpret_cast<jlong>(
ResourceEditor::FileLock(executable).ownHandle(false).get());
JP_CATCH_ALL;
return 0;
}
/*
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
* Method: unlockResource
* Signature: (J;)V
*/
JNIEXPORT void JNICALL
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_unlockResource(
JNIEnv *pEnv, jclass c, jlong jResourceLock) {
JP_TRY;
ResourceEditor::FileLock(
reinterpret_cast<HANDLE>(jResourceLock)).ownHandle(true);
JP_CATCH_ALL;
}
/*
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
* Method: iconSwap
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
* Signature: (J;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL
Java_jdk_incubator_jpackage_internal_WindowsAppImageBuilder_iconSwap(
JNIEnv *pEnv, jclass c, jstring jIconTarget, jstring jLauncher) {
wstring iconTarget = GetStringFromJString(pEnv, jIconTarget);
wstring launcher = GetStringFromJString(pEnv, jLauncher);
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_iconSwap(
JNIEnv *pEnv, jclass c, jlong jResourceLock, jstring jIconTarget) {
if (ChangeIcon(iconTarget, launcher)) {
JP_TRY;
const ResourceEditor::FileLock lock(reinterpret_cast<HANDLE>(jResourceLock));
const std::wstring iconTarget = jni::toUnicodeString(pEnv, jIconTarget);
if (ChangeIcon(lock.get(), iconTarget)) {
return 0;
}
JP_CATCH_ALL;
return 1;
}
/*
* Class: jdk_incubator_jpackage_internal_WindowsAppImageBuilder
* Class: jdk_incubator_jpackage_internal_ExecutableRebrander
* Method: versionSwap
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
* Signature: (J;[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL
Java_jdk_incubator_jpackage_internal_WindowsAppImageBuilder_versionSwap(
JNIEnv *pEnv, jclass c, jstring jExecutableProperties,
jstring jLauncher) {
Java_jdk_incubator_jpackage_internal_ExecutableRebrander_versionSwap(
JNIEnv *pEnv, jclass c, jlong jResourceLock,
jobjectArray jExecutableProperties) {
wstring executableProperties = GetStringFromJString(pEnv,
JP_TRY;
const tstring_array props = jni::toUnicodeStringArray(pEnv,
jExecutableProperties);
wstring launcher = GetStringFromJString(pEnv, jLauncher);
VersionInfoSwap vs(executableProperties, launcher);
if (vs.PatchExecutable()) {
return 0;
VersionInfo vi;
tstring_array::const_iterator it = props.begin();
tstring_array::const_iterator end = props.end();
for (; it != end; ++it) {
const tstring name = *it;
const tstring value = *++it;
vi.setProperty(name, value);
}
const ResourceEditor::FileLock lock(reinterpret_cast<HANDLE>(jResourceLock));
vi.apply(lock);
return 0;
JP_CATCH_ALL;
return 1;
}
/*
* Class: jdk_incubator_jpackage_internal_WinExeBundler
* Method: embedMSI
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
* Signature: (J;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_jdk_incubator_jpackage_internal_WinExeBundler_embedMSI(
JNIEnv *pEnv, jclass c, jstring jexePath, jstring jmsiPath) {
const wstring exePath = GetStringFromJString(pEnv, jexePath);
const wstring msiPath = GetStringFromJString(pEnv, jmsiPath);
JNIEnv *pEnv, jclass c, jlong jResourceLock, jstring jmsiPath) {
JP_TRY;
ResourceEditor()
.id(L"msi")
.type(RT_RCDATA)
.apply(ResourceEditor::FileLock(exePath), msiPath);
const std::wstring msiPath = jni::toUnicodeString(pEnv, jmsiPath);
const ResourceEditor::FileLock lock(reinterpret_cast<HANDLE>(jResourceLock));
ResourceEditor().id(L"msi").type(RT_RCDATA).apply(lock, msiPath);
return 0;