View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.codec.http;
18  
19  import io.netty.buffer.ByteBuf;
20  
21  import java.util.Iterator;
22  import java.util.List;
23  
24  public final class HttpHeaderUtil {
25  
26      /**
27       * Returns {@code true} if and only if the connection can remain open and
28       * thus 'kept alive'.  This methods respects the value of the
29       * {@code "Connection"} header first and then the return value of
30       * {@link HttpVersion#isKeepAliveDefault()}.
31       */
32      public static boolean isKeepAlive(HttpMessage message) {
33          CharSequence connection = message.headers().get(HttpHeaderNames.CONNECTION);
34          if (connection != null && HttpHeaderValues.CLOSE.equalsIgnoreCase(connection)) {
35              return false;
36          }
37  
38          if (message.protocolVersion().isKeepAliveDefault()) {
39              return !HttpHeaderValues.CLOSE.equalsIgnoreCase(connection);
40          } else {
41              return HttpHeaderValues.KEEP_ALIVE.equalsIgnoreCase(connection);
42          }
43      }
44  
45      /**
46       * Sets the value of the {@code "Connection"} header depending on the
47       * protocol version of the specified message.  This getMethod sets or removes
48       * the {@code "Connection"} header depending on what the default keep alive
49       * mode of the message's protocol version is, as specified by
50       * {@link HttpVersion#isKeepAliveDefault()}.
51       * <ul>
52       * <li>If the connection is kept alive by default:
53       *     <ul>
54       *     <li>set to {@code "close"} if {@code keepAlive} is {@code false}.</li>
55       *     <li>remove otherwise.</li>
56       *     </ul></li>
57       * <li>If the connection is closed by default:
58       *     <ul>
59       *     <li>set to {@code "keep-alive"} if {@code keepAlive} is {@code true}.</li>
60       *     <li>remove otherwise.</li>
61       *     </ul></li>
62       * </ul>
63       */
64      public static void setKeepAlive(HttpMessage message, boolean keepAlive) {
65          HttpHeaders h = message.headers();
66          if (message.protocolVersion().isKeepAliveDefault()) {
67              if (keepAlive) {
68                  h.remove(HttpHeaderNames.CONNECTION);
69              } else {
70                  h.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
71              }
72          } else {
73              if (keepAlive) {
74                  h.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
75              } else {
76                  h.remove(HttpHeaderNames.CONNECTION);
77              }
78          }
79      }
80  
81      /**
82       * Returns the length of the content.  Please note that this value is
83       * not retrieved from {@link HttpContent#content()} but from the
84       * {@code "Content-Length"} header, and thus they are independent from each
85       * other.
86       *
87       * @return the content length
88       *
89       * @throws NumberFormatException
90       *         if the message does not have the {@code "Content-Length"} header
91       *         or its value is not a number
92       */
93      public static long getContentLength(HttpMessage message) {
94          Long value = message.headers().getLong(HttpHeaderNames.CONTENT_LENGTH);
95          if (value != null) {
96              return value;
97          }
98  
99          // We know the content length if it's a Web Socket message even if
100         // Content-Length header is missing.
101         long webSocketContentLength = getWebSocketContentLength(message);
102         if (webSocketContentLength >= 0) {
103             return webSocketContentLength;
104         }
105 
106         // Otherwise we don't.
107         throw new NumberFormatException("header not found: " + HttpHeaderNames.CONTENT_LENGTH);
108     }
109 
110     /**
111      * Returns the length of the content.  Please note that this value is
112      * not retrieved from {@link HttpContent#content()} but from the
113      * {@code "Content-Length"} header, and thus they are independent from each
114      * other.
115      *
116      * @return the content length or {@code defaultValue} if this message does
117      *         not have the {@code "Content-Length"} header or its value is not
118      *         a number
119      */
120     public static long getContentLength(HttpMessage message, long defaultValue) {
121         Long value = message.headers().getLong(HttpHeaderNames.CONTENT_LENGTH);
122         if (value != null) {
123             return value;
124         }
125 
126         // We know the content length if it's a Web Socket message even if
127         // Content-Length header is missing.
128         long webSocketContentLength = getWebSocketContentLength(message);
129         if (webSocketContentLength >= 0) {
130             return webSocketContentLength;
131         }
132 
133         // Otherwise we don't.
134         return defaultValue;
135     }
136 
137     /**
138      * Returns the content length of the specified web socket message.  If the
139      * specified message is not a web socket message, {@code -1} is returned.
140      */
141     private static int getWebSocketContentLength(HttpMessage message) {
142         // WebSockset messages have constant content-lengths.
143         HttpHeaders h = message.headers();
144         if (message instanceof HttpRequest) {
145             HttpRequest req = (HttpRequest) message;
146             if (HttpMethod.GET.equals(req.method()) &&
147                     h.contains(HttpHeaderNames.SEC_WEBSOCKET_KEY1) &&
148                     h.contains(HttpHeaderNames.SEC_WEBSOCKET_KEY2)) {
149                 return 8;
150             }
151         } else if (message instanceof HttpResponse) {
152             HttpResponse res = (HttpResponse) message;
153             if (res.status().code() == 101 &&
154                     h.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN) &&
155                     h.contains(HttpHeaderNames.SEC_WEBSOCKET_LOCATION)) {
156                 return 16;
157             }
158         }
159 
160         // Not a web socket message
161         return -1;
162     }
163 
164     /**
165      * Sets the {@code "Content-Length"} header.
166      */
167     public static void setContentLength(HttpMessage message, long length) {
168         message.headers().setLong(HttpHeaderNames.CONTENT_LENGTH, length);
169     }
170 
171     public static boolean isContentLengthSet(HttpMessage m) {
172         return m.headers().contains(HttpHeaderNames.CONTENT_LENGTH);
173     }
174 
175     /**
176      * Returns {@code true} if and only if the specified message contains the
177      * {@code "Expect: 100-continue"} header.
178      */
179     public static boolean is100ContinueExpected(HttpMessage message) {
180         // Expect: 100-continue is for requests only.
181         if (!(message instanceof HttpRequest)) {
182             return false;
183         }
184 
185         // It works only on HTTP/1.1 or later.
186         if (message.protocolVersion().compareTo(HttpVersion.HTTP_1_1) < 0) {
187             return false;
188         }
189 
190         // In most cases, there will be one or zero 'Expect' header.
191         CharSequence value = message.headers().get(HttpHeaderNames.EXPECT);
192         if (value == null) {
193             return false;
194         }
195         if (HttpHeaderValues.CONTINUE.equalsIgnoreCase(value)) {
196             return true;
197         }
198 
199         // Multiple 'Expect' headers.  Search through them.
200         return message.headers().contains(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE, true);
201     }
202 
203     /**
204      * Sets or removes the {@code "Expect: 100-continue"} header to / from the
205      * specified message.  If the specified {@code value} is {@code true},
206      * the {@code "Expect: 100-continue"} header is set and all other previous
207      * {@code "Expect"} headers are removed.  Otherwise, all {@code "Expect"}
208      * headers are removed completely.
209      */
210     public static void set100ContinueExpected(HttpMessage message, boolean expected) {
211         if (expected) {
212             message.headers().set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE);
213         } else {
214             message.headers().remove(HttpHeaderNames.EXPECT);
215         }
216     }
217 
218     /**
219      * Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked
220      *
221      * @param message The message to check
222      * @return True if transfer encoding is chunked, otherwise false
223      */
224     public static boolean isTransferEncodingChunked(HttpMessage message) {
225         return message.headers().contains(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true);
226     }
227 
228     public static void setTransferEncodingChunked(HttpMessage m, boolean chunked) {
229         if (chunked) {
230             m.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
231             m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
232         } else {
233             List<CharSequence> values = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING);
234             if (values.isEmpty()) {
235                 return;
236             }
237             Iterator<CharSequence> valuesIt = values.iterator();
238             while (valuesIt.hasNext()) {
239                 CharSequence value = valuesIt.next();
240                 if (HttpHeaderValues.CHUNKED.equalsIgnoreCase(value)) {
241                     valuesIt.remove();
242                 }
243             }
244             if (values.isEmpty()) {
245                 m.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
246             } else {
247                 m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, values);
248             }
249         }
250     }
251 
252     static void encodeAscii0(CharSequence seq, ByteBuf buf) {
253         int length = seq.length();
254         for (int i = 0 ; i < length; i++) {
255             buf.writeByte((byte) seq.charAt(i));
256         }
257     }
258 
259     private HttpHeaderUtil() { }
260 }