This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-9.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 37fad09296a3ac7550a4cc1103b2830b60bb66e9
Author: Richard Zowalla <r...@apache.org>
AuthorDate: Mon Mar 25 11:15:42 2024 +0100

    TOMEE-4306 - Add unpatched version
---
 .../java/org/apache/coyote/http2/Http2Parser.java  | 839 +++++++++++++++++++++
 1 file changed, 839 insertions(+)

diff --git 
a/tomee/apache-tomee/src/patch/java/org/apache/coyote/http2/Http2Parser.java 
b/tomee/apache-tomee/src/patch/java/org/apache/coyote/http2/Http2Parser.java
new file mode 100644
index 0000000000..72c30532e1
--- /dev/null
+++ b/tomee/apache-tomee/src/patch/java/org/apache/coyote/http2/Http2Parser.java
@@ -0,0 +1,839 @@
+/*
+ *  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;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.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[] frameHeaderBuffer = new byte[9];
+
+    private volatile HpackDecoder hpackDecoder;
+    private volatile ByteBuffer headerReadBuffer =
+            ByteBuffer.allocate(Constants.DEFAULT_HEADER_READ_BUFFER_SIZE);
+    private volatile int headersCurrentStream = -1;
+    private volatile boolean headersEndStream = false;
+
+    Http2Parser(String connectionId, Input input, Output output) {
+        this.connectionId = connectionId;
+        this.input = input;
+        this.output = output;
+    }
+
+
+    /**
+     * 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);
+    }
+
+
+    protected boolean readFrame(boolean block, FrameType expected)
+            throws IOException, Http2Exception {
+
+        if (!input.fill(block, frameHeaderBuffer)) {
+            return false;
+        }
+
+        int payloadSize = ByteUtil.getThreeBytes(frameHeaderBuffer, 0);
+        int frameTypeId = ByteUtil.getOneByte(frameHeaderBuffer, 3);
+        FrameType frameType = FrameType.valueOf(frameTypeId);
+        int flags = ByteUtil.getOneByte(frameHeaderBuffer, 4);
+        int streamId = ByteUtil.get31Bits(frameHeaderBuffer, 5);
+
+        try {
+            validateFrame(expected, frameType, streamId, flags, payloadSize);
+        } catch (StreamException se) {
+            swallowPayload(streamId, frameTypeId, 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, 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 UNKNOWN:
+            readUnknownFrame(streamId, frameTypeId, flags, payloadSize, null);
+        }
+
+        return true;
+    }
+
+    protected void 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 = 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);
+            }
+            // +1 is for the padding length byte we just read above
+            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) {
+            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);
+                    }
+                    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);
+                }
+                // 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);
+            }
+        }
+    }
+
+
+    protected void readHeadersFrame(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) {
+            swallowPayload(streamId, FrameType.HEADERS.getId(), 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);
+
+        swallowPayload(streamId, FrameType.HEADERS.getId(), 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) {
+            // Ensure empty SETTINGS frame increments the overhead count
+            output.setting(null, 0);
+        } else {
+            // Process the settings
+            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);
+                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
+     */
+    protected void readPushPromiseFrame(int streamId, int flags, int 
payloadSize, ByteBuffer buffer)
+            throws Http2Exception, IOException {
+        throw new 
ConnectionException(sm.getString("http2Parser.processFramePushPromise",
+                connectionId, Integer.valueOf(streamId)), 
Http2Error.PROTOCOL_ERROR);
+    }
+
+
+    protected void readPingFrame(int flags, ByteBuffer buffer) throws 
IOException {
+        // Read the payload
+        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)));
+        }
+
+        // Validate the data
+        if (windowSizeIncrement == 0) {
+            if (streamId == 0) {
+                throw new ConnectionException(
+                        
sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement",
+                                connectionId, Integer.toString(streamId)),
+                        Http2Error.PROTOCOL_ERROR);
+            } else {
+                throw new StreamException(
+                        
sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement",
+                                connectionId, Integer.toString(streamId)),
+                        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) {
+            // No headers to continue
+            throw new ConnectionException(sm.getString(
+                    "http2Parser.processFrameContinuation.notExpected", 
connectionId,
+                    Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
+        }
+
+        boolean endOfHeaders = Flags.isEndOfHeaders(flags);
+
+        // Used to detect abusive clients sending large numbers of small
+        // continuation frames
+        output.headersContinue(payloadSize, endOfHeaders);
+
+        readHeaderPayload(streamId, payloadSize, buffer);
+
+        if (endOfHeaders) {
+            headersCurrentStream = -1;
+            onHeadersComplete(streamId);
+        }
+    }
+
+
+    protected void readHeaderPayload(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) {
+                // Buffer needs expansion
+                int newSize;
+                if (headerReadBuffer.capacity() < payloadSize) {
+                    // First step, expand to the current payload. That should
+                    // cover most cases.
+                    newSize = payloadSize;
+                } else {
+                    // Header must be spread over multiple frames. Keep 
doubling
+                    // buffer size until the header can be read.
+                    newSize = headerReadBuffer.capacity() * 2;
+                }
+                headerReadBuffer = ByteBufferUtils.expand(headerReadBuffer, 
newSize);
+            }
+            int toRead = Math.min(headerReadBuffer.remaining(), remaining);
+            // headerReadBuffer in write mode
+            if (buffer == null) {
+                input.fill(true, headerReadBuffer, toRead);
+            } else {
+                int oldLimit = buffer.limit();
+                buffer.limit(buffer.position() + toRead);
+                headerReadBuffer.put(buffer);
+                buffer.limit(oldLimit);
+            }
+            // switch to read mode
+            headerReadBuffer.flip();
+            try {
+                hpackDecoder.decode(headerReadBuffer);
+            } catch (HpackException hpe) {
+                throw new ConnectionException(
+                        
sm.getString("http2Parser.processFrameHeaders.decodingFailed"),
+                        Http2Error.COMPRESSION_ERROR, hpe);
+            }
+
+            // switches to write mode
+            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, 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
+     */
+    protected void 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 = 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 (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) {
+                                throw new 
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);
+                }
+            }
+        }
+    }
+
+
+    protected void onHeadersComplete(int streamId) throws Http2Exception {
+        // Any left over data is a compression error
+        if (headerReadBuffer.position() > 0) {
+            throw new 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();
+
+        synchronized (output) {
+            output.headersEnd(streamId);
+
+            if (headersEndStream) {
+                output.receivedEndOfStream(streamId);
+                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.
+     */
+    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) {
+                // NO-OP: RST is OK here
+            } 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);
+    }
+
+
+    /**
+     * 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 = 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"));
+                }
+            }
+
+            // Must always be followed by a settings frame
+            readFrame(true, FrameType.SETTINGS);
+        } catch (IOException ioe) {
+            throw new 
ProtocolException(sm.getString("http2Parser.preface.io"), ioe);
+        }
+    }
+
+
+    /**
+     * Interface that must be implemented by the source of data for the parser.
+     */
+    static 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;
+
+        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();
+    }
+
+
+    /**
+     * Interface that must be implemented to receive notifications from the
+     * parser as it processes incoming frames.
+     */
+    static interface Output {
+
+        HpackDecoder getHpackDecoder();
+
+        // Data frames
+        ByteBuffer startRequestBodyFrame(int streamId, int payloadSize, 
boolean endOfStream) throws Http2Exception;
+        void endRequestBodyFrame(int streamId, int dataLength) throws 
Http2Exception, IOException;
+        void receivedEndOfStream(int streamId) throws ConnectionException;
+        /**
+         * 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;
+
+        // Header frames
+        HeaderEmitter headersStart(int streamId, boolean headersEndStream)
+                throws Http2Exception, IOException;
+        void headersContinue(int payloadSize, boolean endOfHeaders);
+        void headersEnd(int streamId) throws Http2Exception;
+
+        // Priority frames (also headers)
+        void reprioritise(int streamId, int parentStreamId, boolean exclusive, 
int weight)
+                throws Http2Exception;
+
+        // Reset frames
+        void reset(int streamId, long errorCode) throws Http2Exception;
+
+        // Settings frames
+        void setting(Setting setting, long value) throws ConnectionException;
+        void settingsEnd(boolean ack) throws IOException;
+
+        // Ping frames
+        void pingReceive(byte[] payload, boolean ack) throws IOException;
+
+        // Goaway
+        void goaway(int lastStreamId, long errorCode, String debugData);
+
+        // Window size
+        void incrementWindowSize(int streamId, int increment) throws 
Http2Exception;
+
+        /**
+         * 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;
+    }
+}


Reply via email to