// // ======================================================================== // 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.Flags; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PingFrame; import org.eclipse.jetty.http2.frames.PriorityFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.hpack.HpackDecoder; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * <p>The HTTP/2 protocol parser.</p> * <p>This parser makes use of the {@link HeaderParser} and of * {@link BodyParser}s to parse HTTP/2 frames.</p> */ public class Parser { private static final Logger LOG = Log.getLogger(Parser.class); private final Listener listener; private final HeaderParser headerParser; private final BodyParser[] bodyParsers; private boolean continuation; private State state = State.HEADER; public Parser(ByteBufferPool byteBufferPool, Listener listener, int maxDynamicTableSize, int maxHeaderSize) { this.listener = listener; this.headerParser = new HeaderParser(); this.bodyParsers = new BodyParser[FrameType.values().length]; HeaderBlockParser headerBlockParser = new HeaderBlockParser(byteBufferPool, new HpackDecoder(maxDynamicTableSize, maxHeaderSize)); HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(); bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener); bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments); bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener); bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener); bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener); bodyParsers[FrameType.PUSH_PROMISE.getType()] = new PushPromiseBodyParser(headerParser, listener, headerBlockParser); bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener); bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener); bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener); bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments); } private void reset() { headerParser.reset(); state = State.HEADER; } /** * <p>Parses the given {@code buffer} bytes and emit events to a {@link Listener}.</p> * <p>When this method returns, the buffer may not be fully consumed, so invocations * to this method should be wrapped in a loop:</p> * <pre> * while (buffer.hasRemaining()) * parser.parse(buffer); * </pre> * * @param buffer the buffer to parse */ public void parse(ByteBuffer buffer) { try { while (true) { switch (state) { case HEADER: { if (!parseHeader(buffer)) return; break; } case BODY: { if (!parseBody(buffer)) return; break; } default: { throw new IllegalStateException(); } } } } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug(x); BufferUtil.clear(buffer); notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "parser_error"); } } protected boolean parseHeader(ByteBuffer buffer) { if (!headerParser.parse(buffer)) return false; FrameType frameType = FrameType.from(getFrameType()); if (LOG.isDebugEnabled()) LOG.debug("Parsed {} frame header from {}", frameType, buffer); if (continuation) { if (frameType != FrameType.CONTINUATION) { // SPEC: CONTINUATION frames must be consecutive. BufferUtil.clear(buffer); notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "continuation_frame_expected"); return false; } if (headerParser.hasFlag(Flags.END_HEADERS)) { continuation = false; } } else { if (frameType == FrameType.HEADERS && !headerParser.hasFlag(Flags.END_HEADERS)) { continuation = true; } } state = State.BODY; return true; } protected boolean parseBody(ByteBuffer buffer) { int type = getFrameType(); if (type < 0 || type >= bodyParsers.length) { BufferUtil.clear(buffer); notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unknown_frame_type_" + type); return false; } BodyParser bodyParser = bodyParsers[type]; if (headerParser.getLength() == 0) { bodyParser.emptyBody(buffer); } else { if (!bodyParser.parse(buffer)) return false; } if (LOG.isDebugEnabled()) LOG.debug("Parsed {} frame body from {}", FrameType.from(type), buffer); reset(); return true; } protected int getFrameType() { return headerParser.getFrameType(); } protected boolean hasFlag(int bit) { return headerParser.hasFlag(bit); } protected void notifyConnectionFailure(int error, String reason) { try { listener.onConnectionFailure(error, reason); } catch (Throwable x) { LOG.info("Failure while notifying listener " + listener, x); } } public interface Listener { public void onData(DataFrame frame); public void onHeaders(HeadersFrame frame); public void onPriority(PriorityFrame frame); public void onReset(ResetFrame frame); public void onSettings(SettingsFrame frame); public void onPushPromise(PushPromiseFrame frame); public void onPing(PingFrame frame); public void onGoAway(GoAwayFrame frame); public void onWindowUpdate(WindowUpdateFrame frame); public void onConnectionFailure(int error, String reason); public static class Adapter implements Listener { @Override public void onData(DataFrame frame) { } @Override public void onHeaders(HeadersFrame frame) { } @Override public void onPriority(PriorityFrame frame) { } @Override public void onReset(ResetFrame frame) { } @Override public void onSettings(SettingsFrame frame) { } @Override public void onPushPromise(PushPromiseFrame frame) { } @Override public void onPing(PingFrame frame) { } @Override public void onGoAway(GoAwayFrame frame) { } @Override public void onWindowUpdate(WindowUpdateFrame frame) { } @Override public void onConnectionFailure(int error, String reason) { LOG.warn("Connection failure: {}/{}", error, reason); } } } private enum State { HEADER, BODY } }