Repository: netty/netty
File: codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java (Java)

Code before:
    private static final BitSet VALID_HTTP_METHOD_OCTETS = validHttpMethodOctets();

    private static BitSet validHttpMethodOctets() {
        BitSet bits = new BitSet();
        // https://datatracker.ietf.org/doc/html/rfc9110#name-tokens
        // token          = 1*tchar
        // tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
        //                 / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
        //                 / DIGIT / ALPHA
        //                 ; any VCHAR, except delimiters
        //Many HTTP field values are defined using common syntax components,
        // separated by whitespace or specific delimiting characters. Delimiters
        // are chosen from the set of US-ASCII visual characters not allowed in
        // a token (DQUOTE and "(),/:;<=>?@[\]{}").
        //VCHAR          =  %x21-7E
        bits.set(0x21, 0x7f);
        int[] delimiters = { '(', ')', ',', '/', ':', ';', '<', '=', '>', '?', '@', '"', '[', '\\', ']', '{', '}' };
        for (int delimiter : delimiters) {
            bits.set(delimiter, false);
        }
        return bits;
    }

    @Override
    protected String splitFirstWordInitialLine(final byte[] sb, final int start, final int length) {
        int firstInvalidOctet = firstInvalidMethodOctet(sb, start, length);
        if (firstInvalidOctet != -1) {
            throw new IllegalArgumentException(
                    "HTTP Method contains an invalid character: " + sb[firstInvalidOctet]);
        }
        if (length == 3) {
            if (isGetMethod(sb, start)) {
                return HttpMethod.GET.name();
            }
        } else if (length == 4) {
            if (isPostMethod(sb, start)) {
                return HttpMethod.POST.name();
            }
        }
        return super.splitFirstWordInitialLine(sb, start, length);
    }

    @Override
    protected String splitThirdWordInitialLine(final byte[] sb, final int start, final int length) {
        if (length == 8) {
            final long maybeHttp1_x = sb[start] |
                    sb[start + 1] << 8 |
                    sb[start + 2] << 16 |
                    sb[start + 3] << 24 |
                    (long) sb[start + 4] << 32 |
                    (long) sb[start + 5] << 40 |

Review comment (refactor):
Another approach could be to move this validation to `HttpMethod` constructor. It already has some validation. If it's not sufficient it could be improved. Advantages:
1. This will help to avoid validation here for the fast path of recognizing `GET/POST` methods as well as `methodMap` lookup inside `HttpMethod.valueOf(...)`.
2. It will cover not only incoming but also outgoing http methods made by netty clients.
3. It will allow you to reuse existing code for token validation from `HttpHeaderValidationUtil.validateToken`.

Code after:
    }

    @Override
    protected String splitThirdWordInitialLine(final byte[] sb, final int start, final int length) {
        if (length == 8) {
            final long maybeHttp1_x = sb[start] |
                    sb[start + 1] << 8 |
                    sb[start + 2] << 16 |
                    sb[start + 3] << 24 |
                    (long) sb[start + 4] << 32 |
                    (long) sb[start + 5] << 40 |
                    (long) sb[start + 6] << 48 |
                    (long) sb[start + 7] << 56;
            if (maybeHttp1_x == HTTP_1_1_AS_LONG) {
                return HttpVersion.HTTP_1_1_STRING;
            } else if (maybeHttp1_x == HTTP_1_0_AS_LONG) {
                return HttpVersion.HTTP_1_0_STRING;
            }
        }
        return super.splitThirdWordInitialLine(sb, start, length);
    }

    @Override
    protected HttpMessage createInvalidMessage() {
        return new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request",
                Unpooled.buffer(0), headersFactory, trailersFactory);
    }

    @Override
    protected boolean isDecodingRequest() {
        return true;
    }

    @Override
    protected boolean isContentAlwaysEmpty(final HttpMessage msg) {
        // fast-path to save expensive O(n) checks; users can override createMessage
        // and extends DefaultHttpRequest making implementing HttpResponse:
        // this is why we cannot use instanceof DefaultHttpRequest here :(
        if (msg.getClass() == DefaultHttpRequest.class) {
            return false;
        }
        return super.isContentAlwaysEmpty(msg);
    }
}