Tatsuhiro Tsujikawa пре 14 година
родитељ
комит
c536e460aa

+ 21 - 0
src/Checksum.cc

@@ -33,6 +33,7 @@
  */
 /* copyright --> */
 #include "Checksum.h"
+#include "MessageDigest.h"
 
 namespace aria2 {
 
@@ -62,4 +63,24 @@ void Checksum::setAlgo(const std::string& algo)
   algo_ = algo;
 }
 
+void Checksum::swap(Checksum& other)
+{
+  using std::swap;
+  if(this != &other) {
+    swap(algo_, other.algo_);
+    swap(messageDigest_, other.messageDigest_);
+  }
+}
+
+void swap(Checksum& a, Checksum& b)
+{
+  a.swap(b);
+}
+
+bool HashTypeStronger::operator()
+  (const Checksum& lhs, const Checksum& rhs) const
+{
+  return MessageDigest::isStronger(lhs.getAlgo(), rhs.getAlgo());
+}
+
 } // namespace aria2

+ 9 - 0
src/Checksum.h

@@ -64,6 +64,15 @@ public:
   {
     return algo_;
   }
+
+  void swap(Checksum& other);
+};
+
+void swap(Checksum& a, Checksum& b);
+
+class HashTypeStronger {
+public:
+  bool operator()(const Checksum& lhs, const Checksum& rhs) const;
 };
 
 } // namespace aria2

+ 4 - 2
src/DownloadContext.cc

@@ -53,7 +53,8 @@ DownloadContext::DownloadContext():
   knowsTotalLength_(true),
   ownerRequestGroup_(0),
   downloadStartTime_(0),
-  downloadStopTime_(downloadStartTime_) {}
+  downloadStopTime_(downloadStartTime_),
+  metalinkServerContacted_(false) {}
 
 DownloadContext::DownloadContext(size_t pieceLength,
                                  uint64_t totalLength,
@@ -63,7 +64,8 @@ DownloadContext::DownloadContext(size_t pieceLength,
   knowsTotalLength_(true),
   ownerRequestGroup_(0),
   downloadStartTime_(0),
-  downloadStopTime_(0)
+  downloadStopTime_(0),
+  metalinkServerContacted_(false)
 {
   SharedHandle<FileEntry> fileEntry(new FileEntry(path, totalLength, 0));
   fileEntries_.push_back(fileEntry);

+ 12 - 0
src/DownloadContext.h

@@ -84,6 +84,9 @@ private:
   Timer downloadStopTime_;
 
   SharedHandle<Signature> signature_;
+  // This member variable is required to avoid to parse Metalink/HTTP
+  // Link header fields multiple times.
+  bool metalinkServerContacted_;
 public:
   DownloadContext();
 
@@ -224,6 +227,15 @@ public:
   SharedHandle<FileEntry> findFileEntryByOffset(off_t offset) const;
 
   void releaseRuntimeResource();
+
+  void setMetalinkServerContacted(bool f)
+  {
+    metalinkServerContacted_ = f;
+  }
+  bool getMetalinkServerContacted() const
+  {
+    return metalinkServerContacted_;
+  }
 };
 
 } // namespace aria2

+ 12 - 0
src/HttpHeader.cc

@@ -66,6 +66,10 @@ const std::string HttpHeader::LAST_MODIFIED("Last-Modified");
 
 const std::string HttpHeader::ACCEPT_ENCODING("Accept-Encoding");
 
+const std::string HttpHeader::LINK("Link");
+
+const std::string HttpHeader::DIGEST("Digest");
+
 const char HttpHeader::HTTP_1_1[] = "HTTP/1.1";
 const char HttpHeader::CLOSE[] = "close";
 const char HttpHeader::CHUNKED[] = "chunked";
@@ -110,6 +114,14 @@ std::vector<std::string> HttpHeader::get(const std::string& name) const
   return v;
 }
 
