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