1. /*
  2. * $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/DefaultFileItem.java,v 1.21 2003/06/24 05:45:15 martinc Exp $
  3. * $Revision: 1.21 $
  4. * $Date: 2003/06/24 05:45:15 $
  5. *
  6. * ====================================================================
  7. *
  8. * The Apache Software License, Version 1.1
  9. *
  10. * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
  11. * reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without
  14. * modification, are permitted provided that the following conditions
  15. * are met:
  16. *
  17. * 1. Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * 2. Redistributions in binary form must reproduce the above copyright
  21. * notice, this list of conditions and the following disclaimer in
  22. * the documentation and/or other materials provided with the
  23. * distribution.
  24. *
  25. * 3. The end-user documentation included with the redistribution, if
  26. * any, must include the following acknowlegement:
  27. * "This product includes software developed by the
  28. * Apache Software Foundation (http://www.apache.org/)."
  29. * Alternately, this acknowlegement may appear in the software itself,
  30. * if and wherever such third-party acknowlegements normally appear.
  31. *
  32. * 4. The names "The Jakarta Project", "Commons", and "Apache Software
  33. * Foundation" must not be used to endorse or promote products derived
  34. * from this software without prior written permission. For written
  35. * permission, please contact apache@apache.org.
  36. *
  37. * 5. Products derived from this software may not be called "Apache"
  38. * nor may "Apache" appear in their names without prior written
  39. * permission of the Apache Group.
  40. *
  41. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  42. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  43. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  44. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  45. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  46. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  47. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  48. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  49. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  50. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  51. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  52. * SUCH DAMAGE.
  53. * ====================================================================
  54. *
  55. * This software consists of voluntary contributions made by many
  56. * individuals on behalf of the Apache Software Foundation. For more
  57. * information on the Apache Software Foundation, please see
  58. * <http://www.apache.org/>.
  59. *
  60. */
  61. package org.apache.commons.fileupload;
  62. import java.io.BufferedInputStream;
  63. import java.io.BufferedOutputStream;
  64. import java.io.ByteArrayInputStream;
  65. import java.io.File;
  66. import java.io.FileInputStream;
  67. import java.io.FileOutputStream;
  68. import java.io.IOException;
  69. import java.io.InputStream;
  70. import java.io.OutputStream;
  71. import java.io.UnsupportedEncodingException;
  72. /**
  73. * <p> The default implementation of the
  74. * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
  75. *
  76. * <p> After retrieving an instance of this class from a {@link
  77. * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
  78. * {@link org.apache.commons.fileupload.DiskFileUpload
  79. * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
  80. * either request all contents of file at once using {@link #get()} or
  81. * request an {@link java.io.InputStream InputStream} with
  82. * {@link #getInputStream()} and process the file without attempting to load
  83. * it into memory, which may come handy with large files.
  84. *
  85. * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
  86. * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
  87. * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
  88. * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
  89. * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
  90. * @author Sean C. Sullivan
  91. *
  92. * @version $Id: DefaultFileItem.java,v 1.21 2003/06/24 05:45:15 martinc Exp $
  93. */
  94. public class DefaultFileItem
  95. implements FileItem
  96. {
  97. // ----------------------------------------------------------- Data members
  98. /**
  99. * Counter used in unique identifier generation.
  100. */
  101. private static int counter = 0;
  102. /**
  103. * The name of the form field as provided by the browser.
  104. */
  105. private String fieldName;
  106. /**
  107. * The content type passed by the browser, or <code>null</code> if
  108. * not defined.
  109. */
  110. private String contentType;
  111. /**
  112. * Whether or not this item is a simple form field.
  113. */
  114. private boolean isFormField;
  115. /**
  116. * The original filename in the user's filesystem.
  117. */
  118. private String fileName;
  119. /**
  120. * The threshold above which uploads will be stored on disk.
  121. */
  122. private int sizeThreshold;
  123. /**
  124. * The directory in which uploaded files will be stored, if stored on disk.
  125. */
  126. private File repository;
  127. /**
  128. * Cached contents of the file.
  129. */
  130. private byte[] cachedContent;
  131. /**
  132. * Output stream for this item.
  133. */
  134. private DeferredFileOutputStream dfos;
  135. // ----------------------------------------------------------- Constructors
  136. /**
  137. * Constructs a new <code>DefaultFileItem</code> instance.
  138. *
  139. * @param fieldName The name of the form field.
  140. * @param contentType The content type passed by the browser or
  141. * <code>null</code> if not specified.
  142. * @param isFormField Whether or not this item is a plain form field, as
  143. * opposed to a file upload.
  144. * @param fileName The original filename in the user's filesystem, or
  145. * <code>null</code> if not specified.
  146. * @param sizeThreshold The threshold, in bytes, below which items will be
  147. * retained in memory and above which they will be
  148. * stored as a file.
  149. * @param repository The data repository, which is the directory in
  150. * which files will be created, should the item size
  151. * exceed the threshold.
  152. */
  153. DefaultFileItem(String fieldName, String contentType, boolean isFormField,
  154. String fileName, int sizeThreshold, File repository)
  155. {
  156. this.fieldName = fieldName;
  157. this.contentType = contentType;
  158. this.isFormField = isFormField;
  159. this.fileName = fileName;
  160. this.sizeThreshold = sizeThreshold;
  161. this.repository = repository;
  162. }
  163. // ------------------------------- Methods from javax.activation.DataSource
  164. /**
  165. * Returns an {@link java.io.InputStream InputStream} that can be
  166. * used to retrieve the contents of the file.
  167. *
  168. * @return An {@link java.io.InputStream InputStream} that can be
  169. * used to retrieve the contents of the file.
  170. *
  171. * @exception IOException if an error occurs.
  172. */
  173. public InputStream getInputStream()
  174. throws IOException
  175. {
  176. if (!dfos.isInMemory())
  177. {
  178. return new FileInputStream(dfos.getFile());
  179. }
  180. if (cachedContent == null)
  181. {
  182. cachedContent = dfos.getData();
  183. }
  184. return new ByteArrayInputStream(cachedContent);
  185. }
  186. /**
  187. * Returns the content type passed by the browser or <code>null</code> if
  188. * not defined.
  189. *
  190. * @return The content type passed by the browser or <code>null</code> if
  191. * not defined.
  192. */
  193. public String getContentType()
  194. {
  195. return contentType;
  196. }
  197. /**
  198. * Returns the original filename in the client's filesystem.
  199. *
  200. * @return The original filename in the client's filesystem.
  201. */
  202. public String getName()
  203. {
  204. return fileName;
  205. }
  206. // ------------------------------------------------------- FileItem methods
  207. /**
  208. * Provides a hint as to whether or not the file contents will be read
  209. * from memory.
  210. *
  211. * @return <code>true</code> if the file contents will be read
  212. * from memory; <code>false</code> otherwise.
  213. */
  214. public boolean isInMemory()
  215. {
  216. return (dfos.isInMemory());
  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. * Returns the contents of the file as an array of bytes. If the
  240. * contents of the file were not yet cached in memory, they will be
  241. * loaded from the disk storage and cached.
  242. *
  243. * @return The contents of the file as an array of bytes.
  244. */
  245. public byte[] get()
  246. {
  247. if (dfos.isInMemory())
  248. {
  249. if (cachedContent == null)
  250. {
  251. cachedContent = dfos.getData();
  252. }
  253. return cachedContent;
  254. }
  255. byte[] fileData = new byte[(int) getSize()];
  256. FileInputStream fis = null;
  257. try
  258. {
  259. fis = new FileInputStream(dfos.getFile());
  260. fis.read(fileData);
  261. }
  262. catch (IOException e)
  263. {
  264. fileData = null;
  265. }
  266. finally
  267. {
  268. if (fis != null)
  269. {
  270. try
  271. {
  272. fis.close();
  273. }
  274. catch (IOException e)
  275. {
  276. // ignore
  277. }
  278. }
  279. }
  280. return fileData;
  281. }
  282. /**
  283. * Returns the contents of the file as a String, using the specified
  284. * encoding. This method uses {@link #get()} to retrieve the
  285. * contents of the file.
  286. *
  287. * @param encoding The character encoding to use.
  288. *
  289. * @return The contents of the file, as a string.
  290. *
  291. * @exception UnsupportedEncodingException if the requested character
  292. * encoding is not available.
  293. */
  294. public String getString(String encoding)
  295. throws UnsupportedEncodingException
  296. {
  297. return new String(get(), encoding);
  298. }
  299. /**
  300. * Returns the contents of the file as a String, using the default
  301. * character encoding. This method uses {@link #get()} to retrieve the
  302. * contents of the file.
  303. *
  304. * @return The contents of the file, as a string.
  305. */
  306. public String getString()
  307. {
  308. return new String(get());
  309. }
  310. /**
  311. * A convenience method to write an uploaded item to disk. The client code
  312. * is not concerned with whether or not the item is stored in memory, or on
  313. * disk in a temporary location. They just want to write the uploaded item
  314. * to a file.
  315. * <p>
  316. * This implementation first attempts to rename the uploaded item to the
  317. * specified destination file, if the item was originally written to disk.
  318. * Otherwise, the data will be copied to the specified file.
  319. * <p>
  320. * This method is only guaranteed to work <em>once</em>, the first time it
  321. * is invoked for a particular item. This is because, in the event that the
  322. * method renames a temporary file, that file will no longer be available
  323. * to copy or rename again at a later time.
  324. *
  325. * @param file The <code>File</code> into which the uploaded item should
  326. * be stored.
  327. *
  328. * @exception Exception if an error occurs.
  329. */
  330. public void write(File file) throws Exception
  331. {
  332. if (isInMemory())
  333. {
  334. FileOutputStream fout = null;
  335. try
  336. {
  337. fout = new FileOutputStream(file);
  338. fout.write(get());
  339. }
  340. finally
  341. {
  342. if (fout != null)
  343. {
  344. fout.close();
  345. }
  346. }
  347. }
  348. else
  349. {
  350. File outputFile = getStoreLocation();
  351. if (outputFile != null)
  352. {
  353. /*
  354. * The uploaded file is being stored on disk
  355. * in a temporary location so move it to the
  356. * desired file.
  357. */
  358. if (!outputFile.renameTo(file))
  359. {
  360. BufferedInputStream in = null;
  361. BufferedOutputStream out = null;
  362. try
  363. {
  364. in = new BufferedInputStream(
  365. new FileInputStream(outputFile));
  366. out = new BufferedOutputStream(
  367. new FileOutputStream(file));
  368. byte[] bytes = new byte[2048];
  369. int s = 0;
  370. while ((s = in.read(bytes)) != -1)
  371. {
  372. out.write(bytes, 0, s);
  373. }
  374. }
  375. finally
  376. {
  377. try
  378. {
  379. in.close();
  380. }
  381. catch (IOException e)
  382. {
  383. // ignore
  384. }
  385. try
  386. {
  387. out.close();
  388. }
  389. catch (IOException e)
  390. {
  391. // ignore
  392. }
  393. }
  394. }
  395. }
  396. else
  397. {
  398. /*
  399. * For whatever reason we cannot write the
  400. * file to disk.
  401. */
  402. throw new FileUploadException(
  403. "Cannot write uploaded file to disk!");
  404. }
  405. }
  406. }
  407. /**
  408. * Deletes the underlying storage for a file item, including deleting any
  409. * associated temporary disk file. Although this storage will be deleted
  410. * automatically when the <code>FileItem</code> instance is garbage
  411. * collected, this method can be used to ensure that this is done at an
  412. * earlier time, thus preserving system resources.
  413. */
  414. public void delete()
  415. {
  416. cachedContent = null;
  417. File outputFile = getStoreLocation();
  418. if (outputFile != null && outputFile.exists())
  419. {
  420. outputFile.delete();
  421. }
  422. }
  423. /**
  424. * Returns the name of the field in the multipart form corresponding to
  425. * this file item.
  426. *
  427. * @return The name of the form field.
  428. *
  429. * @see #setFieldName(java.lang.String)
  430. *
  431. */
  432. public String getFieldName()
  433. {
  434. return fieldName;
  435. }
  436. /**
  437. * Sets the field name used to reference this file item.
  438. *
  439. * @param fieldName The name of the form field.
  440. *
  441. * @see #getFieldName()
  442. *
  443. */
  444. public void setFieldName(String fieldName)
  445. {
  446. this.fieldName = fieldName;
  447. }
  448. /**
  449. * Determines whether or not a <code>FileItem</code> instance represents
  450. * a simple form field.
  451. *
  452. * @return <code>true</code> if the instance represents a simple form
  453. * field; <code>false</code> if it represents an uploaded file.
  454. *
  455. * @see #setFormField(boolean)
  456. *
  457. */
  458. public boolean isFormField()
  459. {
  460. return isFormField;
  461. }
  462. /**
  463. * Specifies whether or not a <code>FileItem</code> instance represents
  464. * a simple form field.
  465. *
  466. * @param state <code>true</code> if the instance represents a simple form
  467. * field; <code>false</code> if it represents an uploaded file.
  468. *
  469. * @see #isFormField()
  470. *
  471. */
  472. public void setFormField(boolean state)
  473. {
  474. isFormField = state;
  475. }
  476. /**
  477. * Returns an {@link java.io.OutputStream OutputStream} that can
  478. * be used for storing the contents of the file.
  479. *
  480. * @return An {@link java.io.OutputStream OutputStream} that can be used
  481. * for storing the contensts of the file.
  482. *
  483. * @exception IOException if an error occurs.
  484. */
  485. public OutputStream getOutputStream()
  486. throws IOException
  487. {
  488. if (dfos == null)
  489. {
  490. File outputFile = getTempFile();
  491. dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
  492. }
  493. return dfos;
  494. }
  495. // --------------------------------------------------------- Public methods
  496. /**
  497. * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
  498. * data's temporary location on the disk. Note that for
  499. * <code>FileItem</code>s that have their data stored in memory,
  500. * this method will return <code>null</code>. When handling large
  501. * files, you can use {@link java.io.File#renameTo(java.io.File)} to
  502. * move the file to new location without copying the data, if the
  503. * source and destination locations reside within the same logical
  504. * volume.
  505. *
  506. * @return The data file, or <code>null</code> if the data is stored in
  507. * memory.
  508. */
  509. public File getStoreLocation()
  510. {
  511. return dfos.getFile();
  512. }
  513. // ------------------------------------------------------ Protected methods
  514. /**
  515. * Removes the file contents from the temporary storage.
  516. */
  517. protected void finalize()
  518. {
  519. File outputFile = dfos.getFile();
  520. if (outputFile != null && outputFile.exists())
  521. {
  522. outputFile.delete();
  523. }
  524. }
  525. /**
  526. * Creates and returns a {@link java.io.File File} representing a uniquely
  527. * named temporary file in the configured repository path.
  528. *
  529. * @return The {@link java.io.File File} to be used for temporary storage.
  530. */
  531. protected File getTempFile()
  532. {
  533. File tempDir = repository;
  534. if (tempDir == null)
  535. {
  536. tempDir = new File(System.getProperty("java.io.tmpdir"));
  537. }
  538. String fileName = "upload_" + getUniqueId() + ".tmp";
  539. File f = new File(tempDir, fileName);
  540. f.deleteOnExit();
  541. return f;
  542. }
  543. // -------------------------------------------------------- Private methods
  544. /**
  545. * Returns an identifier that is unique within the class loader used to
  546. * load this class, but does not have random-like apearance.
  547. *
  548. * @return A String with the non-random looking instance identifier.
  549. */
  550. private static String getUniqueId()
  551. {
  552. int current;
  553. synchronized (DefaultFileItem.class)
  554. {
  555. current = counter++;
  556. }
  557. String id = Integer.toString(current);
  558. // If you manage to get more than 100 million of ids, you'll
  559. // start getting ids longer than 8 characters.
  560. if (current < 100000000)
  561. {
  562. id = ("00000000" + id).substring(id.length());
  563. }
  564. return id;
  565. }
  566. }