mirror of https://github.com/aria2/aria2
938 lines
31 KiB
C++
938 lines
31 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 "AbstractCommand.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "Request.h"
|
|
#include "DownloadEngine.h"
|
|
#include "Option.h"
|
|
#include "PeerStat.h"
|
|
#include "SegmentMan.h"
|
|
#include "Logger.h"
|
|
#include "Segment.h"
|
|
#include "DlAbortEx.h"
|
|
#include "DlRetryEx.h"
|
|
#include "DownloadFailureException.h"
|
|
#include "CreateRequestCommand.h"
|
|
#include "InitiateConnectionCommandFactory.h"
|
|
#include "SleepCommand.h"
|
|
#include "StreamCheckIntegrityEntry.h"
|
|
#include "PieceStorage.h"
|
|
#include "SocketCore.h"
|
|
#include "message.h"
|
|
#include "prefs.h"
|
|
#include "fmt.h"
|
|
#include "ServerStat.h"
|
|
#include "RequestGroupMan.h"
|
|
#include "A2STR.h"
|
|
#include "util.h"
|
|
#include "LogFactory.h"
|
|
#include "DownloadContext.h"
|
|
#include "wallclock.h"
|
|
#include "NameResolver.h"
|
|
#include "uri.h"
|
|
#include "FileEntry.h"
|
|
#include "error_code.h"
|
|
#include "SocketRecvBuffer.h"
|
|
#ifdef ENABLE_ASYNC_DNS
|
|
#include "AsyncNameResolver.h"
|
|
#endif // ENABLE_ASYNC_DNS
|
|
#ifdef ENABLE_MESSAGE_DIGEST
|
|
# include "ChecksumCheckIntegrityEntry.h"
|
|
#endif // ENABLE_MESSAGE_DIGEST
|
|
|
|
namespace aria2 {
|
|
|
|
AbstractCommand::AbstractCommand
|
|
(cuid_t cuid,
|
|
const SharedHandle<Request>& req,
|
|
const SharedHandle<FileEntry>& fileEntry,
|
|
RequestGroup* requestGroup,
|
|
DownloadEngine* e,
|
|
const SharedHandle<SocketCore>& s,
|
|
const SharedHandle<SocketRecvBuffer>& socketRecvBuffer,
|
|
bool incNumConnection)
|
|
: Command(cuid), checkPoint_(global::wallclock()),
|
|
timeout_(requestGroup->getTimeout()),
|
|
requestGroup_(requestGroup),
|
|
req_(req), fileEntry_(fileEntry), e_(e), socket_(s),
|
|
socketRecvBuffer_(socketRecvBuffer),
|
|
checkSocketIsReadable_(false), checkSocketIsWritable_(false),
|
|
nameResolverCheck_(false),
|
|
incNumConnection_(incNumConnection),
|
|
serverStatTimer_(global::wallclock())
|
|
{
|
|
if(socket_ && socket_->isOpen()) {
|
|
setReadCheckSocket(socket_);
|
|
}
|
|
if(incNumConnection_) {
|
|
requestGroup->increaseStreamConnection();
|
|
}
|
|
requestGroup_->increaseStreamCommand();
|
|
requestGroup_->increaseNumCommand();
|
|
}
|
|
|
|
AbstractCommand::~AbstractCommand() {
|
|
disableReadCheckSocket();
|
|
disableWriteCheckSocket();
|
|
#ifdef ENABLE_ASYNC_DNS
|
|
disableNameResolverCheck(asyncNameResolver_);
|
|
#endif // ENABLE_ASYNC_DNS
|
|
requestGroup_->decreaseNumCommand();
|
|
requestGroup_->decreaseStreamCommand();
|
|
if(incNumConnection_) {
|
|
requestGroup_->decreaseStreamConnection();
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::useFasterRequest
|
|
(const SharedHandle<Request>& fasterRequest)
|
|
{
|
|
A2_LOG_INFO(fmt("CUID#%" PRId64 " - Use faster Request hostname=%s, port=%u",
|
|
getCuid(),
|
|
fasterRequest->getHost().c_str(),
|
|
fasterRequest->getPort()));
|
|
// Cancel current Request object and use faster one.
|
|
fileEntry_->removeRequest(req_);
|
|
Command* command =
|
|
InitiateConnectionCommandFactory::createInitiateConnectionCommand
|
|
(getCuid(), fasterRequest, fileEntry_, requestGroup_, e_);
|
|
e_->setNoWait(true);
|
|
e_->addCommand(command);
|
|
}
|
|
|
|
bool AbstractCommand::execute() {
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - socket: read:%d, write:%d, hup:%d, err:%d",
|
|
getCuid(),
|
|
readEventEnabled(),
|
|
writeEventEnabled(),
|
|
hupEventEnabled(),
|
|
errorEventEnabled()));
|
|
try {
|
|
if(requestGroup_->downloadFinished() || requestGroup_->isHaltRequested()) {
|
|
return true;
|
|
}
|
|
if(req_ && req_->removalRequested()) {
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Discard original URI=%s because it is"
|
|
" requested.",
|
|
getCuid(), req_->getUri().c_str()));
|
|
return prepareForRetry(0);
|
|
}
|
|
if(getPieceStorage()) {
|
|
segments_.clear();
|
|
getSegmentMan()->getInFlightSegment(segments_, getCuid());
|
|
if(req_ && segments_.empty()) {
|
|
// This command previously has assigned segments, but it is
|
|
// canceled. So discard current request chain. Plus, if no
|
|
// segment is available when http pipelining is used.
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - It seems previously assigned segments"
|
|
" are canceled. Restart.",
|
|
getCuid()));
|
|
// Request::isPipeliningEnabled() == true means aria2
|
|
// accessed the remote server and discovered that the server
|
|
// supports pipelining.
|
|
if(req_ && req_->isPipeliningEnabled()) {
|
|
e_->poolSocket(req_, createProxyRequest(), socket_);
|
|
}
|
|
return prepareForRetry(0);
|
|
}
|
|
// TODO it is not needed to check other PeerStats every time.
|
|
// Find faster Request when no segment is available.
|
|
if(req_ && fileEntry_->countPooledRequest() > 0 &&
|
|
!getPieceStorage()->hasMissingUnusedPiece()) {
|
|
SharedHandle<Request> fasterRequest = fileEntry_->findFasterRequest(req_);
|
|
if(fasterRequest) {
|
|
useFasterRequest(fasterRequest);
|
|
return true;
|
|
}
|
|
}
|
|
// Don't use this feature if PREF_MAX_{OVERALL_}DOWNLOAD_LIMIT
|
|
// is used or total length is unknown.
|
|
if(req_ && fileEntry_->getLength() > 0 &&
|
|
e_->getRequestGroupMan()->getMaxOverallDownloadSpeedLimit() == 0 &&
|
|
requestGroup_->getMaxDownloadSpeedLimit() == 0 &&
|
|
serverStatTimer_.difference(global::wallclock()) >= 10) {
|
|
serverStatTimer_ = global::wallclock();
|
|
std::vector<std::pair<size_t, std::string> > usedHosts;
|
|
if(getOption()->getAsBool(PREF_SELECT_LEAST_USED_HOST)) {
|
|
getDownloadEngine()->getRequestGroupMan()->getUsedHosts(usedHosts);
|
|
}
|
|
SharedHandle<Request> fasterRequest =
|
|
fileEntry_->findFasterRequest
|
|
(req_, usedHosts, e_->getRequestGroupMan()->getServerStatMan());
|
|
if(fasterRequest) {
|
|
useFasterRequest(fasterRequest);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if((checkSocketIsReadable_ &&
|
|
(readEventEnabled() ||
|
|
(socketRecvBuffer_ && !socketRecvBuffer_->bufferEmpty()))) ||
|
|
(checkSocketIsWritable_ && writeEventEnabled()) ||
|
|
hupEventEnabled() ||
|
|
#ifdef ENABLE_ASYNC_DNS
|
|
(nameResolverCheck_ && nameResolveFinished()) ||
|
|
#endif // ENABLE_ASYNC_DNS
|
|
(!checkSocketIsReadable_ && !checkSocketIsWritable_ &&
|
|
!nameResolverCheck_)) {
|
|
checkPoint_ = global::wallclock();
|
|
if(getPieceStorage()) {
|
|
if(!req_ || req_->getMaxPipelinedRequest() == 1 ||
|
|
// Why the following condition is necessary? That's because
|
|
// For single file download, SegmentMan::getSegment(cuid)
|
|
// is more efficient.
|
|
getDownloadContext()->getFileEntries().size() == 1) {
|
|
size_t maxSegments = req_?req_->getMaxPipelinedRequest():1;
|
|
size_t minSplitSize = calculateMinSplitSize();
|
|
while(segments_.size() < maxSegments) {
|
|
SharedHandle<Segment> segment =
|
|
getSegmentMan()->getSegment(getCuid(), minSplitSize);
|
|
if(!segment) {
|
|
break;
|
|
} else {
|
|
segments_.push_back(segment);
|
|
}
|
|
}
|
|
if(segments_.empty()) {
|
|
// TODO socket could be pooled here if pipelining is
|
|
// enabled... Hmm, I don't think if pipelining is enabled
|
|
// it does not go here.
|
|
A2_LOG_INFO(fmt(MSG_NO_SEGMENT_AVAILABLE,
|
|
getCuid()));
|
|
// When all segments are ignored in SegmentMan, there are
|
|
// no URIs available, so don't retry.
|
|
if(getSegmentMan()->allSegmentsIgnored()) {
|
|
A2_LOG_DEBUG("All segments are ignored.");
|
|
// This will execute other idle Commands and let them
|
|
// finish quickly.
|
|
e_->setRefreshInterval(0);
|
|
return true;
|
|
} else {
|
|
return prepareForRetry(1);
|
|
}
|
|
}
|
|
} else {
|
|
// For multi-file downloads
|
|
size_t minSplitSize = calculateMinSplitSize();
|
|
size_t maxSegments = req_->getMaxPipelinedRequest();
|
|
if(segments_.size() < maxSegments) {
|
|
getSegmentMan()->getSegment
|
|
(segments_, getCuid(), minSplitSize, fileEntry_, maxSegments);
|
|
}
|
|
if(segments_.empty()) {
|
|
return prepareForRetry(0);
|
|
}
|
|
}
|
|
}
|
|
return executeInternal();
|
|
} else if(errorEventEnabled()) {
|
|
throw DL_RETRY_EX(fmt(MSG_NETWORK_PROBLEM,
|
|
socket_->getSocketError().c_str()));
|
|
} else {
|
|
if(checkPoint_.difference(global::wallclock()) >= timeout_) {
|
|
// timeout triggers ServerStat error state.
|
|
SharedHandle<ServerStat> ss =
|
|
e_->getRequestGroupMan()->getOrCreateServerStat(req_->getHost(),
|
|
req_->getProtocol());
|
|
ss->setError();
|
|
// When DNS query was timeout, req_->getConnectedAddr() is
|
|
// empty.
|
|
if(!req_->getConnectedAddr().empty()) {
|
|
// Purging IP address cache to renew IP address.
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Marking IP address %s as bad",
|
|
getCuid(),
|
|
req_->getConnectedAddr().c_str()));
|
|
e_->markBadIPAddress(req_->getConnectedHostname(),
|
|
req_->getConnectedAddr(),
|
|
req_->getConnectedPort());
|
|
}
|
|
if(e_->findCachedIPAddress
|
|
(req_->getConnectedHostname(), req_->getConnectedPort()).empty()) {
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - All IP addresses were marked bad."
|
|
" Removing Entry.",
|
|
getCuid()));
|
|
e_->removeCachedIPAddress
|
|
(req_->getConnectedHostname(), req_->getConnectedPort());
|
|
}
|
|
throw DL_RETRY_EX2(EX_TIME_OUT, error_code::TIME_OUT);
|
|
}
|
|
e_->addCommand(this);
|
|
return false;
|
|
}
|
|
} catch(DlAbortEx& err) {
|
|
requestGroup_->setLastErrorCode(err.getErrorCode());
|
|
if(req_) {
|
|
A2_LOG_ERROR_EX(fmt(MSG_DOWNLOAD_ABORTED,
|
|
getCuid(),
|
|
req_->getUri().c_str()),
|
|
DL_ABORT_EX2(fmt("URI=%s", req_->getCurrentUri().c_str()),
|
|
err));
|
|
fileEntry_->addURIResult(req_->getUri(), err.getErrorCode());
|
|
if(err.getErrorCode() == error_code::CANNOT_RESUME) {
|
|
requestGroup_->increaseResumeFailureCount();
|
|
}
|
|
} else {
|
|
A2_LOG_DEBUG_EX(EX_EXCEPTION_CAUGHT, err);
|
|
}
|
|
onAbort();
|
|
tryReserved();
|
|
return true;
|
|
} catch(DlRetryEx& err) {
|
|
assert(req_);
|
|
A2_LOG_INFO_EX(fmt(MSG_RESTARTING_DOWNLOAD,
|
|
getCuid(), req_->getUri().c_str()),
|
|
DL_RETRY_EX2(fmt("URI=%s", req_->getCurrentUri().c_str()),
|
|
err));
|
|
req_->addTryCount();
|
|
req_->resetRedirectCount();
|
|
req_->resetUri();
|
|
const int maxTries = getOption()->getAsInt(PREF_MAX_TRIES);
|
|
bool isAbort = maxTries != 0 && req_->getTryCount() >= maxTries;
|
|
if(isAbort) {
|
|
A2_LOG_INFO(fmt(MSG_MAX_TRY,
|
|
getCuid(),
|
|
req_->getTryCount()));
|
|
A2_LOG_ERROR_EX(fmt(MSG_DOWNLOAD_ABORTED,
|
|
getCuid(),
|
|
req_->getUri().c_str()),
|
|
err);
|
|
fileEntry_->addURIResult(req_->getUri(), err.getErrorCode());
|
|
requestGroup_->setLastErrorCode(err.getErrorCode());
|
|
if(err.getErrorCode() == error_code::CANNOT_RESUME) {
|
|
requestGroup_->increaseResumeFailureCount();
|
|
}
|
|
onAbort();
|
|
tryReserved();
|
|
return true;
|
|
} else {
|
|
Timer wakeTime(global::wallclock());
|
|
wakeTime.advance(getOption()->getAsInt(PREF_RETRY_WAIT));
|
|
req_->setWakeTime(wakeTime);
|
|
return prepareForRetry(0);
|
|
}
|
|
} catch(DownloadFailureException& err) {
|
|
requestGroup_->setLastErrorCode(err.getErrorCode());
|
|
if(req_) {
|
|
A2_LOG_ERROR_EX(fmt(MSG_DOWNLOAD_ABORTED,
|
|
getCuid(),
|
|
req_->getUri().c_str()),
|
|
DL_ABORT_EX2(fmt("URI=%s", req_->getCurrentUri().c_str()),
|
|
err));
|
|
fileEntry_->addURIResult(req_->getUri(), err.getErrorCode());
|
|
} else {
|
|
A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, err);
|
|
}
|
|
requestGroup_->setHaltRequested(true);
|
|
getDownloadEngine()->setRefreshInterval(0);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::tryReserved() {
|
|
if(getDownloadContext()->getFileEntries().size() == 1) {
|
|
const SharedHandle<FileEntry>& entry =
|
|
getDownloadContext()->getFirstFileEntry();
|
|
// Don't create new command if currently file length is unknown
|
|
// and there are no URI left. Because file length is unknown, we
|
|
// can assume that there are no in-flight request object.
|
|
if(entry->getLength() == 0 && entry->getRemainingUris().empty()) {
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Not trying next request."
|
|
" No reserved/pooled request is remaining and"
|
|
" total length is still unknown.",
|
|
getCuid()));
|
|
return;
|
|
}
|
|
}
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Trying reserved/pooled request.",
|
|
getCuid()));
|
|
std::vector<Command*> commands;
|
|
requestGroup_->createNextCommand(commands, e_, 1);
|
|
e_->setNoWait(true);
|
|
e_->addCommand(commands);
|
|
}
|
|
|
|
bool AbstractCommand::prepareForRetry(time_t wait) {
|
|
if(getPieceStorage()) {
|
|
getSegmentMan()->cancelSegment(getCuid());
|
|
}
|
|
if(req_) {
|
|
// Reset persistentConnection and maxPipelinedRequest to handle
|
|
// the situation where remote server returns Connection: close
|
|
// after several pipelined requests.
|
|
req_->supportsPersistentConnection(true);
|
|
req_->setMaxPipelinedRequest(1);
|
|
|
|
fileEntry_->poolRequest(req_);
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Pooling request URI=%s",
|
|
getCuid(), req_->getUri().c_str()));
|
|
if(getSegmentMan()) {
|
|
getSegmentMan()->recognizeSegmentFor(fileEntry_);
|
|
}
|
|
}
|
|
|
|
Command* command = new CreateRequestCommand(getCuid(), requestGroup_, e_);
|
|
if(wait == 0) {
|
|
e_->setNoWait(true);
|
|
e_->addCommand(command);
|
|
} else {
|
|
// We don't use wait so that Command can be executed by
|
|
// DownloadEngine::setRefreshInterval(0).
|
|
command->setStatus(Command::STATUS_INACTIVE);
|
|
e_->addCommand(command);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AbstractCommand::onAbort() {
|
|
if(req_) {
|
|
fileEntry_->removeIdenticalURI(req_->getUri());
|
|
fileEntry_->removeRequest(req_);
|
|
}
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Aborting download",
|
|
getCuid()));
|
|
if(getPieceStorage()) {
|
|
getSegmentMan()->cancelSegment(getCuid());
|
|
// Don't do following process if BitTorrent is involved or files
|
|
// in DownloadContext is more than 1. The latter condition is
|
|
// limitation of current implementation.
|
|
if(!getOption()->getAsBool(PREF_ALWAYS_RESUME) &&
|
|
fileEntry_ &&
|
|
getDownloadContext()->getNetStat().getSessionDownloadLength() == 0 &&
|
|
!requestGroup_->p2pInvolved() &&
|
|
getDownloadContext()->getFileEntries().size() == 1) {
|
|
const int maxTries = getOption()->getAsInt(PREF_MAX_RESUME_FAILURE_TRIES);
|
|
if((maxTries > 0 && requestGroup_->getResumeFailureCount() >= maxTries)||
|
|
fileEntry_->emptyRequestUri()) {
|
|
// Local file exists, but given servers(or at least contacted
|
|
// ones) doesn't support resume. Let's restart download from
|
|
// scratch.
|
|
A2_LOG_NOTICE(fmt(_("CUID#%" PRId64 " - Failed to resume download."
|
|
" Download from scratch."),
|
|
getCuid()));
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Gathering URIs that has CANNOT_RESUME"
|
|
" error",
|
|
getCuid()));
|
|
// Set PREF_ALWAYS_RESUME to A2_V_TRUE to avoid repeating this
|
|
// process.
|
|
getOption()->put(PREF_ALWAYS_RESUME, A2_V_TRUE);
|
|
std::deque<URIResult> res;
|
|
fileEntry_->extractURIResult(res, error_code::CANNOT_RESUME);
|
|
if(!res.empty()) {
|
|
getSegmentMan()->cancelAllSegments();
|
|
getSegmentMan()->eraseSegmentWrittenLengthMemo();
|
|
getPieceStorage()->markPiecesDone(0);
|
|
std::vector<std::string> uris;
|
|
uris.reserve(res.size());
|
|
std::transform(res.begin(), res.end(), std::back_inserter(uris),
|
|
std::mem_fun_ref(&URIResult::getURI));
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - %lu URIs found.",
|
|
getCuid(),
|
|
static_cast<unsigned long int>(uris.size())));
|
|
fileEntry_->addUris(uris.begin(), uris.end());
|
|
getSegmentMan()->recognizeSegmentFor(fileEntry_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::disableReadCheckSocket() {
|
|
if(checkSocketIsReadable_) {
|
|
e_->deleteSocketForReadCheck(readCheckTarget_, this);
|
|
checkSocketIsReadable_ = false;
|
|
readCheckTarget_.reset();
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::setReadCheckSocket
|
|
(const SharedHandle<SocketCore>& socket) {
|
|
if(!socket->isOpen()) {
|
|
disableReadCheckSocket();
|
|
} else {
|
|
if(checkSocketIsReadable_) {
|
|
if(*readCheckTarget_ != *socket) {
|
|
e_->deleteSocketForReadCheck(readCheckTarget_, this);
|
|
e_->addSocketForReadCheck(socket, this);
|
|
readCheckTarget_ = socket;
|
|
}
|
|
} else {
|
|
e_->addSocketForReadCheck(socket, this);
|
|
checkSocketIsReadable_ = true;
|
|
readCheckTarget_ = socket;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::setReadCheckSocketIf
|
|
(const SharedHandle<SocketCore>& socket, bool pred)
|
|
{
|
|
if(pred) {
|
|
setReadCheckSocket(socket);
|
|
} else {
|
|
disableReadCheckSocket();
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::disableWriteCheckSocket() {
|
|
if(checkSocketIsWritable_) {
|
|
e_->deleteSocketForWriteCheck(writeCheckTarget_, this);
|
|
checkSocketIsWritable_ = false;
|
|
writeCheckTarget_.reset();
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::setWriteCheckSocket
|
|
(const SharedHandle<SocketCore>& socket) {
|
|
if(!socket->isOpen()) {
|
|
disableWriteCheckSocket();
|
|
} else {
|
|
if(checkSocketIsWritable_) {
|
|
if(*writeCheckTarget_ != *socket) {
|
|
e_->deleteSocketForWriteCheck(writeCheckTarget_, this);
|
|
e_->addSocketForWriteCheck(socket, this);
|
|
writeCheckTarget_ = socket;
|
|
}
|
|
} else {
|
|
e_->addSocketForWriteCheck(socket, this);
|
|
checkSocketIsWritable_ = true;
|
|
writeCheckTarget_ = socket;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::setWriteCheckSocketIf
|
|
(const SharedHandle<SocketCore>& socket, bool pred)
|
|
{
|
|
if(pred) {
|
|
setWriteCheckSocket(socket);
|
|
} else {
|
|
disableWriteCheckSocket();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
// Constructs proxy URI, merging username and password if they are
|
|
// defined.
|
|
std::string makeProxyUri
|
|
(const Pref* proxyPref,
|
|
const Pref* proxyUser,
|
|
const Pref* proxyPasswd,
|
|
const Option* option)
|
|
{
|
|
uri::UriStruct us;
|
|
if(uri::parse(us, option->get(proxyPref))) {
|
|
if(option->defined(proxyUser)) {
|
|
us.username = option->get(proxyUser);
|
|
}
|
|
if(option->defined(proxyPasswd)) {
|
|
us.password = option->get(proxyPasswd);
|
|
us.hasPassword = true;
|
|
}
|
|
return uri::construct(us);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
namespace {
|
|
// Returns proxy option value for the given protocol.
|
|
std::string getProxyOptionFor
|
|
(const Pref* proxyPref,
|
|
const Pref* proxyUser,
|
|
const Pref* proxyPasswd,
|
|
const Option* option)
|
|
{
|
|
std::string uri = makeProxyUri(proxyPref, proxyUser, proxyPasswd, option);
|
|
if(uri.empty()) {
|
|
return makeProxyUri(PREF_ALL_PROXY, PREF_ALL_PROXY_USER,
|
|
PREF_ALL_PROXY_PASSWD, option);
|
|
} else {
|
|
return uri;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
// Returns proxy URI for given protocol. If no proxy URI is defined,
|
|
// then returns an empty string.
|
|
std::string getProxyUri
|
|
(const std::string& protocol, const Option* option)
|
|
{
|
|
if(protocol == "http") {
|
|
return getProxyOptionFor(PREF_HTTP_PROXY,
|
|
PREF_HTTP_PROXY_USER,
|
|
PREF_HTTP_PROXY_PASSWD,
|
|
option);
|
|
} else if(protocol == "https") {
|
|
return getProxyOptionFor(PREF_HTTPS_PROXY,
|
|
PREF_HTTPS_PROXY_USER,
|
|
PREF_HTTPS_PROXY_PASSWD,
|
|
option);
|
|
} else if(protocol == "ftp") {
|
|
return getProxyOptionFor(PREF_FTP_PROXY,
|
|
PREF_FTP_PROXY_USER,
|
|
PREF_FTP_PROXY_PASSWD,
|
|
option);
|
|
} else {
|
|
return A2STR::NIL;
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
// Returns true if proxy is defined for the given protocol. Otherwise
|
|
// returns false.
|
|
bool isProxyRequest
|
|
(const std::string& protocol, const SharedHandle<Option>& option)
|
|
{
|
|
std::string proxyUri = getProxyUri(protocol, option.get());
|
|
return !proxyUri.empty();
|
|
}
|
|
} // namespace
|
|
|
|
namespace {
|
|
bool inNoProxy(const SharedHandle<Request>& req,
|
|
const std::string& noProxy)
|
|
{
|
|
std::vector<Scip> entries;
|
|
util::splitIter(noProxy.begin(), noProxy.end(), std::back_inserter(entries),
|
|
',', true);
|
|
if(entries.empty()) {
|
|
return false;
|
|
}
|
|
for(std::vector<Scip>::const_iterator i = entries.begin(),
|
|
eoi = entries.end(); i != eoi; ++i) {
|
|
std::string::const_iterator slashpos =
|
|
std::find((*i).first, (*i).second, '/');
|
|
if(slashpos == (*i).second) {
|
|
if(util::noProxyDomainMatch
|
|
(req->getHost(), std::string((*i).first, (*i).second))) {
|
|
return true;
|
|
}
|
|
} else {
|
|
// TODO We don't resolve hostname here. More complete
|
|
// implementation is that we should first resolve
|
|
// hostname(which may result in several IP addresses) and
|
|
// evaluates against all of them
|
|
std::string ip((*i).first, slashpos);
|
|
uint32_t bits;
|
|
if(!util::parseUIntNoThrow(bits, std::string(slashpos+1, (*i).second))) {
|
|
continue;
|
|
}
|
|
if(util::inSameCidrBlock(ip, req->getHost(), bits)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
} // namespace
|
|
|
|
bool AbstractCommand::isProxyDefined() const
|
|
{
|
|
return isProxyRequest(req_->getProtocol(), getOption()) &&
|
|
!inNoProxy(req_, getOption()->get(PREF_NO_PROXY));
|
|
}
|
|
|
|
SharedHandle<Request> AbstractCommand::createProxyRequest() const
|
|
{
|
|
SharedHandle<Request> proxyRequest;
|
|
if(inNoProxy(req_, getOption()->get(PREF_NO_PROXY))) {
|
|
return proxyRequest;
|
|
}
|
|
std::string proxy = getProxyUri(req_->getProtocol(), getOption().get());
|
|
if(!proxy.empty()) {
|
|
proxyRequest.reset(new Request());
|
|
if(proxyRequest->setUri(proxy)) {
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Using proxy", getCuid()));
|
|
} else {
|
|
A2_LOG_DEBUG(fmt("CUID#%" PRId64 " - Failed to parse proxy string",
|
|
getCuid()));
|
|
proxyRequest.reset();
|
|
}
|
|
}
|
|
return proxyRequest;
|
|
}
|
|
|
|
#ifdef ENABLE_ASYNC_DNS
|
|
|
|
bool AbstractCommand::isAsyncNameResolverInitialized() const
|
|
{
|
|
return asyncNameResolver_;
|
|
}
|
|
|
|
void AbstractCommand::initAsyncNameResolver(const std::string& hostname)
|
|
{
|
|
int family;
|
|
if(getOption()->getAsBool(PREF_ENABLE_ASYNC_DNS6)) {
|
|
family = AF_UNSPEC;
|
|
} else {
|
|
family = AF_INET;
|
|
}
|
|
asyncNameResolver_.reset
|
|
(new AsyncNameResolver(family
|
|
#ifdef HAVE_ARES_ADDR_NODE
|
|
,
|
|
e_->getAsyncDNSServers()
|
|
#endif // HAVE_ARES_ADDR_NODE
|
|
));
|
|
A2_LOG_INFO(fmt(MSG_RESOLVING_HOSTNAME,
|
|
getCuid(),
|
|
hostname.c_str()));
|
|
asyncNameResolver_->resolve(hostname);
|
|
setNameResolverCheck(asyncNameResolver_);
|
|
}
|
|
|
|
bool AbstractCommand::asyncResolveHostname()
|
|
{
|
|
switch(asyncNameResolver_->getStatus()) {
|
|
case AsyncNameResolver::STATUS_SUCCESS:
|
|
disableNameResolverCheck(asyncNameResolver_);
|
|
return true;
|
|
case AsyncNameResolver::STATUS_ERROR:
|
|
disableNameResolverCheck(asyncNameResolver_);
|
|
if(!isProxyRequest(req_->getProtocol(), getOption())) {
|
|
e_->getRequestGroupMan()->getOrCreateServerStat
|
|
(req_->getHost(), req_->getProtocol())->setError();
|
|
}
|
|
throw DL_ABORT_EX2
|
|
(fmt(MSG_NAME_RESOLUTION_FAILED,
|
|
getCuid(),
|
|
asyncNameResolver_->getHostname().c_str(),
|
|
asyncNameResolver_->getError().c_str()),
|
|
error_code::NAME_RESOLVE_ERROR);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const std::vector<std::string>& AbstractCommand::getResolvedAddresses()
|
|
{
|
|
return asyncNameResolver_->getResolvedAddresses();
|
|
}
|
|
|
|
void AbstractCommand::setNameResolverCheck
|
|
(const SharedHandle<AsyncNameResolver>& resolver) {
|
|
if(resolver) {
|
|
nameResolverCheck_ = true;
|
|
e_->addNameResolverCheck(resolver, this);
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::disableNameResolverCheck
|
|
(const SharedHandle<AsyncNameResolver>& resolver) {
|
|
if(resolver) {
|
|
nameResolverCheck_ = false;
|
|
e_->deleteNameResolverCheck(resolver, this);
|
|
}
|
|
}
|
|
|
|
bool AbstractCommand::nameResolveFinished() const {
|
|
return
|
|
asyncNameResolver_->getStatus() == AsyncNameResolver::STATUS_SUCCESS ||
|
|
asyncNameResolver_->getStatus() == AsyncNameResolver::STATUS_ERROR;
|
|
}
|
|
#endif // ENABLE_ASYNC_DNS
|
|
|
|
std::string AbstractCommand::resolveHostname
|
|
(std::vector<std::string>& addrs, const std::string& hostname, uint16_t port)
|
|
{
|
|
if(util::isNumericHost(hostname)) {
|
|
addrs.push_back(hostname);
|
|
return hostname;
|
|
}
|
|
e_->findAllCachedIPAddresses(std::back_inserter(addrs), hostname, port);
|
|
std::string ipaddr;
|
|
if(addrs.empty()) {
|
|
#ifdef ENABLE_ASYNC_DNS
|
|
if(getOption()->getAsBool(PREF_ASYNC_DNS)) {
|
|
if(!isAsyncNameResolverInitialized()) {
|
|
initAsyncNameResolver(hostname);
|
|
}
|
|
if(asyncResolveHostname()) {
|
|
addrs = getResolvedAddresses();
|
|
} else {
|
|
return A2STR::NIL;
|
|
}
|
|
} else
|
|
#endif // ENABLE_ASYNC_DNS
|
|
{
|
|
NameResolver res;
|
|
res.setSocktype(SOCK_STREAM);
|
|
if(e_->getOption()->getAsBool(PREF_DISABLE_IPV6)) {
|
|
res.setFamily(AF_INET);
|
|
}
|
|
res.resolve(addrs, hostname);
|
|
}
|
|
A2_LOG_INFO(fmt(MSG_NAME_RESOLUTION_COMPLETE,
|
|
getCuid(),
|
|
hostname.c_str(),
|
|
strjoin(addrs.begin(), addrs.end(), ", ").c_str()));
|
|
for(std::vector<std::string>::const_iterator i = addrs.begin(),
|
|
eoi = addrs.end(); i != eoi; ++i) {
|
|
e_->cacheIPAddress(hostname, *i, port);
|
|
}
|
|
ipaddr = e_->findCachedIPAddress(hostname, port);
|
|
} else {
|
|
ipaddr = addrs.front();
|
|
A2_LOG_INFO(fmt(MSG_DNS_CACHE_HIT,
|
|
getCuid(),
|
|
hostname.c_str(),
|
|
strjoin(addrs.begin(), addrs.end(), ", ").c_str()));
|
|
}
|
|
return ipaddr;
|
|
}
|
|
|
|
void AbstractCommand::prepareForNextAction
|
|
(const SharedHandle<CheckIntegrityEntry>& checkEntry)
|
|
{
|
|
std::vector<Command*>* commands = new std::vector<Command*>();
|
|
auto_delete_container<std::vector<Command*> > commandsDel(commands);
|
|
requestGroup_->processCheckIntegrityEntry(*commands, checkEntry, e_);
|
|
e_->addCommand(*commands);
|
|
commands->clear();
|
|
e_->setNoWait(true);
|
|
}
|
|
|
|
bool AbstractCommand::checkIfConnectionEstablished
|
|
(const SharedHandle<SocketCore>& socket,
|
|
const std::string& connectedHostname,
|
|
const std::string& connectedAddr,
|
|
uint16_t connectedPort)
|
|
{
|
|
std::string error = socket->getSocketError();
|
|
if(!error.empty()) {
|
|
// See also InitiateConnectionCommand::executeInternal()
|
|
e_->markBadIPAddress(connectedHostname, connectedAddr, connectedPort);
|
|
if(!e_->findCachedIPAddress(connectedHostname, connectedPort).empty()) {
|
|
A2_LOG_INFO(fmt(MSG_CONNECT_FAILED_AND_RETRY,
|
|
getCuid(),
|
|
connectedAddr.c_str(), connectedPort));
|
|
Command* command =
|
|
InitiateConnectionCommandFactory::createInitiateConnectionCommand
|
|
(getCuid(), req_, fileEntry_, requestGroup_, e_);
|
|
e_->setNoWait(true);
|
|
e_->addCommand(command);
|
|
return false;
|
|
}
|
|
e_->removeCachedIPAddress(connectedHostname, connectedPort);
|
|
// Don't set error if proxy server is used and its method is GET.
|
|
if(resolveProxyMethod(req_->getProtocol()) != V_GET ||
|
|
!isProxyRequest(req_->getProtocol(), getOption())) {
|
|
e_->getRequestGroupMan()->getOrCreateServerStat
|
|
(req_->getHost(), req_->getProtocol())->setError();
|
|
}
|
|
throw DL_RETRY_EX
|
|
(fmt(MSG_ESTABLISHING_CONNECTION_FAILED, error.c_str()));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const std::string& AbstractCommand::resolveProxyMethod
|
|
(const std::string& protocol) const
|
|
{
|
|
if(getOption()->get(PREF_PROXY_METHOD) == V_TUNNEL ||
|
|
protocol == "https") {
|
|
return V_TUNNEL;
|
|
} else {
|
|
return V_GET;
|
|
}
|
|
}
|
|
|
|
const SharedHandle<Option>& AbstractCommand::getOption() const
|
|
{
|
|
return requestGroup_->getOption();
|
|
}
|
|
|
|
void AbstractCommand::createSocket()
|
|
{
|
|
socket_.reset(new SocketCore());
|
|
}
|
|
|
|
int32_t AbstractCommand::calculateMinSplitSize() const
|
|
{
|
|
if(req_ && req_->isPipeliningEnabled()) {
|
|
return getDownloadContext()->getPieceLength();
|
|
} else {
|
|
return getOption()->getAsInt(PREF_MIN_SPLIT_SIZE);
|
|
}
|
|
}
|
|
|
|
void AbstractCommand::setRequest(const SharedHandle<Request>& request)
|
|
{
|
|
req_ = request;
|
|
}
|
|
|
|
void AbstractCommand::resetRequest()
|
|
{
|
|
req_.reset();
|
|
}
|
|
|
|
void AbstractCommand::setFileEntry(const SharedHandle<FileEntry>& fileEntry)
|
|
{
|
|
fileEntry_ = fileEntry;
|
|
}
|
|
|
|
void AbstractCommand::setSocket(const SharedHandle<SocketCore>& s)
|
|
{
|
|
socket_ = s;
|
|
}
|
|
|
|
const SharedHandle<DownloadContext>& AbstractCommand::getDownloadContext() const
|
|
{
|
|
return requestGroup_->getDownloadContext();
|
|
}
|
|
|
|
const SharedHandle<SegmentMan>& AbstractCommand::getSegmentMan() const
|
|
{
|
|
return requestGroup_->getSegmentMan();
|
|
}
|
|
|
|
const SharedHandle<PieceStorage>& AbstractCommand::getPieceStorage() const
|
|
{
|
|
return requestGroup_->getPieceStorage();
|
|
}
|
|
|
|
void AbstractCommand::checkSocketRecvBuffer()
|
|
{
|
|
if(!socketRecvBuffer_->bufferEmpty()) {
|
|
setStatus(Command::STATUS_ONESHOT_REALTIME);
|
|
e_->setNoWait(true);
|
|
}
|
|
}
|
|
|
|
} // namespace aria2
|