1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.openejb.webadmin.httpd; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.DataInput; 22 import java.io.DataInputStream; 23 import java.io.File; 24 import java.io.FileOutputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.net.URL; 28 import java.net.URLDecoder; 29 import java.rmi.RemoteException; 30 import java.util.HashMap; 31 import java.util.Hashtable; 32 import java.util.Iterator; 33 import java.util.Map; 34 import java.util.StringTokenizer; 35 36 import javax.naming.InitialContext; 37 import javax.naming.NamingException; 38 39 import org.apache.commons.fileupload.MultipartStream; 40 import org.apache.openejb.webadmin.HttpRequest; 41 import org.apache.openejb.webadmin.HttpSession; 42 import org.apache.openejb.core.stateful.StatefulEjbObjectHandler; 43 import org.apache.openejb.loader.FileUtils; 44 45 /** A class to take care of HTTP Requests. It parses headers, content, form and url 46 * parameters. 47 * @author <a href="mailto:david.blevins@visi.com">David Blevins</a> 48 * @author <a href="mailto:tim_urberg@yahoo.com">Tim Urberg</a> 49 */ 50 public class HttpRequestImpl implements HttpRequest { 51 public static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; 52 public static final String MULITPART_FORM_DATA = "multipart/form-data"; 53 public static final String FILENAME = "filename"; 54 public static final String NAME = "name"; 55 56 /** 5.1 Request-Line */ 57 private String line; 58 /** 5.1.1 Method */ 59 private int method; 60 /** 5.1.2 Request-URI */ 61 private URL uri; 62 /** the headers for this page */ 63 private HashMap headers; 64 /** the form parameters for this page */ 65 private HashMap formParams = new HashMap(); 66 /** the URL (or query) parameters for this page */ 67 private HashMap queryParams = new HashMap(); 68 /** the content of the body of this page */ 69 private byte[] body; 70 private String[][] formParamsArray; 71 72 73 private String methodString; 74 private String pathString; 75 76 77 /** 78 * @return Returns the methodString. 79 */ 80 public String getMethodString() { 81 return methodString; 82 } 83 84 /** 85 * @return Returns the pathString. 86 */ 87 public String getPathString() { 88 return pathString; 89 } 90 91 /** Gets a header based the header name passed in. 92 * @param name The name of the header to get 93 * @return The value of the header 94 */ 95 public String getHeader(String name) { 96 return (String) headers.get(name); 97 } 98 99 /** Gets a form parameter based on the name passed in. 100 * @param name The name of the form parameter to get 101 * @return The value of the parameter 102 */ 103 public String getFormParameter(String name) { 104 return (String) formParams.get(name); 105 } 106 107 /** Gets all the form parameters in the form of a two-dimentional array 108 * The second dimention has two indexes which contain the key and value 109 * for example: 110 * <code> 111 * for(int i=0; i<formParams.length; i++) { 112 * key = formParams[i][0]; 113 * value = formParams[i][1]; 114 * } 115 * </code> 116 * 117 * All values are strings 118 * @return All the form parameters 119 */ 120 public String[][] getFormParameters() { 121 Iterator keys = formParams.keySet().iterator(); 122 String[][] returnValue = new String[formParams.size()][2]; 123 124 String temp; 125 int i = 0; 126 while (keys.hasNext()) { 127 temp = (String) keys.next(); 128 returnValue[i][0] = temp; 129 returnValue[i++][1] = (String) formParams.get(temp); 130 } 131 132 return returnValue; 133 } 134 135 /** Gets a URL (or query) parameter based on the name passed in. 136 * @param name The name of the URL (or query) parameter 137 * @return The value of the URL (or query) parameter 138 */ 139 public String getQueryParameter(String name) { 140 return (String) queryParams.get(name); 141 } 142 143 /** Gets an integer value of the request method. These values are: 144 * 145 * OPTIONS = 0 146 * GET = 1 147 * HEAD = 2 148 * POST = 3 149 * PUT = 4 150 * DELETE = 5 151 * TRACE = 6 152 * CONNECT = 7 153 * UNSUPPORTED = 8 154 * @return The integer value of the method 155 */ 156 public int getMethod() { 157 return method; 158 } 159 160 /** Gets the URI for the current URL page. 161 * @return The URI 162 */ 163 public URL getURI() { 164 return uri; 165 } 166 167 /*------------------------------------------------------------*/ 168 /* Methods for reading in and parsing a request */ 169 /*------------------------------------------------------------*/ 170 /** parses the request into the 3 different parts, request, headers, and body 171 * @param input the data input for this page 172 * @throws IOException if an exception is thrown 173 */ 174 protected void readMessage(InputStream input) throws IOException { 175 DataInput in = new DataInputStream(input); 176 177 readRequestLine(in); 178 readHeaders(in); 179 readBody(in); 180 } 181 182 private String requestLine; 183 184 protected String getRequestLine(){ 185 return requestLine; 186 } 187 /** reads and parses the request line 188 * @param in the input to be read 189 * @throws IOException if an exception is thrown 190 */ 191 private void readRequestLine(DataInput in) throws IOException { 192 193 try { 194 line = in.readLine(); 195 requestLine = line; 196 // System.out.println(line); 197 } catch (Exception e) { 198 throw new IOException( 199 "Could not read the HTTP Request Line :" 200 + e.getClass().getName() 201 + " : " 202 + e.getMessage()); 203 } 204 205 StringTokenizer lineParts = new StringTokenizer(line, " "); 206 /* [1] Parse the method */ 207 parseMethod(lineParts); 208 /* [2] Parse the URI */ 209 parseURI(lineParts); 210 } 211 212 /** parses the method for this page 213 * @param lineParts a StringTokenizer of the request line 214 * @throws IOException if an exeption is thrown 215 */ 216 private void parseMethod(StringTokenizer lineParts) throws IOException { 217 String token = null; 218 try { 219 token = lineParts.nextToken(); 220 } catch (Exception e) { 221 throw new IOException( 222 "Could not parse the HTTP Request Method :" 223 + e.getClass().getName() 224 + " : " 225 + e.getMessage()); 226 } 227 228 if (token.equalsIgnoreCase("GET")) { 229 method = GET; 230 } else if (token.equalsIgnoreCase("POST")) { 231 method = POST; 232 } else { 233 method = UNSUPPORTED; 234 throw new IOException("Unsupported HTTP Request Method :" + token); 235 } 236 } 237 238 /** parses the URI into the different parts 239 * @param lineParts a StringTokenizer of the URI 240 * @throws IOException if an exeption is thrown 241 */ 242 private void parseURI(StringTokenizer lineParts) throws IOException { 243 String token = null; 244 try { 245 token = lineParts.nextToken(); 246 } catch (Exception e) { 247 throw new IOException( 248 "Could not parse the HTTP Request Method :" 249 + e.getClass().getName() 250 + " : " 251 + e.getMessage()); 252 } 253 254 try { 255 uri = new URL("http", "localhost", token); 256 } catch (java.net.MalformedURLException e) { 257 throw new IOException("Malformed URL :" + token + " Exception: " + e.getMessage()); 258 } 259 260 parseQueryParams(uri.getQuery()); 261 } 262 263 /** parses the URL (or query) parameters 264 * @param query the URL (or query) parameters to be parsed 265 * @throws IOException if an exception is thrown 266 */ 267 private void parseQueryParams(String query) throws IOException { 268 if (query == null) 269 return; 270 StringTokenizer parameters = new StringTokenizer(query, "&"); 271 272 while (parameters.hasMoreTokens()) { 273 StringTokenizer param = new StringTokenizer(parameters.nextToken(), "="); 274 275 /* [1] Parse the Name */ 276 if (!param.hasMoreTokens()) 277 continue; 278 String name = URLDecoder.decode(param.nextToken()); 279 if (name == null) 280 continue; 281 282 /* [2] Parse the Value */ 283 if (!param.hasMoreTokens()) 284 continue; 285 String value = URLDecoder.decode(param.nextToken()); 286 if (value == null) 287 continue; 288 289 //System.out.println("[] "+name+" = "+value); 290 queryParams.put(name, value); 291 } 292 } 293 294 /** reads the headers from the data input sent from the browser 295 * @param in the data input sent from the browser 296 * @throws IOException if an exeption is thrown 297 */ 298 private void readHeaders(DataInput in) throws IOException { 299 // System.out.println("\nREQUEST"); 300 headers = new HashMap(); 301 while (true) { 302 // Header Field 303 String hf = null; 304 305 try { 306 hf = in.readLine(); 307 //System.out.println(hf); 308 } catch (Exception e) { 309 throw new IOException( 310 "Could not read the HTTP Request Header Field :" 311 + e.getClass().getName() 312 + " : " 313 + e.getMessage()); 314 } 315 316 if (hf == null || hf.equals("")) { 317 break; 318 } 319 320 /* [1] parse the name */ 321 int colonIndex = hf.indexOf((int) ':'); 322 String name = hf.substring(0, colonIndex); 323 if (name == null) 324 break; 325 326 /* [2] Parse the Value */ 327 String value = hf.substring(colonIndex + 1, hf.length()); 328 if (value == null) 329 break; 330 value = value.trim(); 331 headers.put(name, value); 332 } 333 334 //temp-debug------------------------------------------- 335 //java.util.Iterator myKeys = headers.keySet().iterator(); 336 //String temp = null; 337 //while(myKeys.hasNext()) { 338 // temp = (String)myKeys.next(); 339 // System.out.println("Test: " + temp + "=" + headers.get(temp)); 340 //} 341 //end temp-debug--------------------------------------- 342 } 343 344 /** reads the body from the data input passed in 345 * @param in the data input with the body of the page 346 * @throws IOException if an exception is thrown 347 */ 348 private void readBody(DataInput in) throws IOException { 349 readRequestBody(in); 350 //System.out.println("Body Length: " + body.length); 351 // Content-type: application/x-www-form-urlencoded 352 // or multipart/form-data 353 String type = getHeader(HttpRequest.HEADER_CONTENT_TYPE); 354 if (FORM_URL_ENCODED.equals(type)) { 355 parseFormParams(); 356 } else if (type != null && type.startsWith(MULITPART_FORM_DATA)) { 357 parseMultiPartFormParams(); 358 } 359 } 360 361 /** reads the request line of the data input 362 * @param in the data input that contains the request line 363 * @throws IOException if an exception is thrown 364 */ 365 private void readRequestBody(DataInput in) throws IOException { 366 // Content-length: 384 367 String len = getHeader(HttpRequest.HEADER_CONTENT_LENGTH); 368 //System.out.println("readRequestBody Content-Length: " + len); 369 370 int length = -1; 371 if (len != null) { 372 try { 373 length = Integer.parseInt(len); 374 } catch (Exception e) { 375 //don't care 376 } 377 } 378 379 if (length < 1) { 380 this.body = new byte[0]; 381 } else if (length > 0) { 382 this.body = new byte[length]; 383 384 try { 385 in.readFully(body); 386 } catch (Exception e) { 387 throw new IOException( 388 "Could not read the HTTP Request Body :" 389 + e.getClass().getName() 390 + " : " 391 + e.getMessage()); 392 } 393 } 394 } 395 396 /** parses form parameters into the formParams variable 397 * @throws IOException if an exeption is thrown 398 */ 399 private void parseFormParams() throws IOException { 400 String rawParams = new String(body); 401 //System.out.println("rawParams: " + rawParams); 402 StringTokenizer parameters = new StringTokenizer(rawParams, "&"); 403 String name = null; 404 String value = null; 405 406 while (parameters.hasMoreTokens()) { 407 StringTokenizer param = new StringTokenizer(parameters.nextToken(), "="); 408 409 /* [1] Parse the Name */ 410 name = URLDecoder.decode(param.nextToken()); 411 if (name == null) 412 break; 413 414 /* [2] Parse the Value */ 415 if (param.hasMoreTokens()) { 416 value = URLDecoder.decode(param.nextToken()); 417 } else { 418 value = ""; //if there is no token set value to blank string 419 } 420 421 if (value == null) 422 value = ""; 423 424 formParams.put(name, value); 425 //System.out.println(name + ": " + value); 426 } 427 } 428 429 /** 430 * A method which parses form parameters that are multipart/form-data 431 * according to <a href="http://www.ietf.org/rfc/rfc1867.txt" target="_blank"> 432 * RFC 1867</a>. Currently multipart/mixed is not implemented. 433 */ 434 private void parseMultiPartFormParams() throws IOException { 435 /* see http://www.ietf.org/rfc/rfc1867.txt */ 436 ByteArrayOutputStream output; 437 StringBuffer multiPartBuffer; 438 int j; 439 Map headerMap; 440 boolean isFile; 441 String fileName = null; 442 byte[] outputArray; 443 FileOutputStream fos; 444 445 String contentType = getHeader(HttpRequest.HEADER_CONTENT_TYPE); 446 int boundaryIndex = contentType.indexOf("boundary="); 447 if (boundaryIndex < 0) { 448 throw new IOException("the request was rejected because no multipart boundary was found"); 449 } 450 byte[] boundary = contentType.substring(boundaryIndex + 9).getBytes(); 451 452 ByteArrayInputStream input = new ByteArrayInputStream(body); 453 MultipartStream multi = new MultipartStream(input, boundary); 454 455 boolean nextPart = multi.skipPreamble(); 456 while (nextPart) { 457 try { 458 output = new ByteArrayOutputStream(); 459 multi.readBodyData(output); 460 outputArray = output.toByteArray(); 461 multiPartBuffer = new StringBuffer(50); 462 isFile = false; 463 File jarFileInTempDir; 464 j = 0; 465 466 for (int i = 0; i < outputArray.length; i++) { 467 //first check for \r\n end of line 468 if (outputArray[i] == 13 && outputArray[i + 1] == 10) { 469 //we've come to the end of a line 470 headerMap = parseMultiPartHeader(multiPartBuffer); 471 if (headerMap.get(NAME) != null) { 472 fileName = (String) headerMap.get(NAME); 473 } 474 475 //add the filename if there is one 476 if (fileName != null && headerMap.get(FILENAME) != null) { 477 this.formParams.put(fileName, headerMap.get(FILENAME)); 478 isFile = true; 479 } 480 481 if (outputArray[i + 2] == 13 && outputArray[i + 3] == 10) { 482 //we've reached the blank line 483 i+=4; 484 j = i; 485 break; 486 } else { 487 i++; 488 } 489 490 multiPartBuffer = new StringBuffer(50); 491 } else { 492 multiPartBuffer.append((char) outputArray[i]); 493 } 494 } 495 496 //here we know that we have a file and that we need to write it 497 if (isFile) { 498 //create file 499 jarFileInTempDir = new File((String) this.formParams.get(fileName)); 500 if (!jarFileInTempDir.exists()) { 501 jarFileInTempDir.createNewFile(); 502 } 503 504 //write the byte array to the file 505 fos = new FileOutputStream(jarFileInTempDir); 506 fos.write(outputArray, j, outputArray.length-j); 507 fos.close(); 508 } else { //form data, not a file 509 multiPartBuffer = new StringBuffer(outputArray.length-j); 510 for (int i = j; i < outputArray.length; i++) { 511 multiPartBuffer.append((char)outputArray[i]); 512 } 513 514 this.formParams.put( 515 fileName, 516 multiPartBuffer.toString()); 517 } 518 519 nextPart = multi.readBoundary(); 520 } catch (MultipartStream.MalformedStreamException mse) { 521 throw new IOException(mse.getMessage()); 522 } 523 } 524 } 525 526 /** 527 * Parses the first one or two lines of a multipart. The usual headers are 528 * Content-Dispostion or Content-Type. 529 * 530 * @param headerBuffer - the header string to be parsed 531 * @return a map of of header info and their values 532 */ 533 private Map parseMultiPartHeader(StringBuffer headerBuffer) throws IOException { 534 Map headerMap = new HashMap(); 535 int colonIndex = headerBuffer.toString().indexOf(":"); 536 String headerName = headerBuffer.substring(0, colonIndex); 537 StringTokenizer headerValueToken = 538 new StringTokenizer(headerBuffer.substring(colonIndex + 1, headerBuffer.length()), ";"); 539 540 String currentToken; 541 //loop through the tokens of semi-colon 542 while (headerValueToken.hasMoreTokens()) { 543 currentToken = headerValueToken.nextToken(); 544 if (currentToken.indexOf("=") > -1) { 545 headerMap.put( 546 currentToken.substring(0, currentToken.indexOf("=")).trim(), 547 currentToken 548 .substring(currentToken.indexOf("=") + 2, currentToken.length() - 1) 549 .trim()); 550 } else { 551 headerMap.put(headerName, currentToken.trim()); 552 } 553 } 554 555 //first get rid of any path that might already be there then 556 //change the path of the file name to a temp directory 557 String fileName = (String) headerMap.get(FILENAME); 558 if (fileName != null) { 559 StringBuffer temp; 560 if (fileName.indexOf("\\") > -1) { 561 temp = new StringBuffer(fileName).reverse(); 562 fileName = temp.delete(temp.toString().indexOf("\\"), temp.length()).reverse().toString(); 563 } 564 565 temp = new StringBuffer(); 566 temp.append(FileUtils.createTempDirectory().getAbsolutePath()); 567 temp.append(System.getProperty("file.separator")); 568 temp.append(fileName); 569 headerMap.put(FILENAME, temp.toString()); 570 } 571 572 return headerMap; 573 } 574 575 private HashMap cookies; 576 577 protected HashMap getCookies(){ 578 if (cookies != null) return cookies; 579 580 cookies = new HashMap(); 581 582 String cookieHeader = getHeader(HEADER_COOKIE); 583 if (cookieHeader == null ) return cookies; 584 585 StringTokenizer tokens = new StringTokenizer(cookieHeader, ";"); 586 while (tokens.hasMoreTokens()){ 587 StringTokenizer token = new StringTokenizer(tokens.nextToken(),"="); 588 String name = token.nextToken(); 589 String value = token.nextToken(); 590 cookies.put(name, value); 591 } 592 return cookies; 593 } 594 595 protected static final String EJBSESSIONID = "EJBSESSIONID"; 596 597 protected String getCookie(String name){ 598 return (String) getCookies().get(name); 599 } 600 601 public HttpSession getSession() { 602 return getSession(true); 603 } 604 605 private WebSession session; 606 607 public HttpSession getSession(boolean create) { 608 if (session != null) return session; 609 610 String id = getCookie(EJBSESSIONID); 611 612 if (id != null) { 613 session = (WebSession)sessions.get(id); 614 } 615 616 if (session == null && create){ 617 session = createSession(); 618 sessions.put(session.getId(), session); 619 } 620 return session; 621 } 622 623 private static final Hashtable sessions = new Hashtable(); 624 625 private WebSession createSession(){ 626 // Lookup/create sessions 627 WebSessionHome home = null; 628 629 try { 630 home = (WebSessionHome)new InitialContext().lookup("java:openejb/local/httpd/session"); 631 } catch (NamingException e) { 632 // TODO Auto-generated catch block 633 throw new IllegalStateException("The WebSessionBean has not been deployed. "+ 634 " This is required for the HTTPd service to provide HttpSession support. "+ 635 e.getClass().getName()+": "+e.getMessage()); 636 } 637 638 639 WebSession session = null; 640 try { 641 session = home.create(); 642 } catch (RemoteException e) { 643 // TODO Auto-generated catch block 644 e.printStackTrace(); 645 } 646 // mark them as nocopy 647 Object obj = org.apache.openejb.util.proxy.ProxyManager.getInvocationHandler(session); 648 StatefulEjbObjectHandler handler = (StatefulEjbObjectHandler) obj; 649 handler.setIntraVmCopyMode(false); 650 return session; 651 } 652 }