/*
* (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id$
*/
package org.nuxeo.ecm.platform.ui.web.util.files;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import javax.servlet.http.Part;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.Blobs;
import org.nuxeo.ecm.platform.mimetype.MimetypeDetectionException;
import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
import org.nuxeo.runtime.api.Framework;
public class FileUtils {
private static final Log log = LogFactory.getLog(FileUtils.class);
private FileUtils() {
}
/**
* Creates a serializable blob from a stream, with filename and mimetype detection.
* <p>
* Creates a serializable FileBlob which stores data in a temporary file on the hard disk.
*
* @param in the input stream holding data
* @param filename the file name. Will be set on the blob and will used for mimetype detection.
* @param mimeType the detected mimetype at upload. Can be null. Will be verified by the mimetype service.
*/
public static Blob createSerializableBlob(InputStream in, String filename, String mimeType) {
Blob blob = null;
try {
// persisting the blob makes it possible to read the binary content
// of the request stream several times (mimetype sniffing, digest
// computation, core binary storage)
blob = Blobs.createBlob(in, mimeType);
// filename
if (filename != null) {
filename = getCleanFileName(filename);
}
blob.setFilename(filename);
// mimetype detection
MimetypeRegistry mimeService = Framework.getService(MimetypeRegistry.class);
String detectedMimeType = mimeService.getMimetypeFromFilenameAndBlobWithDefault(filename, blob, null);
if (detectedMimeType == null) {
if (mimeType != null) {
detectedMimeType = mimeType;
} else {
// default
detectedMimeType = "application/octet-stream";
}
}
blob.setMimeType(detectedMimeType);
} catch (MimetypeDetectionException e) {
log.error("Could not fetch mimetype for file " + filename, e);
} catch (IOException e) {
log.error(e);
}
return blob;
}
/**
* Creates a Blob from a {@link Part}.
* <p>
* Attempts to capture the underlying temporary file, if one exists. This needs to use reflection to avoid having
* dependencies on the application server.
*
* @param part the servlet part
* @return the blob
* @since 7.2
*/
public static Blob createBlob(Part part) throws IOException {
Blob blob = null;
try {
// part : org.apache.catalina.core.ApplicationPart
// part.fileItem : org.apache.tomcat.util.http.fileupload.disk.DiskFileItem
// part.fileItem.isInMemory() : false if on disk
// part.fileItem.getStoreLocation() : java.io.File
Field fileItemField = part.getClass().getDeclaredField("fileItem");
fileItemField.setAccessible(true);
Object fileItem = fileItemField.get(part);
if (fileItem != null) {
Method isInMemoryMethod = fileItem.getClass().getDeclaredMethod("isInMemory");
boolean inMemory = ((Boolean) isInMemoryMethod.invoke(fileItem)).booleanValue();
if (!inMemory) {
Method getStoreLocationMethod = fileItem.getClass().getDeclaredMethod("getStoreLocation");
File file = (File) getStoreLocationMethod.invoke(fileItem);
if (file != null) {
// move the file to a temporary blob we own
blob = Blobs.createBlobWithExtension(null);
Files.move(file.toPath(), blob.getFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
}
} catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) {
// unknown Part implementation
}
if (blob == null) {
// if we couldn't get to the file, use the InputStream
blob = Blobs.createBlob(part.getInputStream());
}
blob.setMimeType(part.getContentType());
blob.setFilename(retrieveFilename(part));
return blob;
}
/**
* Helper method to retrieve filename from part, waiting for servlet-api related improvements.
* <p>
* Filename is cleaned before being returned.
*
* @see #getCleanFileName(String)
* @since 7.1
*/
public static String retrieveFilename(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String filename = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
return getCleanFileName(filename);
}
}
return null;
}
/**
* Returns a clean filename, stripping upload path on client side.
* For instance, it turns "/tmp/2349876398/foo.pdf" into "foo.pdf"
* <p>
* Fixes NXP-544
*
* @param filename the filename
* @return the stripped filename
*/
public static String getCleanFileName(String filename) {
String res = null;
int lastWinSeparator = filename.lastIndexOf('\\');
int lastUnixSeparator = filename.lastIndexOf('/');
int lastSeparator = Math.max(lastWinSeparator, lastUnixSeparator);
if (lastSeparator != -1) {
res = filename.substring(lastSeparator + 1, filename.length());
} else {
res = filename;
}
return res;
}
public static void configureFileBlob(Blob blob) {
// mimetype detection
MimetypeRegistry mimeService = Framework.getService(MimetypeRegistry.class);
String detectedMimeType;
try {
detectedMimeType = mimeService.getMimetypeFromFilenameAndBlobWithDefault(blob.getFilename(), blob, null);
} catch (MimetypeDetectionException e) {
log.error("could not fetch mimetype for file " + blob.getFilename(), e);
return;
}
if (detectedMimeType == null) {
if (blob.getMimeType() != null) {
return;
}
// default
detectedMimeType = "application/octet-stream";
}
blob.setMimeType(detectedMimeType);
return;
}
}