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    * http://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.util.AsciiString;
20  import io.netty.util.ByteProcessor;
21  import io.netty.util.internal.PlatformDependent;
22  import io.netty.util.internal.UnstableApi;
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.util.AsciiString.CASE_INSENSITIVE_HASHER;
28  import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
29  import static io.netty.util.AsciiString.isUpperCase;
30  
31  @UnstableApi
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      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              if (name instanceof AsciiString) {
48                  final int index;
49                  try {
50                      index = ((AsciiString) name).forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR);
51                  } catch (Http2Exception e) {
52                      PlatformDependent.throwException(e);
53                      return;
54                  } catch (Throwable t) {
55                      PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, t,
56                              "unexpected error. invalid header name [%s]", name));
57                      return;
58                  }
59  
60                  if (index != -1) {
61                      PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
62                              "invalid header name [%s]", name));
63                  }
64              } else {
65                  for (int i = 0; i < name.length(); ++i) {
66                      if (isUpperCase(name.charAt(i))) {
67                          PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
68                                  "invalid header name [%s]", name));
69                      }
70                  }
71              }
72          }
73      };
74  
75      private HeaderEntry<CharSequence, CharSequence> firstNonPseudo = head;
76  
77      /**
78       * Create a new instance.
79       * <p>
80       * Header names will be validated according to
81       * <a href="https://tools.ietf.org/html/rfc7540">rfc7540</a>.
82       */
83      public DefaultHttp2Headers() {
84          this(true);
85      }
86  
87      /**
88       * Create a new instance.
89       * @param validate {@code true} to validate header names according to
90       * <a href="https://tools.ietf.org/html/rfc7540">rfc7540</a>. {@code false} to not validate header names.
91       */
92      @SuppressWarnings("unchecked")
93      public DefaultHttp2Headers(boolean validate) {
94          // Case sensitive compare is used because it is cheaper, and header validation can be used to catch invalid
95          // headers.
96          super(CASE_SENSITIVE_HASHER,
97                CharSequenceValueConverter.INSTANCE,
98                validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL);
99      }
100 
101     /**
102      * Create a new instance.
103      * @param validate {@code true} to validate header names according to
104      * <a href="https://tools.ietf.org/html/rfc7540">rfc7540</a>. {@code false} to not validate header names.
105      * @param arraySizeHint A hint as to how large the hash data structure should be.
106      * The next positive power of two will be used. An upper bound may be enforced.
107      */
108     @SuppressWarnings("unchecked")
109     public DefaultHttp2Headers(boolean validate, int arraySizeHint) {
110         // Case sensitive compare is used because it is cheaper, and header validation can be used to catch invalid
111         // headers.
112         super(CASE_SENSITIVE_HASHER,
113               CharSequenceValueConverter.INSTANCE,
114               validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL,
115               arraySizeHint);
116     }
117 
118     @Override
119     public Http2Headers clear() {
120         this.firstNonPseudo = head;
121         return super.clear();
122     }
123 
124     @Override
125     public boolean equals(Object o) {
126         return o instanceof Http2Headers && equals((Http2Headers) o, CASE_SENSITIVE_HASHER);
127     }
128 
129     @Override
130     public int hashCode() {
131         return hashCode(CASE_SENSITIVE_HASHER);
132     }
133 
134     @Override
135     public Http2Headers method(CharSequence value) {
136         set(PseudoHeaderName.METHOD.value(), value);
137         return this;
138     }
139 
140     @Override
141     public Http2Headers scheme(CharSequence value) {
142         set(PseudoHeaderName.SCHEME.value(), value);
143         return this;
144     }
145 
146     @Override
147     public Http2Headers authority(CharSequence value) {
148         set(PseudoHeaderName.AUTHORITY.value(), value);
149         return this;
150     }
151 
152     @Override
153     public Http2Headers path(CharSequence value) {
154         set(PseudoHeaderName.PATH.value(), value);
155         return this;
156     }
157 
158     @Override
159     public Http2Headers status(CharSequence value) {
160         set(PseudoHeaderName.STATUS.value(), value);
161         return this;
162     }
163 
164     @Override
165     public CharSequence method() {
166         return get(PseudoHeaderName.METHOD.value());
167     }
168 
169     @Override
170     public CharSequence scheme() {
171         return get(PseudoHeaderName.SCHEME.value());
172     }
173 
174     @Override
175     public CharSequence authority() {
176         return get(PseudoHeaderName.AUTHORITY.value());
177     }
178 
179     @Override
180     public CharSequence path() {
181         return get(PseudoHeaderName.PATH.value());
182     }
183 
184     @Override
185     public CharSequence status() {
186         return get(PseudoHeaderName.STATUS.value());
187     }
188 
189     @Override
190     public boolean contains(CharSequence name, CharSequence value) {
191         return contains(name, value, false);
192     }
193 
194     @Override
195     public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
196         return contains(name, value, caseInsensitive ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
197     }
198 
199     @Override
200     protected final HeaderEntry<CharSequence, CharSequence> newHeaderEntry(int h, CharSequence name, CharSequence value,
201                                                            HeaderEntry<CharSequence, CharSequence> next) {
202         return new Http2HeaderEntry(h, name, value, next);
203     }
204 
205     private final class Http2HeaderEntry extends HeaderEntry<CharSequence, CharSequence> {
206         protected Http2HeaderEntry(int hash, CharSequence key, CharSequence value,
207                 HeaderEntry<CharSequence, CharSequence> next) {
208             super(hash, key);
209             this.value = value;
210             this.next = next;
211 
212             // Make sure the pseudo headers fields are first in iteration order
213             if (hasPseudoHeaderFormat(key)) {
214                 after = firstNonPseudo;
215                 before = firstNonPseudo.before();
216             } else {
217                 after = head;
218                 before = head.before();
219                 if (firstNonPseudo == head) {
220                     firstNonPseudo = this;
221                 }
222             }
223             pointNeighborsToThis();
224         }
225 
226         @Override
227         protected void remove() {
228             if (this == firstNonPseudo) {
229                 firstNonPseudo = firstNonPseudo.after();
230             }
231             super.remove();
232         }
233     }
234 }