// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.handler.codec.http;

import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.StringUtil;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.PrematureChannelClosureException;
import java.util.List;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.ObjectUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ByteProcessor;
import io.netty.util.AsciiString;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.ByteToMessageDecoder;

public abstract class HttpObjectDecoder extends ByteToMessageDecoder
{
    public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
    public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
    public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;
    public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS = true;
    public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
    public static final boolean DEFAULT_VALIDATE_HEADERS = true;
    public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
    public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
    public static final boolean DEFAULT_STRICT_LINE_PARSING;
    private static final Runnable THROW_INVALID_CHUNK_EXTENSION;
    private static final Runnable THROW_INVALID_LINE_SEPARATOR;
    private final int maxChunkSize;
    private final boolean chunkedSupported;
    private final boolean allowPartialChunks;
    @Deprecated
    protected final boolean validateHeaders;
    protected final HttpHeadersFactory headersFactory;
    protected final HttpHeadersFactory trailersFactory;
    private final boolean allowDuplicateContentLengths;
    private final ByteBuf parserScratchBuffer;
    private final Runnable defaultStrictCRLFCheck;
    private final HeaderParser headerParser;
    private final LineParser lineParser;
    private HttpMessage message;
    private long chunkSize;
    private long contentLength;
    private boolean chunked;
    private boolean isSwitchingToNonHttp1Protocol;
    private final AtomicBoolean resetRequested;
    private AsciiString name;
    private String value;
    private LastHttpContent trailer;
    private State currentState;
    private static final boolean[] SP_LENIENT_BYTES;
    private static final boolean[] LATIN_WHITESPACE;
    private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
    private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES;
    
    @Override
    protected void handlerRemoved0(final ChannelHandlerContext ctx) throws Exception {
        try {
            this.parserScratchBuffer.release();
        }
        finally {
            super.handlerRemoved0(ctx);
        }
    }
    
    protected HttpObjectDecoder() {
        this(new HttpDecoderConfig());
    }
    
    @Deprecated
    protected HttpObjectDecoder(final int maxInitialLineLength, final int maxHeaderSize, final int maxChunkSize, final boolean chunkedSupported) {
        this(new HttpDecoderConfig().setMaxInitialLineLength(maxInitialLineLength).setMaxHeaderSize(maxHeaderSize).setMaxChunkSize(maxChunkSize).setChunkedSupported(chunkedSupported));
    }
    
    @Deprecated
    protected HttpObjectDecoder(final int maxInitialLineLength, final int maxHeaderSize, final int maxChunkSize, final boolean chunkedSupported, final boolean validateHeaders) {
        this(new HttpDecoderConfig().setMaxInitialLineLength(maxInitialLineLength).setMaxHeaderSize(maxHeaderSize).setMaxChunkSize(maxChunkSize).setChunkedSupported(chunkedSupported).setValidateHeaders(validateHeaders));
    }
    
    @Deprecated
    protected HttpObjectDecoder(final int maxInitialLineLength, final int maxHeaderSize, final int maxChunkSize, final boolean chunkedSupported, final boolean validateHeaders, final int initialBufferSize) {
        this(new HttpDecoderConfig().setMaxInitialLineLength(maxInitialLineLength).setMaxHeaderSize(maxHeaderSize).setMaxChunkSize(maxChunkSize).setChunkedSupported(chunkedSupported).setValidateHeaders(validateHeaders).setInitialBufferSize(initialBufferSize));
    }
    
    @Deprecated
    protected HttpObjectDecoder(final int maxInitialLineLength, final int maxHeaderSize, final int maxChunkSize, final boolean chunkedSupported, final boolean validateHeaders, final int initialBufferSize, final boolean allowDuplicateContentLengths) {
        this(new HttpDecoderConfig().setMaxInitialLineLength(maxInitialLineLength).setMaxHeaderSize(maxHeaderSize).setMaxChunkSize(maxChunkSize).setChunkedSupported(chunkedSupported).setValidateHeaders(validateHeaders).setInitialBufferSize(initialBufferSize).setAllowDuplicateContentLengths(allowDuplicateContentLengths));
    }
    
