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