/*
* $Id$
*
* Copyright (c) 2000-2009 by Rodney Kinney, Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools.io;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
/**
* A {@link FilterInputStream} which converts a file created with
* {@link ObfuscatingOutputStream} back into plain text.
* Additionally, plain text will be passed through unchanged.
*
* @author Joel Uckelman
* @since 3.2.0
*/
public class DeobfuscatingInputStream extends FilterInputStream {
/**
* @param in the stream to wrap
* @throws IOException
*/
public DeobfuscatingInputStream(InputStream in) throws IOException {
super(null);
final byte[] header = new byte[ObfuscatingOutputStream.HEADER.length()];
readFully(in, header, 0, header.length);
if (new String(header, "UTF-8").equals(ObfuscatingOutputStream.HEADER)) {
this.in = new DeobfuscatingInputStreamImpl(in);
}
else {
final PushbackInputStream pin =
new PushbackInputStream(in, header.length);
pin.unread(header);
this.in = pin;
}
}
/**
* Reads the given number of bytes.
*
* @param in the source
* @param bytes the destination
* @param off the offset into the destination array
* @param len the number of bytes to read
* @throws IOException if <code>len</code> bytes cannot be read
*/
private static int readFully(InputStream in, byte[] bytes, int off, int len)
throws IOException {
int count;
int n = 0;
while (n < len) {
count = in.read(bytes, off + n, len - n);
if (count < 0) break;
n += count;
}
return n;
}
private static class DeobfuscatingInputStreamImpl extends FilterInputStream {
private final byte key;
private final byte[] pair = new byte[2];
public DeobfuscatingInputStreamImpl(InputStream in) throws IOException {
super(in);
readFully(in, pair, 0, 2);
key = (byte) ((unhex(pair[0]) << 4) | unhex(pair[1]));
}
@Override
public int read(byte[] bytes, int off, int len) throws IOException {
int b = 0;
int i = 0;
while (i < len && (b = read()) >= 0) bytes[(i++)+off] = (byte) b;
return b == -1 && i == 0 ? -1 : i;
}
@Override
public int read() throws IOException {
switch (readFully(in, pair, 0, 2)) {
case 0:
return -1;
case 1:
throw new IOException();
case 2:
return (((unhex(pair[0]) << 4) | unhex(pair[1])) ^ key) & 0xFF;
default:
throw new IOException();
}
}
private int unhex(int i) throws IOException {
switch (i) {
// digits 0-9
case 0x30:
case 0x31:
case 0x32:
case 0x33:
case 0x34:
case 0x35:
case 0x36:
case 0x37:
case 0x38:
case 0x39:
return i - 0x30;
// digits A-F
case 0x41:
case 0x42:
case 0x43:
case 0x44:
case 0x45:
case 0x46:
return i - 0x37;
// digits a-f
case 0x61:
case 0x62:
case 0x63:
case 0x64:
case 0x65:
case 0x66:
return i - 0x57;
default:
throw new IOException(String.valueOf(i));
}
}
}
public static void main(String[] args) throws IOException {
InputStream in = null;
try {
in = new DeobfuscatingInputStream(
args.length > 0 ? new FileInputStream(args[0]) : System.in);
IOUtils.copy(in, System.out);
}
finally {
IOUtils.closeQuietly(in);
}
System.exit(0);
}
}