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