HttpParser.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

final class HttpParser {
    HttpParser() {
        _type = Type.Unknown;
        _versionMajor = 0;
        _versionMinor = 0;
        _status = 0;
        _state = State.Init;
    }

    private enum Type {
        Unknown,
        Request,
        Response
    }

    int isCompleteMessage(ByteBuffer buf, int begin, int end) {
        int p = begin;

        //
        // Skip any leading CR-LF characters.
        //
        while (p < end) {
            byte ch = buf.get(p);
            if (ch != (byte) '\r' && ch != (byte) '\n') {
                break;
            }
            ++p;
        }

        //
        // Look for adjacent CR-LF/CR-LF or LF/LF.
        //
        boolean seenFirst = false;
        while (p < end) {
            byte ch = buf.get(p++);
            if (ch == (byte) '\n') {
                if (seenFirst) {
                    return p;
                } else {
                    seenFirst = true;
                }
            } else if (ch != (byte) '\r') {
                seenFirst = false;
            }
        }

        return -1;
    }

    boolean parse(ByteBuffer buf, int begin, int end) {
        int p = begin;
        int start = 0;
        final char CR = '\r';
        final char LF = '\n';

        if (_state == State.Complete) {
            _state = State.Init;
        }

        while (p != end && _state != State.Complete) {
            char c = (char) buf.get(p);

            switch (_state) {
                case Init: {
                    _method.setLength(0);
                    _uri.setLength(0);
                    _versionMajor = -1;
                    _versionMinor = -1;
                    _status = -1;
                    _reason = "";
                    _headers.clear();
                    _state = State.Type;
                    continue;
                }
                case Type: {
                    if (c == CR || c == LF) {
                        break;
                    } else if (c == 'H') {
                        // Could be the start of "HTTP/1.1" or "HEAD".
                        _state = State.TypeCheck;
                        break;
                    } else {
                        _state = State.Request;
                        continue;
                    }
                }
                case TypeCheck: {
                    if (c == 'T') {
                        // Continuing "H_T_TP/1.1"
                        _state = State.Response;
                    } else if (c == 'E') {
                        // Expecting "HEAD"
                        _state = State.Request;
                        _method.append('H');
                        _method.append('E');
                    } else {
                        throw new WebSocketException("malformed request or response");
                    }
                    break;
                }
                case Request: {
                    _type = Type.Request;
                    _state = State.RequestMethod;
                    continue;
                }
                case RequestMethod: {
                    if (c == ' ' || c == CR || c == LF) {
                        _state = State.RequestMethodSP;
                        continue;
                    }
                    _method.append(c);
                    break;
                }
                case RequestMethodSP: {
                    if (c == ' ') {
                        break;
                    } else if (c == CR || c == LF) {
                        throw new WebSocketException("malformed request");
                    }
                    _state = State.RequestURI;
                    continue;
                }
                case RequestURI: {
                    if (c == ' ' || c == CR || c == LF) {
                        _state = State.RequestURISP;
                        continue;
                    }
                    _uri.append(c);
                    break;
                }
                case RequestURISP: {
                    if (c == ' ') {
                        break;
                    } else if (c == CR || c == LF) {
                        throw new WebSocketException("malformed request");
                    }
                    _state = State.Version;
                    continue;
                }
                case RequestLF: {
                    if (c != LF) {
                        throw new WebSocketException("malformed request");
                    }
                    _state = State.HeaderFieldStart;
                    break;
                }
                case HeaderFieldStart: {
                    // We've already seen a LF to reach this state.
                    //
                    // Another CR or LF indicates the end of the header fields.
                    if (c == CR) {
                        _state = State.HeaderFieldEndLF;
                        break;
                    } else if (c == LF) {
                        _state = State.Complete;
                        break;
                    } else if (c == ' ') {
                        // Could be a continuation line.
                        _state = State.HeaderFieldContStart;
                        break;
                    }

                    _state = State.HeaderFieldNameStart;
                    continue;
                }
                case HeaderFieldContStart: {
                    if (c == ' ') {
                        break;
                    }

                    _state = State.HeaderFieldCont;
                    start = p;
                    continue;
                }
                case HeaderFieldCont: {
                    if (c == CR || c == LF) {
                        if (p > start) {
                            if (_headerName.isEmpty()) {
                                throw new WebSocketException("malformed header");
                            }
                            String s = _headers.get(_headerName);
                            assert (s != null);
                            StringBuffer newValue = new StringBuffer(s);
                            newValue.append(' ');
                            for (int i = start; i < p; i++) {
                                newValue.append((char) buf.get(i));
                            }
                            _headers.put(_headerName, newValue.toString());
                            _state = c == CR ? State.HeaderFieldLF : State.HeaderFieldStart;
                        } else {
                            //
                            // Could mark the end of the header fields.
                            //
                            _state = c == CR ? State.HeaderFieldEndLF : State.Complete;
                        }
                    }

                    break;
                }
                case HeaderFieldNameStart: {
                    assert (c != ' ');
                    start = p;
                    _headerName = "";
                    _state = State.HeaderFieldName;
                    continue;
                }
                case HeaderFieldName: {
                    if (c == ' ' || c == ':') {
                        _state = State.HeaderFieldNameEnd;
                        continue;
                    } else if (c == CR || c == LF) {
                        throw new WebSocketException("malformed header");
                    }
                    break;
                }
                case HeaderFieldNameEnd: {
                    if (_headerName.isEmpty()) {
                        StringBuffer str = new StringBuffer();
                        for (int i = start; i < p; i++) {
                            str.append((char) buf.get(i));
                        }
                        _headerName = str.toString().toLowerCase();
                        //
                        // Add a placeholder entry if necessary.
                        //
                        if (!_headers.containsKey(_headerName)) {
                            _headers.put(_headerName, "");
                            _headerNames.put(_headerName, str.toString());
                        }
                    }

                    if (c == ' ') {
                        break;
                    } else if (c != ':' || p == start) {
                        throw new WebSocketException("malformed header");
                    }

                    _state = State.HeaderFieldValueStart;
                    break;
                }
                case HeaderFieldValueStart: {
                    if (c == ' ') {
                        break;
                    }

                    //
                    // Check for "Name:\r\n"
                    //
                    if (c == CR) {
                        _state = State.HeaderFieldLF;
                        break;
                    } else if (c == LF) {
                        _state = State.HeaderFieldStart;
                        break;
                    }

                    start = p;
                    _state = State.HeaderFieldValue;
                    continue;
                }
                case HeaderFieldValue: {
                    if (c == CR || c == LF) {
                        _state = State.HeaderFieldValueEnd;
                        continue;
                    }
                    break;
                }
                case HeaderFieldValueEnd: {
                    assert (c == CR || c == LF);
                    if (p > start) {
                        StringBuffer str = new StringBuffer();
                        for (int i = start; i < p; i++) {
                            str.append((char) buf.get(i));
                        }
                        String s = _headers.get(_headerName);
                        if (s == null || s.isEmpty()) {
                            _headers.put(_headerName, str.toString());
                        } else {
                            _headers.put(_headerName, s + ", " + str.toString());
                        }
                    }

                    if (c == CR) {
                        _state = State.HeaderFieldLF;
                    } else {
                        _state = State.HeaderFieldStart;
                    }
                    break;
                }
                case HeaderFieldLF: {
                    if (c != LF) {
                        throw new WebSocketException("malformed header");
                    }
                    _state = State.HeaderFieldStart;
                    break;
                }
                case HeaderFieldEndLF: {
                    if (c != LF) {
                        throw new WebSocketException("malformed header");
                    }
                    _state = State.Complete;
                    break;
                }
                case Version: {
                    if (c != 'H') {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionH;
                    break;
                }
                case VersionH: {
                    if (c != 'T') {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionHT;
                    break;
                }
                case VersionHT: {
                    if (c != 'T') {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionHTT;
                    break;
                }
                case VersionHTT: {
                    if (c != 'P') {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionHTTP;
                    break;
                }
                case VersionHTTP: {
                    if (c != '/') {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionMajor;
                    break;
                }
                case VersionMajor: {
                    if (c == '.') {
                        if (_versionMajor == -1) {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.VersionMinor;
                        break;
                    } else if (c < '0' || c > '9') {
                        throw new WebSocketException("malformed version");
                    }
                    if (_versionMajor == -1) {
                        _versionMajor = 0;
                    }
                    _versionMajor *= 10;
                    _versionMajor += c - '0';
                    break;
                }
                case VersionMinor: {
                    if (c == CR) {
                        if (_versionMinor == -1 || _type != Type.Request) {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.RequestLF;
                        break;
                    } else if (c == LF) {
                        if (_versionMinor == -1 || _type != Type.Request) {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.HeaderFieldStart;
                        break;
                    } else if (c == ' ') {
                        if (_versionMinor == -1 || _type != Type.Response) {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.ResponseVersionSP;
                        break;
                    } else if (c < '0' || c > '9') {
                        throw new WebSocketException("malformed version");
                    }
                    if (_versionMinor == -1) {
                        _versionMinor = 0;
                    }
                    _versionMinor *= 10;
                    _versionMinor += c - '0';
                    break;
                }
                case Response: {
                    _type = Type.Response;
                    _state = State.VersionHT;
                    continue;
                }
                case ResponseVersionSP: {
                    if (c == ' ') {
                        break;
                    }

                    _state = State.ResponseStatus;
                    continue;
                }
                case ResponseStatus: {
                    // TODO: Is reason string optional?
                    if (c == CR) {
                        if (_status == -1) {
                            throw new WebSocketException("malformed response status");
                        }
                        _state = State.ResponseLF;
                        break;
                    } else if (c == LF) {
                        if (_status == -1) {
                            throw new WebSocketException("malformed response status");
                        }
                        _state = State.HeaderFieldStart;
                        break;
                    } else if (c == ' ') {
                        if (_status == -1) {
                            throw new WebSocketException("malformed response status");
                        }
                        _state = State.ResponseReasonStart;
                        break;
                    } else if (c < '0' || c > '9') {
                        throw new WebSocketException("malformed response status");
                    }
                    if (_status == -1) {
                        _status = 0;
                    }
                    _status *= 10;
                    _status += c - '0';
                    break;
                }
                case ResponseReasonStart: {
                    //
                    // Skip leading spaces.
                    //
                    if (c == ' ') {
                        break;
                    }

                    _state = State.ResponseReason;
                    start = p;
                    continue;
                }
                case ResponseReason: {
                    if (c == CR || c == LF) {
                        if (p > start) {
                            StringBuffer str = new StringBuffer();
                            for (int i = start; i < p; i++) {
                                str.append((char) buf.get(i));
                            }
                            _reason = str.toString();
                        }
                        _state = c == CR ? State.ResponseLF : State.HeaderFieldStart;
                    }

                    break;
                }
                case ResponseLF: {
                    if (c != LF) {
                        throw new WebSocketException("malformed status line");
                    }
                    _state = State.HeaderFieldStart;
                    break;
                }
                case Complete: {
                    assert false; // Shouldn't reach
                }
            }

            ++p;
        }

        return _state == State.Complete;
    }

    String uri() {
        assert (_type == Type.Request);
        return _uri.toString();
    }

    int versionMajor() {
        return _versionMajor;
    }

    int versionMinor() {
        return _versionMinor;
    }

    int status() {
        return _status;
    }

    String reason() {
        return _reason;
    }

    String getHeader(String name, boolean toLower) {
        String s = _headers.get(name.toLowerCase());
        if (s != null) {
            return toLower ? s.trim().toLowerCase() : s.trim();
        }

        return null;
    }

    Map<String, String> getHeaders() {
        Map<String, String> headers = new HashMap<>();
        for (Map.Entry<String, String> entry : _headers.entrySet()) {
            headers.put(
                _headerNames.get(entry.getKey()),
                entry.getValue().trim()); // Return original header name.
        }
        return headers;
    }

    private Type _type;

    private final StringBuffer _method = new StringBuffer();
    private final StringBuffer _uri = new StringBuffer();

    private final Map<String, String> _headers = new HashMap<>();
    private final Map<String, String> _headerNames = new HashMap<>();
    private String _headerName = "";

    private int _versionMajor;
    private int _versionMinor;

    private int _status;
    private String _reason;

    private enum State {
        Init,
        Type,
        TypeCheck,
        Request,
        RequestMethod,
        RequestMethodSP,
        RequestURI,
        RequestURISP,
        RequestLF,
        HeaderFieldStart,
        HeaderFieldContStart,
        HeaderFieldCont,
        HeaderFieldNameStart,
        HeaderFieldName,
        HeaderFieldNameEnd,
        HeaderFieldValueStart,
        HeaderFieldValue,
        HeaderFieldValueEnd,
        HeaderFieldLF,
        HeaderFieldEndLF,
        Version,
        VersionH,
        VersionHT,
        VersionHTT,
        VersionHTTP,
        VersionMajor,
        VersionMinor,
        Response,
        ResponseVersionSP,
        ResponseStatus,
        ResponseReasonStart,
        ResponseReason,
        ResponseLF,
        Complete
    }

    private State _state;
}