comparison src/goodjava/webserver/RequestParser.java @ 1402:27efb1fcbcb5

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