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