1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import io.netty.handler.codec.DefaultHeaders;
19 import io.netty.handler.codec.DefaultHeaders.NameValidator;
20 import io.netty.handler.codec.DefaultHeaders.ValueValidator;
21 import io.netty.handler.codec.Headers;
22 import io.netty.handler.codec.ValueConverter;
23 import io.netty.util.HashingStrategy;
24 import io.netty.util.internal.StringUtil;
25
26 import java.util.Collection;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.function.BiPredicate;
31
32 import static io.netty.handler.codec.http.HttpHeaderNames.SET_COOKIE;
33 import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
34 import static io.netty.util.internal.ObjectUtil.checkNotNull;
35 import static io.netty.util.internal.StringUtil.COMMA;
36 import static io.netty.util.internal.StringUtil.unescapeCsvFields;
37
38
39
40
41
42
43 public class CombinedHttpHeaders extends DefaultHttpHeaders {
44
45
46
47
48
49
50
51
52 @Deprecated
53 public CombinedHttpHeaders(boolean validate) {
54 super(new CombinedHttpHeadersImpl(CASE_INSENSITIVE_HASHER, valueConverter(), nameValidator(validate),
55 valueValidator(validate)));
56 }
57
58 CombinedHttpHeaders(NameValidator<CharSequence> nameValidator, ValueValidator<CharSequence> valueValidator) {
59 super(new CombinedHttpHeadersImpl(
60 CASE_INSENSITIVE_HASHER,
61 valueConverter(),
62 checkNotNull(nameValidator, "nameValidator"),
63 checkNotNull(valueValidator, "valueValidator")));
64 }
65
66 CombinedHttpHeaders(
67 NameValidator<CharSequence> nameValidator, ValueValidator<CharSequence> valueValidator, int sizeHint) {
68 super(new CombinedHttpHeadersImpl(
69 CASE_INSENSITIVE_HASHER,
70 valueConverter(),
71 checkNotNull(nameValidator, "nameValidator"),
72 checkNotNull(valueValidator, "valueValidator"),
73 sizeHint));
74 }
75
76 @Override
77 public boolean containsValue(CharSequence name, CharSequence value, boolean ignoreCase) {
78 return super.containsValue(name, StringUtil.trimOws(value), ignoreCase);
79 }
80
81 private static final class CombinedHttpHeadersImpl
82 extends DefaultHeaders<CharSequence, CharSequence, CombinedHttpHeadersImpl> {
83
84
85
86 private static final int VALUE_LENGTH_ESTIMATE = 10;
87 private CsvValueEscaper<Object> objectEscaper;
88 private CsvValueEscaper<CharSequence> charSequenceEscaper;
89
90 private CsvValueEscaper<Object> objectEscaper() {
91 if (objectEscaper == null) {
92 objectEscaper = new CsvValueEscaper<Object>() {
93 @Override
94 public CharSequence escape(CharSequence name, Object value) {
95 CharSequence converted;
96 try {
97 converted = valueConverter().convertObject(value);
98 } catch (IllegalArgumentException e) {
99 throw new IllegalArgumentException(
100 "Failed to convert object value for header '" + name + '\'', e);
101 }
102 return StringUtil.escapeCsv(converted, true);
103 }
104 };
105 }
106 return objectEscaper;
107 }
108
109 private CsvValueEscaper<CharSequence> charSequenceEscaper() {
110 if (charSequenceEscaper == null) {
111 charSequenceEscaper = new CsvValueEscaper<CharSequence>() {
112 @Override
113 public CharSequence escape(CharSequence name, CharSequence value) {
114 return StringUtil.escapeCsv(value, true);
115 }
116 };
117 }
118 return charSequenceEscaper;
119 }
120
121 CombinedHttpHeadersImpl(HashingStrategy<CharSequence> nameHashingStrategy,
122 ValueConverter<CharSequence> valueConverter,
123 NameValidator<CharSequence> nameValidator,
124 ValueValidator<CharSequence> valueValidator) {
125 this(nameHashingStrategy, valueConverter, nameValidator, valueValidator, 16);
126 }
127
128 CombinedHttpHeadersImpl(HashingStrategy<CharSequence> nameHashingStrategy,
129 ValueConverter<CharSequence> valueConverter,
130 NameValidator<CharSequence> nameValidator,
131 ValueValidator<CharSequence> valueValidator,
132 int sizeHint) {
133 super(nameHashingStrategy, valueConverter, nameValidator, sizeHint, valueValidator);
134 }
135
136 @Override
137 public Iterator<CharSequence> valueIterator(CharSequence name) {
138 Iterator<CharSequence> itr = super.valueIterator(name);
139 if (!itr.hasNext() || cannotBeCombined(name)) {
140 return itr;
141 }
142 Iterator<CharSequence> unescapedItr = unescapeCsvFields(itr.next()).iterator();
143 if (itr.hasNext()) {
144 throw new IllegalStateException("CombinedHttpHeaders should only have one value");
145 }
146 return unescapedItr;
147 }
148
149 @Override
150 public boolean containsAny(CharSequence name, CharSequence predicateArg,
151 BiPredicate<? super CharSequence, ? super CharSequence> valuePredicate) {
152 Iterator<CharSequence> itr = valueIterator(name);
153 while (itr.hasNext()) {
154 if (valuePredicate.test(itr.next(), predicateArg)) {
155 return true;
156 }
157 }
158 return false;
159 }
160
161 @Override
162 public List<CharSequence> getAll(CharSequence name) {
163 List<CharSequence> values = super.getAll(name);
164 if (values.isEmpty() || cannotBeCombined(name)) {
165 return values;
166 }
167 if (values.size() != 1) {
168 throw new IllegalStateException("CombinedHttpHeaders should only have one value");
169 }
170 return unescapeCsvFields(values.get(0));
171 }
172
173 @Override
174 public CombinedHttpHeadersImpl add(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
175
176 if (headers == this) {
177 throw new IllegalArgumentException("can't add to itself.");
178 }
179 if (headers instanceof CombinedHttpHeadersImpl) {
180 if (isEmpty()) {
181
182 addImpl(headers);
183 } else {
184
185 for (Map.Entry<? extends CharSequence, ? extends CharSequence> header : headers) {
186 addEscapedValue(header.getKey(), header.getValue());
187 }
188 }
189 } else {
190 for (Map.Entry<? extends CharSequence, ? extends CharSequence> header : headers) {
191 add(header.getKey(), header.getValue());
192 }
193 }
194 return this;
195 }
196
197 @Override
198 public CombinedHttpHeadersImpl set(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
199 if (headers == this) {
200 return this;
201 }
202 clear();
203 return add(headers);
204 }
205
206 @Override
207 public CombinedHttpHeadersImpl setAll(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
208 if (headers == this) {
209 return this;
210 }
211 for (CharSequence key : headers.names()) {
212 remove(key);
213 }
214 return add(headers);
215 }
216
217 @Override
218 public CombinedHttpHeadersImpl add(CharSequence name, CharSequence value) {
219 return addEscapedValue(name, charSequenceEscaper().escape(name, value));
220 }
221
222 @Override
223 public CombinedHttpHeadersImpl add(CharSequence name, CharSequence... values) {
224 return addEscapedValue(name, commaSeparate(name, charSequenceEscaper(), values));
225 }
226
227 @Override
228 public CombinedHttpHeadersImpl add(CharSequence name, Iterable<? extends CharSequence> values) {
229 return addEscapedValue(name, commaSeparate(name, charSequenceEscaper(), values));
230 }
231
232 @Override
233 public CombinedHttpHeadersImpl addObject(CharSequence name, Object value) {
234 return addEscapedValue(name, commaSeparate(name, objectEscaper(), value));
235 }
236
237 @Override
238 public CombinedHttpHeadersImpl addObject(CharSequence name, Iterable<?> values) {
239 return addEscapedValue(name, commaSeparate(name, objectEscaper(), values));
240 }
241
242 @Override
243 public CombinedHttpHeadersImpl addObject(CharSequence name, Object... values) {
244 return addEscapedValue(name, commaSeparate(name, objectEscaper(), values));
245 }
246
247 @Override
248 public CombinedHttpHeadersImpl set(CharSequence name, CharSequence... values) {
249 set(name, commaSeparate(name, charSequenceEscaper(), values));
250 return this;
251 }
252
253 @Override
254 public CombinedHttpHeadersImpl set(CharSequence name, Iterable<? extends CharSequence> values) {
255 set(name, commaSeparate(name, charSequenceEscaper(), values));
256 return this;
257 }
258
259 @Override
260 public CombinedHttpHeadersImpl setObject(CharSequence name, Object value) {
261 set(name, commaSeparate(name, objectEscaper(), value));
262 return this;
263 }
264
265 @Override
266 public CombinedHttpHeadersImpl setObject(CharSequence name, Object... values) {
267 set(name, commaSeparate(name, objectEscaper(), values));
268 return this;
269 }
270
271 @Override
272 public CombinedHttpHeadersImpl setObject(CharSequence name, Iterable<?> values) {
273 set(name, commaSeparate(name, objectEscaper(), values));
274 return this;
275 }
276
277 private static boolean cannotBeCombined(CharSequence name) {
278 return SET_COOKIE.contentEqualsIgnoreCase(name);
279 }
280
281 private CombinedHttpHeadersImpl addEscapedValue(CharSequence name, CharSequence escapedValue) {
282 CharSequence currentValue = get(name);
283 if (currentValue == null || cannotBeCombined(name)) {
284 super.add(name, escapedValue);
285 } else {
286 set(name, commaSeparateEscapedValues(currentValue, escapedValue));
287 }
288 return this;
289 }
290
291 private static <T> CharSequence commaSeparate(CharSequence name, CsvValueEscaper<T> escaper, T... values) {
292 StringBuilder sb = new StringBuilder(values.length * VALUE_LENGTH_ESTIMATE);
293 if (values.length > 0) {
294 int end = values.length - 1;
295 for (int i = 0; i < end; i++) {
296 sb.append(escaper.escape(name, values[i])).append(COMMA);
297 }
298 sb.append(escaper.escape(name, values[end]));
299 }
300 return sb;
301 }
302
303 private static <T> CharSequence commaSeparate(CharSequence name, CsvValueEscaper<T> escaper,
304 Iterable<? extends T> values) {
305 @SuppressWarnings("rawtypes")
306 final StringBuilder sb = values instanceof Collection
307 ? new StringBuilder(((Collection) values).size() * VALUE_LENGTH_ESTIMATE) : new StringBuilder();
308 Iterator<? extends T> iterator = values.iterator();
309 if (iterator.hasNext()) {
310 T next = iterator.next();
311 while (iterator.hasNext()) {
312 sb.append(escaper.escape(name, next)).append(COMMA);
313 next = iterator.next();
314 }
315 sb.append(escaper.escape(name, next));
316 }
317 return sb;
318 }
319
320 private static CharSequence commaSeparateEscapedValues(CharSequence currentValue, CharSequence value) {
321 return new StringBuilder(currentValue.length() + 1 + value.length())
322 .append(currentValue)
323 .append(COMMA)
324 .append(value);
325 }
326
327
328
329
330
331
332 private interface CsvValueEscaper<T> {
333
334
335
336
337
338
339 CharSequence escape(CharSequence name, T value);
340 }
341 }
342 }