Skip to content

Commit d3bb209

Browse files
authored
Prevent stdio trimming from creating invalid unicode data (#701)
1 parent 970dcf2 commit d3bb209

File tree

2 files changed

+37
-3
lines changed

2 files changed

+37
-3
lines changed

src/main/java/hudson/tasks/junit/CaseResult.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,23 @@ static void parseProperty(Map<String, String> r, final XMLStreamReader reader, S
375375
}
376376
}
377377

378+
/**
379+
* Cleans up a truncated string, that might contain unpaired surrogates
380+
* @param str input string
381+
* @return input string, possibly truncated to avoid unpaired surrogates
382+
*/
383+
static CharSequence cleanupTruncated(CharSequence str) {
384+
// remove unpaired trailing surrogates at the start
385+
if (str.length() > 0 && Character.isLowSurrogate(str.charAt(0))) {
386+
str = str.subSequence(1, str.length());
387+
}
388+
// remove unpaired leading surrogates at the end
389+
if (str.length() > 0 && Character.isHighSurrogate(str.charAt(str.length() - 1))) {
390+
str = str.subSequence(0, str.length() - 1);
391+
}
392+
return str;
393+
}
394+
378395
static String possiblyTrimStdio(
379396
Collection<CaseResult> results, StdioRetention stdioRetention, String stdio) { // HUDSON-6516
380397
if (stdio == null) {
@@ -391,8 +408,8 @@ static String possiblyTrimStdio(
391408
if (middle <= 0) {
392409
return stdio;
393410
}
394-
return stdio.subSequence(0, halfMaxSize) + "\n...[truncated " + middle + " chars]...\n"
395-
+ stdio.subSequence(len - halfMaxSize, len);
411+
return cleanupTruncated(stdio.subSequence(0, halfMaxSize)) + "\n...[truncated " + middle + " chars]...\n"
412+
+ cleanupTruncated(stdio.subSequence(len - halfMaxSize, len));
396413
}
397414

398415
static String fixNULs(String stdio) { // JENKINS-71139
@@ -432,7 +449,7 @@ static String possiblyTrimStdio(Collection<CaseResult> results, StdioRetention s
432449
return FileUtils.readFileToString(stdio);
433450
}
434451

435-
return head + "\n...[truncated " + middle + " bytes]...\n" + tail;
452+
return cleanupTruncated(head) + "\n...[truncated " + middle + " bytes]...\n" + cleanupTruncated(tail);
436453
}
437454

438455
private static final int HALF_MAX_SIZE = 500;

src/test/java/hudson/tasks/junit/CaseResultTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,23 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
442442
assertEquals(0, cr.getProperties().size());
443443
}
444444

445+
/**
446+
* Test truncation works along unicode codepoint boundaries.
447+
*/
448+
@Test
449+
void testCleanupTruncated() throws Exception {
450+
// empty
451+
assertEquals("", CaseResult.cleanupTruncated("").toString());
452+
// ordinary
453+
assertEquals("abc", CaseResult.cleanupTruncated("abc").toString());
454+
// starts with a trail surrogate.
455+
assertEquals("abc", CaseResult.cleanupTruncated("\uDC00abc").toString());
456+
// ends with a lead surrogate.
457+
assertEquals("abc", CaseResult.cleanupTruncated("abc\uD800").toString());
458+
// starts with trail surrogate and ends with lead surrogate
459+
assertEquals("abc", CaseResult.cleanupTruncated("\uDC00abc\uD800").toString());
460+
}
461+
445462
private String composeXPath(String[] fields) {
446463
StringBuilder tmp = new StringBuilder(100);
447464
for (String f : fields) {

0 commit comments

Comments
 (0)