1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http.message;
29
30 import java.util.BitSet;
31
32 import org.apache.hc.core5.annotation.Contract;
33 import org.apache.hc.core5.annotation.ThreadingBehavior;
34 import org.apache.hc.core5.http.Header;
35 import org.apache.hc.core5.http.HttpVersion;
36 import org.apache.hc.core5.http.ParseException;
37 import org.apache.hc.core5.http.ProtocolVersion;
38 import org.apache.hc.core5.util.Args;
39 import org.apache.hc.core5.util.CharArrayBuffer;
40 import org.apache.hc.core5.util.TextUtils;
41 import org.apache.hc.core5.util.Tokenizer;
42
43
44
45
46
47
48 @Contract(threading = ThreadingBehavior.IMMUTABLE)
49 public class BasicLineParser implements LineParser {
50
51 public final static BasicLineParser INSTANCE = new BasicLineParser();
52
53
54
55 private static final BitSet FULL_STOP = Tokenizer.INIT_BITSET('.');
56 private static final BitSet BLANKS = Tokenizer.INIT_BITSET(' ', '\t');
57 private static final BitSet COLON = Tokenizer.INIT_BITSET(':');
58
59
60
61
62
63 private final ProtocolVersion protocol;
64 private final Tokenizer tokenizer;
65
66
67
68
69
70
71
72
73 public BasicLineParser(final ProtocolVersion proto) {
74 this.protocol = proto != null? proto : HttpVersion.HTTP_1_1;
75 this.tokenizer = Tokenizer.INSTANCE;
76 }
77
78
79
80
81 public BasicLineParser() {
82 this(null);
83 }
84
85 ProtocolVersion parseProtocolVersion(
86 final CharArrayBuffer buffer,
87 final ParserCursor cursor) throws ParseException {
88 final String protoname = this.protocol.getProtocol();
89 final int protolength = protoname.length();
90
91 this.tokenizer.skipWhiteSpace(buffer, cursor);
92
93 final int pos = cursor.getPos();
94
95
96 if (pos + protolength + 4 > cursor.getUpperBound()) {
97 throw new ParseException("Invalid protocol version",
98 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
99 }
100
101
102 boolean ok = true;
103 for (int i = 0; ok && (i < protolength); i++) {
104 ok = buffer.charAt(pos + i) == protoname.charAt(i);
105 }
106 if (ok) {
107 ok = buffer.charAt(pos + protolength) == '/';
108 }
109 if (!ok) {
110 throw new ParseException("Invalid protocol version",
111 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
112 }
113
114 cursor.updatePos(pos + protolength + 1);
115
116 final String token1 = this.tokenizer.parseToken(buffer, cursor, FULL_STOP);
117 final int major;
118 try {
119 major = Integer.parseInt(token1);
120 } catch (final NumberFormatException e) {
121 throw new ParseException("Invalid protocol major version number",
122 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
123 }
124 if (cursor.atEnd()) {
125 throw new ParseException("Invalid protocol version",
126 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
127 }
128 cursor.updatePos(cursor.getPos() + 1);
129 final String token2 = this.tokenizer.parseToken(buffer, cursor, BLANKS);
130 final int minor;
131 try {
132 minor = Integer.parseInt(token2);
133 } catch (final NumberFormatException e) {
134 throw new ParseException("Invalid protocol minor version number",
135 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
136 }
137 return HttpVersion.get(major, minor);
138 }
139
140
141
142
143
144
145
146
147
148
149 @Override
150 public RequestLine parseRequestLine(final CharArrayBuffer buffer) throws ParseException {
151 Args.notNull(buffer, "Char array buffer");
152
153 final ParserCursor cursor = new ParserCursor(0, buffer.length());
154 this.tokenizer.skipWhiteSpace(buffer, cursor);
155 final String method = this.tokenizer.parseToken(buffer, cursor, BLANKS);
156 if (TextUtils.isEmpty(method)) {
157 throw new ParseException("Invalid request line",
158 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
159 }
160 this.tokenizer.skipWhiteSpace(buffer, cursor);
161 final String uri = this.tokenizer.parseToken(buffer, cursor, BLANKS);
162 if (TextUtils.isEmpty(uri)) {
163 throw new ParseException("Invalid request line",
164 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
165 }
166 final ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
167 this.tokenizer.skipWhiteSpace(buffer, cursor);
168 if (!cursor.atEnd()) {
169 throw new ParseException("Invalid request line",
170 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
171 }
172 return new RequestLine(method, uri, ver);
173 }
174
175 @Override
176 public StatusLine parseStatusLine(final CharArrayBuffer buffer) throws ParseException {
177 Args.notNull(buffer, "Char array buffer");
178
179 final ParserCursor cursor = new ParserCursor(0, buffer.length());
180 this.tokenizer.skipWhiteSpace(buffer, cursor);
181 final ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
182 this.tokenizer.skipWhiteSpace(buffer, cursor);
183 final String s = this.tokenizer.parseToken(buffer, cursor, BLANKS);
184 for (int i = 0; i < s.length(); i++) {
185 if (!Character.isDigit(s.charAt(i))) {
186 throw new ParseException("Status line contains invalid status code",
187 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
188 }
189 }
190 final int statusCode;
191 try {
192 statusCode = Integer.parseInt(s);
193 } catch (final NumberFormatException e) {
194 throw new ParseException("Status line contains invalid status code",
195 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
196 }
197 final String text = buffer.substringTrimmed(cursor.getPos(), cursor.getUpperBound());
198 return new StatusLine(ver, statusCode, text);
199 }
200
201 @Override
202 public Header parseHeader(final CharArrayBuffer buffer) throws ParseException {
203 Args.notNull(buffer, "Char array buffer");
204
205 final ParserCursor cursor = new ParserCursor(0, buffer.length());
206 this.tokenizer.skipWhiteSpace(buffer, cursor);
207 final String name = this.tokenizer.parseToken(buffer, cursor, COLON);
208 if (cursor.getPos() == cursor.getLowerBound() || cursor.getPos() == cursor.getUpperBound() ||
209 buffer.charAt(cursor.getPos()) != ':' ||
210 TextUtils.isEmpty(name) ||
211 Tokenizer.isWhitespace(buffer.charAt(cursor.getPos() - 1))) {
212 throw new ParseException("Invalid header",
213 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
214 }
215 final String value = buffer.substringTrimmed(cursor.getPos() + 1, cursor.getUpperBound());
216 return new BasicHeader(name, value);
217 }
218
219 }