All Downloads are FREE. Search and download functionalities are using the official Maven repository.

christophedetroyer.torrent.TorrentParser Maven / Gradle / Ivy

package christophedetroyer.torrent;

import christophedetroyer.bencoding.Reader;
import christophedetroyer.bencoding.Utils;
import christophedetroyer.bencoding.types.*;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;

/**
 * Created by christophe on 17.01.15.
 */
public class TorrentParser
{
    public static Torrent parseTorrent(String filePath) throws IOException
    {
        Reader r = new Reader(new File(filePath));
        List x = r.read();
        // A valid torrentfile should only return a single dictionary.
        if (x.size() != 1)
            throw new Error("Parsing .torrent yielded wrong number of bencoding structs.");
        try
        {
            return parseTorrent(x.get(0));
        } catch (ParseException e)
        {
            System.err.println("Error parsing torrent!");
        }
        return null;
    }

    private static Torrent parseTorrent(Object o) throws ParseException
    {
        if (o instanceof BDictionary)
        {
            BDictionary torrentDictionary = (BDictionary) o;
            BDictionary infoDictionary = parseInfoDictionary(torrentDictionary);

            Torrent t = new Torrent();

            ///////////////////////////////////
            //// OBLIGATED FIELDS /////////////
            ///////////////////////////////////
            t.setAnnounce(parseAnnounce(torrentDictionary));
            t.setInfo_hash(Utils.SHAsum(infoDictionary.bencode()));
            t.setName(parseTorrentLocation(infoDictionary));
            t.setPieceLength( parsePieceLength(infoDictionary));
            t.setPieces(parsePiecesHashes(infoDictionary));
            t.setPiecesBlob(parsePiecesBlob(infoDictionary));

            ///////////////////////////////////
            //// OPTIONAL FIELDS //////////////
            ///////////////////////////////////
            t.setFileList(parseFileList(infoDictionary));
            t.setComment(parseComment(torrentDictionary));
            t.setCreatedBy(parseCreatorName(torrentDictionary));
            t.setCreationDate(parseCreationDate(torrentDictionary));
            t.setAnnounceList(parseAnnounceList(torrentDictionary));
            t.setTotalSize(parseSingleFileTotalSize(infoDictionary));

            // Determine if torrent is a singlefile torrent.
            t.setSingleFileTorrent(null != infoDictionary.find(new BByteString("length")));
            return t;
        } else
        {
            throw new ParseException("Could not parse Object to BDictionary", 0);
        }
    }

    /**
     * @param info info dictionary
     * @return length — size of the file in bytes (only when one file is being shared)
     */
    private static Long parseSingleFileTotalSize(BDictionary info)
    {
        if (null != info.find(new BByteString("length")))
            return ((BInt) info.find(new BByteString("length"))).getValue();
        return null;
    }

    /**
     * @param dictionary root dictionary of torrent
     * @return info — this maps to a dictionary whose keys are dependent on whether
     * one or more files are being shared.
     */
    private static BDictionary parseInfoDictionary(BDictionary dictionary)
    {
        if (null != dictionary.find(new BByteString("info")))
            return (BDictionary) dictionary.find(new BByteString("info"));
        else
            return null;
    }

    /**
     * @param dictionary root dictionary of torrent
     * @return creation date: (optional) the creation time of the torrent, in standard UNIX epoch format
     * (integer, seconds since 1-Jan-1970 00:00:00 UTC)
     */
    private static Date parseCreationDate(BDictionary dictionary)
    {
        if (null != dictionary.find(new BByteString("creation date")))
            return new Date(Long.parseLong(dictionary.find(new BByteString("creation date")).toString()));
        return null;
    }

    /**
     * @param dictionary root dictionary of torrent
     * @return created by: (optional) name and version of the program used to create the .torrent (string)
     */
    private static String parseCreatorName(BDictionary dictionary)
    {
        if (null != dictionary.find(new BByteString("created by")))
            return dictionary.find(new BByteString("created by")).toString();
        return null;
    }

    /**
     * @param dictionary root dictionary of torrent
     * @return comment: (optional) free-form textual comments of the author (string)
     */
    private static String parseComment(BDictionary dictionary)
    {
        if (null != dictionary.find(new BByteString("comment")))
            return dictionary.find(new BByteString("comment")).toString();
        else
            return null;
    }

    /**
     * @param info infodictionary of torrent
     * @return piece length — number of bytes per piece. This is commonly 28 KiB = 256 KiB = 262,144 B.
     */
    private static Long parsePieceLength(BDictionary info)
    {
        if (null != info.find(new BByteString("piece length")))
            return ((BInt) info.find(new BByteString("piece length"))).getValue();
        else
            return null;
    }

