big MediaCurl refactoring, to share the settings initialization with
[platform/upstream/libzypp.git] / zypp / media / MediaAria2c.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaAria2c.cc
10  *
11 */
12
13 #include <iostream>
14 #include <list>
15
16 #include "zypp/base/Logger.h"
17 #include "zypp/ExternalProgram.h"
18 #include "zypp/ProgressData.h"
19 #include "zypp/base/String.h"
20 #include "zypp/base/Gettext.h"
21 #include "zypp/base/Sysconfig.h"
22 #include "zypp/base/Gettext.h"
23 #include "zypp/ZYppCallbacks.h"
24
25 #include "zypp/Edition.h"
26 #include "zypp/Target.h"
27 #include "zypp/ZYppFactory.h"
28
29 #include "zypp/media/MediaAria2c.h"
30 #include "zypp/media/proxyinfo/ProxyInfos.h"
31 #include "zypp/media/ProxyInfo.h"
32 #include "zypp/media/MediaUserAuth.h"
33 #include "zypp/media/MediaCurl.h"
34 #include "zypp/thread/Once.h"
35 #include <cstdlib>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/mount.h>
39 #include <errno.h>
40 #include <dirent.h>
41 #include <unistd.h>
42 #include <boost/format.hpp>
43
44 #define  DETECT_DIR_INDEX       0
45 #define  CONNECT_TIMEOUT        60
46 #define  TRANSFER_TIMEOUT       60 * 3
47 #define  TRANSFER_TIMEOUT_MAX   60 * 60
48
49
50 using namespace std;
51 using namespace zypp::base;
52
53 namespace zypp
54 {
55 namespace media
56 {
57
58 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
59 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
60 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
61
62 //check if aria2c is present in the system
63 bool
64 MediaAria2c::existsAria2cmd()
65 {
66     const char* argv[] =
67     {
68       "which",
69       "aria2c",
70       NULL
71     };
72
73     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
74     return ( aria.close() == 0 );
75 }
76
77 /**
78  * comannd line for aria.
79  * The argument list gets passed as reference
80  * and it is filled.
81  */
82 void fillAriaCmdLine( const Pathname &ariapath,
83                       const string &ariaver,
84                       const TransferSettings &s,
85                       const Url &url,
86                       const Pathname &destination,
87                       ExternalProgram::Arguments &args )
88 {
89     args.push_back(ariapath.c_str());
90     args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
91     args.push_back("--summary-interval=1");
92     args.push_back("--follow-metalink=mem");
93     args.push_back("--check-integrity=true");
94
95     // only present in recent aria lets find out the aria version
96     vector<string> fields;    
97     // "aria2c version x.x"
98     str::split( ariaver, std::back_inserter(fields));
99     if ( fields.size() == 3 )
100     {
101         if ( Edition(fields[2]) >= Edition("1.1.2") )
102             args.push_back( "--use-head=false");
103     }
104     
105     if ( s.maxDownloadSpeed() > 0 )
106         args.push_back(str::form("--max-download-limit=%ld", s.maxDownloadSpeed()));
107     if ( s.minDownloadSpeed() > 0 )
108         args.push_back(str::form("--lowest-speed-limit=%ld", s.minDownloadSpeed()));
109
110     args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
111
112     if ( Edition(fields[2]) < Edition("1.2.0") )
113         WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
114     
115     // TODO make this one configurable
116     args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
117
118     // add the anonymous id.
119     for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
120           it != s.headersEnd();
121           ++it )
122         args.push_back(str::form("--header=%s", it->c_str() ));
123         
124     args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
125
126     if ( s.username().empty() )
127     {
128         if ( url.getScheme() == "ftp" )
129         {
130             // set anonymous ftp
131             args.push_back(str::form("--ftp-user=%s", "suseuser" ));
132             args.push_back(str::form("--ftp-passwd=%s", VERSION ));
133
134             string id = "yast2";
135             id += VERSION;
136             DBG << "Anonymous FTP identification: '" << id << "'" << endl;
137         }
138     }
139     else
140     {
141         if ( url.getScheme() == "ftp" )
142             args.push_back(str::form("--ftp-user=%s", s.username().c_str() ));
143         else if ( url.getScheme() == "http" ||
144                   url.getScheme() == "https" )
145             args.push_back(str::form("--http-user=%s", s.username().c_str() ));
146         
147         if ( s.password().size() )
148         {
149             if ( url.getScheme() == "ftp" )
150                 args.push_back(str::form("--ftp-passwd=%s", s.password().c_str() ));
151             else if ( url.getScheme() == "http" ||
152                       url.getScheme() == "https" )
153                 args.push_back(str::form("--http-passwd=%s", s.password().c_str() ));
154         }
155     }
156     
157     if ( s.proxyEnabled() )
158     {
159         args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
160         if ( ! s.proxyUsername().empty() )
161         {
162             args.push_back(str::form("--http-proxy-user=%s", s.proxyUsername().c_str() ));
163             if ( ! s.proxyPassword().empty() )
164                 args.push_back(str::form("--http-proxy-passwd=%s", s.proxyPassword().c_str() ));
165         }
166     }
167
168     if ( ! destination.empty() )
169         args.push_back(str::form("--dir=%s", destination.c_str()));
170
171     args.push_back(url.asString().c_str());
172 }
173
174 const char *const MediaAria2c::agentString()
175 {
176   // we need to add the release and identifier to the
177   // agent string.
178   // The target could be not initialized, and then this information
179   // is not available.
180   Target_Ptr target = zypp::getZYpp()->getTarget();
181
182   static const std::string _value(
183     str::form(
184        "ZYpp %s (%s) %s"
185        , VERSION
186        , MediaAria2c::_aria2cVersion.c_str()
187        , target ? target->targetDistribution().c_str() : ""
188     )
189   );
190   return _value.c_str();
191 }
192
193
194
195 MediaAria2c::MediaAria2c( const Url &      url_r,
196                       const Pathname & attach_point_hint_r )
197     : MediaCurl( url_r, attach_point_hint_r )
198 {
199   MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
200
201    //At this point, we initialize aria2c path
202    _aria2cPath = Pathname( whereisAria2c().asString() );
203
204    //Get aria2c version
205    _aria2cVersion = getAria2cVersion();
206 }
207
208 void MediaAria2c::attachTo (bool next)
209 {
210   MediaCurl::attachTo(next);
211   _settings.setUserAgentString(agentString());
212 }
213
214 bool
215 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
216 {
217     return MediaCurl::checkAttachPoint( apoint );
218 }
219
220 void MediaAria2c::disconnectFrom()
221 {
222     MediaCurl::disconnectFrom();
223 }
224
225 void MediaAria2c::releaseFrom( const std::string & ejectDev )
226 {
227   MediaCurl::releaseFrom(ejectDev);
228 }
229
230 static Url getFileUrl(const Url & url, const Pathname & filename)
231 {
232   Url newurl(url);
233   string path = url.getPathName();
234   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
235        filename.absolute() )
236   {
237     // If url has a path with trailing slash, remove the leading slash from
238     // the absolute file name
239     path += filename.asString().substr( 1, filename.asString().size() - 1 );
240   }
241   else if ( filename.relative() )
242   {
243     // Add trailing slash to path, if not already there
244     if (path.empty()) path = "/";
245     else if (*path.rbegin() != '/' ) path += "/";
246     // Remove "./" from begin of relative file name
247     path += filename.asString().substr( 2, filename.asString().size() - 2 );
248   }
249   else
250   {
251     path += filename.asString();
252   }
253
254   newurl.setPathName(path);
255   return newurl;
256 }
257
258 void MediaAria2c::getFile( const Pathname & filename ) const
259 {
260     // Use absolute file name to prevent access of files outside of the
261     // hierarchy below the attach point.
262     getFileCopy(filename, localPath(filename).absolutename());
263 }
264
265 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
266 {
267   callback::SendReport<DownloadProgressReport> report;
268
269   Url fileurl(getFileUrl(_url, filename));
270
271   bool retry = false;
272
273   ExternalProgram::Arguments args;
274
275   fillAriaCmdLine(_aria2cPath, _aria2cVersion, _settings, fileurl, target.dirname(), args);
276   
277   do
278   {
279     try
280     {
281       report->start(_url, target.asString() );
282
283       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
284       int nLine = 0;
285
286       //Process response
287       for(std::string ariaResponse( aria.receiveLine());
288           ariaResponse.length();
289           ariaResponse = aria.receiveLine())
290       {
291         //cout << ariaResponse;
292
293         if (!ariaResponse.substr(0,31).compare("Exception: Authorization failed") )
294         {
295             ZYPP_THROW(MediaUnauthorizedException(
296                   _url, "Login failed.", "Login failed", "auth hint"
297                 ));
298         }
299         if (!ariaResponse.substr(0,29).compare("Exception: Resource not found") )
300         {
301             ZYPP_THROW(MediaFileNotFoundException(_url, filename));
302         }
303
304         if (!ariaResponse.substr(0,9).compare("[#2 SIZE:"))
305         {
306           if (!nLine)
307           {
308             size_t left_bound = ariaResponse.find('(',0) + 1;
309             size_t count = ariaResponse.find('%',left_bound) - left_bound;
310             //cout << ariaResponse.substr(left_bound, count) << endl;
311             //progressData.toMax();
312             report->progress ( std::atoi(ariaResponse.substr(left_bound, count).c_str()), _url, -1, -1 );
313             nLine = 1;
314           }
315           else
316           {
317             nLine = 0;
318           }
319         }
320       }
321
322       aria.close();
323
324       report->finish( _url ,  zypp::media::DownloadProgressReport::NO_ERROR, "");
325       retry = false;
326     }
327
328     // retry with proper authentication data
329     catch (MediaUnauthorizedException & ex_r)
330     {
331       if(authenticate(ex_r.hint(), !retry))
332         retry = true;
333       else
334       {
335         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
336         ZYPP_RETHROW(ex_r);
337       }
338
339     }
340     // unexpected exception
341     catch (MediaException & excpt_r)
342     {
343       // FIXME: error number fix
344       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
345       ZYPP_RETHROW(excpt_r);
346     }
347   }
348   while (retry);
349
350   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
351 }
352
353 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
354 {
355     return MediaCurl::getDoesFileExist(filename);
356 }
357
358 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
359 {
360     return MediaCurl::doGetDoesFileExist(filename);
361 }
362     
363 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
364 {
365     MediaCurl::getDir(dirname, recurse_r);
366 }
367
368 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
369 {
370     return false;
371 }
372
373
374 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
375                                const Pathname & dirname, bool dots ) const
376 {
377   getDirectoryYast( retlist, dirname, dots );
378 }
379
380 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
381                             const Pathname & dirname, bool dots ) const
382 {
383   getDirectoryYast( retlist, dirname, dots );
384 }
385
386 std::string MediaAria2c::getAria2cVersion()
387 {
388     const char* argv[] =
389     {
390         _aria2cPath.c_str(),
391       "--version",
392       NULL
393     };
394
395     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
396
397     std::string vResponse = aria.receiveLine();
398     aria.close();
399     return str::trim(vResponse);
400 }
401
402 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
403
404 Pathname MediaAria2c::whereisAria2c()
405 {
406     Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
407
408     const char* argv[] =
409     {
410       "which",
411       "aria2c",
412       NULL
413     };
414
415     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
416
417     std::string ariaResponse( aria.receiveLine());
418     int code = aria.close();
419
420     if( code == 0 )
421     {
422         aria2cPathr = str::trim(ariaResponse);
423         MIL << "We will use aria2c located here:  " << aria2cPathr << endl;
424     }
425     else
426     {
427         MIL << "We don't know were is ari2ac binary. We will use aria2c located here:  " << aria2cPathr << endl;
428     }
429
430     return aria2cPathr;
431 }
432
433 } // namespace media
434 } // namespace zypp
435 //