Skip to content

Commit b8e86cc

Browse files
committed
refs #51 - percent encode files that contain new lines or carriage returns
1 parent 71bd3c7 commit b8e86cc

7 files changed

Lines changed: 98 additions & 24 deletions

File tree

src/main/java/gov/loc/repository/bagit/domain/Bag.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package gov.loc.repository.bagit.domain;
22

3+
import java.nio.charset.Charset;
34
import java.nio.charset.StandardCharsets;
45
import java.nio.file.Path;
56
import java.util.ArrayList;
@@ -18,7 +19,7 @@ public class Bag {
1819
private Version version = new Version(-1, -1);
1920

2021
//from the bagit.txt or UTF-8 for new bags
21-
private String fileEncoding = StandardCharsets.UTF_8.name();
22+
private Charset fileEncoding = StandardCharsets.UTF_8;
2223

2324
//equivalent to the manifest-<ALG>.txt files
2425
private Set<Manifest> payLoadManifests = new HashSet<>();
@@ -88,11 +89,11 @@ public void setMetadata(List<Pair<String, String>> metadata) {
8889
this.metadata = metadata;
8990
}
9091

91-
public String getFileEncoding() {
92+
public Charset getFileEncoding() {
9293
return fileEncoding;
9394
}
9495

95-
public void setFileEncoding(String fileEncoding) {
96+
public void setFileEncoding(Charset fileEncoding) {
9697
this.fileEncoding = fileEncoding;
9798
}
9899

src/main/java/gov/loc/repository/bagit/reader/BagReader.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.io.BufferedReader;
44
import java.io.IOException;
55
import java.net.URL;
6+
import java.nio.charset.Charset;
7+
import java.nio.charset.StandardCharsets;
68
import java.nio.file.DirectoryStream;
79
import java.nio.file.Files;
810
import java.nio.file.Path;
@@ -94,14 +96,14 @@ public Bag readBagitTextFile(Path bagitFile, Bag bag) throws IOException, Unpars
9496
List<Pair<String, String>> pairs = readKeyValuesFromFile(bagitFile, ":");
9597

9698
String version = "";
97-
String encoding = "";
99+
Charset encoding = StandardCharsets.UTF_8;
98100
for(Pair<String, String> pair : pairs){
99101
if("BagIt-Version".equals(pair.getKey())){
100102
version = pair.getValue();
101103
logger.debug("BagIt-Version is [{}]", version);
102104
}
103105
if("Tag-File-Character-Encoding".equals(pair.getKey())){
104-
encoding = pair.getValue();
106+
encoding = Charset.forName(pair.getValue());
105107
logger.debug("Tag-File-Character-Encoding is [{}]", encoding);
106108
}
107109
}
@@ -199,7 +201,7 @@ protected HashMap<Path, String> readChecksumFileMap(Path manifestFile, Path bagR
199201
String line = br.readLine();
200202
while(line != null){
201203
String[] parts = line.split("\\s+", 2);
202-
Path file = bagRootDir.resolve(parts[1]);
204+
Path file = bagRootDir.resolve(PathUtils.decodeFilname(parts[1]));
203205
if(!file.normalize().startsWith(bagRootDir)){
204206
throw new MaliciousManifestException("Path " + file + " is outside the bag root directory of " + bagRootDir +
205207
"! This is not allowed according to the bagit specification!");

src/main/java/gov/loc/repository/bagit/util/PathUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,22 @@ public static String getFilename(Path path){
2121

2222
return filename;
2323
}
24+
25+
/**
26+
* as per https://github.com/jkunze/bagitspec/commit/152d42f6298b31a4916ea3f8f644ca4490494070 decode percent encoded filenames
27+
* @param encoded the encoded filename
28+
* @return the decoded filename
29+
*/
30+
public static String decodeFilname(String encoded){
31+
return encoded.replaceAll("%0A", "\n").replaceAll("%0D", "\r");
32+
}
33+
34+
/**
35+
* as per https://github.com/jkunze/bagitspec/commit/152d42f6298b31a4916ea3f8f644ca4490494070 encode any new lines or carriage returns
36+
* @param path the path to encode
37+
* @return the encoded filename
38+
*/
39+
public static String encodeFilename(Path path){
40+
return path.toString().replaceAll("\n", "%0A").replaceAll("\r", "%0D");
41+
}
2442
}

src/main/java/gov/loc/repository/bagit/writer/BagWriter.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import gov.loc.repository.bagit.domain.Manifest;
2323
import gov.loc.repository.bagit.domain.Version;
2424
import gov.loc.repository.bagit.hash.Hasher;
25+
import gov.loc.repository.bagit.util.PathUtils;
2526
import javafx.util.Pair;
2627

2728
/**
@@ -92,7 +93,7 @@ protected static Path writeVersionDependentPayloadFiles(Bag bag, Path outputDir)
9293
*
9394
* @throws IOException if there was a problem writing the file
9495
*/
95-
public static void writeBagitFile(Version version, String encoding, Path outputDir) throws IOException{
96+
public static void writeBagitFile(Version version, Charset encoding, Path outputDir) throws IOException{
9697
Path bagitPath = outputDir.resolve("bagit.txt");
9798
logger.debug("Writing bagit.txt file to [{}]", outputDir);
9899

@@ -143,7 +144,7 @@ public static void writePayloadFiles(Set<Manifest> payloadManifests, Path output
143144
*
144145
* @throws IOException if there was a problem writing a file
145146
*/
146-
public static void writePayloadManifests(Set<Manifest> manifests, Path outputDir, Path bagitRootDir, String charsetName) throws IOException{
147+
public static void writePayloadManifests(Set<Manifest> manifests, Path outputDir, Path bagitRootDir, Charset charsetName) throws IOException{
147148
logger.info("Writing payload manifest(s)");
148149
writeManifests(manifests, outputDir, bagitRootDir, "manifest-", charsetName);
149150
}
@@ -178,12 +179,12 @@ protected static Set<Manifest> updateTagManifests(Bag bag, Path newBagRootDir) t
178179
*
179180
* @throws IOException if there was a problem writing a file
180181
*/
181-
public static void writeTagManifests(Set<Manifest> tagManifests, Path outputDir, Path bagitRootDir, String charsetName) throws IOException{
182+
public static void writeTagManifests(Set<Manifest> tagManifests, Path outputDir, Path bagitRootDir, Charset charsetName) throws IOException{
182183
logger.info("Writing tag manifest(s)");
183184
writeManifests(tagManifests, outputDir, bagitRootDir, "tagmanifest-", charsetName);
184185
}
185186

186-
protected static void writeManifests(Set<Manifest> manifests, Path outputDir, Path relativeTo, String filenameBase, String charsetName) throws IOException{
187+
protected static void writeManifests(Set<Manifest> manifests, Path outputDir, Path relativeTo, String filenameBase, Charset charsetName) throws IOException{
187188
for(Manifest manifest : manifests){
188189
Path manifestPath = outputDir.resolve(filenameBase + manifest.getAlgorithm().getBagitName() + ".txt");
189190
logger.debug("Writing manifest to [{}]", manifestPath);
@@ -192,9 +193,10 @@ protected static void writeManifests(Set<Manifest> manifests, Path outputDir, Pa
192193
Files.createFile(manifestPath);
193194

194195
for(Entry<Path, String> entry : manifest.getFileToChecksumMap().entrySet()){
195-
String line = entry.getValue() + " " + relativeTo.relativize(entry.getKey()) + System.lineSeparator();
196+
String line = entry.getValue() + " " +
197+
PathUtils.encodeFilename(relativeTo.relativize(entry.getKey())) + System.lineSeparator();
196198
logger.debug("Writing [{}] to [{}]", line, manifestPath);
197-
Files.write(manifestPath, line.getBytes(Charset.forName(charsetName)),
199+
Files.write(manifestPath, line.getBytes(charsetName),
198200
StandardOpenOption.APPEND, StandardOpenOption.CREATE);
199201
}
200202
}
@@ -224,7 +226,7 @@ protected static void writeAdditionalTagPayloadFiles(Set<Manifest> manifests, Pa
224226
*
225227
* @throws IOException if there was a problem writing a file
226228
*/
227-
public static void writeBagitInfoFile(List<Pair<String, String>> metadata, Version version, Path outputDir, String charsetName) throws IOException{
229+
public static void writeBagitInfoFile(List<Pair<String, String>> metadata, Version version, Path outputDir, Charset charsetName) throws IOException{
228230
Path bagInfoFilePath = outputDir.resolve("bag-info.txt");
229231
if(VERSION_0_95.compareTo(version) >= 0){
230232
bagInfoFilePath = outputDir.resolve("package-info.txt");
@@ -236,7 +238,7 @@ public static void writeBagitInfoFile(List<Pair<String, String>> metadata, Versi
236238
for(Pair<String, String> entry : metadata){
237239
String line = entry.getKey() + " : " + entry.getValue() + System.lineSeparator();
238240
logger.debug("Writing [{}] to [{}]", line, bagInfoFilePath);
239-
Files.write(bagInfoFilePath, line.getBytes(Charset.forName(charsetName)),
241+
Files.write(bagInfoFilePath, line.getBytes(charsetName),
240242
StandardOpenOption.APPEND, StandardOpenOption.CREATE);
241243
}
242244
}
@@ -250,15 +252,14 @@ public static void writeBagitInfoFile(List<Pair<String, String>> metadata, Versi
250252
*
251253
* @throws IOException if there was a problem writing a file
252254
*/
253-
public static void writeFetchFile(List<FetchItem> itemsToFetch, Path outputDir, String charsetName) throws IOException{
255+
public static void writeFetchFile(List<FetchItem> itemsToFetch, Path outputDir, Charset charsetName) throws IOException{
254256
logger.debug("Writing fetch.txt to [{}]", outputDir);
255257
Path fetchFilePath = outputDir.resolve("fetch.txt");
256258

257259
for(FetchItem item : itemsToFetch){
258260
String line = item.toString();
259261
logger.debug("Writing [{}] to [{}]", line, fetchFilePath);
260-
Files.write(fetchFilePath, line.getBytes(Charset.forName(charsetName)),
261-
StandardOpenOption.APPEND, StandardOpenOption.CREATE);
262+
Files.write(fetchFilePath, line.getBytes(charsetName), StandardOpenOption.APPEND, StandardOpenOption.CREATE);
262263
}
263264
}
264265
}

src/test/java/gov/loc/repository/bagit/reader/BagReaderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public void testReadBagitFile()throws Exception{
207207
Path bagitFile = Paths.get(getClass().getClassLoader().getResource("bagitFiles/bagit-0.97.txt").toURI());
208208
Bag returnedBag = sut.readBagitTextFile(bagitFile, new Bag(new Version(0, 96)));
209209
assertEquals(new Version(0, 97), returnedBag.getVersion());
210-
assertEquals(StandardCharsets.UTF_8.name(), returnedBag.getFileEncoding());
210+
assertEquals(StandardCharsets.UTF_8, returnedBag.getFileEncoding());
211211
}
212212

213213
@Test
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package gov.loc.repository.bagit.util;
2+
3+
import java.nio.file.Path;
4+
import java.nio.file.Paths;
5+
6+
import org.junit.Assert;
7+
import org.junit.Test;
8+
9+
public class PathUtilsTest extends Assert {
10+
11+
@Test
12+
public void testDecode(){
13+
//just carriage return
14+
String testString = "/foo/bar/ham%0D";
15+
String expectedDecoded = "/foo/bar/ham\r";
16+
String actualDecoded = PathUtils.decodeFilname(testString);
17+
assertEquals(expectedDecoded, actualDecoded);
18+
19+
//just new line
20+
testString = "/foo/bar/ham%0A";
21+
expectedDecoded = "/foo/bar/ham\n";
22+
actualDecoded = PathUtils.decodeFilname(testString);
23+
assertEquals(expectedDecoded, actualDecoded);
24+
25+
//both carriage return and new line
26+
testString = "/foo/bar/ham%0D%0A";
27+
expectedDecoded = "/foo/bar/ham\r\n";
28+
actualDecoded = PathUtils.decodeFilname(testString);
29+
assertEquals(expectedDecoded, actualDecoded);
30+
}
31+
32+
@Test
33+
public void testEncode(){
34+
//just carriage return
35+
Path testPath = Paths.get("/foo/bar/ham\r");
36+
String expectedEncoded = "/foo/bar/ham%0D";
37+
String actualEncoded = PathUtils.encodeFilename(testPath);
38+
assertEquals(expectedEncoded, actualEncoded);
39+
40+
//just new line
41+
testPath = Paths.get("/foo/bar/ham\n");
42+
expectedEncoded = "/foo/bar/ham%0A";
43+
actualEncoded = PathUtils.encodeFilename(testPath);
44+
assertEquals(expectedEncoded, actualEncoded);
45+
46+
//both carriage return and new line
47+
testPath = Paths.get("/foo/bar/ham\r\n");
48+
expectedEncoded = "/foo/bar/ham%0D%0A";
49+
actualEncoded = PathUtils.encodeFilename(testPath);
50+
assertEquals(expectedEncoded, actualEncoded);
51+
}
52+
}

src/test/java/gov/loc/repository/bagit/writer/BagWriterTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,13 @@ public void testWriteBagitFile() throws Exception{
148148
Path bagit = rootDirPath.resolve("bagit.txt");
149149

150150
assertFalse(Files.exists(bagit));
151-
BagWriter.writeBagitFile(new Version(0, 97), StandardCharsets.UTF_8.name(), rootDirPath);
151+
BagWriter.writeBagitFile(new Version(0, 97), StandardCharsets.UTF_8, rootDirPath);
152152
assertTrue(Files.exists(bagit));
153153

154154
//test truncating existing
155155
long originalModified = Files.getLastModifiedTime(bagit).toMillis();
156156
long size = Files.size(bagit);
157-
BagWriter.writeBagitFile(new Version(0, 97), StandardCharsets.UTF_8.name(), rootDirPath);
157+
BagWriter.writeBagitFile(new Version(0, 97), StandardCharsets.UTF_8, rootDirPath);
158158
assertTrue(Files.exists(bagit));
159159
assertTrue(Files.getLastModifiedTime(bagit) + " should be >= " + originalModified,
160160
Files.getLastModifiedTime(bagit).toMillis() >= originalModified);
@@ -174,10 +174,10 @@ public void testWriteBagitInfoFile() throws IOException{
174174
assertFalse(bagInfo.exists());
175175
assertFalse(packageInfo.exists());
176176

177-
BagWriter.writeBagitInfoFile(metadata, new Version(0,96), Paths.get(rootDir.toURI()), StandardCharsets.UTF_8.name());
177+
BagWriter.writeBagitInfoFile(metadata, new Version(0,96), Paths.get(rootDir.toURI()), StandardCharsets.UTF_8);
178178
assertTrue(bagInfo.exists());
179179

180-
BagWriter.writeBagitInfoFile(metadata, new Version(0,95), Paths.get(rootDir.toURI()), StandardCharsets.UTF_8.name());
180+
BagWriter.writeBagitInfoFile(metadata, new Version(0,95), Paths.get(rootDir.toURI()), StandardCharsets.UTF_8);
181181
assertTrue(packageInfo.exists());
182182
}
183183

@@ -191,7 +191,7 @@ public void testWriteFetchFile() throws Exception{
191191

192192

193193
assertFalse(fetch.exists());
194-
BagWriter.writeFetchFile(itemsToFetch, Paths.get(rootDir.toURI()), StandardCharsets.UTF_8.name());
194+
BagWriter.writeFetchFile(itemsToFetch, Paths.get(rootDir.toURI()), StandardCharsets.UTF_8);
195195
assertTrue(fetch.exists());
196196
}
197197

@@ -205,7 +205,7 @@ public void testWriteTagManifests() throws IOException{
205205
File tagManifest = new File(outputDir, "tagmanifest-md5.txt");
206206

207207
assertFalse(tagManifest.exists());
208-
BagWriter.writeTagManifests(tagManifests, Paths.get(outputDir.toURI()), Paths.get("/foo/bar/ham"), StandardCharsets.UTF_8.name());
208+
BagWriter.writeTagManifests(tagManifests, Paths.get(outputDir.toURI()), Paths.get("/foo/bar/ham"), StandardCharsets.UTF_8);
209209
assertTrue(tagManifest.exists());
210210
}
211211

0 commit comments

Comments
 (0)