/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.spdy; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder { private static final int LENGTH_FIELD_SIZE = 4; private final int maxHeaderSize; private State state; private ByteBuf cumulation; private int headerSize; private int numHeaders; private int length; private String name; private enum State { READ_NUM_HEADERS, READ_NAME_LENGTH, READ_NAME, SKIP_NAME, READ_VALUE_LENGTH, READ_VALUE, SKIP_VALUE, END_HEADER_BLOCK, ERROR } public SpdyHeaderBlockRawDecoder(SpdyVersion spdyVersion, int maxHeaderSize) { if (spdyVersion == null) { throw new NullPointerException("spdyVersion"); } this.maxHeaderSize = maxHeaderSize; state = State.READ_NUM_HEADERS; } private static int readLengthField(ByteBuf buffer) { int length = getSignedInt(buffer, buffer.readerIndex()); buffer.skipBytes(LENGTH_FIELD_SIZE); return length; } @Override void decode(ByteBufAllocator alloc, ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception { if (headerBlock == null) { throw new NullPointerException("headerBlock"); } if (frame == null) { throw new NullPointerException("frame"); } if (cumulation == null) { decodeHeaderBlock(headerBlock, frame); if (headerBlock.isReadable()) { cumulation = alloc.buffer(headerBlock.readableBytes()); cumulation.writeBytes(headerBlock); } } else { cumulation.writeBytes(headerBlock); decodeHeaderBlock(cumulation, frame); if (cumulation.isReadable()) { cumulation.discardReadBytes(); } else { releaseBuffer(); } } } protected void decodeHeaderBlock(ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception { int skipLength; while (headerBlock.isReadable()) { switch(state) { case READ_NUM_HEADERS: if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) { return; } numHeaders = readLengthField(headerBlock); if (numHeaders < 0) { state = State.ERROR; frame.setInvalid(); } else if (numHeaders == 0) { state = State.END_HEADER_BLOCK; } else { state = State.READ_NAME_LENGTH; } break; case READ_NAME_LENGTH: if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) { return; } length = readLengthField(headerBlock); // Recipients of a zero-length name must issue a stream error if (length <= 0) { state = State.ERROR; frame.setInvalid(); } else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) { headerSize = maxHeaderSize + 1; state = State.SKIP_NAME; frame.setTruncated(); } else { headerSize += length; state = State.READ_NAME; } break; case READ_NAME: if (headerBlock.readableBytes() < length) { return; } byte[] nameBytes = new byte[length]; headerBlock.readBytes(nameBytes); name = new String(nameBytes, "UTF-8"); // Check for identically named headers if (frame.headers().contains(name)) { state = State.ERROR; frame.setInvalid(); } else { state = State.READ_VALUE_LENGTH; } break; case SKIP_NAME: skipLength = Math.min(headerBlock.readableBytes(), length); headerBlock.skipBytes(skipLength); length -= skipLength; if (length == 0) { state = State.READ_VALUE_LENGTH; } break; case READ_VALUE_LENGTH: if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) { return; } length = readLengthField(headerBlock); // Recipients of illegal value fields must issue a stream error if (length < 0) { state = State.ERROR; frame.setInvalid(); } else if (length == 0) { if (!frame.isTruncated()) { // SPDY/3 allows zero-length (empty) header values frame.headers().add(name, ""); } name = null; if (--numHeaders == 0) { state = State.END_HEADER_BLOCK; } else { state = State.READ_NAME_LENGTH; } } else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) { headerSize = maxHeaderSize + 1; name = null; state = State.SKIP_VALUE; frame.setTruncated(); } else { headerSize += length; state = State.READ_VALUE; } break; case READ_VALUE: if (headerBlock.readableBytes() < length) { return; } byte[] valueBytes = new byte[length]; headerBlock.readBytes(valueBytes); // Add Name/Value pair to headers int index = 0; int offset = 0; // Value must not start with a NULL character if (valueBytes[0] == (byte) 0) { state = State.ERROR; frame.setInvalid(); break; } while (index < length) { while (index < valueBytes.length && valueBytes[index] != (byte) 0) { index ++; } if (index < valueBytes.length) { // Received NULL character if (index + 1 == valueBytes.length || valueBytes[index + 1] == (byte) 0) { // Value field ended with a NULL character or // received multiple, in-sequence NULL characters. // Recipients of illegal value fields must issue a stream error state = State.ERROR; frame.setInvalid(); break; } } String value = new String(valueBytes, offset, index - offset, "UTF-8"); try { frame.headers().add(name, value); } catch (IllegalArgumentException e) { // Name contains NULL or non-ascii characters state = State.ERROR; frame.setInvalid(); break; } index ++; offset = index; } name = null; // If we broke out of the add header loop, break here if (state == State.ERROR) { break; } if (--numHeaders == 0) { state = State.END_HEADER_BLOCK; } else { state = State.READ_NAME_LENGTH; } break; case SKIP_VALUE: skipLength = Math.min(headerBlock.readableBytes(), length); headerBlock.skipBytes(skipLength); length -= skipLength; if (length == 0) { if (--numHeaders == 0) { state = State.END_HEADER_BLOCK; } else { state = State.READ_NAME_LENGTH; } } break; case END_HEADER_BLOCK: state = State.ERROR; frame.setInvalid(); break; case ERROR: headerBlock.skipBytes(headerBlock.readableBytes()); return; default: throw new Error("Shouldn't reach here."); } } } @Override void endHeaderBlock(SpdyHeadersFrame frame) throws Exception { if (state != State.END_HEADER_BLOCK) { frame.setInvalid(); } releaseBuffer(); // Initialize header block decoding fields headerSize = 0; name = null; state = State.READ_NUM_HEADERS; } @Override void end() { releaseBuffer(); } private void releaseBuffer() { if (cumulation != null) { cumulation.release(); cumulation = null; } } }