TorrentParser.cpp

Go to the documentation of this file.
00001 /*
00002 
00003 Copyright (C) 2006-2007 by Peter Dimov.
00004 
00005 This file is part of Calitko (http://www.calitko.org).
00006 
00007 Calitko is free software; you can redistribute it and/or modify
00008 it under the terms of the GNU General Public License as published by
00009 the Free Software Foundation; either version 2 of the License, or
00010 (at your option) any later version.
00011 
00012 Calitko is distributed in the hope that it will be useful,
00013 but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Calitko; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 
00021 */
00022 
00023 #include "Qt.h"
00024 #include "Torrent.h"
00025 #include "TorrentParser.h"
00026 #include "Imports.cpp"
00027 
00029 
00043 bool TorrentParser::parseAndLoadTorrent (const QByteArray &rawTorrentData,
00044                                          Torrent &torrent)
00045 {
00046     Torrent parsedTorrent;
00047 
00048     bool loadedOk = loadAllTorrentData (rawTorrentData, parsedTorrent);
00049     if (!loadedOk)
00050         return false;
00051 
00052     torrent = parsedTorrent;
00053     return true;
00054 }
00055 
00057 
00060 bool TorrentParser::loadAllTorrentData(const QByteArray &rawData,
00061                                        Torrent &torrent)
00062 {
00063     if (rawData.isEmpty())
00064         return false;
00065 
00066     BDecoder bDecoder (rawData);
00067     auto_ptr <BItem> item = bDecoder.readNext();
00068     if (!bDecoder.hasReadAllCorrectly())
00069         return false;
00070 
00071     const BDictionary *bBasicData = dynamic_cast <const BDictionary *>
00072         (item.get());
00073     if (!bBasicData)
00074         return false;
00075 
00076     bool basicDataLoaded = loadTorrentBasicData (bBasicData, torrent);
00077     if (!basicDataLoaded)
00078         return false;
00079 
00080     const BDictionary *bInfo = dynamic_cast <const BDictionary *>
00081         (bBasicData->item (InfoKeyName));
00082     if (!bInfo)
00083         return false;
00084 
00085     bool filesInfoLoaded = loadTorrentFilesInfo (bInfo, torrent);
00086     if (!filesInfoLoaded)
00087         return false;
00088 
00089     return true;
00090 }
00091 
00093 
00096 bool TorrentParser::loadTorrentBasicData (const BDictionary *bBasicData,
00097                                           Torrent &torrent)
00098 {
00099     Q_ASSERT (bBasicData);
00100 
00101     bool announceLoaded = getAndLoadAnnounce (bBasicData, torrent);
00102     if (!announceLoaded)
00103         return false;
00104 
00105     // These pieces of information are only optional so I'm not testing
00106     // their return values
00107     getAndLoadAnnounceList (bBasicData, torrent);
00108     getAndLoadCreationDate (bBasicData, torrent);
00109     getAndLoadComment (bBasicData, torrent);
00110     getAndLoadCreatedBy (bBasicData, torrent);
00111 
00112     return true;
00113 }
00114 
00116 
00119 bool TorrentParser::loadTorrentFilesInfo (const BDictionary *bInfo,
00120                                           Torrent &torrent)
00121 {
00122     Q_ASSERT (bInfo);
00123 
00124     bool pieceLengthLoaded = getAndLoadPieceLength (bInfo, torrent);
00125     if (!pieceLengthLoaded)
00126         return false;
00127 
00128     bool piecesLoaded = getAndLoadPieces (bInfo, torrent);
00129     if (!piecesLoaded)
00130         return false;
00131 
00132     bool singleFileInfoLoaded = getAndLoadSingleModeFileInfo (bInfo, torrent);
00133     if (!singleFileInfoLoaded) {
00134         bool multipleFileInfoLoaded = getAndLoadMultipleModeFileInfo
00135             (bInfo, torrent);
00136         if (!multipleFileInfoLoaded)
00137             return false;
00138     }
00139 
00140     // Only optional so I'm not testing the return value
00141     getAndLoadPrivate (bInfo, torrent);
00142 
00143     return true;
00144 }
00145 
00147 
00151 bool TorrentParser::getAndLoadAnnounce (const BDictionary *bBasicData,
00152                                         Torrent &torrent)
00153 {
00154     Q_ASSERT (bBasicData);
00155     const BString *bAnnounceUrl = dynamic_cast <const BString *>
00156         (bBasicData->item (AnnounceKeyName));
00157     if (!bAnnounceUrl)
00158         return false;
00159 
00160     Uri announceUrl = Uri::fromUnencoded (bAnnounceUrl->value());
00161     if (!isValidAnnounceUrl (announceUrl))
00162         return false;
00163 
00164     torrent.d->announce = announceUrl;
00165     return true;
00166 }
00167 
00169 
00173 bool TorrentParser::getAndLoadAnnounceList (const BDictionary *bBasicData,
00174                                             Torrent &torrent)
00175 {
00176     Q_ASSERT (bBasicData);
00177     const BList *bAnnounceList = dynamic_cast <const BList *>
00178         (bBasicData->item (AnnounceListKeyName));
00179     if (!bAnnounceList)
00180         return false;
00181 
00182     // Announce list has the following scheme:
00183     // [
00184     //   [tracker1, tracker2, ...], // Tier 1
00185     //   [tracker1, tracker2, ...], // Tier 2
00186     //   ...
00187     //   [tracker1, tracker2, ...]  // Tier N
00188     // ]
00189     Torrent::AnnounceList loadedAnnounceList;
00190 
00191     // For each tier
00192     // (cannot use foreach because BList doesn't support it)
00193     for (int i = 0; i < bAnnounceList->size(); ++i) {
00194         const BList *bTier = dynamic_cast <const BList *>
00195             (bAnnounceList->item (i));
00196         if (!bTier)
00197             return false;
00198 
00199         QList <Uri> tier;
00200         bool convertedOk = convertBTierToTier (bTier, tier);
00201         if (convertedOk && !tier.isEmpty())
00202             loadedAnnounceList.append (tier);
00203         else
00204             return false;
00205     }
00206 
00207     if (loadedAnnounceList.isEmpty())
00208         return false;
00209 
00210     torrent.d->announceList = loadedAnnounceList;
00211     return true;
00212 }
00213 
00215 
00221 bool TorrentParser::getAndLoadCreationDate (const BDictionary *bBasicData,
00222                                             Torrent &torrent)
00223 {
00224     Q_ASSERT (bBasicData);
00225     const BInt *bTimeStamp = dynamic_cast <const BInt *>
00226         (bBasicData->item (CreationDateKeyName));
00227     if (!bTimeStamp)
00228         return false;
00229     else if (!isValidCreationDate (bTimeStamp->value()))
00230         return false;
00231 
00232     torrent.d->creationDate.setTime_t (bTimeStamp->value());
00233     return true;
00234 }
00235 
00237 
00241 bool TorrentParser::getAndLoadComment (const BDictionary *bBasicData,
00242                                        Torrent &torrent)
00243 {
00244     Q_ASSERT (bBasicData);
00245     const BString *bComment = dynamic_cast <const BString *>
00246         (bBasicData->item (CommentKeyName));
00247     if (!bComment)
00248         return false;
00249 
00250     torrent.d->comment = QString::fromUtf8 (bComment->value());
00251     return true;
00252 }
00253 
00255 
00259 bool TorrentParser::getAndLoadCreatedBy (const BDictionary *bBasicData,
00260                                          Torrent &torrent)
00261 {
00262     Q_ASSERT (bBasicData);
00263     const BString *bCreatedBy = dynamic_cast <const BString *>
00264         (bBasicData->item (CreatedByKeyName));
00265     if (!bCreatedBy)
00266         return false;
00267 
00268     torrent.d->createdBy = QString::fromUtf8 (bCreatedBy->value());
00269     return true;
00270 }
00271 
00273 
00279 bool TorrentParser::getAndLoadPieceLength (const BDictionary *bInfo,
00280                                            Torrent &torrent)
00281 {
00282     Q_ASSERT (bInfo);
00283     const BInt *bPieceLength = dynamic_cast <const BInt *>
00284         (bInfo->item (PieceLengthKeyName));
00285     if (!bPieceLength)
00286         return false;
00287     else if (!isValidPieceLength (bPieceLength->value()))
00288         return false;
00289 
00290     torrent.d->pieceLength = bPieceLength->value();
00291     return true;
00292 }
00293 
00295 
00300 bool TorrentParser::getAndLoadPieces (const BDictionary *bInfo,
00301                                       Torrent &torrent)
00302 {
00303     Q_ASSERT (bInfo);
00304     const BString *bPieces = dynamic_cast <const BString *>
00305         (bInfo->item (PiecesKeyName));
00306     if (!bPieces)
00307         return false;
00308 
00309     Torrent::PieceList pieceList;
00310     bool convertedOk = convertPiecesToPieceList (bPieces->value(), pieceList);
00311     if (!convertedOk)
00312         return false;
00313 
00314     torrent.d->pieces = pieceList;
00315     return true;
00316 }
00317 
00319 
00325 bool TorrentParser::getAndLoadPrivate (const BDictionary *bInfo,
00326                                        Torrent &torrent)
00327 {
00328     Q_ASSERT (bInfo);
00329     const BInt *bPrivateFlag = dynamic_cast <const BInt *>
00330         (bInfo->item (PrivateKeyName));
00331     if (!bPrivateFlag)
00332         return false;
00333     else if (!isValidPrivate (bPrivateFlag->value()))
00334         return false;
00335 
00336     torrent.d->isPrivate = convertPrivateFlagToBool
00337         (bPrivateFlag->value());
00338     return true;
00339 }
00340 
00342 
00346 bool TorrentParser::getAndLoadSingleModeFileInfo (const BDictionary *bInfo,
00347                                                   Torrent &torrent)
00348 {
00349     Q_ASSERT (bInfo);
00350     Torrent::FileInfo fileInfo;
00351     bool fileLengthLoaded = getAndLoadFileLength (bInfo, fileInfo);
00352     if (!fileLengthLoaded)
00353         return false;
00354 
00355     QString fileName;
00356     bool fileNameLoaded = getAndLoadFileName (bInfo, fileName);
00357     if (!fileNameLoaded)
00358         return false;
00359     else
00360         fileInfo.filePath = fileName;
00361 
00362     // Only optional so I'm not testing the return value
00363     getAndLoadFileChecksum (bInfo, fileInfo);
00364 
00365     torrent.d->files.push_back (fileInfo);
00366     return true;
00367 }
00368 
00370 
00374 bool TorrentParser::getAndLoadMultipleModeFileInfo (const BDictionary *bInfo,
00375                                                     Torrent &torrent)
00376 {
00377     Q_ASSERT (bInfo);
00378     QString directoryName;
00379     bool directoryNameLoaded = getAndLoadDirectoryName (bInfo, directoryName);
00380     if (!directoryNameLoaded)
00381         return false;
00382 
00383     const BList *bFileInfoList = dynamic_cast <const BList *>
00384         (bInfo->item (FilesKeyName));
00385     if (!bFileInfoList)
00386         return false;
00387 
00388     Torrent::FileInfoList fileInfoList;
00389     bool fileListLoaded = getAndLoadFileInfoList (bFileInfoList, fileInfoList);
00390     if (!fileListLoaded)
00391         return false;
00392 
00393     appendDirectoryName (directoryName, DirectoryDelimiter, fileInfoList);
00394 
00395     torrent.d->files = fileInfoList;
00396     return true;
00397 }
00398 
00400 
00406 bool TorrentParser::getAndLoadDirectoryName (const BDictionary *bFileInfo,
00407                                              QString &directoryName)
00408 {
00409     Q_ASSERT (bFileInfo);
00410     // Directory name is get exactly the same way as file name
00411     return getAndLoadFileName (bFileInfo, directoryName);
00412 }
00413 
00415 
00421 bool TorrentParser::getAndLoadFileName (const BDictionary *bFileInfo,
00422                                         QString &fileName)
00423 {
00424     Q_ASSERT (bFileInfo);
00425     const BString *bFileName = dynamic_cast <const BString *>
00426         (bFileInfo->item (FileNameKeyName));
00427     if (!bFileName)
00428         return false;
00429 
00430     fileName = QString::fromUtf8 (bFileName->value());
00431     return true;
00432 }
00433 
00435 
00439 bool TorrentParser::getAndLoadFilePath (const BDictionary *bFileInfo,
00440                                         QString &filePath)
00441 {
00442     Q_ASSERT (bFileInfo);
00443     const BList *bFilePath = dynamic_cast <const BList *>
00444         (bFileInfo->item (PathKeyName));
00445     if (!bFilePath)
00446         return false;
00447 
00448     bool convertedOk = convertBFilePathToFilePath (bFilePath,
00449         DirectoryDelimiter, filePath);
00450     return convertedOk && !filePath.isEmpty();
00451 }
00452 
00454 
00460 bool TorrentParser::getAndLoadFileLength (const BDictionary *bFileInfo,
00461                                           Torrent::FileInfo &fileInfo)
00462 {
00463     Q_ASSERT (bFileInfo);
00464     const BInt *bFileLength = dynamic_cast <const BInt *>
00465         (bFileInfo->item (FileLengthKeyName));
00466     if (!bFileLength)
00467         return false;
00468     else if (!isValidFileLength (bFileLength->value()))
00469         return false;
00470 
00471     fileInfo.fileLength = bFileLength->value();
00472     return true;
00473 }
00474 
00476 
00482 bool TorrentParser::getAndLoadFileChecksum (const BDictionary *bFileInfo,
00483                                             Torrent::FileInfo &fileInfo)
00484 {
00485     Q_ASSERT (bFileInfo);
00486     const BString *bFileChecksum = dynamic_cast <const BString *>
00487         (bFileInfo->item (ChecksumKeyName));
00488     if (!bFileChecksum)
00489         return false;
00490     else if (!isValidFileChecksum (bFileChecksum->value()))
00491         return false;
00492 
00493     fileInfo.fileChecksum = Torrent::FileInfo::Checksum (bFileChecksum->value());
00494     return true;
00495 }
00496 
00498 
00502 bool TorrentParser::getAndLoadFileInfoList (const BList *bFileInfoList,
00503                                             Torrent::FileInfoList &fileInfoList)
00504 {
00505     Q_ASSERT (bFileInfoList);
00506     // For each file info
00507     // (cannot use foreach because BList doesn't support it)
00508     for (int i = 0; i < bFileInfoList->size(); ++i) {
00509         const BDictionary *bFileInfo = dynamic_cast <const BDictionary *>
00510             (bFileInfoList->item (i));
00511         if (!bFileInfo)
00512             return false;
00513 
00514         Torrent::FileInfo fileInfo;
00515         bool fileInfoLoaded = getAndLoadFileInfo (bFileInfo, fileInfo);
00516         if (!fileInfoLoaded)
00517             return false;
00518 
00519         fileInfoList.push_back (fileInfo);
00520     }
00521 
00522     return !fileInfoList.isEmpty();
00523 }
00524 
00526 
00530 bool TorrentParser::getAndLoadFileInfo (const BDictionary *bFileInfo,
00531                                         Torrent::FileInfo &fileInfo)
00532 {
00533     Q_ASSERT (bFileInfo);
00534     bool fileLengthLoaded = getAndLoadFileLength (bFileInfo, fileInfo);
00535     if (!fileLengthLoaded)
00536         return false;
00537 
00538     QString filePath;
00539     bool filePathLoaded = getAndLoadFilePath (bFileInfo, filePath);
00540     if (!filePathLoaded)
00541         return false;
00542     else
00543         fileInfo.filePath = filePath;
00544 
00545     // Only optional so I'm not testing the return value
00546     getAndLoadFileChecksum (bFileInfo, fileInfo);
00547 
00548     return true;
00549 }
00550 
00552 
00557 bool TorrentParser::isValidAnnounceUrl (const Uri &announceUrl)
00558 {
00559     return !announceUrl.scheme().isEmpty() && !announceUrl.authority().isEmpty();
00560 }
00561 
00563 
00567 bool TorrentParser::isValidCreationDate (int creationDate)
00568 {
00569     return creationDate >= 0;
00570 }
00571 
00573 
00577 bool TorrentParser::isValidPieceLength (int pieceLength)
00578 {
00579     return pieceLength > 0;
00580 }
00581 
00583 
00587 bool TorrentParser::isValidPieces (const QByteArray &pieces)
00588 {
00589     return !pieces.isEmpty() &&
00590         ((pieces.size() % Torrent::Piece::size()) == 0);
00591 }
00592 
00594 
00598 bool TorrentParser::isValidPrivate (int privateFlag)
00599 {
00600     return (privateFlag == 0) || (privateFlag == 1);
00601 }
00602 
00604 
00608 bool TorrentParser::isValidFileLength (qint64 fileLength)
00609 {
00610     return fileLength > 0;
00611 }
00612 
00614 
00618 bool TorrentParser::isValidFileChecksum (const QByteArray &fileChecksum)
00619 {
00620     return static_cast <uint> (fileChecksum.size()) ==
00621         Torrent::FileInfo::Checksum::size();
00622 }
00623 
00625 
00629 bool TorrentParser::convertBTierToTier (const BList *bTier, QList <Uri> &tier)
00630 {
00631     Q_ASSERT (bTier);
00632 
00633     // bTier has the following format:
00634     // [tracker1, tracker2, ..., trackerN]
00635     QList <Uri> loadedTier;
00636 
00637     // For each announce (tracker) URL
00638     // (cannot use foreach because BList doesn't support it)
00639     for (int i = 0; i < bTier->size(); ++i) {
00640         const BString *bAnnounceUrl = dynamic_cast <const BString *>
00641             (bTier->item (i));
00642         if (!bAnnounceUrl)
00643             return false;
00644 
00645         Uri announceUrl = Uri::fromUnencoded (bAnnounceUrl->value());
00646         if (!isValidAnnounceUrl (announceUrl))
00647             return false;
00648 
00649         loadedTier.append (announceUrl);
00650     }
00651 
00652     tier = loadedTier;
00653     return true;
00654 }
00655 
00657 
00661 bool TorrentParser::convertPiecesToPieceList (const QByteArray &pieces,
00662                                                Torrent::PieceList &pieceList)
00663 {
00664     if (!isValidPieces (pieces))
00665         return false;
00666 
00667     // Pieces is an array consisting of the concatenation of all
00668     // hash values, one per piece, so I'll have to split them
00669     Torrent::PieceList convertedPieceList;
00670     for (int pieceIndex = 0; pieceIndex < pieces.size();
00671         pieceIndex += Torrent::Piece::size()) {
00672         convertedPieceList.push_back (
00673             Torrent::Piece (pieces.mid (pieceIndex, Torrent::Piece::size())));
00674     }
00675 
00676     pieceList = convertedPieceList;
00677     return true;
00678 }
00679 
00681 
00688 bool TorrentParser::convertBFilePathToFilePath (const BList *bFilePath,
00689                                                 const QString &delimiter,
00690                                                 QString &filePath)
00691 {
00692     Q_ASSERT (bFilePath);
00693 
00694     // bFilePath has the following format:
00695     // [dir1, dir2, ..., dirN, fileName]
00696     QString loadedFilePath;
00697 
00698     // For each file path part
00699     // (cannot use foreach because BList doesn't support it)
00700     for (int i = 0; i < bFilePath->size(); ++i) {
00701         const BString *bPathPart = dynamic_cast <const BString *>
00702             (bFilePath->item (i));
00703         if (!bPathPart)
00704             return false;
00705 
00706         loadedFilePath += QString::fromUtf8 (bPathPart->value());
00707         if ((i + 1) < bFilePath->size())
00708             loadedFilePath += delimiter;
00709     }
00710 
00711     filePath = loadedFilePath;
00712     return true;
00713 }
00714 
00716 
00721 bool TorrentParser::convertPrivateFlagToBool (int privateFlag)
00722 {
00723     Q_ASSERT (isValidPrivate (privateFlag));
00724 
00725     return privateFlag == 1;
00726 }
00727 
00729 
00734 void TorrentParser::appendDirectoryName (const QString &directoryName,
00735                                          const QString &delimiter,
00736                                          Torrent::FileInfoList &fileInfoList)
00737 {
00738     // I can't do it via std::transform() + functor because there is currently
00739     // no way how to modify Torrent::FileInfo.path out of the TorrentParser
00740     // because only TorrentParser can access it's private data
00741     typedef QList <Torrent::FileInfo>::iterator FileInfoListIterator;
00742     for (FileInfoListIterator iter = fileInfoList.begin();
00743         iter != fileInfoList.end(); ++iter) {
00744         iter->filePath = directoryName + delimiter + iter->filePath;
00745     }
00746 }
00747 
00748 // Static constants definitions
00749 const char *TorrentParser::InfoKeyName          = "info";
00750 const char *TorrentParser::AnnounceKeyName      = "announce";
00751 const char *TorrentParser::AnnounceListKeyName  = "announce-list";
00752 const char *TorrentParser::CreationDateKeyName  = "creation date";
00753 const char *TorrentParser::CommentKeyName       = "comments";
00754 const char *TorrentParser::CreatedByKeyName     = "created by";
00755 const char *TorrentParser::PieceLengthKeyName   = "piece length";
00756 const char *TorrentParser::PiecesKeyName        = "pieces";
00757 const char *TorrentParser::PrivateKeyName       = "private";
00758 const char *TorrentParser::FileNameKeyName      = "name";
00759 const char *TorrentParser::FileLengthKeyName    = "length";
00760 const char *TorrentParser::ChecksumKeyName      = "md5sum";
00761 const char *TorrentParser::FilesKeyName         = "files";
00762 const char *TorrentParser::PathKeyName          = "path";
00763 const char *TorrentParser::DirectoryDelimiter   = "/";