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 org.apache.hc.core5.annotation.Contract;
31 import org.apache.hc.core5.annotation.ThreadingBehavior;
32 import org.apache.hc.core5.http.Header;
33 import org.apache.hc.core5.http.HttpVersion;
34 import org.apache.hc.core5.http.ParseException;
35 import org.apache.hc.core5.http.ProtocolVersion;
36 import org.apache.hc.core5.http.ProtocolVersionParser;
37 import org.apache.hc.core5.util.Args;
38 import org.apache.hc.core5.util.CharArrayBuffer;
39 import org.apache.hc.core5.util.TextUtils;
40 import org.apache.hc.core5.util.Tokenizer;
41
42
43
44
45
46
47 @Contract(threading = ThreadingBehavior.IMMUTABLE)
48 public class BasicLineParser implements LineParser {
49
50 public final static BasicLineParser INSTANCE = new BasicLineParser();
51
52 private static final Tokenizer.Delimiter BLANKS = Tokenizer.delimiters(' ', '\t');
53 private static final Tokenizer.Delimiter COLON = Tokenizer.delimiters(':');
54
55
56
57
58
59 private final ProtocolVersion protocol;
60 private final Tokenizer tokenizer;
61
62
63
64
65
66
67
68
69 public BasicLineParser(final ProtocolVersion proto) {
70 this.protocol = proto != null? proto : HttpVersion.HTTP_1_1;
71 this.tokenizer = Tokenizer.INSTANCE;
72 }
73
74
75
76
77 public BasicLineParser() {
78 this(null);
79 }
80
81 ProtocolVersion parseProtocolVersion(
82 final CharArrayBuffer buffer,
83 final ParserCursor cursor) throws ParseException {
84 final String protoname = this.protocol.getProtocol();
85 final int protolength = protoname.length();
86
87 this.tokenizer.skipWhiteSpace(buffer, cursor);
88
89 final int pos = cursor.getPos();
90
91
92 if (pos + protolength + 4 > cursor.getUpperBound()) {
93 throw new ParseException("Invalid protocol version",
94 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
95 }
96
97
98 boolean ok = true;
99 for (int i = 0; ok && (i < protolength); i++) {
100 ok = buffer.charAt(pos + i) == protoname.charAt(i);
101 }
102 if (ok) {
103 ok = buffer.charAt(pos + protolength) == '/';
104 }
105 if (!ok) {
106 throw new ParseException("Invalid protocol version",
107 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
108 }
109
110 cursor.updatePos(pos + protolength + 1);
111 return ProtocolVersionParser.INSTANCE.parse(protoname, HttpVersion::get, buffer, cursor, null);
112 }
113
114
115
116
117
118
119
120
121
122
123 @Override
124 public RequestLine parseRequestLine(final CharArrayBuffer buffer) throws ParseException {
125 Args.notNull(buffer, "Char array buffer");
126
127 final ParserCursor cursor = new ParserCursor(0, buffer.length());
128 this.tokenizer.skipWhiteSpace(buffer, cursor);
129 final String method = this.tokenizer.parseToken(buffer, cursor, BLANKS);
130 if (TextUtils.isEmpty(method)) {
131 throw new ParseException("Invalid request line",
132 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
133 }
134 this.tokenizer.skipWhiteSpace(buffer, cursor);
135 final String uri = this.tokenizer.parseToken(buffer, cursor, BLANKS);
136 if (TextUtils.isEmpty(uri)) {
137 throw new ParseException("Invalid request line",
138 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
139 }
140 final ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
141 this.tokenizer.skipWhiteSpace(buffer, cursor);
142 if (!cursor.atEnd()) {
143 throw new ParseException("Invalid request line",
144 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
145 }
146 return new RequestLine(method, uri, ver);
147 }
148
149 @Override
150 public StatusLine parseStatusLine(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 ProtocolVersion ver = parseProtocolVersion(buffer, cursor);
156 this.tokenizer.skipWhiteSpace(buffer, cursor);
157 int statusCode = 0;
158 for (int i = 1; i <= 3; i++) {
159 if (cursor.atEnd()) {
160 throw new ParseException("Status line contains invalid status code",
161 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
162 }
163 final char ch = buffer.charAt(cursor.getPos());
164 if (ch >= '0' && ch <= '9') {
165 statusCode = (statusCode * 10) + (ch - '0');
166 } else {
167 throw new ParseException("Status line contains invalid status code",
168 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
169 }
170 cursor.updatePos(cursor.getPos() + 1);
171 }
172 if (!cursor.atEnd()) {
173 final char ch = buffer.charAt(cursor.getPos());
174 if (Tokenizer.isWhitespace(ch)) {
175 cursor.updatePos(cursor.getPos() + 1);
176 } else {
177 throw new ParseException("Status line contains invalid status code",
178 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
179 }
180 }
181 this.tokenizer.skipWhiteSpace(buffer, cursor);
182 final String text = buffer.substringTrimmed(cursor.getPos(), cursor.getUpperBound());
183 return new StatusLine(ver, statusCode, text);
184 }
185
186 @Override
187 public Header parseHeader(final CharArrayBuffer buffer) throws ParseException {
188 Args.notNull(buffer, "Char array buffer");
189
190 final ParserCursor cursor = new ParserCursor(0, buffer.length());
191 this.tokenizer.skipWhiteSpace(buffer, cursor);
192 final String name = this.tokenizer.parseToken(buffer, cursor, COLON);
193 if (cursor.getPos() == cursor.getLowerBound() || cursor.getPos() == cursor.getUpperBound() ||
194 buffer.charAt(cursor.getPos()) != ':' ||
195 TextUtils.isEmpty(name) ||
196 Tokenizer.isWhitespace(buffer.charAt(cursor.getPos() - 1))) {
197 throw new ParseException("Invalid header",
198 buffer, cursor.getLowerBound(), cursor.getUpperBound(), cursor.getPos());
199 }
200 final String value = buffer.substringTrimmed(cursor.getPos() + 1, cursor.getUpperBound());
201 return new BasicHeader(name, value);
202 }
203
204 }