You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ktorrent/libktorrent/torrent/torrent.cpp

450 lines
12 KiB

/***************************************************************************
* Copyright (C) 2005 by *
* Joris Guisson <joris.guisson@gmail.com> *
* Ivan Vasic <ivasic@gmail.com> *
* *
* 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., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include <tqfile.h>
#include <tqdatastream.h>
#include <tqstringlist.h>
#include <util/log.h>
#include <util/functions.h>
#include <util/error.h>
#include <util/sha1hashgen.h>
#include <time.h>
#include <stdlib.h>
#include "torrent.h"
#include "bdecoder.h"
#include "bnode.h"
#include "announcelist.h"
#include <klocale.h>
namespace bt
{
Torrent::Torrent() : piece_length(0),file_length(0),priv_torrent(false)
{
encoding = "utf8";
trackers = 0;
}
Torrent::~Torrent()
{
delete trackers;
}
void Torrent::load(const TQByteArray & data,bool verbose)
{
BNode* node = 0;
try
{
BDecoder decoder(data,verbose);
node = decoder.decode();
BDictNode* dict = dynamic_cast<BDictNode*>(node);
if (!dict)
throw Error(i18n("Corrupted torrent!"));
// see if we can find an encoding node
BValueNode* enc = dict->getValue("encoding");
if (enc)
{
encoding = enc->data().toString();
Out() << "Encoding : " << encoding << endl;
}
BValueNode* announce = dict->getValue("announce");
BListNode* nodes = dict->getList("nodes");
if (!announce && !nodes)
throw Error(i18n("Torrent has no announce or nodes field"));
if (announce)
loadTrackerURL(announce);
if (nodes) // DHT torrrents have a node key
loadNodes(nodes);
loadInfo(dict->getDict(TQString("info")));
loadAnnounceList(dict->getData("announce-list"));
BNode* n = dict->getData("info");
SHA1HashGen hg;
Uint8* info = (Uint8*)data.data();
info_hash = hg.generate(info + n->getOffset(),n->getLength());
delete node;
}
catch (...)
{
delete node;
throw;
}
}
void Torrent::load(const TQString & file,bool verbose)
{
TQFile fptr(file);
if (!fptr.open(IO_ReadOnly))
throw Error(i18n(" Unable to open torrent file %1 : %2")
.arg(file).arg(fptr.errorString()));
TQByteArray data(fptr.size());
// Out() << "File size = " << fptr.size() << endl;
fptr.readBlock(data.data(),fptr.size());
load(data,verbose);
}
void Torrent::loadInfo(BDictNode* dict)
{
if (!dict)
throw Error(i18n("Corrupted torrent!"));
loadPieceLength(dict->getValue("piece length"));
BValueNode* n = dict->getValue("length");
if (n)
loadFileLength(n);
else
loadFiles(dict->getList("files"));
loadHash(dict->getValue("pieces"));
loadName(dict->getValue("name"));
n = dict->getValue("private");
if (n && n->data().toInt() == 1)
priv_torrent = true;
// do a safety check to see if the number of hashes matches the file_length
Uint32 num_chunks = (file_length / this->piece_length);
if (file_length % piece_length > 0)
num_chunks++;
if (num_chunks != hash_pieces.count())
{
Out(SYS_GEN|LOG_DEBUG) << "File sizes and number of hashes do not match for " << name_suggestion << endl;
throw Error(i18n("Corrupted torrent!"));
}
}
void Torrent::loadFiles(BListNode* node)
{
Out() << "Multi file torrent" << endl;
if (!node)
throw Error(i18n("Corrupted torrent!"));
Uint32 idx = 0;
BListNode* fl = node;
for (Uint32 i = 0;i < fl->getNumChildren();i++)
{
BDictNode* d = fl->getDict(i);
if (!d)
throw Error(i18n("Corrupted torrent!"));
BListNode* ln = d->getList("path");
if (!ln)
throw Error(i18n("Corrupted torrent!"));
TQString path;
for (Uint32 j = 0;j < ln->getNumChildren();j++)
{
BValueNode* v = ln->getValue(j);
if (!v || v->data().getType() != Value::STRING)
throw Error(i18n("Corrupted torrent!"));
TQString sd = v->data().toString(encoding);
path += sd;
if (j + 1 < ln->getNumChildren())
path += bt::DirSeparator();
}
// we do not want empty dirs
if (path.endsWith(bt::DirSeparator()))
continue;
if (!checkPathForDirectoryTraversal(path))
throw Error(i18n("Corrupted torrent!"));
BValueNode* v = d->getValue("length");
if (!v)
throw Error(i18n("Corrupted torrent!"));
if (v->data().getType() == Value::INT || v->data().getType() == Value::INT64)
{
Uint64 s = v->data().toInt64();
TorrentFile file(idx,path,file_length,s,piece_length);
// update file_length
file_length += s;
files.append(file);
}
else
{
throw Error(i18n("Corrupted torrent!"));
}
idx++;
}
}
void Torrent::loadTrackerURL(BValueNode* node)
{
if (!node || node->data().getType() != Value::STRING)
throw Error(i18n("Corrupted torrent!"));
// tracker_urls.append(KURL(node->data().toString(encoding).stripWhiteSpace()));
if (!trackers)
trackers = new TrackerTier();
trackers->urls.append(KURL(node->data().toString(encoding).stripWhiteSpace()));
}
void Torrent::loadPieceLength(BValueNode* node)
{
if (!node)
throw Error(i18n("Corrupted torrent!"));
if (node->data().getType() == Value::INT)
piece_length = node->data().toInt();
else if (node->data().getType() == Value::INT64)
piece_length = node->data().toInt64();
else
throw Error(i18n("Corrupted torrent!"));
}
void Torrent::loadFileLength(BValueNode* node)
{
if (!node)
throw Error(i18n("Corrupted torrent!"));
if (node->data().getType() == Value::INT)
file_length = node->data().toInt();
else if (node->data().getType() == Value::INT64)
file_length = node->data().toInt64();
else
throw Error(i18n("Corrupted torrent!"));
}
void Torrent::loadHash(BValueNode* node)
{
if (!node || node->data().getType() != Value::STRING)
throw Error(i18n("Corrupted torrent!"));
TQByteArray hash_string = node->data().toByteArray();
for (unsigned int i = 0;i < hash_string.size();i+=20)
{
Uint8 h[20];
memcpy(h,hash_string.data()+i,20);
SHA1Hash hash(h);
hash_pieces.append(hash);
}
}
void Torrent::loadName(BValueNode* node)
{
if (!node || node->data().getType() != Value::STRING)
throw Error(i18n("Corrupted torrent!"));
name_suggestion = node->data().toString(encoding);
}
void Torrent::loadAnnounceList(BNode* node)
{
if (!node)
return;
BListNode* ml = dynamic_cast<BListNode*>(node);
if (!ml)
return;
if (!trackers)
trackers = new TrackerTier();
TrackerTier* tier = trackers;
//ml->printDebugInfo();
for (Uint32 i = 0;i < ml->getNumChildren();i++)
{
BListNode* url = dynamic_cast<BListNode*>(ml->getChild(i));
if (!url)
throw Error(i18n("Parse Error"));
for (Uint32 j = 0;j < url->getNumChildren();j++)
{
BValueNode* vn = dynamic_cast<BValueNode*>(url->getChild(j));
if (!vn)
throw Error(i18n("Parse Error"));
KURL url(vn->data().toString().stripWhiteSpace());
tier->urls.append(url);
//Out() << "Added tracker " << url << endl;
}
tier->next = new TrackerTier();
tier = tier->next;
}
}
void Torrent::loadNodes(BListNode* node)
{
for (Uint32 i = 0;i < node->getNumChildren();i++)
{
BListNode* c = node->getList(i);
if (!c || c->getNumChildren() != 2)
throw Error(i18n("Corrupted torrent!"));
// first child is the IP, second the port
BValueNode* ip = c->getValue(0);
BValueNode* port = c->getValue(1);
if (!ip || !port)
throw Error(i18n("Corrupted torrent!"));
if (ip->data().getType() != Value::STRING)
throw Error(i18n("Corrupted torrent!"));
if (port->data().getType() != Value::INT)
throw Error(i18n("Corrupted torrent!"));
// add the DHT node
kt::DHTNode n;
n.ip = ip->data().toString();
n.port = port->data().toInt();
nodes.append(n);
}
}
void Torrent::debugPrintInfo()
{
Out() << "Name : " << name_suggestion << endl;
// for (KURL::List::iterator i = tracker_urls.begin();i != tracker_urls.end();i++)
// Out() << "Tracker URL : " << *i << endl;
Out() << "Piece Length : " << piece_length << endl;
if (this->isMultiFile())
{
Out() << "Files : " << endl;
Out() << "===================================" << endl;
for (Uint32 i = 0;i < getNumFiles();i++)
{
TorrentFile & tf = getFile(i);
Out() << "Path : " << tf.getPath() << endl;
Out() << "Size : " << tf.getSize() << endl;
Out() << "First Chunk : " << tf.getFirstChunk() << endl;
Out() << "Last Chunk : " << tf.getLastChunk() << endl;
Out() << "First Chunk Off : " << tf.getFirstChunkOffset() << endl;
Out() << "Last Chunk Size : " << tf.getLastChunkSize() << endl;
Out() << "===================================" << endl;
}
}
else
{
Out() << "File Length : " << file_length << endl;
}
Out() << "Pieces : " << hash_pieces.size() << endl;
}
bool Torrent::verifyHash(const SHA1Hash & h,Uint32 index)
{
if (index >= hash_pieces.count())
return false;
const SHA1Hash & ph = hash_pieces[index];
return ph == h;
}
const SHA1Hash & Torrent::getHash(Uint32 idx) const
{
if (idx >= hash_pieces.count())
throw Error(TQString("Torrent::getHash %1 is out of bounds").arg(idx));
return hash_pieces[idx];
}
TorrentFile & Torrent::getFile(Uint32 idx)
{
if (idx >= files.size())
return TorrentFile::null;
return files.at(idx);
}
const TorrentFile & Torrent::getFile(Uint32 idx) const
{
if (idx >= files.size())
return TorrentFile::null;
return files.at(idx);
}
unsigned int Torrent::getNumTrackerURLs() const
{
Uint32 count = 0;
TrackerTier* tt = trackers;
while (tt)
{
count += tt->urls.count();
tt = tt->next;
}
return count;
}
void Torrent::calcChunkPos(Uint32 chunk,TQValueList<Uint32> & file_list) const
{
file_list.clear();
if (chunk >= hash_pieces.size() || files.empty())
return;
for (Uint32 i = 0;i < files.count();i++)
{
const TorrentFile & f = files[i];
if (chunk >= f.getFirstChunk() && chunk <= f.getLastChunk() && f.getSize() != 0)
file_list.append(f.getIndex());
}
}
bool Torrent::isMultimedia() const
{
return IsMultimediaFile(this->getNameSuggestion());
}
void Torrent::updateFilePercentage(const BitSet & bs)
{
for (Uint32 i = 0;i < files.count();i++)
{
TorrentFile & f = files[i];
f.updateNumDownloadedChunks(bs);
}
}
void Torrent::updateFilePercentage(Uint32 chunk,const BitSet & bs)
{
TQValueList<Uint32> cfiles;
calcChunkPos(chunk,cfiles);
TQValueList<Uint32>::iterator i = cfiles.begin();
while (i != cfiles.end())
{
TorrentFile & f = getFile(*i);
f.updateNumDownloadedChunks(bs);
i++;
}
}
bool Torrent::checkPathForDirectoryTraversal(const TQString & p)
{
TQStringList sl = TQStringList::split(bt::DirSeparator(),p);
return !sl.contains("..");
}
}