001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.platform.ui.web.util.files; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.lang.reflect.Field; 026import java.lang.reflect.Method; 027import java.nio.file.Files; 028import java.nio.file.StandardCopyOption; 029 030import javax.servlet.http.Part; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.ecm.core.api.Blob; 035import org.nuxeo.ecm.core.api.Blobs; 036import org.nuxeo.ecm.platform.mimetype.MimetypeDetectionException; 037import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; 038import org.nuxeo.runtime.api.Framework; 039 040public class FileUtils { 041 042 private static final Log log = LogFactory.getLog(FileUtils.class); 043 044 private FileUtils() { 045 } 046 047 /** 048 * Creates a serializable blob from a stream, with filename and mimetype detection. 049 * <p> 050 * Creates a serializable FileBlob which stores data in a temporary file on the hard disk. 051 * 052 * @param in the input stream holding data 053 * @param filename the file name. Will be set on the blob and will used for mimetype detection. 054 * @param mimeType the detected mimetype at upload. Can be null. Will be verified by the mimetype service. 055 */ 056 public static Blob createSerializableBlob(InputStream in, String filename, String mimeType) { 057 Blob blob = null; 058 try { 059 // persisting the blob makes it possible to read the binary content 060 // of the request stream several times (mimetype sniffing, digest 061 // computation, core binary storage) 062 blob = Blobs.createBlob(in, mimeType); 063 // filename 064 if (filename != null) { 065 filename = getCleanFileName(filename); 066 } 067 blob.setFilename(filename); 068 // mimetype detection 069 MimetypeRegistry mimeService = Framework.getService(MimetypeRegistry.class); 070 String detectedMimeType = mimeService.getMimetypeFromFilenameAndBlobWithDefault(filename, blob, null); 071 if (detectedMimeType == null) { 072 if (mimeType != null) { 073 detectedMimeType = mimeType; 074 } else { 075 // default 076 detectedMimeType = "application/octet-stream"; 077 } 078 } 079 blob.setMimeType(detectedMimeType); 080 } catch (MimetypeDetectionException e) { 081 log.error(String.format("could not fetch mimetype for file %s", filename), e); 082 } catch (IOException e) { 083 log.error(e); 084 } 085 return blob; 086 } 087 088 /** 089 * Creates a Blob from a {@link Part}. 090 * <p> 091 * Attempts to capture the underlying temporary file, if one exists. This needs to use reflection to avoid having 092 * dependencies on the application server. 093 * 094 * @param part the servlet part 095 * @return the blob 096 * @since 7.2 097 */ 098 public static Blob createBlob(Part part) throws IOException { 099 Blob blob = null; 100 try { 101 // part : org.apache.catalina.core.ApplicationPart 102 // part.fileItem : org.apache.tomcat.util.http.fileupload.disk.DiskFileItem 103 // part.fileItem.isInMemory() : false if on disk 104 // part.fileItem.getStoreLocation() : java.io.File 105 Field fileItemField = part.getClass().getDeclaredField("fileItem"); 106 fileItemField.setAccessible(true); 107 Object fileItem = fileItemField.get(part); 108 if (fileItem != null) { 109 Method isInMemoryMethod = fileItem.getClass().getDeclaredMethod("isInMemory"); 110 boolean inMemory = ((Boolean) isInMemoryMethod.invoke(fileItem)).booleanValue(); 111 if (!inMemory) { 112 Method getStoreLocationMethod = fileItem.getClass().getDeclaredMethod("getStoreLocation"); 113 File file = (File) getStoreLocationMethod.invoke(fileItem); 114 if (file != null) { 115 // move the file to a temporary blob we own 116 blob = Blobs.createBlobWithExtension(null); 117 Files.move(file.toPath(), blob.getFile().toPath(), StandardCopyOption.REPLACE_EXISTING); 118 } 119 } 120 } 121 } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) { 122 // unknown Part implementation 123 } 124 if (blob == null) { 125 // if we couldn't get to the file, use the InputStream 126 blob = Blobs.createBlob(part.getInputStream()); 127 } 128 blob.setMimeType(part.getContentType()); 129 blob.setFilename(retrieveFilename(part)); 130 return blob; 131 } 132 133 /** 134 * Helper method to retrieve filename from part, waiting for servlet-api related improvements. 135 * <p> 136 * Filename is cleaned before being returned. 137 * 138 * @see #getCleanFileName(String) 139 * @since 7.1 140 */ 141 public static String retrieveFilename(Part part) { 142 for (String cd : part.getHeader("content-disposition").split(";")) { 143 if (cd.trim().startsWith("filename")) { 144 String filename = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", ""); 145 return getCleanFileName(filename); 146 } 147 } 148 return null; 149 } 150 151 /** 152 * Returns a clean filename, stripping upload path on client side. 153 * <p> 154 * Fixes NXP-544 155 */ 156 public static String getCleanFileName(String filename) { 157 String res = null; 158 int lastWinSeparator = filename.lastIndexOf('\\'); 159 int lastUnixSeparator = filename.lastIndexOf('/'); 160 int lastSeparator = Math.max(lastWinSeparator, lastUnixSeparator); 161 if (lastSeparator != -1) { 162 res = filename.substring(lastSeparator + 1, filename.length()); 163 } else { 164 res = filename; 165 } 166 return res; 167 } 168 169 public static void configureFileBlob(Blob blob) { 170 // mimetype detection 171 MimetypeRegistry mimeService = Framework.getService(MimetypeRegistry.class); 172 String detectedMimeType; 173 try { 174 detectedMimeType = mimeService.getMimetypeFromFilenameAndBlobWithDefault(blob.getFilename(), blob, null); 175 } catch (MimetypeDetectionException e) { 176 log.error("could not fetch mimetype for file " + blob.getFilename(), e); 177 return; 178 } 179 if (detectedMimeType == null) { 180 if (blob.getMimeType() != null) { 181 return; 182 } 183 // default 184 detectedMimeType = "application/octet-stream"; 185 } 186 blob.setMimeType(detectedMimeType); 187 return; 188 } 189 190}