1 /*
2 * Copyright 2022 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.http;
17
18 import io.netty.util.AsciiString;
19
20 import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
21
22 /**
23 * Functions used to perform various validations of HTTP header names and values.
24 */
25 public final class HttpHeaderValidationUtil {
26 private HttpHeaderValidationUtil() {
27 }
28
29 /**
30 * Check if a header name is "connection related".
31 * <p>
32 * The <a href="https://datatracker.ietf.org/doc/html/rfc9110#section-7.6.1">RFC9110</a> only specify an incomplete
33 * list of the following headers:
34 *
35 * <ul>
36 * <li><tt>Connection</tt></li>
37 * <li><tt>Proxy-Connection</tt></li>
38 * <li><tt>Keep-Alive</tt></li>
39 * <li><tt>TE</tt></li>
40 * <li><tt>Transfer-Encoding</tt></li>
41 * <li><tt>Upgrade</tt></li>
42 * </ul>
43 *
44 * @param name the name of the header to check. The check is case-insensitive.
45 * @param ignoreTeHeader {@code true} if the <tt>TE</tt> header should be ignored by this check.
46 * This is relevant for HTTP/2 header validation, where the <tt>TE</tt> header has special rules.
47 * @return {@code true} if the given header name is one of the specified connection-related headers.
48 */
49 @SuppressWarnings("deprecation") // We need to check for deprecated headers as well.
50 public static boolean isConnectionHeader(CharSequence name, boolean ignoreTeHeader) {
51 // These are the known standard and non-standard connection related headers:
52 // - upgrade (7 chars)
53 // - connection (10 chars)
54 // - keep-alive (10 chars)
55 // - proxy-connection (16 chars)
56 // - transfer-encoding (17 chars)
57 //
58 // See https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.2
59 // and https://datatracker.ietf.org/doc/html/rfc9110#section-7.6.1
60 // for the list of connection related headers.
61 //
62 // We scan for these based on the length, then double-check any matching name.
63 int len = name.length();
64 switch (len) {
65 case 2: return ignoreTeHeader? false : contentEqualsIgnoreCase(name, HttpHeaderNames.TE);
66 case 7: return contentEqualsIgnoreCase(name, HttpHeaderNames.UPGRADE);
67 case 10: return contentEqualsIgnoreCase(name, HttpHeaderNames.CONNECTION) ||
68 contentEqualsIgnoreCase(name, HttpHeaderNames.KEEP_ALIVE);
69 case 16: return contentEqualsIgnoreCase(name, HttpHeaderNames.PROXY_CONNECTION);
70 case 17: return contentEqualsIgnoreCase(name, HttpHeaderNames.TRANSFER_ENCODING);
71 default:
72 return false;
73 }
74 }
75
76 /**
77 * If the given header is {@link HttpHeaderNames#TE} and the given header value is <em>not</em>
78 * {@link HttpHeaderValues#TRAILERS}, then return {@code true}. Otherwie, {@code false}.
79 * <p>
80 * The string comparisons are case-insensitive.
81 * <p>
82 * This check is important for HTTP/2 header validation.
83 *
84 * @param name the header name to check if it is <tt>TE</tt> or not.
85 * @param value the header value to check if it is something other than <tt>TRAILERS</tt>.
86 * @return {@code true} only if the header name is <tt>TE</tt>, and the header value is <em>not</em>
87 * <tt>TRAILERS</tt>. Otherwise, {@code false}.
88 */
89 public static boolean isTeNotTrailers(CharSequence name, CharSequence value) {
90 if (name.length() == 2) {
91 return contentEqualsIgnoreCase(name, HttpHeaderNames.TE) &&
92 !contentEqualsIgnoreCase(value, HttpHeaderValues.TRAILERS);
93 }
94 return false;
95 }
96
97 /**
98 * Validate the given HTTP header value by searching for any illegal characters.
99 *
100 * @param value the HTTP header value to validate.
101 * @return the index of the first illegal character found, or {@code -1} if there are none and the header value is
102 * valid.
103 */
104 public static int validateValidHeaderValue(CharSequence value) {
105 int length = value.length();
106 if (length == 0) {
107 return -1;
108 }
109 if (value instanceof AsciiString) {
110 return verifyValidHeaderValueAsciiString((AsciiString) value);
111 }
112 return verifyValidHeaderValueCharSequence(value);
113 }
114
115 private static int verifyValidHeaderValueAsciiString(AsciiString value) {
116 // Validate value to field-content rule.
117 // field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
118 // field-vchar = VCHAR / obs-text
119 // VCHAR = %x21-7E ; visible (printing) characters
120 // obs-text = %x80-FF
121 // SP = %x20
122 // HTAB = %x09 ; horizontal tab
123 // See: https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
124 // And: https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
125 final byte[] array = value.array();
126 final int start = value.arrayOffset();
127 int b = array[start] & 0xFF;
128 if (b < 0x21 || b == 0x7F) {
129 return 0;
130 }
131 int end = start + value.length();
132 for (int i = start + 1; i < end; i++) {
133 b = array[i] & 0xFF;
134 if (b < 0x20 && b != 0x09 || b == 0x7F) {
135 return i - start;
136 }
137 }
138 return -1;
139 }
140
141 private static int verifyValidHeaderValueCharSequence(CharSequence value) {
142 // Validate value to field-content rule.
143 // field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
144 // field-vchar = VCHAR / obs-text
145 // VCHAR = %x21-7E ; visible (printing) characters
146 // obs-text = %x80-FF
147 // SP = %x20
148 // HTAB = %x09 ; horizontal tab
149 // See: https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
150 // And: https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
151 int b = value.charAt(0);
152 if (b < 0x21 || b == 0x7F) {
153 return 0;
154 }
155 int length = value.length();
156 for (int i = 1; i < length; i++) {
157 b = value.charAt(i);
158 if (b < 0x20 && b != 0x09 || b == 0x7F) {
159 return i;
160 }
161 }
162 return -1;
163 }
164
165 /**
166 * Validate a <a href="https://tools.ietf.org/html/rfc7230#section-3.2.6">token</a> contains only allowed
167 * characters.
168 * <p>
169 * The <a href="https://tools.ietf.org/html/rfc2616#section-2.2">token</a> format is used for variety of HTTP
170 * components, like <a href="https://tools.ietf.org/html/rfc6265#section-4.1.1">cookie-name</a>,
171 * <a href="https://tools.ietf.org/html/rfc7230#section-3.2.6">field-name</a> of a
172 * <a href="https://tools.ietf.org/html/rfc7230#section-3.2">header-field</a>, or
173 * <a href="https://tools.ietf.org/html/rfc7231#section-4">request method</a>.
174 *
175 * @param token the token to validate.
176 * @return the index of the first invalid token character found, or {@code -1} if there are none.
177 */
178 public static int validateToken(CharSequence token) {
179 return HttpUtil.validateToken(token);
180 }
181 }