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