Java tutorial
/* * Copyright (C) 2000 - 2018 Silverpeas * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * As a special exception to the terms and conditions of version 3.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * Open Source Software ("FLOSS") applications as described in Silverpeas's * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * "https://www.silverpeas.org/legal/floss_exception.html" * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.silverpeas.core.util.file; import org.apache.commons.exec.util.StringUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.silverpeas.core.SilverpeasExceptionMessages; import org.silverpeas.core.exception.RelativeFileAccessException; import org.silverpeas.core.io.media.MetadataExtractor; import org.silverpeas.core.mail.extractor.Mail; import org.silverpeas.core.util.ImageUtil; import org.silverpeas.core.util.MimeTypes; import org.silverpeas.core.util.OsEnum; import org.silverpeas.core.util.ResourceLocator; import org.silverpeas.core.util.SettingBundle; import org.silverpeas.core.util.StringUtil; import org.silverpeas.core.util.logging.SilverLogger; import javax.activation.MimetypesFileTypeMap; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.MissingResourceException; import java.util.StringTokenizer; import java.util.stream.Stream; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.silverpeas.core.cache.service.CacheServiceProvider.getRequestCacheService; /** * Util class to perform file system operations. * All file operations wil be removed in the future in profit of the new Files class in the JDK. */ public class FileUtil implements MimeTypes { private static final SettingBundle MIME_TYPES_EXTENSIONS = ResourceLocator .getSettingBundle("org.silverpeas.util.attachment.mime_types"); public static final String CONTEXT_TOKEN = ","; public static final String BASE_CONTEXT = "Attachment"; private static final MimetypesFileTypeMap MIME_TYPES = new MimetypesFileTypeMap(); private static final String MIME_TYPE_CACHE_KEY_PREFIX = "FileUtil.getMimeType$$"; private FileUtil() { } /** * Detects the mime-type of the specified file. * * The mime-type is first extracted from its content. If the detection fails or if the file cannot * be located by its specified name, then the mime-type is detected from the file extension. * * @param fileName the name of the file with its path. * @return the mime-type of the specified file. */ public static String getMimeType(final String fileName) { // Request caching in order to increase significantly the performance about file parsing String cacheKey = MIME_TYPE_CACHE_KEY_PREFIX + fileName; String cachedMimeType = getRequestCacheService().getCache().get(cacheKey, String.class); if (StringUtil.isDefined(cachedMimeType)) { return cachedMimeType; } String mimeType = null; final String fileExtension = FileRepositoryManager.getFileExtension(fileName).toLowerCase(); File file = new File(fileName); if (file.exists()) { try { mimeType = MetadataExtractor.get().detectMimeType(file); } catch (Exception ex) { SilverLogger.getLogger(FileUtil.class).warn( "File exists ({0}), but mime-type has been detected: {1}", file.getName(), ex.getMessage(), ex); } } if (!StringUtil.isDefined(mimeType) && MIME_TYPES_EXTENSIONS != null && !fileExtension.isEmpty()) { try { mimeType = MIME_TYPES_EXTENSIONS.getString(fileExtension); } catch (final MissingResourceException e) { SilverLogger.getLogger(FileUtil.class).warn("Unknown mime-type: {0}", e.getMessage(), e); } } if (!StringUtil.isDefined(mimeType)) { mimeType = MIME_TYPES.getContentType(fileName); } // if the mime type is application/xhml+xml or text/html whereas the file is a JSP or PHP script if (XHTML_MIME_TYPE.equalsIgnoreCase(mimeType) || HTML_MIME_TYPE.equalsIgnoreCase(mimeType)) { if (fileExtension.contains(JSP_EXTENSION)) { mimeType = JSP_MIME_TYPE; } else if (fileExtension.contains(PHP_EXTENSION)) { mimeType = PHP_MIME_TYPE; } // if the mime type refers a ZIP archive, checks if it is an archive of the java platform } else if (ARCHIVE_MIME_TYPE.equalsIgnoreCase(mimeType) || SHORT_ARCHIVE_MIME_TYPE.equalsIgnoreCase(mimeType)) { if (JAR_EXTENSION.equalsIgnoreCase(fileExtension) || WAR_EXTENSION.equalsIgnoreCase(fileExtension) || EAR_EXTENSION.equalsIgnoreCase(fileExtension)) { mimeType = JAVA_ARCHIVE_MIME_TYPE; } else if ("3D".equalsIgnoreCase(fileExtension)) { mimeType = SPINFIRE_MIME_TYPE; } } if (mimeType == null) { mimeType = DEFAULT_MIME_TYPE; } // The computed mime type is put into the request cache (performance) getRequestCacheService().getCache().put(cacheKey, mimeType); return mimeType; } /** * Create the array of strings this array represents the repertories where the files must be * stored. */ public static String[] getAttachmentContext(final String context) { if (!StringUtil.isDefined(context)) { return new String[] { BASE_CONTEXT }; } final StringTokenizer strToken = new StringTokenizer(context, CONTEXT_TOKEN); final List<String> folders = new ArrayList<>(10); folders.add(BASE_CONTEXT); while (strToken.hasMoreElements()) { folders.add(strToken.nextToken().trim()); } return folders.toArray(new String[folders.size()]); } /** * Read the content of a file as text (the text is supposed to be in the UTF-8 charset). * Instead of using this method, prefer to use the following Java > 7 statement:<br/> * <pre>{@code new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());}</pre> * * @param file the file to read. * @return the file content as a String. * @throws IOException if an error occurs while reading the file. */ public static String readFileToString(final File file) throws IOException { return new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); } /** * Indicates if the OS is from the Microsoft Windows familly * * @return true if the OS is from the Microsoft Windows familly - false otherwise. */ public static boolean isWindows() { return OsEnum.getOS().isWindows(); } /** * If 3D document. * * @param filename the name of the file. * @return true or false */ public static boolean isSpinfireDocument(String filename) { return SPINFIRE_MIME_TYPE.equals(getMimeType(filename)); } /** * Indicates if the current file is of type archive. * * @param filename the name of the file. * @return true is the file s of type archive - false otherwise. */ public static boolean isArchive(final String filename) { return ARCHIVE_MIME_TYPES.contains(getMimeType(filename)); } /** * Indicates if the current file is of type image. * * @param filename the name of the file. * @return true is the file is of type image - false otherwise. */ public static boolean isImage(final String filename) { String mimeType = getMimeType(filename); if (DEFAULT_MIME_TYPE.equals(mimeType)) { return FilenameUtils.isExtension(filename.toLowerCase(), ImageUtil.IMAGE_EXTENTIONS); } else { return mimeType.startsWith("image"); } } /** * Indicates if the current file is of type mail. * * @param filename the name of the file. * @return true is the file is of type mail - false otherwise. */ public static boolean isMail(final String filename) { return FilenameUtils.isExtension(filename, Mail.MAIL_EXTENTIONS); } /** * Indicates if the current file is of type PDF. * * @param filename the name of the file. * @return true is the file s of type archive - false otherwise. */ public static boolean isPdf(final String filename) { final String mimeType = getMimeType(filename); return PDF_MIME_TYPE.equals(mimeType); } public static boolean isOpenOfficeCompatible(final String filename) { final String mimeType = getMimeType(filename); return OPEN_OFFICE_MIME_TYPES.contains(mimeType) || isMsOfficeExtension(mimeType); } static boolean isMsOfficeExtension(final String mimeType) { return mimeType.startsWith(WORD_2007_EXTENSION) || mimeType.startsWith(EXCEL_2007_EXTENSION) || mimeType.startsWith(POWERPOINT_2007_EXTENSION); } /** * Asserts that the path doesn't contain relative navigation between pathes. * * @param path the path to check * @throws RelativeFileAccessException when a relative path is detected. */ public static void assertPathNotRelative(String path) throws RelativeFileAccessException { String unixPath = FilenameUtils.separatorsToUnix(path); if (unixPath != null && (unixPath.contains("../") || unixPath.contains("/.."))) { throw new RelativeFileAccessException( SilverpeasExceptionMessages.failureOnGetting("path with relative parts", path)); } } /** * Forces the deletion of the specified file. If the write property of the file to delete isn't * set, this property is then set before deleting. * * @param fileToDelete file to delete. * @throws IOException if the deletion failed or if the file doesn't exist. */ public static void forceDeletion(File fileToDelete) throws IOException { if (fileToDelete.exists() && !fileToDelete.canWrite()) { fileToDelete.setWritable(true); } try (Stream<Path> paths = Files.walk(fileToDelete.toPath())) { paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } } /** * Moves the specified source file to the specified destination. If the destination exists, it is * then replaced by the source; if the destination is a directory, then it is deleted with all of * its contain. * * @param source the file to move. * @param destination the destination file of the move. * @throws IOException if the source or the destination is invalid or if an error occurs while * moving the file. */ public static void moveFile(File source, File destination) throws IOException { if (destination.exists()) { forceDeletion(destination); } Files.move(source.toPath(), destination.toPath()); } /** * Copies the specified source file to the specified destination. If the destination exists, it is * then replaced by the source. If the destination can be overwritten, its write property is set * before the copy. * * @param source the file to copy. * @param destination the destination file of the move. * @throws IOException if the source or the destination is invalid or if an error occurs while * copying the file. */ public static void copyFile(File source, File destination) throws IOException { if (destination.exists() && !destination.canWrite()) { destination.setWritable(true); } Files.copy(source.toPath(), destination.toPath(), REPLACE_EXISTING); } /* * Remove any \ or / from the filename thus avoiding conflicts on the server. * * @param fileName * @return */ public static String getFilename(String fileName) { if (!StringUtil.isDefined(fileName)) { return ""; } return FilenameUtils.getName(fileName); } /** * Convert a path to the current OS path format. * * @param undeterminedOsPath a path * @return server OS pah. */ public static String convertPathToServerOS(String undeterminedOsPath) { if (undeterminedOsPath == null || !StringUtil.isDefined(undeterminedOsPath)) { return ""; } String localPath = undeterminedOsPath; localPath = localPath.replace('\\', File.separatorChar).replace('/', File.separatorChar); return localPath; } public static String convertFilePath(File file) { if (OsEnum.getOS().isWindows()) { return StringUtils.quoteArgument(file.getAbsolutePath()); } String path = file.getAbsolutePath(); path = path.replaceAll("\\\\", "\\\\\\\\"); path = path.replaceAll("\\s", "\\\\ "); path = path.replaceAll("<", "\\\\<"); path = path.replaceAll(">", "\\\\>"); path = path.replaceAll("'", "\\\\'"); path = path.replaceAll("\"", "\\\\\""); path = path.replaceAll("\\{", "\\\\{"); path = path.replaceAll("}", "\\\\}"); path = path.replaceAll("\\(", "\\\\("); path = path.replaceAll("\\)", "\\\\)"); path = path.replaceAll("\\[", "\\\\["); path = path.replaceAll("\\]", "\\\\]"); path = path.replaceAll("\\&", "\\\\&"); path = path.replaceAll("\\|", "\\\\|"); return path; } public static boolean deleteEmptyDir(File directory) { if (directory.exists() && directory.isDirectory() && directory.list() != null && directory.list().length == 0) { try { Files.delete(directory.toPath()); return true; } catch (IOException e) { SilverLogger.getLogger(FileUtil.class).warn(e); return false; } } return false; } /** * Moves all files from sub folders to the given root folder and deletes after all the sub * folders. * @param rootFolder the root folder from which the sub folders are retrieved and into which the * files will be moved if any. * @return an array of {@link File} that represents the found sub folders. The returned array is * never null. * @throws IOException */ public static File[] moveAllFilesAtRootFolder(File rootFolder) throws IOException { return moveAllFilesAtRootFolder(rootFolder, true); } /** * Moves all files from sub folders to the given root folder. * @param rootFolder the root folder from which the sub folders are retrieved and into which the * files will be moved if any. * @param deleteFolders true if the sub folders must be deleted. * @return an array of {@link File} that represents the found sub folders. The returned array is * never null. * @throws IOException */ public static File[] moveAllFilesAtRootFolder(File rootFolder, boolean deleteFolders) throws IOException { File[] foldersAtRoot = rootFolder != null ? rootFolder.listFiles((FileFilter) FileFilterUtils.directoryFileFilter()) : null; if (foldersAtRoot != null) { for (File folderAtRoot : foldersAtRoot) { for (File file : FileUtils.listFiles(folderAtRoot, FileFilterUtils.fileFileFilter(), FileFilterUtils.trueFileFilter())) { File newFilePath = new File(rootFolder, file.getName()); if (!newFilePath.exists()) { FileUtils.moveFile(file, newFilePath); } } if (deleteFolders) { FileUtils.deleteQuietly(folderAtRoot); } } } return foldersAtRoot != null ? foldersAtRoot : new File[0]; } /** * Validate that fileName given in parameter is inside extraction target directory (intendedDir) * @param fileName the file name to extract * @param intendedDir the extraction target directory * @return the filename if fileName is inside extraction target directory * @throws java.io.IOException if fileName is outside extraction target directory */ public static String validateFilename(String fileName, String intendedDir) throws java.io.IOException { File f = new File(fileName); String canonicalPath = f.getCanonicalPath(); File iD = new File(intendedDir); String canonicalID = iD.getCanonicalPath(); if (canonicalPath.startsWith(canonicalID)) { return canonicalPath; } else { throw new IllegalStateException("File is outside extraction target directory (security)"); } } }