Added Metalink/HTTP Link and Digest header field parser.

pull/1/head
Tatsuhiro Tsujikawa 2011-07-26 23:41:21 +09:00
parent 9ff60ac477
commit 7c317de4e7
9 changed files with 363 additions and 2 deletions

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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[];

View File

@ -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,134 @@ 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(first+1, last);
uri::UriStruct us;
if(uri::parse(us, uri)) {
result.uri = uri;
} else {
return false;
}
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.
SharedHandle<Checksum> HttpResponse::getDigest() const
{
SharedHandle<Checksum> res;
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;
}
if(!res) {
res.reset(new Checksum(hashType, hexDigest));
} else if(MessageDigest::isStronger(hashType, res->getAlgo())) {
res->setAlgo(hashType);
res->setMessageDigest(hexDigest);
}
}
}
return res;
}
#endif // ENABLE_MESSAGE_DIGEST
} // namespace aria2

View File

@ -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 digest specified in Digest header field. If multiple
// digest algorithm is available, use strongest one defined in
// MessageDigest. If several same digest algorithms are available,
// but they have different value, they are all ignored.
SharedHandle<Checksum> getDigest() const;
#endif // ENABLE_MESSAGE_DIGEST
};
} // namespace aria2

View File

@ -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

View File

@ -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,73 @@ 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", "<baduri>; rel=duplicate");
httpHeader->put("Link", "<http://norel/>");
httpHeader->put("Link", "<http://badpri/>; 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("http://badpri/"), 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());
httpHeader->put("Digest", "SHA-1=82AD8itGL/oYQ5BTPFANiYnp9oE=");
httpHeader->put("Digest", "NOT_SUPPORTED");
httpHeader->put("Digest",
"SHA-256=+D8nGudz3G/kpkVKQeDrI3xD57v0UeQmzGCZOk03nsU=,"
"MD5=LJDK2+9ClF8Nz/K5WZd/+A==");
SharedHandle<Checksum> c = httpResponse.getDigest();
CPPUNIT_ASSERT(c);
CPPUNIT_ASSERT_EQUAL(std::string("sha-256"), c->getAlgo());
}
#endif // ENABLE_MESSAGE_DIGEST
} // namespace aria2

View File

@ -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