/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support.io;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Random;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.SICBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import freenet.client.DefaultMIMETypes;
import freenet.node.NodeStarter;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.SizeUtil;
import freenet.support.StringValidityChecker;
import freenet.support.Logger.LogLevel;
final public class FileUtil {
public static final int BUFFER_SIZE = 32*1024;
/**
* Returns a line reading stream for the content of <code>logfile</code>. The stream will
* contain at most <code>byteLimit</code> bytes. If <code>byteLimit</code> is less than the
* size of <code>logfile</code>, the first part of the file will be skipped. If this leaves a
* partial line at the beginning of the content to read, that partial line will also be
* skipped.
* @param logfile The file to open
* @param byteLimit The maximum number of bytes to read
* @return A line reader for the trailing portion of the file
* @throws java.io.IOException if an I/O error occurs
*/
public static LineReadingInputStream getLogTailReader(File logfile, long byteLimit) throws IOException {
long length = logfile.length();
long skip = 0;
if (length > byteLimit) {
skip = length - byteLimit;
}
FileInputStream fis = null;
LineReadingInputStream lis = null;
try {
fis = new FileInputStream(logfile);
lis = new LineReadingInputStream(fis);
if (skip > 0) {
lis.skip(skip);
lis.readLine(100000, 200, true);
}
} catch (IOException e) {
Closer.close(lis);
Closer.close(fis);
throw e;
}
return lis;
}
public static enum OperatingSystem {
Unknown(false, false, false), // Special-cased in filename sanitising code.
MacOS(false, true, true), // OS/X in that it can run scripts.
Linux(false, false, true),
FreeBSD(false, false, true),
GenericUnix(false, false, true),
Windows(true, false, false);
public final boolean isWindows;
public final boolean isMac;
public final boolean isUnix;
OperatingSystem(boolean win, boolean mac, boolean unix) {
this.isWindows = win;
this.isMac = mac;
this.isUnix = unix;
};
};
public static enum CPUArchitecture {
Unknown,
X86,
X86_64,
PPC_32,
PPC_64,
ARM,
SPARC,
IA64
}
public static final OperatingSystem detectedOS;
/** Caveat: Sometimes this may not be entirely accurate, e.g. we may not be able to distinguish
* 32-bit from 64-bit, we may be using the wrong JVM for the platform, we may be using an x86
* wrapper or JVM on an IA64 system etc. This *should* be the version the JVM is running. */
public static final CPUArchitecture detectedArch;
private static final Charset fileNameCharset;
static {
detectedOS = detectOperatingSystem();
detectedArch = detectCPUArchitecture();
// I did not find any way to detect the Charset of the file system so I'm using the file encoding charset.
// On Windows and Linux this is set based on the users configured system language which is probably equal to the filename charset.
// The worst thing which can happen if we misdetect the filename charset is that downloads fail because the filenames are invalid:
// We disallow path and file separator characters anyway so its not possible to cause files to be stored in arbitrary places.
fileNameCharset = getFileEncodingCharset();
}
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
/**
* Detects the operating system in which the JVM is running. Returns OperatingSystem.Unknown if the OS is unknown or an error occured.
* Therefore this function should never throw.
*/
private static OperatingSystem detectOperatingSystem() { // TODO: Move to the proper class
try {
final String name = System.getProperty("os.name").toLowerCase();
// Order the if() by probability instead alphabetically to decrease the false-positive rate in case they decide to call it "Windows Mac" or whatever
// Please adapt sanitizeFileName when adding new OS.
if(name.indexOf("win") >= 0)
return OperatingSystem.Windows;
if(name.indexOf("mac") >= 0)
return OperatingSystem.MacOS;
if(name.indexOf("linux") >= 0)
return OperatingSystem.Linux;
if(name.indexOf("freebsd") >= 0)
return OperatingSystem.FreeBSD;
if(name.indexOf("unix") >= 0)
return OperatingSystem.GenericUnix;
else if(File.separatorChar == '/')
return OperatingSystem.GenericUnix;
else if(File.separatorChar == '\\')
return OperatingSystem.Windows;
Logger.error(FileUtil.class, "Unknown operating system:" + name);
} catch(Throwable t) {
Logger.error(FileUtil.class, "Operating system detection failed", t);
}
return OperatingSystem.Unknown;
}
private static CPUArchitecture detectCPUArchitecture() { // TODO Move to the proper class
try {
final String name = System.getProperty("os.arch").toLowerCase();
if(name.equals("x86") || name.equals("i386") || name.matches("i[3-9]86"))
return CPUArchitecture.X86;
if(name.equals("amd64") || name.equals("x86-64") || name.equals("x86_64") ||
name.equals("x86") || name.equals("em64t") || name.equals("x8664") ||
name.equals("8664"))
return CPUArchitecture.X86_64;
if(name.startsWith("arm"))
return CPUArchitecture.ARM; // FIXME arm64 support?
if(name.equals("ppc") || name.equals("powerpc"))
return CPUArchitecture.PPC_32;
if(name.equals("ppc64"))
return CPUArchitecture.PPC_64;
if(name.startsWith("ia64"))
return CPUArchitecture.IA64;
} catch (Throwable t) {
Logger.error(FileUtil.class, "CPU architecture detection failed", t);
}
return CPUArchitecture.Unknown;
}
/**
* Returns the Charset which is equal to the "file.encoding" property.
* This property is set to the users configured system language on windows for example.
*
* If any error occurs, the default Charset is returned. Therefore this function should never throw.
*/
public static Charset getFileEncodingCharset() {
try {
return Charset.forName(System.getProperty("file.encoding"));
} catch(Throwable t) {
return Charset.defaultCharset();
}
}
/** Round up a value to the next multiple of a power of 2 */
private static long roundup_2n (long val, int blocksize) {
int mask=blocksize-1;
return (val+mask)&~mask;
}
/**
* Guesstimate real disk usage for a file with a given filename, of a given length.
*/
public static long estimateUsage(File file, long flen) {
/**
* It's possible that none of these assumptions are accurate for any filesystem;
* this is intended to be a plausible worst case.
*/
// Assume 4kB clusters for calculating block usage (NTFS)
long blockUsage = roundup_2n(flen, 4096);
// Assume 512 byte filename entries, with 100 bytes overhead, for filename overhead (NTFS)
String filename = file.getName();
int nameLength;
try {
nameLength = Math.max(filename.getBytes("UTF-16").length, filename.getBytes("UTF-8").length) + 100;
} catch (UnsupportedEncodingException e) {
// Impossible.
throw new RuntimeException("UTF-16 or UTF-8 charset not supported?!");
}
long filenameUsage = roundup_2n(nameLength, 512);
// Assume 50 bytes per block tree overhead with 1kB blocks (reiser3 worst case)
long extra = (roundup_2n(flen, 1024) / 1024) * 50;
return blockUsage + filenameUsage + extra;
}
/**
* Is possParent a parent of filename?
* Why doesn't java provide this? :(
* */
public static boolean isParent(File poss, File filename) {
File canon = FileUtil.getCanonicalFile(poss);
File canonFile = FileUtil.getCanonicalFile(filename);
if(isParentInner(poss, filename)) return true;
if(isParentInner(poss, canonFile)) return true;
if(isParentInner(canon, filename)) return true;
if(isParentInner(canon, canonFile)) return true;
return false;
}
private static boolean isParentInner(File possParent, File filename) {
while(true) {
if(filename.equals(possParent)) return true;
filename = filename.getParentFile();
if(filename == null) return false;
}
}
public static File getCanonicalFile(File file) {
// Having some problems storing File's in db4o ...
// It would start up, and canonicalise a file with path "/var/lib/freenet-experimental/persistent-temp-24374"
// to /var/lib/freenet-experimental/var/lib/freenet-experimental/persistent-temp-24374
// (where /var/lib/freenet-experimental is the current working dir)
// Regenerating from path worked. So do that here.
// And yes, it's voodoo.
String name = file.getPath();
if(File.pathSeparatorChar == '\\') {
name = name.toLowerCase();
}
file = new File(name);
File result;
try {
result = file.getAbsoluteFile().getCanonicalFile();
} catch (IOException e) {
result = file.getAbsoluteFile();
}
return result;
}
/**
* Reads the entire content of a file as UTF-8 and returns it.
* @param file The file to read
* @return The content of <code>file</code>
* @throws FileNotFoundException if <code>file</code> cannot be opened
* @throws IOException if an I/O error occurs
*/
public static StringBuilder readUTF(File file) throws FileNotFoundException, IOException {
return readUTF(file, 0);
}
/**
* Reads the content of a file as UTF-8, starting at a specified offset, and returns it.
* @param file The file to read
* @param offset The point in <code>file</code> at which to start reading
* @return The content of <code>file</code>, starting at <code>offset</code>
* @throws FileNotFoundException if <code>file</code> cannot be opened
* @throws IOException if an I/O error occurs
*/
public static StringBuilder readUTF(File file, long offset) throws FileNotFoundException, IOException {
StringBuilder result = new StringBuilder();
FileInputStream fis = null;
BufferedInputStream bis = null;
InputStreamReader isr = null;
try {
fis = new FileInputStream(file);
skipFully(fis, offset);
bis = new BufferedInputStream(fis);
isr = new InputStreamReader(bis, "UTF-8");
char[] buf = new char[4096];
int length = 0;
while((length = isr.read(buf)) > 0) {
result.append(buf, 0, length);
}
} finally {
Closer.close(isr);
Closer.close(bis);
Closer.close(fis);
}
return result;
}
/**
* Reads the entire content of a stream as UTF-8 and returns it.
* @param stream The stream to read
* @return The content of <code>stream</code>
* @throws IOException if an I/O error occurs
*/
public static StringBuilder readUTF(InputStream stream) throws IOException {
return readUTF(stream, 0);
}
/**
* Reads the content of a stream as UTF-8, starting at a specified offset, and returns it.
* @param stream The stream to read
* @param offset The point in <code>stream</code> at which to start reading
* @return The content of <code>stream</code>, starting at <code>offset</code>
* @throws IOException if an I/O error occurs
*/
public static StringBuilder readUTF(InputStream stream, long offset) throws IOException {
StringBuilder result = new StringBuilder();
skipFully(stream, offset);
InputStreamReader reader = null;
try {
reader = new InputStreamReader(stream, "UTF-8");
char[] buf = new char[4096];
int length = 0;
while((length = reader.read(buf)) > 0) {
result.append(buf, 0, length);
}
} finally {
Closer.close(reader);
}
return result;
}
/**
* Reliably skip a number of bytes or throw.
*/
public static void skipFully(InputStream is, long skip) throws IOException {
long skipped = 0;
while(skipped < skip) {
long x = is.skip(skip - skipped);
if(x <= 0) throw new IOException("Unable to skip "+(skip - skipped)+" bytes");
skipped += x;
}
}
public static boolean writeTo(InputStream input, File target) throws FileNotFoundException, IOException {
DataInputStream dis = null;
FileOutputStream fos = null;
File file = File.createTempFile("temp", ".tmp", target.getParentFile());
if(logMINOR)
Logger.minor(FileUtil.class, "Writing to "+file+" to be renamed to "+target);
try {
dis = new DataInputStream(input);
fos = new FileOutputStream(file);
int len = 0;
byte[] buffer = new byte[4096];
while ((len = dis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
throw e;
} finally {
if(dis != null) dis.close();
if(fos != null) fos.close();
}
if(FileUtil.renameTo(file, target))
return true;
else {
file.delete();
return false;
}
}
public static boolean renameTo(File orig, File dest) {
// Try an atomic rename
// Shall we prevent symlink-race-conditions here ?
if(orig.equals(dest))
throw new IllegalArgumentException("Huh? the two file descriptors are the same!");
if(!orig.exists()) {
throw new IllegalArgumentException("Original doesn't exist!");
}
if (!orig.renameTo(dest)) {
// Not supported on some systems (Windows)
if (!dest.delete()) {
if (dest.exists()) {
Logger.error("FileUtil", "Could not delete " + dest + " - check permissions");
System.err.println("Could not delete " + dest + " - check permissions");
}
}
if (!orig.renameTo(dest)) {
String err = "Could not rename " + orig + " to " + dest +
(dest.exists() ? " (target exists)" : "") +
(orig.exists() ? " (source exists)" : "") +
" - check permissions";
Logger.error(FileUtil.class, err);
System.err.println(err);
return false;
}
}
return true;
}
/**
* Like renameTo(), but can move across filesystems, by copying the data.
* @param f
* @param file
*/
public static boolean moveTo(File orig, File dest, boolean overwrite) {
if(orig.equals(dest))
throw new IllegalArgumentException("Huh? the two file descriptors are the same!");
if(!orig.exists()) {
throw new IllegalArgumentException("Original doesn't exist!");
}
if(dest.exists()) {
if(overwrite)
dest.delete();
else {
System.err.println("Not overwriting "+dest+" - already exists moving "+orig);
return false;
}
}
if(!orig.renameTo(dest))
return copyFile(orig, dest);
else return true;
}
/**
* Sanitizes the given filename to be valid on the given operating system.
* If OperatingSystem.Unknown is specified this function will generate a filename which fullfils the restrictions of all known OS, currently
* this is MacOS, Unix and Windows.
*/
public static String sanitizeFileName(final String fileName, OperatingSystem targetOS, String extraChars) {
// Filter out any characters which do not exist in the charset.
final CharBuffer buffer = fileNameCharset.decode(fileNameCharset.encode(fileName)); // Charset are thread-safe
final StringBuilder sb = new StringBuilder(fileName.length() + 1);
switch(targetOS) {
case Unknown: break;
case MacOS: break;
case Linux: break;
case FreeBSD: break;
case GenericUnix: break;
case Windows: break;
default:
Logger.error(FileUtil.class, "Unsupported operating system: " + targetOS);
targetOS = OperatingSystem.Unknown;
break;
}
char def = ' ';
if(extraChars.indexOf(' ') != -1) {
def = '_';
if(extraChars.indexOf(def) != -1) {
def = '-';
if(extraChars.indexOf(def) != -1)
throw new IllegalArgumentException("What do you want me to use instead of spaces???");
}
}
for(char c : buffer.array()) { // Note that this will add extra whitespace to the end, which we will trim later.
if(extraChars.indexOf(c) != -1) {
sb.append(def);
continue;
}
// Control characters and whitespace are converted to space for all OS.
// We do not check for the file separator character because it is included in each OS list of reserved characters.
if(Character.getType(c) == Character.CONTROL || Character.isWhitespace(c)) {
sb.append(def);
continue;
}
if(targetOS == OperatingSystem.Unknown || targetOS.isWindows) {
if(StringValidityChecker.isWindowsReservedPrintableFilenameCharacter(c)) {
sb.append(def);
continue;
}
}
if(targetOS == OperatingSystem.Unknown || targetOS.isMac) {
if(StringValidityChecker.isMacOSReservedPrintableFilenameCharacter(c)) {
sb.append(def);
continue;
}
}
if(targetOS == OperatingSystem.Unknown || targetOS.isUnix) {
if(StringValidityChecker.isUnixReservedPrintableFilenameCharacter(c)) {
sb.append(def);
continue;
}
}
// Nothing did continue; so the character is okay
sb.append(c);
}
// In windows, the last character of a filename may not be space or dot. We cut them off
if(targetOS == OperatingSystem.Unknown || targetOS.isWindows) {
int lastCharIndex = sb.length() - 1;
while(lastCharIndex >= 0) {
char lastChar = sb.charAt(lastCharIndex);
if(lastChar == ' ' || lastChar == '.')
sb.deleteCharAt(lastCharIndex--);
else
break;
}
}
// Now the filename might be one of the reserved filenames in Windows (CON etc.) and we must replace it if it is...
if(targetOS == OperatingSystem.Unknown || targetOS.isWindows) {
if(StringValidityChecker.isWindowsReservedFilename(sb.toString()))
sb.insert(0, '_');
}
if(sb.length() == 0) {
sb.append("Invalid filename"); // TODO: L10n
}
return sb.toString().trim(); // Trim leading and trailing whitespace.
// Some of the trailing whitespace may be from the CharBuffer.
}
public static String sanitize(String fileName) {
return sanitizeFileName(fileName, detectedOS, "");
}
public static String sanitizeFileNameWithExtras(String fileName, String extraChars) {
return sanitizeFileName(fileName, detectedOS, extraChars);
}
public static String sanitize(String filename, String mimeType) {
filename = sanitize(filename);
if(mimeType == null) return filename;
return DefaultMIMETypes.forceExtension(filename, mimeType);
}
/**
* Find the length of an input stream. This method will consume the complete
* input stream until its {@link InputStream#read(byte[])} method returns
* <code>-1</code>, thus signalling the end of the stream.
*
* @param source
* The input stream to find the length of
* @return The numbe of bytes that can be read from the stream
* @throws IOException
* if an I/O error occurs
*/
public static long findLength(InputStream source) throws IOException {
long length = 0;
byte[] buffer = new byte[BUFFER_SIZE];
int read = 0;
while (read > -1) {
read = source.read(buffer);
if (read != -1) {
length += read;
}
}
return length;
}
/**
* Copies <code>length</code> bytes from the source input stream to the
* destination output stream. If <code>length</code> is <code>-1</code>
* as much bytes as possible will be copied (i.e. until
* {@link InputStream#read()} returns <code>-1</code> to signal the end of
* the stream).
*
* @param source
* The input stream to read from
* @param destination
* The output stream to write to
* @param length
* The number of bytes to copy
* @throws IOException
* if an I/O error occurs
*/
public static void copy(InputStream source, OutputStream destination, long length) throws IOException {
long remaining = length;
byte[] buffer = new byte[BUFFER_SIZE];
int read = 0;
while ((remaining == -1) || (remaining > 0)) {
read = source.read(buffer, 0, ((remaining > BUFFER_SIZE) || (remaining == -1)) ? BUFFER_SIZE : (int) remaining);
if (read == -1) {
if (length == -1) {
return;
}
throw new EOFException("stream reached eof");
}
destination.write(buffer, 0, read);
if (remaining > 0)
remaining -= read;
}
}
public static boolean secureDeleteAll(File wd) throws IOException {
if(!wd.isDirectory()) {
System.err.println("DELETING FILE "+wd);
try {
secureDelete(wd);
} catch (IOException e) {
Logger.error(FileUtil.class, "Could not delete file: "+wd, e);
return false;
}
} else {
for(File subfile: wd.listFiles()) {
if(!removeAll(subfile)) return false;
}
if(!wd.delete()) {
Logger.error(FileUtil.class, "Could not delete directory: "+wd);
}
}
return true;
}
/** Delete everything in a directory. Only use this when we are *very sure* there is no
* important data below it! */
public static boolean removeAll(File wd) {
if(!wd.isDirectory()) {
System.err.println("DELETING FILE "+wd);
if(!wd.delete() && wd.exists()) {
Logger.error(FileUtil.class, "Could not delete file: "+wd);
return false;
}
} else {
for(File subfile: wd.listFiles()) {
if(!removeAll(subfile)) return false;
}
if(!wd.delete()) {
Logger.error(FileUtil.class, "Could not delete directory: "+wd);
}
}
return true;
}
public static void secureDelete(File file) throws IOException {
// FIXME somebody who understands these things should have a look at this...
if(!file.exists()) return;
long size = file.length();
if(size > 0) {
RandomAccessFile raf = null;
try {
System.out.println("Securely deleting "+file+" which is of length "+size+" bytes...");
raf = new RandomAccessFile(file, "rw");
long count;
// Random data first.
raf.seek(0);
fill(new RandomAccessFileOutputStream(raf), size);
raf.getFD().sync();
raf.close();
raf = null;
} finally {
Closer.close(raf);
}
}
if((!file.delete()) && file.exists())
throw new IOException("Unable to delete file "+file);
}
/** @Deprecated */
public static void secureDelete(File file, Random random) throws IOException {
secureDelete(file);
}
/**
** Set owner-only RW on the given file.
*/
public static boolean setOwnerRW(File f) {
return setOwnerPerm(f, true, true, false);
}
/**
** Set owner-only RWX on the given file.
*/
public static boolean setOwnerRWX(File f) {
return setOwnerPerm(f, true, true, true);
}
/**
** Set owner-only permissions on the given file.
*/
public static boolean setOwnerPerm(File f, boolean r, boolean w, boolean x) {
/* JDK6 replace when we upgrade
boolean b = f.setReadable(false, false);
b &= f.setWritable(false, false);
b &= f.setExecutable(false, false);
b &= f.setReadable(r, true);
b &= f.setWritable(w, true);
b &= f.setExecutable(x, true);
return b;
*/
boolean success = true;
try {
String[] methods = {"setReadable", "setWritable", "setExecutable"};
boolean[] perms = {r, w, x};
for (int i=0; i<methods.length; ++i) {
Method m = File.class.getDeclaredMethod(methods[i], boolean.class, boolean.class);
if (m != null) {
success &= (Boolean)m.invoke(f, false, false);
success &= (Boolean)m.invoke(f, perms[i], true);
}
}
} catch (NoSuchMethodException e) {
success = false;
} catch (java.lang.reflect.InvocationTargetException e) {
success = false;
} catch (IllegalAccessException e) {
success = false;
} catch (ExceptionInInitializerError e) {
success = false;
} catch (RuntimeException e) {
success = false;
}
return success;
}
public static boolean equals(File a, File b) {
if(a == b) return true;
if(a.equals(b)) return true;
a = getCanonicalFile(a);
b = getCanonicalFile(b);
return a.equals(b);
}
/** Create a temp file in a specific directory. Null = ".".
* @throws IOException */
public static File createTempFile(String prefix, String suffix,
File directory) throws IOException {
if(directory == null) directory = new File(".");
return File.createTempFile(prefix, suffix, directory);
}
public static boolean copyFile(File copyFrom, File copyTo) {
copyTo.delete();
boolean executable = copyFrom.canExecute();
FileBucket outBucket = new FileBucket(copyTo, false, true, false, false);
FileBucket inBucket = new FileBucket(copyFrom, true, false, false, false);
try {
BucketTools.copy(inBucket, outBucket);
if(executable) {
if(!(copyTo.setExecutable(true) || copyTo.canExecute())) {
System.err.println("Unable to preserve executable bit when copying "+copyFrom+" to "+copyTo+" - you may need to make it executable!");
// return false; ??? FIXME debatable.
}
}
return true;
} catch (IOException e) {
System.err.println("Unable to copy from "+copyFrom+" to "+copyTo);
return false;
}
}
private static CipherInputStream cis;
private static ZeroInputStream zis = new ZeroInputStream();
private static long cisCounter;
/** Write hard to identify random data to the OutputStream. Does not drain the global secure
* random number generator, and is significantly faster than it.
* @param os The stream to write to.
* @param length The number of bytes to write.
* @throws IOException If unable to write to the stream.
*/
public static void fill(OutputStream os, long length) throws IOException {
long remaining = length;
byte[] buffer = new byte[BUFFER_SIZE];
int read = 0;
while ((remaining == -1) || (remaining > 0)) {
synchronized(FileUtil.class) {
if(cis == null || cisCounter > Long.MAX_VALUE/2) {
// Reset it well before the birthday paradox (note this is actually counting bytes).
byte[] key = new byte[16];
byte[] iv = new byte[16];
SecureRandom rng = NodeStarter.getGlobalSecureRandom();
rng.nextBytes(key);
rng.nextBytes(iv);
AESFastEngine e = new AESFastEngine();
SICBlockCipher ctr = new SICBlockCipher(e);
ctr.init(true, new ParametersWithIV(new KeyParameter(key),iv));
cis = new CipherInputStream(zis, new BufferedBlockCipher(ctr));
cisCounter = 0;
}
read = cis.read(buffer, 0, ((remaining > BUFFER_SIZE) || (remaining == -1)) ? BUFFER_SIZE : (int) remaining);
cisCounter += read;
}
if (read == -1) {
if (length == -1) {
return;
}
throw new EOFException("stream reached eof");
}
os.write(buffer, 0, read);
if (remaining > 0)
remaining -= read;
}
}
/** @deprecated */
public static void fill(OutputStream os, Random random, long length) throws IOException {
long moved = 0;
byte[] buf = new byte[BUFFER_SIZE];
while(moved < length) {
int toRead = (int)Math.min(BUFFER_SIZE, length - moved);
random.nextBytes(buf);
os.write(buf, 0, toRead);
moved += toRead;
}
}
public static boolean equalStreams(InputStream a, InputStream b, long size) throws IOException {
byte[] aBuffer = new byte[BUFFER_SIZE];
byte[] bBuffer = new byte[BUFFER_SIZE];
DataInputStream aIn = new DataInputStream(a);
DataInputStream bIn = new DataInputStream(b);
long checked = 0;
while(checked < size) {
int toRead = (int)Math.min(BUFFER_SIZE, size - checked);
aIn.readFully(aBuffer, 0, toRead);
bIn.readFully(bBuffer, 0, toRead);
if(!MessageDigest.isEqual(aBuffer, bBuffer))
return false;
checked += toRead;
}
return true;
}
}