Skip to content

Commit 1af9316

Browse files
authored
Merge pull request #277 from buzzcz/fix/filename-too-long
fix: filename too long in some cases
2 parents 7e95a71 + fc74f0a commit 1af9316

File tree

4 files changed

+51
-4
lines changed

4 files changed

+51
-4
lines changed

agent/src/main/java/com/appland/appmap/process/hooks/RecordingSupport.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ public static void stopRecording(TestDetails details, Boolean succeeded, String
7979
logger.debug("Recording stopped for {}",
8080
canonicalName(details.definedClass, details.isStatic, details.methodId));
8181
String filePath = Recorder.sanitizeFilename(String.join("_", details.definedClass, details.methodId));
82-
filePath += ".appmap.json";
8382

8483
Metadata metadata = recorder.getMetadata();
8584
if (succeeded != null) {

agent/src/main/java/com/appland/appmap/process/hooks/RequestRecording.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public static void stop(HttpServletRequest req) {
6767

6868
Recording recording = Recorder.getInstance().stopThread();
6969
String filename = String.format("%.3f_%s", startTime.toEpochMilli() / 1000.0, req.getRequestURI());
70-
filename = Recorder.sanitizeFilename(filename) + ".appmap.json";
70+
filename = Recorder.sanitizeFilename(filename);
7171
recording.moveTo(filename);
7272
}
7373

agent/src/main/java/com/appland/appmap/record/Recorder.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.appland.appmap.record;
22

33
import java.io.IOException;
4+
import java.nio.charset.StandardCharsets;
5+
import java.security.MessageDigest;
6+
import java.security.NoSuchAlgorithmException;
47
import java.util.ArrayList;
58
import java.util.Iterator;
69
import java.util.List;
@@ -13,6 +16,7 @@
1316
import com.appland.appmap.output.v1.CodeObject;
1417
import com.appland.appmap.output.v1.Event;
1518
import com.appland.appmap.util.Logger;
19+
import org.apache.commons.lang3.RandomStringUtils;
1620

1721
/**
1822
* Keep track of what's going on in the current thread.
@@ -51,14 +55,44 @@ public class Recorder {
5155
private static final String ERROR_SESSION_PRESENT = "an active recording session already exists";
5256
private static final String ERROR_NO_SESSION = "there is no active recording session";
5357

58+
private static final Integer FILENAME_MAX_LENGTH = 255; // Max length of a filename on most filesystems
59+
private static final Integer HASH_LENGTH = 7; // Arbitrary but Git provides it's a reasonable value
60+
private static final String APPMAP_SUFFIX = ".appmap.json";
61+
5462
private static final Recorder instance = new Recorder();
5563

5664
private final ActiveSession activeSession = new ActiveSession();
5765
private final CodeObjectTree globalCodeObjects = new CodeObjectTree();
5866
private final Map<Long, ThreadState> threadState = new ConcurrentHashMap<>();
5967

6068
public static String sanitizeFilename(String filename) {
61-
return filename.replaceAll("[^a-zA-Z0-9-_]", "_");
69+
String sanitizedFilename = filename.replaceAll("[^a-zA-Z0-9-_]", "_");
70+
71+
if (sanitizedFilename.length() > FILENAME_MAX_LENGTH - APPMAP_SUFFIX.length()) {
72+
int part = FILENAME_MAX_LENGTH - APPMAP_SUFFIX.length() - 1 - HASH_LENGTH;
73+
sanitizedFilename = sanitizedFilename.substring(0, part) + "-" + hashFilename(sanitizedFilename.substring(part));
74+
}
75+
76+
return sanitizedFilename + APPMAP_SUFFIX;
77+
}
78+
79+
private static String hashFilename(String filename) {
80+
MessageDigest digest;
81+
try {
82+
digest = MessageDigest.getInstance("SHA-256");
83+
} catch (NoSuchAlgorithmException e) {
84+
return RandomStringUtils.random(HASH_LENGTH);
85+
}
86+
byte[] hash = digest.digest(filename.getBytes(StandardCharsets.UTF_8));
87+
StringBuilder hexString = new StringBuilder(2 * hash.length);
88+
for (byte b : hash) {
89+
String hex = Integer.toHexString(0xff & b);
90+
if (hex.length() == 1) {
91+
hexString.append('0');
92+
}
93+
hexString.append(hex);
94+
}
95+
return hexString.substring(0, HASH_LENGTH);
6296
}
6397

6498
/**
@@ -363,7 +397,7 @@ public void record(String name, Runnable fn) throws ActiveSessionException, IOEx
363397
this.start(metadata);
364398
fn.run();
365399
Recording recording = this.stop();
366-
recording.moveTo(fileName + ".appmap.json");
400+
recording.moveTo(fileName);
367401
}
368402

369403
// Mockito can't stub methods on the Collection<ThreadState>

agent/src/test/java/com/appland/appmap/record/RecorderTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import java.util.concurrent.Executors;
2929
import java.util.concurrent.Future;
3030
import java.util.concurrent.Semaphore;
31+
import java.util.regex.Matcher;
32+
import java.util.regex.Pattern;
3133

3234
import org.apache.commons.lang3.StringUtils;
3335
import org.junit.jupiter.api.AfterEach;
@@ -239,4 +241,16 @@ public void testMultithreadCheckpoint() throws InterruptedException {
239241
es.shutdown();
240242
}
241243

244+
@Test
245+
public void testSanitizeFilename() {
246+
assertEquals("foo.appmap.json", Recorder.sanitizeFilename("foo"));
247+
248+
String longFilename = StringUtils.repeat("foo", 100);
249+
Pattern p = Pattern.compile("([fo]*?)-538355d.appmap.json");
250+
Matcher m = p.matcher(Recorder.sanitizeFilename(longFilename));
251+
assertTrue(m.find());
252+
assertEquals(255, m.group(0).length());
253+
assertEquals(235, m.group(1).length());
254+
}
255+
242256
}

0 commit comments

Comments
 (0)