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
20 import io.netty.buffer.ByteBuf;
21 import io.netty.util.CharsetUtil;
22 import io.netty.util.internal.ObjectUtil;
23
24 import java.util.regex.Pattern;
25 import java.util.Locale;
26
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 if (text.isEmpty()) {
74 throw new IllegalArgumentException("text is empty (possibly HTTP/0.9)");
75 }
76
77
78
79
80
81
82
83
84
85 HttpVersion version = version0(text);
86 if (version == null) {
87 version = new HttpVersion(text, strict, true);
88 }
89 return version;
90 }
91
92 private static HttpVersion version0(String text) {
93 if (HTTP_1_1_STRING.equals(text)) {
94 return HTTP_1_1;
95 }
96 if (HTTP_1_0_STRING.equals(text)) {
97 return HTTP_1_0;
98 }
99 return null;
100 }
101
102 private final String protocolName;
103 private final int majorVersion;
104 private final int minorVersion;
105 private final String text;
106 private final boolean keepAliveDefault;
107 private final byte[] bytes;
108
109
110
111
112
113
114
115
116
117
118
119
120 public HttpVersion(String text, boolean keepAliveDefault) {
121 this(text, false, keepAliveDefault);
122 }
123
124 HttpVersion(String text, boolean strict, boolean keepAliveDefault) {
125
126
127
128
129 ObjectUtil.checkNotNull(text, "text");
130 if (text.isEmpty()) {
131 throw new IllegalArgumentException("text must not be empty");
132 }
133 text = text.toUpperCase(Locale.US);
134
135 if (strict) {
136
137
138
139
140 if (text.length() != 8 || !text.startsWith("HTTP/") || text.charAt(6) != '.') {
141 throw new IllegalArgumentException("invalid version format: " + text);
142 }
143 protocolName = "HTTP";
144 majorVersion = toDecimal(text.charAt(5));
145 minorVersion = toDecimal(text.charAt(7));
146 } else {
147 int slashIndex = text.indexOf('/');
148 int dotIndex = text.indexOf('.', slashIndex + 1);
149
150 if (slashIndex <= 0 || dotIndex <= slashIndex + 1
151 || dotIndex >= text.length() - 1 || hasControlOrWhitespace(text, slashIndex)) {
152 throw new IllegalArgumentException("invalid version format: " + text);
153 }
154
155 protocolName = text.substring(0, slashIndex);
156 majorVersion = parseInt(text, slashIndex + 1, dotIndex);
157 minorVersion = parseInt(text, dotIndex + 1, text.length());
158 }
159
160 this.text = protocolName + '/' + majorVersion + '.' + minorVersion;
161 this.keepAliveDefault = keepAliveDefault;
162 bytes = null;
163 }
164
165 private static boolean hasControlOrWhitespace(String s, int end) {
166 for (int i = 0; i < end; i++) {
167 char c = s.charAt(i);
168 if (Character.isISOControl(c) || Character.isWhitespace(c)) {
169 return true;
170 }
171 }
172 return false;
173 }
174
175 private static int parseInt(String text, int start, int end) {
176 int result = 0;
177 for (int i = start; i < end; i++) {
178 char ch = text.charAt(i);
179 result = result * 10 + toDecimal(ch);
180 }
181 return result;
182 }
183
184 private static int toDecimal(final int value) {
185 if (value < '0' || value > '9') {
186 throw new IllegalArgumentException("Invalid version number, only 0-9 (0x30-0x39) allowed," +
187 " but received a '" + (char) value + "' (0x" + Integer.toHexString(value) + ")");
188 }
189 return value - '0';
190 }
191
192
193
194
195
196
197
198
199
200
201
202
203 public HttpVersion(
204 String protocolName, int majorVersion, int minorVersion,
205 boolean keepAliveDefault) {
206 this(protocolName, majorVersion, minorVersion, keepAliveDefault, false);
207 }
208
209 private HttpVersion(
210 String protocolName, int majorVersion, int minorVersion,
211 boolean keepAliveDefault, boolean bytes) {
212
213
214 ObjectUtil.checkNotNull(protocolName, "protocolName");
215 if (protocolName.isEmpty()) {
216 throw new IllegalArgumentException("protocolName must not be empty");
217 }
218 protocolName = protocolName.toUpperCase(Locale.US);
219
220 if (hasControlOrWhitespace(protocolName, protocolName.length())) {
221 throw new IllegalArgumentException("invalid character in protocolName");
222 }
223
224 checkPositiveOrZero(majorVersion, "majorVersion");
225 checkPositiveOrZero(minorVersion, "minorVersion");
226
227 this.protocolName = protocolName;
228 this.majorVersion = majorVersion;
229 this.minorVersion = minorVersion;
230 text = protocolName + '/' + majorVersion + '.' + minorVersion;
231 this.keepAliveDefault = keepAliveDefault;
232
233 if (bytes) {
234 this.bytes = text.getBytes(CharsetUtil.US_ASCII);
235 } else {
236 this.bytes = null;
237 }
238 }
239
240
241
242
243 public String protocolName() {
244 return protocolName;
245 }
246
247
248
249
250 public int majorVersion() {
251 return majorVersion;
252 }
253
254
255
256
257 public int minorVersion() {
258 return minorVersion;
259 }
260
261
262
263
264 public String text() {
265 return text;
266 }
267
268
269
270
271
272 public boolean isKeepAliveDefault() {
273 return keepAliveDefault;
274 }
275
276
277
278
279 @Override
280 public String toString() {
281 return text();
282 }
283
284 @Override
285 public int hashCode() {
286 return (protocolName().hashCode() * 31 + majorVersion()) * 31 +
287 minorVersion();
288 }
289
290 @Override
291 public boolean equals(Object o) {
292 if (!(o instanceof HttpVersion)) {
293 return false;
294 }
295
296 HttpVersion that = (HttpVersion) o;
297 return minorVersion() == that.minorVersion() &&
298 majorVersion() == that.majorVersion() &&
299 protocolName().equals(that.protocolName());
300 }
301
302 @Override
303 public int compareTo(HttpVersion o) {
304 int v = protocolName().compareTo(o.protocolName());
305 if (v != 0) {
306 return v;
307 }
308
309 v = majorVersion() - o.majorVersion();
310 if (v != 0) {
311 return v;
312 }
313
314 return minorVersion() - o.minorVersion();
315 }
316
317 void encode(ByteBuf buf) {
318 if (bytes == null) {
319 buf.writeCharSequence(text, CharsetUtil.US_ASCII);
320 } else {
321 buf.writeBytes(bytes);
322 }
323 }
324 }