View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * 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 distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  package io.netty.handler.codec.http2;
16  
17  import io.netty.handler.codec.CharSequenceValueConverter;
18  import io.netty.handler.codec.DefaultHeaders;
19  import io.netty.handler.codec.http.HttpHeaderValidationUtil;
20  import io.netty.util.AsciiString;
21  import io.netty.util.ByteProcessor;
22  import io.netty.util.internal.PlatformDependent;
23  
24  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
25  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
26  import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat;
27  import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.isPseudoHeader;
28  import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
29  import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
30  import static io.netty.util.AsciiString.isUpperCase;
31  
32  public class DefaultHttp2Headers
33          extends DefaultHeaders<CharSequence, CharSequence, Http2Headers> implements Http2Headers {
34      private static final ByteProcessor HTTP2_NAME_VALIDATOR_PROCESSOR = new ByteProcessor() {
35          @Override
36          public boolean process(byte value) {
37              return !isUpperCase(value);
38          }
39      };
40      private static final NameValidator<CharSequence> HTTP2_NAME_VALIDATOR = new NameValidator<CharSequence>() {
41          @Override
42          public void validateName(CharSequence name) {
43              if (name == null || name.length() == 0) {
44                  PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
45                          "empty headers are not allowed [%s]", name));
46              }
47  
48              if (hasPseudoHeaderFormat(name)) {
49                  if (!isPseudoHeader(name)) {
50                      PlatformDependent.throwException(connectionError(
51                              PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name));
52                  }
53                  // no need for lower-case validation, we trust our own pseudo header constants
54                  return;
55              }
56  
57              // RFC 9113 Section 8.2.1: HTTP/2 field names are valid HTTP/1.1 tokens (RFC 7230 Section 3.2.6)
58              // with the additional constraint that they MUST be lowercase. Reject anything outside the token
59              // grammar (non-ASCII, control characters, SP/HTAB, separators) before the lowercase check.
60              int tokenIndex = HttpHeaderValidationUtil.validateToken(name);
61              if (tokenIndex != -1) {
62                  PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
63                          "invalid header name [%s]", name));
64              }
65  
66              if (name instanceof AsciiString) {
67                  final int index;
68                  try {
69                      index = ((AsciiString) name).forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR);
70                  } catch (Http2Exception e) {
71                      PlatformDependent.throwException(e);
72                      return;
73                  } catch (Throwable t) {
74                      PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, t,
75                              "unexpected error. invalid header name [%s]", name));
76                      return;
77                  }
78  
79                  if (index != -1) {
80                      PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
81                              "invalid header name [%s]", name));
82                  }
83              } else {
84                  for (int i = 0; i < name.length(); ++i) {
85                      if (isUpperCase(name.charAt(i))) {
86                          PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
87                                  "invalid header name [%s]", name));
88                      }
89                  }
90              }
91          }
92      };
93  
94      private static final ValueValidator<CharSequence> VALUE_VALIDATOR = new ValueValidator<CharSequence>() {
95          @Override
96          public void validate(CharSequence value) {
97              int index = HttpHeaderValidationUtil.validateValidHeaderValue(value);
98              if (index != -1) {
99                  throw new IllegalArgumentException("a header value contains prohibited character 0x" +
100                         Integer.toHexString(value.charAt(index)) + " at index " + index + '.');
101             }
102         }
103     };
104 
105     private HeaderEntry<CharSequence, CharSequence> firstNonPseudo = head;
106 
107     /**
108      * Create a new instance.
109      * <p>
110      * Header names will be validated according to
111      * <a href="https://tools.ietf.org/html/rfc7540">rfc7540</a>.
112      */
113     public DefaultHttp2Headers() {
114         this(true);
115     }
116 
117     /**
118      * Create a new instance.
119      * @param validate {@code true} to validate header names according to
120      * <a href="https://tools.ietf.org/html/rfc7540">rfc7540</a>. {@code false} to not validate header names.
121      */
122     @SuppressWarnings("unchecked")
123     public DefaultHttp2Headers(boolean validate) {
124         // Case sensitive compare is used because it is cheaper, and header validation can be used to catch invalid
125         // headers.
126         super(CASE_SENSITIVE_HASHER,
127               CharSequenceValueConverter.INSTANCE,
128               validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL);
129     }
130 
131     /**
132      * Create a new instance.
133      * @param validate {@code true} to validate header names according to
134      * <a href="https://tools.ietf.org/html/rfc7540">rfc7540</a>. {@code false} to not validate header names.
135      * @param arraySizeHint A hint as to how large the hash data structure should be.
136      * The next positive power of two will be used. An upper bound may be enforced.
137      * @see DefaultHttp2Headers#DefaultHttp2Headers(boolean, boolean, int)
138      */
139     @SuppressWarnings("unchecked")
140     public DefaultHttp2Headers(boolean validate, int arraySizeHint) {
141         // Case sensitive compare is used because it is cheaper, and header validation can be used to catch invalid
142         // headers.
143         super(CASE_SENSITIVE_HASHER,
144               CharSequenceValueConverter.INSTANCE,
145               validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL,
146               arraySizeHint);
147     }
148 
149     /**
150      * Create a new instance.
151      * @param validate {@code true} to validate header names according to
152      * <a href="https://tools.ietf.org/html/rfc7540">rfc7540</a>. {@code false} to not validate header names.
153      * @param validateValues {@code true} to validate header values according to
154      * <a href="https://datatracker.ietf.org/doc/html/rfc7230#section-3.2">rfc7230</a> and
155      * <a href="https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1">rfc5234</a>. Otherwise, {@code false}
156      * (the default) to not validate values.
157      * @param arraySizeHint A hint as to how large the hash data structure should be.
158      * The next positive power of two will be used. An upper bound may be enforced.
159      */
160     @SuppressWarnings("unchecked")
161     public DefaultHttp2Headers(boolean validate, boolean validateValues, int arraySizeHint) {
162         // Case sensitive compare is used because it is cheaper, and header validation can be used to catch invalid
163         // headers.
164         super(CASE_SENSITIVE_HASHER,
165                 CharSequenceValueConverter.INSTANCE,
166                 validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL,
167                 arraySizeHint,
168                 validateValues ? VALUE_VALIDATOR : (ValueValidator<CharSequence>) ValueValidator.NO_VALIDATION);
169     }
170 
171     @Override
172     protected void validateName(NameValidator<CharSequence> validator, boolean forAdd, CharSequence name) {
173         super.validateName(validator, forAdd, name);
174         if (nameValidator() == HTTP2_NAME_VALIDATOR && forAdd && hasPseudoHeaderFormat(name)) {
175             if (contains(name)) {
176                 PlatformDependent.throwException(connectionError(
177                         PROTOCOL_ERROR, "Duplicate HTTP/2 pseudo-header '%s' encountered.", name));
178             }
179         }
180     }
181 
182     @Override
183     protected void validateValue(ValueValidator<CharSequence> validator, CharSequence name, CharSequence value) {
184         // This method has a noop override for backward compatibility, see https://github.com/netty/netty/pull/12975
185         super.validateValue(validator, name, value);
186         // https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1
187         // pseudo headers must not be empty
188         if (nameValidator() == HTTP2_NAME_VALIDATOR && (value == null || value.length() == 0) &&
189                 hasPseudoHeaderFormat(name)) {
190             PlatformDependent.throwException(connectionError(
191                     PROTOCOL_ERROR, "HTTP/2 pseudo-header '%s' must not be empty.", name));
192         }
193     }
194 
195     @Override
196     public Http2Headers clear() {
197         firstNonPseudo = head;
198         return super.clear();
199     }
200 
201     @Override
202     public boolean equals(Object o) {
203         return o instanceof Http2Headers && equals((Http2Headers) o, CASE_SENSITIVE_HASHER);
204     }
205 
206     @Override
207     public int hashCode() {
208         return hashCode(CASE_SENSITIVE_HASHER);
209     }
210 
211     @Override
212     public Http2Headers method(CharSequence value) {
213         set(PseudoHeaderName.METHOD.value(), value);
214         return this;
215     }
216 
217     @Override
218     public Http2Headers scheme(CharSequence value) {
219         set(PseudoHeaderName.SCHEME.value(), value);
220         return this;
221     }
222 
223     @Override
224     public Http2Headers authority(CharSequence value) {
225         set(PseudoHeaderName.AUTHORITY.value(), value);
226         return this;
227     }
228 
229     @Override
230     public Http2Headers path(CharSequence value) {
231         set(PseudoHeaderName.PATH.value(), value);
232         return this;
233     }
234 
235     @Override
236     public Http2Headers status(CharSequence value) {
237         set(PseudoHeaderName.STATUS.value(), value);
238         return this;
239     }
240 
241     @Override
242     public CharSequence method() {
243         return get(PseudoHeaderName.METHOD.value());
244     }
245 
246     @Override
247     public CharSequence scheme() {
248         return get(PseudoHeaderName.SCHEME.value());
249     }
250 
251     @Override
252     public CharSequence authority() {
253         return get(PseudoHeaderName.AUTHORITY.value());
254     }
255 
256     @Override
257     public CharSequence path() {
258         return get(PseudoHeaderName.PATH.value());
259     }
260 
261     @Override
262     public CharSequence status() {
263         return get(PseudoHeaderName.STATUS.value());
264     }
265 
266     @Override
267     public boolean contains(CharSequence name, CharSequence value) {
268         return contains(name, value, false);
269     }
270 
271     @Override
272     public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
273         return contains(name, value, caseInsensitive ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
274     }
275 
276     @Override
277     protected final HeaderEntry<CharSequence, CharSequence> newHeaderEntry(int h, CharSequence name, CharSequence value,
278                                                            HeaderEntry<CharSequence, CharSequence> next) {
279         return new Http2HeaderEntry(h, name, value, next);
280     }
281 
282     private final class Http2HeaderEntry extends HeaderEntry<CharSequence, CharSequence> {
283         Http2HeaderEntry(int hash, CharSequence key, CharSequence value,
284                 HeaderEntry<CharSequence, CharSequence> next) {
285             super(hash, key);
286             this.value = value;
287             this.next = next;
288 
289             // Make sure the pseudo headers fields are first in iteration order
290             if (hasPseudoHeaderFormat(key)) {
291                 after = firstNonPseudo;
292                 before = firstNonPseudo.before();
293             } else {
294                 after = head;
295                 before = head.before();
296                 if (firstNonPseudo == head) {
297                     firstNonPseudo = this;
298                 }
299             }
300             pointNeighborsToThis();
301         }
302 
303         @Override
304         protected void remove() {
305             if (this == firstNonPseudo) {
306                 firstNonPseudo = firstNonPseudo.after();
307             }
308             super.remove();
309         }
310     }
311 
312     /**
313      * Default {@link io.netty.handler.codec.DefaultHeaders.NameValidator} used for HTTP/2.
314      */
315     public static NameValidator<CharSequence> defaultHtt2NameValidator() {
316         return HTTP2_NAME_VALIDATOR;
317     }
318 
319     /**
320      * Default {@link io.netty.handler.codec.DefaultHeaders.ValueValidator} used for HTTP/2.
321      */
322     public static ValueValidator<CharSequence> defaultHttp2ValueValidator() {
323         return VALUE_VALIDATOR;
324     }
325 }