Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/java/org/apache/commons/csv/CSVFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -2467,8 +2467,10 @@ private void printWithEscapes(final Reader reader, final Appendable appendable)
builder.append((char) c);
Arrays.fill(lookAheadBuffer, (char) 0);
bufferedReader.peek(lookAheadBuffer);
final String test = builder.toString() + new String(lookAheadBuffer);
final boolean isDelimiterStart = isDelimiter((char) c, test, pos, delimArray, delimLength);
// Match the delimiter against the current character plus the look-ahead buffer only. Rebuilding the test
// string from the whole accumulated builder made this loop O(n^2) for values without escapable characters.
final String test = String.valueOf((char) c) + new String(lookAheadBuffer);
final boolean isDelimiterStart = isDelimiter((char) c, test, 0, delimArray, delimLength);
final boolean isCr = c == Constants.CR;
final boolean isLf = c == Constants.LF;
// A leading comment marker would be read back as a comment, so escape it.
Expand Down
16 changes: 16 additions & 0 deletions src/test/java/org/apache/commons/csv/CSVFormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,22 @@ void testPrintWithEscapesEndWithoutCRLF() throws IOException {
assertEquals("x?,y?,x", out.toString());
}

@Test
void testPrintWithEscapesReaderLargeValueIsLinear() {
// A Reader value with no escapable characters used to run in O(n^2): the delimiter look-ahead rebuilt a
// String from the whole accumulated output on every character. A large value must stay linear and correct.
final int size = 1_000_000;
final char[] data = new char[size];
Arrays.fill(data, 'a');
final CSVFormat format = CSVFormat.RFC4180.withEscape('?').withDelimiter(',').withQuote(null).withRecordSeparator(CRLF);
assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
final StringBuilder out = new StringBuilder(size);
format.print(new StringReader(new String(data)), out, true);
// No character in the value is special, so the output is the value unchanged.
assertEquals(size, out.length());
});
}

@Test
void testPrintWithoutQuotes() throws IOException {
final Reader in = new StringReader("");
Expand Down
Loading