View Javadoc
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 }