001// Copyright (C) 1999-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>. 002// All rights reserved. Use of this class is limited. 003// Please see the LICENSE for more information. 004 005package com.oreilly.servlet.multipart; 006 007import java.io.File; 008import java.io.InputStream; 009import java.io.OutputStream; 010import java.io.BufferedOutputStream; 011import java.io.FileOutputStream; 012import java.io.IOException; 013import javax.servlet.ServletInputStream; 014 015/** 016 * A <code>FilePart</code> is an upload part which represents a 017 * <code>INPUT TYPE="file"</code> form parameter. Note that because file 018 * upload data arrives via a single InputStream, each FilePart's contents 019 * must be read before moving onto the next part. Don't try to store a 020 * FilePart object for later processing because by then their content will 021 * have been passed by. 022 * 023 * @author Geoff Soutter 024 * @version 1.2, 2001/01/22, getFilePath() addition thanks to Stefan Eissing 025 * @version 1.1, 2000/11/26, writeTo() bug fix thanks to Mike Shivas 026 * @version 1.0, 2000/10/27, initial revision 027 */ 028public class FilePart extends Part { 029 030 /** "file system" name of the file */ 031 private String fileName; 032 033 /** path of the file as sent in the request, if given */ 034 private String filePath; 035 036 /** content type of the file */ 037 private String contentType; 038 039 /** input stream containing file data */ 040 private PartInputStream partInput; 041 042 /** file rename policy */ 043 private FileRenamePolicy policy; 044 045 /** 046 * Construct a file part; this is called by the parser. 047 * 048 * @param name the name of the parameter. 049 * @param in the servlet input stream to read the file from. 050 * @param boundary the MIME boundary that delimits the end of file. 051 * @param contentType the content type of the file provided in the 052 * MIME header. 053 * @param fileName the file system name of the file provided in the 054 * MIME header. 055 * @param filePath the file system path of the file provided in the 056 * MIME header (as specified in disposition info). 057 * 058 * @exception IOException if an input or output exception has occurred. 059 */ 060 FilePart(String name, ServletInputStream in, String boundary, 061 String contentType, String fileName, String filePath) 062 throws IOException { 063 super(name); 064 this.fileName = fileName; 065 this.filePath = filePath; 066 this.contentType = contentType; 067 partInput = new PartInputStream(in, boundary); 068 } 069 070 /** 071 * Puts in place the specified policy for handling file name collisions. 072 */ 073 public void setRenamePolicy(FileRenamePolicy policy) { 074 this.policy = policy; 075 } 076 077 /** 078 * Returns the name that the file was stored with on the remote system, 079 * or <code>null</code> if the user didn't enter a file to be uploaded. 080 * Note: this is not the same as the name of the form parameter used to 081 * transmit the file; that is available from the <code>getName</code> 082 * method. Further note: if file rename logic is in effect, the file 083 * name can change during the writeTo() method when there's a collision 084 * with another file of the same name in the same directory. If this 085 * matters to you, be sure to pay attention to when you call the method. 086 * 087 * @return name of file uploaded or <code>null</code>. 088 * 089 * @see Part#getName() 090 */ 091 public String getFileName() { 092 return fileName; 093 } 094 095 /** 096 * Returns the full path and name of the file on the remote system, 097 * or <code>null</code> if the user didn't enter a file to be uploaded. 098 * If path information was not supplied by the remote system, this method 099 * will return the same as <code>getFileName()</code>. 100 * 101 * @return path of file uploaded or <code>null</code>. 102 * 103 * @see Part#getName() 104 */ 105 public String getFilePath() { 106 return filePath; 107 } 108 109 /** 110 * Returns the content type of the file data contained within. 111 * 112 * @return content type of the file data. 113 */ 114 public String getContentType() { 115 return contentType; 116 } 117 118 /** 119 * Returns an input stream which contains the contents of the 120 * file supplied. If the user didn't enter a file to upload 121 * there will be <code>0</code> bytes in the input stream. 122 * It's important to read the contents of the InputStream 123 * immediately and in full before proceeding to process the 124 * next part. The contents will otherwise be lost on moving 125 * to the next part. 126 * 127 * @return an input stream containing contents of file. 128 */ 129 public InputStream getInputStream() { 130 return partInput; 131 } 132 133 /** 134 * Write this file part to a file or directory. If the user 135 * supplied a file, we write it to that file, and if they supplied 136 * a directory, we write it to that directory with the filename 137 * that accompanied it. If this part doesn't contain a file this 138 * method does nothing. 139 * 140 * @return number of bytes written 141 * @exception IOException if an input or output exception has occurred. 142 */ 143 public long writeTo(File fileOrDirectory) throws IOException { 144 long written = 0; 145 146 OutputStream fileOut = null; 147 try { 148 // Only do something if this part contains a file 149 if (fileName != null) { 150 // Check if user supplied directory 151 File file; 152 if (fileOrDirectory.isDirectory()) { 153 // Write it to that dir the user supplied, 154 // with the filename it arrived with 155 file = new File(fileOrDirectory, fileName); 156 } 157 else { 158 // Write it to the file the user supplied, 159 // ignoring the filename it arrived with 160 file = fileOrDirectory; 161 } 162 if (policy != null) { 163 file = policy.rename(file); 164 fileName = file.getName(); 165 } 166 fileOut = new BufferedOutputStream(new FileOutputStream(file)); 167 written = write(fileOut); 168 } 169 } 170 finally { 171 if (fileOut != null) fileOut.close(); 172 } 173 return written; 174 } 175 176 /** 177 * Write this file part to the given output stream. If this part doesn't 178 * contain a file this method does nothing. 179 * 180 * @return number of bytes written. 181 * @exception IOException if an input or output exception has occurred. 182 */ 183 public long writeTo(OutputStream out) throws IOException { 184 long size=0; 185 // Only do something if this part contains a file 186 if (fileName != null) { 187 // Write it out 188 size = write( out ); 189 } 190 return size; 191 } 192 193 /** 194 * Internal method to write this file part; doesn't check to see 195 * if it has contents first. 196 * 197 * @return number of bytes written. 198 * @exception IOException if an input or output exception has occurred. 199 */ 200 long write(OutputStream out) throws IOException { 201 // decode macbinary if this was sent 202 if (contentType.equals("application/x-macbinary")) { 203 out = new MacBinaryDecoderOutputStream(out); 204 } 205 long size=0; 206 int read; 207 byte[] buf = new byte[8 * 1024]; 208 while((read = partInput.read(buf)) != -1) { 209 out.write(buf, 0, read); 210 size += read; 211 } 212 return size; 213 } 214 215 /** 216 * Returns <code>true</code> to indicate this part is a file. 217 * 218 * @return true. 219 */ 220 public boolean isFile() { 221 return true; 222 } 223}