    @Deprecated
    protected HttpObjectDecoder(final int maxInitialLineLength, final int maxHeaderSize, final int maxChunkSize, final boolean chunkedSupported, final boolean validateHeaders, final int initialBufferSize, final boolean allowDuplicateContentLengths, final boolean allowPartialChunks) {
        this(new HttpDecoderConfig().setMaxInitialLineLength(maxInitialLineLength).setMaxHeaderSize(maxHeaderSize).setMaxChunkSize(maxChunkSize).setChunkedSupported(chunkedSupported).setValidateHeaders(validateHeaders).setInitialBufferSize(initialBufferSize).setAllowDuplicateContentLengths(allowDuplicateContentLengths).setAllowPartialChunks(allowPartialChunks));
    }
    
    protected HttpObjectDecoder(final HttpDecoderConfig config) {
        this.contentLength = Long.MIN_VALUE;
        this.resetRequested = new AtomicBoolean();
        this.currentState = State.SKIP_CONTROL_CHARS;
        ObjectUtil.checkNotNull(config, "config");
        this.parserScratchBuffer = Unpooled.buffer(config.getInitialBufferSize());
        this.defaultStrictCRLFCheck = (config.isStrictLineParsing() ? HttpObjectDecoder.THROW_INVALID_LINE_SEPARATOR : null);
        this.lineParser = new LineParser(this.parserScratchBuffer, config.getMaxInitialLineLength());
        this.headerParser = new HeaderParser(this.parserScratchBuffer, config.getMaxHeaderSize());
        this.maxChunkSize = config.getMaxChunkSize();
        this.chunkedSupported = config.isChunkedSupported();
        this.headersFactory = config.getHeadersFactory();
        this.trailersFactory = config.getTrailersFactory();
        this.validateHeaders = this.isValidating(this.headersFactory);
        this.allowDuplicateContentLengths = config.isAllowDuplicateContentLengths();
        this.allowPartialChunks = config.isAllowPartialChunks();
    }
    
    protected boolean isValidating(final HttpHeadersFactory headersFactory) {
        if (headersFactory instanceof DefaultHttpHeadersFactory) {
            final DefaultHttpHeadersFactory builder = (DefaultHttpHeadersFactory)headersFactory;
            return builder.isValidatingHeaderNames() || builder.isValidatingHeaderValues();
        }
        return true;
    }
    
