1
2
3
4
5
6
7
8
9
10
11
12
13
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 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
54 return;
55 }
56
57 if (name instanceof AsciiString) {
58 final int index;
59 try {
60 index = ((AsciiString) name).forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR);
61 } catch (Http2Exception e) {
62 PlatformDependent.throwException(e);
63 return;
64 } catch (Throwable t) {
65 PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, t,
66 "unexpected error. invalid header name [%s]", name));
67 return;
68 }
69
70 if (index != -1) {
71 PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
72 "invalid header name [%s]", name));
73 }
74 } else {
75 for (int i = 0; i < name.length(); ++i) {
76 if (isUpperCase(name.charAt(i))) {
77 PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
78 "invalid header name [%s]", name));
79 }
80 }
81 }
82 }
83 };
84
85 private static final ValueValidator<CharSequence> VALUE_VALIDATOR = new ValueValidator<CharSequence>() {
86 @Override
87 public void validate(CharSequence value) {
88 int index = HttpHeaderValidationUtil.validateValidHeaderValue(value);
89 if (index != -1) {
90 throw new IllegalArgumentException("a header value contains prohibited character 0x" +
91 Integer.toHexString(value.charAt(index)) + " at index " + index + '.');
92 }
93 }
94 };
95
96 private HeaderEntry<CharSequence, CharSequence> firstNonPseudo = head;
97
98
99
100
101
102
103
104 public DefaultHttp2Headers() {
105 this(true);
106 }
107
108
109
110
111
112
113 @SuppressWarnings("unchecked")
114 public DefaultHttp2Headers(boolean validate) {
115
116
117 super(CASE_SENSITIVE_HASHER,
118 CharSequenceValueConverter.INSTANCE,
119 validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL);
120 }
121
122
123
124
125
126
127
128
129
130 @SuppressWarnings("unchecked")
131 public DefaultHttp2Headers(boolean validate, int arraySizeHint) {
132
133
134 super(CASE_SENSITIVE_HASHER,
135 CharSequenceValueConverter.INSTANCE,
136 validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL,
137 arraySizeHint);
138 }
139
140
141
142
143
144
145
146
147
148
149
150
151 @SuppressWarnings("unchecked")
152 public DefaultHttp2Headers(boolean validate, boolean validateValues, int arraySizeHint) {
153
154
155 super(CASE_SENSITIVE_HASHER,
156 CharSequenceValueConverter.INSTANCE,
157 validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL,
158 arraySizeHint,
159 validateValues ? VALUE_VALIDATOR : (ValueValidator<CharSequence>) ValueValidator.NO_VALIDATION);
160 }
161
162 @Override
163 protected void validateName(NameValidator<CharSequence> validator, boolean forAdd, CharSequence name) {
164 super.validateName(validator, forAdd, name);
165 if (nameValidator() == HTTP2_NAME_VALIDATOR && forAdd && hasPseudoHeaderFormat(name)) {
166 if (contains(name)) {
167 PlatformDependent.throwException(connectionError(
168 PROTOCOL_ERROR, "Duplicate HTTP/2 pseudo-header '%s' encountered.", name));
169 }
170 }
171 }
172
173 @Override
174 protected void validateValue(ValueValidator<CharSequence> validator, CharSequence name, CharSequence value) {
175
176 super.validateValue(validator, name, value);
177
178
179 if (nameValidator() == HTTP2_NAME_VALIDATOR && (value == null || value.length() == 0) &&
180 hasPseudoHeaderFormat(name)) {
181 PlatformDependent.throwException(connectionError(
182 PROTOCOL_ERROR, "HTTP/2 pseudo-header '%s' must not be empty.", name));
183 }
184 }
185
186 @Override
187 public Http2Headers clear() {
188 firstNonPseudo = head;
189 return super.clear();
190 }
191
192 @Override
193 public boolean equals(Object o) {
194 return o instanceof Http2Headers && equals((Http2Headers) o, CASE_SENSITIVE_HASHER);
195 }
196
197 @Override
198 public int hashCode() {
199 return hashCode(CASE_SENSITIVE_HASHER);
200 }
201
202 @Override
203 public Http2Headers method(CharSequence value) {
204 set(PseudoHeaderName.METHOD.value(), value);
205 return this;
206 }
207
208 @Override
209 public Http2Headers scheme(CharSequence value) {
210 set(PseudoHeaderName.SCHEME.value(), value);
211 return this;
212 }
213
214 @Override
215 public Http2Headers authority(CharSequence value) {
216 set(PseudoHeaderName.AUTHORITY.value(), value);
217 return this;
218 }
219
220 @Override
221 public Http2Headers path(CharSequence value) {
222 set(PseudoHeaderName.PATH.value(), value);
223 return this;
224 }
225
226 @Override
227 public Http2Headers status(CharSequence value) {
228 set(PseudoHeaderName.STATUS.value(), value);
229 return this;
230 }
231
232 @Override
233 public CharSequence method() {
234 return get(PseudoHeaderName.METHOD.value());
235 }
236
237 @Override
238 public CharSequence scheme() {
239 return get(PseudoHeaderName.SCHEME.value());
240 }
241
242 @Override
243 public CharSequence authority() {
244 return get(PseudoHeaderName.AUTHORITY.value());
245 }
246
247 @Override
248 public CharSequence path() {
249 return get(PseudoHeaderName.PATH.value());
250 }
251
252 @Override
253 public CharSequence status() {
254 return get(PseudoHeaderName.STATUS.value());
255 }
256
257 @Override
258 public boolean contains(CharSequence name, CharSequence value) {
259 return contains(name, value, false);
260 }
261
262 @Override
263 public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
264 return contains(name, value, caseInsensitive ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
265 }
266
267 @Override
268 protected final HeaderEntry<CharSequence, CharSequence> newHeaderEntry(int h, CharSequence name, CharSequence value,
269 HeaderEntry<CharSequence, CharSequence> next) {
270 return new Http2HeaderEntry(h, name, value, next);
271 }
272
273 private final class Http2HeaderEntry extends HeaderEntry<CharSequence, CharSequence> {
274 Http2HeaderEntry(int hash, CharSequence key, CharSequence value,
275 HeaderEntry<CharSequence, CharSequence> next) {
276 super(hash, key);
277 this.value = value;
278 this.next = next;
279
280
281 if (hasPseudoHeaderFormat(key)) {
282 after = firstNonPseudo;
283 before = firstNonPseudo.before();
284 } else {
285 after = head;
286 before = head.before();
287 if (firstNonPseudo == head) {
288 firstNonPseudo = this;
289 }
290 }
291 pointNeighborsToThis();
292 }
293
294 @Override
295 protected void remove() {
296 if (this == firstNonPseudo) {
297 firstNonPseudo = firstNonPseudo.after();
298 }
299 super.remove();
300 }
301 }
302 }