Mercurial Hosting > luan
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 } |