1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http.message;
29
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.Iterator;
34 import java.util.LinkedHashSet;
35 import java.util.List;
36 import java.util.Set;
37 import java.util.TreeSet;
38 import java.util.function.Consumer;
39
40 import org.apache.hc.core5.http.EntityDetails;
41 import org.apache.hc.core5.http.FormattedHeader;
42 import org.apache.hc.core5.http.Header;
43 import org.apache.hc.core5.http.HeaderElement;
44 import org.apache.hc.core5.http.HeaderElements;
45 import org.apache.hc.core5.http.HttpHeaders;
46 import org.apache.hc.core5.http.HttpMessage;
47 import org.apache.hc.core5.http.HttpResponse;
48 import org.apache.hc.core5.http.HttpStatus;
49 import org.apache.hc.core5.http.MessageHeaders;
50 import org.apache.hc.core5.http.Method;
51 import org.apache.hc.core5.http.NameValuePair;
52 import org.apache.hc.core5.util.Args;
53 import org.apache.hc.core5.util.CharArrayBuffer;
54 import org.apache.hc.core5.util.Tokenizer;
55
56
57
58
59
60
61 public class MessageSupport {
62
63 private MessageSupport() {
64
65 }
66
67
68
69
70 public static void formatTokens(final CharArrayBuffer dst, final List<String> tokens) {
71 Args.notNull(dst, "Destination");
72 if (tokens == null) {
73 return;
74 }
75 for (int i = 0; i < tokens.size(); i++) {
76 final String element = tokens.get(i);
77 if (i > 0) {
78 dst.append(", ");
79 }
80 dst.append(element);
81 }
82 }
83
84 public static void formatTokens(final CharArrayBuffer dst, final String... tokens) {
85 Args.notNull(dst, "Destination");
86 boolean first = true;
87 for (final String token : tokens) {
88 if (!first) {
89 dst.append(", ");
90 }
91 dst.append(token);
92 first = false;
93 }
94 }
95
96 public static void formatTokens(final CharArrayBuffer dst, final Set<String> tokens) {
97 Args.notNull(dst, "Destination");
98 if (tokens == null) {
99 return;
100 }
101 boolean first = true;
102 for (final String token : tokens) {
103 if (!first) {
104 dst.append(", ");
105 }
106 dst.append(token);
107 first = false;
108 }
109 }
110
111
112
113
114 @Deprecated
115 public static Header format(final String name, final Set<String> tokens) {
116 return header(name, tokens);
117 }
118
119
120
121
122 public static Header headerOfTokens(final String name, final List<String> tokens) {
123 Args.notBlank(name, "Header name");
124 if (tokens == null) {
125 return null;
126 }
127 final CharArrayBuffer buffer = new CharArrayBuffer(256);
128 buffer.append(name);
129 buffer.append(": ");
130 formatTokens(buffer, tokens);
131 return BufferedHeader.create(buffer);
132 }
133
134
135
136
137 public static Header header(final String name, final Set<String> tokens) {
138 Args.notBlank(name, "Header name");
139 if (tokens == null) {
140 return null;
141 }
142 final CharArrayBuffer buffer = new CharArrayBuffer(256);
143 buffer.append(name);
144 buffer.append(": ");
145 formatTokens(buffer, tokens);
146 return BufferedHeader.create(buffer);
147 }
148
149 private static final Tokenizer.Delimiter COMMA = Tokenizer.delimiters(',');
150
151
152
153 public static Header header(final String name, final String... tokens) {
154 Args.notBlank(name, "Header name");
155 final CharArrayBuffer buffer = new CharArrayBuffer(256);
156 buffer.append(name);
157 buffer.append(": ");
158 formatTokens(buffer, tokens);
159 return BufferedHeader.create(buffer);
160 }
161
162
163
164
165 @Deprecated
166 public static Header format(final String name, final String... tokens) {
167 return headerOfTokens(name, Arrays.asList(tokens));
168 }
169
170
171
172
173 public static void parseTokens(final CharSequence src, final ParserCursor cursor, final Consumer<String> consumer) {
174 Args.notNull(src, "Source");
175 Args.notNull(cursor, "Cursor");
176 Args.notNull(consumer, "Consumer");
177 while (!cursor.atEnd()) {
178 final int pos = cursor.getPos();
179 if (src.charAt(pos) == ',') {
180 cursor.updatePos(pos + 1);
181 }
182 final String token = Tokenizer.INSTANCE.parseToken(src, cursor, COMMA);
183 consumer.accept(token);
184 }
185 }
186
187
188
189
190 public static void parseTokens(final Header header, final Consumer<String> consumer) {
191 Args.notNull(header, "Header");
192 if (header instanceof FormattedHeader) {
193 final CharArrayBuffer buf = ((FormattedHeader) header).getBuffer();
194 final ParserCursor cursor = new ParserCursor(0, buf.length());
195 cursor.updatePos(((FormattedHeader) header).getValuePos());
196 parseTokens(buf, cursor, consumer);
197 } else {
198 final String value = header.getValue();
199 final ParserCursor cursor = new ParserCursor(0, value.length());
200 parseTokens(value, cursor, consumer);
201 }
202 }
203
204
205
206
207 public static void parseTokens(final MessageHeaders headers, final String headerName, final Consumer<String> consumer) {
208 Args.notNull(headers, "Headers");
209 final Iterator<Header> it = headers.headerIterator(headerName);
210 while (it.hasNext()) {
211 parseTokens(it.next(), consumer);
212 }
213 }
214
215 public static Set<String> parseTokens(final CharSequence src, final ParserCursor cursor) {
216 Args.notNull(src, "Source");
217 Args.notNull(cursor, "Cursor");
218 final Set<String> tokens = new LinkedHashSet<>();
219 parseTokens(src, cursor, tokens::add);
220 return tokens;
221 }
222
223 public static Set<String> parseTokens(final Header header) {
224 Args.notNull(header, "Header");
225 final Set<String> tokens = new LinkedHashSet<>();
226 parseTokens(header, tokens::add);
227 return tokens;
228 }
229
230
231
232
233 public static Iterator<String> iterateTokens(final MessageHeaders headers, final String name) {
234 Args.notNull(headers, "Message headers");
235 Args.notBlank(name, "Header name");
236 return new BasicTokenIterator(headers.headerIterator(name));
237 }
238
239
240
241
242 public static void formatElements(final CharArrayBuffer dst, final List<HeaderElement> elements) {
243 Args.notNull(dst, "Destination");
244 if (elements == null) {
245 return;
246 }
247 for (int i = 0; i < elements.size(); i++) {
248 final HeaderElement element = elements.get(i);
249 if (i > 0) {
250 dst.append(", ");
251 }
252 BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false);
253 }
254 }
255
256
257
258
259 public static void formatElements(final CharArrayBuffer dst, final HeaderElement... elements) {
260 formatElements(dst, Arrays.asList(elements));
261 }
262
263
264
265
266 public static Header headerOfElements(final String name, final List<HeaderElement> elements) {
267 Args.notBlank(name, "Header name");
268 if (elements == null) {
269 return null;
270 }
271 final CharArrayBuffer buffer = new CharArrayBuffer(256);
272 buffer.append(name);
273 buffer.append(": ");
274 formatElements(buffer, elements);
275 return BufferedHeader.create(buffer);
276 }
277
278
279
280
281 public static Header header(final String name, final HeaderElement... elements) {
282 Args.notBlank(name, "Header name");
283 final CharArrayBuffer buffer = new CharArrayBuffer(256);
284 buffer.append(name);
285 buffer.append(": ");
286 formatElements(buffer, elements);
287 return BufferedHeader.create(buffer);
288 }
289
290
291
292
293 public static void parseElements(final CharSequence buffer, final ParserCursor cursor, final Consumer<HeaderElement> consumer) {
294 Args.notNull(buffer, "Char sequence");
295 Args.notNull(cursor, "Parser cursor");
296 Args.notNull(consumer, "Consumer");
297 while (!cursor.atEnd()) {
298 final HeaderElement element = BasicHeaderValueParser.INSTANCE.parseHeaderElement(buffer, cursor);
299 consumer.accept(element);
300 if (!cursor.atEnd()) {
301 final char ch = buffer.charAt(cursor.getPos());
302 if (ch == ',') {
303 cursor.updatePos(cursor.getPos() + 1);
304 }
305 }
306 }
307 }
308
309
310
311
312 public static void parseElements(final Header header, final Consumer<HeaderElement> consumer) {
313 Args.notNull(header, "Header");
314 if (header instanceof FormattedHeader) {
315 final CharArrayBuffer buf = ((FormattedHeader) header).getBuffer();
316 final ParserCursor cursor = new ParserCursor(0, buf.length());
317 cursor.updatePos(((FormattedHeader) header).getValuePos());
318 parseElements(buf, cursor, consumer);
319 } else {
320 final String value = header.getValue();
321 final ParserCursor cursor = new ParserCursor(0, value.length());
322 parseElements(value, cursor, consumer);
323 }
324 }
325
326
327
328
329 public static void parseElements(final MessageHeaders headers, final String headerName, final Consumer<HeaderElement> consumer) {
330 Args.notNull(headers, "Headers");
331 final Iterator<Header> it = headers.headerIterator(headerName);
332 while (it.hasNext()) {
333 parseElements(it.next(), consumer);
334 }
335 }
336
337
338
339
340 @Deprecated
341 public static HeaderElement[] parse(final Header header) {
342 final List<HeaderElement> elements = new ArrayList<>();
343 parseElements(header, elements::add);
344 return elements.toArray(new HeaderElement[]{});
345 }
346
347
348
349
350 public static List<HeaderElement> parseElements(final Header header) {
351 final List<HeaderElement> elements = new ArrayList<>();
352 parseElements(header, elements::add);
353 return elements;
354 }
355
356 public static Iterator<HeaderElement> iterate(final MessageHeaders headers, final String name) {
357 Args.notNull(headers, "Message headers");
358 Args.notBlank(name, "Header name");
359 return new BasicHeaderElementIterator(headers.headerIterator(name));
360 }
361
362
363
364
365 public static void formatParameters(final CharArrayBuffer dst, final List<NameValuePair> params) {
366 Args.notNull(dst, "Destination");
367 if (params == null) {
368 return;
369 }
370 for (int i = 0; i < params.size(); i++) {
371 final NameValuePair param = params.get(i);
372 if (i > 0) {
373 dst.append("; ");
374 }
375 BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false);
376 }
377 }
378
379
380
381
382 public static void formatParameters(final CharArrayBuffer dst, final NameValuePair... params) {
383 Args.notNull(dst, "Destination");
384 if (params == null) {
385 return;
386 }
387 boolean first = true;
388 for (final NameValuePair param : params) {
389 if (!first) {
390 dst.append("; ");
391 }
392 BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false);
393 first = false;
394 }
395 }
396
397
398
399
400 public static void parseParameters(final CharSequence src, final ParserCursor cursor, final Consumer<NameValuePair> consumer) {
401 Args.notNull(src, "Source");
402 Args.notNull(cursor, "Cursor");
403 Args.notNull(consumer, "Consumer");
404
405 while (!cursor.atEnd()) {
406 final NameValuePair param = BasicHeaderValueParser.INSTANCE.parseNameValuePair(src, cursor);
407 consumer.accept(param);
408 if (!cursor.atEnd()) {
409 final char ch = src.charAt(cursor.getPos());
410 if (ch == ';') {
411 cursor.updatePos(cursor.getPos() + 1);
412 }
413 if (ch == ',') {
414 break;
415 }
416 }
417 }
418 }
419
420 public static void addContentTypeHeader(final HttpMessage message, final EntityDetails entity) {
421 if (entity != null && entity.getContentType() != null && !message.containsHeader(HttpHeaders.CONTENT_TYPE)) {
422 message.addHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, entity.getContentType()));
423 }
424 }
425
426 public static void addContentEncodingHeader(final HttpMessage message, final EntityDetails entity) {
427 if (entity != null && entity.getContentEncoding() != null && !message.containsHeader(HttpHeaders.CONTENT_ENCODING)) {
428 message.addHeader(new BasicHeader(HttpHeaders.CONTENT_ENCODING, entity.getContentEncoding()));
429 }
430 }
431
432 public static void addTrailerHeader(final HttpMessage message, final EntityDetails entity) {
433 if (entity != null && !message.containsHeader(HttpHeaders.TRAILER)) {
434 final Set<String> trailerNames = entity.getTrailerNames();
435 if (trailerNames != null && !trailerNames.isEmpty()) {
436 message.setHeader(MessageSupport.header(HttpHeaders.TRAILER, trailerNames));
437 }
438 }
439 }
440
441
442
443
444 public static boolean canResponseHaveBody(final String method, final HttpResponse response) {
445 if (Method.HEAD.isSame(method)) {
446 return false;
447 }
448 final int status = response.getCode();
449 if (Method.CONNECT.isSame(method) && status == HttpStatus.SC_OK) {
450 return false;
451 }
452 return status >= HttpStatus.SC_SUCCESS
453 && status != HttpStatus.SC_NO_CONTENT
454 && status != HttpStatus.SC_NOT_MODIFIED;
455 }
456
457 private final static Set<String> HOP_BY_HOP;
458
459 static {
460 final TreeSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
461 set.add(HttpHeaders.CONNECTION);
462 set.add(HttpHeaders.CONTENT_LENGTH);
463 set.add(HttpHeaders.TRANSFER_ENCODING);
464 set.add(HttpHeaders.HOST);
465 set.add(HttpHeaders.KEEP_ALIVE);
466 set.add(HttpHeaders.TE);
467 set.add(HttpHeaders.UPGRADE);
468 set.add(HttpHeaders.PROXY_AUTHORIZATION);
469 set.add("Proxy-Authentication-Info");
470 set.add(HttpHeaders.PROXY_AUTHENTICATE);
471 HOP_BY_HOP = Collections.unmodifiableSet(set);
472 }
473
474
475
476
477 public static boolean isHopByHop(final String headerName) {
478 if (headerName == null) {
479 return false;
480 }
481 return HOP_BY_HOP.contains(headerName);
482 }
483
484
485
486
487 public static Set<String> hopByHopConnectionSpecific(final MessageHeaders headers) {
488 final Header connectionHeader = headers.getFirstHeader(HttpHeaders.CONNECTION);
489 final String connDirective = connectionHeader != null ? connectionHeader.getValue() : null;
490
491 if (connDirective != null &&
492 !connDirective.equalsIgnoreCase(HeaderElements.CLOSE) &&
493 !connDirective.equalsIgnoreCase(HeaderElements.KEEP_ALIVE)) {
494 final TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
495 result.addAll(HOP_BY_HOP);
496 result.addAll(parseTokens(connectionHeader));
497 return result;
498 } else {
499 return HOP_BY_HOP;
500 }
501 }
502
503 }