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