/*************************************************************************** * Copyright (C) 2005 by * * Joris Guisson * * Ivan Vasic * * * * 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 #include #include #include #include #include #include #include #include #include "torrent.h" #include "bdecoder.h" #include "bnode.h" #include "announcelist.h" #include 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(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(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(ml->getChild(i)); if (!url) throw Error(i18n("Parse Error")); for (Uint32 j = 0;j < url->getNumChildren();j++) { BValueNode* vn = dynamic_cast(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 & 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 cfiles; calcChunkPos(chunk,cfiles); TQValueList::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(".."); } }