Просмотр исходного кода

2008-01-04 Tatsuhiro Tsujikawa <tujikawa at rednoah dot com>

	Fixed segmentation fault when bad torrent metainfo is parsed.
	Added dynamic_cast properly to detect the misconfiguration of 
metainfo
	and then throw exception or skip it.
	* src/DefaultBtContext.{h, cc}
	* test/DefaultBtContextTest.cc
	* src/AnnounceList.cc
	* src/CompactPeerListProcessor.cc
	* src/message.h
	* src/DefaultBtAnnounce.{h, cc}
	* test/DefaultBtAnnounceTest.cc
	* src/BencodeVisitor.cc
Tatsuhiro Tsujikawa 18 лет назад
Родитель
Сommit
da4d9092b2

+ 14 - 0
ChangeLog

@@ -1,3 +1,17 @@
+2008-01-04  Tatsuhiro Tsujikawa  <tujikawa at rednoah dot com>
+
+	Fixed segmentation fault when bad torrent metainfo is parsed.
+	Added dynamic_cast properly to detect the misconfiguration of metainfo
+	and then throw exception or skip it.
+	* src/DefaultBtContext.{h, cc}
+	* test/DefaultBtContextTest.cc
+	* src/AnnounceList.cc
+	* src/CompactPeerListProcessor.cc
+	* src/message.h
+	* src/DefaultBtAnnounce.{h, cc}
+	* test/DefaultBtAnnounceTest.cc
+	* src/BencodeVisitor.cc
+
 2007-12-29  Tatsuhiro Tsujikawa  <tujikawa at rednoah dot com>
 
 	Added missing "B"(=Byte). So now the message looks like this:

+ 8 - 3
src/AnnounceList.cc