    @Override
    protected void decode(final ChannelHandlerContext ctx, final ByteBuf buffer, final List<Object> out) throws Exception {
        if (this.resetRequested.get()) {
            this.resetNow();
        }
        switch (this.currentState) {
            case SKIP_CONTROL_CHARS:
            case READ_INITIAL: {
                try {
                    final ByteBuf line = this.lineParser.parse(buffer, this.defaultStrictCRLFCheck);
                    if (line == null) {
                        return;
                    }
                    final String[] initialLine = this.splitInitialLine(line);
                    assert initialLine.length == 3 : "initialLine::length must be 3";
                    this.message = this.createMessage(initialLine);
                    this.currentState = State.READ_HEADER;
                }
                catch (final Exception e) {
                    out.add(this.invalidMessage(this.message, buffer, e));
                }
            }
            case READ_HEADER: {
                try {
                    final State nextState = this.readHeaders(buffer);
                    if (nextState == null) {
                        return;
                    }
                    this.currentState = nextState;
                    switch (nextState) {
                        case SKIP_CONTROL_CHARS: {
                            this.addCurrentMessage(out);
                            out.add(LastHttpContent.EMPTY_LAST_CONTENT);
                            this.resetNow();
                            return;
                        }
                        case READ_CHUNK_SIZE: {
                            if (!this.chunkedSupported) {
                                throw new IllegalArgumentException("Chunked messages not supported");
                            }
                            this.addCurrentMessage(out);
                            return;
                        }
                        default: {
                            if (this.contentLength == 0L || (this.contentLength == -1L && this.isDecodingRequest())) {
                                this.addCurrentMessage(out);
                                out.add(LastHttpContent.EMPTY_LAST_CONTENT);
                                this.resetNow();
                                return;
                            }
                            assert nextState == State.READ_VARIABLE_LENGTH_CONTENT;
                            this.addCurrentMessage(out);
                            if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
                                this.chunkSize = this.contentLength;
                            }
                        }
                    }
                }
                catch (final Exception e) {
                    out.add(this.invalidMessage(this.message, buffer, e));
                }
            }
            case READ_VARIABLE_LENGTH_CONTENT: {
                final int toRead = Math.min(buffer.readableBytes(), this.maxChunkSize);
                if (toRead > 0) {
                    final ByteBuf content = buffer.readRetainedSlice(toRead);
                    out.add(new DefaultHttpContent(content));
                }
                return;
            }
            case READ_FIXED_LENGTH_CONTENT: {
                final int readLimit = buffer.readableBytes();
                if (readLimit == 0) {
                    return;
                }
                int toRead2 = Math.min(readLimit, this.maxChunkSize);
                if (toRead2 > this.chunkSize) {
                    toRead2 = (int)this.chunkSize;
                }
                final ByteBuf content2 = buffer.readRetainedSlice(toRead2);
                this.chunkSize -= toRead2;
                if (this.chunkSize == 0L) {
                    out.add(new DefaultLastHttpContent(content2, this.trailersFactory));
                    this.resetNow();
                }
                else {
                    out.add(new DefaultHttpContent(content2));
                }
                return;
            }
            case READ_CHUNK_SIZE: {
                try {
                    final ByteBuf line = this.lineParser.parse(buffer, HttpObjectDecoder.THROW_INVALID_CHUNK_EXTENSION);
                    if (line == null) {
                        return;
                    }
                    final int chunkSize = getChunkSize(line.array(), line.arrayOffset() + line.readerIndex(), line.readableBytes());
                    this.chunkSize = chunkSize;
                    if (chunkSize == 0) {
                        this.currentState = State.READ_CHUNK_FOOTER;
                        return;
                    }
                    this.currentState = State.READ_CHUNKED_CONTENT;
                }
                catch (final Exception e) {
                    out.add(this.invalidChunk(buffer, e));
                }
            }
            case READ_CHUNKED_CONTENT: {
                assert this.chunkSize <= 2147483647L;
                int toRead = Math.min((int)this.chunkSize, this.maxChunkSize);
                if (!this.allowPartialChunks && buffer.readableBytes() < toRead) {
                    return;
                }
                toRead = Math.min(toRead, buffer.readableBytes());
                if (toRead == 0) {
                    return;
                }
                final HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
                this.chunkSize -= toRead;
                out.add(chunk);
                if (this.chunkSize != 0L) {
                    return;
                }
                this.currentState = State.READ_CHUNK_DELIMITER;
            }
            case READ_CHUNK_DELIMITER: {
                if (buffer.readableBytes() >= 2) {
                    final int rIdx = buffer.readerIndex();
                    if (buffer.getByte(rIdx) == 13 && buffer.getByte(rIdx + 1) == 10) {
                        buffer.skipBytes(2);
                        this.currentState = State.READ_CHUNK_SIZE;
                    }
                    else {
                        out.add(this.invalidChunk(buffer, new InvalidChunkTerminationException()));
                    }
                }
                return;
            }
            case READ_CHUNK_FOOTER: {
                try {
                    final LastHttpContent trailer = this.readTrailingHeaders(buffer);
                    if (trailer == null) {
                        return;
                    }
                    out.add(trailer);
                    this.resetNow();
                }
                catch (final Exception e) {
                    out.add(this.invalidChunk(buffer, e));
                }
            }
            case BAD_MESSAGE: {
                buffer.skipBytes(buffer.readableBytes());
                break;
            }
            case UPGRADED: {
                final int readableBytes = buffer.readableBytes();
                if (readableBytes > 0) {
                    out.add(buffer.readBytes(readableBytes));
                    break;
                }
                break;
            }
        }
    }
    
    @Override
    protected void decodeLast(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) throws Exception {
        super.decodeLast(ctx, in, out);
        if (this.resetRequested.get()) {
            this.resetNow();
        }
        switch (this.currentState) {
            case READ_VARIABLE_LENGTH_CONTENT: {
                if (!this.chunked && !in.isReadable()) {
                    out.add(LastHttpContent.EMPTY_LAST_CONTENT);
                    this.resetNow();
                }
                return;
            }
            case READ_HEADER: {
                out.add(this.invalidMessage(this.message, Unpooled.EMPTY_BUFFER, new PrematureChannelClosureException("Connection closed before received headers")));
                this.resetNow();
                return;
            }
            case READ_CHUNK_SIZE:
            case READ_FIXED_LENGTH_CONTENT:
            case READ_CHUNKED_CONTENT:
            case READ_CHUNK_DELIMITER:
            case READ_CHUNK_FOOTER: {
                final boolean prematureClosure = this.isDecodingRequest() || this.chunked || this.contentLength > 0L;
                if (!prematureClosure) {
                    out.add(LastHttpContent.EMPTY_LAST_CONTENT);
                }
                this.resetNow();
                return;
            }
            case SKIP_CONTROL_CHARS:
            case READ_INITIAL:
            case BAD_MESSAGE:
            case UPGRADED: {
                return;
            }
            default: {
                throw new IllegalStateException("Unhandled state " + this.currentState);
            }
        }
    }
    
    @Override
    public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
        if (evt instanceof HttpExpectationFailedEvent) {
            switch (this.currentState) {
                case READ_CHUNK_SIZE:
                case READ_VARIABLE_LENGTH_CONTENT:
                case READ_FIXED_LENGTH_CONTENT: {
                    this.reset();
                    break;
                }
            }
        }
        super.userEventTriggered(ctx, evt);
    }
    
    private void addCurrentMessage(final List<Object> out) {
        final HttpMessage message = this.message;
        assert message != null;
        this.message = null;
        out.add(message);
    }
    
    protected boolean isContentAlwaysEmpty(final HttpMessage msg) {
        if (!(msg instanceof HttpResponse)) {
            return false;
        }
        final HttpResponse res = (HttpResponse)msg;
        final HttpResponseStatus status = res.status();
        final int code = status.code();
        final HttpStatusClass statusClass = status.codeClass();
        if (statusClass == HttpStatusClass.INFORMATIONAL) {
            return code != 101 || res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT) || !res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true);
        }
        switch (code) {
            case 204:
            case 304: {
                return true;
            }
            default: {
                return false;
            }
        }
    }
    
    protected boolean isSwitchingToNonHttp1Protocol(final HttpResponse msg) {
        if (msg.status().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
            return false;
        }
        final String newProtocol = msg.headers().get(HttpHeaderNames.UPGRADE);
        return newProtocol == null || (!newProtocol.contains(HttpVersion.HTTP_1_0.text()) && !newProtocol.contains(HttpVersion.HTTP_1_1.text()));
    }
    
    public void reset() {
        this.resetRequested.lazySet(true);
    }
    
    private void resetNow() {
        this.message = null;
        this.name = null;
        this.value = null;
        this.contentLength = Long.MIN_VALUE;
        this.chunked = false;
        this.lineParser.reset();
        this.headerParser.reset();
        this.trailer = null;
        if (this.isSwitchingToNonHttp1Protocol) {
            this.isSwitchingToNonHttp1Protocol = false;
            this.currentState = State.UPGRADED;
            return;
        }
        this.resetRequested.lazySet(false);
        this.currentState = State.SKIP_CONTROL_CHARS;
    }
    
    private HttpMessage invalidMessage(HttpMessage current, final ByteBuf in, final Exception cause) {
        this.currentState = State.BAD_MESSAGE;
        this.message = null;
        this.trailer = null;
        in.skipBytes(in.readableBytes());
        if (current == null) {
            current = this.createInvalidMessage();
        }
        current.setDecoderResult(DecoderResult.failure(cause));
        return current;
    }
    
    private HttpContent invalidChunk(final ByteBuf in, final Exception cause) {
        this.currentState = State.BAD_MESSAGE;
        this.message = null;
        this.trailer = null;
        in.skipBytes(in.readableBytes());
        final HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
        chunk.setDecoderResult(DecoderResult.failure(cause));
        return chunk;
    }
    
    private State readHeaders(final ByteBuf buffer) {
        final HttpMessage message = this.message;
        final HttpHeaders headers = message.headers();
        final HeaderParser headerParser = this.headerParser;
        ByteBuf line = headerParser.parse(buffer, this.defaultStrictCRLFCheck);
        if (line == null) {
            return null;
        }
        for (int lineLength = line.readableBytes(); lineLength > 0; lineLength = line.readableBytes()) {
            final byte[] lineContent = line.array();
            final int startLine = line.arrayOffset() + line.readerIndex();
            final byte firstChar = lineContent[startLine];
            if (this.name != null && (firstChar == 32 || firstChar == 9)) {
                final String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
                final String valueStr = this.value;
                this.value = valueStr + ' ' + trimmedLine;
            }
            else {
                if (this.name != null) {
                    headers.add(this.name, this.value);
                }
                this.splitHeader(lineContent, startLine, lineLength);
            }
            line = headerParser.parse(buffer, this.defaultStrictCRLFCheck);
            if (line == null) {
                return null;
            }
        }
        if (this.name != null) {
            headers.add(this.name, this.value);
        }
        this.name = null;
        this.value = null;
        final HttpMessageDecoderResult decoderResult = new HttpMessageDecoderResult(this.lineParser.size, headerParser.size);
        message.setDecoderResult(decoderResult);
        final List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
        if (!contentLengthFields.isEmpty()) {
            final HttpVersion version = message.protocolVersion();
            final boolean isHttp10OrEarlier = version.majorVersion() < 1 || (version.majorVersion() == 1 && version.minorVersion() == 0);
            this.contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields, isHttp10OrEarlier, this.allowDuplicateContentLengths);
            if (this.contentLength != -1L) {
                final String lengthValue = contentLengthFields.get(0).trim();
                if (contentLengthFields.size() > 1 || !lengthValue.equals(Long.toString(this.contentLength))) {
                    headers.set(HttpHeaderNames.CONTENT_LENGTH, this.contentLength);
                }
            }
        }
        else {
            this.contentLength = HttpUtil.getWebSocketContentLength(message);
        }
        if (!this.isDecodingRequest() && message instanceof HttpResponse) {
            final HttpResponse res = (HttpResponse)message;
            this.isSwitchingToNonHttp1Protocol = this.isSwitchingToNonHttp1Protocol(res);
        }
        if (this.isContentAlwaysEmpty(message)) {
            HttpUtil.setTransferEncodingChunked(message, false);
            return State.SKIP_CONTROL_CHARS;
        }
        if (HttpUtil.isTransferEncodingChunked(message)) {
            this.chunked = true;
            if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
                this.handleTransferEncodingChunkedWithContentLength(message);
            }
            return State.READ_CHUNK_SIZE;
        }
        if (this.contentLength >= 0L) {
            return State.READ_FIXED_LENGTH_CONTENT;
        }
        return State.READ_VARIABLE_LENGTH_CONTENT;
    }
    
    protected void handleTransferEncodingChunkedWithContentLength(final HttpMessage message) {
        message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
        this.contentLength = Long.MIN_VALUE;
    }
    
    private LastHttpContent readTrailingHeaders(final ByteBuf buffer) {
        final HeaderParser headerParser = this.headerParser;
        ByteBuf line = headerParser.parse(buffer, this.defaultStrictCRLFCheck);
        if (line == null) {
            return null;
        }
        LastHttpContent trailer = this.trailer;
        int lineLength = line.readableBytes();
        if (lineLength == 0 && trailer == null) {
            return LastHttpContent.EMPTY_LAST_CONTENT;
        }
        CharSequence lastHeader = null;
        if (trailer == null) {
            final DefaultLastHttpContent trailer2 = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, this.trailersFactory);
            this.trailer = trailer2;
            trailer = trailer2;
        }
        while (lineLength > 0) {
            final byte[] lineContent = line.array();
            final int startLine = line.arrayOffset() + line.readerIndex();
            final byte firstChar = lineContent[startLine];
            if (lastHeader != null && (firstChar == 32 || firstChar == 9)) {
                final List<String> current = trailer.trailingHeaders().getAll(lastHeader);
                if (!current.isEmpty()) {
                    final int lastPos = current.size() - 1;
                    final String lineTrimmed = langAsciiString(lineContent, startLine, line.readableBytes()).trim();
                    final String currentLastPos = current.get(lastPos);
                    current.set(lastPos, currentLastPos + lineTrimmed);
                }
            }
            else {
                this.splitHeader(lineContent, startLine, lineLength);
                final AsciiString headerName = this.name;
                if (!HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(headerName) && !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(headerName) && !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(headerName)) {
                    trailer.trailingHeaders().add(headerName, this.value);
                }
                lastHeader = this.name;
                this.name = null;
                this.value = null;
            }
            line = headerParser.parse(buffer, this.defaultStrictCRLFCheck);
            if (line == null) {
                return null;
            }
            lineLength = line.readableBytes();
        }
        this.trailer = null;
        return trailer;
    }
    
    protected abstract boolean isDecodingRequest();
    
    protected abstract HttpMessage createMessage(final String[] p0) throws Exception;
    
    protected abstract HttpMessage createInvalidMessage();
    
    private static int skipWhiteSpaces(final byte[] hex, final int start, final int length) {
        for (int i = 0; i < length; ++i) {
            if (!isWhitespace(hex[start + i])) {
                return i;
            }
        }
        return length;
    }
    
    private static int getChunkSize(final byte[] hex, int start, int length) {
        final int skipped = skipWhiteSpaces(hex, start, length);
        if (skipped == length) {
            throw new NumberFormatException();
        }
        start += skipped;
        length -= skipped;
        int result = 0;
        int i = 0;
        while (i < length) {
            final int digit = StringUtil.decodeHexNibble(hex[start + i]);
            if (digit == -1) {
                final byte b = hex[start + i];
                if (b != 59 && !isControlOrWhitespaceAsciiChar(b)) {
                    throw new NumberFormatException("Invalid character in chunk size");
                }
                if (i == 0) {
                    throw new NumberFormatException("Empty chunk size");
                }
                return result;
            }
            else {
                result *= 16;
                result += digit;
                if (result < 0) {
                    throw new NumberFormatException("Chunk size overflow: " + result);
                }
                ++i;
            }
        }
        return result;
    }
    
    private String[] splitInitialLine(final ByteBuf asciiBuffer) {
        final byte[] asciiBytes = asciiBuffer.array();
        final int arrayOffset = asciiBuffer.arrayOffset();
        final int startContent = arrayOffset + asciiBuffer.readerIndex();
        final int end = startContent + asciiBuffer.readableBytes();
        final byte lastByte = asciiBytes[end - 1];
        if (isControlOrWhitespaceAsciiChar(lastByte) && (this.isDecodingRequest() || !isOWS(lastByte))) {
            throw new IllegalArgumentException("Illegal character in request line: 0x" + Integer.toHexString(lastByte));
        }
        final int aStart = findNonSPLenient(asciiBytes, startContent, end);
        final int aEnd = findSPLenient(asciiBytes, aStart, end);
        final int bStart = findNonSPLenient(asciiBytes, aEnd, end);
        final int bEnd = findSPLenient(asciiBytes, bStart, end);
        final int cStart = findNonSPLenient(asciiBytes, bEnd, end);
        final int cEnd = findEndOfString(asciiBytes, Math.max(cStart - 1, startContent), end);
        return new String[] { this.splitFirstWordInitialLine(asciiBytes, aStart, aEnd - aStart), this.splitSecondWordInitialLine(asciiBytes, bStart, bEnd - bStart), (cStart < cEnd) ? this.splitThirdWordInitialLine(asciiBytes, cStart, cEnd - cStart) : "" };
    }
    
    protected String splitFirstWordInitialLine(final byte[] asciiContent, final int start, final int length) {
        return langAsciiString(asciiContent, start, length);
    }
    
    protected String splitSecondWordInitialLine(final byte[] asciiContent, final int start, final int length) {
        return langAsciiString(asciiContent, start, length);
    }
    
    protected String splitThirdWordInitialLine(final byte[] asciiContent, final int start, final int length) {
        return langAsciiString(asciiContent, start, length);
    }
    
    private static String langAsciiString(final byte[] asciiContent, final int start, final int length) {
        if (length == 0) {
            return "";
        }
        if (start != 0) {
            return new String(asciiContent, 0, start, length);
        }
        if (length == asciiContent.length) {
            return new String(asciiContent, 0, 0, asciiContent.length);
        }
        return new String(asciiContent, 0, 0, length);
    }
    
    private void splitHeader(final byte[] line, final int start, final int length) {
        final int end = start + length;
        final int nameStart = start;
        final boolean isDecodingRequest = this.isDecodingRequest();
        int nameEnd;
        for (nameEnd = nameStart; nameEnd < end; ++nameEnd) {
            final byte ch = line[nameEnd];
            if (ch == 58) {
                break;
            }
            if (!isDecodingRequest && isOWS(ch)) {
                break;
            }
        }
        if (nameEnd == end) {
            throw new IllegalArgumentException("No colon found");
        }
        int colonEnd;
        for (colonEnd = nameEnd; colonEnd < end; ++colonEnd) {
            if (line[colonEnd] == 58) {
                ++colonEnd;
                break;
            }
        }
        this.name = this.splitHeaderName(line, nameStart, nameEnd - nameStart);
        final int valueStart = findNonWhitespace(line, colonEnd, end);
        if (valueStart == end) {
            this.value = "";
        }
        else {
            final int valueEnd = findEndOfString(line, start, end);
            this.value = langAsciiString(line, valueStart, valueEnd - valueStart);
        }
    }
    
    protected AsciiString splitHeaderName(final byte[] sb, final int start, final int length) {
        return new AsciiString(sb, start, length, true);
    }
    
    private static int findNonSPLenient(final byte[] sb, final int offset, final int end) {
        int result = offset;
        while (result < end) {
            final byte c = sb[result];
            if (isSPLenient(c)) {
                ++result;
            }
            else {
                if (isWhitespace(c)) {
                    throw new IllegalArgumentException("Invalid separator");
                }
                return result;
            }
        }
        return end;
    }
    
    private static int findSPLenient(final byte[] sb, final int offset, final int end) {
        for (int result = offset; result < end; ++result) {
            if (isSPLenient(sb[result])) {
                return result;
            }
        }
        return end;
    }
    
    private static boolean isSPLenient(final byte c) {
        return HttpObjectDecoder.SP_LENIENT_BYTES[c + 128];
    }
    
    private static boolean isWhitespace(final byte b) {
        return HttpObjectDecoder.LATIN_WHITESPACE[b + 128];
    }
    
    private static int findNonWhitespace(final byte[] sb, final int offset, final int end) {
        for (int result = offset; result < end; ++result) {
            final byte c = sb[result];
            if (!isWhitespace(c)) {
                return result;
            }
            if (!isOWS(c)) {
                throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed, but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
            }
        }
        return end;
    }
    
    private static int findEndOfString(final byte[] sb, final int start, final int end) {
        for (int result = end - 1; result > start; --result) {
            if (!isOWS(sb[result])) {
                return result + 1;
            }
        }
        return 0;
    }
    
    private static boolean isOWS(final byte ch) {
        return ch == 32 || ch == 9;
    }
    
    private static boolean isControlOrWhitespaceAsciiChar(final byte b) {
        return HttpObjectDecoder.ISO_CONTROL_OR_WHITESPACE[128 + b];
    }
    
    static {
        DEFAULT_STRICT_LINE_PARSING = SystemPropertyUtil.getBoolean("io.netty.handler.codec.http.defaultStrictLineParsing", true);
        THROW_INVALID_CHUNK_EXTENSION = new Runnable() {
            @Override
            public void run() {
                throw new InvalidChunkExtensionException();
            }
        };
        THROW_INVALID_LINE_SEPARATOR = new Runnable() {
            @Override
            public void run() {
                throw new InvalidLineSeparatorException();
            }
        };
        (SP_LENIENT_BYTES = new boolean[256])[160] = true;
        HttpObjectDecoder.SP_LENIENT_BYTES[137] = true;
        HttpObjectDecoder.SP_LENIENT_BYTES[139] = true;
        HttpObjectDecoder.SP_LENIENT_BYTES[140] = true;
        HttpObjectDecoder.SP_LENIENT_BYTES[141] = true;
        LATIN_WHITESPACE = new boolean[256];
        for (byte b = -128; b < 127; ++b) {
            HttpObjectDecoder.LATIN_WHITESPACE[128 + b] = Character.isWhitespace(b);
        }
        ISO_CONTROL_OR_WHITESPACE = new boolean[256];
        for (byte b = -128; b < 127; ++b) {
            HttpObjectDecoder.ISO_CONTROL_OR_WHITESPACE[128 + b] = (Character.isISOControl(b) || isWhitespace(b));
        }
        SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
            @Override
            public boolean process(final byte value) {
                return HttpObjectDecoder.ISO_CONTROL_OR_WHITESPACE[128 + value];
            }
        };
    }
    
    private enum State
    {
        SKIP_CONTROL_CHARS, 
        READ_INITIAL, 
        READ_HEADER, 
        READ_VARIABLE_LENGTH_CONTENT, 
        READ_FIXED_LENGTH_CONTENT, 
        READ_CHUNK_SIZE, 
        READ_CHUNKED_CONTENT, 
        READ_CHUNK_DELIMITER, 
        READ_CHUNK_FOOTER, 
        BAD_MESSAGE, 
        UPGRADED;
    }
    
    private static class HeaderParser
    {
        protected final ByteBuf seq;
        protected final int maxLength;
        int size;
        
        HeaderParser(final ByteBuf seq, final int maxLength) {
            this.seq = seq;
            this.maxLength = maxLength;
        }
        
        public ByteBuf parse(final ByteBuf buffer, final Runnable strictCRLFCheck) {
            final int readableBytes = buffer.readableBytes();
            final int readerIndex = buffer.readerIndex();
            final int maxBodySize = this.maxLength - this.size;
            assert maxBodySize >= 0;
            final long maxBodySizeWithCRLF = maxBodySize + 2L;
            final int toProcess = (int)Math.min(maxBodySizeWithCRLF, readableBytes);
            final int toIndexExclusive = readerIndex + toProcess;
            assert toIndexExclusive >= readerIndex;
            final int indexOfLf = buffer.indexOf(readerIndex, toIndexExclusive, (byte)10);
            if (indexOfLf == -1) {
                if (readableBytes > maxBodySize) {
                    throw this.newException(this.maxLength);
                }
                return null;
            }
            else {
                int endOfSeqIncluded;
                if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == 13) {
                    endOfSeqIncluded = indexOfLf - 1;
                }
                else {
                    if (strictCRLFCheck != null) {
                        strictCRLFCheck.run();
                    }
                    endOfSeqIncluded = indexOfLf;
                }
                final int newSize = endOfSeqIncluded - readerIndex;
                if (newSize == 0) {
                    this.seq.clear();
                    buffer.readerIndex(indexOfLf + 1);
                    return this.seq;
                }
                final int size = this.size + newSize;
                if (size > this.maxLength) {
                    throw this.newException(this.maxLength);
                }
                this.size = size;
                this.seq.clear();
                this.seq.writeBytes(buffer, readerIndex, newSize);
                buffer.readerIndex(indexOfLf + 1);
                return this.seq;
            }
        }
        
        public void reset() {
            this.size = 0;
        }
        
        protected TooLongFrameException newException(final int maxLength) {
            return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
        }
    }
    
    private final class LineParser extends HeaderParser
    {
        LineParser(final ByteBuf seq, final int maxLength) {
            super(seq, maxLength);
        }
        
        @Override
        public ByteBuf parse(final ByteBuf buffer, final Runnable strictCRLFCheck) {
            this.reset();
            final int readableBytes = buffer.readableBytes();
            if (readableBytes == 0) {
                return null;
            }
            if (HttpObjectDecoder.this.currentState == State.SKIP_CONTROL_CHARS && this.skipControlChars(buffer, readableBytes, buffer.readerIndex())) {
                return null;
            }
            return super.parse(buffer, strictCRLFCheck);
        }
        
        private boolean skipControlChars(final ByteBuf buffer, final int readableBytes, final int readerIndex) {
            assert HttpObjectDecoder.this.currentState == State.SKIP_CONTROL_CHARS;
            final int maxToSkip = Math.min(this.maxLength, readableBytes);
            final int firstNonControlIndex = buffer.forEachByte(readerIndex, maxToSkip, HttpObjectDecoder.SKIP_CONTROL_CHARS_BYTES);
            if (firstNonControlIndex != -1) {
                buffer.readerIndex(firstNonControlIndex);
                HttpObjectDecoder.this.currentState = State.READ_INITIAL;
                return false;
            }
            buffer.skipBytes(maxToSkip);
            if (readableBytes > this.maxLength) {
                throw this.newException(this.maxLength);
            }
            return true;
        }
        
        @Override
        protected TooLongFrameException newException(final int maxLength) {
            return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
        }
    }
}
