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