+std::pair<std::multimap<std::string, std::string>::const_iterator,
+          std::multimap<std::string, std::string>::const_iterator>
+HttpHeader::getIterator(const std::string& name) const
+{
+  std::string n(util::toLower(name));
+  return table_.equal_range(n);
+}
+
 unsigned int HttpHeader::getFirstAsUInt(const std::string& name) const {
   return getFirstAsULLInt(name);
 }

+ 7 - 0
src/HttpHeader.h

@@ -71,6 +71,9 @@ public:
   bool defined(const std::string& name) const;
   const std::string& getFirst(const std::string& name) const;
   std::vector<std::string> get(const std::string& name) const;
+  std::pair<std::multimap<std::string, std::string>::const_iterator,
+            std::multimap<std::string, std::string>::const_iterator>
+  getIterator(const std::string& name) const;
   unsigned int getFirstAsUInt(const std::string& name) const;
   uint64_t getFirstAsULLInt(const std::string& name) const;
 
@@ -136,6 +139,10 @@ public:
 
   static const std::string ACCEPT_ENCODING;
 
+  static const std::string LINK;
+
+  static const std::string DIGEST;
+
   static const char HTTP_1_1[];
 
   static const char CLOSE[];

+ 152 - 0
src/HttpResponse.cc

@@ -51,6 +51,15 @@
 #include "AuthConfig.h"
 #include "ChunkedDecodingStreamFilter.h"
 #include "error_code.h"
+#include "prefs.h"
+#include "Option.h"
+#include "Checksum.h"
+#include "uri.h"
+#include "MetalinkHttpEntry.h"
+#include "Base64.h"
+#ifdef ENABLE_MESSAGE_DIGEST
+#include "MessageDigest.h"
+#endif // ENABLE_MESSAGE_DIGEST
 #ifdef HAVE_ZLIB
 # include "GZipDecodingStreamFilter.h"
 #endif // HAVE_ZLIB
@@ -286,4 +295,147 @@ bool HttpResponse::supportsPersistentConnection() const
      != std::string::npos);
 }
 
