Skip to content

Commit 012802b

Browse files
committed
#130 adding filename sanitization to FileManager
1 parent 576056c commit 012802b

File tree

2 files changed

+154
-8
lines changed

2 files changed

+154
-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: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
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;
67
import java.io.IOException;
8+
79
import org.junit.After;
810
import org.junit.Before;
911
import org.junit.Test;
@@ -50,8 +52,9 @@ public void testDeleteEmptyFolder() {
5052
@Test
5153
public void testCreateFile() {
5254
FileManager manager = new FileManager(TESTDIR);
55+
String name = "foo.txt";
5356
try {
54-
manager.mkOutputFile("foo.txt", w -> w.write("foo"));
57+
manager.mkOutputFile(name, w -> w.write("foo"));
5558
} catch (IOException e) {
5659
e.printStackTrace();
5760
fail("The file has not been created");
@@ -61,16 +64,20 @@ public void testCreateFile() {
6164
if (!dir.exists())
6265
fail("The working directory has not been created");
6366

64-
File file = new File(dir, "foo.txt");
67+
File file = new File(dir, name);
6568
if (!file.exists())
6669
fail("The file has not been created");
70+
71+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
72+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(), name);
6773
}
6874

6975
@Test
7076
public void testCreateFileWithBom() {
7177
FileManager manager = new FileManager(TESTDIR);
78+
String name = "foo.txt";
7279
try {
73-
manager.mkOutputFile("foo.txt", true, w -> w.write("foo"));
80+
manager.mkOutputFile(name, true, w -> w.write("foo"));
7481
} catch (IOException e) {
7582
e.printStackTrace();
7683
fail("The file has not been created");
@@ -80,16 +87,19 @@ public void testCreateFileWithBom() {
8087
if (!dir.exists())
8188
fail("The working directory has not been created");
8289

83-
File file = new File(dir, "foo.txt");
90+
File file = new File(dir, name);
8491
if (!file.exists())
8592
fail("The file has not been created");
93+
94+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
95+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(), name);
8696
}
8797

8898
@Test
8999
public void testCreateFileInSubfolder() {
90100
FileManager manager = new FileManager(TESTDIR);
91101
try {
92-
manager.mkOutputFile("sub/foo.txt", w -> w.write("foo"));
102+
manager.mkOutputFile("sub", "foo.txt", w -> w.write("foo"));
93103
} catch (IOException e) {
94104
e.printStackTrace();
95105
fail("The file has not been created");
@@ -106,6 +116,10 @@ public void testCreateFileInSubfolder() {
106116
File file = new File(sub, "foo.txt");
107117
if (!file.exists())
108118
fail("The file has not been created");
119+
120+
assertEquals("FileManager did not track the correct number of files", manager.createdFiles().size(), 1);
121+
assertEquals("FileManager did not track the created file", manager.createdFiles().iterator().next(),
122+
"sub" + File.separatorChar + "foo.txt");
109123
}
110124

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

0 commit comments

Comments
 (0)