001 // ======================================================================== 002 // Copyright (c) 1996 Mort Bay Consulting Pty. Ltd. All rights reserved. 003 // $Id: MultiPartRequest.java,v 1.6 2004/03/29 12:52:09 laurent Exp $ 004 // ------------------------------------------------------------------------ 005 006 package org.objectweb.jac.aspects.gui.web; 007 008 import java.io.ByteArrayInputStream; 009 import java.io.ByteArrayOutputStream; 010 import java.io.IOException; 011 import java.io.InputStream; 012 import java.io.UnsupportedEncodingException; 013 import java.util.Hashtable; 014 import java.util.List; 015 import java.util.StringTokenizer; 016 import javax.servlet.http.HttpServletRequest; 017 import org.apache.log4j.Logger; 018 import org.mortbay.http.HttpFields; 019 import org.mortbay.util.Code; 020 import org.mortbay.util.LineInput; 021 import org.mortbay.util.MultiMap; 022 import org.mortbay.util.StringUtil; 023 import org.objectweb.jac.util.ExtArrays; 024 025 /* ------------------------------------------------------------ */ 026 /** Multipart Form Data request. 027 * <p> 028 * This class decodes the multipart/form-data stream sent by 029 * a HTML form that uses a file input item. 030 * 031 * <p><h4>Usage</h4> 032 * Each part of the form data is named from the HTML form and 033 * is available either via getString(name) or getInputStream(name). 034 * Furthermore the MIME parameters and filename can be requested for 035 * each part. 036 * <pre> 037 * </pre> 038 * 039 * @version $Id: MultiPartRequest.java,v 1.6 2004/03/29 12:52:09 laurent Exp $ 040 * @author Greg Wilkins 041 * @author Jim Crossley 042 */ 043 public class MultiPartRequest 044 { 045 static Logger logger = Logger.getLogger("web.servlet"); 046 047 /* ------------------------------------------------------------ */ 048 HttpServletRequest _request; 049 LineInput _in; 050 String _boundary; 051 byte[] _byteBoundary; 052 MultiMap _partMap = new MultiMap(10); 053 int _char = -2; 054 boolean _lastPart = false; 055 String _enc = StringUtil.__ISO_8859_1; 056 057 /* ------------------------------------------------------------ */ 058 /** Constructor. 059 * @param request The request containing a multipart/form-data 060 * request 061 * @param enc the encoding of characters 062 * @exception IOException IOException 063 */ 064 public MultiPartRequest(HttpServletRequest request, String enc) 065 throws IOException 066 { 067 _request=request; 068 _enc = enc; 069 String content_type = request.getHeader(HttpFields.__ContentType); 070 if (!content_type.startsWith("multipart/form-data")) 071 throw new IOException("Not multipart/form-data request"); 072 073 Code.debug("Multipart content type = ",content_type); 074 075 _in = new LineInput(request.getInputStream()); 076 077 // Extract boundary string 078 _boundary="--"+ 079 value(content_type.substring(content_type.indexOf("boundary="))); 080 081 Code.debug("Boundary=",_boundary); 082 _byteBoundary= (_boundary+"--").getBytes(StringUtil.__ISO_8859_1); 083 084 loadAllParts(); 085 } 086 087 protected Part getPart(String name) { 088 Object part = _partMap.get(name); 089 if (part instanceof List) 090 return (Part)((List)part).get(0); 091 else 092 return (Part)part; 093 } 094 095 /* ------------------------------------------------------------ */ 096 /** Get the part names. 097 * @return an array of part names 098 */ 099 public String[] getPartNames() 100 { 101 return (String[]) _partMap.keySet().toArray(ExtArrays.emptyStringArray); 102 } 103 104 /* ------------------------------------------------------------ */ 105 /** Check if a named part is present 106 * @param name The part 107 * @return true if it was included 108 */ 109 public boolean contains(String name) 110 { 111 Part part = (Part)_partMap.get(name); 112 return (part!=null); 113 } 114 115 /* ------------------------------------------------------------ */ 116 /** Get the data of a part as a string. 117 * @param name The part name 118 * @return The part data 119 */ 120 public String getString(String name) 121 { 122 List part = (List)_partMap.getValues(name); 123 if (part==null) 124 return null; 125 return decodeString(((Part)part.get(0))._data); 126 } 127 128 /* ------------------------------------------------------------ */ 129 /** 130 * @param name The part name 131 * @return The parts data 132 */ 133 public String[] getStrings(String name) 134 { 135 List parts = (List)_partMap.getValues(name); 136 if (parts==null) 137 return null; 138 String[] strings = new String[parts.size()]; 139 for (int i=0; i<strings.length; i++) { 140 strings[i] = decodeString(((Part)parts.get(i))._data); 141 } 142 return strings; 143 } 144 145 protected String decodeString(byte[] bytes) { 146 try { 147 return new String(bytes,_enc); 148 } catch (UnsupportedEncodingException e) { 149 logger.error("Unsupported encoding: "+_enc); 150 return new String(bytes); 151 } 152 } 153 154 /* ------------------------------------------------------------ */ 155 /** Get the data of a part as a stream. 156 * @param name The part name 157 * @return Stream providing the part data 158 */ 159 public InputStream getInputStream(String name) 160 { 161 Part part = getPart(name); 162 if (part==null) 163 return null; 164 else 165 return new ByteArrayInputStream(part._data); 166 } 167 168 public InputStream[] getInputStreams(String name) 169 { 170 List parts = (List)_partMap.getValues(name); 171 if (parts==null) 172 return null; 173 InputStream[] streams = new InputStream[parts.size()]; 174 for (int i=0; i<streams.length; i++) { 175 streams[i] = new ByteArrayInputStream(((Part)parts.get(i))._data); 176 } 177 return streams; 178 } 179 180 /* ------------------------------------------------------------ */ 181 /** Get the MIME parameters associated with a part. 182 * @param name The part name 183 * @return Hashtable of parameters 184 */ 185 public Hashtable getParams(String name) 186 { 187 List part = (List)_partMap.getValues(name); 188 if (part==null) 189 return null; 190 return ((Part)part.get(0))._headers; 191 } 192 193 public Hashtable[] getMultipleParams(String name) 194 { 195 List parts = (List)_partMap.getValues(name); 196 if (parts==null) 197 return null; 198 Hashtable[] params = new Hashtable[parts.size()]; 199 for (int i=0; i<params.length; i++) { 200 params[i] = ((Part)parts.get(i))._headers; 201 } 202 return params; 203 } 204 205 /* ------------------------------------------------------------ */ 206 /** Get any file name associated with a part. 207 * @param name The part name 208 * @return The filename 209 */ 210 public String getFilename(String name) 211 { 212 List part = (List)_partMap.getValues(name); 213 if (part==null) 214 return null; 215 return ((Part)part.get(0))._filename; 216 } 217 218 public String[] getFilenames(String name) 219 { 220 List parts = (List)_partMap.getValues(name); 221 if (parts==null) 222 return null; 223 String[] filenames = new String[parts.size()]; 224 for (int i=0; i<filenames.length; i++) { 225 filenames[i] = ((Part)parts.get(i))._filename; 226 } 227 return filenames; 228 } 229 230 /* ------------------------------------------------------------ */ 231 private void loadAllParts() 232 throws IOException 233 { 234 // Get first boundary 235 String line = _in.readLine(); 236 if (!line.equals(_boundary)) 237 { 238 Code.warning(line); 239 throw new IOException("Missing initial multi part boundary"); 240 } 241 242 // Read each part 243 while (!_lastPart) 244 { 245 // Read Part headers 246 Part part = new Part(); 247 248 String content_disposition=null; 249 while ((line=_in.readLine())!=null) 250 { 251 // If blank line, end of part headers 252 if (line.length()==0) 253 break; 254 255 Code.debug("LINE=",line); 256 257 // place part header key and value in map 258 int c = line.indexOf(':',0); 259 if (c>0) 260 { 261 String key = line.substring(0,c).trim().toLowerCase(); 262 String value = line.substring(c+1,line.length()).trim(); 263 String ev = (String) part._headers.get(key); 264 part._headers.put(key,(ev!=null)?(ev+';'+value):value); 265 Code.debug(key,": ",value); 266 if (key.equals("content-disposition")) 267 content_disposition=value; 268 } 269 } 270 271 // Extract content-disposition 272 boolean form_data=false; 273 if (content_disposition==null) 274 { 275 throw new IOException("Missing content-disposition"); 276 } 277 278 StringTokenizer tok = 279 new StringTokenizer(content_disposition,";"); 280 while (tok.hasMoreTokens()) 281 { 282 String t = tok.nextToken().trim(); 283 String tl = t.toLowerCase(); 284 if (t.startsWith("form-data")) 285 form_data=true; 286 else if (tl.startsWith("name=")) 287 part._name=value(t); 288 else if (tl.startsWith("filename=")) 289 part._filename=value(t); 290 } 291 292 // Check disposition 293 if (!form_data) 294 { 295 Code.warning("Non form-data part in multipart/form-data"); 296 continue; 297 } 298 if (part._name==null || part._name.length()==0) 299 { 300 Code.warning("Part with no name in multipart/form-data"); 301 continue; 302 } 303 Code.debug("name=",part._name); 304 Code.debug("filename=",part._filename); 305 _partMap.add(part._name,part); 306 part._data=readBytes(); 307 } 308 } 309 310 /* ------------------------------------------------------------ */ 311 private byte[] readBytes() 312 throws IOException 313 { 314 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 315 316 int c; 317 boolean cr=false; 318 boolean lf=false; 319 320 // loop for all lines` 321 while (true) 322 { 323 int b=0; 324 while ((c=(_char!=-2)?_char:_in.read())!=-1) 325 { 326 _char=-2; 327 328 // look for CR and/or LF 329 if (c==13 || c==10) 330 { 331 if (c==13) _char=_in.read(); 332 break; 333 } 334 335 // look for boundary 336 if (b>=0 && b<_byteBoundary.length && c==_byteBoundary[b]) 337 b++; 338 else 339 { 340 // this is not a boundary 341 if (cr) baos.write(13); 342 if (lf) baos.write(10); 343 cr=lf=false; 344 345 if (b>0) 346 baos.write(_byteBoundary,0,b); 347 b=-1; 348 349 baos.write(c); 350 } 351 } 352 353 // check partial boundary 354 if ((b>0 && b<_byteBoundary.length-2) || 355 (b==_byteBoundary.length-1)) 356 { 357 if (cr) baos.write(13); 358 if (lf) baos.write(10); 359 cr=lf=false; 360 baos.write(_byteBoundary,0,b); 361 b=-1; 362 } 363 364 // boundary match 365 if (b>0 || c==-1) 366 { 367 if (b==_byteBoundary.length) 368 _lastPart=true; 369 if (_char==10) _char=-2; 370 break; 371 } 372 373 // handle CR LF 374 if (cr) baos.write(13); 375 if (lf) baos.write(10); 376 cr=(c==13); 377 lf=(c==10 || _char==10); 378 if (_char==10) _char=-2; 379 } 380 if (Code.verbose()) Code.debug(baos.toString()); 381 return baos.toByteArray(); 382 } 383 384 385 /* ------------------------------------------------------------ */ 386 private String value(String nameEqualsValue) 387 { 388 String value = 389 nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim(); 390 391 int i=value.indexOf(';'); 392 if (i>0) 393 value=value.substring(0,i); 394 if (value.startsWith("\"")) 395 { 396 value=value.substring(1,value.indexOf('"',1)); 397 } 398 399 else 400 { 401 i=value.indexOf(' '); 402 if (i>0) 403 value=value.substring(0,i); 404 } 405 return value; 406 } 407 408 /* ------------------------------------------------------------ */ 409 private class Part 410 { 411 String _name=null; 412 String _filename=null; 413 Hashtable _headers= new Hashtable(10); 414 byte[] _data=null; 415 } 416 };