+namespace {
+bool parseMetalinkHttpLink(MetalinkHttpEntry& result, const std::string& s)
+{
+  std::string::const_iterator first = std::find(s.begin(), s.end(), '<');
+  if(first == s.end()) {
+    return false;
+  }
+  std::string::const_iterator last = std::find(first, s.end(), '>');
+  if(last == s.end()) {
+    return false;
+  }
+  std::string uri = util::stripIter(first+1, last);
+  if(uri.empty()) {
+    return false;
+  } else {
+    result.uri = uri;
+  }
+  last = std::find(last, s.end(), ';');
+  if(last != s.end()) {
+    ++last;
+  }
+  bool ok = false;
+  while(1) {
+    std::string name, value;
+    std::pair<std::string::const_iterator, bool> r =
+      util::nextParam(name, value, last, s.end(), ';');
+    last = r.first;
+    if(!r.second) {
+      break;
+    }
+    if(value.empty()) {
+      if(name == "pref") {
+        result.pref = true;
+      }
+    } else {
+      if(name == "rel") {
+        if(value == "duplicate") {
+          ok = true;
+        } else {
+          ok = false;
+        }
+      } else if(name == "pri") {
+        int32_t priValue;
+        if(util::parseIntNoThrow(priValue, value)) {
+          if(1 <= priValue && priValue <= 999999) {
+            result.pri = priValue;
+          }
+        }
+      } else if(name == "geo") {
+        util::lowercase(value);
+        result.geo = value;
+      }
+    }
+  }
+  return ok;
+}
+} // namespace
+
+// Metalink/HTTP is defined by http://tools.ietf.org/html/rfc6249.
+// Link header field is defined by http://tools.ietf.org/html/rfc5988.
+void HttpResponse::getMetalinKHttpEntries
+(std::vector<MetalinkHttpEntry>& result,
+ const SharedHandle<Option>& option) const
+{
+  std::pair<std::multimap<std::string, std::string>::const_iterator,
+            std::multimap<std::string, std::string>::const_iterator> p =
+    httpHeader_->getIterator(HttpHeader::LINK);
+  for(; p.first != p.second; ++p.first) {
+    MetalinkHttpEntry e;
+    if(parseMetalinkHttpLink(e, (*p.first).second)) {
+      result.push_back(e);
+    }
+  }
+  if(!result.empty()) {
+    std::vector<std::string> locs;
+    if(option->defined(PREF_METALINK_LOCATION)) {
+      util::split(util::toLower(option->get(PREF_METALINK_LOCATION)),
+                  std::back_inserter(locs), ",", true);
+    }
+    for(std::vector<MetalinkHttpEntry>::iterator i = result.begin(),
+          eoi = result.end(); i != eoi; ++i) {
+      if(std::find(locs.begin(), locs.end(), (*i).geo) != locs.end()) {
+        (*i).pri -= 999999;
+      }
+    }
+  }
+  std::sort(result.begin(), result.end());
+}
+
+#ifdef ENABLE_MESSAGE_DIGEST
+// Digest header field is defined by
+// http://tools.ietf.org/html/rfc3230.
+void HttpResponse::getDigest(std::vector<Checksum>& result) const
+{
+  using std::swap;
+  std::pair<std::multimap<std::string, std::string>::const_iterator,
+            std::multimap<std::string, std::string>::const_iterator> p =
+    httpHeader_->getIterator(HttpHeader::DIGEST);
+  for(; p.first != p.second; ++p.first) {
+    const std::string& s = (*p.first).second;
+    std::string::const_iterator itr = s.begin();
+    while(1) {
+      std::string hashType, digest;
+      std::pair<std::string::const_iterator, bool> r =
+        util::nextParam(hashType, digest, itr, s.end(), ',');
+      itr = r.first;
+      if(!r.second) {
+        break;
+      }
+      util::lowercase(hashType);
+      if(!MessageDigest::supports(hashType)) {
+        continue;
+      }
+      std::string hexDigest = util::toHex(Base64::decode(digest));
+      if(!MessageDigest::isValidHash(hashType, hexDigest)) {
+        continue;
+      }
+      result.push_back(Checksum(hashType, hexDigest));
+    }
+  }
+  std::sort(result.begin(), result.end(), HashTypeStronger());
+  std::vector<Checksum> temp;
+  for(std::vector<Checksum>::iterator i = result.begin(),
+        eoi = result.end(); i != eoi;) {
+    bool ok = true;
+    std::vector<Checksum>::iterator j = i+1;
+    for(; j != eoi; ++j) {
+      if((*i).getAlgo() != (*j).getAlgo()) {
+        break;
+      }
+      if((*i).getMessageDigest() != (*j).getMessageDigest()) {
+        ok = false;
+      }
+    }
+    if(ok) {
+      temp.push_back(*i);
+    }
+    i = j;
+  }
+  swap(temp, result);
+}
+#endif // ENABLE_MESSAGE_DIGEST
+
 } // namespace aria2

+ 15 - 0
src/HttpResponse.h

@@ -38,6 +38,7 @@
 #include "common.h"
 
 #include <stdint.h>
+#include <vector>
 
 #include "SharedHandle.h"
 #include "TimeA2.h"
@@ -48,6 +49,9 @@ namespace aria2 {
 class HttpRequest;
 class HttpHeader;
 class StreamFilter;
+class MetalinkHttpEntry;
+class Option;
+class Checksum;
 
 class HttpResponse {
 private:
@@ -127,6 +131,17 @@ public:
   Time getLastModifiedTime() const;
 
   bool supportsPersistentConnection() const;
+
+  void getMetalinKHttpEntries
+  (std::vector<MetalinkHttpEntry>& result,
+   const SharedHandle<Option>& option) const;
+#ifdef ENABLE_MESSAGE_DIGEST
+  // Returns all digests specified in Digest header field.  Sort
+  // strong algorithm first. Strength is defined in MessageDigest. If
+  // several same digest algorithms are available, but they have
+  // different value, they are all ignored.
+  void getDigest(std::vector<Checksum>& result) const;
+#endif // ENABLE_MESSAGE_DIGEST
 };
 
 } // namespace aria2

