| // |
| // ======================================================================== |
| // 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 org.eclipse.jetty.http.HttpField; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| |
| 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 = Charset.forName("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 |
| } |
| } |