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