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