+ 70 - 0
src/HttpResponseCommand.cc

@@ -72,6 +72,10 @@
 #include "ChunkedDecodingStreamFilter.h"
 #include "uri.h"
 #include "SocketRecvBuffer.h"
+#include "MetalinkHttpEntry.h"
+#ifdef ENABLE_MESSAGE_DIGEST
+#include "Checksum.h"
+#endif // ENABLE_MESSAGE_DIGEST
 #ifdef HAVE_ZLIB
 # include "GZipDecodingStreamFilter.h"
 #endif // HAVE_ZLIB
@@ -189,6 +193,42 @@ bool HttpResponseCommand::executeInternal()
     getFileEntry()->poolRequest(getRequest());
     return true;
   }
+  if(!getPieceStorage()) {
+    // Metalink/HTTP
+    if(!getDownloadContext()->getMetalinkServerContacted()) {
+      if(httpHeader->defined(HttpHeader::LINK)) {
+        getDownloadContext()->setMetalinkServerContacted(true);
+        std::vector<MetalinkHttpEntry> entries;
+        httpResponse->getMetalinKHttpEntries(entries, getOption());
+        for(std::vector<MetalinkHttpEntry>::iterator i = entries.begin(),
+              eoi = entries.end(); i != eoi; ++i) {
+          getFileEntry()->addUri((*i).uri);
+          A2_LOG_DEBUG(fmt("Adding URI=%s", (*i).uri.c_str()));
+        }
+      }
+    }
+#ifdef ENABLE_MESSAGE_DIGEST
+    if(httpHeader->defined(HttpHeader::DIGEST)) {
+      std::vector<Checksum> checksums;
+      httpResponse->getDigest(checksums);
+      for(std::vector<Checksum>::iterator i = checksums.begin(),
+            eoi = checksums.end(); i != eoi; ++i) {
+        if(getDownloadContext()->getChecksumHashAlgo().empty()) {
+          A2_LOG_DEBUG(fmt("Setting digest: type=%s, digest=%s",
+                           (*i).getAlgo().c_str(),
+                           (*i).getMessageDigest().c_str()));
+          getDownloadContext()->setChecksumHashAlgo((*i).getAlgo());
+          getDownloadContext()->setChecksum((*i).getMessageDigest());
+          break;
+        } else {
+          if(checkChecksum(getDownloadContext(), *i)) {
+            break;
+          }
+        }
+      }
+    }
+#endif // ENABLE_MESSAGE_DIGEST
+  }
   if(statusCode >= 300) {
     if(statusCode == 404) {
       getRequestGroup()->increaseAndValidateFileNotFoundCount();
@@ -241,6 +281,19 @@ bool HttpResponseCommand::executeInternal()
       return handleDefaultEncoding(httpResponse);
     }
   } else {
+#ifdef ENABLE_MESSAGE_DIGEST
+    if(!getDownloadContext()->getChecksumHashAlgo().empty() &&
+       httpHeader->defined(HttpHeader::DIGEST)) {
+      std::vector<Checksum> checksums;
+      httpResponse->getDigest(checksums);
+      for(std::vector<Checksum>::iterator i = checksums.begin(),
+            eoi = checksums.end(); i != eoi; ++i) {
+        if(checkChecksum(getDownloadContext(), *i)) {
+          break;
+        }
+      }
+    }
+#endif // ENABLE_MESSAGE_DIGEST
     // validate totalsize
     getRequestGroup()->validateTotalLength(getFileEntry()->getLength(),
                                        httpResponse->getEntityLength());
@@ -501,4 +554,21 @@ void HttpResponseCommand::onDryRunFileFound()
   poolConnection();
 }
 
