// // ======================================================================== // 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 java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * <p>Parser for the PARAMS frame body.</p> * <pre> * struct small_name_small_value_params_body { * ubyte nameLength; * ubyte valueLength; * ubyte[] nameBytes; * ubyte[] valueBytes; * } * * struct small_name_large_value_params_body { * ubyte nameLength; * uint valueLength; * ubyte[] nameBytes; * ubyte[] valueBytes; * } * * struct large_name_small_value_params_body { * uint nameLength; * ubyte valueLength; * ubyte[] nameBytes; * ubyte[] valueBytes; * } * * struct large_name_large_value_params_body { * uint nameLength; * uint valueLength; * ubyte[] nameBytes; * ubyte[] valueBytes; * } * </pre> */ public class ParamsContentParser extends ContentParser { private static final Logger LOG = Log.getLogger(ParamsContentParser.class); private final ServerParser.Listener listener; private State state = State.LENGTH; private int cursor; private int length; private int nameLength; private int valueLength; private byte[] nameBytes; private byte[] valueBytes; public ParamsContentParser(HeaderParser headerParser, ServerParser.Listener listener) { super(headerParser); this.listener = listener; } @Override public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining() || state == State.PARAM) { switch (state) { case LENGTH: { length = getContentLength(); state = State.NAME_LENGTH; break; } case NAME_LENGTH: { if (isLargeLength(buffer)) { if (buffer.remaining() >= 4) { nameLength = buffer.getInt() & 0x7F_FF; state = State.VALUE_LENGTH; length -= 4; } else { state = State.NAME_LENGTH_BYTES; cursor = 0; } } else { nameLength = buffer.get() & 0xFF; state = State.VALUE_LENGTH; --length; } break; } case NAME_LENGTH_BYTES: { int quarterInt = buffer.get() & 0xFF; nameLength = (nameLength << 8) + quarterInt; --length; if (++cursor == 4) { nameLength &= 0x7F_FF; state = State.VALUE_LENGTH; } break; } case VALUE_LENGTH: { if (isLargeLength(buffer)) { if (buffer.remaining() >= 4) { valueLength = buffer.getInt() & 0x7F_FF; state = State.NAME; length -= 4; } else { state = State.VALUE_LENGTH_BYTES; cursor = 0; } } else { valueLength = buffer.get() & 0xFF; state = State.NAME; --length; } break; } case VALUE_LENGTH_BYTES: { int quarterInt = buffer.get() & 0xFF; valueLength = (valueLength << 8) + quarterInt; --length; if (++cursor == 4) { valueLength &= 0x7F_FF; state = State.NAME; } break; } case NAME: { nameBytes = new byte[nameLength]; if (buffer.remaining() >= nameLength) { buffer.get(nameBytes); state = State.VALUE; length -= nameLength; } else { state = State.NAME_BYTES; cursor = 0; } break; } case NAME_BYTES: { nameBytes[cursor] = buffer.get(); --length; if (++cursor == nameLength) state = State.VALUE; break; } case VALUE: { valueBytes = new byte[valueLength]; if (buffer.remaining() >= valueLength) { buffer.get(valueBytes); state = State.PARAM; length -= valueLength; } else { state = State.VALUE_BYTES; cursor = 0; } break; } case VALUE_BYTES: { valueBytes[cursor] = buffer.get(); --length; if (++cursor == valueLength) state = State.PARAM; break; } case PARAM: { Charset utf8 = StandardCharsets.UTF_8; onParam(new String(nameBytes, utf8), new String(valueBytes, utf8)); partialReset(); if (length == 0) { reset(); return Result.COMPLETE; } break; } default: { throw new IllegalStateException(); } } } return Result.PENDING; } @Override public void noContent() { onParams(); } protected void onParam(String name, String value) { try { listener.onHeader(getRequest(), new HttpField(name, value)); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Exception while invoking listener " + listener, x); } } protected void onParams() { try { listener.onHeaders(getRequest()); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Exception while invoking listener " + listener, x); } } private boolean isLargeLength(ByteBuffer buffer) { return (buffer.get(buffer.position()) & 0x80) == 0x80; } private void partialReset() { state = State.NAME_LENGTH; cursor = 0; nameLength = 0; valueLength = 0; nameBytes = null; valueBytes = null; } private void reset() { partialReset(); state = State.LENGTH; length = 0; } private enum State { LENGTH, NAME_LENGTH, NAME_LENGTH_BYTES, VALUE_LENGTH, VALUE_LENGTH_BYTES, NAME, NAME_BYTES, VALUE, VALUE_BYTES, PARAM } }