mirror of https://github.com/aria2/aria2
436 lines
12 KiB
C++
436 lines
12 KiB
C++
/* <!-- 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 "HttpResponse.h"
|
|
#include "Request.h"
|
|
#include "Segment.h"
|
|
#include "HttpRequest.h"
|
|
#include "HttpHeader.h"
|
|
#include "Range.h"
|
|
#include "LogFactory.h"
|
|
#include "Logger.h"
|
|
#include "util.h"
|
|
#include "message.h"
|
|
#include "DlAbortEx.h"
|
|
#include "DlRetryEx.h"
|
|
#include "fmt.h"
|
|
#include "A2STR.h"
|
|
#include "CookieStorage.h"
|
|
#include "AuthConfigFactory.h"
|
|
#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"
|
|
#include "array_fun.h"
|
|
#include "MessageDigest.h"
|
|
#ifdef HAVE_ZLIB
|
|
#include "GZipDecodingStreamFilter.h"
|
|
#endif // HAVE_ZLIB
|
|
|
|
namespace aria2 {
|
|
|
|
HttpResponse::HttpResponse() : cuid_{0} {}
|
|
|
|
void HttpResponse::validateResponse() const
|
|
{
|
|
int statusCode = getStatusCode();
|
|
switch (statusCode) {
|
|
case 200: // OK
|
|
case 206: // Partial Content
|
|
if (!httpHeader_->defined(HttpHeader::TRANSFER_ENCODING)) {
|
|
// compare the received range against the requested range
|
|
auto responseRange = httpHeader_->getRange();
|
|
if (!httpRequest_->isRangeSatisfied(responseRange)) {
|
|
throw DL_ABORT_EX2(
|
|
fmt(EX_INVALID_RANGE_HEADER, httpRequest_->getStartByte(),
|
|
httpRequest_->getEndByte(), httpRequest_->getEntityLength(),
|
|
responseRange.startByte, responseRange.endByte,
|
|
responseRange.entityLength),
|
|
error_code::CANNOT_RESUME);
|
|
}
|
|
}
|
|
return;
|
|
case 304: // Not Modified
|
|
if (!httpRequest_->conditionalRequest()) {
|
|
throw DL_ABORT_EX2("Got 304 without If-Modified-Since or If-None-Match",
|
|
error_code::HTTP_PROTOCOL_ERROR);
|
|
}
|
|
return;
|
|
case 300: // Multiple Choices
|
|
case 301: // Moved Permanently
|
|
case 302: // Found
|
|
case 303: // See Other
|
|
case 307: // Temporary Redirect
|
|
case 308: // Permanent Redirect
|
|
if (!httpHeader_->defined(HttpHeader::LOCATION)) {
|
|
throw DL_ABORT_EX2(fmt(EX_LOCATION_HEADER_REQUIRED, statusCode),
|
|
error_code::HTTP_PROTOCOL_ERROR);
|
|
}
|
|
return;
|
|
}
|
|
if (statusCode >= 400) {
|
|
return;
|
|
}
|
|
throw DL_ABORT_EX2(fmt("Unexpected status %d", statusCode),
|
|
error_code::HTTP_PROTOCOL_ERROR);
|
|
}
|
|
|
|
std::string HttpResponse::determineFilename() const
|
|
{
|
|
std::string contentDisposition = util::getContentDispositionFilename(
|
|
httpHeader_->find(HttpHeader::CONTENT_DISPOSITION));
|
|
if (contentDisposition.empty()) {
|
|
auto file = httpRequest_->getFile();
|
|
file = util::percentDecode(file.begin(), file.end());
|
|
if (file.empty()) {
|
|
return Request::DEFAULT_FILE;
|
|
}
|
|
return file;
|
|
}
|
|
|
|
A2_LOG_INFO(
|
|
fmt(MSG_CONTENT_DISPOSITION_DETECTED, cuid_, contentDisposition.c_str()));
|
|
return contentDisposition;
|
|
}
|
|
|
|
void HttpResponse::retrieveCookie()
|
|
{
|
|
Time now;
|
|
auto r = httpHeader_->equalRange(HttpHeader::SET_COOKIE);
|
|
for (; r.first != r.second; ++r.first) {
|
|
httpRequest_->getCookieStorage()->parseAndStore(
|
|
(*r.first).second, httpRequest_->getHost(), httpRequest_->getDir(),
|
|
now.getTimeFromEpoch());
|
|
}
|
|
}
|
|
|
|
bool HttpResponse::isRedirect() const
|
|
{
|
|
switch (getStatusCode()) {
|
|
case 300: // Multiple Choices
|
|
case 301: // Moved Permanently
|
|
case 302: // Found
|
|
case 303: // See Other
|
|
case 307: // Temporary Redirect
|
|
case 308: // Permanent Redirect
|
|
return httpHeader_->defined(HttpHeader::LOCATION);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void HttpResponse::processRedirect()
|
|
{
|
|
const auto& req = httpRequest_->getRequest();
|
|
if (!req->redirectUri(util::percentEncodeMini(getRedirectURI()))) {
|
|
throw DL_RETRY_EX(fmt(
|
|
"CUID#%" PRId64 " - Redirect to %s failed. It may not be a valid URI.",
|
|
cuid_, req->getCurrentUri().c_str()));
|
|
}
|
|
|
|
A2_LOG_INFO(fmt(MSG_REDIRECT, cuid_,
|
|
httpRequest_->getRequest()->getCurrentUri().c_str()));
|
|
}
|
|
|
|
const std::string& HttpResponse::getRedirectURI() const
|
|
{
|
|
return httpHeader_->find(HttpHeader::LOCATION);
|
|
}
|
|
|
|
bool HttpResponse::isTransferEncodingSpecified() const
|
|
{
|
|
return httpHeader_->defined(HttpHeader::TRANSFER_ENCODING);
|
|
}
|
|
|
|
const std::string& HttpResponse::getTransferEncoding() const
|
|
{
|
|
// TODO See TODO in getTransferEncodingStreamFilter()
|
|
return httpHeader_->find(HttpHeader::TRANSFER_ENCODING);
|
|
}
|
|
|
|
std::unique_ptr<StreamFilter>
|
|
HttpResponse::getTransferEncodingStreamFilter() const
|
|
{
|
|
// TODO Transfer-Encoding header field can contains multiple tokens. We should
|
|
// parse the field and retrieve each token.
|
|
if (isTransferEncodingSpecified()) {
|
|
if (util::strieq(getTransferEncoding(), "chunked")) {
|
|
return make_unique<ChunkedDecodingStreamFilter>();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool HttpResponse::isContentEncodingSpecified() const
|
|
{
|
|
return httpHeader_->defined(HttpHeader::CONTENT_ENCODING);
|
|
}
|
|
|
|
const std::string& HttpResponse::getContentEncoding() const
|
|
{
|
|
return httpHeader_->find(HttpHeader::CONTENT_ENCODING);
|
|
}
|
|
|
|
std::unique_ptr<StreamFilter>
|
|
HttpResponse::getContentEncodingStreamFilter() const
|
|
{
|
|
#ifdef HAVE_ZLIB
|
|
if (util::strieq(getContentEncoding(), "gzip") ||
|
|
util::strieq(getContentEncoding(), "deflate")) {
|
|
return make_unique<GZipDecodingStreamFilter>();
|
|
}
|
|
#endif // HAVE_ZLIB
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int64_t HttpResponse::getContentLength() const
|
|
{
|
|
if (!httpHeader_) {
|
|
return 0;
|
|
}
|
|
|
|
return httpHeader_->getRange().getContentLength();
|
|
}
|
|
|
|
int64_t HttpResponse::getEntityLength() const
|
|
{
|
|
if (!httpHeader_) {
|
|
return 0;
|
|
}
|
|
|
|
return httpHeader_->getRange().entityLength;
|
|
}
|
|
|
|
std::string HttpResponse::getContentType() const
|
|
{
|
|
if (!httpHeader_) {
|
|
return A2STR::NIL;
|
|
}
|
|
|
|
const auto& ctype = httpHeader_->find(HttpHeader::CONTENT_TYPE);
|
|
auto i = std::find(ctype.begin(), ctype.end(), ';');
|
|
Scip p = util::stripIter(ctype.begin(), i);
|
|
return std::string(p.first, p.second);
|
|
}
|
|
|
|
void HttpResponse::setHttpHeader(std::unique_ptr<HttpHeader> httpHeader)
|
|
{
|
|
httpHeader_ = std::move(httpHeader);
|
|
}
|
|
|
|
const std::unique_ptr<HttpHeader>& HttpResponse::getHttpHeader() const
|
|
{
|
|
return httpHeader_;
|
|
}
|
|
|
|
void HttpResponse::setHttpRequest(std::unique_ptr<HttpRequest> httpRequest)
|
|
{
|
|
httpRequest_ = std::move(httpRequest);
|
|
}
|
|
|
|
int HttpResponse::getStatusCode() const { return httpHeader_->getStatusCode(); }
|
|
|
|
Time HttpResponse::getLastModifiedTime() const
|
|
{
|
|
return Time::parseHTTPDate(httpHeader_->find(HttpHeader::LAST_MODIFIED));
|
|
}
|
|
|
|
bool HttpResponse::supportsPersistentConnection() const
|
|
{
|
|
return httpHeader_->isKeepAlive();
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool parseMetalinkHttpLink(MetalinkHttpEntry& result, const std::string& s)
|
|
{
|
|
const auto first = std::find(s.begin(), s.end(), '<');
|
|
if (first == s.end()) {
|
|
return false;
|
|
}
|
|
|
|
auto last = std::find(first, s.end(), '>');
|
|
if (last == s.end()) {
|
|
return false;
|
|
}
|
|
|
|
auto p = util::stripIter(first + 1, last);
|
|
if (p.first == p.second) {
|
|
return false;
|
|
}
|
|
|
|
result.uri.assign(p.first, p.second);
|
|
last = std::find(last, s.end(), ';');
|
|
if (last != s.end()) {
|
|
++last;
|
|
}
|
|
bool ok = false;
|
|
while (1) {
|
|
std::string name, value;
|
|
auto r = util::nextParam(name, value, last, s.end(), ';');
|
|
last = r.first;
|
|
|
|
if (!r.second) {
|
|
break;
|
|
}
|
|
|
|
if (value.empty()) {
|
|
if (name == "pref") {
|
|
result.pref = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (name == "rel") {
|
|
if (value == "duplicate") {
|
|
ok = true;
|
|
}
|
|
else {
|
|
ok = false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (name == "pri") {
|
|
int32_t priValue;
|
|
if (util::parseIntNoThrow(priValue, value)) {
|
|
if (1 <= priValue && priValue <= 999999) {
|
|
result.pri = priValue;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (name == "geo") {
|
|
util::lowercase(value);
|
|
result.geo = value;
|
|
continue;
|
|
}
|
|
}
|
|
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 std::shared_ptr<Option>& option) const
|
|
{
|
|
auto p = httpHeader_->equalRange(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)) {
|
|
const std::string& loc = option->get(PREF_METALINK_LOCATION);
|
|
util::split(loc.begin(), loc.end(), std::back_inserter(locs), ',', true);
|
|
for (auto& l : locs) {
|
|
util::lowercase(l);
|
|
}
|
|
}
|
|
for (auto& r : result) {
|
|
if (std::find(locs.begin(), locs.end(), r.geo) != locs.end()) {
|
|
r.pri -= 999999;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::sort(result.begin(), result.end());
|
|
}
|
|
|
|
// Digest header field is defined by
|
|
// http://tools.ietf.org/html/rfc3230.
|
|
void HttpResponse::getDigest(std::vector<Checksum>& result) const
|
|
{
|
|
auto p = httpHeader_->equalRange(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;
|
|
auto r = util::nextParam(hashType, digest, itr, s.end(), ',');
|
|
itr = r.first;
|
|
if (!r.second) {
|
|
break;
|
|
}
|
|
|
|
util::lowercase(hashType);
|
|
digest = base64::decode(digest.begin(), digest.end());
|
|
if (!MessageDigest::supports(hashType) ||
|
|
MessageDigest::getDigestLength(hashType) != digest.size()) {
|
|
continue;
|
|
}
|
|
|
|
result.push_back(Checksum(hashType, digest));
|
|
}
|
|
}
|
|
|
|
std::sort(result.begin(), result.end(), HashTypeStronger());
|
|
std::vector<Checksum> temp;
|
|
for (auto i = result.begin(), eoi = result.end(); i != eoi;) {
|
|
bool ok = true;
|
|
auto j = i + 1;
|
|
for (; j != eoi; ++j) {
|
|
if ((*i).getHashType() != (*j).getHashType()) {
|
|
break;
|
|
}
|
|
|
|
if ((*i).getDigest() != (*j).getDigest()) {
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
if (ok) {
|
|
temp.push_back(*i);
|
|
}
|
|
i = j;
|
|
}
|
|
std::swap(temp, result);
|
|
}
|
|
|
|
} // namespace aria2
|