/** * This file is part of Waarp Project. * * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the * COPYRIGHT.txt in the distribution for a full listing of individual contributors. * * All Waarp Project is free software: you can redistribute it and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * Waarp 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 General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Waarp . If not, see * <http://www.gnu.org/licenses/>. */ package org.waarp.common.file.filesystembased; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; import org.waarp.common.command.exception.CommandAbstractException; import org.waarp.common.command.exception.Reply550Exception; import org.waarp.common.command.exception.Reply553Exception; import org.waarp.common.digest.FilesystemBasedDigest; import org.waarp.common.digest.FilesystemBasedDigest.DigestAlgo; import org.waarp.common.file.AbstractDir; import org.waarp.common.file.FileInterface; import org.waarp.common.file.OptsMLSxInterface; import org.waarp.common.file.SessionInterface; import org.waarp.common.file.filesystembased.specific.FilesystemBasedCommonsIo; import org.waarp.common.file.filesystembased.specific.FilesystemBasedDirJdk5; import org.waarp.common.file.filesystembased.specific.FilesystemBasedDirJdk6; import org.waarp.common.file.filesystembased.specific.FilesystemBasedDirJdkAbstract; import org.waarp.common.logging.WaarpLogger; import org.waarp.common.logging.WaarpLoggerFactory; import org.waarp.common.utility.DetectionUtils; /** * Directory implementation for Filesystem Based * * @author Frederic Bregier * */ public abstract class FilesystemBasedDirImpl extends AbstractDir { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory .getLogger(FilesystemBasedDirImpl.class); /** * Class that handles specifity of one Jdk or another */ protected static FilesystemBasedDirJdkAbstract filesystemBasedFtpDirJdk = null; /** * Initialize the filesystem */ { initJdkDependent(); } /** * Init according to internals of JDK * */ private static void initJdkDependent() { if (DetectionUtils.javaVersion() >= 6) { filesystemBasedFtpDirJdk = new FilesystemBasedDirJdk6(); } else { filesystemBasedFtpDirJdk = new FilesystemBasedDirJdk5(); } } /** * Init the dependant object according to internals of JDK * * @param filesystemBasedFtpDirJdkChoice * * @deprecated replaced by initJdkDependent() */ @Deprecated public static void initJdkDependent( FilesystemBasedDirJdkAbstract filesystemBasedFtpDirJdkChoice) { filesystemBasedFtpDirJdk = filesystemBasedFtpDirJdkChoice; } /** * @param session * @param optsMLSx */ public FilesystemBasedDirImpl(SessionInterface session, OptsMLSxInterface optsMLSx) { this.session = session; this.optsMLSx = optsMLSx; this.optsMLSx.setOptsModify((byte) 1); this.optsMLSx.setOptsPerm((byte) 1); this.optsMLSx.setOptsSize((byte) 1); this.optsMLSx.setOptsType((byte) 1); } /** * Finds all files matching a wildcard expression (based on '?', '~' or '*'). * * @param pathWithWildcard * The wildcard expression with a business path. * @return List of String as relative paths matching the wildcard expression. Those files are * tested as valid from business point of view. If Wildcard support is not active, if * the path contains any wildcards, it will throw an error. * @throws CommandAbstractException */ protected List<String> wildcardFiles(String pathWithWildcard) throws CommandAbstractException { List<String> resultPaths = new ArrayList<String>(); // First check if pathWithWildcard contains wildcards if (!(pathWithWildcard.contains("*") || pathWithWildcard.contains("?") || pathWithWildcard .contains("~"))) { // No so simply return the list containing this path after // validating it if (getSession().getAuth().isBusinessPathValid(pathWithWildcard)) { resultPaths.add(pathWithWildcard); } return resultPaths; } // Do we support Wildcard path if (!FilesystemBasedDirJdkAbstract.ueApacheCommonsIo) { throw new Reply553Exception("Wildcards in pathname is not allowed"); } File wildcardFile; File rootFile; if (!ISUNIX && isAbsolute(pathWithWildcard)) { wildcardFile = new File(pathWithWildcard); rootFile = getCorrespondingRoot(wildcardFile); } else { if (isAbsolute(pathWithWildcard)) { rootFile = new File("/"); } else { rootFile = new File(((FilesystemBasedAuthImpl) getSession() .getAuth()).getBaseDirectory()); } wildcardFile = new File(rootFile, pathWithWildcard); } // Split wildcard path into subdirectories. List<String> subdirs = new ArrayList<String>(); while (wildcardFile != null) { File parent = wildcardFile.getParentFile(); if (parent == null) { subdirs.add(0, wildcardFile.getPath()); break; } subdirs.add(0, wildcardFile.getName()); if (parent.equals(rootFile)) { // End of wildcard path subdirs.add(0, parent.getPath()); break; } wildcardFile = parent; } List<File> basedPaths = new ArrayList<File>(); // First set root basedPaths.add(new File(subdirs.get(0))); int i = 1; // For each wilcard subdirectory while (i < subdirs.size()) { // Set current filter FileFilter fileFilter = FilesystemBasedCommonsIo .getWildcardFileFilter(subdirs.get(i)); List<File> newBasedPaths = new ArrayList<File>(); // Look for matches in all the current search paths for (File dir : basedPaths) { if (dir.isDirectory()) { for (File match : dir.listFiles(fileFilter)) { newBasedPaths.add(match); } } } // base Search Path changes now basedPaths = newBasedPaths; i++; } // Valid each file first for (File file : basedPaths) { String relativePath = ((FilesystemBasedAuthImpl) getSession() .getAuth()).getRelativePath(normalizePath(file .getAbsolutePath())); String newpath = this.validatePath(relativePath); resultPaths.add(newpath); } return resultPaths; } /** * Get the FileInterface from this path, checking first its validity * * @param path * @return the FileInterface * @throws CommandAbstractException */ protected File getFileFromPath(String path) throws CommandAbstractException { String newdir = validatePath(path); if (isAbsolute(newdir)) { return new File(newdir); } String truedir = ((FilesystemBasedAuthImpl) getSession().getAuth()) .getAbsolutePath(newdir); return new File(truedir); } /** * Get the true file from the path * * @param path * @return the true File from the path * @throws CommandAbstractException */ protected File getTrueFile(String path) throws CommandAbstractException { checkIdentify(); String newpath = consolidatePath(path); List<String> paths = wildcardFiles(normalizePath(newpath)); if (paths.size() != 1) { throw new Reply550Exception("File not found: " + paths.size() + " founds"); } String extDir = paths.get(0); extDir = this.validatePath(extDir); File file = getFileFromPath(extDir); if (!file.isFile()) { throw new Reply550Exception("Path is not a file: " + path); } return file; } /** * Get the relative path (without mount point) * * @param file * @return the relative path */ protected String getRelativePath(File file) { return ((FilesystemBasedAuthImpl) getSession().getAuth()) .getRelativePath(normalizePath(file.getAbsolutePath())); } public boolean changeDirectory(String path) throws CommandAbstractException { checkIdentify(); String newpath = consolidatePath(path); List<String> paths = wildcardFiles(newpath); if (paths.size() != 1) { logger.warn("CD error: {}", newpath); throw new Reply550Exception("Directory not found: " + paths.size() + " founds"); } String extDir = paths.get(0); extDir = this.validatePath(extDir); if (isDirectory(extDir)) { currentDir = extDir; return true; } throw new Reply550Exception("Directory not found: " + extDir); } public boolean changeDirectoryNotChecked(String path) throws CommandAbstractException { checkIdentify(); String newpath = consolidatePath(path); List<String> paths = wildcardFiles(newpath); if (paths.size() != 1) { logger.warn("CD error: {}", newpath); throw new Reply550Exception("Directory not found: " + paths.size() + " founds"); } String extDir = paths.get(0); extDir = this.validatePath(extDir); currentDir = extDir; return true; } public String mkdir(String directory) throws CommandAbstractException { checkIdentify(); String newdirectory = consolidatePath(directory); File dir = new File(newdirectory); String parent = dir.getParentFile().getPath(); List<String> paths = wildcardFiles(normalizePath(parent)); if (paths.size() != 1) { throw new Reply550Exception("Base Directory not found: " + paths.size() + " founds"); } String newDir = paths.get(0) + SEPARATOR + dir.getName(); newDir = this.validatePath(newDir); File newdir = getFileFromPath(newDir); if (newdir.mkdir()) { return newDir; } throw new Reply550Exception("Cannot create directory " + newDir); } public String rmdir(String directory) throws CommandAbstractException { checkIdentify(); String newdirectory = consolidatePath(directory); List<String> paths = wildcardFiles(normalizePath(newdirectory)); if (paths.size() != 1) { throw new Reply550Exception("Directory not found: " + paths.size() + " founds"); } String extDir = paths.get(0); extDir = this.validatePath(extDir); File dir = getFileFromPath(extDir); if (dir.delete()) { return extDir; } throw new Reply550Exception("Cannot delete directory " + extDir); } public boolean isDirectory(String path) throws CommandAbstractException { checkIdentify(); File dir = getFileFromPath(path); return dir.isDirectory(); } public boolean isFile(String path) throws CommandAbstractException { checkIdentify(); return getFileFromPath(path).isFile(); } public String getModificationTime(String path) throws CommandAbstractException { checkIdentify(); File file = getFileFromPath(path); if (file.exists()) { return getModificationTime(file); } throw new Reply550Exception("\"" + path + "\" does not exist"); } /** * Return the Modification time for the File * * @param file * @return the Modification time as a String YYYYMMDDHHMMSS.sss */ protected String getModificationTime(File file) { long mstime = file.lastModified(); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(mstime); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH) + 1; int day = calendar.get(Calendar.DAY_OF_MONTH); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND); int ms = calendar.get(Calendar.MILLISECOND); StringBuilder sb = new StringBuilder(18); sb.append(year); if (month < 10) { sb.append(0); } sb.append(month); if (day < 10) { sb.append(0); } sb.append(day); if (hour < 10) { sb.append(0); } sb.append(hour); if (minute < 10) { sb.append(0); } sb.append(minute); if (second < 10) { sb.append(0); } sb.append(second).append('.'); if (ms < 10) { sb.append(0); } if (ms < 100) { sb.append(0); } sb.append(ms); return sb.toString(); } public List<String> list(String path) throws CommandAbstractException { checkIdentify(); // First get all base directories String newpath = path; if (newpath == null || newpath.isEmpty()) { newpath = currentDir; } if (newpath.startsWith("-a") || newpath.startsWith("-A")) { String[] args = newpath.split(" "); if (args.length > 1) { newpath = args[1]; } else { newpath = currentDir; } } newpath = consolidatePath(newpath); logger.debug("debug: " + newpath); List<String> paths = wildcardFiles(newpath); if (paths.isEmpty()) { throw new Reply550Exception("No files found"); } // Now if they are directories, list inside them List<String> newPaths = new ArrayList<String>(); for (String file : paths) { File dir = getFileFromPath(file); if (dir.exists()) { if (dir.isDirectory()) { String[] files = dir.list(); for (String finalFile : files) { String relativePath = ((FilesystemBasedAuthImpl) getSession() .getAuth()).getRelativePath(finalFile); newPaths.add(relativePath); } } else { newPaths.add(file); } } } return newPaths; } public List<String> listFull(String path, boolean lsFormat) throws CommandAbstractException { checkIdentify(); boolean listAllFiles = false; String newpath = path; if (newpath == null || newpath.isEmpty()) { newpath = currentDir; } if (newpath.startsWith("-a") || newpath.startsWith("-A")) { String[] args = newpath.split(" "); if (args.length > 1) { newpath = args[1]; } else { newpath = currentDir; } listAllFiles = true; } newpath = consolidatePath(newpath); // First get all base directories List<String> paths = wildcardFiles(newpath); if (paths.isEmpty()) { throw new Reply550Exception("No files found"); } // Now if they are directories, list inside them List<String> newPaths = new ArrayList<String>(); for (String file : paths) { File dir = getFileFromPath(file); if (dir.exists()) { if (dir.isDirectory()) { File[] files = dir.listFiles(); for (File finalFile : files) { if (lsFormat) { newPaths.add(lsInfo(finalFile)); } else { newPaths.add(mlsxInfo(finalFile)); } } } else { if (lsFormat) { newPaths.add(lsInfo(dir)); } else { newPaths.add(mlsxInfo(dir)); } } } } if (listAllFiles) { File dir = new File(getFileFromPath(newpath), SEPARATOR + ".."); if (lsFormat) { newPaths.add(lsInfo(dir)); } else { newPaths.add(mlsxInfo(dir)); } } return newPaths; } public String fileFull(String path, boolean lsFormat) throws CommandAbstractException { checkIdentify(); String newpath = consolidatePath(path); List<String> paths = wildcardFiles(normalizePath(newpath)); if (paths.size() != 1) { throw new Reply550Exception("No files found " + paths.size() + " founds"); } File file = getFileFromPath(paths.get(0)); if (file.exists()) { if (lsFormat) { return "Listing of \"" + paths.get(0) + "\"\n" + lsInfo(file) + "\nEnd of listing"; } return "Listing of \"" + paths.get(0) + "\"\n" + mlsxInfo(file) + "\nEnd of listing"; } return "No file with name \"" + path + "\""; } /** * Decide if Full time or partial time as in 'ls' command * * @return True if Full Time, False is Default (as in 'ls' command) */ protected boolean isFullTime() { // FIXME should be it the default ? return false; } /** * * @param file * @return the ls format information */ protected String lsInfo(File file) { // Unix FileInterface type,permissions,hard // link(?),owner(?),group(?),size,date // and filename StringBuilder builder = new StringBuilder() .append((file.isDirectory() ? 'd' : '-')) .append((file.canRead() ? 'r' : '-')) .append((file.canWrite() ? 'w' : '-')); if (filesystemBasedFtpDirJdk != null) builder.append(filesystemBasedFtpDirJdk.canExecute(file) ? 'x' : '-'); else builder.append('-'); // Group and others not supported builder.append("---").append("---").append(' ') .append("1 ")// hard link ? .append("anybody\t")// owner ? .append("anygroup\t")// group ? .append(file.length())// size .append('\t'); long lastmod = file.lastModified(); String fmt = null; // It seems Full Time is not recognized by some FTP client /* * if(isFullTime()) { fmt = "EEE MMM dd HH:mm:ss yyyy"; } else { */ long currentTime = System.currentTimeMillis(); if (currentTime > lastmod + 6L * 30L * 24L * 60L * 60L * 1000L // Old. || currentTime < lastmod - 60L * 60L * 1000L) { // In the // future. // The file is fairly old or in the future. // POSIX says the cutoff is 6 months old; // approximate this by 6*30 days. // Allow a 1 hour slop factor for what is considered "the future", // to allow for NFS server/client clock disagreement. // Show the year instead of the time of day. fmt = "MMM dd yyyy"; } else { fmt = "MMM dd HH:mm"; } /* } */ SimpleDateFormat dateFormat = (SimpleDateFormat) DateFormat .getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.ENGLISH); dateFormat.applyPattern(fmt); builder.append(dateFormat.format(new Date(lastmod)))// date .append('\t').append(file.getName()); return builder.toString(); } /** * * @param file * @return the MLSx information: ' Fact=facts;...; filename' */ protected String mlsxInfo(File file) { // don't have create, unique, lang, media-type, charset StringBuilder builder = new StringBuilder(" "); if (getOptsMLSx().getOptsSize() == 1) { builder.append("Size=").append(file.length()).append(';'); } if (getOptsMLSx().getOptsModify() == 1) { builder.append("Modify=").append(this.getModificationTime(file)).append(';'); } if (getOptsMLSx().getOptsType() == 1) { builder.append("Type="); try { if (getFileFromPath(currentDir).equals(file)) { builder.append("cdir"); } else { if (file.isDirectory()) { builder.append("dir"); } else { builder.append("file"); } } } catch (CommandAbstractException e) { if (file.isDirectory()) { builder.append("dir"); } else { builder.append("file"); } } builder.append(';'); } if (getOptsMLSx().getOptsPerm() == 1) { builder.append("Perm="); if (file.isFile()) { if (file.canWrite()) { builder.append('a').append('d').append('f').append('w'); } if (file.canRead()) { builder.append('r'); } } else { // Directory if (file.canWrite()) { builder.append('c'); try { if (this.validatePath(file) != null) { builder.append('d').append('m').append('p'); } } catch (CommandAbstractException e) { } } if (file.canRead()) { builder.append('l').append('e'); } } builder.append(';'); } builder.append(' ').append(file.getName()); return builder.toString(); } public long getFreeSpace() throws CommandAbstractException { checkIdentify(); File directory = getFileFromPath(currentDir); if (filesystemBasedFtpDirJdk != null) return filesystemBasedFtpDirJdk.getFreeSpace(directory); else return Integer.MAX_VALUE; } public FileInterface setUniqueFile() throws CommandAbstractException { checkIdentify(); File file = null; try { file = File.createTempFile(getSession().getAuth().getUser(), this.session.getUniqueExtension(), getFileFromPath(currentDir)); } catch (IOException e) { throw new Reply550Exception("Cannot create unique file"); } String currentFile = getRelativePath(file); return newFile(normalizePath(currentFile), false); } public boolean canRead() throws CommandAbstractException { checkIdentify(); return getFileFromPath(currentDir).canRead(); } public boolean canWrite() throws CommandAbstractException { checkIdentify(); File file = getFileFromPath(currentDir); return file.canWrite(); } public boolean exists() throws CommandAbstractException { checkIdentify(); return getFileFromPath(currentDir).exists(); } public long getCRC(String path) throws CommandAbstractException { File file = getTrueFile(path); CheckedInputStream cis = null; try { try { // Computer CRC32 checksum cis = new CheckedInputStream(new FileInputStream(file), new CRC32()); } catch (FileNotFoundException e) { throw new Reply550Exception("File not found: " + path); } byte[] buf = new byte[session.getBlockSize()]; while (cis.read(buf) >= 0) { } long result = cis.getChecksum().getValue(); return result; } catch (IOException e) { throw new Reply550Exception("Error while reading file: " + path); } finally { if (cis != null) { try { cis.close(); } catch (IOException e) { } } } } public byte[] getMD5(String path) throws CommandAbstractException { File file = getTrueFile(path); try { if (FilesystemBasedFileParameterImpl.useNio) { return FilesystemBasedDigest.getHashMd5Nio(file); } return FilesystemBasedDigest.getHashMd5(file); } catch (IOException e1) { throw new Reply550Exception("Error while reading file: " + path); } } public byte[] getSHA1(String path) throws CommandAbstractException { File file = getTrueFile(path); try { if (FilesystemBasedFileParameterImpl.useNio) { return FilesystemBasedDigest.getHashSha1Nio(file); } return FilesystemBasedDigest.getHashSha1(file); } catch (IOException e1) { throw new Reply550Exception("Error while reading file: " + path); } } public byte[] getSHA256(String path) throws CommandAbstractException { File file = getTrueFile(path); try { return FilesystemBasedDigest.getHash(file, FilesystemBasedFileParameterImpl.useNio, DigestAlgo.SHA256); } catch (IOException e1) { throw new Reply550Exception("Error while reading file: " + path); } } public byte[] getSHA512(String path) throws CommandAbstractException { File file = getTrueFile(path); try { return FilesystemBasedDigest.getHash(file, FilesystemBasedFileParameterImpl.useNio, DigestAlgo.SHA512); } catch (IOException e1) { throw new Reply550Exception("Error while reading file: " + path); } } }