    /**
     * @param info info dictionary of torrent
     * @return name — suggested filename where the file is to be saved (if one file)/suggested directory name
     * where the files are to be saved (if multiple files)
     */
    private static String parseTorrentLocation(BDictionary info)
    {
        if (null != info.find(new BByteString("name")))
            return info.find(new BByteString("name")).toString();
        else
            return null;
    }

    /**
     * @param dictionary root dictionary of torrent
     * @return announce — the URL of the tracke
     */
    private static String parseAnnounce(BDictionary dictionary)
    {
        if (null != dictionary.find(new BByteString("announce")))
            return dictionary.find(new BByteString("announce")).toString();
        else
            return null;
    }

    /**
     * @param info info dictionary of .torrent file.
     * @return pieces — a hash list, i.e., a concatenation of each piece's SHA-1 hash. As SHA-1 returns a 160-bit hash,
     * pieces will be a string whose length is a multiple of 160-bits.
     */
    private static byte[] parsePiecesBlob(BDictionary info)
    {
        if (null != info.find(new BByteString("pieces")))
        {
            return ((BByteString) info.find(new BByteString("pieces"))).getData();
        } else
        {
            throw new Error("Info dictionary does not contain pieces bytestring!");
        }
    }

    /**
     * @param info info dictionary of .torrent file.
     * @return pieces — a hash list, i.e., a concatenation of each piece's SHA-1 hash. As SHA-1 returns a 160-bit hash,
     * pieces will be a string whose length is a multiple of 160-bits.
     */
    private static List parsePiecesHashes(BDictionary info)
    {
        if (null != info.find(new BByteString("pieces")))
        {
            List sha1HexRenders = new ArrayList();
            byte[] piecesBlob = ((BByteString) info.find(new BByteString("pieces"))).getData();
            // Split the piecesData into multiple hashes. 1 hash = 20 bytes.
            if (piecesBlob.length % 20 == 0)
            {
                int hashCount = piecesBlob.length / 20;
                for (int currHash = 0; currHash < hashCount; currHash++)
                {
                    byte[] currHashByteBlob = Arrays.copyOfRange(piecesBlob, 20 * currHash, (20 * (currHash + 1)));
                    String sha1 = Utils.bytesToHex(currHashByteBlob);
                    sha1HexRenders.add(sha1);
                }
            } else
            {
                throw new Error("Error parsing SHA1 piece hashes. Bytecount was not a multiple of 20.");
            }
            return sha1HexRenders;
        } else
        {
            throw new Error("Info dictionary does not contain pieces bytestring!");
        }
    }

    /**
     * @param info info dictionary of torrent
     * @return files — a list of dictionaries each corresponding to a file (only when multiple files are being shared).
     */
    private static List parseFileList(BDictionary info)
    {
        if (null != info.find(new BByteString("files")))
        {
            List fileList = new ArrayList();
            BList filesBList = (BList) info.find(new BByteString("files"));

            Iterator fileBDicts = filesBList.getIterator();
            while (fileBDicts.hasNext())
            {
                Object fileObject = fileBDicts.next();
                if (fileObject instanceof BDictionary)
                {
                    BDictionary fileBDict = (BDictionary) fileObject;
                    BList filePaths = (BList) fileBDict.find(new BByteString("path"));
                    BInt fileLength = (BInt) fileBDict.find(new BByteString("length"));
                    // Pick out each subdirectory as a string.
                    List paths = new LinkedList();
                    Iterator filePathsIterator = filePaths.getIterator();
                    while (filePathsIterator.hasNext())
                        paths.add(filePathsIterator.next().toString());

                    fileList.add(new TorrentFile(fileLength.getValue(), paths));
                }
            }
            return fileList;
        }
        return null;
    }

    /**
     * @param dictionary root dictionary of torrent
     * @return announce-list: (optional) this is an extention to the official specification, offering
     * backwards-compatibility. (list of lists of strings).
     */
    private static List parseAnnounceList(BDictionary dictionary)
    {
        if (null != dictionary.find(new BByteString("announce-list")))
        {
            List announceUrls = new LinkedList();

            BList announceList = (BList) dictionary.find(new BByteString("announce-list"));
            Iterator subLists = announceList.getIterator();
            while (subLists.hasNext())
            {
                BList subList = (BList) subLists.next();
                Iterator elements = subList.getIterator();
                while (elements.hasNext())
                {
                    // Assume that each element is a BByteString
                    BByteString tracker = (BByteString) elements.next();
                    announceUrls.add(tracker.toString());
                }
            }
            return announceUrls;
        } else
        {
            return null;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy