// // ======================================================================== // 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.fcgi.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * <p>The FastCGI protocol exchanges <em>frames</em>.</p> * <pre> * struct frame { * ubyte version; * ubyte type; * ushort requestId; * ushort contentLength; * ubyte paddingLength; * ubyte reserved; * ubyte[] content; * ubyte[] padding; * } * </pre> * <p>Depending on the {@code type}, the content may have a different format, * so there are specialized content parsers.</p> * * @see HeaderParser * @see ContentParser */ public abstract class Parser { private static final Logger LOG = Log.getLogger(Parser.class); protected final HeaderParser headerParser = new HeaderParser(); private State state = State.HEADER; private int padding; /** * @param buffer the bytes to parse * @return true if the caller should stop parsing, false if the caller should continue parsing */ public boolean parse(ByteBuffer buffer) { while (true) { switch (state) { case HEADER: { if (!headerParser.parse(buffer)) return false; state = State.CONTENT; break; } case CONTENT: { ContentParser contentParser = findContentParser(headerParser.getFrameType()); if (headerParser.getContentLength() == 0) { contentParser.noContent(); } else { ContentParser.Result result = contentParser.parse(buffer); if (LOG.isDebugEnabled()) LOG.debug("Parsed request {} content {} result={}", headerParser.getRequest(), headerParser.getFrameType(), result); if (result == ContentParser.Result.PENDING) { // Not enough data, signal to read/parse more. return false; } if (result == ContentParser.Result.ASYNC) { // The content will be processed asynchronously, signal to stop // parsing; the async operation will eventually resume parsing. return true; } } padding = headerParser.getPaddingLength(); state = State.PADDING; break; } case PADDING: { if (buffer.remaining() >= padding) { buffer.position(buffer.position() + padding); reset(); break; } else { padding -= buffer.remaining(); buffer.position(buffer.limit()); return false; } } default: { throw new IllegalStateException(); } } } } protected abstract ContentParser findContentParser(FCGI.FrameType frameType); private void reset() { headerParser.reset(); state = State.HEADER; padding = 0; } public interface Listener { public void onHeader(int request, HttpField field); public void onHeaders(int request); /** * @param request the request id * @param stream the stream type * @param buffer the content bytes * @return true to signal to the parser to stop parsing, false to continue parsing * @see Parser#parse(java.nio.ByteBuffer) */ public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer); public void onEnd(int request); public void onFailure(int request, Throwable failure); public static class Adapter implements Listener { @Override public void onHeader(int request, HttpField field) { } @Override public void onHeaders(int request) { } @Override public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { return false; } @Override public void onEnd(int request) { } @Override public void onFailure(int request, Throwable failure) { } } } private enum State { HEADER, CONTENT, PADDING } }