package com.limegroup.gnutella.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import com.limegroup.gnutella.FileDesc;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.UploadManager;
/**
* This class provides static functions to load/store the files.
* @author Anurag Singla
*/
public class FileUtils {
/**
* Writes the passed map to corresponding file
* @param filename The name of the file to which to write the passed map
* @param map The map to be stored
*/
public static void writeMap(String filename, Map map)
throws IOException, ClassNotFoundException {
ObjectOutputStream out = null;
try {
//open the file
out = new ObjectOutputStream(new FileOutputStream(filename));
//write to the file
out.writeObject(map);
} finally {
//close the stream
if(out != null)
out.close();
}
}
/**
* Reads the map stored, in serialized object form,
* in the passed file and returns it. from the file where it is stored
* @param filename The file from where to read the Map
* @return The map that was read
*/
public static Map readMap(String filename)
throws IOException, ClassNotFoundException {
ObjectInputStream in = null;
try {
//open the file
in = new ObjectInputStream(new FileInputStream(filename));
//read and return the object
return (Map)in.readObject();
} finally {
//close the file
if(in != null)
in.close();
}
}
/**
* Gets the canonical path, catching buggy Windows errors
*/
public static String getCanonicalPath(File f) throws IOException {
try {
return f.getCanonicalPath();
} catch(IOException ioe) {
String msg = ioe.getMessage();
// windows bugs out :(
if(CommonUtils.isWindows() && msg != null && msg.indexOf("There are no more files") != -1)
return f.getAbsolutePath();
else
throw ioe;
}
}
/** Same as f.getCanonicalFile() in JDK1.3. */
public static File getCanonicalFile(File f) throws IOException {
try {
return f.getCanonicalFile();
} catch(IOException ioe) {
String msg = ioe.getMessage();
// windows bugs out :(
if(CommonUtils.isWindows() && msg != null && msg.indexOf("There are no more files") != -1)
return f.getAbsoluteFile();
else
throw ioe;
}
}
/**
* Detects attempts at directory traversal by testing if testDirectory
* really is the parent of testPath. This method should be used to make
* sure directory traversal tricks aren't being used to trick
* LimeWire into reading or writing to unexpected places.
*
* Directory traversal security problems occur when software doesn't
* check if input paths contain characters (such as "../") that cause the
* OS to go up a directory. This function will ignore benign cases where
* the path goes up one directory and then back down into the original directory.
*
* @return false if testParent is not the parent of testChild.
* @throws IOException if getCanonicalPath throws IOException for either input file
*/
public static final boolean isReallyParent(File testParent, File testChild) throws IOException {
// Don't check testDirectory.isDirectory...
// If it's not a directory, it won't be the parent anyway.
// This makes the tests more simple.
String testParentName = getCanonicalPath(testParent);
String testChildParentName = getCanonicalPath(testChild.getAbsoluteFile().getParentFile());
if (! testParentName.equals(testChildParentName))
return false;
return true;
}
/**
* Utility method that returns the file extension of the given file.
*
* @param f the <tt>File</tt> instance from which the extension
* should be extracted
* @return the file extension string, or <tt>null</tt> if the extension
* could not be extracted
*/
public static String getFileExtension(File f) {
String name = f.getName();
return getFileExtension(name);
}
/**
* Utility method that returns the file extension of the given file.
*
* @param name the file name <tt>String</tt> from which the extension
* should be extracted
* @return the file extension string, or <tt>null</tt> if the extension
* could not be extracted
*/
public static String getFileExtension(String name) {
int index = name.lastIndexOf(".");
if(index == -1) return null;
// the file must have a name other than the extension
if(index == 0) return null;
// if the last character of the string is the ".", then there's
// no extension
if(index == (name.length()-1)) return null;
return name.substring(index+1);
}
/**
* Utility method to set a file as non read only.
* If the file is already writable, does nothing.
*
* @param f the <tt>File</tt> instance whose read only flag should
* be unset.
*
* @return whether or not <tt>f</tt> is writable after trying to make it
* writeable -- note that if the file doesn't exist, then this returns
* <tt>true</tt>
*/
public static boolean setWriteable(File f) {
if(!f.exists())
return true;
// non Windows-based systems return the wrong value
// for canWrite when the argument is a directory --
// writing is based on the 'x' attribute, not the 'w'
// attribute for directories.
if(f.canWrite()) {
if(CommonUtils.isWindows())
return true;
else if(!f.isDirectory())
return true;
}
String fName;
try {
fName = f.getCanonicalPath();
} catch(IOException ioe) {
fName = f.getPath();
}
String cmds[] = null;
if( CommonUtils.isWindows() || CommonUtils.isMacOSX() )
SystemUtils.setWriteable(fName);
else if ( CommonUtils.isOS2() )
cmds = null; // Find the right command for OS/2 and fill in
else {
if(f.isDirectory())
cmds = new String[] { "chmod", "u+w+x", fName };
else
cmds = new String[] { "chmod", "u+w", fName};
}
if( cmds != null ) {
try {
Process p = Runtime.getRuntime().exec(cmds);
p.waitFor();
}
catch(SecurityException ignored) { }
catch(IOException ignored) { }
catch(InterruptedException ignored) { }
}
return f.canWrite();
}
/**
* Touches a file, to ensure it exists.
*/
public static void touch(File f) throws IOException {
if(f.exists())
return;
File parent = f.getParentFile();
if(parent != null)
parent.mkdirs();
try {
f.createNewFile();
} catch(IOException failed) {
// Okay, createNewFile failed. Let's try the old way.
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
} catch(IOException ioe) {
ioe.initCause(failed);
throw ioe;
} finally {
if(fos != null) {
try {
fos.close();
} catch(IOException ignored) {}
}
}
}
}
public static boolean forceRename(File a, File b) {
// First attempt to rename it.
boolean success = a.renameTo(b);
// If that fails, try killing any partial uploads we may have
// to unlock the file, and then rename it.
if (!success) {
FileDesc fd = RouterService.getFileManager().getFileDescForFile(
a);
if( fd != null ) {
UploadManager upMan = RouterService.getUploadManager();
// This must all be synchronized so that a new upload
// doesn't lock the file before we rename it.
synchronized(upMan) {
if( upMan.killUploadsForFileDesc(fd) )
success = a.renameTo(b);
}
}
}
// If that didn't work, try copying the file.
if (!success) {
success = CommonUtils.copy(a, b);
//if copying succeeded, get rid of the original
//at this point any active uploads will have been killed
if (success)
a.delete();
}
return success;
}
/**
* Saves the data iff it was written exactly as we wanted.
*/
public static boolean verySafeSave(File dir, String name, byte[] data) {
File tmp;
try {
tmp = File.createTempFile(name, "tmp", dir);
} catch(IOException hrorible) {
return false;
}
File out = new File(dir, name);
OutputStream os = null;
try {
os = new BufferedOutputStream(new FileOutputStream(tmp));
os.write(data);
os.flush();
} catch(IOException bad) {
return false;
} finally {
IOUtils.close(os);
}
//verify that we wrote everything correctly
byte[] read = readFileFully(tmp);
if(read == null || !Arrays.equals(read, data))
return false;
return forceRename(tmp, out);
}
/**
* Reads a file, filling a byte array.
*/
public static byte[] readFileFully(File source) {
DataInputStream raf = null;
int length = (int)source.length();
if(length <= 0)
return null;
byte[] data = new byte[length];
try {
raf = new DataInputStream(new BufferedInputStream(new FileInputStream(source)));
raf.readFully(data);
} catch(IOException ioe) {
return null;
} finally {
IOUtils.close(raf);
}
return data;
}
/**
* @param directory Gets all files under this directory RECURSIVELY.
* @param filter If null, then returns all files. Else, only returns files
* extensions in the filter array.
* @return An array of Files recursively obtained from the directory,
* according to the filter.
*
*/
public static File[] getFilesRecursive(File directory,
String[] filter) {
ArrayList dirs = new ArrayList();
// the return array of files...
ArrayList retFileArray = new ArrayList();
File[] retArray = new File[0];
// bootstrap the process
if (directory.exists() && directory.isDirectory())
dirs.add(directory);
// while i have dirs to process
while (dirs.size() > 0) {
File currDir = (File) dirs.remove(0);
String[] listedFiles = currDir.list();
for (int i = 0; (listedFiles != null) && (i < listedFiles.length); i++) {
File currFile = new File(currDir,listedFiles[i]);
if (currFile.isDirectory()) // to be dealt with later
dirs.add(currFile);
else if (currFile.isFile()) { // we have a 'file'....
boolean shouldAdd = false;
if (filter == null)
shouldAdd = true;
else {
String ext = FileUtils.getFileExtension(currFile);
for (int j = 0; (j < filter.length) && (ext != null); j++) {
if (ext.equalsIgnoreCase(filter[j])) {
shouldAdd = true;
// don't keep looping through all filters --
// one match is good enough
break;
}
}
}
if (shouldAdd)
retFileArray.add(currFile);
}
}
}
if (!retFileArray.isEmpty()) {
retArray = new File[retFileArray.size()];
for (int i = 0; i < retArray.length; i++)
retArray[i] = (File) retFileArray.get(i);
}
return retArray;
}
public static boolean deleteRecursive(File file) {
// make sure we only delete canonical children of the parent file we
// wish to delete. I have a hunch this might be an issue on OSX and
// Linux under certain circumstances.
// If anyone can test whether this really happens (possibly related to
// symlinks), I would much appreciate it.
String canonicalParent;
try {
canonicalParent = file.getCanonicalPath();
} catch (IOException ioe) {
return false;
}
if (!file.isDirectory())
return file.delete();
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
try {
if (!files[i].getCanonicalPath().startsWith(canonicalParent))
continue;
} catch (IOException ioe) {
return false;
}
if (!deleteRecursive(files[i]))
return false;
}
return file.delete();
}
}