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
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);
115 }
116
117
118
119
120
121
122 @SuppressWarnings("unchecked")
123 public DefaultHttp2Headers(boolean validate) {
124
125
126 super(CASE_SENSITIVE_HASHER,
127 CharSequenceValueConverter.INSTANCE,
128 validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL);
129 }
130
131
132
133
134
135
136
137
138
139 @SuppressWarnings("unchecked")
140 public DefaultHttp2Headers(boolean validate, int arraySizeHint) {
141
142
143 super(CASE_SENSITIVE_HASHER,
144 CharSequenceValueConverter.INSTANCE,
145 validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL,
146 arraySizeHint);
147 }
148
149
150
151
152
153
154
155
156
157
158
159
160 @SuppressWarnings("unchecked")
161 public DefaultHttp2Headers(boolean validate, boolean validateValues, int arraySizeHint) {
162
163
164 super(CASE_SENSITIVE_HASHER,
165 CharSequenceValueConverter.INSTANCE,
166 validate ? HTTP2_NAME_VALIDATOR : NameValidator.NOT_NULL,
167 arraySizeHint,
168 validateValues ? VALUE_VALIDATOR : (ValueValidator<CharSequence>) ValueValidator.NO_VALIDATION);
169 }
170
171 @Override
172 protected void validateName(NameValidator<CharSequence> validator, boolean forAdd, CharSequence name) {
173 super.validateName(validator, forAdd, name);
174 if (nameValidator() == HTTP2_NAME_VALIDATOR && forAdd && hasPseudoHeaderFormat(name)) {
175 if (contains(name)) {
176 PlatformDependent.throwException(connectionError(
177 PROTOCOL_ERROR, "Duplicate HTTP/2 pseudo-header '%s' encountered.", name));
178 }
179 }
180 }
181
182 @Override
183 protected void validateValue(ValueValidator<CharSequence> validator, CharSequence name, CharSequence value) {
184
185 super.validateValue(validator, name, value);
186
187
188 if (nameValidator() == HTTP2_NAME_VALIDATOR && (value == null || value.length() == 0) &&
189 hasPseudoHeaderFormat(name)) {
190 PlatformDependent.throwException(connectionError(
191 PROTOCOL_ERROR, "HTTP/2 pseudo-header '%s' must not be empty.", name));
192 }
193 }
194
195 @Override
196 public Http2Headers clear() {
197 firstNonPseudo = head;
198 return super.clear();
199 }
200
201 @Override
202 public boolean equals(Object o) {
203 return o instanceof Http2Headers && equals((Http2Headers) o, CASE_SENSITIVE_HASHER);
204 }
205
206 @Override
207 public int hashCode() {
208 return hashCode(CASE_SENSITIVE_HASHER);
209 }
210
211 @Override
212 public Http2Headers method(CharSequence value) {
213 set(PseudoHeaderName.METHOD.value(), value);
214 return this;
215 }
216
217 @Override
218 public Http2Headers scheme(CharSequence value) {
219 set(PseudoHeaderName.SCHEME.value(), value);
220 return this;
221 }
222
223 @Override
224 public Http2Headers authority(CharSequence value) {
225 set(PseudoHeaderName.AUTHORITY.value(), value);
226 return this;
227 }
228
229 @Override
230 public Http2Headers path(CharSequence value) {
231 set(PseudoHeaderName.PATH.value(), value);
232 return this;
233 }
234
235 @Override
236 public Http2Headers status(CharSequence value) {
237 set(PseudoHeaderName.STATUS.value(), value);
238 return this;
239 }
240
241 @Override
242 public CharSequence method() {
243 return get(PseudoHeaderName.METHOD.value());
244 }
245
246 @Override
247 public CharSequence scheme() {
248 return get(PseudoHeaderName.SCHEME.value());
249 }
250
251 @Override
252 public CharSequence authority() {
253 return get(PseudoHeaderName.AUTHORITY.value());
254 }
255
256 @Override
257 public CharSequence path() {
258 return get(PseudoHeaderName.PATH.value());
259 }
260
261 @Override
262 public CharSequence status() {
263 return get(PseudoHeaderName.STATUS.value());
264 }
265
266 @Override
267 public boolean contains(CharSequence name, CharSequence value) {
268 return contains(name, value, false);
269 }
270
271 @Override
272 public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
273 return contains(name, value, caseInsensitive ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
274 }
275
276 @Override
277 protected final HeaderEntry<CharSequence, CharSequence> newHeaderEntry(int h, CharSequence name, CharSequence value,
278 HeaderEntry<CharSequence, CharSequence> next) {
279 return new Http2HeaderEntry(h, name, value, next);
280 }
281
282 private final class Http2HeaderEntry extends HeaderEntry<CharSequence, CharSequence> {
283 Http2HeaderEntry(int hash, CharSequence key, CharSequence value,
284 HeaderEntry<CharSequence, CharSequence> next) {
285 super(hash, key);
286 this.value = value;
287 this.next = next;
288
289
290 if (hasPseudoHeaderFormat(key)) {
291 after = firstNonPseudo;
292 before = firstNonPseudo.before();
293 } else {
294 after = head;
295 before = head.before();
296 if (firstNonPseudo == head) {
297 firstNonPseudo = this;
298 }
299 }
300 pointNeighborsToThis();
301 }
302
303 @Override
304 protected void remove() {
305 if (this == firstNonPseudo) {
306 firstNonPseudo = firstNonPseudo.after();
307 }
308 super.remove();
309 }
310 }
311 }