comparison src/luan/lib/webserver/RequestParser.java @ 1347:643cf1c37723

move webserver to lib and bug fixes
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 25 Feb 2019 13:02:33 -0700
parents src/luan/webserver/RequestParser.java@8b61c8c4e07a
children 4c5548a61d4f
comparison
equal deleted inserted replaced
1346:efd1c6380f2c 1347:643cf1c37723
1 package luan.lib.webserver;
2
3 import java.io.UnsupportedEncodingException;
4 import java.net.URLDecoder;
5 import java.util.List;
6 import java.util.ArrayList;
7 import luan.lib.logging.Logger;
8 import luan.lib.logging.LoggerFactory;
9 import luan.lib.parser.Parser;
10 import luan.lib.parser.ParseException;
11
12
13 final class RequestParser {
14 private static final Logger logger = LoggerFactory.getLogger(RequestParser.class);
15 private final Request request;
16 private Parser parser;
17
18 RequestParser(Request request) {
19 this.request = request;
20 }
21
22 void parseUrlencoded(String charset) throws ParseException, UnsupportedEncodingException {
23 if( request.body == null ) {
24 logger.warn("body is null\n"+request.rawHead);
25 return;
26 }
27 this.parser = new Parser(Util.toString(request.body,charset));
28 parseQuery();
29 require( parser.endOfInput() );
30 }
31
32 void parseHead() throws ParseException {
33 this.parser = new Parser(request.rawHead);
34 parseRequestLine();
35 while( !parser.match("\r\n") ) {
36 parserHeaderField();
37 }
38 parseCookies();
39 }
40
41 private void parseRequestLine() throws ParseException {
42 parseMethod();
43 require( parser.match(' ') );
44 parseRawPath();
45 require( parser.match(' ') );
46 parseProtocol();
47 require( parser.match("\r\n") );
48 }
49
50 private void parseMethod() throws ParseException {
51 int start = parser.currentIndex();
52 if( !methodChar() )
53 throw new ParseException(parser,"no method");
54 while( methodChar() );
55 request.method = parser.textFrom(start);
56 }
57
58 private boolean methodChar() {
59 return parser.inCharRange('A','Z');
60 }
61
62 private void parseRawPath() throws ParseException {
63 int start = parser.currentIndex();
64 parsePath();
65 if( parser.match('?') )
66 parseQuery();
67 request.rawPath = parser.textFrom(start);
68 }
69
70 private void parsePath() throws ParseException {
71 int start = parser.currentIndex();
72 if( !parser.match('/') )
73 throw new ParseException(parser,"bad path");
74 while( parser.noneOf(" ?#") );
75 request.path = urlDecode( parser.textFrom(start) );
76 }
77
78 private void parseQuery() throws ParseException {
79 do {
80 int start = parser.currentIndex();
81 while( queryChar() );
82 String name = urlDecode( parser.textFrom(start) );
83 String value = null;
84 if( parser.match('=') ) {
85 start = parser.currentIndex();
86 while( queryChar() || parser.match('=') );
87 value = urlDecode( parser.textFrom(start) );
88 }
89 if( name.length() > 0 || value != null ) {
90 if( value==null )
91 value = "";
92 Util.add(request.parameters,name,value);
93 }
94 } while( parser.match('&') );
95 }
96
97 private boolean queryChar() {
98 return parser.noneOf("=&# \t\n\f\r\u000b");
99 }
100
101 private void parseProtocol() throws ParseException {
102 int start = parser.currentIndex();
103 if( !(
104 parser.match("HTTP/")
105 && parser.inCharRange('0','9')
106 && parser.match('.')
107 && parser.inCharRange('0','9')
108 ) )
109 throw new ParseException(parser,"bad protocol");
110 request.protocol = parser.textFrom(start);
111 request.scheme = "http";
112 }
113
114
115 private void parserHeaderField() throws ParseException {
116 String name = parseName();
117 require( parser.match(':') );
118 while( parser.anyOf(" \t") );
119 String value = parseValue();
120 while( parser.anyOf(" \t") );
121 require( parser.match("\r\n") );
122 Util.add(request.headers,name,value);
123 }
124
125 private String parseName() throws ParseException {
126 int start = parser.currentIndex();
127 require( tokenChar() );
128 while( tokenChar() );
129 return parser.textFrom(start).toLowerCase();
130 }
131
132 private String parseValue() throws ParseException {
133 int start = parser.currentIndex();
134 while( !testEndOfValue() )
135 require( parser.anyChar() );
136 return parser.textFrom(start);
137 }
138
139 private boolean testEndOfValue() {
140 parser.begin();
141 while( parser.anyOf(" \t") );
142 boolean b = parser.endOfInput() || parser.anyOf("\r\n");
143 parser.failure(); // rollback
144 return b;
145 }
146
147 private void require(boolean b) throws ParseException {
148 if( !b )
149 throw new ParseException(parser,"failed");
150 }
151
152 boolean tokenChar() {
153 if( parser.endOfInput() )
154 return false;
155 char c = parser.currentChar();
156 if( 32 <= c && c <= 126 && "()<>@,;:\\\"/[]?={} \t\r\n".indexOf(c) == -1 ) {
157 parser.anyChar();
158 return true;
159 } else {
160 return false;
161 }
162 }
163
164
165 private void parseCookies() throws ParseException {
166 String text = (String)request.headers.get("cookie");
167 if( text == null )
168 return;
169 this.parser = new Parser(text);
170 while(true) {
171 int start = parser.currentIndex();
172 while( parser.noneOf("=;") );
173 String name = urlDecode( parser.textFrom(start) );
174 if( parser.match('=') ) {
175 start = parser.currentIndex();
176 while( parser.noneOf(";") );
177 String value = parser.textFrom(start);
178 int len = value.length();
179 if( value.charAt(0)=='"' && value.charAt(len-1)=='"' )
180 value = value.substring(1,len-1);
181 value = urlDecode(value);
182 request.cookies.put(name,value);
183 }
184 if( parser.endOfInput() )
185 return;
186 require( parser.match(';') );
187 parser.match(' '); // optional for bad browsers
188 }
189 }
190
191
192 private static final String contentTypeStart = "multipart/form-data; boundary=";
193
194 void parseMultipart() throws ParseException, UnsupportedEncodingException {
195 if( request.body == null ) {
196 logger.warn("body is null\n"+request.rawHead);
197 return;
198 }
199 String contentType = (String)request.headers.get("content-type");
200 if( !contentType.startsWith(contentTypeStart) )
201 throw new RuntimeException(contentType);
202 String boundary = "--"+contentType.substring(contentTypeStart.length());
203 this.parser = new Parser(Util.toString(request.body,null));
204 //System.out.println(this.parser.text);
205 require( parser.match(boundary) );
206 boundary = "\r\n" + boundary;
207 while( !parser.match("--\r\n") ) {
208 require( parser.match("\r\n") );
209 require( parser.match("Content-Disposition: form-data; name=") );
210 String name = quotedString();
211 String filename = null;
212 boolean isBinary = false;
213 if( parser.match("; filename=") ) {
214 filename = quotedString();
215 require( parser.match("\r\n") );
216 require( parser.match("Content-Type: ") );
217 int start = parser.currentIndex();
218 if( parser.match("application/") ) {
219 isBinary = true;
220 } else if( parser.match("image/") ) {
221 isBinary = true;
222 } else if( parser.match("text/") ) {
223 isBinary = false;
224 } else
225 throw new ParseException(parser,"bad file content-type");
226 while( parser.inCharRange('a','z') || parser.anyOf("-.") );
227 contentType = parser.textFrom(start);
228 }
229 require( parser.match("\r\n") );
230 require( parser.match("\r\n") );
231 int start = parser.currentIndex();
232 while( !parser.test(boundary) ) {
233 require( parser.anyChar() );
234 }
235 String value = parser.textFrom(start);
236 if( filename == null ) {
237 Util.add(request.parameters,name,value);
238 } else {
239 Object content = isBinary ? Util.toBytes(value) : value;
240 Request.MultipartFile mf = new Request.MultipartFile(filename,contentType,content);
241 Util.add(request.parameters,name,mf);
242 }
243 require( parser.match(boundary) );
244 }
245 }
246
247 private String quotedString() throws ParseException {
248 StringBuilder sb = new StringBuilder();
249 require( parser.match('"') );
250 while( !parser.match('"') ) {
251 if( parser.match("\\\"") ) {
252 sb.append('"');
253 } else {
254 require( parser.anyChar() );
255 sb.append( parser.lastChar() );
256 }
257 }
258 return sb.toString();
259 }
260
261 private String urlDecode(String s) throws ParseException {
262 try {
263 return URLDecoder.decode(s,"UTF-8");
264 } catch(UnsupportedEncodingException e) {
265 parser.rollback();
266 throw new ParseException(parser,e);
267 } catch(IllegalArgumentException e) {
268 parser.rollback();
269 throw new ParseException(parser,e);
270 }
271 }
272
273 // improve later
274 void parseJson() throws UnsupportedEncodingException {
275 if( request.body == null ) {
276 logger.warn("body is null\n"+request.rawHead);
277 return;
278 }
279 String contentType = (String)request.headers.get("content-type");
280 if( !contentType.equals("application/json; charset=utf-8") )
281 throw new RuntimeException(contentType);
282 String value = new String(request.body,"utf-8");
283 Util.add(request.parameters,"json",value);
284 }
285
286 }