1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
30
31
32
33 public class HttpVersion implements Comparable<HttpVersion> {
34
35 private static final Pattern VERSION_PATTERN =
36 Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)");
37 static final String HTTP_1_0_STRING = "HTTP/1.0";
38 static final String HTTP_1_1_STRING = "HTTP/1.1";
39
40
41
42
43 public static final HttpVersion HTTP_1_0 = new HttpVersion("HTTP", 1, 0, false, true);
44
45
46
47
48 public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true, true);
49
50
51
52
53
54
55
56
57
58 public static HttpVersion valueOf(String text) {
59 return valueOf(text, false);
60 }
61
62 static HttpVersion valueOf(String text, boolean strict) {
63 ObjectUtil.checkNotNull(text, "text");
64
65
66 if (text == HTTP_1_1_STRING) {
67 return HTTP_1_1;
68 }
69 if (text == HTTP_1_0_STRING) {
70 return HTTP_1_0;
71 }
72
73 text = text.trim();
74
75 if (text.isEmpty()) {
76 throw new IllegalArgumentException("text is empty (possibly HTTP/0.9)");
77 }
78
79
80
81
82
83
84
85
86
87 HttpVersion version = version0(text);
88 if (version == null) {
89 version = new HttpVersion(text, strict, true);
90 }
91 return version;
92 }
93
94 private static HttpVersion version0(String text) {
95 if (HTTP_1_1_STRING.equals(text)) {
96 return HTTP_1_1;
97 }
98 if (HTTP_1_0_STRING.equals(text)) {
99 return HTTP_1_0;
100 }
101 return null;
102 }
103
104 private final String protocolName;
105 private final int majorVersion;
106 private final int minorVersion;
107 private final String text;
108 private final boolean keepAliveDefault;
109 private final byte[] bytes;
110
111
112
113
114
115
116
117
118
119
120
121
122 public HttpVersion(String text, boolean keepAliveDefault) {
123 this(text, false, keepAliveDefault);
124 }
125
126 HttpVersion(String text, boolean strict, boolean keepAliveDefault) {
127 text = checkNonEmptyAfterTrim(text, "text").toUpperCase();
128
129 if (strict) {
130
131
132
133
134 if (text.length() != 8 || !text.startsWith("HTTP/") || text.charAt(6) != '.') {
135 throw new IllegalArgumentException("invalid version format: " + text);
136 }
137 protocolName = "HTTP";
138 majorVersion = toDecimal(text.charAt(5));
139 minorVersion = toDecimal(text.charAt(7));
140 } else {
141 Matcher m = VERSION_PATTERN.matcher(text);
142 if (!m.matches()) {
143 throw new IllegalArgumentException("invalid version format: " + text);
144 }
145
146 protocolName = m.group(1);
147 majorVersion = Integer.parseInt(m.group(2));
148 minorVersion = Integer.parseInt(m.group(3));
149 }
150
151 this.text = protocolName + '/' + majorVersion + '.' + minorVersion;
152 this.keepAliveDefault = keepAliveDefault;
153 bytes = null;
154 }
155
156 private static int toDecimal(final int value) {
157 if (value < '0' || value > '9') {
158 throw new IllegalArgumentException("Invalid version number, only 0-9 (0x30-0x39) allowed," +
159 " but received a '" + (char) value + "' (0x" + Integer.toHexString(value) + ")");
160 }
161 return value - '0';
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175 public HttpVersion(
176 String protocolName, int majorVersion, int minorVersion,
177 boolean keepAliveDefault) {
178 this(protocolName, majorVersion, minorVersion, keepAliveDefault, false);
179 }
180
181 private HttpVersion(
182 String protocolName, int majorVersion, int minorVersion,
183 boolean keepAliveDefault, boolean bytes) {
184 protocolName = checkNonEmptyAfterTrim(protocolName, "protocolName").toUpperCase();
185
186 for (int i = 0; i < protocolName.length(); i ++) {
187 if (Character.isISOControl(protocolName.charAt(i)) ||
188 Character.isWhitespace(protocolName.charAt(i))) {
189 throw new IllegalArgumentException("invalid character in protocolName");
190 }
191 }
192
193 checkPositiveOrZero(majorVersion, "majorVersion");
194 checkPositiveOrZero(minorVersion, "minorVersion");
195
196 this.protocolName = protocolName;
197 this.majorVersion = majorVersion;
198 this.minorVersion = minorVersion;
199 text = protocolName + '/' + majorVersion + '.' + minorVersion;
200 this.keepAliveDefault = keepAliveDefault;
201
202 if (bytes) {
203 this.bytes = text.getBytes(CharsetUtil.US_ASCII);
204 } else {
205 this.bytes = null;
206 }
207 }
208
209
210
211
212 public String protocolName() {
213 return protocolName;
214 }
215
216
217
218
219 public int majorVersion() {
220 return majorVersion;
221 }
222
223
224
225
226 public int minorVersion() {
227 return minorVersion;
228 }
229
230
231
232
233 public String text() {
234 return text;
235 }
236
237
238
239
240
241 public boolean isKeepAliveDefault() {
242 return keepAliveDefault;
243 }
244
245
246
247
248 @Override
249 public String toString() {
250 return text();
251 }
252
253 @Override
254 public int hashCode() {
255 return (protocolName().hashCode() * 31 + majorVersion()) * 31 +
256 minorVersion();
257 }
258
259 @Override
260 public boolean equals(Object o) {
261 if (!(o instanceof HttpVersion)) {
262 return false;
263 }
264
265 HttpVersion that = (HttpVersion) o;
266 return minorVersion() == that.minorVersion() &&
267 majorVersion() == that.majorVersion() &&
268 protocolName().equals(that.protocolName());
269 }
270
271 @Override
272 public int compareTo(HttpVersion o) {
273 int v = protocolName().compareTo(o.protocolName());
274 if (v != 0) {
275 return v;
276 }
277
278 v = majorVersion() - o.majorVersion();
279 if (v != 0) {
280 return v;
281 }
282
283 return minorVersion() - o.minorVersion();
284 }
285
286 void encode(ByteBuf buf) {
287 if (bytes == null) {
288 buf.writeCharSequence(text, CharsetUtil.US_ASCII);
289 } else {
290 buf.writeBytes(bytes);
291 }
292 }
293 }