001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.form.upload; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.InputStream; 022import java.security.MessageDigest; 023import java.security.NoSuchAlgorithmException; 024import java.util.ArrayList; 025import java.util.List; 026 027import org.apache.commons.fileupload.FileItem; 028import org.apache.wicket.util.io.IClusterable; 029import org.apache.wicket.Session; 030import org.apache.wicket.WicketRuntimeException; 031import org.apache.wicket.request.cycle.RequestCycle; 032import org.apache.wicket.util.file.Files; 033import org.apache.wicket.util.io.IOUtils; 034import org.apache.wicket.util.lang.Args; 035import org.apache.wicket.util.string.Strings; 036 037 038/** 039 * Model for file uploads. Objects of this class should not be kept between requests, and should 040 * therefore be marked as <code>transient</code> if they become a property of an IModel. 041 * 042 * @author Jonathan Locke 043 */ 044public class FileUpload 045{ 046 private static final long serialVersionUID = 1L; 047 048 private final FileItem item; 049 050 private transient List<InputStream> inputStreamsToClose; 051 052 /** 053 * Constructor 054 * 055 * @param item 056 * The uploaded file item 057 */ 058 public FileUpload(final FileItem item) 059 { 060 Args.notNull(item, "item"); 061 this.item = item; 062 } 063 064 /** 065 * Close the streams which has been opened when getting the InputStream using 066 * {@link #getInputStream()}. All the input streams are closed at the end of the request. This 067 * is done when the FileUploadField, which is associated with this FileUpload is detached. 068 * <p> 069 * If an exception is thrown when closing the input streams, we ignore it, because the stream 070 * might have been closed already. 071 */ 072 public final void closeStreams() 073 { 074 if (inputStreamsToClose != null) 075 { 076 for (InputStream inputStream : inputStreamsToClose) 077 { 078 IOUtils.closeQuietly(inputStream); 079 } 080 081 // Reset the list 082 inputStreamsToClose = null; 083 } 084 } 085 086 /** 087 * Deletes temp file from disk 088 */ 089 public void delete() 090 { 091 item.delete(); 092 } 093 094 /** 095 * @return Uploaded file as an array of bytes 096 */ 097 public byte[] getBytes() 098 { 099 return item.get(); 100 } 101 102 /** 103 * Get the MD5 checksum. 104 * 105 * @param algorithm 106 * the digest algorithm, e.g. MD5, SHA-1, SHA-256, SHA-512 107 * 108 * @return The cryptographic digest of the file 109 */ 110 public byte[] getDigest(String algorithm) 111 { 112 try 113 { 114 Args.notEmpty(algorithm, "algorithm"); 115 MessageDigest digest = java.security.MessageDigest.getInstance(algorithm); 116 117 if (item.isInMemory()) 118 { 119 digest.update(getBytes()); 120 return digest.digest(); 121 } 122 123 InputStream in = null; 124 125 try 126 { 127 in = item.getInputStream(); 128 byte[] buf = new byte[Math.min((int)item.getSize(), 4096 * 10)]; 129 int len; 130 while (-1 != (len = in.read(buf))) 131 { 132 digest.update(buf, 0, len); 133 } 134 return digest.digest(); 135 } 136 catch (IOException ex) 137 { 138 throw new WicketRuntimeException("Error while reading input data for " + algorithm + 139 " checksum", ex); 140 } 141 finally 142 { 143 IOUtils.closeQuietly(in); 144 } 145 } 146 catch (NoSuchAlgorithmException ex) 147 { 148 String error = String.format( 149 "Your java runtime does not support digest algorithm [%s]. " 150 + "Please see java.security.MessageDigest.getInstance(\"%s\")", algorithm, 151 algorithm); 152 153 throw new WicketRuntimeException(error, ex); 154 } 155 } 156 157 /** 158 * Get the MD5 checksum. 159 * 160 * @return The MD5 checksum of the file 161 */ 162 public byte[] getMD5() 163 { 164 return getDigest("MD5"); 165 } 166 167 /** 168 * @since 1.2 169 * @return name of uploaded client side file 170 */ 171 public String getClientFileName() 172 { 173 String name = item.getName(); 174 175 // when uploading from localhost some browsers will specify the entire path, we strip it 176 // down to just the file name 177 name = Strings.lastPathComponent(name, '/'); 178 name = Strings.lastPathComponent(name, '\\'); 179 180 return name; 181 } 182 183 /** 184 * @return Content type for upload 185 */ 186 public String getContentType() 187 { 188 return item.getContentType(); 189 } 190 191 /** 192 * Get an input stream for the file uploaded. Use this input stream if you can't use 193 * {@link #writeTo(File)} for persisting the uploaded file. This can be if you need to react 194 * upon the content of the file or need to persist it elsewhere, i.e. a database or external 195 * filesystem. 196 * <p> 197 * <b>PLEASE NOTE!</b><br> 198 * The InputStream return will be closed be Wicket at the end of the request. If you need it 199 * across a request you need to hold on to this FileUpload instead. 200 * 201 * @return Input stream with file contents. 202 * @throws IOException 203 */ 204 public InputStream getInputStream() throws IOException 205 { 206 if (inputStreamsToClose == null) 207 { 208 inputStreamsToClose = new ArrayList<InputStream>(); 209 } 210 211 InputStream is = item.getInputStream(); 212 inputStreamsToClose.add(is); 213 214 return is; 215 } 216 217 /** 218 * @return The upload's size 219 */ 220 public long getSize() 221 { 222 return item.getSize(); 223 } 224 225 /** 226 * Saves this file upload to a given file on the server side. 227 * 228 * @param file 229 * The file 230 * @throws Exception 231 */ 232 public void writeTo(final File file) throws Exception 233 { 234 Files.remove(file); 235 item.write(file); 236 } 237 238 /** 239 * Convenience method that copies the input stream returned by {@link #getInputStream()} into a 240 * temporary file. 241 * <p> 242 * Only use this if you actually need a {@link File} to work with, in all other cases use 243 * {@link #getInputStream()} or {@link #getBytes()} 244 * 245 * @since 1.2 246 * 247 * @return temporary file containing the contents of the uploaded file 248 * @throws Exception 249 */ 250 public final File writeToTempFile() throws Exception 251 { 252 String sessionId = Session.exists() ? Session.get().getId() : ""; 253 String tempFileName = sessionId + "_" + RequestCycle.get().getStartTime(); 254 File temp = File.createTempFile(tempFileName, Files.cleanupFilename(item.getFieldName())); 255 writeTo(temp); 256 return temp; 257 } 258}