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