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 private 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
58
59
60 int tokenIndex = HttpHeaderValidationUtil.validateToken(name);
61 if (tokenIndex != -1) {
62 PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
63 "invalid header name [%s]", name));
64 }
65
66 if (name instanceof AsciiString) {
67 final int index;
68 try {
69 index = ((AsciiString) name).forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR);
70 } catch (Http2Exception e) {
71 PlatformDependent.throwException(e);
72 return;
73 } catch (Throwable t) {
74 PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, t,
75 "unexpected error. invalid header name [%s]", name));
76 return;
77 }
78
79 if (index != -1) {
80 PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
81 "invalid header name [%s]", name));
82 }
83 } else {
84 for (int i = 0; i < name.length(); ++i) {
85 if (isUpperCase(name.charAt(i))) {
86 PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
87 "invalid header name [%s]", name));
88 }
89 }
90 }
91 }
92 };
93
94 private static final ValueValidator<CharSequence> VALUE_VALIDATOR = new ValueValidator<CharSequence>() {
95 @Override
96 public void validate(CharSequence value) {
97 int index = HttpHeaderValidationUtil.validateValidHeaderValue(value);
98 if (index != -1) {
99 throw new IllegalArgumentException("a header value contains prohibited character 0x" +
100 Integer.toHexString(value.charAt(index)) + " at index " + index + '.');
101 }
102 }
103 };
104
105 private HeaderEntry<CharSequence, CharSequence> firstNonPseudo = head;
106
107
108
109
110
111
112
113 public DefaultHttp2Headers() {
114 this(true, true, 16);
115 }
116
117
118
119
120
121
122
123 public DefaultHttp2Headers(boolean validate) {
124 this(validate, validate, 16);
125 }
126
127
128
129
130
131
132
133
134
135
136 @SuppressWarnings("unchecked")
137 public DefaultHttp2Headers(boolean validate, int arraySizeHint) {
138 this(validate, validate, arraySizeHint);
139 }
140
141
142
143
144
145
146
147
148
149
150
151 @SuppressWarnings("unchecked")
152 public DefaultHttp2Headers(boolean validateNames, boolean validateValues, int arraySizeHint) {
153
154
155 super(CASE_SENSITIVE_HASHER,
156 CharSequenceValueConverter.INSTANCE,
157 validateNames ? 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
303
304
305
306 public static NameValidator<CharSequence> defaultHtt2NameValidator() {
307 return HTTP2_NAME_VALIDATOR;
308 }
309
310
311
312
313 public static ValueValidator<CharSequence> defaultHttp2ValueValidator() {
314 return VALUE_VALIDATOR;
315 }
316 }