瀏覽代碼

2008-12-09 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

	Added bencode helper functions and BDE class.
	They will replace MetaFileUtil and Dictionary/List/Data classes.
	* src/Makefile.am
	* src/bencode.cc
	* src/bencode.h
	* test/BencodeTest.cc
	* test/Makefile.am
Tatsuhiro Tsujikawa 17 年之前
父節點
當前提交
1b54e64d34
共有 8 個文件被更改,包括 997 次插入25 次删除
  1. 10 0
      ChangeLog
  2. 2 1
      src/Makefile.am
  3. 21 18
      src/Makefile.in
  4. 469 0
      src/bencode.cc
  5. 214 0
      src/bencode.h
  6. 272 0
      test/BencodeTest.cc
  7. 2 1
      test/Makefile.am
  8. 7 5
      test/Makefile.in

+ 10 - 0
ChangeLog

@@ -1,3 +1,13 @@
+2008-12-09  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Added bencode helper functions and BDE class.
+	They will replace MetaFileUtil and Dictionary/List/Data classes.
+	* src/Makefile.am
+	* src/bencode.cc
+	* src/bencode.h
+	* test/BencodeTest.cc
+	* test/Makefile.am
+
 2008-12-09  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 
 	Fixed the bug that bad URI is sent to the tracker when the announe

+ 2 - 1
src/Makefile.am

@@ -193,7 +193,8 @@ SRCS =  Socket.h\
 	NsCookieParser.cc NsCookieParser.h\
 	CookieStorage.cc CookieStorage.h\
 	SocketBuffer.cc SocketBuffer.h\
-	OptionHandlerException.cc OptionHandlerException.h
+	OptionHandlerException.cc OptionHandlerException.h\
+	bencode.cc bencode.h
 
 if ENABLE_SSL
 SRCS += TLSContext.h

+ 21 - 18
src/Makefile.in

@@ -417,9 +417,10 @@ am__libaria2c_a_SOURCES_DIST = Socket.h SocketCore.cc SocketCore.h \
 	ServerStatURISelector.h NsCookieParser.cc NsCookieParser.h \
 	CookieStorage.cc CookieStorage.h SocketBuffer.cc \
 	SocketBuffer.h OptionHandlerException.cc \
-	OptionHandlerException.h TLSContext.h LibgnutlsTLSContext.cc \
-	LibgnutlsTLSContext.h LibsslTLSContext.cc LibsslTLSContext.h \
-	GZipDecoder.cc GZipDecoder.h Sqlite3MozCookieParser.cc \
+	OptionHandlerException.h bencode.cc bencode.h TLSContext.h \
+	LibgnutlsTLSContext.cc LibgnutlsTLSContext.h \
+	LibsslTLSContext.cc LibsslTLSContext.h GZipDecoder.cc \
+	GZipDecoder.h Sqlite3MozCookieParser.cc \
 	Sqlite3MozCookieParser.h AsyncNameResolver.cc \
 	AsyncNameResolver.h IteratableChunkChecksumValidator.cc \
 	IteratableChunkChecksumValidator.h \
@@ -813,14 +814,14 @@ am__objects_21 = SocketCore.$(OBJEXT) Command.$(OBJEXT) \
 	ServerStatMan.$(OBJEXT) InOrderURISelector.$(OBJEXT) \
 	ServerStatURISelector.$(OBJEXT) NsCookieParser.$(OBJEXT) \
 	CookieStorage.$(OBJEXT) SocketBuffer.$(OBJEXT) \
-	OptionHandlerException.$(OBJEXT) $(am__objects_1) \
-	$(am__objects_2) $(am__objects_3) $(am__objects_4) \
-	$(am__objects_5) $(am__objects_6) $(am__objects_7) \
-	$(am__objects_8) $(am__objects_9) $(am__objects_10) \
-	$(am__objects_11) $(am__objects_12) $(am__objects_13) \
-	$(am__objects_14) $(am__objects_15) $(am__objects_16) \
-	$(am__objects_17) $(am__objects_18) $(am__objects_19) \
-	$(am__objects_20)
+	OptionHandlerException.$(OBJEXT) bencode.$(OBJEXT) \
+	$(am__objects_1) $(am__objects_2) $(am__objects_3) \
+	$(am__objects_4) $(am__objects_5) $(am__objects_6) \
+	$(am__objects_7) $(am__objects_8) $(am__objects_9) \
+	$(am__objects_10) $(am__objects_11) $(am__objects_12) \
+	$(am__objects_13) $(am__objects_14) $(am__objects_15) \
+	$(am__objects_16) $(am__objects_17) $(am__objects_18) \
+	$(am__objects_19) $(am__objects_20)
 am_libaria2c_a_OBJECTS = $(am__objects_21)
 libaria2c_a_OBJECTS = $(am_libaria2c_a_OBJECTS)
 am__installdirs = "$(DESTDIR)$(bindir)"
