Skip to content

Commit cfa9fb0

Browse files
Merge pull request #131 from UniVE-SSV/file-path-sanitization
Filename sanitization for `FileManager`
2 parents 576056c + 23e4179 commit cfa9fb0

File tree

2 files changed

+153
-8
lines changed

2 files changed

+153
-8
lines changed

lisa/lisa-sdk/src/main/java/it/unive/lisa/util/file/FileManager.java

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.io.Writer;
88
import java.nio.charset.StandardCharsets;
99
import java.nio.file.Paths;
10+
import java.util.Arrays;
1011
import java.util.Collection;
1112
import java.util.TreeSet;
1213
import org.apache.commons.io.FileUtils;
@@ -60,6 +61,24 @@ public void mkOutputFile(String name, WriteAction filler) throws IOException {
6061
mkOutputFile(name, false, filler);
6162
}
6263

64+
/**
65+
* Creates a UTF-8 encoded file with the given name. If name is a path, all
66+
* missing directories will be created as well. The given name will be
67+
* joined with the workdir used to initialize this file manager, thus
68+
* raising an exception if {@code name} is absolute. {@code filler} will
69+
* then be used to write to the writer.
70+
*
71+
* @param path the sub-path, relative to the workdir, where the file
72+
* should be created
73+
* @param name the name of the file to create
74+
* @param filler the callback to write to the file
75+
*
76+
* @throws IOException if something goes wrong while creating the file
77+
*/
78+
public void mkOutputFile(String path, String name, WriteAction filler) throws IOException {
79+
mkOutputFile(path, name, false, filler);
80+
}
81+
6382
/**
6483
* Creates a UTF-8 encoded file with the given name, appending the
6584
* {@code dot} extension. If name is a path, all missing directories will be
@@ -78,6 +97,26 @@ public void mkDotFile(String name, WriteAction filler) throws IOException {
7897
mkOutputFile(cleanupForDotFile(name) + ".dot", false, filler);
7998
}
8099

100+
/**
101+
* Creates a UTF-8 encoded file with the given name, appending the
102+
* {@code dot} extension. If name is a path, all missing directories will be
103+
* created as well. The name will be stripped of any characters that might
104+
* cause problems in the file name. The given name will be joined with the
105+
* workdir used to initialize this file manager, thus raising an exception
106+
* if {@code name} is absolute. {@code filler} will then be used to write to
107+
* the writer.
108+
*
109+
* @param path the sub-path, relative to the workdir, where the file
110+
* should be created
111+
* @param name the name of the file to create
112+
* @param filler the callback to write to the file
113+
*
114+
* @throws IOException if something goes wrong while creating the file
115+
*/
116+
public void mkDotFile(String path, String name, WriteAction filler) throws IOException {
117+
mkOutputFile(path, cleanupForDotFile(name) + ".dot", false, filler);
118+
}
119+
81120
/**
82121
* A functional interface for a write operation that can throw
83122
* {@link IOException}s.
@@ -112,20 +151,63 @@ public interface WriteAction {
112151
* the file
113152
*/
114153
public void mkOutputFile(String name, boolean bom, WriteAction filler) throws IOException {
115-
File file = new File(workdir, name);
154+
mkOutputFile(null, name, bom, filler);
155+
}
156+
157+
/**
158+
* Creates a UTF-8 encoded file with the given name. If name is a path, all
159+
* missing directories will be created as well. The given name will be
160+
* joined with the workdir used to initialize this file manager, thus
161+
* raising an exception if {@code name} is absolute. {@code filler} will
162+
* then be used to write to the writer.
163+
*
164+
* @param path the sub-path, relative to the workdir, where the file
165+
* should be created
166+
* @param name the name of the file to create
167+
* @param bom if {@code true}, the bom marker {@code \ufeff} will be
168+
* written to the file
169+
* @param filler the callback to write to the file
170+
*
171+
* @throws IOException if something goes wrong while creating or writing to
172+
* the file
173+
*/
174+
public void mkOutputFile(String path, String name, boolean bom, WriteAction filler) throws IOException {
175+
File parent = workdir;
176+
if (path != null)
177+
parent = new File(workdir, cleanFileName(path, true));
178+
File file = new File(parent, cleanFileName(name, false));
116179

117-
File parent = file.getAbsoluteFile().getParentFile();
118180
if (!parent.exists() && !parent.mkdirs())
119181
throw new IOException("Unable to create directory structure for " + file);
120182

121-
createdFiles.add(name);
183+
createdFiles.add(workdir.toPath().relativize(file.toPath()).toString());
122184
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8.newEncoder())) {
123185
if (bom)
124186
writer.write('\ufeff');
125187
filler.perform(writer);
126188
}
127189
}
128190

