Skip to content

Commit ccdb0ef

Browse files
committed
8189953: FileHandler constructor throws NoSuchFileException with absolute path
Reviewed-by: mchung
1 parent 6c99853 commit ccdb0ef

2 files changed

Lines changed: 309 additions & 60 deletions

File tree

src/java.logging/share/classes/java/util/logging/FileHandler.java

Lines changed: 77 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2017, 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
@@ -616,79 +616,96 @@ && isParentWritable(lockFilePath)) {
616616
* @throws IOException
617617
*/
618618
private File generate(String pattern, int generation, int unique)
619-
throws IOException {
620-
File file = null;
621-
String word = "";
622-
int ix = 0;
619+
throws IOException
620+
{
621+
return generate(pattern, count, generation, unique);
622+
}
623+
624+
// The static method here is provided for whitebox testing of the algorithm.
625+
static File generate(String pat, int count, int generation, int unique)
626+
throws IOException
627+
{
628+
Path path = Paths.get(pat);
629+
Path result = null;
623630
boolean sawg = false;
624631
boolean sawu = false;
625-
while (ix < pattern.length()) {
626-
char ch = pattern.charAt(ix);
627-
ix++;
628-
char ch2 = 0;
629-
if (ix < pattern.length()) {
630-
ch2 = Character.toLowerCase(pattern.charAt(ix));
632+
StringBuilder word = new StringBuilder();
633+
Path prev = null;
634+
for (Path elem : path) {
635+
if (prev != null) {
636+
prev = prev.resolveSibling(word.toString());
637+
result = result == null ? prev : result.resolve(prev);
631638
}
632-
if (ch == '/') {
633-
if (file == null) {
634-
file = new File(word);
635-
} else {
636-
file = new File(file, word);
639+
String pattern = elem.toString();
640+
int ix = 0;
641+
word.setLength(0);
642+
while (ix < pattern.length()) {
643+
char ch = pattern.charAt(ix);
644+
ix++;
645+
char ch2 = 0;
646+
if (ix < pattern.length()) {
647+
ch2 = Character.toLowerCase(pattern.charAt(ix));
637648
}
638-
word = "";
639-
continue;
640-
} else if (ch == '%') {
641-
if (ch2 == 't') {
642-
String tmpDir = System.getProperty("java.io.tmpdir");
643-
if (tmpDir == null) {
644-
tmpDir = System.getProperty("user.home");
645-
}
646-
file = new File(tmpDir);
647-
ix++;
648-
word = "";
649-
continue;
650-
} else if (ch2 == 'h') {
651-
file = new File(System.getProperty("user.home"));
652-
if (jdk.internal.misc.VM.isSetUID()) {
653-
// Ok, we are in a set UID program. For safety's sake
654-
// we disallow attempts to open files relative to %h.
655-
throw new IOException("can't use %h in set UID program");
649+
if (ch == '%') {
650+
if (ch2 == 't') {
651+
String tmpDir = System.getProperty("java.io.tmpdir");
652+
if (tmpDir == null) {
653+
tmpDir = System.getProperty("user.home");
654+
}
655+
result = Paths.get(tmpDir);
656+
ix++;
657+
word.setLength(0);
658+
continue;
659+
} else if (ch2 == 'h') {
660+
result = Paths.get(System.getProperty("user.home"));
661+
if (jdk.internal.misc.VM.isSetUID()) {
662+
// Ok, we are in a set UID program. For safety's sake
663+
// we disallow attempts to open files relative to %h.
664+
throw new IOException("can't use %h in set UID program");
665+
}
666+
ix++;
667+
word.setLength(0);
668+
continue;
669+
} else if (ch2 == 'g') {
670+
word = word.append(generation);
671+
sawg = true;
672+
ix++;
673+
continue;
674+
} else if (ch2 == 'u') {
675+
word = word.append(unique);
676+
sawu = true;
677+
ix++;
678+
continue;
679+
} else if (ch2 == '%') {
680+
word = word.append('%');
681+
ix++;
682+
continue;
656683
}
657-
ix++;
658-
word = "";
659-
continue;
660-
} else if (ch2 == 'g') {
661-
word = word + generation;
662-
sawg = true;
663-
ix++;
664-
continue;
665-
} else if (ch2 == 'u') {
666-
word = word + unique;
667-
sawu = true;
668-
ix++;
669-
continue;
670-
} else if (ch2 == '%') {
671-
word = word + "%";
672-
ix++;
673-
continue;
674684
}
685+
word = word.append(ch);
675686
}
676-
word = word + ch;
687+
prev = elem;
677688
}
689+
678690
if (count > 1 && !sawg) {
679-
word = word + "." + generation;
691+
word = word.append('.').append(generation);
680692
}
681693
if (unique > 0 && !sawu) {
682-
word = word + "." + unique;
694+
word = word.append('.').append(unique);
683695
}
684696
if (word.length() > 0) {
685-
if (file == null) {
686-
file = new File(word);
687-
} else {
688-
file = new File(file, word);
689-
}
697+
String n = word.toString();
698+
Path p = prev == null ? Paths.get(n) : prev.resolveSibling(n);
699+
result = result == null ? p : result.resolve(p);
700+
} else if (result == null) {
701+
result = Paths.get("");
702+
}
703+
704+
if (path.getRoot() == null) {
705+
return result.toFile();
706+
} else {
707+
return path.getRoot().resolve(result).toFile();
690708
}
691-
return file;
692709
}
693710

694711
/**
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*
2+
* Copyright (c) 2017, 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.ByteArrayInputStream;
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.File;
27+
import java.io.IOException;
28+
import java.lang.reflect.InvocationTargetException;
29+
import java.lang.reflect.Method;
30+
import java.nio.file.Files;
31+
import java.nio.file.Paths;
32+
import java.security.*;
33+
import java.util.*;
34+
import java.util.concurrent.atomic.AtomicBoolean;
35+
import java.util.logging.FileHandler;
36+
import java.util.logging.LogManager;
37+
38+
/**
39+
* @test
40+
* @bug 8189953
41+
* @summary tests the pattern generation algorithm
42+
* @modules java.logging/java.util.logging:open
43+
* @run main/othervm FileHandlerPatternGeneration
44+
* @author danielfuchs
45+
*/
46+
public class FileHandlerPatternGeneration {
47+
48+
/**
49+
* An array of strings where the elements at even indices are the input
50+
* to give to FileHandler::generate(pattern, count, generation, unique),
51+
* and the elements at the next odd index are a partially computed expected
52+
* output, where %t, %h, %u, %g and file separator still need to be replaced.
53+
* The final expected output is obtained by passing the partially computed
54+
* output to FileHandlerPatternGeneration::generateExpected
55+
* <p>
56+
* The test verifies that {@code
57+
* FileHandler.generate(PATTERN[i], c, g, u).toString()
58+
* }
59+
* is equal to {@code
60+
* FileHandlerPatternGeneration.generateExpected(PATTERN[i],
61+
* PATTERN[i+1],
62+
* c, g, u)
63+
* }
64+
*/
65+
static final String[] PATTERNS = {
66+
"C:/Workspace/hoge.log", "C:/Workspace/hoge.log",
67+
"C:/Workspace%g/hoge.log", "C:/Workspace%g/hoge.log",
68+
"C:/%uWorkspace/hoge.log", "C:/%uWorkspace/hoge.log",
69+
"C:/%uWorkspace%g/hoge.log", "C:/%uWorkspace%g/hoge.log",
70+
"C:/Workspace/%ghoge.log", "C:/Workspace/%ghoge.log",
71+
"C:/Workspace/%ghoge%u.log", "C:/Workspace/%ghoge%u.log",
72+
"C:/Workspace-%g/hoge.log", "C:/Workspace-%g/hoge.log",
73+
"C:/Work%hspace/hoge.log", "%h/space/hoge.log",
74+
"C:/Works%tpace%g/hoge.log", "%t/pace%g/hoge.log",
75+
"C:/%uWork%hspace/hoge.log", "%h/space/hoge.log",
76+
"C:/%uWorkspace%g/%thoge.log", "%t/hoge.log",
77+
"C:/Workspace/%g%h%%hoge.log", "%h/%%hoge.log",
78+
"C:/Work%h%%hspace/hoge.log", "%h/%%hspace/hoge.log",
79+
"C:/Works%t%%hpace%g/hoge.log", "%t/%%hpace%g/hoge.log",
80+
"C:/%uWork%h%%tspace/hoge.log", "%h/%%tspace/hoge.log",
81+
"C:/%uWorkspace%g/%t%%hoge.log", "%t/%%hoge.log",
82+
"C:/Workspace/%g%h%%hoge.log", "%h/%%hoge.log",
83+
"ahaha", "ahaha",
84+
"ahaha/ahabe", "ahaha/ahabe",
85+
"../ahaha/ahabe", "../ahaha/ahabe",
86+
"/x%ty/w/hoge.log", "%t/y/w/hoge.log",
87+
"/x/%ty/w/hoge.log", "%t/y/w/hoge.log",
88+
"/x%t/y/w/hoge.log", "%t/y/w/hoge.log",
89+
"/x/%t/y/w/hoge.log", "%t/y/w/hoge.log",
90+
"%ty/w/hoge.log", "%t/y/w/hoge.log",
91+
"%t/y/w/hoge.log", "%t/y/w/hoge.log",
92+
"/x%hy/w/hoge.log", "%h/y/w/hoge.log",
93+
"/x/%hy/w/hoge.log", "%h/y/w/hoge.log",
94+
"/x%h/y/w/hoge.log", "%h/y/w/hoge.log",
95+
"/x/%h/y/w/hoge.log", "%h/y/w/hoge.log",
96+
"%hy/w/hoge.log", "%h/y/w/hoge.log",
97+
"%h/y/w/hoge.log", "%h/y/w/hoge.log",
98+
"ahaha-%u-%g", "ahaha-%u-%g",
99+
"ahaha-%g/ahabe-%u", "ahaha-%g/ahabe-%u",
100+
"../ahaha-%u/ahabe", "../ahaha-%u/ahabe",
101+
"/x%ty/w/hoge-%g.log", "%t/y/w/hoge-%g.log",
102+
"/x/%ty/w/hoge-%u.log", "%t/y/w/hoge-%u.log",
103+
"%u-%g/x%t/y/w/hoge.log", "%t/y/w/hoge.log",
104+
"/x/%g%t%u/y/w/hoge.log", "%t/%u/y/w/hoge.log",
105+
"%ty/w-%g/hoge.log", "%t/y/w-%g/hoge.log",
106+
"%t/y/w-%u/hoge.log", "%t/y/w-%u/hoge.log",
107+
"/x%hy/%u-%g-w/hoge.log", "%h/y/%u-%g-w/hoge.log",
108+
"/x/%hy/w-%u-%g/hoge.log", "%h/y/w-%u-%g/hoge.log",
109+
"/x%h/y/w/%u-%ghoge.log", "%h/y/w/%u-%ghoge.log",
110+
"/x/%h/y/w/hoge-%u-%g.log", "%h/y/w/hoge-%u-%g.log",
111+
"%hy/w/%u-%g-hoge.log", "%h/y/w/%u-%g-hoge.log",
112+
"%h/y/w/hoge-%u-%g.log", "%h/y/w/hoge-%u-%g.log",
113+
"/x/y/z/hoge-%u.log", "/x/y/z/hoge-%u.log",
114+
};
115+
116+
// the (count, generation, unique) parameters to pass to
117+
// FileHandler.generate(pattern, count, generation, unique)
118+
static final int[][] GENERATIONS = {
119+
{0, 0, 0},
120+
{0, 1, 0},
121+
{0, 1, 1},
122+
{1, 1, 0},
123+
{1, 1, 1},
124+
{1, 1, 2},
125+
{1, 2, 3},
126+
{3, 4, 0},
127+
{3, 4, 1},
128+
{3, 4, 2},
129+
{3, 0, 5},
130+
{3, 1, 5},
131+
{3, 2, 5},
132+
};
133+
134+
static final Class<FileHandler> FILE_HANDLER_CLASS = FileHandler.class;
135+
static final Method GENERATE;
136+
static final String USER_HOME;
137+
static final String TMP;
138+
static {
139+
Method generate;
140+
try {
141+
generate = FILE_HANDLER_CLASS.getDeclaredMethod("generate",
142+
String.class,
143+
int.class,
144+
int.class,
145+
int.class);
146+
generate.setAccessible(true);
147+
} catch (Exception e) {
148+
throw new ExceptionInInitializerError(e);
149+
}
150+
GENERATE = generate;
151+
USER_HOME = System.getProperty("user.home");
152+
TMP = System.getProperty("java.io.tmpdir", USER_HOME);
153+
}
154+
155+
public static void main(String... args) throws Throwable {
156+
157+
for (int i=0; i < PATTERNS.length; i+=2) {
158+
String s = PATTERNS[i];
159+
String partial = PATTERNS[i+1];
160+
System.out.println("generate: " + s);
161+
for (int[] gen : GENERATIONS) {
162+
String expected = generateExpected(s, partial, gen[0], gen[1], gen[2]);
163+
String output = generate(s, gen[0], gen[1], gen[2]).toString();
164+
System.out.println("\t" + Arrays.toString(gen)+ ": " + output);
165+
if (!expected.equals(output)) {
166+
throw new RuntimeException("test failed for \""
167+
+ s +"\" " + Arrays.toString(gen) + ": "
168+
+ "\n\tgenerated: \"" + output +"\""
169+
+ "\n\t expected: \"" + expected +"\"");
170+
}
171+
}
172+
}
173+
174+
}
175+
176+
// Strip the trailing separator from the string, if present
177+
static String stripTrailingSeparator(String s) {
178+
if (s.endsWith("/")) {
179+
return s.substring(0, s.length() -1);
180+
} else if (s.endsWith(File.separator)) {
181+
return s.substring(0, s.length() - File.separator.length());
182+
} else {
183+
return s;
184+
}
185+
}
186+
187+
/**
188+
* Compute the final expected output from a partially computed output found
189+
* at PATTERNS[i+1]
190+
* @param s The pattern string, found at PATTERN[i]
191+
* (with i % 2 == 0)
192+
* @param partial The partially computed output, found at PATTERN[i+1]
193+
* @param count The count parameter given to FileHandler::generate
194+
* @param generation The generation parameter given to FileHandler::generate
195+
* @param unique The unique parameter given to FileHandler::generate
196+
* @return The expected output that FileHandler.generate(s, count, gen, unique)
197+
* should produce.
198+
*/
199+
static String generateExpected(String s, String partial,
200+
int count, int generation, int unique)
201+
{
202+
boolean sawu = s.replace("%%", "$$$$").contains("%u");
203+
boolean sawg = s.replace("%%", "$$$$").contains("%g");
204+
String result = partial.replace("%%", "$$$$");
205+
String tmp = stripTrailingSeparator(TMP);
206+
String home = stripTrailingSeparator(USER_HOME);
207+
result = result.replace("%h", home);
208+
result = result.replace("%t", tmp);
209+
result = result.replace("%g", String.valueOf(generation));
210+
result = result.replace("%u", String.valueOf(unique));
211+
result = result.replace("$$$$", "%");
212+
result = result.replace("/", File.separator);
213+
if (count > 1 && !sawg) {
214+
result = result + "." + generation;
215+
}
216+
if (unique > 0 && !sawu) {
217+
result = result + "." + unique;
218+
}
219+
return result;
220+
}
221+
222+
// Calls FileHandler.generate(s, count, generation, unique) through reflection
223+
static File generate(String s, int count, int generation, int unique)
224+
throws Throwable
225+
{
226+
try {
227+
return (File) GENERATE.invoke(null, s, count, generation, unique);
228+
} catch (InvocationTargetException e) {
229+
throw e.getCause();
230+
}
231+
}
232+
}

0 commit comments

Comments
 (0)