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