191+
private final static int[] illegalChars = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
192+
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 34, 42, 47, 58, 60, 62, 63, 92, 124 };
193+
194+
private static String cleanFileName(String name, boolean keepDirSeparator) {
195+
// https://stackoverflow.com/questions/1155107/is-there-a-cross-platform-java-method-to-remove-filename-special-chars
196+
StringBuilder cleanName = new StringBuilder();
197+
int len = name.codePointCount(0, name.length());
198+
for (int i = 0; i < len; i++) {
199+
int c = name.codePointAt(i);
200+
// 57 is / and 92 is \
201+
if ((keepDirSeparator && (c == 57 || c == 92))
202+
|| Arrays.binarySearch(illegalChars, c) < 0)
203+
cleanName.appendCodePoint(c);
204+
else
205+
cleanName.appendCodePoint('_');
206+
207+
}
208+
return cleanName.toString();
209+
}
210+
129211
private static String cleanupForDotFile(String name) {
130212
String result = name.replace(' ', '_');
131213
result = result.replace("::", ".");

lisa/lisa-sdk/src/test/java/it/unive/lisa/util/file/FileManagerTest.java

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package it.unive.lisa.util.file;
22

3+
import static org.junit.Assert.assertEquals;
34
import static org.junit.Assert.fail;
45

56
import java.io.File;
@@ -50,8 +51,9 @@ public void testDeleteEmptyFolder() {
5051
@Test
5152
public void testCreateFile() {
5253
FileManager manager = new FileManager(TESTDIR);
54+
String name = "foo.txt";
5355
try {
54-
manager.mkOutputFile("foo.txt", w -> w.write("foo"));
56+
manager.mkOutputFile(name, w -> w.write("foo"));
5557
} catch (IOException e) {
5658
e.printStackTrace();
5759
fail("The file has not been created");
@@ -61,16 +63,20 @@ public void testCreateFile() {
6163
if (!dir.exists())
6264
fail("The working directory has not been created");
6365

64-
File file = new File(dir, "foo.txt");
66+
File file = new File(dir, name);
6567
if (!file.exists())
6668
fail("The file has not been created");
69+
70+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
71+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(), name);
6772
}
6873

6974
@Test
7075
public void testCreateFileWithBom() {
7176
FileManager manager = new FileManager(TESTDIR);
77+
String name = "foo.txt";
7278
try {
73-
manager.mkOutputFile("foo.txt", true, w -> w.write("foo"));
79+
manager.mkOutputFile(name, true, w -> w.write("foo"));
7480
} catch (IOException e) {
7581
e.printStackTrace();
7682
fail("The file has not been created");
@@ -80,16 +86,19 @@ public void testCreateFileWithBom() {
8086
if (!dir.exists())
8187
fail("The working directory has not been created");
8288

83-
File file = new File(dir, "foo.txt");
89+
File file = new File(dir, name);
8490
if (!file.exists())
8591
fail("The file has not been created");
92+
93+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
94+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(), name);
8695
}
8796

8897
@Test
8998
public void testCreateFileInSubfolder() {
9099
FileManager manager = new FileManager(TESTDIR);
91100
try {
92-
manager.mkOutputFile("sub/foo.txt", w -> w.write("foo"));
101+
manager.mkOutputFile("sub", "foo.txt", w -> w.write("foo"));
93102
} catch (IOException e) {
94103
e.printStackTrace();
95104
fail("The file has not been created");
@@ -106,6 +115,10 @@ public void testCreateFileInSubfolder() {
106115
File file = new File(sub, "foo.txt");
107116
if (!file.exists())
108117
fail("The file has not been created");
118+
119+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
120+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(),
121+
"sub" + File.separatorChar + "foo.txt");
109122
}
110123

111124
@Test
@@ -125,5 +138,55 @@ public void testDotFileNameSanitization() {
125138
File file = new File(dir, "foo()__bar.jar.dot");
126139
if (!file.exists())
127140
fail("The file has not been created");
141+
142+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
143+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(),
144+
file.getName());
145+
}
146+
147+
@Test
148+
public void testFileNameWithUnixSlashes() {
149+
FileManager manager = new FileManager(TESTDIR);
150+
try {
151+
manager.mkOutputFile("foo/bar.txt", w -> w.write("foo"));
152+
} catch (IOException e) {
153+
e.printStackTrace();
154+
fail("The file has not been created");
155+
}
156+
157+
File dir = new File(TESTDIR);
158+
if (!dir.exists())
159+
fail("The working directory has not been created");
160+
161+
File file = new File(dir, "foo_bar.txt");
162+
if (!file.exists())
163+
fail("The file has not been created");
164+
165+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
166+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(),
167+
file.getName());
168+
}
169+
170+
@Test
171+
public void testFileNameWithWindowsSlashes() {
172+
FileManager manager = new FileManager(TESTDIR);
173+
try {
174+
manager.mkOutputFile("foo\\bar.txt", w -> w.write("foo"));
175+
} catch (IOException e) {
176+
e.printStackTrace();
177+
fail("The file has not been created");
178+
}
179+
180+
File dir = new File(TESTDIR);
181+
if (!dir.exists())
182+
fail("The working directory has not been created");
183+
184+
File file = new File(dir, "foo_bar.txt");
185+
if (!file.exists())
186+
fail("The file has not been created");
187+
188+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
189+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(),
190+
file.getName());
128191
}
129192
}

0 commit comments

Comments
 (0)