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