@@ -51,12 +51,17 @@ void AnnounceList::reconfigure(const MetaEntry* announceListEntry) {
   if(l) {
     for(MetaList::const_iterator itr = l->getList().begin();
 	itr != l->getList().end(); itr++) {
-      const List* elem = (List*)*itr;
+      const List* elem = dynamic_cast<const List*>(*itr);
+      if(!elem) {
+	continue;
+      }
       Strings urls;
       for(MetaList::const_iterator elemItr = elem->getList().begin();
 	  elemItr != elem->getList().end(); elemItr++) {
-	const Data* data = (Data*)*elemItr;
-	urls.push_back(data->toString());
+	const Data* data = dynamic_cast<const Data*>(*elemItr);
+	if(data) {
+	  urls.push_back(data->toString());
+	}
       }
       if(urls.size()) {
 	AnnounceTierHandle tier(new AnnounceTier(urls));

+ 3 - 3
src/BencodeVisitor.cc

@@ -75,10 +75,10 @@ void BencodeVisitor::visit(const Dictionary* d)
 void BencodeVisitor::visit(const MetaEntry* e)
 {
   if(dynamic_cast<const Data*>(e) != 0) {
-    visit((const Data*)e);
+    visit(reinterpret_cast<const Data*>(e));
   } else if(dynamic_cast<const List*>(e) != 0) {
-    visit((const List*)e);
+    visit(reinterpret_cast<const List*>(e));
   } else if(dynamic_cast<const Dictionary*>(e) != 0) {
-    visit((const Dictionary*)e);
+    visit(reinterpret_cast<const Dictionary*>(e));
   }
 }

+ 4 - 2
src/CompactPeerListProcessor.cc

@@ -41,8 +41,10 @@ bool CompactPeerListProcessor::canHandle(const MetaEntry* peersEntry) const {
 
 Peers CompactPeerListProcessor::extractPeer(const MetaEntry* peersEntry) {
   Peers peers;
-
-  const Data* peersData = (const Data*)peersEntry;
+  const Data* peersData = dynamic_cast<const Data*>(peersEntry);
+  if(!peersData) {
+    return peers;
+  }
   if(peersData->getLen()%6 == 0) {
     for(int32_t i = 0; i < peersData->getLen(); i += 6) {
       struct in_addr in;

+ 21 - 12
src/DefaultBtAnnounce.cc

@@ -184,41 +184,50 @@ DefaultBtAnnounce::processAnnounceResponse(const char* trackerResponse,
 {
   SharedHandle<MetaEntry> entry(MetaFileUtil::bdecoding(trackerResponse,
 							trackerResponseLength));
-  Dictionary* response = (Dictionary*)entry.get();
-  Data* failureReasonData = (Data*)response->get("failure reason");
+  const Dictionary* response = dynamic_cast<const Dictionary*>(entry.get());
+  if(!response) {
+    throw new DlAbortEx(MSG_NULL_TRACKER_RESPONSE);
+  }
+  const Data* failureReasonData = dynamic_cast<const Data*>(response->get("failure reason"));
   if(failureReasonData) {
     throw new DlAbortEx(EX_TRACKER_FAILURE,
 			failureReasonData->toString().c_str());
   }
-  Data* warningMessageData = (Data*)response->get("warning message");
+  const Data* warningMessageData = dynamic_cast<const Data*>(response->get("warning message"));
   if(warningMessageData) {
     logger->warn(MSG_TRACKER_WARNING_MESSAGE,
 		 warningMessageData->toString().c_str());
   }
-  Data* trackerIdData = (Data*)response->get("tracker id");
+  const Data* trackerIdData = dynamic_cast<const Data*>(response->get("tracker id"));
   if(trackerIdData) {
     trackerId = trackerIdData->toString();
     logger->debug("Tracker ID:%s", trackerId.c_str());
   }
-  Data* intervalData = (Data*)response->get("interval");
+  const Data* intervalData = dynamic_cast<const Data*>(response->get("interval"));
   if(intervalData) {
-    interval = intervalData->toInt();
-    logger->debug("Interval:%d", interval);
+    int32_t t = intervalData->toInt();
+    if(t > 0) {
+      interval = intervalData->toInt();
+      logger->debug("Interval:%d", interval);
+    }
   }
-  Data* minIntervalData = (Data*)response->get("min interval");
+  const Data* minIntervalData = dynamic_cast<const Data*>(response->get("min interval"));
   if(minIntervalData) {
-    minInterval = minIntervalData->toInt();
-    logger->debug("Min interval:%d", minInterval);
+    int32_t t = minIntervalData->toInt();
+    if(t > 0) {
+      minInterval = minIntervalData->toInt();
+      logger->debug("Min interval:%d", minInterval);
+    }
   }
   if(minInterval > interval) {
     minInterval = interval;
   }
-  Data* completeData = (Data*)response->get("complete");
+  const Data* completeData = dynamic_cast<const Data*>(response->get("complete"));
   if(completeData) {
     complete = completeData->toInt();
     logger->debug("Complete:%d", complete);
   }
-  Data* incompleteData = (Data*)response->get("incomplete");
+  const Data* incompleteData = dynamic_cast<const Data*>(response->get("incomplete"));
   if(incompleteData) {
     incomplete = incompleteData->toInt();
     logger->debug("Incomplete:%d", incomplete);

+ 25 - 0
src/DefaultBtAnnounce.h

@@ -117,6 +117,31 @@ public:
   void generateKey();
 
   void setRandomizer(const RandomizerHandle& randomizer);
+
+  int32_t getInterval() const
+  {
+    return interval;
+  }
+
+  int32_t getMinInterval() const
+  {
+    return minInterval;
+  }
+
+  int32_t getComplete() const
+  {
+    return complete;
+  }
+
+  int32_t getIncomplete() const
+  {
+    return incomplete;
+  }
+
+  const string& getTrackerID() const
+  {
+    return trackerId;
+  }
 };
 
 #endif // _D_DEFAULT_BT_ANNOUNCE_H_

+ 78 - 41
src/DefaultBtContext.cc

@@ -44,11 +44,14 @@
 #include "a2netcompat.h"
 #include "AnnounceTier.h"
 #include "SimpleRandomizer.h"
+#include "LogFactory.h"
+#include "message.h"
 #include <libgen.h>
 
 DefaultBtContext::DefaultBtContext():_peerIdPrefix("-aria2-"),
 				     _randomizer(SimpleRandomizer::getInstance()),
-				     _ownerRequestGroup(0) {}
+				     _ownerRequestGroup(0),
+				     _logger(LogFactory::getInstance()) {}
 
 DefaultBtContext::~DefaultBtContext() {}
 
@@ -101,11 +104,10 @@ void DefaultBtContext::extractPieceHash(const unsigned char* hashData,
   }
 }
 
-void DefaultBtContext::extractFileEntries(Dictionary* infoDic,
+void DefaultBtContext::extractFileEntries(const Dictionary* infoDic,
 					  const string& defaultName,
 					  const Strings& urlList) {
-  // TODO use dynamic_cast
-  Data* nameData = (Data*)infoDic->get("name");
+  const Data* nameData = dynamic_cast<const Data*>(infoDic->get("name"));
   if(nameData) {
     name = nameData->toString();
   } else {
@@ -113,8 +115,7 @@ void DefaultBtContext::extractFileEntries(Dictionary* infoDic,
     name = string(basename(basec))+".file";
     free(basec);
   }
-  // TODO use dynamic_cast
-  List* files = (List*)infoDic->get("files");
+  const List* files = dynamic_cast<const List*>(infoDic->get("files"));
   if(files) {
     int64_t length = 0;
     int64_t offset = 0;
@@ -123,21 +124,36 @@ void DefaultBtContext::extractFileEntries(Dictionary* infoDic,
     const MetaList& metaList = files->getList();
     for(MetaList::const_iterator itr = metaList.begin();
 	itr != metaList.end(); itr++) {
-      Dictionary* fileDic = (Dictionary*)(*itr);
-      // TODO use dynamic_cast
-      Data* lengthData = (Data*)fileDic->get("length");
-      length += lengthData->toLLInt();
-      // TODO use dynamic_cast
-      List* pathList = (List*)fileDic->get("path");
+      const Dictionary* fileDic = dynamic_cast<const Dictionary*>((*itr));
+      if(!fileDic) {
+	continue;
+      }
+      const Data* lengthData = dynamic_cast<const Data*>(fileDic->get("length"));
+      if(lengthData) {
+	length += lengthData->toLLInt();
+      } else {
+	throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "file length");
+      }
+      const List* pathList = dynamic_cast<const List*>(fileDic->get("path"));
+      if(!pathList) {
+	throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "file path list");
+      }
       const MetaList& paths = pathList->getList();
       string path;
       for(int32_t i = 0; i < (int32_t)paths.size()-1; i++) {
-	Data* subpath = (Data*)paths[i];
-	path += subpath->toString()+"/";
+	const Data* subpath = dynamic_cast<const Data*>(paths[i]);
+	if(subpath) {
+	  path += subpath->toString()+"/";
+	} else {
+	  throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "file path element");
+	}
+      }
+      const Data* lastPath = dynamic_cast<const Data*>(paths.back());
+      if(lastPath) {
+	path += lastPath->toString();
+      } else {
+	throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "file path element");
       }
-      // TODO use dynamic_cast
-      Data* lastPath = (Data*)paths.back();
-      path += lastPath->toString();
 
       Strings uris;
       transform(urlList.begin(), urlList.end(), back_inserter(uris),
@@ -153,28 +169,37 @@ void DefaultBtContext::extractFileEntries(Dictionary* infoDic,
   } else {
     // single-file mode;
     fileMode = BtContext::SINGLE;
-    Data* length = (Data*)infoDic->get("length");
-    totalLength = length->toLLInt();
+    const Data* length = dynamic_cast<const Data*>(infoDic->get("length"));
+    if(length) {
+      totalLength = length->toLLInt();
+    } else {
+      throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "file length");
+    }
     FileEntryHandle fileEntry(new FileEntry(name, totalLength, 0, urlList));
     fileEntries.push_back(fileEntry);
   }
 }
 
-void DefaultBtContext::extractAnnounce(Data* announceData) {
+void DefaultBtContext::extractAnnounce(const Data* announceData) {
   Strings urls;
   urls.push_back(Util::trim(announceData->toString()));
   announceTiers.push_back(AnnounceTierHandle(new AnnounceTier(urls)));
 }
 
-void DefaultBtContext::extractAnnounceList(List* announceListData) {
+void DefaultBtContext::extractAnnounceList(const List* announceListData) {
   for(MetaList::const_iterator itr = announceListData->getList().begin();
       itr != announceListData->getList().end(); itr++) {
-    const List* elem = (List*)*itr;
+    const List* elem = dynamic_cast<const List*>(*itr);
+    if(!elem) {
+      continue;
+    }
     Strings urls;
     for(MetaList::const_iterator elemItr = elem->getList().begin();
 	elemItr != elem->getList().end(); elemItr++) {
-      const Data* data = (Data*)*elemItr;
-      urls.push_back(Util::trim(data->toString()));
+      const Data* data = dynamic_cast<const Data*>(*elemItr);
+      if(data) {
+	urls.push_back(Util::trim(data->toString()));
+      }
     }
     if(urls.size()) {
       AnnounceTierHandle tier(new AnnounceTier(urls));
@@ -187,16 +212,16 @@ Strings DefaultBtContext::extractUrlList(const MetaEntry* obj)
 {
   Strings uris;
   if(dynamic_cast<const List*>(obj)) {
-    const List* urlList = (const List*)obj;
+    const List* urlList = reinterpret_cast<const List*>(obj);
     for(MetaList::const_iterator itr = urlList->getList().begin();
 	itr != urlList->getList().end(); ++itr) {
-      Data* data = dynamic_cast<Data*>(*itr);
+      const Data* data = dynamic_cast<const Data*>(*itr);
       if(data) {
 	uris.push_back(data->toString());
       }
     }
   } else if(dynamic_cast<const Data*>(obj)) {
-    const Data* urlData = (const Data*)obj;
+    const Data* urlData = reinterpret_cast<const Data*>(obj);
     uris.push_back(urlData->toString());
   }
   return uris;
@@ -204,27 +229,30 @@ Strings DefaultBtContext::extractUrlList(const MetaEntry* obj)
 
 void DefaultBtContext::loadFromMemory(const char* content, int32_t length, const string& defaultName)
 {
-  MetaEntry* rootEntry = MetaFileUtil::bdecoding(content, length);
-  if(!dynamic_cast<Dictionary*>(rootEntry)) {
+  SharedHandle<MetaEntry> rootEntry = MetaFileUtil::bdecoding(content, length);
+  const Dictionary* rootDic = dynamic_cast<const Dictionary*>(rootEntry.get());
+  if(!rootDic) {
     throw new DlAbortEx("torrent file does not contain a root dictionary .");
   }
-  processMetaInfo(rootEntry, defaultName);
+  processRootDictionary(rootDic, defaultName);
 }
 
 void DefaultBtContext::load(const string& torrentFile) {
-  MetaEntry* rootEntry = MetaFileUtil::parseMetaFile(torrentFile);
-  if(!dynamic_cast<Dictionary*>(rootEntry)) {
+  SharedHandle<MetaEntry> rootEntry = MetaFileUtil::parseMetaFile(torrentFile);
+  const Dictionary* rootDic = dynamic_cast<const Dictionary*>(rootEntry.get());
+  if(!rootDic) {
     throw new DlAbortEx("torrent file does not contain a root dictionary .");
   }
-  processMetaInfo(rootEntry, torrentFile);
+  processRootDictionary(rootDic, torrentFile);
 }
 
-void DefaultBtContext::processMetaInfo(const MetaEntry* rootEntry, const string& defaultName)
+void DefaultBtContext::processRootDictionary(const Dictionary* rootDic, const string& defaultName)
 {
   clear();
-  SharedHandle<Dictionary> rootDic =
-    SharedHandle<Dictionary>((Dictionary*)rootEntry);
-  Dictionary* infoDic = (Dictionary*)rootDic->get("info");
+  const Dictionary* infoDic = dynamic_cast<const Dictionary*>(rootDic->get("info"));
+  if(!infoDic) {
+    throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "info directory");
+  }
   // retrieve infoHash
   ShaVisitor v;
   infoDic->accept(&v);
@@ -232,10 +260,16 @@ void DefaultBtContext::processMetaInfo(const MetaEntry* rootEntry, const string&
   v.getHash(infoHash, len);
   infoHashString = Util::toHex(infoHash, INFO_HASH_LENGTH);
   // calculate the number of pieces
-  Data* pieceHashData = (Data*)infoDic->get("pieces");
+  const Data* pieceHashData = dynamic_cast<const Data*>(infoDic->get("pieces"));
+  if(!pieceHashData) {
+    throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "pieces");
+  }
   numPieces = pieceHashData->getLen()/PIECE_HASH_LENGTH;
   // retrieve piece length
-  Data* pieceLengthData = (Data*)infoDic->get("piece length");
+  const Data* pieceLengthData = dynamic_cast<const Data*>(infoDic->get("piece length"));
+  if(!pieceLengthData) {
+    throw new DlAbortEx(MSG_SOMETHING_MISSING_IN_TORRENT, "piece length");
+  }
   pieceLength = pieceLengthData->toInt();
   // retrieve piece hashes
   extractPieceHash((unsigned char*)pieceHashData->getData(),
@@ -253,9 +287,12 @@ void DefaultBtContext::processMetaInfo(const MetaEntry* rootEntry, const string&
   Strings urlList = extractUrlList(rootDic->get("url-list"));
   // retrieve file entries
   extractFileEntries(infoDic, defaultName, urlList);
+  if((totalLength+pieceLength-1)/pieceLength != numPieces) {
+    throw new DlAbortEx("Too few/many piece hash.");
+  }
   // retrieve announce
-  Data* announceData = (Data*)rootDic->get("announce");
-  List* announceListData = (List*)rootDic->get("announce-list");
+  const Data* announceData = dynamic_cast<const Data*>(rootDic->get("announce"));
+  const List* announceListData = dynamic_cast<const List*>(rootDic->get("announce-list"));
   if(announceListData) {
     extractAnnounceList(announceListData);
   } else if(announceData) {

+ 7 - 4
src/DefaultBtContext.h

@@ -42,6 +42,7 @@
 
 class Randomizer;
 typedef SharedHandle<Randomizer> RandomizerHandle;
+class Logger;
 
 #define INFO_HASH_LENGTH 20
 #define PIECE_HASH_LENGTH 20
@@ -66,19 +67,21 @@ private:
 
   RequestGroup* _ownerRequestGroup;
 
+  const Logger* _logger;
+
   void clear();
   void extractPieceHash(const unsigned char* hashData,
 			int32_t hashDataLength,
 			int32_t hashLength);
-  void extractFileEntries(Dictionary* infoDic,
+  void extractFileEntries(const Dictionary* infoDic,
 			  const string& defaultName,
 			  const Strings& urlList);
-  void extractAnnounce(Data* announceData);
-  void extractAnnounceList(List* announceListData);
+  void extractAnnounce(const Data* announceData);
+  void extractAnnounceList(const List* announceListData);
 
   Strings extractUrlList(const MetaEntry* obj);
 
-  void processMetaInfo(const MetaEntry* rootEntry, const string& defaultName);
+  void processRootDictionary(const Dictionary* rootDic, const string& defaultName);
 
  public:
   DefaultBtContext();

+ 2 - 0
src/message.h

@@ -135,6 +135,8 @@
 #define MSG_TOO_SMALL_PAYLOAD_SIZE _("Too small payload size for %s, size=%d.")
 #define MSG_REMOVED_DEFUNCT_CONTROL_FILE _("Removed the defunct control file %s because the download file %s doesn't exist.")
 #define MSG_SHARE_RATIO_REPORT _("Your share ratio was %.1f, uploaded/downloaded=%sB/%sB")
+#define MSG_SOMETHING_MISSING_IN_TORRENT _("Missing %s in torrent metainfo.")
+#define MSG_NULL_TRACKER_RESPONSE _("Tracker returned null data.")
 
 #define EX_TIME_OUT _("Timeout.")
 #define EX_INVALID_CHUNK_SIZE _("Invalid chunk size.")

+ 50 - 0
test/DefaultBtAnnounceTest.cc

@@ -21,6 +21,9 @@ class DefaultBtAnnounceTest:public CppUnit::TestFixture {
   CPPUNIT_TEST(testIsAllAnnounceFailed);
   CPPUNIT_TEST(testURLOrderInStoppedEvent);
   CPPUNIT_TEST(testURLOrderInCompletedEvent);
+  CPPUNIT_TEST(testProcessAnnounceResponse_malformed);
+  CPPUNIT_TEST(testProcessAnnounceResponse_failureReason);
+  CPPUNIT_TEST(testProcessAnnounceResponse);
   CPPUNIT_TEST_SUITE_END();
 private:
   MockBtContextHandle _btContext;
@@ -78,6 +81,9 @@ public:
   void testIsAllAnnounceFailed();
   void testURLOrderInStoppedEvent();
   void testURLOrderInCompletedEvent();
+  void testProcessAnnounceResponse_malformed();
+  void testProcessAnnounceResponse_failureReason();
+  void testProcessAnnounceResponse();
 };
 
 
@@ -275,3 +281,47 @@ void DefaultBtAnnounceTest::testURLOrderInCompletedEvent()
 
   btAnnounce.announceSuccess();
 }
+
+void DefaultBtAnnounceTest::testProcessAnnounceResponse_malformed()
+{
+  try {
+    string res = "i123e";
+    DefaultBtAnnounce(new MockBtContext(), _option).processAnnounceResponse(res.c_str(), res.size());
+    CPPUNIT_FAIL("exception must be thrown.");
+  } catch(Exception* e) {
+    cerr << *e << endl;
+    delete e;
+  }
+}
+
+void DefaultBtAnnounceTest::testProcessAnnounceResponse_failureReason()
+{
+  try {
+    string res = "d14:failure reason11:hello worlde";
+    DefaultBtAnnounce(new MockBtContext(), _option).processAnnounceResponse(res.c_str(), res.size());
+    CPPUNIT_FAIL("exception must be thrown.");
+  } catch(Exception* e) {
+    cerr << *e << endl;
+    delete e;
+  }
+}
+
+void DefaultBtAnnounceTest::testProcessAnnounceResponse()
+{
+  string res = "d"
+    "15:warning message11:hello world"
+    "10:tracker id3:foo"
+    "8:intervali3000e"
+    "12:min intervali1800e"
+    "8:completei100e"
+    "10:incompletei200e"
+    "e";
+  
+  DefaultBtAnnounce an(new MockBtContext(), _option);
+  an.processAnnounceResponse(res.c_str(), res.size());
+  CPPUNIT_ASSERT_EQUAL(string("foo"), an.getTrackerID());
+  CPPUNIT_ASSERT_EQUAL(3000, an.getInterval());
+  CPPUNIT_ASSERT_EQUAL(1800, an.getMinInterval());
+  CPPUNIT_ASSERT_EQUAL(100, an.getComplete());
+  CPPUNIT_ASSERT_EQUAL(200, an.getIncomplete());
+}

+ 16 - 0
test/DefaultBtContextTest.cc

@@ -29,6 +29,7 @@ class DefaultBtContextTest:public CppUnit::TestFixture {
   CPPUNIT_TEST(testGetFileEntries_multiFileUrlList);
   CPPUNIT_TEST(testGetFileEntries_singleFileUrlList);
   CPPUNIT_TEST(testLoadFromMemory);
+  CPPUNIT_TEST(testLoadFromMemory_somethingMissing);
   CPPUNIT_TEST_SUITE_END();
 public:
   void setUp() {
@@ -53,6 +54,7 @@ public:
   void testGetFileEntries_multiFileUrlList();
   void testGetFileEntries_singleFileUrlList();
   void testLoadFromMemory();
+  void testLoadFromMemory_somethingMissing();
 };
 
 
@@ -325,3 +327,17 @@ void DefaultBtContextTest::testLoadFromMemory()
   CPPUNIT_ASSERT_EQUAL(correctHash, Util::toHex(btContext.getInfoHash(),
 						btContext.getInfoHashLength()));
 }
+
+void DefaultBtContextTest::testLoadFromMemory_somethingMissing()
+{
+  // pieces missing
+  try {
+    string memory = "d8:announce36:http://aria.rednoah.com/announce.php4:infod4:name13:aria2.tar.bz26:lengthi262144eee";
+    DefaultBtContext btContext;
+    btContext.loadFromMemory(memory.c_str(), memory.size(), "default");
+    CPPUNIT_FAIL("exception must be thrown.");
+  } catch(Exception* e) {
+    cerr << *e << endl;
+    delete e;
+  }
+}

+ 1 - 1
test/url-list-singleFile.torrent

@@ -1 +1 @@
-d8:url-list35:http://localhost/dist/aria2.tar.bz28:announce36:http://aria.rednoah.com/announce.php13:announce-listll15:http://tracker1el15:http://tracker2el15:http://tracker3ee7:comment17:REDNOAH.COM RULES13:creation datei1123456789e4:infod6:lengthi7680e4:name13:aria2.tar.bz212:piece lengthi128e6:pieces60:AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCee
+d8:url-list35:http://localhost/dist/aria2.tar.bz28:announce36:http://aria.rednoah.com/announce.php13:announce-listll15:http://tracker1el15:http://tracker2el15:http://tracker3ee7:comment17:REDNOAH.COM RULES13:creation datei1123456789e4:infod6:lengthi380e4:name13:aria2.tar.bz212:piece lengthi128e6:pieces60:AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCee