227 lines
5.8 KiB
Java
227 lines
5.8 KiB
Java
/*
|
|
* BeDecoder.java
|
|
*
|
|
* Created on May 30, 2003, 2:44 PM
|
|
* Copyright (C) 2003, 2004, 2005, 2006 Aelitis, All Rights Reserved.
|
|
*
|
|
* This program 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 2
|
|
* of the License, or (at your option) any later version.
|
|
* This program 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* AELITIS, SAS au capital de 46,603.30 euros
|
|
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
|
|
*/
|
|
|
|
package net.sourceforge.filebot.torrent;
|
|
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.CharBuffer;
|
|
import java.nio.charset.Charset;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
|
|
/**
|
|
* A set of utility methods to decode a bencoded array of byte into a Map. integer are
|
|
* represented as Long, String as byte[], dictionnaries as Map, and list as List.
|
|
*
|
|
* @author TdC_VgA
|
|
*/
|
|
class BDecoder {
|
|
|
|
private static final Charset BINARY_CHARSET = Charset.forName("ISO-8859-1");
|
|
|
|
|
|
public static Map<?, ?> decode(InputStream is) throws IOException {
|
|
return (new BDecoder().decodeStream(is));
|
|
}
|
|
|
|
|
|
public Map<?, ?> decodeStream(InputStream data) throws IOException {
|
|
Object res = decodeInputStream(data, 0);
|
|
|
|
if (res == null)
|
|
throw (new IOException("BDecoder: zero length file"));
|
|
else if (!(res instanceof Map))
|
|
throw (new IOException("BDecoder: top level isn't a Map"));
|
|
|
|
return ((Map<?, ?>) res);
|
|
}
|
|
|
|
|
|
private Object decodeInputStream(InputStream bais, int nesting) throws IOException {
|
|
if (!bais.markSupported())
|
|
throw new IOException("InputStream must support the mark() method");
|
|
|
|
// set a mark
|
|
bais.mark(Integer.MAX_VALUE);
|
|
|
|
// read a byte
|
|
int tempByte = bais.read();
|
|
|
|
// decide what to do
|
|
switch (tempByte) {
|
|
case 'd':
|
|
// create a new dictionary object
|
|
Map<String, Object> tempMap = new HashMap<String, Object>();
|
|
|
|
// get the key
|
|
byte[] tempByteArray = null;
|
|
|
|
while ((tempByteArray = (byte[]) decodeInputStream(bais, nesting + 1)) != null) {
|
|
|
|
// decode some more
|
|
|
|
Object value = decodeInputStream(bais, nesting + 1);
|
|
|
|
// add the value to the map
|
|
|
|
CharBuffer cb = BINARY_CHARSET.decode(ByteBuffer.wrap(tempByteArray));
|
|
|
|
String key = new String(cb.array(), 0, cb.limit());
|
|
|
|
tempMap.put(key, value);
|
|
}
|
|
|
|
if (bais.available() < nesting)
|
|
throw (new IOException("BDecoder: invalid input data, 'e' missing from end of dictionary"));
|
|
|
|
// return the map
|
|
return tempMap;
|
|
|
|
case 'l':
|
|
// create the list
|
|
List<Object> tempList = new ArrayList<Object>();
|
|
|
|
// create the key
|
|
Object tempElement = null;
|
|
while ((tempElement = decodeInputStream(bais, nesting + 1)) != null)
|
|
// add the element
|
|
tempList.add(tempElement);
|
|
|
|
if (bais.available() < nesting)
|
|
throw (new IOException("BDecoder: invalid input data, 'e' missing from end of list"));
|
|
|
|
// return the list
|
|
return tempList;
|
|
|
|
case 'e':
|
|
case -1:
|
|
return null;
|
|
|
|
case 'i':
|
|
return new Long(getNumberFromStream(bais, 'e'));
|
|
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
// move back one
|
|
bais.reset();
|
|
// get the string
|
|
return getByteArrayFromStream(bais);
|
|
|
|
default: {
|
|
|
|
int rem_len = bais.available();
|
|
|
|
if (rem_len > 256)
|
|
rem_len = 256;
|
|
|
|
byte[] rem_data = new byte[rem_len];
|
|
|
|
bais.read(rem_data);
|
|
|
|
throw (new IOException("BDecoder: unknown command '" + tempByte + ", remainder = " + new String(rem_data)));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private long getNumberFromStream(InputStream bais, char parseChar) throws IOException {
|
|
int length = 0;
|
|
|
|
// place a mark
|
|
bais.mark(Integer.MAX_VALUE);
|
|
|
|
int tempByte = bais.read();
|
|
while ((tempByte != parseChar) && (tempByte >= 0)) {
|
|
tempByte = bais.read();
|
|
length++;
|
|
}
|
|
|
|
// are we at the end of the stream?
|
|
if (tempByte < 0)
|
|
return -1;
|
|
|
|
// reset the mark
|
|
bais.reset();
|
|
|
|
// get the length
|
|
byte[] tempArray = new byte[length];
|
|
int count = 0;
|
|
int len = 0;
|
|
|
|
// get the string
|
|
while ((count != length) && ((len = bais.read(tempArray, count, length - count)) > 0))
|
|
count += len;
|
|
|
|
// jump ahead in the stream to compensate for the :
|
|
bais.skip(1);
|
|
|
|
// return the value
|
|
CharBuffer cb = BINARY_CHARSET.decode(ByteBuffer.wrap(tempArray));
|
|
|
|
String str_value = new String(cb.array(), 0, cb.limit());
|
|
|
|
return Long.parseLong(str_value);
|
|
}
|
|
|
|
|
|
private byte[] getByteArrayFromStream(InputStream bais) throws IOException {
|
|
int length = (int) getNumberFromStream(bais, ':');
|
|
|
|
if (length < 0)
|
|
return null;
|
|
|
|
// note that torrent hashes can be big (consider a 55GB file with 2MB
|
|
// pieces
|
|
// this generates a pieces hash of 1/2 meg
|
|
if (length > 8 * 1024 * 1024)
|
|
throw (new IOException("Byte array length too large (" + length + ")"));
|
|
|
|
byte[] tempArray = new byte[length];
|
|
int count = 0;
|
|
int len = 0;
|
|
|
|
// get the string
|
|
while ((count != length) && ((len = bais.read(tempArray, count, length - count)) > 0))
|
|
count += len;
|
|
|
|
if (count != tempArray.length)
|
|
throw (new IOException("BDecoder::getByteArrayFromStream: truncated"));
|
|
|
|
return tempArray;
|
|
}
|
|
|
|
}
|