diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java index 29e29f68..e670dd33 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java @@ -13,17 +13,18 @@ */ package org.shredzone.acme4j.connector; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; /** * Normalizes line separators in an InputStream. Converts all line separators to '\n'. - * Multiple line separators are compressed to a single line separator. + * Multiple line separators are compressed to a single line separator. Leading line + * separators are removed. Trailing line separators are compressed to a single separator. */ public class TrimmingInputStream extends InputStream { - - private final InputStream in; - private boolean wasLineSeparator = true; + private final BufferedInputStream in; + private boolean startOfFile = true; /** * Creates a new {@link TrimmingInputStream}. @@ -33,26 +34,50 @@ public class TrimmingInputStream extends InputStream { * closed. */ public TrimmingInputStream(InputStream in) { - this.in = in; + this.in = new BufferedInputStream(in, 1024); } @Override public int read() throws IOException { int ch = in.read(); - if (wasLineSeparator) { - while (isLineSeparator(ch)) { - ch = in.read(); + if (!isLineSeparator(ch)) { + startOfFile = false; + return ch; + } + + in.mark(1); + ch = in.read(); + while (isLineSeparator(ch)) { + in.mark(1); + ch = in.read(); + } + + if (startOfFile) { + startOfFile = false; + return ch; + } else { + in.reset(); + return '\n'; + } + } + + @Override + public int available() throws IOException { + // Workaround for https://github.com/google/conscrypt/issues/1068. Conscrypt + // requires the stream to have at least one non-blocking byte available for + // reading, otherwise generateCertificates() will not read the stream, but + // immediately returns an empty list. This workaround pre-fills the buffer + // of the BufferedInputStream by reading 1 byte ahead. + if (in.available() == 0) { + in.mark(1); + int read = in.read(); + in.reset(); + if (read < 0) { + return 0; } } - - wasLineSeparator = isLineSeparator(ch); - - if (ch == '\r') { - ch = '\n'; - } - - return ch; + return in.available(); } @Override diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java index d57fa9fb..8d88e181 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java @@ -15,6 +15,7 @@ package org.shredzone.acme4j.connector; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -26,6 +27,15 @@ import org.junit.Test; * Unit tests for {@link TrimmingInputStream}. */ public class TrimmingInputStreamTest { + private final static String FULL_TEXT = + "Gallia est omnis divisa in partes tres,\r\n\r\n\r\n" + + "quarum unam incolunt Belgae, aliam Aquitani,\r\r\r\n\n" + + "tertiam, qui ipsorum lingua Celtae, nostra Galli appellantur."; + + private final static String TRIMMED_TEXT = + "Gallia est omnis divisa in partes tres,\n" + + "quarum unam incolunt Belgae, aliam Aquitani,\n" + + "tertiam, qui ipsorum lingua Celtae, nostra Galli appellantur."; @Test public void testEmpty() throws IOException { @@ -33,15 +43,48 @@ public class TrimmingInputStreamTest { assertThat(out, is("")); } + @Test + public void testLineBreakOnly() throws IOException { + String out1 = trimByStream("\n"); + assertThat(out1, is("")); + + String out2 = trimByStream("\r"); + assertThat(out2, is("")); + + String out3 = trimByStream("\r\n"); + assertThat(out2, is("")); + } + @Test public void testTrim() throws IOException { - String out = trimByStream("\n\n" - + "Gallia est omnis divisa in partes tres,\r\n\r\n\r\n" - + "quarum unam incolunt Belgae, aliam Aquitani,\r\r\r\n\n" - + "tertiam, qui ipsorum lingua Celtae, nostra Galli appellantur."); - assertThat(out, is("Gallia est omnis divisa in partes tres,\n" - + "quarum unam incolunt Belgae, aliam Aquitani,\n" - + "tertiam, qui ipsorum lingua Celtae, nostra Galli appellantur.")); + String out = trimByStream(FULL_TEXT); + assertThat(out, is(TRIMMED_TEXT)); + } + + @Test + public void testTrimEndOnly() throws IOException { + String out = trimByStream(FULL_TEXT + "\r\n\r\n"); + assertThat(out, is(TRIMMED_TEXT + "\n")); + } + + @Test + public void testTrimStartOnly() throws IOException { + String out = trimByStream("\n\n" + FULL_TEXT); + assertThat(out, is(TRIMMED_TEXT)); + } + + @Test + public void testTrimFull() throws IOException { + String out = trimByStream("\n\n" + FULL_TEXT + "\r\n\r\n"); + assertThat(out, is(TRIMMED_TEXT + "\n")); + } + + @Test + public void testAvailable() throws IOException { + try (TrimmingInputStream in = new TrimmingInputStream( + new ByteArrayInputStream("Test".getBytes(StandardCharsets.US_ASCII)))) { + assertThat(in.available(), not(0)); + } } /**