+#ifdef ENABLE_MESSAGE_DIGEST
+bool HttpResponseCommand::checkChecksum
+(const SharedHandle<DownloadContext>& dctx,
+ const Checksum& checksum)
+{
+  if(dctx->getChecksumHashAlgo() == checksum.getAlgo()) {
+    if(dctx->getChecksum() == checksum.getMessageDigest()) {
+      A2_LOG_INFO("Valid hash found in Digest header field.");
+      return true;
+    } else {
+      throw DL_ABORT_EX("Invalid hash found in Digest header field.");
+    }
+  }
+  return false;
+}
+#endif // ENABLE_MESSAGE_DIGEST
+
 } // namespace aria2

+ 11 - 0
src/HttpResponseCommand.h

@@ -45,6 +45,9 @@ class HttpDownloadCommand;
 class HttpResponse;
 class SocketCore;
 class StreamFilter;
+#ifdef ENABLE_MESSAGE_DIGEST
+class Checksum;
+#endif // ENABLE_MESSAGE_DIGEST
 
 // HttpResponseCommand receives HTTP response header from remote
 // server.  Because network I/O is non-blocking, execute() returns
@@ -74,6 +77,14 @@ private:
   void poolConnection();
 
   void onDryRunFileFound();
+#ifdef ENABLE_MESSAGE_DIGEST
+  // Returns true if dctx and checksum has same hash type and hash
+  // value.  If they have same hash type but different hash value,
+  // throws exception.  Otherwise returns false.
+  bool checkChecksum
+  (const SharedHandle<DownloadContext>& dctx,
+   const Checksum& checksum);
+#endif // ENABLE_MESSAGE_DIGEST
 protected:
   bool executeInternal();
 

+ 2 - 1
src/Makefile.am

@@ -224,7 +224,8 @@ SRCS =  Socket.h\
 	HttpServer.cc HttpServer.h\
 	StreamPieceSelector.h\
 	DefaultStreamPieceSelector.cc DefaultStreamPieceSelector.h\
-	InorderStreamPieceSelector.cc InorderStreamPieceSelector.h
+	InorderStreamPieceSelector.cc InorderStreamPieceSelector.h\
+	MetalinkHttpEntry.cc MetalinkHttpEntry.h
 
 if ENABLE_XML_RPC
 SRCS += XmlRpcRequestParserController.cc XmlRpcRequestParserController.h\

+ 71 - 0
src/MetalinkHttpEntry.cc

@@ -0,0 +1,71 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2011 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 "MetalinkHttpEntry.h"
+
+namespace aria2 {
+
+MetalinkHttpEntry::MetalinkHttpEntry()
+  : pri(999999),
+    pref(false)
+{}
+
+MetalinkHttpEntry::~MetalinkHttpEntry() {}
+
+void MetalinkHttpEntry::swap(MetalinkHttpEntry& other)
+{
+  using std::swap;
+  if(this != &other) {
+    swap(uri, other.uri);
+    swap(pri, other.pri);
+    swap(pref, other.pref);
+    swap(geo, other.geo);
+  }
+}
+
+bool MetalinkHttpEntry::operator<(const MetalinkHttpEntry& rhs) const
+{
+  if(pref^rhs.pref) {
+    return pref;
+  } else {
+    return pri < rhs.pri;
+  }
+}
+
+void swap(MetalinkHttpEntry& a, MetalinkHttpEntry& b)
+{
+  a.swap(b);
+}
+
+} // namespace aria2

+ 62 - 0
src/MetalinkHttpEntry.h

