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.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   * Support methods for HTTP message processing.
58   *
59   * @since 5.0
60   */
61  public class MessageSupport {
62  
63      private MessageSupport() {
64          // Do not allow utility class to be instantiated.
65      }
66  
67      /**
68       * @since 5.3
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      * @deprecated Use {@link #header(String, Set)}
113      */
114     @Deprecated
115     public static Header format(final String name, final Set<String> tokens) {
116         return header(name, tokens);
117     }
118 
119     /**
120      * @since 5.3
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      * @since 5.3
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      * @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     /**
171      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
258      */
259     public static void formatElements(final CharArrayBuffer dst, final HeaderElement... elements) {
260         formatElements(dst, Arrays.asList(elements));
261     }
262 
263     /**
264      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @deprecated Use {@link #parseElements(Header, Consumer)}
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since 5.3
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      * @since  5.0
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      * @since 5.3
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      * @since 5.3
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         // Disregard most common 'Close' and 'Keep-Alive' tokens
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 }