mirror of https://github.com/shred/acme4j
Prefetch from certificate stream (fixes #127)
Works around a bug in Conscrypt. The certificate stream is not read there if InputStream.available() returns 0, which is the case in acme4j since the stream is directly read from the CA via HTTP. The workaround uses a BufferedInputStream and prefetches a few bytes from the HTTP stream if available() is invoked.pull/129/head
parent
df221291e8
commit
cf0bfc1390
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue