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 } |
