Skip to content

Commit

Permalink
[JENKINS-73090] Handle CR from LineTransformationOutputStream (#9219)
Browse files Browse the repository at this point in the history
* [JENKINS-73090] Handle CR from `LineTransformationOutputStream`

* Spotless

* Strengthening test to assert that actual linefeed is passed to `eol`

* Also needed to adjust `HyperlinkNote` trick
  • Loading branch information
jglick authored May 18, 2024
1 parent 90a6ac5 commit aaf4b64
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 7 deletions.
6 changes: 2 additions & 4 deletions core/src/main/java/hudson/console/HyperlinkNote.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,8 @@ static String encodeTo(String url, String text, BiFunction<String, Integer, Cons
// If text contains newlines, then its stored length will not match its length when being
// displayed, since the display length will only include text up to the first newline,
// which will cause an IndexOutOfBoundsException in MarkupText#rangeCheck when
// ConsoleAnnotationOutputStream converts the note into markup. That stream treats '\n' as
// the sole end-of-line marker on all platforms, so we ignore '\r' because it will not
// break the conversion.
text = text.replace('\n', ' ');
// ConsoleAnnotationOutputStream converts the note into markup.
text = text.replace('\n', ' ').replace('\r', ' ');
try {
return constructor.apply(url, text.length()).encode() + text;
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
* @since 1.349
*/
public abstract class LineTransformationOutputStream extends OutputStream {
private boolean sawCR;
private ByteArrayOutputStream2 buf = new ByteArrayOutputStream2();

/**
Expand All @@ -53,8 +54,15 @@ public abstract class LineTransformationOutputStream extends OutputStream {

@Override
public void write(int b) throws IOException {
if (sawCR && b != '\n') {
eol();
}
buf.write(b);
if (b == LF) eol();
if (b == '\n') {
eol();
} else if (b == '\r') {
sawCR = true;
}
}

private void eol() throws IOException {
Expand All @@ -65,6 +73,7 @@ private void eol() throws IOException {
buf = new ByteArrayOutputStream2();
else
buf.reset();
sawCR = false;
}

@Override
Expand Down Expand Up @@ -110,8 +119,6 @@ protected String trimEOL(String line) {
return line;
}

private static final int LF = 0x0A;

/**
* Convenience subclass for cases where you wish to process lines being sent to an underlying stream.
* {@link #eol} will typically {@link OutputStream#write(byte[], int, int)} to {@link #out}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* The MIT License
*
* Copyright 2024 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package hudson.console;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Test;

public final class LineTransformationOutputStreamTest {

@Test public void nl() throws Exception {
test("\n");
}

@Test public void crnl() throws Exception {
test("\r\n");
}

@Test public void cr() throws Exception {
test("\r");
}

private void test(String linefeed) throws Exception {
var count = new AtomicLong();
long max = 1_000_000; // to see OOME in cr without fix: 1_000_000_000
try (var counter = new LineTransformationOutputStream() {
@Override protected void eol(byte[] b, int len) throws IOException {
var line = new String(b, 0, len);
assertThat(line, endsWith(linefeed));
count.addAndGet(Integer.parseInt(trimEOL(line)));
}
}) {
for (long i = 0; i < max; i++) {
counter.write((i + linefeed).getBytes(StandardCharsets.UTF_8));
}
}
assertThat(count.get(), is((max * (max - 1)) / 2));
}

}

0 comments on commit aaf4b64

Please sign in to comment.