// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.http2.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.util.BufferUtil; public class DataBodyParser extends BodyParser { private State state = State.PREPARE; private int padding; private int paddingLength; private int length; public DataBodyParser(HeaderParser headerParser, Parser.Listener listener) { super(headerParser, listener); } private void reset() { state = State.PREPARE; padding = 0; paddingLength = 0; length = 0; } @Override protected void emptyBody(ByteBuffer buffer) { if (isPadding()) connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame"); else onData(BufferUtil.EMPTY_BUFFER, false, 0); } @Override public boolean parse(ByteBuffer buffer) { boolean loop = false; while (buffer.hasRemaining() || loop) { switch (state) { case PREPARE: { // SPEC: wrong streamId is treated as connection error. if (getStreamId() == 0) return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame"); length = getBodyLength(); state = isPadding() ? State.PADDING_LENGTH : State.DATA; break; } case PADDING_LENGTH: { padding = 1; // We have seen this byte. paddingLength = buffer.get() & 0xFF; --length; length -= paddingLength; state = State.DATA; loop = length == 0; if (length < 0) return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_data_frame_padding"); break; } case DATA: { int size = Math.min(buffer.remaining(), length); int position = buffer.position(); int limit = buffer.limit(); buffer.limit(position + size); ByteBuffer slice = buffer.slice(); buffer.limit(limit); buffer.position(position + size); length -= size; if (length == 0) { state = State.PADDING; loop = paddingLength == 0; // Padding bytes include the bytes that define the // padding length plus the actual padding bytes. onData(slice, false, padding + paddingLength); } else { // We got partial data, simulate a smaller frame, and stay in DATA state. // No padding for these synthetic frames (even if we have read // the padding length already), it will be accounted at the end. onData(slice, true, 0); } break; } case PADDING: { int size = Math.min(buffer.remaining(), paddingLength); buffer.position(buffer.position() + size); paddingLength -= size; if (paddingLength == 0) { reset(); return true; } break; } default: { throw new IllegalStateException(); } } } return false; } private void onData(ByteBuffer buffer, boolean fragment, int padding) { DataFrame frame = new DataFrame(getStreamId(), buffer, !fragment && isEndStream(), padding); notifyData(frame); } private enum State { PREPARE, PADDING_LENGTH, DATA, PADDING } }