/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ package org.apache.coyote.http2;
/** * Read and process a single frame. Once the start of a frame is read, the remainder will be read using blocking IO. * * @param block Should this method block until a frame is available if no frame is available immediately? * * @return <code>true</code> if a frame was read otherwise <code>false</code> * * @throws IOException If an IO error occurs while trying to read a frame * * @deprecated Unused. Will be removed in Tomcat 11 onwards.
*/
@Deprecated boolean readFrame(boolean block) throws Http2Exception, IOException { return readFrame(block, null);
}
/** * Read and process a single frame. The initial read is non-blocking to determine if a frame is present. Once the * start of a frame is read, the remainder will be read using blocking IO. * * @return <code>true</code> if a frame was read otherwise <code>false</code> * * @throws IOException If an IO error occurs while trying to read a frame
*/ boolean readFrame() throws Http2Exception, IOException { return readFrame(false, null);
}
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, flags, payloadSize, 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 PRIORITY_UPDATE:
readPriorityUpdateFrame(payloadSize, null); break; case UNKNOWN:
readUnknownFrame(streamId, frameTypeId, flags, payloadSize, null);
}
returntrue;
}
protectedvoid readDataFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer) throws Http2Exception, IOException { // Process the Stream int padLength = 0;
boolean endOfStream = Flags.isEndOfStream(flags);
int dataLength; if (Flags.hasPadding(flags)) { if (buffer == null) { byte[] b = newbyte[1];
input.fill(true, b);
padLength = b[0] & 0xFF;
} else {
padLength = buffer.get() & 0xFF;
}
if (padLength >= payloadSize) { thrownew ConnectionException(
sm.getString("http2Parser.processFrame.tooMuchPadding", connectionId,
Integer.toString(streamId), Integer.toString(padLength), Integer.toString(payloadSize)),
Http2Error.PROTOCOL_ERROR);
} // +1 is for the padding length byte we just read above
dataLength = payloadSize - (padLength + 1);
} else {
dataLength = payloadSize;
}
ByteBuffer dest = output.startRequestBodyFrame(streamId, payloadSize, endOfStream); if (dest == null) {
swallowPayload(streamId, FrameType.DATA.getId(), dataLength, false, buffer); // Process padding before sending any notifications in case padding // is invalid. if (Flags.hasPadding(flags)) {
swallowPayload(streamId, FrameType.DATA.getId(), padLength, true, buffer);
} if (endOfStream) {
output.receivedEndOfStream(streamId);
}
} else { synchronized (dest) { if (dest.remaining() < payloadSize) { // Client has sent more data than permitted by Window size
swallowPayload(streamId, FrameType.DATA.getId(), dataLength, false, buffer); if (Flags.hasPadding(flags)) {
swallowPayload(streamId, FrameType.DATA.getId(), padLength, true, buffer);
} thrownew 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);
} // Process padding before sending any notifications in case // padding is invalid. if (Flags.hasPadding(flags)) {
swallowPayload(streamId, FrameType.DATA.getId(), padLength, true, buffer);
} if (endOfStream) {
output.receivedEndOfStream(streamId);
}
output.endRequestBodyFrame(streamId, dataLength);
}
}
}
protectedvoid readHeadersFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer) throws Http2Exception, IOException {
protectedvoid readPriorityFrame(int streamId, ByteBuffer buffer) throws IOException { // RFC 7450 priority frames are ignored. Still need to treat as overhead. try {
swallowPayload(streamId, FrameType.PRIORITY.getId(), 5, false, buffer);
} catch (ConnectionException e) { // Will never happen because swallowPayload() is called with isPadding set // to false
}
output.increaseOverheadCount(FrameType.PRIORITY);
}
if (payloadSize == 0 && !ack) { // Ensure empty SETTINGS frame increments the overhead count
output.setting(null, 0);
} else { // Process the settings byte[] setting = newbyte[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);
Setting key = Setting.valueOf(id); if (key == Setting.UNKNOWN) {
log.warn(sm.getString("connectionSettings.unknown", connectionId, Integer.toString(id), Long.toString(value)));
}
output.setting(key, value);
}
}
output.settingsEnd(ack);
}
/** * This default server side implementation always throws an exception. If re-used for client side parsing, this * method should be overridden with an appropriate implementation. * * @param streamId The pushed stream * @param flags The flags set in the frame header * @param payloadSize The size of the payload in bytes * @param buffer The payload, if available * * @throws Http2Exception Always * @throws IOException May be thrown by sub-classes that parse this frame
*/ protectedvoid readPushPromiseFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer) throws Http2Exception, IOException { thrownew ConnectionException(
sm.getString("http2Parser.processFramePushPromise", connectionId, Integer.valueOf(streamId)),
Http2Error.PROTOCOL_ERROR);
}
int prioritizedStreamID = ByteUtil.get31Bits(payload, 0);
if (prioritizedStreamID == 0) { thrownew ConnectionException(sm.getString("http2Parser.processFramePriorityUpdate.streamZero"),
Http2Error.PROTOCOL_ERROR);
}
ByteArrayInputStream bais = new ByteArrayInputStream(payload, 4, payloadSize - 4);
Reader r = new BufferedReader(new InputStreamReader(bais, StandardCharsets.US_ASCII));
Priority p = Priority.parsePriority(r);
if (log.isDebugEnabled()) {
log.debug(sm.getString("http2Parser.processFramePriorityUpdate.debug", connectionId,
Integer.toString(prioritizedStreamID), Integer.toString(p.getUrgency()), Boolean.valueOf(p.getIncremental())));
}
protectedvoid readUnknownFrame(int streamId, int frameTypeId, int flags, int payloadSize, ByteBuffer buffer) throws IOException { try {
swallowPayload(streamId, frameTypeId, payloadSize, false, buffer);
} catch (ConnectionException e) { // Will never happen because swallowPayload() is called with isPadding set // to false
} finally {
output.onSwallowedUnknownFrame(streamId, frameTypeId, flags, payloadSize);
}
}
/** * Swallow some or all of the bytes from the payload of an HTTP/2 frame. * * @param streamId Stream being swallowed * @param frameTypeId Type of HTTP/2 frame for which the bytes will be swallowed * @param len Number of bytes to swallow * @param isPadding Are the bytes to be swallowed padding bytes? * @param byteBuffer Used with {@link Http2AsyncParser} to access the data that has already been read * * @throws IOException If an I/O error occurs reading additional bytes into the input buffer. * @throws ConnectionException If the swallowed bytes are expected to have a value of zero but do not
*/ protectedvoid swallowPayload(int streamId, int frameTypeId, int len, boolean isPadding, ByteBuffer byteBuffer) throws IOException, ConnectionException { if (log.isDebugEnabled()) {
log.debug(sm.getString("http2Parser.swallow.debug", connectionId, Integer.toString(streamId),
Integer.toString(len)));
} try { if (len == 0) { return;
} if (!isPadding && byteBuffer != null) {
byteBuffer.position(byteBuffer.position() + len);
} else { int read = 0; byte[] buffer = newbyte[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 (isPadding) { // Validate the padding is zero since receiving non-zero padding // is a strong indication of either a faulty client or a server // side bug. for (int i = 0; i < thisTime; i++) { if (buffer[i] != 0) { thrownew ConnectionException(sm.getString("http2Parser.nonZeroPadding", connectionId,
Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
}
}
}
read += thisTime;
}
}
} finally { if (FrameType.DATA.getIdByte() == frameTypeId) { if (isPadding) { // Need to add 1 for the padding length bytes that was also // part of the payload.
len += 1;
} if (len > 0) {
output.onSwallowedDataFramePayload(streamId, len);
}
}
}
}
protectedvoid onHeadersComplete(int streamId) throws Http2Exception { // Any left over data is a compression error if (headerReadBuffer.position() > 0) { thrownew ConnectionException(sm.getString("http2Parser.processFrameHeaders.decodingDataLeft"),
Http2Error.COMPRESSION_ERROR);
}
// Delay validation (and triggering any exception) until this point // since all the headers still have to be read if a StreamException is // going to be thrown.
hpackDecoder.getHeaderEmitter().validateHeaders();
if (headersEndStream) {
headersEndStream = false;
}
}
// Reset size for new request if the buffer was previously expanded if (headerReadBuffer.capacity() > Constants.DEFAULT_HEADER_READ_BUFFER_SIZE) {
headerReadBuffer = ByteBuffer.allocate(Constants.DEFAULT_HEADER_READ_BUFFER_SIZE);
}
}
/* * Implementation note: Validation applicable to all incoming frames should be implemented here. Frame type specific * validation should be performed in the appropriate readXxxFrame() method. For validation applicable to some but * not all frame types, use your judgement.
*/ protectedvoid 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)));
}
int maxFrameSize = input.getMaxFrameSize(); if (payloadSize > maxFrameSize) { thrownew ConnectionException(sm.getString("http2Parser.payloadTooBig", Integer.toString(payloadSize),
Integer.toString(maxFrameSize)), Http2Error.FRAME_SIZE_ERROR);
}
if (headersCurrentStream != -1) { if (headersCurrentStream != streamId) { thrownew ConnectionException(
sm.getString("http2Parser.headers.wrongStream", connectionId,
Integer.toString(headersCurrentStream), Integer.toString(streamId)),
Http2Error.COMPRESSION_ERROR);
} if (frameType == FrameType.RST) { // NO-OP: RST is OK here
} elseif (frameType != FrameType.CONTINUATION) { thrownew ConnectionException(sm.getString("http2Parser.headers.wrongFrameType", connectionId,
Integer.toString(headersCurrentStream), frameType), Http2Error.COMPRESSION_ERROR);
}
}
frameType.check(streamId, payloadSize);
}
/** * Read and validate the connection preface from input using blocking IO. * * @param webConnection The connection * @param stream The current stream
*/ void readConnectionPreface(WebConnection webConnection, Stream stream) throws Http2Exception { byte[] data = newbyte[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]) { thrownew ProtocolException(sm.getString("http2Parser.preface.invalid"));
}
}
// Must always be followed by a settings frame
readFrame(true, FrameType.SETTINGS);
} catch (IOException ioe) { thrownew ProtocolException(sm.getString("http2Parser.preface.io"), ioe);
}
}
/** * Interface that must be implemented by the source of data for the parser.
*/ interface Input {
/** * Fill the given array with data unless non-blocking is requested and no data is available. If any data is * available then the buffer will be filled using blocking I/O. * * @param block Should the first read into the provided buffer be a blocking read or not. * @param data Buffer to fill * @param offset Position in buffer to start writing * @param length Number of bytes to read * * @return <code>true</code> if the buffer was filled otherwise <code>false</code> * * @throws IOException If an I/O occurred while obtaining data with which to fill the buffer
*/ boolean fill(boolean block, byte[] data, int offset, int length) throws IOException;
/** * Notification triggered when the parser swallows some or all of a DATA frame payload without writing it to the * ByteBuffer returned by {@link #startRequestBodyFrame(int, int, boolean)}. * * @param streamId The stream on which the payload that has been swallowed was received * @param swallowedDataBytesCount The number of bytes that the parser swallowed. * * @throws ConnectionException If an error fatal to the HTTP/2 connection occurs while swallowing the payload * @throws IOException If an I/O occurred while swallowing the payload
*/ void onSwallowedDataFramePayload(int streamId, int swallowedDataBytesCount) throws ConnectionException, IOException;
/** * Notification triggered when the parser swallows the payload of an unknown frame. * * @param streamId The stream on which the swallowed frame was received * @param frameTypeId The (unrecognised) type of swallowed frame * @param flags The flags set in the header of the swallowed frame * @param size The payload size of the swallowed frame * * @throws IOException If an I/O occurred while swallowing the unknown frame
*/ void onSwallowedUnknownFrame(int streamId, int frameTypeId, int flags, int size) throws IOException;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.