/* */ #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(bool contentDispositionUTF8) const { std::string contentDisposition = util::getContentDispositionFilename( httpHeader_->find(HttpHeader::CONTENT_DISPOSITION), contentDispositionUTF8); 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 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(); } } 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 HttpResponse::getContentEncodingStreamFilter() const { #ifdef HAVE_ZLIB if (util::strieq(getContentEncoding(), "gzip") || util::strieq(getContentEncoding(), "deflate")) { return make_unique(); } #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_ = std::move(httpHeader); } const std::unique_ptr& HttpResponse::getHttpHeader() const { return httpHeader_; } void HttpResponse::setHttpRequest(std::unique_ptr 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& result, const std::shared_ptr