All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.chandre.admintool.filebrowser.AdminToolFilebrowserServiceImpl Maven / Gradle / Ivy

The newest version!
package de.chandre.admintool.filebrowser;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import de.chandre.admintool.core.utils.RegexUtil;

/**
 * 
 * @author Andre
 * @since 1.0.1
 */
@Service("adminToolFilebrowserService")
public class AdminToolFilebrowserServiceImpl extends AbstractFileBrowserService implements AdminToolFilebrowserService {

	private static final Log LOGGER = LogFactory.getLog(AdminToolFilebrowserServiceImpl.class);
	
	private static final Map FILE_SIZE_EXP = new TreeMap<>();
	static {
		FILE_SIZE_EXP.put(Integer.valueOf(1), " B");
		FILE_SIZE_EXP.put(Integer.valueOf(2), " KB");
		FILE_SIZE_EXP.put(Integer.valueOf(3), " MB");
		FILE_SIZE_EXP.put(Integer.valueOf(4), " GB");
		FILE_SIZE_EXP.put(Integer.valueOf(5), " TB");
		FILE_SIZE_EXP.put(Integer.valueOf(6), " PB");
		FILE_SIZE_EXP.put(Integer.valueOf(7), " EB");
	}
	
	@Autowired
	private AdminToolFilebrowserConfig config;
	
	@Autowired
	private Environment env;
	
	private List rootDirsCache = new ArrayList<>();
	
	
	@Override
	public Collection getRootDirs() {
		File[] roots = File.listRoots();
		
		if (this.rootDirsCache.isEmpty()) {
			synchronized (this.rootDirsCache) {
				for (File file : roots) {
					if(!config.getForbiddenDrives().contains(file.getAbsolutePath().toLowerCase())) {
						// if not forbidden add it to result
						this.rootDirsCache.add(file.getAbsolutePath());
					}
				}
				Collections.sort(this.rootDirsCache);
			}
		}
		return this.rootDirsCache;
	}
	
	@Override
	public boolean isRootActive(String rootDir, String currentDir) {
		if (!StringUtils.isEmpty(currentDir) && currentDir.toLowerCase().startsWith(rootDir.toLowerCase())) {
			return true;
		}
		return false;
	}
	
	@Override
	public String getParent(String dir) throws IOException {
		File file = new File(dir);
		if (null != file.getParent() && isAllowed(file.getParentFile(), false)) {
			return file.getParent();
		}
		return "";
	}
	
	protected List sort(File[] fileAr, SortColumn sortCol, Boolean sortAsc) {
		List files = Arrays.asList(fileAr);
		final SortColumn sortColToUse;
		if (null == sortCol) {
			sortColToUse = SortColumn.NAME;
		} else {
			sortColToUse = sortCol;
		}
		if (null == sortAsc) {
			sortAsc = Boolean.TRUE;
		}
		final int direction = sortAsc.booleanValue() ? 1 : -1;
		
		Collections.sort(files, new Comparator() {
			@Override
			public int compare(File o1, File o2) {
				try {
					switch (sortColToUse) {
						case DATE:
							return getLastChange(o1).compareTo(getLastChange(o2)) * direction;
						case SIZE: 
							return Long.valueOf(o1.length()).compareTo(Long.valueOf(o2.length())) * direction;
						case TYPE: 
							return getFileType(o1).compareTo(getFileType(o2)) * direction;
						case NAME:
						default:
							return o1.getName().compareTo(o2.getName()) * direction;
					}
				} catch (Exception ignore) {
				}
				
				return 0;
			}
		});
		return files;
	}
	
	@Override
	public String getSortDirection(int current, SortColumn sortCol, Boolean sortAsc) {
		if(null == sortCol) {
			return "up";
		}
		if (current == sortCol.getIndex() && sortAsc != null) {
			return sortAsc ? "up" : "down";
		}
		return "";
	}
	
	@Override
	public List getDirectories(String currentDir, SortColumn sortCol, Boolean sortAsc, String filter) throws IOException {
		File file = new File(currentDir);
		if (null != file && isAllowed(file, false)) {
			final Pattern fileNamePattern = getFileNamePattern(filter);
			File[] files = file.listFiles(new FileFilter() {
				@Override
				public boolean accept(File dir) {
					try {
						if (isAllowed(dir, false) && dir.isDirectory()) {
							if (null == fileNamePattern) {
								return true;
							} else if (null != fileNamePattern && fileNamePattern.matcher(dir.getName()).matches()) {
								return true;
							}
						}
					} catch (IOException e) {
						LOGGER.debug(e.getMessage(), e);
					}
					return false;
				}
			});
			if (files != null) {
				return sort(files, sortCol, sortAsc);
			}
		}
		return Collections.emptyList();
	}
	
