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