1 /* <!-- copyright */
2 /*
3  * aria2 - The high speed download utility
4  *
5  * Copyright (C) 2006 Tatsuhiro Tsujikawa
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  *
21  * In addition, as a special exception, the copyright holders give
22  * permission to link the code of portions of this program with the
23  * OpenSSL library under certain conditions as described in each
24  * individual source file, and distribute linked combinations
25  * including the two.
26  * You must obey the GNU General Public License in all respects
27  * for all of the code used other than OpenSSL.  If you modify
28  * file(s) with this exception, you may extend this exception to your
29  * version of the file(s), but you are not obligated to do so.  If you
30  * do not wish to do so, delete this exception statement from your
31  * version.  If you delete this exception statement from all source
32  * files in the program, then also delete it here.
33  */
34 /* copyright --> */
35 #include "download_helper.h"
36 
37 #include <algorithm>
38 #include <sstream>
39 
40 #include "RequestGroup.h"
41 #include "Option.h"
42 #include "prefs.h"
43 #include "Metalink2RequestGroup.h"
44 #include "ProtocolDetector.h"
45 #include "paramed_string.h"
46 #include "UriListParser.h"
47 #include "DownloadContext.h"
48 #include "RecoverableException.h"
49 #include "DlAbortEx.h"
50 #include "message.h"
51 #include "fmt.h"
52 #include "FileEntry.h"
53 #include "LogFactory.h"
54 #include "File.h"
55 #include "util.h"
56 #include "array_fun.h"
57 #include "OptionHandler.h"
58 #include "ByteArrayDiskWriter.h"
59 #include "a2functional.h"
60 #include "ByteArrayDiskWriterFactory.h"
61 #include "MetadataInfo.h"
62 #include "OptionParser.h"
63 #include "SegList.h"
64 #include "download_handlers.h"
65 #include "SimpleRandomizer.h"
66 #ifdef ENABLE_BITTORRENT
67 #  include "bittorrent_helper.h"
68 #  include "BtConstants.h"
69 #  include "ValueBaseBencodeParser.h"
70 #endif // ENABLE_BITTORRENT
71 
72 namespace aria2 {
73 
74 namespace {
unfoldURI(std::vector<std::string> & result,const std::vector<std::string> & args)75 void unfoldURI(std::vector<std::string>& result,
76                const std::vector<std::string>& args)
77 {
78   for (const auto& i : args) {
79     paramed_string::expand(std::begin(i), std::end(i),
80                            std::back_inserter(result));
81   }
82 }
83 } // namespace
84 
85 namespace {
86 template <typename InputIterator>
splitURI(std::vector<std::string> & result,InputIterator begin,InputIterator end,size_t numSplit,size_t maxIter)87 void splitURI(std::vector<std::string>& result, InputIterator begin,
88               InputIterator end, size_t numSplit, size_t maxIter)
89 {
90   size_t numURIs = std::distance(begin, end);
91   if (numURIs >= numSplit) {
92     result.insert(std::end(result), begin, end);
93   }
94   else if (numURIs > 0) {
95     size_t num = std::min(numSplit / numURIs, maxIter);
96     for (size_t i = 0; i < num; ++i) {
97       result.insert(std::end(result), begin, end);
98     }
99     if (num < maxIter) {
100       result.insert(std::end(result), begin, begin + (numSplit % numURIs));
101     }
102   }
103 }
104 } // namespace
105 
106 namespace {
getGID(const std::shared_ptr<Option> & option)107 std::shared_ptr<GroupId> getGID(const std::shared_ptr<Option>& option)
108 {
109   std::shared_ptr<GroupId> gid;
110   if (option->defined(PREF_GID)) {
111     a2_gid_t n;
112     if (GroupId::toNumericId(n, option->get(PREF_GID).c_str()) != 0) {
113       throw DL_ABORT_EX(
114           fmt("%s is invalid for GID.", option->get(PREF_GID).c_str()));
115     }
116     gid = GroupId::import(n);
117     if (!gid) {
118       throw DL_ABORT_EX(
119           fmt("GID %s is not unique.", option->get(PREF_GID).c_str()));
120     }
121   }
122   else {
123     gid = GroupId::create();
124   }
125   return gid;
126 }
127 } // namespace
128 
129 namespace {
130 std::shared_ptr<RequestGroup>
createRequestGroup(const std::shared_ptr<Option> & optionTemplate,const std::vector<std::string> & uris,bool useOutOption=false)131 createRequestGroup(const std::shared_ptr<Option>& optionTemplate,
132                    const std::vector<std::string>& uris,
133                    bool useOutOption = false)
134 {
135   auto option = util::copy(optionTemplate);
136   auto rg = std::make_shared<RequestGroup>(getGID(option), option);
137   auto dctx = std::make_shared<DownloadContext>(
138       option->getAsInt(PREF_PIECE_LENGTH), 0,
139       useOutOption && !option->blank(PREF_OUT)
140           ? util::applyDir(option->get(PREF_DIR), option->get(PREF_OUT))
141           : A2STR::NIL);
142   dctx->getFirstFileEntry()->setUris(uris);
143   dctx->getFirstFileEntry()->setMaxConnectionPerServer(
144       option->getAsInt(PREF_MAX_CONNECTION_PER_SERVER));
145   const std::string& checksum = option->get(PREF_CHECKSUM);
146   if (!checksum.empty()) {
147     auto p = util::divide(std::begin(checksum), std::end(checksum), '=');
148     std::string hashType(p.first.first, p.first.second);
149     std::string hexDigest(p.second.first, p.second.second);
150     util::lowercase(hashType);
151     dctx->setDigest(hashType,
152                     util::fromHex(std::begin(hexDigest), std::end(hexDigest)));
153   }
154   rg->setDownloadContext(dctx);
155 
156   if (option->getAsBool(PREF_ENABLE_RPC)) {
157     rg->setPauseRequested(option->getAsBool(PREF_PAUSE));
158   }
159 
160   removeOneshotOption(option);
161   return rg;
162 }
163 } // namespace
164 
165 #if defined(ENABLE_BITTORRENT) || defined(ENABLE_METALINK)
166 namespace {
167 std::shared_ptr<MetadataInfo>
createMetadataInfo(const std::shared_ptr<GroupId> & gid,const std::string & uri)168 createMetadataInfo(const std::shared_ptr<GroupId>& gid, const std::string& uri)
169 {
170   return std::make_shared<MetadataInfo>(gid, uri);
171 }
172 } // namespace
173 
174 namespace {
createMetadataInfoDataOnly()175 std::shared_ptr<MetadataInfo> createMetadataInfoDataOnly()
176 {
177   return std::make_shared<MetadataInfo>();
178 }
179 } // namespace
180 #endif // ENABLE_BITTORRENT || ENABLE_METALINK
181 
182 #ifdef ENABLE_BITTORRENT
183 
184 namespace {
185 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)186 createBtRequestGroup(const std::string& metaInfoUri,
187                      const std::shared_ptr<Option>& optionTemplate,
188                      const std::vector<std::string>& auxUris,
189                      const ValueBase* torrent, bool adjustAnnounceUri = true)
190 {
191   auto option = util::copy(optionTemplate);
192   auto gid = getGID(option);
193   auto rg = std::make_shared<RequestGroup>(gid, option);
194   auto dctx = std::make_shared<DownloadContext>();
195   // may throw exception
196   bittorrent::loadFromMemory(torrent, dctx, option, auxUris,
197                              metaInfoUri.empty() ? "default" : metaInfoUri);
198   for (auto& fe : dctx->getFileEntries()) {
199     auto& uris = fe->getRemainingUris();
200     std::shuffle(std::begin(uris), std::end(uris),
201                  *SimpleRandomizer::getInstance());
202   }
203   if (metaInfoUri.empty()) {
204     rg->setMetadataInfo(createMetadataInfoDataOnly());
205   }
206   else {
207     rg->setMetadataInfo(createMetadataInfo(gid, metaInfoUri));
208   }
209   if (adjustAnnounceUri) {
210     bittorrent::adjustAnnounceUri(bittorrent::getTorrentAttrs(dctx), option);
211   }
212   auto sgl = util::parseIntSegments(option->get(PREF_SELECT_FILE));
213   sgl.normalize();
214   dctx->setFileFilter(std::move(sgl));
215   std::istringstream indexOutIn(option->get(PREF_INDEX_OUT));
216   auto indexPaths = util::createIndexPaths(indexOutIn);
217   for (const auto& i : indexPaths) {
218     dctx->setFilePathWithIndex(i.first,
219                                util::applyDir(option->get(PREF_DIR), i.second));
220   }
221   rg->setDownloadContext(dctx);
222 
223   if (option->getAsBool(PREF_ENABLE_RPC)) {
224     rg->setPauseRequested(option->getAsBool(PREF_PAUSE));
225   }
226 
227   // Remove "metalink" from Accept Type list to avoid server from
228   // responding Metalink file for web-seeding URIs.
229   dctx->setAcceptMetalink(false);
230   removeOneshotOption(option);
231   return rg;
232 }
233 } // namespace
234 
235 namespace {
236 std::shared_ptr<RequestGroup>
createBtMagnetRequestGroup(const std::string & magnetLink,const std::shared_ptr<Option> & optionTemplate)237 createBtMagnetRequestGroup(const std::string& magnetLink,
238                            const std::shared_ptr<Option>& optionTemplate)
239 {
240   auto dctx = std::make_shared<DownloadContext>(METADATA_PIECE_SIZE, 0);
241 
242   // We only know info hash. Total Length is unknown at this moment.
243   dctx->markTotalLengthIsUnknown();
244 
245   bittorrent::loadMagnet(magnetLink, dctx);
246   auto torrentAttrs = bittorrent::getTorrentAttrs(dctx);
247 
248   if (optionTemplate->getAsBool(PREF_BT_LOAD_SAVED_METADATA)) {
249     // Try to read .torrent file saved by aria2 (see
250     // UTMetadataPostDownloadHandler and --bt-save-metadata option).
251     auto torrentFilename =
252         util::applyDir(optionTemplate->get(PREF_DIR),
253                        util::toHex(torrentAttrs->infoHash) + ".torrent");
254 
255     bittorrent::ValueBaseBencodeParser parser;
256     auto torrent = parseFile(parser, torrentFilename);
257     if (torrent) {
258       auto rg = createBtRequestGroup(torrentFilename, optionTemplate, {},
259                                      torrent.get());
260       const auto& actualInfoHash =
261           bittorrent::getTorrentAttrs(rg->getDownloadContext())->infoHash;
262 
263       if (torrentAttrs->infoHash == actualInfoHash) {
264         A2_LOG_NOTICE(fmt("BitTorrent metadata was loaded from %s",
265                           torrentFilename.c_str()));
266         rg->setMetadataInfo(createMetadataInfo(rg->getGroupId(), magnetLink));
267         return rg;
268       }
269 
270       A2_LOG_WARN(
271           fmt("BitTorrent metadata loaded from %s has unexpected infohash %s\n",
272               torrentFilename.c_str(), util::toHex(actualInfoHash).c_str()));
273     }
274   }
275 
276   auto option = util::copy(optionTemplate);
277   bittorrent::adjustAnnounceUri(torrentAttrs, option);
278   // torrentAttrs->name may contain "/", but we use basename of
279   // FileEntry::getPath() to print out in-memory download entry.
280   // Since "/" is treated as separator, we replace it with "-".
281   dctx->getFirstFileEntry()->setPath(
282       util::replace(torrentAttrs->name, "/", "-"));
283 
284   auto gid = getGID(option);
285   auto rg = std::make_shared<RequestGroup>(gid, option);
286   rg->setFileAllocationEnabled(false);
287   rg->setPreLocalFileCheckEnabled(false);
288   rg->setDownloadContext(dctx);
289   rg->clearPostDownloadHandler();
290   rg->addPostDownloadHandler(
291       download_handlers::getUTMetadataPostDownloadHandler());
292   rg->setDiskWriterFactory(std::make_shared<ByteArrayDiskWriterFactory>());
293   rg->setMetadataInfo(createMetadataInfo(gid, magnetLink));
294   rg->markInMemoryDownload();
295 
296   if (option->getAsBool(PREF_ENABLE_RPC)) {
297     rg->setPauseRequested(option->getAsBool(PREF_PAUSE));
298   }
299 
300   removeOneshotOption(option);
301   return rg;
302 }
303 } // namespace
304 
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)305 void createRequestGroupForBitTorrent(
306     std::vector<std::shared_ptr<RequestGroup>>& result,
307     const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,
308     const std::string& metaInfoUri, const std::string& torrentData,
309     bool adjustAnnounceUri)
310 {
311   std::unique_ptr<ValueBase> torrent;
312   bittorrent::ValueBaseBencodeParser parser;
313   if (torrentData.empty()) {
314     torrent = parseFile(parser, metaInfoUri);
315   }
316   else {
317     ssize_t error;
318     torrent = parser.parseFinal(torrentData.c_str(), torrentData.size(), error);
319   }
320   if (!torrent) {
321     throw DL_ABORT_EX2("Bencode decoding failed",
322                        error_code::BENCODE_PARSE_ERROR);
323   }
324   createRequestGroupForBitTorrent(result, option, uris, metaInfoUri,
325                                   torrent.get(), adjustAnnounceUri);
326 }
327 
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)328 void createRequestGroupForBitTorrent(
329     std::vector<std::shared_ptr<RequestGroup>>& result,
330     const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,
331     const std::string& metaInfoUri, const ValueBase* torrent,
332     bool adjustAnnounceUri)
333 {
334   std::vector<std::string> nargs;
335   if (option->get(PREF_PARAMETERIZED_URI) == A2_V_TRUE) {
336     unfoldURI(nargs, uris);
337   }
338   else {
339     nargs = uris;
340   }
341   // we ignore -Z option here
342   size_t numSplit = option->getAsInt(PREF_SPLIT);
343   auto rg = createBtRequestGroup(metaInfoUri, option, nargs, torrent,
344                                  adjustAnnounceUri);
345   rg->setNumConcurrentCommand(numSplit);
346   result.push_back(rg);
347 }
348 
349 #endif // ENABLE_BITTORRENT
350 
351 #ifdef ENABLE_METALINK
createRequestGroupForMetalink(std::vector<std::shared_ptr<RequestGroup>> & result,const std::shared_ptr<Option> & option,const std::string & metalinkData)352 void createRequestGroupForMetalink(
353     std::vector<std::shared_ptr<RequestGroup>>& result,
354     const std::shared_ptr<Option>& option, const std::string& metalinkData)
355 {
356   if (metalinkData.empty()) {
357     Metalink2RequestGroup().generate(result, option->get(PREF_METALINK_FILE),
358                                      option,
359                                      option->get(PREF_METALINK_BASE_URI));
360   }
361   else {
362     auto dw = std::make_shared<ByteArrayDiskWriter>();
363     dw->setString(metalinkData);
364     Metalink2RequestGroup().generate(result, dw, option,
365                                      option->get(PREF_METALINK_BASE_URI));
366   }
367 }
368 #endif // ENABLE_METALINK
369 
370 namespace {
371 class AccRequestGroup {
372 private:
373   std::vector<std::shared_ptr<RequestGroup>>& requestGroups_;
374   ProtocolDetector detector_;
375   std::shared_ptr<Option> option_;
376   bool ignoreLocalPath_;
377   bool throwOnError_;
378 
379 public:
AccRequestGroup(std::vector<std::shared_ptr<RequestGroup>> & requestGroups,std::shared_ptr<Option> option,bool ignoreLocalPath=false,bool throwOnError=false)380   AccRequestGroup(std::vector<std::shared_ptr<RequestGroup>>& requestGroups,
381                   std::shared_ptr<Option> option, bool ignoreLocalPath = false,
382                   bool throwOnError = false)
383       : requestGroups_(requestGroups),
384         option_(std::move(option)),
385         ignoreLocalPath_(ignoreLocalPath),
386         throwOnError_(throwOnError)
387   {
388   }
389 
operator ()(const std::string & uri)390   void operator()(const std::string& uri)
391   {
392     if (detector_.isStreamProtocol(uri)) {
393       std::vector<std::string> streamURIs;
394       size_t numIter = option_->getAsInt(PREF_MAX_CONNECTION_PER_SERVER);
395       size_t numSplit = option_->getAsInt(PREF_SPLIT);
396       size_t num = std::min(numIter, numSplit);
397       for (size_t i = 0; i < num; ++i) {
398         streamURIs.push_back(uri);
399       }
400       auto rg = createRequestGroup(option_, streamURIs);
401       rg->setNumConcurrentCommand(numSplit);
402       requestGroups_.push_back(rg);
403     }
404 #ifdef ENABLE_BITTORRENT
405     else if (detector_.guessTorrentMagnet(uri)) {
406       requestGroups_.push_back(createBtMagnetRequestGroup(uri, option_));
407     }
408     else if (!ignoreLocalPath_ && detector_.guessTorrentFile(uri)) {
409       try {
410         bittorrent::ValueBaseBencodeParser parser;
411         auto torrent = parseFile(parser, uri);
412         if (!torrent) {
413           throw DL_ABORT_EX2("Bencode decoding failed",
414                              error_code::BENCODE_PARSE_ERROR);
415         }
416         requestGroups_.push_back(
417             createBtRequestGroup(uri, option_, {}, torrent.get()));
418       }
419       catch (RecoverableException& e) {
420         if (throwOnError_) {
421           throw;
422         }
423         else {
424           // error occurred while parsing torrent file.
425           // We simply ignore it.
426           A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
427         }
428       }
429     }
430 #endif // ENABLE_BITTORRENT
431 #ifdef ENABLE_METALINK
432     else if (!ignoreLocalPath_ && detector_.guessMetalinkFile(uri)) {
433       try {
434         Metalink2RequestGroup().generate(requestGroups_, uri, option_,
435                                          option_->get(PREF_METALINK_BASE_URI));
436       }
437       catch (RecoverableException& e) {
438         if (throwOnError_) {
439           throw;
440         }
441         else {
442           // error occurred while parsing metalink file.
443           // We simply ignore it.
444           A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
445         }
446       }
447     }
448 #endif // ENABLE_METALINK
449     else {
450       if (throwOnError_) {
451         throw DL_ABORT_EX(fmt(MSG_UNRECOGNIZED_URI, uri.c_str()));
452       }
453       else {
454         A2_LOG_ERROR(fmt(MSG_UNRECOGNIZED_URI, uri.c_str()));
455       }
456     }
457   }
458 };
459 } // namespace
460 
461 namespace {
462 class StreamProtocolFilter {
463 private:
464   ProtocolDetector detector_;
465 
466 public:
operator ()(const std::string & uri)467   bool operator()(const std::string& uri)
468   {
469     return detector_.isStreamProtocol(uri);
470   }
471 };
472 } // namespace
473 
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)474 void createRequestGroupForUri(
475     std::vector<std::shared_ptr<RequestGroup>>& result,
476     const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,
477     bool ignoreForceSequential, bool ignoreLocalPath, bool throwOnError)
478 {
479   std::vector<std::string> nargs;
480   if (option->get(PREF_PARAMETERIZED_URI) == A2_V_TRUE) {
481     unfoldURI(nargs, uris);
482   }
483   else {
484     nargs = uris;
485   }
486   if (!ignoreForceSequential &&
487       option->get(PREF_FORCE_SEQUENTIAL) == A2_V_TRUE) {
488     std::for_each(
489         std::begin(nargs), std::end(nargs),
490         AccRequestGroup(result, option, ignoreLocalPath, throwOnError));
491   }
492   else {
493     auto strmProtoEnd = std::stable_partition(
494         std::begin(nargs), std::end(nargs), StreamProtocolFilter());
495     // let's process http/ftp protocols first.
496     if (std::begin(nargs) != strmProtoEnd) {
497       size_t numIter = option->getAsInt(PREF_MAX_CONNECTION_PER_SERVER);
498       size_t numSplit = option->getAsInt(PREF_SPLIT);
499       std::vector<std::string> streamURIs;
500       splitURI(streamURIs, std::begin(nargs), strmProtoEnd, numSplit, numIter);
501       try {
502         auto rg = createRequestGroup(option, streamURIs, true);
503         rg->setNumConcurrentCommand(numSplit);
504         result.push_back(rg);
505       }
506       catch (RecoverableException& e) {
507         if (throwOnError) {
508           throw;
509         }
510         else {
511           A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
512         }
513       }
514     }
515     // process remaining URIs(local metalink, BitTorrent files)
516     std::for_each(
517         strmProtoEnd, std::end(nargs),
518         AccRequestGroup(result, option, ignoreLocalPath, throwOnError));
519   }
520 }
521 
createRequestGroupFromUriListParser(std::vector<std::shared_ptr<RequestGroup>> & result,const Option * option,UriListParser * uriListParser)522 bool createRequestGroupFromUriListParser(
523     std::vector<std::shared_ptr<RequestGroup>>& result, const Option* option,
524     UriListParser* uriListParser)
525 {
526   // Since result already contains some entries, we cache the size of
527   // it. Later, we use this value to determine RequestGroup is
528   // actually created.
529   size_t num = result.size();
530   while (uriListParser->hasNext()) {
531     std::vector<std::string> uris;
532     Option tempOption;
533     uriListParser->parseNext(uris, tempOption);
534     if (uris.empty()) {
535       continue;
536     }
537     auto requestOption = std::make_shared<Option>(*option);
538     requestOption->remove(PREF_OUT);
539     const auto& oparser = OptionParser::getInstance();
540     for (size_t i = 1, len = option::countOption(); i < len; ++i) {
541       auto pref = option::i2p(i);
542       auto h = oparser->find(pref);
543       if (h && h->getInitialOption() && tempOption.defined(pref)) {
544         requestOption->put(pref, tempOption.get(pref));
545       }
546     }
547     // This does not throw exception because throwOnError = false.
548     createRequestGroupForUri(result, requestOption, uris);
549     if (num < result.size()) {
550       return true;
551     }
552   }
553   return false;
554 }
555 
openUriListParser(const std::string & filename)556 std::shared_ptr<UriListParser> openUriListParser(const std::string& filename)
557 {
558   std::string listPath;
559 
560   auto f = File(filename);
561   if (!f.exists() || f.isDir()) {
562     throw DL_ABORT_EX(fmt(EX_FILE_OPEN, filename.c_str(),
563                           "File not found or it is a directory"));
564   }
565   listPath = filename;
566 
567   return std::make_shared<UriListParser>(listPath);
568 }
569 
createRequestGroupForUriList(std::vector<std::shared_ptr<RequestGroup>> & result,const std::shared_ptr<Option> & option)570 void createRequestGroupForUriList(
571     std::vector<std::shared_ptr<RequestGroup>>& result,
572     const std::shared_ptr<Option>& option)
573 {
574   auto uriListParser = openUriListParser(option->get(PREF_INPUT_FILE));
575   while (createRequestGroupFromUriListParser(result, option.get(),
576                                              uriListParser.get()))
577     ;
578 }
579 
createMetadataInfoFromFirstFileEntry(const std::shared_ptr<GroupId> & gid,const std::shared_ptr<DownloadContext> & dctx)580 std::shared_ptr<MetadataInfo> createMetadataInfoFromFirstFileEntry(
581     const std::shared_ptr<GroupId>& gid,
582     const std::shared_ptr<DownloadContext>& dctx)
583 {
584   if (dctx->getFileEntries().empty()) {
585     return nullptr;
586   }
587   else {
588     auto uris = dctx->getFileEntries()[0]->getUris();
589     if (uris.empty()) {
590       return nullptr;
591     }
592     return std::make_shared<MetadataInfo>(gid, uris[0]);
593   }
594 }
595 
removeOneshotOption(const std::shared_ptr<Option> & option)596 void removeOneshotOption(const std::shared_ptr<Option>& option)
597 {
598   option->remove(PREF_PAUSE);
599   option->remove(PREF_GID);
600 }
601 
602 } // namespace aria2
603