@@ -1144,13 +1145,14 @@ SRCS = Socket.h SocketCore.cc SocketCore.h BinaryStream.h Command.cc \
 	ServerStatURISelector.h NsCookieParser.cc NsCookieParser.h \
 	CookieStorage.cc CookieStorage.h SocketBuffer.cc \
 	SocketBuffer.h OptionHandlerException.cc \
-	OptionHandlerException.h $(am__append_1) $(am__append_2) \
-	$(am__append_3) $(am__append_4) $(am__append_5) \
-	$(am__append_6) $(am__append_7) $(am__append_8) \
-	$(am__append_9) $(am__append_10) $(am__append_11) \
-	$(am__append_12) $(am__append_13) $(am__append_14) \
-	$(am__append_15) $(am__append_16) $(am__append_17) \
-	$(am__append_18) $(am__append_19) $(am__append_20)
+	OptionHandlerException.h bencode.cc bencode.h $(am__append_1) \
+	$(am__append_2) $(am__append_3) $(am__append_4) \
+	$(am__append_5) $(am__append_6) $(am__append_7) \
+	$(am__append_8) $(am__append_9) $(am__append_10) \
+	$(am__append_11) $(am__append_12) $(am__append_13) \
+	$(am__append_14) $(am__append_15) $(am__append_16) \
+	$(am__append_17) $(am__append_18) $(am__append_19) \
+	$(am__append_20)
 noinst_LIBRARIES = libaria2c.a
 libaria2c_a_SOURCES = $(SRCS)
 aria2c_LDADD = libaria2c.a @LIBINTL@ @ALLOCA@ @LIBGNUTLS_LIBS@\
@@ -1522,6 +1524,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/VersionMetalinkParserState.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XML2SAXMetalinkProcessor.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asctime_r.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bencode.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/download_helper.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gai_strerror.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getaddrinfo.Po@am__quote@

+ 469 - 0
src/bencode.cc