@@ -0,0 +1,62 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2011 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 --> */
+#ifndef D_METALINK_HTTP_ENTRY_H
+#define D_METALINK_HTTP_ENTRY_H
+
+#include "common.h"
+
+#include <string>
+
+namespace aria2 {
+
+// Holds values of Metalink/HTTP in Link header fields.  Metalink/HTTP
+// is defined by http://tools.ietf.org/html/rfc6249
+struct MetalinkHttpEntry {
+  std::string uri;
+  int pri;
+  bool pref;
+  std::string geo;
+
+  MetalinkHttpEntry();
+  ~MetalinkHttpEntry();
+  void swap(MetalinkHttpEntry& c);
+  bool operator<(const MetalinkHttpEntry& rhs) const;
+};
+
+void swap(MetalinkHttpEntry& a, MetalinkHttpEntry& b);
+
+} // namespace aria2
+
+#endif // D_METALINK_HTTP_ENTRY_H

+ 54 - 0
src/util.h

@@ -438,6 +438,60 @@ std::string makeString(const char* str);
 // strerror returns NULL, this function returns empty string.
 std::string safeStrerror(int errNum);
 
+// Parses sequence [first, last) and find name=value pair delimited by
+// delim character. If name(and optionally value) is found, returns
+// pair of iterator which can use as first parameter of next call of
+// this function, and true. If no name is found, returns the pair of
+// last and false.
+template<typename Iterator>
+std::pair<Iterator, bool>
+nextParam
+(std::string& name,
+ std::string& value,
+ Iterator first,
+ Iterator last,
+ char delim)
+{
+  Iterator end = last;
+  while(first != end) {
+    last = first;
+    Iterator parmnameFirst = first;
+    Iterator parmnameLast = first;
+    bool eqFound = false;
+    for(; last != end; ++last) {
+      if(*last == delim) {
+        break;
+      } else if(!eqFound && *last == '=') {
+        eqFound = true;
+        parmnameFirst = first;
+        parmnameLast = last;
+      }
+    }
+    std::string tname, tvalue;
+    if(parmnameFirst == parmnameLast) {
+      if(!eqFound) {
+        parmnameFirst = first;
+        parmnameLast = last;
+        tname = util::stripIter(parmnameFirst, parmnameLast);
+      }
+    } else {
+      first = parmnameLast+1;
+      tname = util::stripIter(parmnameFirst, parmnameLast);
+      tvalue = util::stripIter(first, last);
+    }
+    if(last != end) {
+      ++last;
+    }
+    if(!tname.empty()) {
+      name.swap(tname);
+      value.swap(tvalue);
+      return std::make_pair(last, true);
+    }
+    first = last;
+  }
+  return std::make_pair(end, false);
+}
+
 } // namespace util
 
 } // namespace aria2

+ 92 - 0
test/HttpResponseTest.cc

@@ -17,6 +17,9 @@
 #include "AuthConfigFactory.h"
 #include "AuthConfig.h"
 #include "StreamFilter.h"
+#include "MetalinkHttpEntry.h"
+#include "Option.h"
+#include "Checksum.h"
 
 namespace aria2 {
 
@@ -49,6 +52,10 @@ class HttpResponseTest : public CppUnit::TestFixture {
   CPPUNIT_TEST(testProcessRedirect);
   CPPUNIT_TEST(testRetrieveCookie);
   CPPUNIT_TEST(testSupportsPersistentConnection);
+  CPPUNIT_TEST(testGetMetalinKHttpEntries);
+#ifdef ENABLE_MESSAGE_DIGEST
+  CPPUNIT_TEST(testGetDigest);
+#endif // ENABLE_MESSAGE_DIGEST
   CPPUNIT_TEST_SUITE_END();
 private:
 
@@ -81,6 +88,10 @@ public:
   void testProcessRedirect();
   void testRetrieveCookie();
   void testSupportsPersistentConnection();
+  void testGetMetalinKHttpEntries();
+#ifdef ENABLE_MESSAGE_DIGEST
+  void testGetDigest();
+#endif // ENABLE_MESSAGE_DIGEST
 };
 
 
@@ -590,4 +601,85 @@ void HttpResponseTest::testSupportsPersistentConnection()
   httpHeader->clearField();
 }
 
