2005-10-19 22:02:37 +00:00
|
|
|
/* MetaInfo - Holds all information gotten from a torrent file.
|
|
|
|
Copyright (C) 2003 Mark J. Wielaard
|
|
|
|
|
|
|
|
This file is part of Snark.
|
|
|
|
|
|
|
|
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, 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package org.klomp.snark;
|
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
import java.io.FileInputStream;
|
2005-10-19 22:02:37 +00:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.security.MessageDigest;
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.util.ArrayList;
|
2010-12-21 03:04:10 +00:00
|
|
|
import java.util.Collections;
|
2008-07-16 13:42:54 +00:00
|
|
|
import java.util.HashMap;
|
2005-10-19 22:02:37 +00:00
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
|
2010-04-12 19:07:53 +00:00
|
|
|
import net.i2p.I2PAppContext;
|
2008-07-16 13:42:54 +00:00
|
|
|
import net.i2p.crypto.SHA1;
|
2005-12-13 09:38:51 +00:00
|
|
|
import net.i2p.data.Base64;
|
2011-03-08 03:01:02 +00:00
|
|
|
import net.i2p.data.DataHelper;
|
2005-12-13 09:38:51 +00:00
|
|
|
import net.i2p.util.Log;
|
2008-07-16 13:42:54 +00:00
|
|
|
|
|
|
|
import org.klomp.snark.bencode.BDecoder;
|
|
|
|
import org.klomp.snark.bencode.BEValue;
|
|
|
|
import org.klomp.snark.bencode.BEncoder;
|
|
|
|
import org.klomp.snark.bencode.InvalidBEncodingException;
|
2005-12-13 09:38:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Note: this class is buggy, as it doesn't propogate custom meta fields into the bencoded
|
|
|
|
* info data, and from there to the info_hash. At the moment, though, it seems to work with
|
|
|
|
* torrents created by I2P-BT, I2PRufus and Azureus.
|
|
|
|
*
|
|
|
|
*/
|
2005-10-19 22:02:37 +00:00
|
|
|
public class MetaInfo
|
2005-12-13 09:38:51 +00:00
|
|
|
{
|
2010-04-12 19:07:53 +00:00
|
|
|
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(MetaInfo.class);
|
2005-10-19 22:02:37 +00:00
|
|
|
private final String announce;
|
|
|
|
private final byte[] info_hash;
|
|
|
|
private final String name;
|
2005-12-13 09:38:51 +00:00
|
|
|
private final String name_utf8;
|
2010-12-22 22:22:38 +00:00
|
|
|
private final List<List<String>> files;
|
|
|
|
private final List<List<String>> files_utf8;
|
|
|
|
private final List<Long> lengths;
|
2005-10-19 22:02:37 +00:00
|
|
|
private final int piece_length;
|
|
|
|
private final byte[] piece_hashes;
|
|
|
|
private final long length;
|
2010-12-22 22:22:38 +00:00
|
|
|
private Map<String, BEValue> infoMap;
|
2005-10-19 22:02:37 +00:00
|
|
|
|
2010-12-21 03:04:10 +00:00
|
|
|
/**
|
|
|
|
* Called by Storage when creating a new torrent from local data
|
2010-12-21 16:36:08 +00:00
|
|
|
*
|
|
|
|
* @param announce may be null
|
2010-12-22 22:22:38 +00:00
|
|
|
* @param files null for single-file torrent
|
|
|
|
* @param lengths null for single-file torrent
|
2010-12-21 03:04:10 +00:00
|
|
|
*/
|
2010-12-22 22:22:38 +00:00
|
|
|
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
2005-10-19 22:02:37 +00:00
|
|
|
int piece_length, byte[] piece_hashes, long length)
|
|
|
|
{
|
|
|
|
this.announce = announce;
|
|
|
|
this.name = name;
|
2005-12-13 09:38:51 +00:00
|
|
|
this.name_utf8 = name_utf8;
|
2010-12-22 22:22:38 +00:00
|
|
|
this.files = files == null ? null : Collections.unmodifiableList(files);
|
2005-12-13 09:38:51 +00:00
|
|
|
this.files_utf8 = null;
|
2010-12-22 22:22:38 +00:00
|
|
|
this.lengths = lengths == null ? null : Collections.unmodifiableList(lengths);
|
2005-10-19 22:02:37 +00:00
|
|
|
this.piece_length = piece_length;
|
|
|
|
this.piece_hashes = piece_hashes;
|
|
|
|
this.length = length;
|
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
// TODO if we add a parameter for other keys
|
|
|
|
//if (other != null) {
|
|
|
|
// otherInfo = new HashMap(2);
|
|
|
|
// otherInfo.putAll(other);
|
|
|
|
//}
|
|
|
|
|
2005-10-19 22:02:37 +00:00
|
|
|
this.info_hash = calculateInfoHash();
|
2010-12-21 03:04:10 +00:00
|
|
|
//infoMap = null;
|
2005-10-19 22:02:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new MetaInfo from the given InputStream. The
|
|
|
|
* InputStream must start with a correctly bencoded dictonary
|
|
|
|
* describing the torrent.
|
2010-12-21 16:36:08 +00:00
|
|
|
* Caller must close the stream.
|
2005-10-19 22:02:37 +00:00
|
|
|
*/
|
|
|
|
public MetaInfo(InputStream in) throws IOException
|
|
|
|
{
|
|
|
|
this(new BDecoder(in));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new MetaInfo from the given BDecoder. The BDecoder
|
|
|
|
* must have a complete dictionary describing the torrent.
|
|
|
|
*/
|
2011-03-08 03:01:02 +00:00
|
|
|
private MetaInfo(BDecoder be) throws IOException
|
2005-10-19 22:02:37 +00:00
|
|
|
{
|
|
|
|
// Note that evaluation order matters here...
|
|
|
|
this(be.bdecodeMap().getMap());
|
2011-03-08 03:01:02 +00:00
|
|
|
byte[] origInfohash = be.get_special_map_digest();
|
|
|
|
// shouldn't ever happen
|
|
|
|
if (!DataHelper.eq(origInfohash, info_hash))
|
|
|
|
throw new InvalidBEncodingException("Infohash mismatch, please report");
|
2005-10-19 22:02:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new MetaInfo from a Map of BEValues and the SHA1 over
|
|
|
|
* the original bencoded info dictonary (this is a hack, we could
|
|
|
|
* reconstruct the bencoded stream and recalculate the hash). Will
|
2010-12-21 03:04:10 +00:00
|
|
|
* NOT throw a InvalidBEncodingException if the given map does not
|
2010-12-21 16:36:08 +00:00
|
|
|
* contain a valid announce string.
|
|
|
|
* WILL throw a InvalidBEncodingException if the given map does not
|
|
|
|
* contain a valid info dictionary.
|
2005-10-19 22:02:37 +00:00
|
|
|
*/
|
2011-03-08 03:01:02 +00:00
|
|
|
public MetaInfo(Map<String, BEValue> m) throws InvalidBEncodingException
|
2005-10-19 22:02:37 +00:00
|
|
|
{
|
2010-07-09 17:40:59 +00:00
|
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
|
|
_log.debug("Creating a metaInfo: " + m, new Exception("source"));
|
2011-03-08 03:01:02 +00:00
|
|
|
BEValue val = m.get("announce");
|
2010-12-21 03:04:10 +00:00
|
|
|
// Disabled check, we can get info from a magnet now
|
|
|
|
if (val == null) {
|
|
|
|
//throw new InvalidBEncodingException("Missing announce string");
|
|
|
|
this.announce = null;
|
|
|
|
} else {
|
|
|
|
this.announce = val.getString();
|
|
|
|
}
|
2005-10-19 22:02:37 +00:00
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
val = m.get("info");
|
2005-10-19 22:02:37 +00:00
|
|
|
if (val == null)
|
|
|
|
throw new InvalidBEncodingException("Missing info map");
|
2011-03-08 03:01:02 +00:00
|
|
|
Map<String, BEValue> info = val.getMap();
|
2010-12-22 22:22:38 +00:00
|
|
|
infoMap = Collections.unmodifiableMap(info);
|
2005-10-19 22:02:37 +00:00
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
val = info.get("name");
|
2005-10-19 22:02:37 +00:00
|
|
|
if (val == null)
|
|
|
|
throw new InvalidBEncodingException("Missing name string");
|
|
|
|
name = val.getString();
|
2011-03-08 03:01:02 +00:00
|
|
|
// We could silently replace the '/', but that messes up the info hash, so just throw instead.
|
|
|
|
if (name.indexOf('/') >= 0)
|
|
|
|
throw new InvalidBEncodingException("Invalid name containing '/' " + name);
|
2005-10-19 22:02:37 +00:00
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
val = info.get("name.utf-8");
|
2005-12-13 09:38:51 +00:00
|
|
|
if (val != null)
|
|
|
|
name_utf8 = val.getString();
|
|
|
|
else
|
|
|
|
name_utf8 = null;
|
2005-12-14 09:32:50 +00:00
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
val = info.get("piece length");
|
2005-10-19 22:02:37 +00:00
|
|
|
if (val == null)
|
|
|
|
throw new InvalidBEncodingException("Missing piece length number");
|
|
|
|
piece_length = val.getInt();
|
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
val = info.get("pieces");
|
2005-10-19 22:02:37 +00:00
|
|
|
if (val == null)
|
|
|
|
throw new InvalidBEncodingException("Missing piece bytes");
|
|
|
|
piece_hashes = val.getBytes();
|
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
val = info.get("length");
|
2005-10-19 22:02:37 +00:00
|
|
|
if (val != null)
|
|
|
|
{
|
|
|
|
// Single file case.
|
|
|
|
length = val.getLong();
|
|
|
|
files = null;
|
2005-12-13 09:38:51 +00:00
|
|
|
files_utf8 = null;
|
2005-10-19 22:02:37 +00:00
|
|
|
lengths = null;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Multi file case.
|
2011-03-08 03:01:02 +00:00
|
|
|
val = info.get("files");
|
2005-10-19 22:02:37 +00:00
|
|
|
if (val == null)
|
|
|
|
throw new InvalidBEncodingException
|
|
|
|
("Missing length number and/or files list");
|
|
|
|
|
2010-12-22 22:22:38 +00:00
|
|
|
List<BEValue> list = val.getList();
|
2005-10-19 22:02:37 +00:00
|
|
|
int size = list.size();
|
|
|
|
if (size == 0)
|
|
|
|
throw new InvalidBEncodingException("zero size files list");
|
|
|
|
|
2010-12-22 22:22:38 +00:00
|
|
|
List<List<String>> m_files = new ArrayList(size);
|
|
|
|
List<List<String>> m_files_utf8 = new ArrayList(size);
|
|
|
|
List<Long> m_lengths = new ArrayList(size);
|
2005-10-19 22:02:37 +00:00
|
|
|
long l = 0;
|
|
|
|
for (int i = 0; i < list.size(); i++)
|
|
|
|
{
|
2010-12-22 22:22:38 +00:00
|
|
|
Map<String, BEValue> desc = list.get(i).getMap();
|
|
|
|
val = desc.get("length");
|
2005-10-19 22:02:37 +00:00
|
|
|
if (val == null)
|
|
|
|
throw new InvalidBEncodingException("Missing length number");
|
|
|
|
long len = val.getLong();
|
2011-03-08 03:01:02 +00:00
|
|
|
if (len < 0)
|
|
|
|
throw new InvalidBEncodingException("Negative file length");
|
2011-01-10 17:14:34 +00:00
|
|
|
m_lengths.add(Long.valueOf(len));
|
2011-03-08 03:01:02 +00:00
|
|
|
// check for overflowing the long
|
|
|
|
long oldTotal = l;
|
2005-10-19 22:02:37 +00:00
|
|
|
l += len;
|
2011-03-08 03:01:02 +00:00
|
|
|
if (l < oldTotal)
|
|
|
|
throw new InvalidBEncodingException("Huge total length");
|
2005-10-19 22:02:37 +00:00
|
|
|
|
|
|
|
val = (BEValue)desc.get("path");
|
|
|
|
if (val == null)
|
|
|
|
throw new InvalidBEncodingException("Missing path list");
|
2010-12-22 22:22:38 +00:00
|
|
|
List<BEValue> path_list = val.getList();
|
2005-10-19 22:02:37 +00:00
|
|
|
int path_length = path_list.size();
|
|
|
|
if (path_length == 0)
|
|
|
|
throw new InvalidBEncodingException("zero size file path list");
|
|
|
|
|
2010-12-22 22:22:38 +00:00
|
|
|
List<String> file = new ArrayList(path_length);
|
|
|
|
Iterator<BEValue> it = path_list.iterator();
|
2011-03-08 03:01:02 +00:00
|
|
|
while (it.hasNext()) {
|
|
|
|
String s = it.next().getString();
|
|
|
|
// We could throw an IBEE, but just silently replace instead.
|
|
|
|
if (s.indexOf('/') >= 0)
|
|
|
|
s = s.replace("/", "_");
|
|
|
|
file.add(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
// quick dup check - case sensitive, etc. - Storage does a better job
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
|
|
if (file.equals(m_files.get(j)))
|
|
|
|
throw new InvalidBEncodingException("Duplicate file path " + DataHelper.toString(file));
|
|
|
|
}
|
2005-10-19 22:02:37 +00:00
|
|
|
|
2010-12-22 22:22:38 +00:00
|
|
|
m_files.add(Collections.unmodifiableList(file));
|
2005-12-13 09:38:51 +00:00
|
|
|
|
|
|
|
val = (BEValue)desc.get("path.utf-8");
|
|
|
|
if (val != null) {
|
|
|
|
path_list = val.getList();
|
|
|
|
path_length = path_list.size();
|
|
|
|
if (path_length > 0) {
|
|
|
|
file = new ArrayList(path_length);
|
|
|
|
it = path_list.iterator();
|
|
|
|
while (it.hasNext())
|
2010-12-22 22:22:38 +00:00
|
|
|
file.add(it.next().getString());
|
|
|
|
m_files_utf8.add(Collections.unmodifiableList(file));
|
2005-12-13 09:38:51 +00:00
|
|
|
}
|
|
|
|
}
|
2005-10-19 22:02:37 +00:00
|
|
|
}
|
2010-12-22 22:22:38 +00:00
|
|
|
files = Collections.unmodifiableList(m_files);
|
|
|
|
files_utf8 = Collections.unmodifiableList(m_files_utf8);
|
|
|
|
lengths = Collections.unmodifiableList(m_lengths);
|
2005-10-19 22:02:37 +00:00
|
|
|
length = l;
|
|
|
|
}
|
|
|
|
|
|
|
|
info_hash = calculateInfoHash();
|
|
|
|
}
|
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
/**
|
|
|
|
* Efficiently returns the name and the 20 byte SHA1 hash of the info dictionary in a torrent file
|
|
|
|
* Caller must close stream.
|
|
|
|
*
|
|
|
|
* @param infoHashOut 20-byte out parameter
|
|
|
|
* @since 0.8.5
|
|
|
|
*/
|
|
|
|
public static String getNameAndInfoHash(InputStream in, byte[] infoHashOut) throws IOException {
|
|
|
|
BDecoder bd = new BDecoder(in);
|
|
|
|
Map<String, BEValue> m = bd.bdecodeMap().getMap();
|
|
|
|
BEValue ibev = m.get("info");
|
|
|
|
if (ibev == null)
|
|
|
|
throw new InvalidBEncodingException("Missing info map");
|
|
|
|
Map<String, BEValue> i = ibev.getMap();
|
|
|
|
BEValue rvbev = i.get("name");
|
|
|
|
if (rvbev == null)
|
|
|
|
throw new InvalidBEncodingException("Missing name");
|
|
|
|
byte[] h = bd.get_special_map_digest();
|
|
|
|
System.arraycopy(h, 0, infoHashOut, 0, 20);
|
|
|
|
return rvbev.getString();
|
|
|
|
}
|
|
|
|
|
2005-10-19 22:02:37 +00:00
|
|
|
/**
|
|
|
|
* Returns the string representing the URL of the tracker for this torrent.
|
2010-12-21 03:04:10 +00:00
|
|
|
* @return may be null!
|
2005-10-19 22:02:37 +00:00
|
|
|
*/
|
|
|
|
public String getAnnounce()
|
|
|
|
{
|
|
|
|
return announce;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the original 20 byte SHA1 hash over the bencoded info map.
|
|
|
|
*/
|
|
|
|
public byte[] getInfoHash()
|
|
|
|
{
|
|
|
|
// XXX - Should we return a clone, just to be sure?
|
|
|
|
return info_hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the piece hashes. Only used by storage so package local.
|
|
|
|
*/
|
|
|
|
byte[] getPieceHashes()
|
|
|
|
{
|
|
|
|
return piece_hashes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the requested name for the file or toplevel directory.
|
|
|
|
* If it is a toplevel directory name getFiles() will return a
|
|
|
|
* non-null List of file name hierarchy name.
|
|
|
|
*/
|
|
|
|
public String getName()
|
|
|
|
{
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of lists of file name hierarchies or null if it is
|
|
|
|
* a single name. It has the same size as the list returned by
|
|
|
|
* getLengths().
|
|
|
|
*/
|
2010-12-22 22:22:38 +00:00
|
|
|
public List<List<String>> getFiles()
|
2005-10-19 22:02:37 +00:00
|
|
|
{
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of Longs indication the size of the individual
|
|
|
|
* files, or null if it is a single file. It has the same size as
|
|
|
|
* the list returned by getFiles().
|
|
|
|
*/
|
2010-12-22 22:22:38 +00:00
|
|
|
public List<Long> getLengths()
|
2005-10-19 22:02:37 +00:00
|
|
|
{
|
|
|
|
return lengths;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the number of pieces.
|
|
|
|
*/
|
|
|
|
public int getPieces()
|
|
|
|
{
|
|
|
|
return piece_hashes.length/20;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the length of a piece. All pieces are of equal length
|
|
|
|
* except for the last one (<code>getPieces()-1</code>).
|
|
|
|
*
|
|
|
|
* @exception IndexOutOfBoundsException when piece is equal to or
|
|
|
|
* greater then the number of pieces in the torrent.
|
|
|
|
*/
|
|
|
|
public int getPieceLength(int piece)
|
|
|
|
{
|
|
|
|
int pieces = getPieces();
|
|
|
|
if (piece >= 0 && piece < pieces -1)
|
|
|
|
return piece_length;
|
|
|
|
else if (piece == pieces -1)
|
2010-12-26 13:17:12 +00:00
|
|
|
return (int)(length - ((long)piece * piece_length));
|
2005-10-19 22:02:37 +00:00
|
|
|
else
|
|
|
|
throw new IndexOutOfBoundsException("no piece: " + piece);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks that the given piece has the same SHA1 hash as the given
|
|
|
|
* byte array. Returns random results or IndexOutOfBoundsExceptions
|
|
|
|
* when the piece number is unknown.
|
|
|
|
*/
|
|
|
|
public boolean checkPiece(int piece, byte[] bs, int off, int length)
|
|
|
|
{
|
2011-03-08 03:01:02 +00:00
|
|
|
//if (true)
|
2005-12-17 09:22:07 +00:00
|
|
|
return fast_checkPiece(piece, bs, off, length);
|
2011-03-08 03:01:02 +00:00
|
|
|
//else
|
|
|
|
// return orig_checkPiece(piece, bs, off, length);
|
2005-12-17 09:22:07 +00:00
|
|
|
}
|
2011-03-08 03:01:02 +00:00
|
|
|
|
|
|
|
/****
|
2005-12-17 09:22:07 +00:00
|
|
|
private boolean orig_checkPiece(int piece, byte[] bs, int off, int length) {
|
2005-10-19 22:02:37 +00:00
|
|
|
// Check digest
|
|
|
|
MessageDigest sha1;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
sha1 = MessageDigest.getInstance("SHA");
|
|
|
|
}
|
|
|
|
catch (NoSuchAlgorithmException nsae)
|
|
|
|
{
|
|
|
|
throw new InternalError("No SHA digest available: " + nsae);
|
|
|
|
}
|
|
|
|
|
|
|
|
sha1.update(bs, off, length);
|
|
|
|
byte[] hash = sha1.digest();
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
|
|
if (hash[i] != piece_hashes[20 * piece + i])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2011-03-08 03:01:02 +00:00
|
|
|
****/
|
2005-12-17 09:22:07 +00:00
|
|
|
|
|
|
|
private boolean fast_checkPiece(int piece, byte[] bs, int off, int length) {
|
|
|
|
SHA1 sha1 = new SHA1();
|
|
|
|
|
|
|
|
sha1.update(bs, off, length);
|
|
|
|
byte[] hash = sha1.digest();
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
|
|
if (hash[i] != piece_hashes[20 * piece + i])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2005-10-19 22:02:37 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the total length of the torrent in bytes.
|
|
|
|
*/
|
|
|
|
public long getTotalLength()
|
|
|
|
{
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2009-08-11 21:58:56 +00:00
|
|
|
@Override
|
2005-10-19 22:02:37 +00:00
|
|
|
public String toString()
|
|
|
|
{
|
2011-03-08 03:01:02 +00:00
|
|
|
return "MetaInfo[info_hash='" + I2PSnarkUtil.toHex(info_hash)
|
2005-10-19 22:02:37 +00:00
|
|
|
+ "', announce='" + announce
|
|
|
|
+ "', name='" + name
|
|
|
|
+ "', files=" + files
|
|
|
|
+ ", #pieces='" + piece_hashes.length/20
|
|
|
|
+ "', piece_length='" + piece_length
|
|
|
|
+ "', length='" + length
|
|
|
|
+ "']";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a copy of this MetaInfo that shares everything except the
|
|
|
|
* announce URL.
|
|
|
|
*/
|
|
|
|
public MetaInfo reannounce(String announce)
|
|
|
|
{
|
2005-12-13 09:38:51 +00:00
|
|
|
return new MetaInfo(announce, name, name_utf8, files,
|
2005-10-19 22:02:37 +00:00
|
|
|
lengths, piece_length,
|
|
|
|
piece_hashes, length);
|
|
|
|
}
|
|
|
|
|
2010-12-21 03:04:10 +00:00
|
|
|
/**
|
|
|
|
* Called by servlet to save a new torrent file generated from local data
|
|
|
|
*/
|
|
|
|
public synchronized byte[] getTorrentData()
|
2005-10-19 22:02:37 +00:00
|
|
|
{
|
|
|
|
Map m = new HashMap();
|
2010-12-21 16:36:08 +00:00
|
|
|
if (announce != null)
|
|
|
|
m.put("announce", announce);
|
2005-10-19 22:02:37 +00:00
|
|
|
Map info = createInfoMap();
|
|
|
|
m.put("info", info);
|
2010-12-21 03:04:10 +00:00
|
|
|
// don't save this locally, we should only do this once
|
|
|
|
return BEncoder.bencode(m);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @since 0.8.4 */
|
|
|
|
public synchronized byte[] getInfoBytes() {
|
|
|
|
if (infoMap == null)
|
|
|
|
createInfoMap();
|
|
|
|
return BEncoder.bencode(infoMap);
|
2005-10-19 22:02:37 +00:00
|
|
|
}
|
|
|
|
|
2010-12-21 03:04:10 +00:00
|
|
|
/** @return an unmodifiable view of the Map */
|
|
|
|
private Map<String, BEValue> createInfoMap()
|
2005-10-19 22:02:37 +00:00
|
|
|
{
|
2011-03-08 03:01:02 +00:00
|
|
|
// If we loaded this metainfo from a file, we have the map, and we must use it
|
|
|
|
// or else we will lose any non-standard keys and corrupt the infohash.
|
2010-12-21 03:04:10 +00:00
|
|
|
if (infoMap != null)
|
|
|
|
return Collections.unmodifiableMap(infoMap);
|
|
|
|
// otherwise we must create it
|
2005-10-19 22:02:37 +00:00
|
|
|
Map info = new HashMap();
|
|
|
|
info.put("name", name);
|
2005-12-13 09:38:51 +00:00
|
|
|
if (name_utf8 != null)
|
|
|
|
info.put("name.utf-8", name_utf8);
|
2008-10-26 18:18:34 +00:00
|
|
|
info.put("piece length", Integer.valueOf(piece_length));
|
2005-10-19 22:02:37 +00:00
|
|
|
info.put("pieces", piece_hashes);
|
|
|
|
if (files == null)
|
2011-01-10 17:14:34 +00:00
|
|
|
info.put("length", Long.valueOf(length));
|
2005-10-19 22:02:37 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
List l = new ArrayList();
|
|
|
|
for (int i = 0; i < files.size(); i++)
|
|
|
|
{
|
|
|
|
Map file = new HashMap();
|
|
|
|
file.put("path", files.get(i));
|
2005-12-13 09:38:51 +00:00
|
|
|
if ( (files_utf8 != null) && (files_utf8.size() > i) )
|
|
|
|
file.put("path.utf-8", files_utf8.get(i));
|
2005-10-19 22:02:37 +00:00
|
|
|
file.put("length", lengths.get(i));
|
|
|
|
l.add(file);
|
|
|
|
}
|
|
|
|
info.put("files", l);
|
|
|
|
}
|
2011-03-08 03:01:02 +00:00
|
|
|
|
|
|
|
// TODO if we add the ability for other keys in the first constructor
|
|
|
|
//if (otherInfo != null)
|
|
|
|
// info.putAll(otherInfo);
|
|
|
|
|
2010-12-21 03:04:10 +00:00
|
|
|
infoMap = info;
|
|
|
|
return Collections.unmodifiableMap(infoMap);
|
2005-10-19 22:02:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private byte[] calculateInfoHash()
|
|
|
|
{
|
2011-03-08 03:01:02 +00:00
|
|
|
Map<String, BEValue> info = createInfoMap();
|
|
|
|
if (_log.shouldLog(Log.DEBUG)) {
|
|
|
|
StringBuilder buf = new StringBuilder(128);
|
|
|
|
buf.append("info: ");
|
|
|
|
for (Map.Entry<String, BEValue> entry : info.entrySet()) {
|
|
|
|
String key = entry.getKey();
|
|
|
|
Object val = entry.getValue();
|
|
|
|
buf.append(key).append('=');
|
2005-12-13 09:38:51 +00:00
|
|
|
buf.append(val.toString());
|
2011-03-08 03:01:02 +00:00
|
|
|
}
|
2010-07-09 17:40:59 +00:00
|
|
|
_log.debug(buf.toString());
|
2011-03-08 03:01:02 +00:00
|
|
|
}
|
2005-10-19 22:02:37 +00:00
|
|
|
byte[] infoBytes = BEncoder.bencode(info);
|
2005-12-19 13:34:52 +00:00
|
|
|
//_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]");
|
2005-10-19 22:02:37 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
MessageDigest digest = MessageDigest.getInstance("SHA");
|
2005-12-13 09:38:51 +00:00
|
|
|
byte hash[] = digest.digest(infoBytes);
|
2010-07-09 17:40:59 +00:00
|
|
|
if (_log.shouldLog(Log.DEBUG))
|
2011-03-08 03:01:02 +00:00
|
|
|
_log.debug("info hash: " + I2PSnarkUtil.toHex(hash));
|
2005-12-13 09:38:51 +00:00
|
|
|
return hash;
|
2005-10-19 22:02:37 +00:00
|
|
|
}
|
|
|
|
catch(NoSuchAlgorithmException nsa)
|
|
|
|
{
|
|
|
|
throw new InternalError(nsa.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-08 03:01:02 +00:00
|
|
|
/** @since 0.8.5 */
|
|
|
|
public static void main(String[] args) {
|
|
|
|
if (args.length <= 0) {
|
|
|
|
System.err.println("Usage: MetaInfo files...");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < args.length; i++) {
|
|
|
|
InputStream in = null;
|
|
|
|
try {
|
|
|
|
in = new FileInputStream(args[i]);
|
|
|
|
MetaInfo meta = new MetaInfo(in);
|
|
|
|
System.out.println(args[i] + " InfoHash: " + I2PSnarkUtil.toHex(meta.getInfoHash()));
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
System.err.println("Error in file " + args[i] + ": " + ioe);
|
|
|
|
} finally {
|
|
|
|
try { if (in != null) in.close(); } catch (IOException ioe) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2005-10-19 22:02:37 +00:00
|
|
|
}
|