| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 | /* <!-- 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 "download_helper.h"#include <algorithm>#include <sstream>#include "RequestGroup.h"#include "Option.h"#include "prefs.h"#include "Metalink2RequestGroup.h"#include "ProtocolDetector.h"#include "paramed_string.h"#include "UriListParser.h"#include "DownloadContext.h"#include "RecoverableException.h"#include "DlAbortEx.h"#include "message.h"#include "fmt.h"#include "FileEntry.h"#include "LogFactory.h"#include "File.h"#include "util.h"#include "array_fun.h"#include "OptionHandler.h"#include "ByteArrayDiskWriter.h"#include "a2functional.h"#include "ByteArrayDiskWriterFactory.h"#include "MetadataInfo.h"#include "OptionParser.h"#include "SegList.h"#include "download_handlers.h"#include "SimpleRandomizer.h"#ifdef ENABLE_BITTORRENT#include "bittorrent_helper.h"#include "BtConstants.h"#include "ValueBaseBencodeParser.h"#endif // ENABLE_BITTORRENTnamespace aria2 {namespace {void unfoldURI(std::vector<std::string>& result,               const std::vector<std::string>& args){  for (const auto& i : args) {    paramed_string::expand(std::begin(i), std::end(i),                           std::back_inserter(result));  }}} // namespacenamespace {template <typename InputIterator>void splitURI(std::vector<std::string>& result, InputIterator begin,              InputIterator end, size_t numSplit, size_t maxIter){  size_t numURIs = std::distance(begin, end);  if (numURIs >= numSplit) {    result.insert(std::end(result), begin, end);  }  else if (numURIs > 0) {    size_t num = std::min(numSplit / numURIs, maxIter);    for (size_t i = 0; i < num; ++i) {      result.insert(std::end(result), begin, end);    }    if (num < maxIter) {      result.insert(std::end(result), begin, begin + (numSplit % numURIs));    }  }}} // namespacenamespace {std::shared_ptr<GroupId> getGID(const std::shared_ptr<Option>& option){  std::shared_ptr<GroupId> gid;  if (option->defined(PREF_GID)) {    a2_gid_t n;    if (GroupId::toNumericId(n, option->get(PREF_GID).c_str()) != 0) {      throw DL_ABORT_EX(          fmt("%s is invalid for GID.", option->get(PREF_GID).c_str()));    }    gid = GroupId::import(n);    if (!gid) {      throw DL_ABORT_EX(          fmt("GID %s is not unique.", option->get(PREF_GID).c_str()));    }  }  else {    gid = GroupId::create();  }  return gid;}} // namespacenamespace {std::shared_ptr<RequestGroup>createRequestGroup(const std::shared_ptr<Option>& optionTemplate,                   const std::vector<std::string>& uris,                   bool useOutOption = false){  auto option = util::copy(optionTemplate);  auto rg = std::make_shared<RequestGroup>(getGID(option), option);  auto dctx = std::make_shared<DownloadContext>(      option->getAsInt(PREF_PIECE_LENGTH), 0,      useOutOption && !option->blank(PREF_OUT)          ? util::applyDir(option->get(PREF_DIR), option->get(PREF_OUT))          : A2STR::NIL);  dctx->getFirstFileEntry()->setUris(uris);  dctx->getFirstFileEntry()->setMaxConnectionPerServer(      option->getAsInt(PREF_MAX_CONNECTION_PER_SERVER));  const std::string& checksum = option->get(PREF_CHECKSUM);  if (!checksum.empty()) {    auto p = util::divide(std::begin(checksum), std::end(checksum), '=');    std::string hashType(p.first.first, p.first.second);    std::string hexDigest(p.second.first, p.second.second);    util::lowercase(hashType);    dctx->setDigest(hashType,                    util::fromHex(std::begin(hexDigest), std::end(hexDigest)));  }  rg->setDownloadContext(dctx);  if (option->getAsBool(PREF_ENABLE_RPC)) {    rg->setPauseRequested(option->getAsBool(PREF_PAUSE));  }  removeOneshotOption(option);  return rg;}} // namespace#if defined(ENABLE_BITTORRENT) || defined(ENABLE_METALINK)namespace {std::shared_ptr<MetadataInfo>createMetadataInfo(const std::shared_ptr<GroupId>& gid, const std::string& uri){  return std::make_shared<MetadataInfo>(gid, uri);}} // namespacenamespace {std::shared_ptr<MetadataInfo> createMetadataInfoDataOnly(){  return std::make_shared<MetadataInfo>();}} // namespace#endif // ENABLE_BITTORRENT || ENABLE_METALINK#ifdef ENABLE_BITTORRENTnamespace {std::shared_ptr<RequestGroup>createBtRequestGroup(const std::string& metaInfoUri,                     const std::shared_ptr<Option>& optionTemplate,                     const std::vector<std::string>& auxUris,                     const ValueBase* torrent, bool adjustAnnounceUri = true){  auto option = util::copy(optionTemplate);  auto gid = getGID(option);  auto rg = std::make_shared<RequestGroup>(gid, option);  auto dctx = std::make_shared<DownloadContext>();  // may throw exception  bittorrent::loadFromMemory(torrent, dctx, option, auxUris,                             metaInfoUri.empty() ? "default" : metaInfoUri);  for (auto& fe : dctx->getFileEntries()) {    auto& uris = fe->getRemainingUris();    std::shuffle(std::begin(uris), std::end(uris),                 *SimpleRandomizer::getInstance());  }  if (metaInfoUri.empty()) {    rg->setMetadataInfo(createMetadataInfoDataOnly());  }  else {    rg->setMetadataInfo(createMetadataInfo(gid, metaInfoUri));  }  if (adjustAnnounceUri) {    bittorrent::adjustAnnounceUri(bittorrent::getTorrentAttrs(dctx), option);  }  auto sgl = util::parseIntSegments(option->get(PREF_SELECT_FILE));  sgl.normalize();  dctx->setFileFilter(std::move(sgl));  std::istringstream indexOutIn(option->get(PREF_INDEX_OUT));  auto indexPaths = util::createIndexPaths(indexOutIn);  for (const auto& i : indexPaths) {    dctx->setFilePathWithIndex(i.first,                               util::applyDir(option->get(PREF_DIR), i.second));  }  rg->setDownloadContext(dctx);  if (option->getAsBool(PREF_ENABLE_RPC)) {    rg->setPauseRequested(option->getAsBool(PREF_PAUSE));  }  // Remove "metalink" from Accept Type list to avoid server from  // responding Metalink file for web-seeding URIs.  dctx->setAcceptMetalink(false);  removeOneshotOption(option);  return rg;}} // namespacenamespace {std::shared_ptr<RequestGroup>createBtMagnetRequestGroup(const std::string& magnetLink,                           const std::shared_ptr<Option>& optionTemplate){  auto option = util::copy(optionTemplate);  auto gid = getGID(option);  auto rg = std::make_shared<RequestGroup>(gid, option);  auto dctx = std::make_shared<DownloadContext>(METADATA_PIECE_SIZE, 0);  // We only know info hash. Total Length is unknown at this moment.  dctx->markTotalLengthIsUnknown();  rg->setFileAllocationEnabled(false);  rg->setPreLocalFileCheckEnabled(false);  bittorrent::loadMagnet(magnetLink, dctx);  auto torrentAttrs = bittorrent::getTorrentAttrs(dctx);  bittorrent::adjustAnnounceUri(torrentAttrs, option);  dctx->getFirstFileEntry()->setPath(torrentAttrs->name);  rg->setDownloadContext(dctx);  rg->clearPostDownloadHandler();  rg->addPostDownloadHandler(      download_handlers::getUTMetadataPostDownloadHandler());  rg->setDiskWriterFactory(std::make_shared<ByteArrayDiskWriterFactory>());  rg->setMetadataInfo(createMetadataInfo(gid, magnetLink));  rg->markInMemoryDownload();  if (option->getAsBool(PREF_ENABLE_RPC)) {    rg->setPauseRequested(option->getAsBool(PREF_PAUSE));  }  removeOneshotOption(option);  return rg;}} // namespacevoid createRequestGroupForBitTorrent(    std::vector<std::shared_ptr<RequestGroup>>& result,    const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,    const std::string& metaInfoUri, const std::string& torrentData,    bool adjustAnnounceUri){  std::unique_ptr<ValueBase> torrent;  bittorrent::ValueBaseBencodeParser parser;  if (torrentData.empty()) {    torrent = parseFile(parser, metaInfoUri);  }  else {    ssize_t error;    torrent = parser.parseFinal(torrentData.c_str(), torrentData.size(), error);  }  if (!torrent) {    throw DL_ABORT_EX2("Bencode decoding failed",                       error_code::BENCODE_PARSE_ERROR);  }  createRequestGroupForBitTorrent(result, option, uris, metaInfoUri,                                  torrent.get(), adjustAnnounceUri);}void createRequestGroupForBitTorrent(    std::vector<std::shared_ptr<RequestGroup>>& result,    const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,    const std::string& metaInfoUri, const ValueBase* torrent,    bool adjustAnnounceUri){  std::vector<std::string> nargs;  if (option->get(PREF_PARAMETERIZED_URI) == A2_V_TRUE) {    unfoldURI(nargs, uris);  }  else {    nargs = uris;  }  // we ignore -Z option here  size_t numSplit = option->getAsInt(PREF_SPLIT);  auto rg = createBtRequestGroup(metaInfoUri, option, nargs, torrent,                                 adjustAnnounceUri);  rg->setNumConcurrentCommand(numSplit);  result.push_back(rg);}#endif // ENABLE_BITTORRENT#ifdef ENABLE_METALINKvoid createRequestGroupForMetalink(    std::vector<std::shared_ptr<RequestGroup>>& result,    const std::shared_ptr<Option>& option, const std::string& metalinkData){  if (metalinkData.empty()) {    Metalink2RequestGroup().generate(result, option->get(PREF_METALINK_FILE),                                     option,                                     option->get(PREF_METALINK_BASE_URI));  }  else {    auto dw = std::make_shared<ByteArrayDiskWriter>();    dw->setString(metalinkData);    Metalink2RequestGroup().generate(result, dw, option,                                     option->get(PREF_METALINK_BASE_URI));  }}#endif // ENABLE_METALINKnamespace {class AccRequestGroup {private:  std::vector<std::shared_ptr<RequestGroup>>& requestGroups_;  ProtocolDetector detector_;  std::shared_ptr<Option> option_;  bool ignoreLocalPath_;  bool throwOnError_;public:  AccRequestGroup(std::vector<std::shared_ptr<RequestGroup>>& requestGroups,                  std::shared_ptr<Option> option, bool ignoreLocalPath = false,                  bool throwOnError = false)      : requestGroups_(requestGroups),        option_(std::move(option)),        ignoreLocalPath_(ignoreLocalPath),        throwOnError_(throwOnError)  {  }  void operator()(const std::string& uri)  {    if (detector_.isStreamProtocol(uri)) {      std::vector<std::string> streamURIs;      size_t numIter = option_->getAsInt(PREF_MAX_CONNECTION_PER_SERVER);      size_t numSplit = option_->getAsInt(PREF_SPLIT);      size_t num = std::min(numIter, numSplit);      for (size_t i = 0; i < num; ++i) {        streamURIs.push_back(uri);      }      auto rg = createRequestGroup(option_, streamURIs);      rg->setNumConcurrentCommand(numSplit);      requestGroups_.push_back(rg);    }#ifdef ENABLE_BITTORRENT    else if (detector_.guessTorrentMagnet(uri)) {      requestGroups_.push_back(createBtMagnetRequestGroup(uri, option_));    }    else if (!ignoreLocalPath_ && detector_.guessTorrentFile(uri)) {      try {        bittorrent::ValueBaseBencodeParser parser;        auto torrent = parseFile(parser, uri);        if (!torrent) {          throw DL_ABORT_EX2("Bencode decoding failed",                             error_code::BENCODE_PARSE_ERROR);        }        requestGroups_.push_back(            createBtRequestGroup(uri, option_, {}, torrent.get()));      }      catch (RecoverableException& e) {        if (throwOnError_) {          throw;        }        else {          // error occurred while parsing torrent file.          // We simply ignore it.          A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);        }      }    }#endif // ENABLE_BITTORRENT#ifdef ENABLE_METALINK    else if (!ignoreLocalPath_ && detector_.guessMetalinkFile(uri)) {      try {        Metalink2RequestGroup().generate(requestGroups_, uri, option_,                                         option_->get(PREF_METALINK_BASE_URI));      }      catch (RecoverableException& e) {        if (throwOnError_) {          throw;        }        else {          // error occurred while parsing metalink file.          // We simply ignore it.          A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);        }      }    }#endif // ENABLE_METALINK    else {      if (throwOnError_) {        throw DL_ABORT_EX(fmt(MSG_UNRECOGNIZED_URI, uri.c_str()));      }      else {        A2_LOG_ERROR(fmt(MSG_UNRECOGNIZED_URI, uri.c_str()));      }    }  }};} // namespacenamespace {class StreamProtocolFilter {private:  ProtocolDetector detector_;public:  bool operator()(const std::string& uri)  {    return detector_.isStreamProtocol(uri);  }};} // namespacevoid createRequestGroupForUri(    std::vector<std::shared_ptr<RequestGroup>>& result,    const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,    bool ignoreForceSequential, bool ignoreLocalPath, bool throwOnError){  std::vector<std::string> nargs;  if (option->get(PREF_PARAMETERIZED_URI) == A2_V_TRUE) {    unfoldURI(nargs, uris);  }  else {    nargs = uris;  }  if (!ignoreForceSequential &&      option->get(PREF_FORCE_SEQUENTIAL) == A2_V_TRUE) {    std::for_each(        std::begin(nargs), std::end(nargs),        AccRequestGroup(result, option, ignoreLocalPath, throwOnError));  }  else {    auto strmProtoEnd = std::stable_partition(        std::begin(nargs), std::end(nargs), StreamProtocolFilter());    // let's process http/ftp protocols first.    if (std::begin(nargs) != strmProtoEnd) {      size_t numIter = option->getAsInt(PREF_MAX_CONNECTION_PER_SERVER);      size_t numSplit = option->getAsInt(PREF_SPLIT);      std::vector<std::string> streamURIs;      splitURI(streamURIs, std::begin(nargs), strmProtoEnd, numSplit, numIter);      try {        auto rg = createRequestGroup(option, streamURIs, true);        rg->setNumConcurrentCommand(numSplit);        result.push_back(rg);      }      catch (RecoverableException& e) {        if (throwOnError) {          throw;        }        else {          A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);        }      }    }    // process remaining URIs(local metalink, BitTorrent files)    std::for_each(        strmProtoEnd, std::end(nargs),        AccRequestGroup(result, option, ignoreLocalPath, throwOnError));  }}bool createRequestGroupFromUriListParser(    std::vector<std::shared_ptr<RequestGroup>>& result, const Option* option,    UriListParser* uriListParser){  // Since result already contains some entries, we cache the size of  // it. Later, we use this value to determine RequestGroup is  // actually created.  size_t num = result.size();  while (uriListParser->hasNext()) {    std::vector<std::string> uris;    Option tempOption;    uriListParser->parseNext(uris, tempOption);    if (uris.empty()) {      continue;    }    auto requestOption = std::make_shared<Option>(*option);    requestOption->remove(PREF_OUT);    const auto& oparser = OptionParser::getInstance();    for (size_t i = 1, len = option::countOption(); i < len; ++i) {      auto pref = option::i2p(i);      auto h = oparser->find(pref);      if (h && h->getInitialOption() && tempOption.defined(pref)) {        requestOption->put(pref, tempOption.get(pref));      }    }    // This does not throw exception because throwOnError = false.    createRequestGroupForUri(result, requestOption, uris);    if (num < result.size()) {      return true;    }  }  return false;}std::shared_ptr<UriListParser> openUriListParser(const std::string& filename){  std::string listPath;  if (filename == "-") {    listPath = DEV_STDIN;  }  else {    if (!File(filename).isFile()) {      throw DL_ABORT_EX(fmt(EX_FILE_OPEN, filename.c_str(), "No such file"));    }    listPath = filename;  }  return std::make_shared<UriListParser>(listPath);}void createRequestGroupForUriList(    std::vector<std::shared_ptr<RequestGroup>>& result,    const std::shared_ptr<Option>& option){  auto uriListParser = openUriListParser(option->get(PREF_INPUT_FILE));  while (createRequestGroupFromUriListParser(result, option.get(),                                             uriListParser.get()))    ;}std::shared_ptr<MetadataInfo> createMetadataInfoFromFirstFileEntry(    const std::shared_ptr<GroupId>& gid,    const std::shared_ptr<DownloadContext>& dctx){  if (dctx->getFileEntries().empty()) {    return nullptr;  }  else {    auto uris = dctx->getFileEntries()[0]->getUris();    if (uris.empty()) {      return nullptr;    }    return std::make_shared<MetadataInfo>(gid, uris[0]);  }}void removeOneshotOption(const std::shared_ptr<Option>& option){  option->remove(PREF_PAUSE);  option->remove(PREF_GID);}} // namespace aria2
 |