View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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   * Support methods for HTTP message processing.
59   *
60   * @since 5.0
61   */
62  public class MessageSupport {
63  
64      private MessageSupport() {
65          // Do not allow utility class to be instantiated.
66      }
67  
68      /**
69       * @since 5.3
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      * @deprecated Use {@link #header(String, Set)}
114      */
115     @Deprecated
116     public static Header format(final String name, final Set<String> tokens) {
117         return header(name, tokens);
118     }
119 
120     /**
121      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @deprecated use {@link #header(String, String...)}
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
260      */
261     public static void formatElements(final CharArrayBuffer dst, final HeaderElement... elements) {
262         formatElements(dst, Arrays.asList(elements));
263     }
264 
265     /**
266      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @deprecated Use {@link #parseElements(Header, Consumer)}
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since  5.0
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      * @since 5.3
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      * @since 5.3
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         // Disregard most common 'Close' and 'Keep-Alive' tokens
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 }