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 18 19 package org.apache.tomcat.util.http.fileupload; 20 21 22 import java.io.BufferedInputStream; 23 import java.io.BufferedOutputStream; 24 import java.io.ByteArrayInputStream; 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.OutputStream; 31 import java.io.UnsupportedEncodingException; 32 33 34 /** 35 * <p> The default implementation of the 36 * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface. 37 * 38 * <p> After retrieving an instance of this class from a {@link 39 * org.apache.tomcat.util.http.fileupload.DiskFileUpload DiskFileUpload} instance (see 40 * {@link org.apache.tomcat.util.http.fileupload.DiskFileUpload 41 * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may 42 * either request all contents of file at once using {@link #get()} or 43 * request an {@link java.io.InputStream InputStream} with 44 * {@link #getInputStream()} and process the file without attempting to load 45 * it into memory, which may come handy with large files. 46 * 47 * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a> 48 * @author <a href="mailto:sean@informage.net">Sean Legassick</a> 49 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 50 * @author <a href="mailto:jmcnally@apache.org">John McNally</a> 51 * @author <a href="mailto:martinc@apache.org">Martin Cooper</a> 52 * @author Sean C. Sullivan 53 * 54 * @version $Id: DefaultFileItem.java 467222 2006-10-24 03:17:11Z markt $ 55 */ 56 public class DefaultFileItem 57 implements FileItem 58 { 59 60 // ----------------------------------------------------------- Data members 61 62 63 /** 64 * Counter used in unique identifier generation. 65 */ 66 private static int counter = 0; 67 68 69 /** 70 * The name of the form field as provided by the browser. 71 */ 72 private String fieldName; 73 74 75 /** 76 * The content type passed by the browser, or <code>null</code> if 77 * not defined. 78 */ 79 private String contentType; 80 81 82 /** 83 * Whether or not this item is a simple form field. 84 */ 85 private boolean isFormField; 86 87 88 /** 89 * The original filename in the user's filesystem. 90 */ 91 private String fileName; 92 93 94 /** 95 * The threshold above which uploads will be stored on disk. 96 */ 97 private int sizeThreshold; 98 99 100 /** 101 * The directory in which uploaded files will be stored, if stored on disk. 102 */ 103 private File repository; 104 105 106 /** 107 * Cached contents of the file. 108 */ 109 private byte[] cachedContent; 110 111 112 /** 113 * Output stream for this item. 114 */ 115 private DeferredFileOutputStream dfos; 116 117 118 // ----------------------------------------------------------- Constructors 119 120 121 /** 122 * Constructs a new <code>DefaultFileItem</code> instance. 123 * 124 * @param fieldName The name of the form field. 125 * @param contentType The content type passed by the browser or 126 * <code>null</code> if not specified. 127 * @param isFormField Whether or not this item is a plain form field, as 128 * opposed to a file upload. 129 * @param fileName The original filename in the user's filesystem, or 130 * <code>null</code> if not specified. 131 * @param sizeThreshold The threshold, in bytes, below which items will be 132 * retained in memory and above which they will be 133 * stored as a file. 134 * @param repository The data repository, which is the directory in 135 * which files will be created, should the item size 136 * exceed the threshold. 137 */ 138 DefaultFileItem(String fieldName, String contentType, boolean isFormField, 139 String fileName, int sizeThreshold, File repository) 140 { 141 this.fieldName = fieldName; 142 this.contentType = contentType; 143 this.isFormField = isFormField; 144 this.fileName = fileName; 145 this.sizeThreshold = sizeThreshold; 146 this.repository = repository; 147 } 148 149 150 // ------------------------------- Methods from javax.activation.DataSource 151 152 153 /** 154 * Returns an {@link java.io.InputStream InputStream} that can be 155 * used to retrieve the contents of the file. 156 * 157 * @return An {@link java.io.InputStream InputStream} that can be 158 * used to retrieve the contents of the file. 159 * 160 * @exception IOException if an error occurs. 161 */ 162 public InputStream getInputStream() 163 throws IOException 164 { 165 if (!dfos.isInMemory()) 166 { 167 return new FileInputStream(dfos.getFile()); 168 } 169 170 if (cachedContent == null) 171 { 172 cachedContent = dfos.getData(); 173 } 174 return new ByteArrayInputStream(cachedContent); 175 } 176 177 178 /** 179 * Returns the content type passed by the browser or <code>null</code> if 180 * not defined. 181 * 182 * @return The content type passed by the browser or <code>null</code> if 183 * not defined. 184 */ 185 public String getContentType() 186 { 187 return contentType; 188 } 189 190 191 /** 192 * Returns the original filename in the client's filesystem. 193 * 194 * @return The original filename in the client's filesystem. 195 */ 196 public String getName() 197 { 198 return fileName; 199 } 200 201 202 // ------------------------------------------------------- FileItem methods 203 204 205 /** 206 * Provides a hint as to whether or not the file contents will be read 207 * from memory. 208 * 209 * @return <code>true</code> if the file contents will be read 210 * from memory; <code>false</code> otherwise. 211 */ 212 public boolean isInMemory() 213 { 214 return (dfos.isInMemory()); 215 } 216 217 218 /** 219 * Returns the size of the file. 220 * 221 * @return The size of the file, in bytes. 222 */ 223 public long getSize() 224 { 225 if (cachedContent != null) 226 { 227 return cachedContent.length; 228 } 229 else if (dfos.isInMemory()) 230 { 231 return dfos.getData().length; 232 } 233 else 234 { 235 return dfos.getFile().length(); 236 } 237 } 238 239 240 /** 241 * Returns the contents of the file as an array of bytes. If the 242 * contents of the file were not yet cached in memory, they will be 243 * loaded from the disk storage and cached. 244 * 245 * @return The contents of the file as an array of bytes. 246 */ 247 public byte[] get() 248 { 249 if (dfos.isInMemory()) 250 { 251 if (cachedContent == null) 252 { 253 cachedContent = dfos.getData(); 254 } 255 return cachedContent; 256 } 257 258 byte[] fileData = new byte[(int) getSize()]; 259 FileInputStream fis = null; 260 261 try 262 { 263 fis = new FileInputStream(dfos.getFile()); 264 fis.read(fileData); 265 } 266 catch (IOException e) 267 { 268 fileData = null; 269 } 270 finally 271 { 272 if (fis != null) 273 { 274 try 275 { 276 fis.close(); 277 } 278 catch (IOException e) 279 { 280 // ignore 281 } 282 } 283 } 284 285 return fileData; 286 } 287 288 289 /** 290 * Returns the contents of the file as a String, using the specified 291 * encoding. This method uses {@link #get()} to retrieve the 292 * contents of the file. 293 * 294 * @param encoding The character encoding to use. 295 * 296 * @return The contents of the file, as a string. 297 * 298 * @exception UnsupportedEncodingException if the requested character 299 * encoding is not available. 300 */ 301 public String getString(String encoding) 302 throws UnsupportedEncodingException 303 { 304 return new String(get(), encoding); 305 } 306 307 308 /** 309 * Returns the contents of the file as a String, using the default 310 * character encoding. This method uses {@link #get()} to retrieve the 311 * contents of the file. 312 * 313 * @return The contents of the file, as a string. 314 */ 315 public String getString() 316 { 317 return new String(get()); 318 } 319 320 321 /** 322 * A convenience method to write an uploaded item to disk. The client code 323 * is not concerned with whether or not the item is stored in memory, or on 324 * disk in a temporary location. They just want to write the uploaded item 325 * to a file. 326 * <p> 327 * This implementation first attempts to rename the uploaded item to the 328 * specified destination file, if the item was originally written to disk. 329 * Otherwise, the data will be copied to the specified file. 330 * <p> 331 * This method is only guaranteed to work <em>once</em>, the first time it 332 * is invoked for a particular item. This is because, in the event that the 333 * method renames a temporary file, that file will no longer be available 334 * to copy or rename again at a later time. 335 * 336 * @param file The <code>File</code> into which the uploaded item should 337 * be stored. 338 * 339 * @exception Exception if an error occurs. 340 */ 341 public void write(File file) throws Exception 342 { 343 if (isInMemory()) 344 { 345 FileOutputStream fout = null; 346 try 347 { 348 fout = new FileOutputStream(file); 349 fout.write(get()); 350 } 351 finally 352 { 353 if (fout != null) 354 { 355 fout.close(); 356 } 357 } 358 } 359 else 360 { 361 File outputFile = getStoreLocation(); 362 if (outputFile != null) 363 { 364 /* 365 * The uploaded file is being stored on disk 366 * in a temporary location so move it to the 367 * desired file. 368 */ 369 if (!outputFile.renameTo(file)) 370 { 371 BufferedInputStream in = null; 372 BufferedOutputStream out = null; 373 try 374 { 375 in = new BufferedInputStream( 376 new FileInputStream(outputFile)); 377 out = new BufferedOutputStream( 378 new FileOutputStream(file)); 379 byte[] bytes = new byte[2048]; 380 int s = 0; 381 while ((s = in.read(bytes)) != -1) 382 { 383 out.write(bytes, 0, s); 384 } 385 } 386 finally 387 { 388 try 389 { 390 in.close(); 391 } 392 catch (IOException e) 393 { 394 // ignore 395 } 396 try 397 { 398 out.close(); 399 } 400 catch (IOException e) 401 { 402 // ignore 403 } 404 } 405 } 406 } 407 else 408 { 409 /* 410 * For whatever reason we cannot write the 411 * file to disk. 412 */ 413 throw new FileUploadException( 414 "Cannot write uploaded file to disk!"); 415 } 416 } 417 } 418 419 420 /** 421 * Deletes the underlying storage for a file item, including deleting any 422 * associated temporary disk file. Although this storage will be deleted 423 * automatically when the <code>FileItem</code> instance is garbage 424 * collected, this method can be used to ensure that this is done at an 425 * earlier time, thus preserving system resources. 426 */ 427 public void delete() 428 { 429 cachedContent = null; 430 File outputFile = getStoreLocation(); 431 if (outputFile != null && outputFile.exists()) 432 { 433 outputFile.delete(); 434 } 435 } 436 437 438 /** 439 * Returns the name of the field in the multipart form corresponding to 440 * this file item. 441 * 442 * @return The name of the form field. 443 * 444 * @see #setFieldName(java.lang.String) 445 * 446 */ 447 public String getFieldName() 448 { 449 return fieldName; 450 } 451 452 453 /** 454 * Sets the field name used to reference this file item. 455 * 456 * @param fieldName The name of the form field. 457 * 458 * @see #getFieldName() 459 * 460 */ 461 public void setFieldName(String fieldName) 462 { 463 this.fieldName = fieldName; 464 } 465 466 467 /** 468 * Determines whether or not a <code>FileItem</code> instance represents 469 * a simple form field. 470 * 471 * @return <code>true</code> if the instance represents a simple form 472 * field; <code>false</code> if it represents an uploaded file. 473 * 474 * @see #setFormField(boolean) 475 * 476 */ 477 public boolean isFormField() 478 { 479 return isFormField; 480 } 481 482 483 /** 484 * Specifies whether or not a <code>FileItem</code> instance represents 485 * a simple form field. 486 * 487 * @param state <code>true</code> if the instance represents a simple form 488 * field; <code>false</code> if it represents an uploaded file. 489 * 490 * @see #isFormField() 491 * 492 */ 493 public void setFormField(boolean state) 494 { 495 isFormField = state; 496 } 497 498 499 /** 500 * Returns an {@link java.io.OutputStream OutputStream} that can 501 * be used for storing the contents of the file. 502 * 503 * @return An {@link java.io.OutputStream OutputStream} that can be used 504 * for storing the contensts of the file. 505 * 506 * @exception IOException if an error occurs. 507 */ 508 public OutputStream getOutputStream() 509 throws IOException 510 { 511 if (dfos == null) 512 { 513 File outputFile = getTempFile(); 514 dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); 515 } 516 return dfos; 517 } 518 519 520 // --------------------------------------------------------- Public methods 521 522 523 /** 524 * Returns the {@link java.io.File} object for the <code>FileItem</code>'s 525 * data's temporary location on the disk. Note that for 526 * <code>FileItem</code>s that have their data stored in memory, 527 * this method will return <code>null</code>. When handling large 528 * files, you can use {@link java.io.File#renameTo(java.io.File)} to 529 * move the file to new location without copying the data, if the 530 * source and destination locations reside within the same logical 531 * volume. 532 * 533 * @return The data file, or <code>null</code> if the data is stored in 534 * memory. 535 */ 536 public File getStoreLocation() 537 { 538 return dfos.getFile(); 539 } 540 541 542 // ------------------------------------------------------ Protected methods 543 544 545 /** 546 * Removes the file contents from the temporary storage. 547 */ 548 protected void finalize() 549 { 550 File outputFile = dfos.getFile(); 551 552 if (outputFile != null && outputFile.exists()) 553 { 554 outputFile.delete(); 555 } 556 } 557 558 559 /** 560 * Creates and returns a {@link java.io.File File} representing a uniquely 561 * named temporary file in the configured repository path. 562 * 563 * @return The {@link java.io.File File} to be used for temporary storage. 564 */ 565 protected File getTempFile() 566 { 567 File tempDir = repository; 568 if (tempDir == null) 569 { 570 tempDir = new File(System.getProperty("java.io.tmpdir")); 571 } 572 573 String fileName = "upload_" + getUniqueId() + ".tmp"; 574 575 File f = new File(tempDir, fileName); 576 f.deleteOnExit(); 577 return f; 578 } 579 580 581 // -------------------------------------------------------- Private methods 582 583 584 /** 585 * Returns an identifier that is unique within the class loader used to 586 * load this class, but does not have random-like apearance. 587 * 588 * @return A String with the non-random looking instance identifier. 589 */ 590 private static String getUniqueId() 591 { 592 int current; 593 synchronized (DefaultFileItem.class) 594 { 595 current = counter++; 596 } 597 String id = Integer.toString(current); 598 599 // If you manage to get more than 100 million of ids, you'll 600 // start getting ids longer than 8 characters. 601 if (current < 100000000) 602 { 603 id = ("00000000" + id).substring(id.length()); 604 } 605 return id; 606 } 607 608 }