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