	@Override
	public List getFiles(String currentDir, SortColumn sortCol, Boolean sortAsc, String filter) throws IOException {
		File file = new File(currentDir);
		if (null != file && isAllowed(file, false)) {
			final Pattern fileNamePattern = getFileNamePattern(filter);
			File[] files = file.listFiles(new FileFilter() {
				@Override
				public boolean accept(File dir) {
					try {
						if (isAllowed(dir, false) && dir.isFile()) {
							if (null == fileNamePattern) {
								return true;
							} else if (null != fileNamePattern && fileNamePattern.matcher(dir.getName()).matches()) {
								return true;
							}
						}
					} catch (IOException e) {
						LOGGER.debug(e.getMessage(), e);
					}
					return false;
				}
			});
			if (files != null) {
				return sort(files, sortCol, sortAsc);
			}
		}
		return Collections.emptyList();
	}
	
	private Pattern getFileNamePattern(String filter) {
		if (!StringUtils.isEmpty(filter)) {
			return Pattern.compile(RegexUtil.wildcardToRegex(filter), Pattern.CASE_INSENSITIVE);
		}
		return null;
	}
	
	@Override
	public String getDirOrRootName(File currentDir) {
		if (StringUtils.isEmpty(currentDir.getName())) {
			return currentDir.getAbsolutePath();
		}
		return currentDir.getName();
	}
	
	@Override
	public List getBreadcrumb(String currentDir) {
		if (null == currentDir) {
			return Collections.emptyList();
		}
		List result = new ArrayList<>();
		File file = new File(currentDir);
		getParentsRecursive(file, result);
		return result;
	}
	
	private void getParentsRecursive(File actual, List files) {
		if (null != actual.getParentFile()) {
			getParentsRecursive(actual.getParentFile(), files);
		} 
		if (actual.isDirectory()) {
			files.add(actual);
		}
	}
	
	
	
	@Override
	public String getFileSizeSum(String dir, String filter) throws IOException {
		List files = getFiles(dir, null, true, filter);
		Long res = files.stream().collect(Collectors.summingLong(File::length));
		return String.format("%s in %s files", getFileSize(res), files.size());
	}
	
	@Override
	public Date getLastChange(File file) throws IOException {
		if (null != file) {
			FileTime time = Files.getLastModifiedTime(file.toPath(), new LinkOption[]{});
			return new Date(time.toMillis());
		}
		return new Date();
	}
	
	@Override
	public String getFileType(File file) {
		if (null == file) {
			return "";
		}
		if (file.isDirectory()) {
			return "DIR";
		}
		if (file.getName().lastIndexOf('.') == -1) {
			return "";
		}
		return getExtension(file);
	}
	
	@Override
	public String getFileSize(File file) {
		return getFileSize(file.length());
	}
	
	/**
	 * returns the the size in B, KB, MB or GB depending on the length
	 * 
	 * @param fileLength
	 * @return
	 */
	@Override
	public String getFileSize(long fileLength) {
		return calculateDisplayFileSize(fileLength, config.getSizeDivisorMultiplicator());
	}
	
	@Override
	public String getNormalFileSize(long fileLength) {
		return calculateDisplayFileSize(fileLength, FileUtils.ONE_KB);
	}
	
	protected String calculateDisplayFileSize(long fileLength, long sizeDivisorMultiplicator) {
		BigInteger multiplicator = BigInteger.valueOf(sizeDivisorMultiplicator);
		BigInteger size = BigInteger.valueOf(fileLength);
		
		for (Entry entry : FILE_SIZE_EXP.entrySet()) {
			BigInteger divisor = multiplicator.pow(entry.getKey().intValue());
			if (fileLength < divisor.longValue()) {
				if (entry.getKey().intValue() == 1) {
					return  String.valueOf(size) + entry.getValue();
				}
				return formatFileSize(size, multiplicator.pow(entry.getKey().intValue() - 1), entry.getValue());
			}
		}
		//should not happen
		return FileUtils.byteCountToDisplaySize(fileLength);
	}
	