+void HttpResponseTest::testGetMetalinKHttpEntries()
+{
+  HttpResponse httpResponse;
+  SharedHandle<HttpHeader> httpHeader(new HttpHeader());
+  httpResponse.setHttpHeader(httpHeader);
+  SharedHandle<Option> option(new Option());
+
+  httpHeader->put("Link", "<http://uri1/>; rel=duplicate; pri=1; pref; geo=JP");
+  httpHeader->put("Link", "<http://uri2/>; rel=duplicate");
+  httpHeader->put("Link", "<http://uri3/>;;;;;;;;rel=duplicate;;;;;pri=2;;;;;");
+  httpHeader->put("Link", "<http://uri4/>;rel=duplicate;=pri=1;pref");
+  httpHeader->put("Link", "<http://describedby>; rel=describedby");
+  httpHeader->put("Link", "<http://norel/>");
+  httpHeader->put("Link", "<baduri>; rel=duplicate; pri=-1;");
+  std::vector<MetalinkHttpEntry> result;
+  httpResponse.getMetalinKHttpEntries(result, option);
+  CPPUNIT_ASSERT_EQUAL((size_t)5, result.size());
+
+  MetalinkHttpEntry e = result[0];
+  CPPUNIT_ASSERT_EQUAL(std::string("http://uri1/"), e.uri);
+  CPPUNIT_ASSERT_EQUAL(1, e.pri);
+  CPPUNIT_ASSERT(e.pref);
+  CPPUNIT_ASSERT_EQUAL(std::string("jp"), e.geo);
+
+  e = result[1];
+  CPPUNIT_ASSERT_EQUAL(std::string("http://uri4/"), e.uri);
+  CPPUNIT_ASSERT_EQUAL(999999, e.pri);
+  CPPUNIT_ASSERT(e.pref);
+  CPPUNIT_ASSERT(e.geo.empty());
+
+  e = result[2];
+  CPPUNIT_ASSERT_EQUAL(std::string("http://uri3/"), e.uri);
+  CPPUNIT_ASSERT_EQUAL(2, e.pri);
+  CPPUNIT_ASSERT(!e.pref);
+  CPPUNIT_ASSERT(e.geo.empty());
+
+  e = result[3];
+  CPPUNIT_ASSERT_EQUAL(std::string("http://uri2/"), e.uri);
+  CPPUNIT_ASSERT_EQUAL(999999, e.pri);
+  CPPUNIT_ASSERT(!e.pref);
+  CPPUNIT_ASSERT(e.geo.empty());
+
+  e = result[4];
+  CPPUNIT_ASSERT_EQUAL(std::string("baduri"), e.uri);
+  CPPUNIT_ASSERT_EQUAL(999999, e.pri);
+  CPPUNIT_ASSERT(!e.pref);
+  CPPUNIT_ASSERT(e.geo.empty());
+}
+
+#ifdef ENABLE_MESSAGE_DIGEST
+void HttpResponseTest::testGetDigest()
+{
+  HttpResponse httpResponse;
+  SharedHandle<HttpHeader> httpHeader(new HttpHeader());
+  httpResponse.setHttpHeader(httpHeader);
+  SharedHandle<Option> option(new Option());
+  // Python binascii.hexlify(base64.b64decode(B64ED_HASH)) is handy to
+  // retrieve ascii hex hash string.
+  httpHeader->put("Digest", "SHA-1=82AD8itGL/oYQ5BTPFANiYnp9oE=");
+  httpHeader->put("Digest", "NOT_SUPPORTED");
+  httpHeader->put("Digest", "SHA-224=rQdowoLHQJTMVZ3rF7vmYOIzUXlu7F+FcMbPnA==");
+  httpHeader->put("Digest", "SHA-224=6Ik6LNZ1iPy6cbmlKO4NHfvxzaiurmHilMyhGA==");
+  httpHeader->put("Digest",
+                  "SHA-256=+D8nGudz3G/kpkVKQeDrI3xD57v0UeQmzGCZOk03nsU=,"
+                  "MD5=LJDK2+9ClF8Nz/K5WZd/+A==");
+  std::vector<Checksum> result;
+  httpResponse.getDigest(result);
+  CPPUNIT_ASSERT_EQUAL((size_t)3, result.size());
+
+  Checksum c = result[0];
+  CPPUNIT_ASSERT_EQUAL(std::string("sha-256"), c.getAlgo());
+  CPPUNIT_ASSERT_EQUAL(std::string("f83f271ae773dc6fe4a6454a41e0eb237c43e7bbf451e426cc60993a4d379ec5"),
+                       c.getMessageDigest());
+
+  c = result[1];
+  CPPUNIT_ASSERT_EQUAL(std::string("sha-1"), c.getAlgo());
+  CPPUNIT_ASSERT_EQUAL(std::string("f36003f22b462ffa184390533c500d8989e9f681"),
+                       c.getMessageDigest());
+}
+#endif // ENABLE_MESSAGE_DIGEST
+
 } // namespace aria2

