All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.codec.http.HttpHeaderValidationUtil Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * Copyright 2022 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:
 *
 *   https://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.http;

import io.netty.util.AsciiString;

import static io.netty.util.AsciiString.contentEqualsIgnoreCase;

/**
 * Functions used to perform various validations of HTTP header names and values.
 */
public final class HttpHeaderValidationUtil {
    private HttpHeaderValidationUtil() {
    }

    /**
     * Check if a header name is "connection related".
     * 

* The RFC9110 only specify an incomplete * list of the following headers: * *

    *
  • Connection
  • *
  • Proxy-Connection
  • *
  • Keep-Alive
  • *
  • TE
  • *
  • Transfer-Encoding
  • *
  • Upgrade
  • *
* * @param name the name of the header to check. The check is case-insensitive. * @param ignoreTeHeader {@code true} if the TE header should be ignored by this check. * This is relevant for HTTP/2 header validation, where the TE header has special rules. * @return {@code true} if the given header name is one of the specified connection-related headers. */ @SuppressWarnings("deprecation") // We need to check for deprecated headers as well. public static boolean isConnectionHeader(CharSequence name, boolean ignoreTeHeader) { // These are the known standard and non-standard connection related headers: // - upgrade (7 chars) // - connection (10 chars) // - keep-alive (10 chars) // - proxy-connection (16 chars) // - transfer-encoding (17 chars) // // See https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.2 // and https://datatracker.ietf.org/doc/html/rfc9110#section-7.6.1 // for the list of connection related headers. // // We scan for these based on the length, then double-check any matching name. int len = name.length(); switch (len) { case 2: return ignoreTeHeader? false : contentEqualsIgnoreCase(name, HttpHeaderNames.TE); case 7: return contentEqualsIgnoreCase(name, HttpHeaderNames.UPGRADE); case 10: return contentEqualsIgnoreCase(name, HttpHeaderNames.CONNECTION) || contentEqualsIgnoreCase(name, HttpHeaderNames.KEEP_ALIVE); case 16: return contentEqualsIgnoreCase(name, HttpHeaderNames.PROXY_CONNECTION); case 17: return contentEqualsIgnoreCase(name, HttpHeaderNames.TRANSFER_ENCODING); default: return false; } } /** * If the given header is {@link HttpHeaderNames#TE} and the given header value is not * {@link HttpHeaderValues#TRAILERS}, then return {@code true}. Otherwie, {@code false}. *

* The string comparisons are case-insensitive. *

* This check is important for HTTP/2 header validation. * * @param name the header name to check if it is TE or not. * @param value the header value to check if it is something other than TRAILERS. * @return {@code true} only if the header name is TE, and the header value is not * TRAILERS. Otherwise, {@code false}. */ public static boolean isTeNotTrailers(CharSequence name, CharSequence value) { if (name.length() == 2) { return contentEqualsIgnoreCase(name, HttpHeaderNames.TE) && !contentEqualsIgnoreCase(value, HttpHeaderValues.TRAILERS); } return false; } /** * Validate the given HTTP header value by searching for any illegal characters. * * @param value the HTTP header value to validate. * @return the index of the first illegal character found, or {@code -1} if there are none and the header value is * valid. */ public static int validateValidHeaderValue(CharSequence value) { int length = value.length(); if (length == 0) { return -1; } if (value instanceof AsciiString) { return verifyValidHeaderValueAsciiString((AsciiString) value); } return verifyValidHeaderValueCharSequence(value); } private static int verifyValidHeaderValueAsciiString(AsciiString value) { // Validate value to field-content rule. // field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] // field-vchar = VCHAR / obs-text // VCHAR = %x21-7E ; visible (printing) characters // obs-text = %x80-FF // SP = %x20 // HTAB = %x09 ; horizontal tab // See: https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 // And: https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1 final byte[] array = value.array(); final int start = value.arrayOffset(); int b = array[start] & 0xFF; if (b < 0x21 || b == 0x7F) { return 0; } int length = value.length(); for (int i = start + 1; i < length; i++) { b = array[i] & 0xFF; if (b < 0x20 && b != 0x09 || b == 0x7F) { return i - start; } } return -1; } private static int verifyValidHeaderValueCharSequence(CharSequence value) { // Validate value to field-content rule. // field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] // field-vchar = VCHAR / obs-text // VCHAR = %x21-7E ; visible (printing) characters // obs-text = %x80-FF // SP = %x20 // HTAB = %x09 ; horizontal tab // See: https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 // And: https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1 int b = value.charAt(0); if (b < 0x21 || b == 0x7F) { return 0; } int length = value.length(); for (int i = 1; i < length; i++) { b = value.charAt(i); if (b < 0x20 && b != 0x09 || b == 0x7F) { return i; } } return -1; } /** * Validate a token contains only allowed * characters. *

* The token format is used for variety of HTTP * components, like cookie-name, * field-name of a * header-field, or * request method. * * @param token the token to validate. * @return the index of the first invalid token character found, or {@code -1} if there are none. */ public static int validateToken(CharSequence token) { if (token instanceof AsciiString) { return validateAsciiStringToken((AsciiString) token); } return validateCharSequenceToken(token); } /** * Validate that an {@link AsciiString} contain onlu valid * token characters. * * @param token the ascii string to validate. */ private static int validateAsciiStringToken(AsciiString token) { byte[] array = token.array(); for (int i = token.arrayOffset(), len = token.arrayOffset() + token.length(); i < len; i++) { if (!BitSet128.contains(array[i], TOKEN_CHARS_HIGH, TOKEN_CHARS_LOW)) { return i - token.arrayOffset(); } } return -1; } /** * Validate that a {@link CharSequence} contain onlu valid * token characters. * * @param token the character sequence to validate. */ private static int validateCharSequenceToken(CharSequence token) { for (int i = 0, len = token.length(); i < len; i++) { byte value = (byte) token.charAt(i); if (!BitSet128.contains(value, TOKEN_CHARS_HIGH, TOKEN_CHARS_LOW)) { return i; } } return -1; } private static final long TOKEN_CHARS_HIGH; private static final long TOKEN_CHARS_LOW; static { // HEADER // header-field = field-name ":" OWS field-value OWS // // field-name = token // token = 1*tchar // // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" // / DIGIT / ALPHA // ; any VCHAR, except delimiters. // Delimiters are chosen // from the set of US-ASCII visual characters not allowed in a token // (DQUOTE and "(),/:;<=>?@[\]{}") // // COOKIE // cookie-pair = cookie-name "=" cookie-value // cookie-name = token // token = 1* // CTL = // separators = "(" | ")" | "<" | ">" | "@" // | "," | ";" | ":" | "\" | <"> // | "/" | "[" | "]" | "?" | "=" // | "{" | "}" | SP | HT // // field-name's token is equivalent to cookie-name's token, we can reuse the tchar mask for both: BitSet128 tokenChars = new BitSet128() .range('0', '9').range('a', 'z').range('A', 'Z') // Alphanumeric. .bits('-', '.', '_', '~') // Unreserved characters. .bits('!', '#', '$', '%', '&', '\'', '*', '+', '^', '`', '|'); // Token special characters. TOKEN_CHARS_HIGH = tokenChars.high(); TOKEN_CHARS_LOW = tokenChars.low(); } private static final class BitSet128 { private long high; private long low; BitSet128 range(char fromInc, char toInc) { for (int bit = fromInc; bit <= toInc; bit++) { if (bit < 64) { low |= 1L << bit; } else { high |= 1L << bit - 64; } } return this; } BitSet128 bits(char... bits) { for (char bit : bits) { if (bit < 64) { low |= 1L << bit; } else { high |= 1L << bit - 64; } } return this; } long high() { return high; } long low() { return low; } static boolean contains(byte bit, long high, long low) { if (bit < 0) { return false; } if (bit < 64) { return 0 != (low & 1L << bit); } return 0 != (high & 1L << bit - 64); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy