package org.apache.coyote.http2;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.WebConnection;
import org.apache.coyote.ProtocolException;
import org.apache.coyote.http2.HpackDecoder.HeaderEmitter;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteBufferUtils;
import org.apache.tomcat.util.res.StringManager;
class Http2Parser {
protected static final Log log = LogFactory.getLog(Http2Parser.class);
protected static final StringManager sm = StringManager.getManager(Http2Parser.class);
static final byte[] CLIENT_PREFACE_START =
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);
protected final String connectionId;
protected final Input input;
private final Output output;
private final byte[] = new byte[9];
private volatile HpackDecoder hpackDecoder;
private volatile ByteBuffer =
ByteBuffer.allocate(Constants.DEFAULT_HEADER_READ_BUFFER_SIZE);
private volatile int = -1;
private volatile boolean = false;
Http2Parser(String connectionId, Input input, Output output) {
this.connectionId = connectionId;
this.input = input;
this.output = output;
}
boolean readFrame(boolean block) throws Http2Exception, IOException {
return readFrame(block, null);
}
protected boolean readFrame(boolean block, FrameType expected)
throws IOException, Http2Exception {
if (!input.fill(block, frameHeaderBuffer)) {
return false;
}
int payloadSize = ByteUtil.getThreeBytes(frameHeaderBuffer, 0);
FrameType frameType = FrameType.valueOf(ByteUtil.getOneByte(frameHeaderBuffer, 3));
int flags = ByteUtil.getOneByte(frameHeaderBuffer, 4);
int streamId = ByteUtil.get31Bits(frameHeaderBuffer, 5);
try {
validateFrame(expected, frameType, streamId, flags, payloadSize);
} catch (StreamException se) {
swallow(streamId, payloadSize, false, null);
throw se;
}
switch (frameType) {
case DATA:
readDataFrame(streamId, flags, payloadSize, null);
break;
case HEADERS:
readHeadersFrame(streamId, flags, payloadSize, null);
break;
case PRIORITY:
readPriorityFrame(streamId, null);
break;
case RST:
readRstFrame(streamId, null);
break;
case SETTINGS:
readSettingsFrame(flags, payloadSize, null);
break;
case PUSH_PROMISE:
readPushPromiseFrame(streamId, null);
break;
case PING:
readPingFrame(flags, null);
break;
case GOAWAY:
readGoawayFrame(payloadSize, null);
break;
case WINDOW_UPDATE:
readWindowUpdateFrame(streamId, null);
break;
case CONTINUATION:
readContinuationFrame(streamId, flags, payloadSize, null);
break;
case UNKNOWN:
readUnknownFrame(streamId, frameType, flags, payloadSize, null);
}
return true;
}
protected void readDataFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
throws Http2Exception, IOException {
int padLength = 0;
boolean endOfStream = Flags.isEndOfStream(flags);
int dataLength;
if (Flags.hasPadding(flags)) {
if (buffer == null) {
byte[] b = new byte[1];
input.fill(true, b);
padLength = b[0] & 0xFF;
} else {
padLength = buffer.get() & 0xFF;
}
if (padLength >= payloadSize) {
throw new ConnectionException(
sm.getString("http2Parser.processFrame.tooMuchPadding", connectionId,
Integer.toString(streamId), Integer.toString(padLength),
Integer.toString(payloadSize)), Http2Error.PROTOCOL_ERROR);
}
dataLength = payloadSize - (padLength + 1);
} else {
dataLength = payloadSize;
}
if (log.isDebugEnabled()) {
String padding;
if (Flags.hasPadding(flags)) {
padding = Integer.toString(padLength);
} else {
padding = "none";
}
log.debug(sm.getString("http2Parser.processFrameData.lengths", connectionId,
Integer.toString(streamId), Integer.toString(dataLength), padding));
}
ByteBuffer dest = output.startRequestBodyFrame(streamId, payloadSize, endOfStream);
if (dest == null) {
swallow(streamId, dataLength, false, buffer);
if (padLength > 0) {
swallow(streamId, padLength, true, buffer);
}
if (endOfStream) {
output.receivedEndOfStream(streamId);
}
} else {
synchronized (dest) {
if (dest.remaining() < dataLength) {
swallow(streamId, dataLength, false, buffer);
throw new StreamException(sm.getString("http2Parser.processFrameData.window", connectionId),
Http2Error.FLOW_CONTROL_ERROR, streamId);
}
if (buffer == null) {
input.fill(true, dest, dataLength);
} else {
int oldLimit = buffer.limit();
buffer.limit(buffer.position() + dataLength);
dest.put(buffer);
buffer.limit(oldLimit);
}
if (padLength > 0) {
swallow(streamId, padLength, true, buffer);
}
if (endOfStream) {
output.receivedEndOfStream(streamId);
}
output.endRequestBodyFrame(streamId);
}
}
if (padLength > 0) {
output.swallowedPadding(streamId, padLength);
}
}
protected void (int streamId, int flags, int payloadSize, ByteBuffer buffer)
throws Http2Exception, IOException {
headersEndStream = Flags.isEndOfStream(flags);
if (hpackDecoder == null) {
hpackDecoder = output.getHpackDecoder();
}
try {
hpackDecoder.setHeaderEmitter(output.headersStart(streamId, headersEndStream));
} catch (StreamException se) {
swallow(streamId, payloadSize, false, buffer);
throw se;
}
int padLength = 0;
boolean padding = Flags.hasPadding(flags);
boolean priority = Flags.hasPriority(flags);
int optionalLen = 0;
if (padding) {
optionalLen = 1;
}
if (priority) {
optionalLen += 5;
}
if (optionalLen > 0) {
byte[] optional = new byte[optionalLen];
if (buffer == null) {
input.fill(true, optional);
} else {
buffer.get(optional);
}
int optionalPos = 0;
if (padding) {
padLength = ByteUtil.getOneByte(optional, optionalPos++);
if (padLength >= payloadSize) {
throw new ConnectionException(
sm.getString("http2Parser.processFrame.tooMuchPadding", connectionId,
Integer.toString(streamId), Integer.toString(padLength),
Integer.toString(payloadSize)), Http2Error.PROTOCOL_ERROR);
}
}
if (priority) {
boolean exclusive = ByteUtil.isBit7Set(optional[optionalPos]);
int parentStreamId = ByteUtil.get31Bits(optional, optionalPos);
int weight = ByteUtil.getOneByte(optional, optionalPos + 4) + 1;
output.reprioritise(streamId, parentStreamId, exclusive, weight);
}
payloadSize -= optionalLen;
payloadSize -= padLength;
}
readHeaderPayload(streamId, payloadSize, buffer);
swallow(streamId, padLength, true, buffer);
if (Flags.isEndOfHeaders(flags)) {
onHeadersComplete(streamId);
} else {
headersCurrentStream = streamId;
}
}
protected void readPriorityFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
byte[] payload = new byte[5];
if (buffer == null) {
input.fill(true, payload);
} else {
buffer.get(payload);
}
boolean exclusive = ByteUtil.isBit7Set(payload[0]);
int parentStreamId = ByteUtil.get31Bits(payload, 0);
int weight = ByteUtil.getOneByte(payload, 4) + 1;
if (streamId == parentStreamId) {
throw new StreamException(sm.getString("http2Parser.processFramePriority.invalidParent",
connectionId, Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR, streamId);
}
output.reprioritise(streamId, parentStreamId, exclusive, weight);
}
protected void readRstFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
byte[] payload = new byte[4];
if (buffer == null) {
input.fill(true, payload);
} else {
buffer.get(payload);
}
long errorCode = ByteUtil.getFourBytes(payload, 0);
output.reset(streamId, errorCode);
headersCurrentStream = -1;
headersEndStream = false;
}
protected void readSettingsFrame(int flags, int payloadSize, ByteBuffer buffer) throws Http2Exception, IOException {
boolean ack = Flags.isAck(flags);
if (payloadSize > 0 && ack) {
throw new ConnectionException(sm.getString(
"http2Parser.processFrameSettings.ackWithNonZeroPayload"),
Http2Error.FRAME_SIZE_ERROR);
}
if (payloadSize == 0 && !ack) {
output.setting(null, 0);
} else {
byte[] setting = new byte[6];
for (int i = 0; i < payloadSize / 6; i++) {
if (buffer == null) {
input.fill(true, setting);
} else {
buffer.get(setting);
}
int id = ByteUtil.getTwoBytes(setting, 0);
long value = ByteUtil.getFourBytes(setting, 2);
output.setting(Setting.valueOf(id), value);
}
}
output.settingsEnd(ack);
}
protected void readPushPromiseFrame(int streamId, ByteBuffer buffer) throws Http2Exception {
throw new ConnectionException(sm.getString("http2Parser.processFramePushPromise",
connectionId, Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR);
}
protected void readPingFrame(int flags, ByteBuffer buffer) throws IOException {
byte[] payload = new byte[8];
if (buffer == null) {
input.fill(true, payload);
} else {
buffer.get(payload);
}
output.pingReceive(payload, Flags.isAck(flags));
}
protected void readGoawayFrame(int payloadSize, ByteBuffer buffer) throws IOException {
byte[] payload = new byte[payloadSize];
if (buffer == null) {
input.fill(true, payload);
} else {
buffer.get(payload);
}
int lastStreamId = ByteUtil.get31Bits(payload, 0);
long errorCode = ByteUtil.getFourBytes(payload, 4);
String debugData = null;
if (payloadSize > 8) {
debugData = new String(payload, 8, payloadSize - 8, StandardCharsets.UTF_8);
}
output.goaway(lastStreamId, errorCode, debugData);
}
protected void readWindowUpdateFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
byte[] payload = new byte[4];
if (buffer == null) {
input.fill(true, payload);
} else {
buffer.get(payload);
}
int windowSizeIncrement = ByteUtil.get31Bits(payload, 0);
if (log.isDebugEnabled()) {
log.debug(sm.getString("http2Parser.processFrameWindowUpdate.debug", connectionId,
Integer.toString(streamId), Integer.toString(windowSizeIncrement)));
}
if (windowSizeIncrement == 0) {
if (streamId == 0) {
throw new ConnectionException(
sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement"),
Http2Error.PROTOCOL_ERROR);
} else {
throw new StreamException(
sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement"),
Http2Error.PROTOCOL_ERROR, streamId);
}
}
output.incrementWindowSize(streamId, windowSizeIncrement);
}
protected void readContinuationFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
throws Http2Exception, IOException {
if (headersCurrentStream == -1) {
throw new ConnectionException(sm.getString(
"http2Parser.processFrameContinuation.notExpected", connectionId,
Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
}
boolean endOfHeaders = Flags.isEndOfHeaders(flags);
output.headersContinue(payloadSize, endOfHeaders);
readHeaderPayload(streamId, payloadSize, buffer);
if (endOfHeaders) {
headersCurrentStream = -1;
onHeadersComplete(streamId);
}
}
protected void (int streamId, int payloadSize, ByteBuffer buffer)
throws Http2Exception, IOException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("http2Parser.processFrameHeaders.payload", connectionId,
Integer.valueOf(streamId), Integer.valueOf(payloadSize)));
}
int remaining = payloadSize;
while (remaining > 0) {
if (headerReadBuffer.remaining() == 0) {
int newSize;
if (headerReadBuffer.capacity() < payloadSize) {
newSize = payloadSize;
} else {
newSize = headerReadBuffer.capacity() * 2;
}
headerReadBuffer = ByteBufferUtils.expand(headerReadBuffer, newSize);
}
int toRead = Math.min(headerReadBuffer.remaining(), remaining);
if (buffer == null) {
input.fill(true, headerReadBuffer, toRead);
} else {
int oldLimit = buffer.limit();
buffer.limit(buffer.position() + toRead);
headerReadBuffer.put(buffer);
buffer.limit(oldLimit);
}
headerReadBuffer.flip();
try {
hpackDecoder.decode(headerReadBuffer);
} catch (HpackException hpe) {
throw new ConnectionException(
sm.getString("http2Parser.processFrameHeaders.decodingFailed"),
Http2Error.COMPRESSION_ERROR, hpe);
}
headerReadBuffer.compact();
remaining -= toRead;
if (hpackDecoder.isHeaderCountExceeded()) {
StreamException headerException = new StreamException(sm.getString(
"http2Parser.headerLimitCount", connectionId, Integer.valueOf(streamId)),
Http2Error.ENHANCE_YOUR_CALM, streamId);
hpackDecoder.getHeaderEmitter().setHeaderException(headerException);
}
if (hpackDecoder.isHeaderSizeExceeded(headerReadBuffer.position())) {
StreamException headerException = new StreamException(sm.getString(
"http2Parser.headerLimitSize", connectionId, Integer.valueOf(streamId)),
Http2Error.ENHANCE_YOUR_CALM, streamId);
hpackDecoder.getHeaderEmitter().setHeaderException(headerException);
}
if (hpackDecoder.isHeaderSwallowSizeExceeded(headerReadBuffer.position())) {
throw new ConnectionException(sm.getString("http2Parser.headerLimitSize",
connectionId, Integer.valueOf(streamId)), Http2Error.ENHANCE_YOUR_CALM);
}
}
}
protected void readUnknownFrame(int streamId, FrameType frameType, int flags, int payloadSize, ByteBuffer buffer)
throws IOException {
try {
swallow(streamId, payloadSize, false, buffer);
} catch (ConnectionException e) {
}
output.swallowed(streamId, frameType, flags, payloadSize);
}
protected void swallow(int streamId, int len, boolean mustBeZero, ByteBuffer byteBuffer)
throws IOException, ConnectionException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("http2Parser.swallow.debug", connectionId,
Integer.toString(streamId), Integer.toString(len)));
}
if (len == 0) {
return;
}
if (!mustBeZero && byteBuffer != null) {
byteBuffer.position(byteBuffer.position() + len);
} else {
int read = 0;
byte[] buffer = new byte[1024];
while (read < len) {
int thisTime = Math.min(buffer.length, len - read);
if (byteBuffer == null) {
input.fill(true, buffer, 0, thisTime);
} else {
byteBuffer.get(buffer, 0, thisTime);
}
if (mustBeZero) {
for (int i = 0; i < thisTime; i++) {
if (buffer[i] != 0) {
throw new ConnectionException(sm.getString("http2Parser.nonZeroPadding",
connectionId, Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
}
}
}
read += thisTime;
}
}
}
protected void (int streamId) throws Http2Exception {
if (headerReadBuffer.position() > 0) {
throw new ConnectionException(
sm.getString("http2Parser.processFrameHeaders.decodingDataLeft"),
Http2Error.COMPRESSION_ERROR);
}
hpackDecoder.getHeaderEmitter().validateHeaders();
synchronized (output) {
output.headersEnd(streamId);
if (headersEndStream) {
output.receivedEndOfStream(streamId);
headersEndStream = false;
}
}
if (headerReadBuffer.capacity() > Constants.DEFAULT_HEADER_READ_BUFFER_SIZE) {
headerReadBuffer = ByteBuffer.allocate(Constants.DEFAULT_HEADER_READ_BUFFER_SIZE);
}
}
protected void validateFrame(FrameType expected, FrameType frameType, int streamId, int flags,
int payloadSize) throws Http2Exception {
if (log.isDebugEnabled()) {
log.debug(sm.getString("http2Parser.processFrame", connectionId,
Integer.toString(streamId), frameType, Integer.toString(flags),
Integer.toString(payloadSize)));
}
if (expected != null && frameType != expected) {
throw new StreamException(sm.getString("http2Parser.processFrame.unexpectedType",
expected, frameType), Http2Error.PROTOCOL_ERROR, streamId);
}
int maxFrameSize = input.getMaxFrameSize();
if (payloadSize > maxFrameSize) {
throw new ConnectionException(sm.getString("http2Parser.payloadTooBig",
Integer.toString(payloadSize), Integer.toString(maxFrameSize)),
Http2Error.FRAME_SIZE_ERROR);
}
if (headersCurrentStream != -1) {
if (headersCurrentStream != streamId) {
throw new ConnectionException(sm.getString("http2Parser.headers.wrongStream",
connectionId, Integer.toString(headersCurrentStream),
Integer.toString(streamId)), Http2Error.COMPRESSION_ERROR);
}
if (frameType == FrameType.RST) {
} else if (frameType != FrameType.CONTINUATION) {
throw new ConnectionException(sm.getString("http2Parser.headers.wrongFrameType",
connectionId, Integer.toString(headersCurrentStream),
frameType), Http2Error.COMPRESSION_ERROR);
}
}
frameType.check(streamId, payloadSize);
}
void readConnectionPreface(WebConnection webConnection, Stream stream) throws Http2Exception {
byte[] data = new byte[CLIENT_PREFACE_START.length];
try {
input.fill(true, data);
for (int i = 0; i < CLIENT_PREFACE_START.length; i++) {
if (CLIENT_PREFACE_START[i] != data[i]) {
throw new ProtocolException(sm.getString("http2Parser.preface.invalid"));
}
}
readFrame(true, FrameType.SETTINGS);
} catch (IOException ioe) {
throw new ProtocolException(sm.getString("http2Parser.preface.io"), ioe);
}
}
static interface Input {
boolean fill(boolean block, byte[] data, int offset, int length) throws IOException;
default boolean fill(boolean block, byte[] data) throws IOException {
return fill(block, data, 0, data.length);
}
default boolean fill(boolean block, ByteBuffer data, int len) throws IOException {
boolean result = fill(block, data.array(), data.arrayOffset() + data.position(), len);
if (result) {
data.position(data.position() + len);
}
return result;
}
int getMaxFrameSize();
}
static interface Output {
HpackDecoder getHpackDecoder();
ByteBuffer startRequestBodyFrame(int streamId, int payloadSize, boolean endOfStream) throws Http2Exception;
void endRequestBodyFrame(int streamId) throws Http2Exception;
void receivedEndOfStream(int streamId) throws ConnectionException;
void swallowedPadding(int streamId, int paddingLength) throws ConnectionException, IOException;
HeaderEmitter (int streamId, boolean headersEndStream)
throws Http2Exception, IOException;
void (int payloadSize, boolean endOfHeaders);
void (int streamId) throws ConnectionException;
void reprioritise(int streamId, int parentStreamId, boolean exclusive, int weight)
throws Http2Exception;
void reset(int streamId, long errorCode) throws Http2Exception;
void setting(Setting setting, long value) throws ConnectionException;
void settingsEnd(boolean ack) throws IOException;
void pingReceive(byte[] payload, boolean ack) throws IOException;
void goaway(int lastStreamId, long errorCode, String debugData);
void incrementWindowSize(int streamId, int increment) throws Http2Exception;
void swallowed(int streamId, FrameType frameType, int flags, int size) throws IOException;
}
}