+ 40 - 0
test/UtilTest.cc

@@ -71,6 +71,7 @@ class UtilTest:public CppUnit::TestFixture {
   CPPUNIT_TEST(testGetCidrPrefix);
   CPPUNIT_TEST(testInSameCidrBlock);
   CPPUNIT_TEST(testIsUtf8String);
+  CPPUNIT_TEST(testNextParam);
   CPPUNIT_TEST_SUITE_END();
 private:
 
@@ -130,6 +131,7 @@ public:
   void testGetCidrPrefix();
   void testInSameCidrBlock();
   void testIsUtf8String();
+  void testNextParam();
 };
 
 
@@ -1247,4 +1249,42 @@ void UtilTest::testIsUtf8String()
   CPPUNIT_ASSERT(!util::isUtf8(util::fromHex("00")));
 }
 
+void UtilTest::testNextParam()
+{
+  std::string s1 = "    :a  :  b=c :d=b::::g::";
+  std::pair<std::string::iterator, bool> r;
+  std::string name, value;
+  r = util::nextParam(name, value, s1.begin(), s1.end(), ':');
+  CPPUNIT_ASSERT(r.second);
+  CPPUNIT_ASSERT_EQUAL(std::string("a"), name);
+  CPPUNIT_ASSERT_EQUAL(std::string(), value);
+
+  r = util::nextParam(name, value, r.first, s1.end(), ':');
+  CPPUNIT_ASSERT(r.second);
+  CPPUNIT_ASSERT_EQUAL(std::string("b"), name);
+  CPPUNIT_ASSERT_EQUAL(std::string("c"), value);
+
+  r = util::nextParam(name, value, r.first, s1.end(), ':');
+  CPPUNIT_ASSERT(r.second);
+  CPPUNIT_ASSERT_EQUAL(std::string("d"), name);
+  CPPUNIT_ASSERT_EQUAL(std::string("b"), value);
+
+  r = util::nextParam(name, value, r.first, s1.end(), ':');
+  CPPUNIT_ASSERT(r.second);
+  CPPUNIT_ASSERT_EQUAL(std::string("g"), name);
+  CPPUNIT_ASSERT_EQUAL(std::string(), value);
+
+  std::string s2 = "";
+  r = util::nextParam(name, value, s2.begin(), s2.end(), ':');
+  CPPUNIT_ASSERT(!r.second);
+
+  std::string s3 = "   ";
+  r = util::nextParam(name, value, s3.begin(), s3.end(), ':');
+  CPPUNIT_ASSERT(!r.second);
+
+  std::string s4 = ":::";
+  r = util::nextParam(name, value, s4.begin(), s4.end(), ':');
+  CPPUNIT_ASSERT(!r.second);
+}
+
 } // namespace aria2