@@ -0,0 +1,469 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2006 Tatsuhiro Tsujikawa
+ *
+ * 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
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#include "bencode.h"
+
+#include <fstream>
+#include <sstream>
+
+#include "StringFormat.h"
+#include "Util.h"
+
+namespace aria2 {
+
+namespace bencode {
+
+const BDE BDE::none;
+
+BDE::BDE() throw():_type(TYPE_NONE) {}
+
+BDE::BDE(Integer integer) throw():_type(TYPE_INTEGER),
+				  _integer(new Integer(integer)) {}
+
+
+BDE::BDE(const std::string& string) throw():_type(TYPE_STRING),
+					    _string(new std::string(string)) {}
+
+BDE::BDE(const char* cstring) throw():_type(TYPE_STRING),
+				      _string(new std::string(cstring)) {}
+
+BDE::BDE(const char* data, size_t length) throw():
+  _type(TYPE_STRING),
+  _string(new std::string(&data[0], &data[length])) {}
+
+BDE::BDE(const unsigned char* data, size_t length) throw():
+  _type(TYPE_STRING),
+  _string(new std::string(&data[0], &data[length])) {}
+
+BDE BDE::dict() throw()
+{
+  BDE bde;
+  bde._type = TYPE_DICT;
+  bde._dict.reset(new Dict());
+  return bde;
+}
+
+BDE BDE::list() throw()
+{
+  BDE bde;
+  bde._type = TYPE_LIST;
+  bde._list.reset(new List());
+  return bde;
+}
+
+// Test for Null data
+bool BDE::isNone() const throw()
+{
+  return _type == TYPE_NONE;
+}
+
+// Integer Interface
+
+bool BDE::isInteger() const throw()
+{
+  return _type == TYPE_INTEGER;
+}
+
+BDE::Integer BDE::i() const throw(RecoverableException)
+{
+  if(isInteger()) {
+    return *_integer.get();
+  } else {
+    throw RecoverableException("Not Integer");
+  }
+}
+
+// String Interface
+
+bool BDE::isString() const throw()
+{
+  return _type == TYPE_STRING;
+}
+
+const std::string& BDE::s() const throw(RecoverableException)
+{
+  if(isString()) {
+    return *_string.get();
+  } else {
+    throw RecoverableException("Not String");
+  }
+}
+
+// Dictionary Interface
+
+bool BDE::isDict() const throw()
+{
+  return _type == TYPE_DICT;
+}
+
+BDE& BDE::operator[](const std::string& key) throw(RecoverableException)
+{
+  if(isDict()) {
+    return (*_dict.get())[key];
+  } else {
+    throw RecoverableException("Not Dict");
+  }
+}
+
+const BDE& BDE::operator[](const std::string& key) const
+  throw(RecoverableException)
+{
+  if(isDict()) {
+    BDE::Dict::const_iterator i = _dict->find(key);
+    if(i == _dict->end()) {
+      return none;
+    } else {
+      return (*i).second;
+    }
+  } else {
+    throw RecoverableException("Not Dict");
+  }
+}
+
+bool BDE::containsKey(const std::string& key) const throw(RecoverableException)
+{
+  if(isDict()) {
+    return _dict->find(key) != _dict->end();
+  } else {
+    throw RecoverableException("Not Dict");
+  }
+}
+
+
+BDE::Dict::iterator BDE::dictBegin() throw(RecoverableException)
+{
+  if(isDict()) {
+    return _dict->begin();
+  } else {
+    throw RecoverableException("Not Dict");
+  }
+}
+
+BDE::Dict::const_iterator BDE::dictBegin() const throw(RecoverableException)
+{
+  if(isDict()) {
+    return _dict->begin();
+  } else {
+    throw RecoverableException("Not Dict");
+  }
+}
+
+BDE::Dict::iterator BDE::dictEnd() throw(RecoverableException)
+{
+  if(isDict()) {
+    return _dict->end();
+  } else {
+    throw RecoverableException("Not Dict");
+  }
+}
+
+BDE::Dict::const_iterator BDE::dictEnd() const throw(RecoverableException)
+{
+  if(isDict()) {
+    return _dict->end();
+  } else {
+    throw RecoverableException("Not Dict");
+  }
+}
+
+// List Interface
+
+bool BDE::isList() const throw()
+{
+  return _type == TYPE_LIST;
+}
+
+void BDE::append(const BDE& bde) throw(RecoverableException)
+{
+  if(isList()) {
+    _list->push_back(bde);
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+void BDE::operator<<(const BDE& bde) throw(RecoverableException)
+{
+  if(isList()) {
+    _list->push_back(bde);
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+BDE& BDE::operator[](size_t index) throw(RecoverableException)
+{
+  if(isList()) {
+    return (*_list.get())[index];
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+const BDE& BDE::operator[](size_t index) const throw(RecoverableException)
+{
+  if(isList()) {
+    return (*_list.get())[index];
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+BDE::List::iterator BDE::listBegin() throw(RecoverableException)
+{
+  if(isList()) {
+    return _list->begin();
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+BDE::List::const_iterator BDE::listBegin() const throw(RecoverableException)
+{
+  if(isList()) {
+    return _list->begin();
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+BDE::List::iterator BDE::listEnd() throw(RecoverableException)
+{
+  if(isList()) {
+    return _list->end();
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+BDE::List::const_iterator BDE::listEnd() const throw(RecoverableException)
+{
+  if(isList()) {
+    return _list->end();
+  } else {
+    throw RecoverableException("Not List");
+  }
+}
+
+// Callable from List and Dict
+size_t BDE::size() const throw(RecoverableException)
+{
+  if(isDict()) {
+    return _dict->size();
+  } else if(isList()) {
+    return _list->size();
+  } else {
+    throw RecoverableException("Not Dict nor List");
+  }
+}
+
+// Callable from List and Dict
+bool BDE::empty() const throw(RecoverableException)
+{
+  if(isDict()) {
+    return _dict->empty();
+  } else if(isList()) {
+    return _list->empty();
+  } else {
+    throw RecoverableException("Not Dict nor List");
+  }
+}
+
+static BDE decodeiter(std::istream& ss) throw(RecoverableException);
+
+static void checkdelim(std::istream& ss, const char delim = ':')
+  throw(RecoverableException)
+{
+  char d;
+  if(!(ss.get(d) && d == delim)) {
+    throw RecoverableException
+      (StringFormat("Delimiter '%c' not found.", delim).str());
+  }
+}
+
+static std::string decoderawstring(std::istream& ss)
+  throw(RecoverableException)
+{
+  size_t length;
+  ss >> length;
+  if(!ss) {
+    throw RecoverableException("Integer expected but none found.");
+  }
+  // TODO check length, it must be less than or equal to INT_MAX
+  checkdelim(ss);
+  char* buf = new char[length];
+  ss.read(buf, length);
+  if(ss.gcount() != static_cast<int>(length)) {
+    throw RecoverableException
+      (StringFormat("Expected %lu bytes of data, but only %d read.",
+		    static_cast<unsigned long>(length), ss.gcount()).str());
+  }
+  std::string str(&buf[0], &buf[length]);
+  delete [] buf;
+  return str;
+}
+
+static BDE decodestring(std::istream& ss) throw(RecoverableException)
+{
+  return BDE(decoderawstring(ss));
+}
+
+static BDE decodeinteger(std::istream& ss) throw(RecoverableException)
+{
+  BDE::Integer integer;
+  ss >> integer;
+  if(!ss) {
+    throw RecoverableException("Integer expected but none found");
+  }
+  checkdelim(ss, 'e');
+  return BDE(integer);
+}
+
+static BDE decodedict(std::istream& ss) throw(RecoverableException)
+{
+  BDE dict = BDE::dict();
+  char c;
+  while(ss.get(c)) {
+    if(c == 'e') {
+      return dict;
+    } else {
+      ss.unget();
+      std::string key = decoderawstring(ss);
+      dict[key] = decodeiter(ss);
+    }
+  }
+  throw RecoverableException("Unexpected EOF in dict context. 'e' expected.");
+}
+
+static BDE decodelist(std::istream& ss) throw(RecoverableException)
+{
+  BDE list = BDE::list();
+  char c;
+  while(ss.get(c)) {
+    if(c == 'e') {
+      return list;
+    } else {
+      ss.unget();
+      list << decodeiter(ss);
+    }
+  }
+  throw RecoverableException("Unexpected EOF in list context. 'e' expected.");
+}
+
+static BDE decodeiter(std::istream& ss) throw(RecoverableException)
+{
+  char c;
+  if(!ss.get(c)) {
+    throw RecoverableException("Unexpected EOF in term context."
+			       " 'd', 'l', 'i' or digit is expected.");
+  }
+  if(c == 'd') {
+    return decodedict(ss);
+  } else if(c == 'l') {
+    return decodelist(ss);
+  } else if(c == 'i') {
+    return decodeinteger(ss);
+  } else {
+    ss.unget();
+    return decodestring(ss);
+  }
+}
+
+BDE decode(std::istream& in) throw(RecoverableException)
+{
+  return decodeiter(in);
+}
+
+BDE decode(const std::string& s) throw(RecoverableException)
+{
+  if(s.empty()) {
+    return BDE::none;
+  }
+  std::istringstream ss(s);
+
+  return decodeiter(ss);
+}
+
+BDE decode(const unsigned char* data, size_t length) throw(RecoverableException)
+{
+  return decode(std::string(&data[0], &data[length]));
+}
+
+BDE decodeFromFile(const std::string& filename) throw(RecoverableException)
+{
+  std::ifstream f(filename.c_str());
+  if(f) {
+    return decode(f);
+  } else {
+    throw RecoverableException
+      (StringFormat("Cannot open file '%s'.", filename.c_str()).str());
+  }
+}
+
+static void encodeIter(std::ostream& o, const BDE& bde) throw()
+{
+  if(bde.isInteger()) {
+    o << "i" << bde.i() << "e";
+  } else if(bde.isString()) {
+    const std::string& s = bde.s();
+    o << s.size() << ":";
+    o.write(s.data(), s.size());
+  } else if(bde.isDict()) {
+    o << "d";
+    for(BDE::Dict::const_iterator i = bde.dictBegin(); i != bde.dictEnd(); ++i){
+      const std::string& key = (*i).first;
+      o << key.size() << ":";
+      o.write(key.data(), key.size());
+      encodeIter(o, (*i).second);
+    }
+    o << "e";
+  } else if(bde.isList()) {
+    o << "l";
+    for(BDE::List::const_iterator i = bde.listBegin(); i != bde.listEnd(); ++i){
+      encodeIter(o, *i);
+    }
+    o << "e";
+  }
+}
+
+std::string encode(const BDE& bde) throw()
+{
+  std::ostringstream ss;
+  encodeIter(ss, bde);
+  return ss.str();
+}
+
+} // namespace bencode
+
+} // namespace aria2

+ 214 - 0
src/bencode.h

@@ -0,0 +1,214 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2006 Tatsuhiro Tsujikawa
+ *
+ * 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
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#include "common.h"
+
+#include <string>
+#include <map>
+#include <deque>
+#include <iosfwd>
+
+#include "SharedHandle.h"
+#include "RecoverableException.h"
+
+namespace aria2 {
+
+namespace bencode {
+
+class BDE;
+
+class BDE {
+public:
+  typedef std::map<std::string, BDE> Dict;
+  typedef std::deque<BDE> List;
+  typedef int64_t Integer;
+private:
+  enum TYPE{
+    TYPE_NONE,
+    TYPE_INTEGER,
+    TYPE_STRING,
+    TYPE_DICT,
+    TYPE_LIST,
+  };
+
+  TYPE _type;
+  SharedHandle<Dict> _dict;
+  SharedHandle<List> _list;
+  SharedHandle<std::string> _string;
+  SharedHandle<Integer> _integer;
+
+public:
+  BDE() throw();
+
+  static BDE dict() throw();
+
+  static BDE list() throw();
+
+  static const BDE none;
+
+  // Test for Null data
+  // Return true if the type of this object is None.
+  bool isNone() const throw();
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Integer Interface
+
+  BDE(Integer integer) throw();
+
+  // Returns true if the type of this object is Integer.
+  bool isInteger() const throw();
+
+  // Returns Integer. Requires this object to be Integer.
+  Integer i() const throw(RecoverableException);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // String Interface
+
+  BDE(const std::string& string) throw();
+
+  // Made explicit to avoid ambiguity with BDE(Integer).
+  explicit BDE(const char* cstring) throw();
+
+  BDE(const char* data, size_t length) throw();
+
+  BDE(const unsigned char* data, size_t length) throw();
+
+  // Returns true if the type of this object is String.
+  bool isString() const throw();
+
+  // Returns std::string. Requires this object to be String
+  const std::string& s() const throw(RecoverableException);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Dictionary Interface
+
+  // Returns true if the type of this object is Dict.
+  bool isDict() const throw();
+
+  // Returns the reference to BDE object associated with given key.
+  // If the key is not found, new pair with that key is created using default
+  // values, which is then returned. In other words, this is the same behavior
+  // of std::map's operator[].
+  // Requires this object to be Dict.
+  BDE& operator[](const std::string& key) throw(RecoverableException);
+
+  // Returns the const reference to BDE ojbect associated with given key.
+  // If the key is not found, BDE::none is returned.
+  // Requires this object to be Dict.
+  const BDE& operator[](const std::string& key) const
+    throw(RecoverableException);
+
+  // Returns true if the given key is found in dict.
+  // Requires this object to be Dict.
+  bool containsKey(const std::string& key) const throw(RecoverableException);
+
+  // Returns a read/write iterator that points to the first pair in the dict.
+  // Requires this object to be Dict.
+  Dict::iterator dictBegin() throw(RecoverableException);
+
+  // Returns a read/write read-only iterator that points to the first pair in
+  // the dict.
+  // Requires this object to be Dict.
+  Dict::const_iterator dictBegin() const throw(RecoverableException);
+
+  // Returns a read/write read-only iterator that points to one past the last
+  // pair in the dict.
+  // Requires this object to be Dict.
+  Dict::iterator dictEnd() throw(RecoverableException);
+
+  // Returns a read/write read-only iterator that points to one past the last
+  // pair in the dict.
+  // Requires this object to be Dict.
+  Dict::const_iterator dictEnd() const throw(RecoverableException);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // List Interface
+
+  // Returns true if the type of this object is List.
+  bool isList() const throw();
+
+  // Appends given bde to list. Required the type of this object to be List.
+  void append(const BDE& bde) throw(RecoverableException);
+
+  // Alias for append()
+  void operator<<(const BDE& bde) throw(RecoverableException);
+
+  // Returns the reference of the object at the given index. Required this
+  // object to be List.
+  BDE& operator[](size_t index) throw(RecoverableException);
+
+  // Returns the const reference of the object at the given index.
+  // Required this object to be List.
+  const BDE& operator[](size_t index) const throw(RecoverableException);
+
+  // Returns a read/write iterator that points to the first object in list.
+  // Required this object to be List.
+  List::iterator listBegin() throw(RecoverableException);
+
+  // Returns a read/write read-only iterator that points to the first object
+  // in list. Required this object to be List.
+  List::const_iterator listBegin() const throw(RecoverableException);
+
+  // Returns a read/write iterator that points to the one past the last object
+  // in list. Required this object to be List.
+  List::iterator listEnd() throw(RecoverableException);
+
+  // Returns a read/write read-only iterator that points to the one past the
+  // last object in list. Required this object to be List.
+  List::const_iterator listEnd() const throw(RecoverableException);
+
+  // For List type: Returns size of list.
+  // For Dict type: Returns size of dict.
+  size_t size() const throw(RecoverableException);
+
+  // For List type: Returns true if size of list is 0.
+  // For Dict type: Returns true if size of dict is 0.
+  bool empty() const throw(RecoverableException);
+};
+
+BDE decode(std::istream& in) throw(RecoverableException);
+
+// Decode the data in s.
+BDE decode(const std::string& s) throw(RecoverableException);
+
+BDE decode(const unsigned char* data, size_t length)
+  throw(RecoverableException);
+
+BDE decodeFromFile(const std::string& filename) throw(RecoverableException);
+
+std::string encode(const BDE& bde) throw();
+
+} // namespace bencode
+
+} // namespace aria2

+ 272 - 0
test/BencodeTest.cc

@@ -0,0 +1,272 @@
+#include "bencode.h"
+
+#include <cstring>
+#include <iostream>
+
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace aria2 {
+
+class BencodeTest:public CppUnit::TestFixture {
+
+  CPPUNIT_TEST_SUITE(BencodeTest);
+  CPPUNIT_TEST(testString);
+  CPPUNIT_TEST(testInteger);
+  CPPUNIT_TEST(testDict);
+  CPPUNIT_TEST(testDictIter);
+  CPPUNIT_TEST(testList);
+  CPPUNIT_TEST(testListIter);
+  CPPUNIT_TEST(testDecode);
+  CPPUNIT_TEST(testEncode);
+  CPPUNIT_TEST_SUITE_END();
+private:
+
+public:
+  void testString();
+  void testInteger();
+  void testDict();
+  void testDictIter();
+  void testList();
+  void testListIter();
+  void testDecode();
+  void testEncode();
+};
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION( BencodeTest );
+
+void BencodeTest::testString()
+{
+  bencode::BDE s(std::string("aria2"));
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2"), s.s());
+
+  unsigned char dataWithNull[] = { 0xf0, '\0', 0x0f };
+  bencode::BDE sWithNull(dataWithNull, sizeof(dataWithNull));
+  CPPUNIT_ASSERT(memcmp(dataWithNull, sWithNull.s().c_str(),
+			sizeof(dataWithNull)) == 0);
+
+  bencode::BDE zero("");
+  CPPUNIT_ASSERT_EQUAL(std::string(""), zero.s());
+}
+
+void BencodeTest::testInteger()
+{
+  bencode::BDE integer(INT64_MAX);
+  CPPUNIT_ASSERT_EQUAL(INT64_MAX, integer.i());
+}
+
+void BencodeTest::testDict()
+{
+  bencode::BDE dict = bencode::BDE::dict();
+  CPPUNIT_ASSERT(dict.empty());
+
+  dict["ki"] = 7;
+  dict["ks"] = std::string("abc");
+
+  CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), dict.size());
+  CPPUNIT_ASSERT(dict.containsKey("ki"));
+  CPPUNIT_ASSERT_EQUAL(static_cast<bencode::BDE::Integer>(7), dict["ki"].i());
+  CPPUNIT_ASSERT(dict.containsKey("ks"));
+  CPPUNIT_ASSERT_EQUAL(std::string("abc"), dict["ks"].s());
+
+  CPPUNIT_ASSERT(dict["kn"].isNone()); // This adds kn key with default value.
+  CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), dict.size());
+  CPPUNIT_ASSERT(dict.containsKey("kn"));
+
+  const bencode::BDE& ref = dict;
+  ref["kn2"]; // This doesn't add kn2 key.
+  CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), ref.size());
+  CPPUNIT_ASSERT(!ref.containsKey("kn2"));
+}
+
+void BencodeTest::testDictIter()
+{
+  bencode::BDE dict = bencode::BDE::dict();
+  dict["alpha2"] = std::string("alpha2");
+  dict["charlie"] = std::string("charlie");
+  dict["bravo"] = std::string("bravo");
+  dict["alpha"] = std::string("alpha");
+
+  bencode::BDE::Dict::iterator i = dict.dictBegin();
+  CPPUNIT_ASSERT_EQUAL(std::string("alpha"), (*i++).first); 
+  CPPUNIT_ASSERT_EQUAL(std::string("alpha2"), (*i++).first);
+  CPPUNIT_ASSERT_EQUAL(std::string("bravo"), (*i++).first);
+  CPPUNIT_ASSERT_EQUAL(std::string("charlie"), (*i++).first);
+  CPPUNIT_ASSERT(dict.dictEnd() == i);
+
+  const bencode::BDE& ref = dict;
+  bencode::BDE::Dict::const_iterator ci = ref.dictBegin();
+  CPPUNIT_ASSERT_EQUAL(std::string("alpha"), (*ci++).first);
+  std::advance(ci, 3);
+  CPPUNIT_ASSERT(ref.dictEnd() == ci);
+}
+
+void BencodeTest::testList()
+{
+  bencode::BDE list = bencode::BDE::list();
+  CPPUNIT_ASSERT(list.empty());
+  list << 7;
+  list << std::string("aria2");
+
+  CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), list.size());
+  CPPUNIT_ASSERT_EQUAL(static_cast<bencode::BDE::Integer>(7), list[0].i());
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2"), list[1].s());
+
+  const bencode::BDE& ref = list;
+  CPPUNIT_ASSERT_EQUAL(static_cast<bencode::BDE::Integer>(7), ref[0].i());
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2"), ref[1].s());
+}
+
+void BencodeTest::testListIter()
+{
+  bencode::BDE list = bencode::BDE::list();
+  list << std::string("alpha2");
+  list << std::string("charlie");
+  list << std::string("bravo");
+  list << std::string("alpha");
+
+  bencode::BDE::List::iterator i = list.listBegin();
+  CPPUNIT_ASSERT_EQUAL(std::string("alpha2"), (*i++).s());
+  CPPUNIT_ASSERT_EQUAL(std::string("charlie"), (*i++).s());
+  CPPUNIT_ASSERT_EQUAL(std::string("bravo"), (*i++).s());
+  CPPUNIT_ASSERT_EQUAL(std::string("alpha"), (*i++).s());
+  CPPUNIT_ASSERT(list.listEnd() == i);
+
+  const bencode::BDE& ref = list;
+  bencode::BDE::List::const_iterator ci = ref.listBegin();
+  CPPUNIT_ASSERT_EQUAL(std::string("alpha2"), (*ci++).s());
+  std::advance(ci, 3);
+  CPPUNIT_ASSERT(ref.listEnd() == ci);
+}
+
+void BencodeTest::testDecode()
+{
+  {
+    // string, integer and list in dict
+    bencode::BDE dict =
+      bencode::decode("d4:name5:aria24:sizei12345678900e5:filesl3:bin3:docee");
+    CPPUNIT_ASSERT(dict.isDict());
+    CPPUNIT_ASSERT_EQUAL(std::string("aria2"), dict["name"].s());
+    CPPUNIT_ASSERT_EQUAL(static_cast<bencode::BDE::Integer>(12345678900LL),
+			 dict["size"].i());
+    bencode::BDE list = dict["files"];
+    CPPUNIT_ASSERT(list.isList());
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), list.size());
+    CPPUNIT_ASSERT_EQUAL(std::string("bin"), list[0].s());
+    CPPUNIT_ASSERT_EQUAL(std::string("doc"), list[1].s());
+  }
+  {
+    // dict in list
+    bencode::BDE list = bencode::decode("ld1:ki123eee");
+    CPPUNIT_ASSERT(list.isList());
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), list.size());
+    bencode::BDE dict = list[0];
+    CPPUNIT_ASSERT(dict.isDict());
+    CPPUNIT_ASSERT_EQUAL(static_cast<bencode::BDE::Integer>(123),
+			 dict["k"].i());
+  }
+  {
+    // empty key is allowed
+    bencode::BDE s = bencode::decode("d0:1:ve");
+  }
+  {
+    // empty string
+    bencode::BDE s = bencode::decode("0:");
+    CPPUNIT_ASSERT_EQUAL(std::string(""), s.s());
+  }
+  {
+    // empty dict
+    bencode::BDE d = bencode::decode("de");
+    CPPUNIT_ASSERT(d.empty());
+  }
+  {
+    // empty list
+    bencode::BDE l = bencode::decode("le");
+    CPPUNIT_ASSERT(l.empty());
+  }
+  {
+    // integer, without ending 'e'
+    try {
+      bencode::decode("i3");
+      CPPUNIT_FAIL("exception must be thrown.");
+    } catch(RecoverableException& e) {
+      CPPUNIT_ASSERT_EQUAL(std::string("Delimiter 'e' not found."),
+			   std::string(e.what()));
+    }    
+  }
+  {
+    // dict, without ending 'e'
+    try {
+      bencode::decode("d");
+      CPPUNIT_FAIL("exception must be thrown.");
+    } catch(RecoverableException& e) {
+      CPPUNIT_ASSERT_EQUAL(std::string("Unexpected EOF in dict context."
+				       " 'e' expected."),
+			   std::string(e.what()));
+    }          
+  }
+  {
+    // list, without ending 'e'
+    try {
+      bencode::decode("l");
+      CPPUNIT_FAIL("exception must be thrown.");
+    } catch(RecoverableException& e) {
+      CPPUNIT_ASSERT_EQUAL(std::string("Unexpected EOF in list context."
+				       " 'e' expected."),
+			   std::string(e.what()));
+    }          
+  }
+  {
+    // string, less than the specified length.
+    try {
+      bencode::decode("3:ab");
+      CPPUNIT_FAIL("exception must be thrown.");
+    } catch(RecoverableException& e) {
+      CPPUNIT_ASSERT_EQUAL(std::string("Expected 3 bytes of data,"
+				       " but only 2 read."),
+			   std::string(e.what()));
+    }
+  }
+  {
+    // string, but length is invalid
+    try {
+      bencode::decode("x:abc");
+      CPPUNIT_FAIL("exception must be thrown.");
+    } catch(RecoverableException& e) {
+      CPPUNIT_ASSERT_EQUAL(std::string("Integer expected but none found."),
+			   std::string(e.what()));
+    }
+  }
+  {
+    // empty encoded data
+    CPPUNIT_ASSERT(bencode::decode("").isNone());
+  }
+  {
+    // ignore trailing garbage at the end of the input.
+    bencode::BDE s = bencode::decode("5:aria2trail");
+    CPPUNIT_ASSERT_EQUAL(std::string("aria2"), s.s());
+  }
+}
+
+void BencodeTest::testEncode()
+{
+  {
+    bencode::BDE dict = bencode::BDE::dict();
+    dict["name"] = std::string("aria2");
+    dict["loc"] = 80000;
+    dict["files"] = bencode::BDE::list();
+    dict["files"] << std::string("aria2c");
+    dict["attrs"] = bencode::BDE::dict();
+    dict["attrs"]["license"] = std::string("GPL");
+
+    CPPUNIT_ASSERT_EQUAL(std::string("d"
+				     "5:attrsd7:license3:GPLe"
+				     "5:filesl6:aria2ce"
+				     "3:loci80000e"
+				     "4:name5:aria2"
+				     "e"),
+			 bencode::encode(dict));
+  }
+}
+
+} // namespace aria2

+ 2 - 1
test/Makefile.am

@@ -63,7 +63,8 @@ aria2c_SOURCES = AllTest.cc\
 	FtpConnectionTest.cc\
 	OptionParserTest.cc\
 	SimpleDNSCacheTest.cc\
-	DownloadHelperTest.cc
+	DownloadHelperTest.cc\
+	BencodeTest.cc
 
 if HAVE_LIBZ
 aria2c_SOURCES += GZipDecoderTest.cc

+ 7 - 5
test/Makefile.in

@@ -197,7 +197,7 @@ am__aria2c_SOURCES_DIST = AllTest.cc TestUtil.cc TestUtil.h \
 	DirectDiskAdaptorTest.cc CookieTest.cc CookieStorageTest.cc \
 	TimeTest.cc CopyDiskAdaptorTest.cc FtpConnectionTest.cc \
 	OptionParserTest.cc SimpleDNSCacheTest.cc \
-	DownloadHelperTest.cc GZipDecoderTest.cc \
+	DownloadHelperTest.cc BencodeTest.cc GZipDecoderTest.cc \
 	Sqlite3MozCookieParserTest.cc MessageDigestHelperTest.cc \
 	IteratableChunkChecksumValidatorTest.cc \
 	IteratableChecksumValidatorTest.cc BtAllowedFastMessageTest.cc \
@@ -371,8 +371,8 @@ am_aria2c_OBJECTS = AllTest.$(OBJEXT) TestUtil.$(OBJEXT) \
 	TimeTest.$(OBJEXT) CopyDiskAdaptorTest.$(OBJEXT) \
 	FtpConnectionTest.$(OBJEXT) OptionParserTest.$(OBJEXT) \
 	SimpleDNSCacheTest.$(OBJEXT) DownloadHelperTest.$(OBJEXT) \
-	$(am__objects_1) $(am__objects_2) $(am__objects_3) \
-	$(am__objects_4) $(am__objects_5)
+	BencodeTest.$(OBJEXT) $(am__objects_1) $(am__objects_2) \
+	$(am__objects_3) $(am__objects_4) $(am__objects_5)
 aria2c_OBJECTS = $(am_aria2c_OBJECTS)
 am__DEPENDENCIES_1 =
 aria2c_DEPENDENCIES = ../src/libaria2c.a ../src/download_helper.o \
@@ -595,8 +595,9 @@ aria2c_SOURCES = AllTest.cc TestUtil.cc TestUtil.h SocketCoreTest.cc \
 	DirectDiskAdaptorTest.cc CookieTest.cc CookieStorageTest.cc \
 	TimeTest.cc CopyDiskAdaptorTest.cc FtpConnectionTest.cc \
 	OptionParserTest.cc SimpleDNSCacheTest.cc \
-	DownloadHelperTest.cc $(am__append_1) $(am__append_2) \
-	$(am__append_3) $(am__append_4) $(am__append_5)
+	DownloadHelperTest.cc BencodeTest.cc $(am__append_1) \
+	$(am__append_2) $(am__append_3) $(am__append_4) \
+	$(am__append_5)
 
 #aria2c_CXXFLAGS = ${CPPUNIT_CFLAGS} -I../src -I../lib -Wall -D_FILE_OFFSET_BITS=64
 #aria2c_LDFLAGS = ${CPPUNIT_LIBS}
@@ -692,6 +693,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AuthConfigFactoryTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BNodeTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Base64Test.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BencodeTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BencodeVisitorTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BitfieldManTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BtAllowedFastMessageTest.Po@am__quote@