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}