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

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

There is a newer version: 1.1.7.3
Show 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.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.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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.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 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);
	
	@Autowired
	private AdminToolFilebrowserConfig config;
	
	@Autowired
	private Environment env;
	
	private Set rootDirsCache = Collections.newSetFromMap(new ConcurrentHashMap<>());
	
	@Override
	public Set getRootDirs() {
		File[] roots = File.listRoots();
		
		if (this.rootDirsCache.isEmpty()) {
			for (File file : roots) {
				if(!config.getForbiddenDrives().contains(file.getAbsolutePath().toLowerCase())) {
					// if not forbidden add it to result
					this.rootDirsCache.add(file.getAbsolutePath());
				}
			}
		}
		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, final SortColumn sortCol, Boolean sortAsc) {
		List files = Arrays.asList(fileAr);
		if (null == sortCol) {
			return files;
		}
		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 (sortCol) {
						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 (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
	 */
	protected String getFileSize(long fileLength) {
		long multiplicator = config.getSizeDivisorMultiplicator();
		if (fileLength < 1024L) {
			return String.valueOf(fileLength) + " B";
		}
		if (fileLength < 1024L * multiplicator) {
			return getFileSize(fileLength, 1024, "KB");
		}
		if (fileLength < 1024L * multiplicator * multiplicator) {
			return getFileSize(fileLength, 1024 * multiplicator, "MB");
		}
		return getFileSize(fileLength, 1024 * multiplicator * multiplicator, "GB");
	}
	
	/**
	 * calculates the and formats files size
	 * @see #getFileSize(long)
	 * @param fileLength
	 * @param divisor 
	 * @param unit the Unit for the divisor
	 * @return
	 */
	protected String getFileSize(long fileLength, long divisor, String unit) {
		BigDecimal size = BigDecimal.valueOf(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());
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy