mirror of
https://github.com/mitb-archive/filebot
synced 2025-03-09 05:51:31 -04:00
Refactor bencode / torrent parser
This commit is contained in:
parent
a63556e3f4
commit
3f3643ad71
1
ivy.xml
1
ivy.xml
@ -27,6 +27,7 @@
|
||||
<dependency rev="9.20-2.00beta" org="net.sf.sevenzipjbinding" name="sevenzipjbinding" />
|
||||
<dependency rev="9.20-2.00beta" org="net.sf.sevenzipjbinding" name="sevenzipjbinding-all-platforms" />
|
||||
<dependency rev="0.6" org="com.optimaize.languagedetector" name="language-detector" />
|
||||
<dependency rev="1.2.1" org="com.dampcake" name="bencode" />
|
||||
<dependency rev="0.6.7" org="one.util" name="streamex" />
|
||||
<dependency rev="3.0.1" org="com.googlecode.lanterna" name="lanterna" />
|
||||
<dependency rev="2.11.0" org="com.drewnoakes" name="metadata-extractor" />
|
||||
|
@ -17,6 +17,7 @@ ivy/jar/junrar.jar
|
||||
ivy/jar/jna.jar
|
||||
ivy/jar/jna-platform.jar
|
||||
ivy/jar/simmetrics-core.jar
|
||||
ivy/jar/bencode.jar
|
||||
ivy/jar/streamex.jar
|
||||
ivy/jar/icu4j.jar
|
||||
ivy/jar/language-detector.jar
|
||||
|
@ -1,217 +0,0 @@
|
||||
/*
|
||||
* 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.filebot.torrent;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
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 {
|
||||
|
||||
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 = ISO_8859_1.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 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 = ISO_8859_1.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;
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
package net.filebot.torrent;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.*;
|
||||
import static java.util.Collections.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.dampcake.bencode.Bencode;
|
||||
import com.dampcake.bencode.Type;
|
||||
|
||||
import net.filebot.vfs.FileInfo;
|
||||
import net.filebot.vfs.SimpleFileInfo;
|
||||
|
||||
@ -39,87 +37,62 @@ public class Torrent {
|
||||
}
|
||||
|
||||
public Torrent(Map<?, ?> torrentMap) {
|
||||
Charset charset = UTF_8;
|
||||
encoding = decodeString(torrentMap.get("encoding"), charset);
|
||||
createdBy = getString(torrentMap.get("created by"));
|
||||
announce = getString(torrentMap.get("announce"));
|
||||
comment = getString(torrentMap.get("comment"));
|
||||
creationDate = getLong(torrentMap.get("creation date"));
|
||||
|
||||
try {
|
||||
charset = Charset.forName(encoding);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// invalid encoding, just keep using UTF-8
|
||||
}
|
||||
Map<?, ?> info = getMap(torrentMap.get("info"));
|
||||
name = getString(info.get("name"));
|
||||
pieceLength = getLong(info.get("piece length"));
|
||||
|
||||
createdBy = decodeString(torrentMap.get("created by"), charset);
|
||||
announce = decodeString(torrentMap.get("announce"), charset);
|
||||
comment = decodeString(torrentMap.get("comment"), charset);
|
||||
creationDate = decodeLong(torrentMap.get("creation date"));
|
||||
|
||||
Map<?, ?> infoMap = (Map<?, ?>) torrentMap.get("info");
|
||||
|
||||
name = decodeString(infoMap.get("name"), charset);
|
||||
pieceLength = (Long) infoMap.get("piece length");
|
||||
|
||||
if (infoMap.containsKey("files")) {
|
||||
if (info.containsKey("files")) {
|
||||
// torrent contains multiple entries
|
||||
singleFileTorrent = false;
|
||||
|
||||
List<FileInfo> entries = new ArrayList<FileInfo>();
|
||||
|
||||
for (Object fileMapObject : (List<?>) infoMap.get("files")) {
|
||||
Map<?, ?> fileMap = (Map<?, ?>) fileMapObject;
|
||||
List<?> pathList = (List<?>) fileMap.get("path");
|
||||
|
||||
StringBuilder path = new StringBuilder(80);
|
||||
|
||||
Iterator<?> iterator = pathList.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
// append path element
|
||||
path.append(decodeString(iterator.next(), charset));
|
||||
|
||||
// append separator
|
||||
if (iterator.hasNext()) {
|
||||
path.append("/");
|
||||
}
|
||||
}
|
||||
|
||||
Long length = decodeLong(fileMap.get("length"));
|
||||
|
||||
entries.add(new SimpleFileInfo(path.toString(), length));
|
||||
}
|
||||
|
||||
files = unmodifiableList(entries);
|
||||
files = getList(info.get("files")).stream().map(this::getMap).map(f -> {
|
||||
String path = getList(f.get("path")).stream().map(Object::toString).collect(joining("/"));
|
||||
long length = getLong(f.get("length"));
|
||||
return new SimpleFileInfo(path, length);
|
||||
}).collect(toList());
|
||||
} else {
|
||||
// single file torrent
|
||||
// torrent contains only a single entry
|
||||
singleFileTorrent = true;
|
||||
|
||||
Long length = decodeLong(infoMap.get("length"));
|
||||
|
||||
files = singletonList(new SimpleFileInfo(name, length));
|
||||
files = singletonList(new SimpleFileInfo(name, getLong(info.get("length"))));
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<?, ?> decodeTorrent(File torrent) throws IOException {
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(torrent));
|
||||
byte[] bytes = Files.readAllBytes(torrent.toPath());
|
||||
|
||||
try {
|
||||
return BDecoder.decode(in);
|
||||
} finally {
|
||||
in.close();
|
||||
return new Bencode().decode(bytes, Type.DICTIONARY);
|
||||
}
|
||||
|
||||
private String getString(Object value) {
|
||||
if (value instanceof CharSequence) {
|
||||
return value.toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String decodeString(Object byteArray, Charset charset) {
|
||||
if (byteArray == null)
|
||||
return null;
|
||||
|
||||
return new String((byte[]) byteArray, charset);
|
||||
private long getLong(Object value) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private Long decodeLong(Object number) {
|
||||
if (number == null)
|
||||
return null;
|
||||
private Map<?, ?> getMap(Object value) {
|
||||
if (value instanceof Map) {
|
||||
return (Map) value;
|
||||
}
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
return (Long) number;
|
||||
private List<?> getList(Object value) {
|
||||
if (value instanceof List) {
|
||||
return (List) value;
|
||||
}
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
public String getAnnounce() {
|
||||
@ -143,7 +116,7 @@ public class Torrent {
|
||||
}
|
||||
|
||||
public List<FileInfo> getFiles() {
|
||||
return files;
|
||||
return unmodifiableList(files);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user