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
168 for (Map.Entry<? extends CharSequence, ? extends CharSequence> header : headers) {
169 addEscapedValue(header.getKey(), header.getValue());
170 }
171 } else {
172 for (Map.Entry<? extends CharSequence, ? extends CharSequence> header : headers) {
173 add(header.getKey(), header.getValue());
174 }
175 }
176 return this;
177 }
178
179 @Override
180 public CombinedHttpHeadersImpl set(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
181 if (headers == this) {
182 return this;
183 }
184 clear();
185 return add(headers);
186 }
187
188 @Override
189 public CombinedHttpHeadersImpl setAll(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
190 if (headers == this) {
191 return this;
192 }
193 for (CharSequence key : headers.names()) {
194 remove(key);
195 }
196 return add(headers);
197 }
198
199 @Override
200 public CombinedHttpHeadersImpl add(CharSequence name, CharSequence value) {
201 return addEscapedValue(name, charSequenceEscaper().escape(name, value));
202 }
203
204 @Override
205 public CombinedHttpHeadersImpl add(CharSequence name, CharSequence... values) {
206 return addEscapedValue(name, commaSeparate(name, charSequenceEscaper(), values));
207 }
208
209 @Override
210 public CombinedHttpHeadersImpl add(CharSequence name, Iterable<? extends CharSequence> values) {
211 return addEscapedValue(name, commaSeparate(name, charSequenceEscaper(), values));
212 }
213
214 @Override
215 public CombinedHttpHeadersImpl addObject(CharSequence name, Object value) {
216 return addEscapedValue(name, commaSeparate(name, objectEscaper(), value));
217 }
218
219 @Override
220 public CombinedHttpHeadersImpl addObject(CharSequence name, Iterable<?> values) {
221 return addEscapedValue(name, commaSeparate(name, objectEscaper(), values));
222 }
223
224 @Override
225 public CombinedHttpHeadersImpl addObject(CharSequence name, Object... values) {
226 return addEscapedValue(name, commaSeparate(name, objectEscaper(), values));
227 }
228
229 @Override
230 public CombinedHttpHeadersImpl set(CharSequence name, CharSequence... values) {
231 set(name, commaSeparate(name, charSequenceEscaper(), values));
232 return this;
233 }
234
235 @Override
236 public CombinedHttpHeadersImpl set(CharSequence name, Iterable<? extends CharSequence> values) {
237 set(name, commaSeparate(name, charSequenceEscaper(), values));
238 return this;
239 }
240
241 @Override
242 public CombinedHttpHeadersImpl setObject(CharSequence name, Object value) {
243 set(name, commaSeparate(name, objectEscaper(), value));
244 return this;
245 }
246
247 @Override
248 public CombinedHttpHeadersImpl setObject(CharSequence name, Object... values) {
249 set(name, commaSeparate(name, objectEscaper(), values));
250 return this;
251 }
252
253 @Override
254 public CombinedHttpHeadersImpl setObject(CharSequence name, Iterable<?> values) {
255 set(name, commaSeparate(name, objectEscaper(), values));
256 return this;
257 }
258
259 private static boolean cannotBeCombined(CharSequence name) {
260 return SET_COOKIE.contentEqualsIgnoreCase(name);
261 }
262
263 private CombinedHttpHeadersImpl addEscapedValue(CharSequence name, CharSequence escapedValue) {
264 CharSequence currentValue = get(name);
265 if (currentValue == null || cannotBeCombined(name)) {
266 super.add(name, escapedValue);
267 } else {
268 set(name, commaSeparateEscapedValues(currentValue, escapedValue));
269 }
270 return this;
271 }
272
273 private static <T> CharSequence commaSeparate(CharSequence name, CsvValueEscaper<T> escaper, T... values) {
274 StringBuilder sb = new StringBuilder(values.length * VALUE_LENGTH_ESTIMATE);
275 if (values.length > 0) {
276 int end = values.length - 1;
277 for (int i = 0; i < end; i++) {
278 sb.append(escaper.escape(name, values[i])).append(COMMA);
279 }
280 sb.append(escaper.escape(name, values[end]));
281 }
282 return sb;
283 }
284
285 private static <T> CharSequence commaSeparate(CharSequence name, CsvValueEscaper<T> escaper,
286 Iterable<? extends T> values) {
287 @SuppressWarnings("rawtypes")
288 final StringBuilder sb = values instanceof Collection
289 ? new StringBuilder(((Collection) values).size() * VALUE_LENGTH_ESTIMATE) : new StringBuilder();
290 Iterator<? extends T> iterator = values.iterator();
291 if (iterator.hasNext()) {
292 T next = iterator.next();
293 while (iterator.hasNext()) {
294 sb.append(escaper.escape(name, next)).append(COMMA);
295 next = iterator.next();
296 }
297 sb.append(escaper.escape(name, next));
298 }
299 return sb;
300 }
301
302 private static CharSequence commaSeparateEscapedValues(CharSequence currentValue, CharSequence value) {
303 return new StringBuilder(currentValue.length() + 1 + value.length())
304 .append(currentValue)
305 .append(COMMA)
306 .append(value);
307 }
308
309
310
311
312
313
314 private interface CsvValueEscaper<T> {
315
316
317
318
319
320
321 CharSequence escape(CharSequence name, T value);
322 }
323 }
324 }