Skip to content
Open
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
148 changes: 102 additions & 46 deletions src/main/java/org/takes/rq/ChunkedInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,70 @@
*/
final class ChunkedInputStream extends InputStream {

/**
* Empty value for checking the result.
*/
private static final int EMPTY_VALUE = -1;

/**
* Default radix value.
*/
private static final int DEFAULT_RADIX = 16;

/**
* Double slash value.
*/
private static final int DOUBLE_SLASH = '\\';

/**
* Quoted string value.
*/
private static final int QUOTED_VALUE = '\"';

/**
* Next line value.
*/
private static final int NEXT_LINE = '\n';

/**
* R value.
*/
private static final int R_VALUE = '\r';

/**
* Semicolon value.
*/
private static final int SEMICOLON = ';';

/**
* Exception for bad state.
*/
private static final String BAD_STATE = "Bad state";

/**
* Exception for bad chunk.
*/
private static final String BAD_CHUNK_SIZE = "Bad chunk size: %s";

/**
* Exception for chunk stream end.
*/
private static final String END_OF_STREAM = "chunked stream ended unexpectedly";

/**
* Exception for crlf expectation state.
*/
private static final String CRLF_EXPECTED = "CRLF expected at end of chunk: ";

/**
* Exception for protocol violation.
*/
private static final String BAD_PROTOCOL = String.format(
"%s%s",
"Protocol violation: Unexpected",
" single newline character in chunk size"
);

/**
* The inputstream that we're wrapping.
*/
Expand Down Expand Up @@ -85,7 +149,7 @@ public int read() throws IOException {
}
final int result;
if (this.eof) {
result = -1;
result = ChunkedInputStream.EMPTY_VALUE;
} else {
++this.pos;
result = this.origin.read();
Expand All @@ -101,7 +165,7 @@ public int read(final byte[] buf, final int off, final int len)
}
final int result;
if (this.eof) {
result = -1;
result = ChunkedInputStream.EMPTY_VALUE;
} else {
final int shift = Math.min(len, this.size - this.pos);
final int count = this.origin.read(buf, off, shift);
Expand Down Expand Up @@ -137,7 +201,7 @@ private void readCrlf() throws IOException {
throw new IOException(
String.format(
"%s %d%s%d",
"CRLF expected at end of chunk: ",
ChunkedInputStream.CRLF_EXPECTED,
crsymbol,
"/",
lfsymbol
Expand Down Expand Up @@ -174,7 +238,7 @@ private static int chunkSize(final InputStream stream)
throws IOException {
final ByteArrayOutputStream baos = ChunkedInputStream.sizeLine(stream);
final String data = baos.toString(Charset.defaultCharset().name());
final int separator = data.indexOf(';');
final int separator = data.indexOf(ChunkedInputStream.SEMICOLON);
final Text number = new Trimmed(
new Unchecked<>(
new Ternary<>(
Expand All @@ -186,44 +250,20 @@ private static int chunkSize(final InputStream stream)
);
try {
return Integer.parseInt(
new UncheckedText(
number
).asString(),
16
new UncheckedText(number).asString(),
ChunkedInputStream.DEFAULT_RADIX
);
} catch (final NumberFormatException ex) {
throw new IOException(
String.format(
"Bad chunk size: %s",
ChunkedInputStream.BAD_CHUNK_SIZE,
baos.toString(Charset.defaultCharset().name())
),
ex
);
}
}

/**
* Possible states of FSM that used to find chunk size.
*/
private enum State {
/**
* Normal.
*/
NORMAL,
/**
* If \r was scanned.
*/
R,
/**
* Inside quoted string.
*/
QUOTED_STRING,
/**
* End.
*/
END;
}

/**
* Extract line with chunk size from stream.
* @param stream Input stream.
Expand Down Expand Up @@ -251,32 +291,26 @@ private static ByteArrayOutputStream sizeLine(final InputStream stream)
private static State next(final InputStream stream, final State state,
final ByteArrayOutputStream line) throws IOException {
final int next = stream.read();
if (next == -1) {
throw new IOException("chunked stream ended unexpectedly");
if (next == ChunkedInputStream.EMPTY_VALUE) {
throw new IOException(ChunkedInputStream.END_OF_STREAM);
}
final State result;
switch (state) {
case NORMAL:
result = nextNormal(state, line, next);
break;
case R:
if (next == '\n') {
if (next == ChunkedInputStream.NEXT_LINE) {
result = State.END;
} else {
throw new IOException(
String.format(
"%s%s",
"Protocol violation: Unexpected",
" single newline character in chunk size"
)
);
throw new IOException(ChunkedInputStream.BAD_PROTOCOL);
}
break;
case QUOTED_STRING:
result = nextQuoted(stream, state, line, next);
break;
default:
throw new IllegalStateException("Bad state");
throw new IllegalStateException(ChunkedInputStream.BAD_STATE);
}
return result;
}
Expand All @@ -292,10 +326,10 @@ private static State nextNormal(final State state,
final ByteArrayOutputStream line, final int next) {
final State result;
switch (next) {
case '\r':
case ChunkedInputStream.R_VALUE:
result = State.R;
break;
case '\"':
case ChunkedInputStream.QUOTED_VALUE:
result = State.QUOTED_STRING;
break;
default:
Expand All @@ -321,11 +355,11 @@ private static State nextQuoted(final InputStream stream, final State state,
throws IOException {
final State result;
switch (next) {
case '\\':
case ChunkedInputStream.DOUBLE_SLASH:
result = state;
line.write(stream.read());
break;
case '\"':
case ChunkedInputStream.QUOTED_VALUE:
result = State.NORMAL;
break;
default:
Expand All @@ -335,4 +369,26 @@ private static State nextQuoted(final InputStream stream, final State state,
}
return result;
}

/**
* Possible states of FSM that used to find chunk size.
*/
private enum State {
/**
* Normal.
*/
NORMAL,
/**
* If \r was scanned.
*/
R,
/**
* Inside quoted string.
*/
QUOTED_STRING,
/**
* End.
*/
END;
}
}
8 changes: 6 additions & 2 deletions src/main/java/org/takes/servlet/HttpServletRequestFake.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import org.takes.Request;
import org.takes.misc.Opt;
import org.takes.rq.RqHeaders;
import org.takes.rq.RqHref;
import org.takes.rq.RqMethod;
Expand Down Expand Up @@ -218,9 +219,12 @@ public String getServerName() {
ex
);
}
String host = uri.getHost();
if (host == null || host.isEmpty()) {
Opt<String> optHost = new Opt.Single<>(uri.getHost());
String host;
if (!optHost.has() || optHost.get().isEmpty()) {
host = "localhost";
} else {
host = optHost.get();
}
return host;
}
Expand Down
Loading