	/**
	 * calculates the and formats files size
	 * @see #getFileSize(long)
	 * @param fileLength
	 * @param divisor 
	 * @param unit the Unit for the divisor
	 * @return
	 */
	protected String formatFileSize(BigInteger fileLength, BigInteger divisor, String unit) {
		BigDecimal size = new BigDecimal(fileLength);
		size = size.setScale(config.getFileSizeDisplayScale()).divide(new BigDecimal(divisor), BigDecimal.ROUND_HALF_EVEN);
		return String.format("%s %s", size.doubleValue(), unit);
	}
	
	/**
	 * 
	 * @param fileName the name
	 * @param size (optional) size/length of content
	 * @param response the servlet response
	 */
	protected void prepareDownloadResponse(String fileName, Long size, HttpServletResponse response, boolean asAttachment) {
		
		String mimeType = MimeTypes.getMimeType(getExtension(fileName));
		LOGGER.info("following mimeType:" + mimeType);
		if (asAttachment || null == mimeType) {
			response.setContentType("application/octet-stream");
			response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");
		} else {
			response.setContentType(mimeType);
			response.setHeader("Content-Disposition", "inline;filename=\"" + fileName + "\"");
		}
		if (null != size) {
			response.setContentLength(size.intValue());
		}
	}
	
	@Override
	public void downloadFile(String filePath, HttpServletResponse response, boolean asAttachment) throws DownloadNotAllowedException, GenericFilebrowserException {
		downloadFile(filePath, response, null, asAttachment);
	}
	
	@Override
	public void downloadFile(String filePath, HttpServletResponse response, String alternativeFileName, boolean asAttachment) throws DownloadNotAllowedException, GenericFilebrowserException {
		File file = new File(filePath);
		try {
			if (!isAllowed(file, false)) {
				throw new DownloadNotAllowedException();
			}
		} catch (IOException e) {
			throw new GenericFilebrowserException(e);
		}
		
		prepareDownloadResponse(StringUtils.isEmpty(alternativeFileName) ? file.getName() : alternativeFileName, 
				Long.valueOf(file.length()), response, asAttachment);
		
		InputStream in = null;
		BufferedInputStream fileInput = null;
		try {
			in = new FileInputStream(file);
			fileInput = new BufferedInputStream(in);
			ServletOutputStream out = response.getOutputStream();
			IOUtils.copy(fileInput, out, 8192);
			out.flush();
		} catch (IOException e) {
			throw new GenericFilebrowserException("could not prepare file for downloading", e);
		}
		finally {
			IOUtils.closeQuietly(fileInput);
			IOUtils.closeQuietly(in);
		}
	}
	
	@Override
	public void downloadFilesAsZip(List filePaths, HttpServletResponse response) throws GenericFilebrowserException {
		
		File tempFile = null;
		try {
			OutputStream out = null;
			
			try {
				if (config.isZipUseTempFile()) {
					String tempDirConf = config.getZipTempDir();
					File tempDir = null;
					if (StringUtils.isEmpty(tempDirConf)) {
						tempFile = File.createTempFile("zip", null);
					}
					else if (tempDirConf.startsWith("sys")) {
						tempDir = new File(System.getProperty(tempDirConf.substring(4, tempDirConf.length())));
					}
					else if (tempDirConf.startsWith("env")) {
						tempDir = new File(env.getProperty(tempDirConf.substring(4, tempDirConf.length())));
					}
					else {
						tempDir = new File(tempDirConf);
					}
					if (null == tempFile) {
						tempFile = File.createTempFile("zip", null, tempDir);
					}
					out = new FileOutputStream(tempFile);
				}
			} catch (Exception e) {
				LOGGER.warn("could not create temporary file, using servlet outputstream for serving ZIP");
			}
			
			if (null == out) {
				tempFile = null;
				prepareDownloadResponse("rename_me.zip", null, response, true);
				out = response.getOutputStream();
			}
			
			ZipOutputStream zos = new ZipOutputStream(out);
			
			zos.setLevel(config.getZipCompessionLevel());
			try {
				for (String filePathStr : filePaths) {
					File orgfile = new File(filePathStr);
					Files.walkFileTree(orgfile.toPath(), new SimpleFileVisitor() {
						public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
							if (!isAllowed(file.toFile(), false)) {
								LOGGER.debug("ZIP creation: skipping not allowed file: " + file.toAbsolutePath());
								return FileVisitResult.CONTINUE;
							}
							String entry = orgfile.getParentFile().toPath().relativize(file).toString();
							LOGGER.trace("creating entry: " + entry);
							zos.putNextEntry(new ZipEntry(entry));
							Files.copy(file, zos);
							zos.closeEntry();
							return FileVisitResult.CONTINUE;
						}

						public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
							if (!isAllowed(dir.toFile(), false)) {
								LOGGER.debug("ZIP creation: skipping not allowed directory and subtree for: " + dir.toAbsolutePath());
								return FileVisitResult.SKIP_SUBTREE;
							}
							String entry = orgfile.getParentFile().toPath().relativize(dir).toString() + "/";
							LOGGER.trace("creating dir: " + entry);
							zos.putNextEntry(new ZipEntry(entry));
							zos.closeEntry();
							return FileVisitResult.CONTINUE;
						}
					});
				}
				
				zos.finish();
				
			} catch (IOException e) {
				IOUtils.closeQuietly(zos);
				throw new GenericFilebrowserException("Could not create zip file is allowed ", e);
			}
			// flushing the output
			out.flush();
			
