View Javadoc
1   /*
2    * Copyright 2012 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    *   https://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  package io.netty.handler.codec.http;
17  
18  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
19  import static io.netty.util.internal.ObjectUtil.checkNonEmptyAfterTrim;
20  
21  import io.netty.buffer.ByteBuf;
22  import io.netty.util.CharsetUtil;
23  import io.netty.util.internal.ObjectUtil;
24  
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  
28  /**
29   * The version of HTTP or its derived protocols, such as
30   * <a href="https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
31   * <a href="https://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
32   */
33  public class HttpVersion implements Comparable<HttpVersion> {
34  
35      private static final Pattern VERSION_PATTERN =
36          Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)");
37  
38      static final String HTTP_1_0_STRING = "HTTP/1.0";
39      static final String HTTP_1_1_STRING = "HTTP/1.1";
40  
41      /**
42       * HTTP/1.0
43       */
44      public static final HttpVersion HTTP_1_0 = new HttpVersion("HTTP", 1, 0, false, true);
45  
46      /**
47       * HTTP/1.1
48       */
49      public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true, true);
50  
51      /**
52       * Returns an existing or new {@link HttpVersion} instance which matches to
53       * the specified protocol version string.  If the specified {@code text} is
54       * equal to {@code "HTTP/1.0"}, {@link #HTTP_1_0} will be returned.  If the
55       * specified {@code text} is equal to {@code "HTTP/1.1"}, {@link #HTTP_1_1}
56       * will be returned.  Otherwise, a new {@link HttpVersion} instance will be
57       * returned.
58       */
59      public static HttpVersion valueOf(String text) {
60          ObjectUtil.checkNotNull(text, "text");
61  
62          // super fast-path
63          if (text == HTTP_1_1_STRING) {
64              return HTTP_1_1;
65          } else if (text == HTTP_1_0_STRING) {
66              return HTTP_1_0;
67          }
68  
69          text = text.trim();
70  
71          if (text.isEmpty()) {
72              throw new IllegalArgumentException("text is empty (possibly HTTP/0.9)");
73          }
74  
75          // Try to match without convert to uppercase first as this is what 99% of all clients
76          // will send anyway. Also there is a change to the RFC to make it clear that it is
77          // expected to be case-sensitive
78          //
79          // See:
80          // * https://trac.tools.ietf.org/wg/httpbis/trac/ticket/1
81          // * https://trac.tools.ietf.org/wg/httpbis/trac/wiki
82          //
83          HttpVersion version = version0(text);
84          if (version == null) {
85              version = new HttpVersion(text, true);
86          }
87          return version;
88      }
89  
90      private static HttpVersion version0(String text) {
91          if (HTTP_1_1_STRING.equals(text)) {
92              return HTTP_1_1;
93          }
94          if (HTTP_1_0_STRING.equals(text)) {
95              return HTTP_1_0;
96          }
97          return null;
98      }
99  
100     private final String protocolName;
101     private final int majorVersion;
102     private final int minorVersion;
103     private final String text;
104     private final boolean keepAliveDefault;
105     private final byte[] bytes;
106 
107     /**
108      * Creates a new HTTP version with the specified version string.  You will
109      * not need to create a new instance unless you are implementing a protocol
110      * derived from HTTP, such as
111      * <a href="https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
112      * <a href="https://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
113      *
114      * @param keepAliveDefault
115      *        {@code true} if and only if the connection is kept alive unless
116      *        the {@code "Connection"} header is set to {@code "close"} explicitly.
117      */
118     public HttpVersion(String text, boolean keepAliveDefault) {
119         text = checkNonEmptyAfterTrim(text, "text").toUpperCase();
120 
121         Matcher m = VERSION_PATTERN.matcher(text);
122         if (!m.matches()) {
123             throw new IllegalArgumentException("invalid version format: " + text);
124         }
125 
126         protocolName = m.group(1);
127         majorVersion = Integer.parseInt(m.group(2));
128         minorVersion = Integer.parseInt(m.group(3));
129         this.text = protocolName + '/' + majorVersion + '.' + minorVersion;
130         this.keepAliveDefault = keepAliveDefault;
131         bytes = null;
132     }
133 
134     /**
135      * Creates a new HTTP version with the specified protocol name and version
136      * numbers.  You will not need to create a new instance unless you are
137      * implementing a protocol derived from HTTP, such as
138      * <a href="https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
139      * <a href="https://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>
140      *
141      * @param keepAliveDefault
142      *        {@code true} if and only if the connection is kept alive unless
143      *        the {@code "Connection"} header is set to {@code "close"} explicitly.
144      */
145     public HttpVersion(
146             String protocolName, int majorVersion, int minorVersion,
147             boolean keepAliveDefault) {
148         this(protocolName, majorVersion, minorVersion, keepAliveDefault, false);
149     }
150 
151     private HttpVersion(
152             String protocolName, int majorVersion, int minorVersion,
153             boolean keepAliveDefault, boolean bytes) {
154         protocolName = checkNonEmptyAfterTrim(protocolName, "protocolName").toUpperCase();
155 
156         for (int i = 0; i < protocolName.length(); i ++) {
157             if (Character.isISOControl(protocolName.charAt(i)) ||
158                     Character.isWhitespace(protocolName.charAt(i))) {
159                 throw new IllegalArgumentException("invalid character in protocolName");
160             }
161         }
162 
163         checkPositiveOrZero(majorVersion, "majorVersion");
164         checkPositiveOrZero(minorVersion, "minorVersion");
165 
166         this.protocolName = protocolName;
167         this.majorVersion = majorVersion;
168         this.minorVersion = minorVersion;
169         text = protocolName + '/' + majorVersion + '.' + minorVersion;
170         this.keepAliveDefault = keepAliveDefault;
171 
172         if (bytes) {
173             this.bytes = text.getBytes(CharsetUtil.US_ASCII);
174         } else {
175             this.bytes = null;
176         }
177     }
178 
179     /**
180      * Returns the name of the protocol such as {@code "HTTP"} in {@code "HTTP/1.0"}.
181      */
182     public String protocolName() {
183         return protocolName;
184     }
185 
186     /**
187      * Returns the name of the protocol such as {@code 1} in {@code "HTTP/1.0"}.
188      */
189     public int majorVersion() {
190         return majorVersion;
191     }
192 
193     /**
194      * Returns the name of the protocol such as {@code 0} in {@code "HTTP/1.0"}.
195      */
196     public int minorVersion() {
197         return minorVersion;
198     }
199 
200     /**
201      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
202      */
203     public String text() {
204         return text;
205     }
206 
207     /**
208      * Returns {@code true} if and only if the connection is kept alive unless
209      * the {@code "Connection"} header is set to {@code "close"} explicitly.
210      */
211     public boolean isKeepAliveDefault() {
212         return keepAliveDefault;
213     }
214 
215     /**
216      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
217      */
218     @Override
219     public String toString() {
220         return text();
221     }
222 
223     @Override
224     public int hashCode() {
225         return (protocolName().hashCode() * 31 + majorVersion()) * 31 +
226                minorVersion();
227     }
228 
229     @Override
230     public boolean equals(Object o) {
231         if (!(o instanceof HttpVersion)) {
232             return false;
233         }
234 
235         HttpVersion that = (HttpVersion) o;
236         return minorVersion() == that.minorVersion() &&
237                majorVersion() == that.majorVersion() &&
238                protocolName().equals(that.protocolName());
239     }
240 
241     @Override
242     public int compareTo(HttpVersion o) {
243         int v = protocolName().compareTo(o.protocolName());
244         if (v != 0) {
245             return v;
246         }
247 
248         v = majorVersion() - o.majorVersion();
249         if (v != 0) {
250             return v;
251         }
252 
253         return minorVersion() - o.minorVersion();
254     }
255 
256     void encode(ByteBuf buf) {
257         if (bytes == null) {
258             buf.writeCharSequence(text, CharsetUtil.US_ASCII);
259         } else {
260             buf.writeBytes(bytes);
261         }
262     }
263 }