Skip to content

Commit cd6f659

Browse files
committed
8282351: jpackage does not work if class file has $$ in the name on windows
Reviewed-by: asemenyuk Backport-of: 29395534d9683a802364dc53610dee2b525fb032
1 parent 37c9cd1 commit cd6f659

File tree

2 files changed

+180
-2
lines changed

2 files changed

+180
-2
lines changed

src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixFragmentBuilder.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,13 +25,19 @@
2525
package jdk.jpackage.internal;
2626

2727
import java.io.IOException;
28+
import java.lang.reflect.InvocationHandler;
29+
import java.lang.reflect.Method;
30+
import java.lang.reflect.Proxy;
2831
import java.nio.file.Path;
2932
import java.text.MessageFormat;
3033
import java.util.ArrayList;
3134
import java.util.Collection;
3235
import java.util.List;
3336
import java.util.Map;
3437
import java.util.Optional;
38+
import java.util.regex.Matcher;
39+
import java.util.regex.Pattern;
40+
import javax.xml.stream.XMLStreamWriter;
3541
import jdk.jpackage.internal.IOUtils.XmlConsumer;
3642
import jdk.jpackage.internal.OverridableResource.Source;
3743
import static jdk.jpackage.internal.OverridableResource.createResource;
@@ -131,7 +137,9 @@ static void createWixSource(Path file, XmlConsumer xmlConsumer)
131137
xml.writeNamespace("util",
132138
"http://schemas.microsoft.com/wix/UtilExtension");
133139

134-
xmlConsumer.accept(xml);
140+
xmlConsumer.accept((XMLStreamWriter) Proxy.newProxyInstance(
141+
XMLStreamWriter.class.getClassLoader(), new Class<?>[]{
142+
XMLStreamWriter.class}, new WixPreprocessorEscaper(xml)));
135143

136144
xml.writeEndElement(); // <Wix>
137145
});
@@ -147,6 +155,58 @@ private static class ResourceWithName {
147155
private final String saveAsName;
148156
}
149157

158+
private static class WixPreprocessorEscaper implements InvocationHandler {
159+
160+
WixPreprocessorEscaper(XMLStreamWriter target) {
161+
this.target = target;
162+
}
163+
164+
@Override
165+
public Object invoke(Object proxy, Method method, Object[] args) throws
166+
Throwable {
167+
switch (method.getName()) {
168+
case "writeAttribute" -> {
169+
Object newArgs[] = new Object[args.length];
170+
for (int i = 0; i < args.length - 1; ++i) {
171+
newArgs[i] = args[i];
172+
}
173+
newArgs[args.length - 1] = escape(
174+
(CharSequence) args[args.length - 1]);
175+
return method.invoke(target, newArgs);
176+
}
177+
case "writeCData" -> {
178+
target.writeCData(escape((CharSequence) args[0]));
179+
return null;
180+
}
181+
case "writeCharacters" -> {
182+
if (args.length == 3) {
183+
// writeCharacters(char[] text, int start, int len)
184+
target.writeCharacters(escape(String.copyValueOf(
185+
(char[]) args[0], (int) args[1], (int) args[2])));
186+
} else {
187+
target.writeCharacters(escape((CharSequence) args[0]));
188+
}
189+
return null;
190+
}
191+
}
192+
return method.invoke(target, args);
193+
}
194+
195+
private String escape(CharSequence str) {
196+
Matcher m = dollarPattern.matcher(str);
197+
StringBuilder sb = new StringBuilder();
198+
while (m.find()) {
199+
m.appendReplacement(sb, "\\$\\$");
200+
}
201+
m.appendTail(sb);
202+
return sb.toString();
203+
}
204+
205+
// Match '$', but don't match $(var.foo)
206+
private final Pattern dollarPattern = Pattern.compile("\\$(?!\\([^)]*\\))");
207+
private final XMLStreamWriter target;
208+
}
209+
150210
private DottedVersion wixVersion;
151211
private WixVariables wixVariables;
152212
private List<ResourceWithName> additionalResources;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.io.IOException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import jdk.jpackage.test.PackageTest;
28+
import jdk.jpackage.test.JPackageCommand;
29+
import jdk.jpackage.test.Annotations.Test;
30+
import jdk.jpackage.test.PackageType;
31+
import jdk.jpackage.test.RunnablePackageTest.Action;
32+
import jdk.jpackage.test.TKit;
33+
34+
/**
35+
* Test packaging of files with paths containing multiple dollar ($$, $$$)
36+
* character sequences.
37+
*/
38+
39+
/*
40+
* @test
41+
* @summary Test case for JDK-8248254
42+
* @library ../helpers
43+
* @build jdk.jpackage.test.*
44+
* @build Win8282351Test
45+
* @requires (os.family == "windows")
46+
* @modules jdk.jpackage/jdk.jpackage.internal
47+
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
48+
* --jpt-run=Win8282351Test
49+
*/
50+
public class Win8282351Test {
51+
52+
@Test
53+
public void test() throws IOException {
54+
Path appimageOutput = TKit.createTempDirectory("appimage");
55+
56+
JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
57+
.setFakeRuntime().setArgumentValue("--dest", appimageOutput);
58+
59+
String[] filesWithDollarCharsInNames = new String[]{
60+
"Pane$$anon$$greater$1.class",
61+
"$",
62+
"$$",
63+
"$$$",
64+
"$$$$",
65+
"$$$$$",
66+
"foo$.class",
67+
"1$b$$a$$$r$$$$.class"
68+
};
69+
70+
String[] dirsWithDollarCharsInNames = new String[]{
71+
Path.of("foo", String.join("/", filesWithDollarCharsInNames)).toString()
72+
};
73+
74+
String name = appImageCmd.name() + "$-$$-$$$";
75+
76+
new PackageTest()
77+
.addRunOnceInitializer(() -> {
78+
appImageCmd.execute();
79+
for (var path : filesWithDollarCharsInNames) {
80+
createImageFile(appImageCmd, Path.of(path));
81+
}
82+
83+
for (var path : dirsWithDollarCharsInNames) {
84+
Files.createDirectories(
85+
appImageCmd.outputBundle().resolve(path));
86+
}
87+
})
88+
.addInitializer(cmd -> {
89+
cmd.setArgumentValue("--name", name);
90+
cmd.addArguments("--app-image", appImageCmd.outputBundle());
91+
cmd.removeArgumentWithValue("--input");
92+
cmd.addArgument("--win-menu");
93+
cmd.addArgument("--win-shortcut");
94+
})
95+
.addInstallVerifier(cmd -> {
96+
for (var path : filesWithDollarCharsInNames) {
97+
verifyImageFile(appImageCmd, Path.of(path));
98+
}
99+
100+
for (var path : dirsWithDollarCharsInNames) {
101+
TKit.assertDirectoryExists(
102+
appImageCmd.outputBundle().resolve(path));
103+
}
104+
}).run(Action.CREATE_AND_UNPACK);
105+
}
106+
107+
private static void createImageFile(JPackageCommand cmd, Path name) throws
108+
IOException {
109+
Files.writeString(cmd.outputBundle().resolve(name), name.toString());
110+
}
111+
112+
private static void verifyImageFile(JPackageCommand cmd, Path name) throws
113+
IOException {
114+
TKit.assertEquals(name.toString(), Files.readString(
115+
(cmd.outputBundle().resolve(name))), String.format(
116+
"Test contents of [%s] image file are euqal to [%s]", name, name));
117+
}
118+
}

0 commit comments

Comments
 (0)