ParserUtils.java

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2025 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.undertow.util;

import java.nio.charset.StandardCharsets;

/**
 * @author <a href="mailto:opalka.richard@gmail.com">Richard Opalka</a>
 */
public final class ParserUtils {

    // char constants
    public static final byte AMPERSAND = '&';
    public static final byte ASTERISK = '*';
    public static final byte COLON = ':';
    public static final byte COMMA = ',';
    public static final byte CARRIAGE_RETURN = '\r';
    public static final byte DOT = '.';
    public static final byte EQUALS = '=';
    public static final byte LEFT_SQUARE_BRACKET = '[';
    public static final byte LINE_FEED = '\n';
    public static final byte PERCENT = '%';
    public static final byte PLUS  = '+';
    public static final byte QUESTION = '?';
    public static final byte RIGHT_SQUARE_BRACKET = ']';
    public static final byte SEMICOLON = ';';
    public static final byte SLASH = '/';
    public static final byte SPACE = ' ';

    // masks and utils
    private static final int MAXIMUM_REQUEST_METHOD_LENGTH = 1 << 5;
    private static final int FIRST_64_MASK = 0b1100_0000;
    private static final int FIRST_128_MASK = 0b1000_0000;
    private static final long DECIMAL_DIGITS_LOWER = 0b00000011_11111111_00000000_00000000_00000000_00000000_00000000_00000000L;
    private static final long IPV4_LOWER = 0b00000011_11111111_01000000_00000000_00000000_00000000_00000000_00000000L;
    private static final long IPV6_LOWER = 0b00000111_11111111_00000000_00000000_00000000_00000000_00000000_00000000L;
    private static final long DNS_LOWER = 0b00101011_11111111_01111111_11010010_00000000_00000000_00000000_00000000L;
    private static final long DNS_UPPER = 0b01000111_11111111_11111111_11111110_10000111_11111111_11111111_11111110L;
    private static final long HEXADECIMAL_DIGITS_UPPER = 0b00000000_00000000_00000000_01111110_00000000_00000000_00000000_01111110L;
    private static final long PATH_SEGMENT_LOWER = 0b00101111_11111111_01111111_11110010_00000000_00000000_00000000_00000000L;
    private static final long PATH_SEGMENT_UPPER = 0b01000111_11111111_11111111_11111110_10000111_11111111_11111111_11111111L;
    private static final long REQUEST_TARGET_LOWER = 0b10101111_11111111_11111111_11110010_00000000_00000000_00000000_00000000L;
    private static final long REQUEST_TARGET_UPPER = 0b01000111_11111111_11111111_11111110_10101111_11111111_11111111_11111111L;
    private static final long SCHEME_LOWER = 0b00000011_11111111_01101000_00000000_00000000_00000000_00000000_00000000L;
    private static final long SCHEME_UPPER = 0b00000111_11111111_11111111_11111110_00000111_11111111_11111111_11111110L;
    private static final long SPACE_OR_TAB_LOWER = 0b00000000_00000000_00000000_00000001_00000000_00000000_00000010_00000000L;
    private static final long TOKEN_LOWER = 0b00000011_11111111_01101100_11111010_00000000_00000000_00000000_00000000L;
    private static final long TOKEN_UPPER = 0b01010111_11111111_11111111_11111111_11000111_11111111_11111111_11111110L;
    private static final long VISIBLE_ASCII_LOWER = 0b11111111_11111111_11111111_11111110_00000000_00000000_00000000_00000000L;
    private static final long VISIBLE_ASCII_UPPER = 0b01111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L;
    private static final long ALPHA_ASCII_UPPER = 0b00000111_11111111_11111111_11111110_00000111_11111111_11111111_11111110L;
    private static final byte[] PROTOCOL = "HTTP/1.1".getBytes(StandardCharsets.US_ASCII);
    private static final int PROTOCOL_DIGIT_INDEX_1 = 5;
    private static final int PROTOCOL_DIGIT_INDEX_2 = 7;

    private ParserUtils() {
        // forbidden instantiation
    }

    public static int getMaximumRequestMethodLength() {
        return MAXIMUM_REQUEST_METHOD_LENGTH;
    }

    public static int getProtocolLength() {
        return PROTOCOL.length;
    }

    public static boolean isRequestTargetChar(byte c) {
        return applyBothMasks(c, REQUEST_TARGET_LOWER, REQUEST_TARGET_UPPER);
    }

    public static boolean isDNSNameChar(final byte c) {
        return applyBothMasks(c, DNS_LOWER, DNS_UPPER);
    }

    public static boolean isIPv4AddressChar(final byte c) {
        return applyLowerMask(c, IPV4_LOWER);
    }

    public static boolean isIPv6AddressChar(final byte c) {
        return applyBothMasks(c, IPV6_LOWER, HEXADECIMAL_DIGITS_UPPER);
    }

    public static boolean isAlphaChar(final byte c) {
        return applyUpperMask(c, ALPHA_ASCII_UPPER);
    }

    public static boolean isSchemeChar(final byte c) {
        return applyBothMasks(c, SCHEME_LOWER, SCHEME_UPPER);
    }

    public static boolean isTokenChar(final byte c) {
        return applyBothMasks(c, TOKEN_LOWER, TOKEN_UPPER);
    }

    public static boolean isObsoleteChar(final byte c) {
        return (c & FIRST_128_MASK) != 0;
    }

    public static boolean isVisibleAsciiChar(final byte c) {
        return applyBothMasks(c, VISIBLE_ASCII_LOWER, VISIBLE_ASCII_UPPER);
    }

    public static boolean isSpaceOrTabChar(final byte c) {
        return applyLowerMask(c, SPACE_OR_TAB_LOWER);
    }

    public static boolean isProtocolChar(final byte c, final int index) {
        boolean isDigitIndex = index == PROTOCOL_DIGIT_INDEX_1 || index == PROTOCOL_DIGIT_INDEX_2;
        return 0 <= index && index < PROTOCOL.length && (c == PROTOCOL[index] || (isDigitIndex && isDigitChar(c)));
    }

    public static boolean isPathSegmentChar(final byte c) {
        return applyBothMasks(c, PATH_SEGMENT_LOWER, PATH_SEGMENT_UPPER);
    }

    public static boolean isDigitChar(final byte c) {
        return applyLowerMask(c, DECIMAL_DIGITS_LOWER);
    }

    public static boolean isHexDigitChar(final byte c) {
        return applyBothMasks(c, DECIMAL_DIGITS_LOWER, HEXADECIMAL_DIGITS_UPPER);
    }

    private static boolean applyLowerMask(final byte c, final long lowerMask) {
        return (c & FIRST_64_MASK) == 0 && (lowerMask & (1L << c)) != 0;
    }

    private static boolean applyUpperMask(final byte c, final long upperMask) {
        return (c & FIRST_128_MASK) == 0 && ((c & FIRST_64_MASK) != 0) && (upperMask & (1L << (c & ~FIRST_64_MASK))) != 0;
    }

    private static boolean applyBothMasks(final byte c, final long lowerMask, final long upperMask) {
        return ((c & FIRST_128_MASK) == 0 ) && ((c & FIRST_64_MASK) == 0 ? (lowerMask & (1L << (c))) : (upperMask & (1L << (c & ~FIRST_64_MASK)))) != 0;
    }
}