			if (null != tempFile && config.isZipUseTempFile()) {
				IOUtils.closeQuietly(out);
				downloadFile(tempFile.getAbsolutePath(), response, "rename_me.zip", true);
			}
			
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
			throw new GenericFilebrowserException(e);
		} finally {
			if (null != tempFile && config.isZipUseTempFile()) {
				try {
					if(!tempFile.delete()) {
						tempFile.deleteOnExit();
					}
				} catch (Exception e2) {
					LOGGER.warn("could not delete tempfile " + tempFile.getAbsolutePath());
					tempFile.deleteOnExit();
				}
			}
		}
	}
	
	/**
	 * checks if file is allowed for access
	 * 
	 * @param path
	 * @param write
	 * @return
	 * @throws IOException
	 */
	protected boolean isAllowed(File path, boolean write) throws IOException {
		return isAllowed(path, write, config.isReadOnly());
	}
	
	@Override
	public String accessibleCSS(File file) {
		String res = "";
		if (!file.canRead()) {
			res += "not-readable";
		}
		if (!file.canWrite()) {
			res += " not-writeable";
		}
		if (file.isHidden()) {
			res += " file-hidden";
		}
		return res.trim();
	}
	
	@Override
	public String createFolder(String path, String folderName) throws IOException, GenericFilebrowserException {
		if (StringUtils.isEmpty(path) && StringUtils.isEmpty(folderName)) {
			throw new GenericFilebrowserException("Nothing to create.");
		}
		File file = new File(path);
		if (file.exists()) {
			if (isAllowed(file, true)) {
				file = new File(file, folderName);
				if(file.mkdirs()) {
					return file.getAbsolutePath();
				}
				throw new GenericFilebrowserException("Could not create directories.");
			}
		} else {
			LOGGER.warn("[createFolder] folder already exists: " + path);
		}
		return null;
	}
	
	@Override
	public String deleteResource(String path) throws IOException, GenericFilebrowserException {
		if (StringUtils.isEmpty(path)) {
			throw new GenericFilebrowserException("Nothing to delete.");
		}
		File file = new File(path);
		
		if (file.isDirectory() && !config.isDelteFolderAllowed()) {
			throw new GenericFilebrowserException("Delete folders functionality is deactivated.");
		}
		if (file.isFile() && !config.isDelteFileAllowed()) {
			throw new GenericFilebrowserException("Delete files functionality is deactivated.");
		}
		if (!isAllowed(file, true)) {
			throw new GenericFilebrowserException("Delete "+(file.isDirectory() ?  "folder" : "file")+" is not allowed.");
		}
		if (!file.canWrite() && config.isNotDeletableIfNotWriteable()) {
			throw new GenericFilebrowserException(
					"Deleting " +(file.isDirectory() ?  "folder" : "file") + " '" + file.getName() + "' is not allowed.");
		}
		
		String parent = file.getParent();
		
		if (file.isFile()) {
			FileUtils.deleteQuietly(file);
		} else {
			try {
				FileUtils.deleteDirectory(file);
			} catch (Exception e) {
				LOGGER.error("error deleting folder", e);
			}
		}
		return parent;
	}
	
	@Override
	public Map getFileInfo(String path) throws IOException {
		File file = new File(path);
		Map result = new TreeMap<>();
		result.put("file", file);
		
		if (file.isFile() && file.canRead() && config.getMaxFilesizeForHashes() > file.length()) {
			if (config.isInfoCrc32()) {
				result.put("file.checksumCRC32", FileUtils.checksumCRC32(file));
			}
			
			FileInputStream fis = new FileInputStream(file);
			if (config.isInfoMD5()) {
				result.put("file.md5Hex", DigestUtils.md5Hex(fis));
			}
			if (config.isInfoSha1()) {
				result.put("file.sha1Hex", DigestUtils.sha1Hex(fis));
			}
			if (config.isInfoSha256()) {
				result.put("file.sha256Hex", DigestUtils.sha256Hex(fis));
			}
//			result.put("file.sha384Hex", DigestUtils.sha384Hex(fis));
//			result.put("file.sha512Hex", DigestUtils.sha512Hex(fis));
			
			IOUtils.closeQuietly(fis);
		}
		
		result.put("file.lastModified", file.lastModified());
		result.put("file.canWrite", file.canWrite());
		result.put("file.canRead", file.canRead());
		result.put("file.canExecute", file.canExecute());
		result.put("file.isHidden", file.isHidden());
		
		result.put("disk.totalSpace", file.getTotalSpace());
		result.put("disk.usableSpace", file.getUsableSpace());
		result.put("disk.freeSpace", file.getFreeSpace());
		result.put("disk.totalSpace.coreFormat", getFileSize(file.getTotalSpace()));
		result.put("disk.usableSpace.coreFormat", getFileSize(file.getUsableSpace()));
		result.put("disk.freeSpace.coreFormat", getFileSize(file.getFreeSpace()));
		result.put("disk.totalSpace.commonFormat", getNormalFileSize(file.getTotalSpace()));
		result.put("disk.usableSpace.commonFormat", getNormalFileSize(file.getUsableSpace()));
		result.put("disk.freeSpace.commonFormat", getNormalFileSize(file.getFreeSpace()));
		
		String os = System.getProperty("os.name").toLowerCase();
		result.put("system.operationSystem", os);
		
		Class attributesClass = 
				isWindows(os) ? DosFileAttributes.class : BasicFileAttributes.class;
		BasicFileAttributes attr = Files.readAttributes(file.toPath(), attributesClass);
		
		result.put("file.attr.creationTime", attr.creationTime());
		result.put("file.attr.lastAccessTime", attr.lastAccessTime());
		result.put("file.attr.lastModifiedTime", attr.lastModifiedTime());

		result.put("file.attr.isDirectory", attr.isDirectory());
		result.put("file.attr.isOther", attr.isOther());
		result.put("file.attr.isRegularFile", attr.isRegularFile());
		result.put("file.attr.isSymbolicLink", attr.isSymbolicLink());
		result.put("file.attr.size", attr.size());
		
		
		if (DosFileAttributes.class.isAssignableFrom(attr.getClass())) {
			result.put("file.attr.isArchive", ((DosFileAttributes)attr).isArchive());
			result.put("file.attr.isHidden", ((DosFileAttributes)attr).isHidden());
			result.put("file.attr.isReadOnly", ((DosFileAttributes)attr).isReadOnly());
			result.put("file.attr.isSystem", ((DosFileAttributes)attr).isSystem());
		}
		
		long size = file.length();
		if (file.isDirectory() && !attr.isSymbolicLink() && config.isCountFolderSize()) {
			size = FileUtils.sizeOfDirectory(file);
		}
		result.put("file.size", size);
		result.put("file.size.coreFormat", getFileSize(size));
		result.put("file.size.commonFormat", getNormalFileSize(size));
		
		if (!isWindows(os)) {
			Set permissions = Files.getPosixFilePermissions(file.toPath(), LinkOption.NOFOLLOW_LINKS);
			result.put("file.permissions", PosixFilePermissions.toString(permissions));
		}
		
		return result;
	}
	
	public static boolean isWindows(String os) {
        return (os.indexOf("win") >= 0);
    }
	
	@Override
	public boolean saveFile(String decodedPath, MultipartFile upload) throws IOException, GenericFilebrowserException {
		if (StringUtils.isEmpty(decodedPath)) {
			return false;
		}
		File uploadFolder = new File(decodedPath);
		if (uploadFolder.isDirectory() && isAllowed(uploadFolder, true)) {
			
			OutputStream fos = null;
			try {
				File file = new File(uploadFolder, upload.getOriginalFilename());
				fos = new FileOutputStream(file);
				long result = IOUtils.copyLarge(upload.getInputStream(), fos);
				LOGGER.info(String.format("uploaded %s bytes (%s)", result, getNormalFileSize(result)));
			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
				return false;
			} finally {
				IOUtils.closeQuietly(fos);
			}
			return true;
		} 
		throw new GenericFilebrowserException("upload not allowed");
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy