1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaAria2c.cc
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"
25 #include "zypp/media/MediaAria2c.h"
26 #include "zypp/media/proxyinfo/ProxyInfos.h"
27 #include "zypp/media/ProxyInfo.h"
28 #include "zypp/media/MediaUserAuth.h"
29 //#include "zypp/media/CurlConfig.h"
30 #include "zypp/thread/Once.h"
32 #include <sys/types.h>
34 #include <sys/mount.h>
38 #include <boost/format.hpp>
40 #define DETECT_DIR_INDEX 0
41 #define CONNECT_TIMEOUT 60
42 #define TRANSFER_TIMEOUT 60 * 3
43 #define TRANSFER_TIMEOUT_MAX 60 * 60
47 using namespace zypp::base;
51 //zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
52 //zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
54 extern "C" void _do_aria2c_free_once()
56 curl_global_cleanup();
59 extern "C" void globalAria2cFreeOnce()
61 zypp::thread::callOnce(g_FreeOnceFlag, _do_aria2c_free_once);
64 extern "C" void _do_aria2c_init_once()
66 CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
69 WAR << "curl global init failed" << endl;
73 // register at exit handler ?
74 // this may cause trouble, because we can protect it
75 // against ourself only.
76 // if the app sets an atexit handler as well, it will
77 // cause a double free while the second of them runs.
79 //std::atexit( globalFreeOnce);
83 inline void globalInitOnce()
85 zypp::thread::callOnce(g_InitOnceFlag, _do_aria2c_init_once);
89 int log_curl(CURL *curl, curl_infotype info,
90 char *ptr, size_t len, void *max_lvl)
96 case CURLINFO_TEXT: lvl = 1; pfx = "*"; break;
97 case CURLINFO_HEADER_IN: lvl = 2; pfx = "<"; break;
98 case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
101 if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
103 std::string msg(ptr, len);
104 std::list<std::string> lines;
105 std::list<std::string>::const_iterator line;
106 zypp::str::split(msg, std::back_inserter(lines), "\r\n");
107 for(line = lines.begin(); line != lines.end(); ++line)
109 DBG << pfx << " " << *line << endl;
124 ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
125 callback::SendReport<DownloadProgressReport> *_report=NULL)
140 callback::SendReport<DownloadProgressReport> *report;
141 // download rate of the last period (cca 1 sec)
143 // bytes downloaded at the start of the last period
145 // seconds from the start of the download
147 // average download rate
149 // last time the progress was reported
151 // bytes downloaded at the moment the progress was last reported
153 // bytes uploaded at the moment the progress was last reported
159 ///////////////////////////////////////////////////////////////////
161 inline void escape( string & str_r,
162 const char char_r, const string & escaped_r ) {
163 for ( string::size_type pos = str_r.find( char_r );
164 pos != string::npos; pos = str_r.find( char_r, pos ) ) {
165 str_r.replace( pos, 1, escaped_r );
169 inline string escapedPath( string path_r ) {
170 escape( path_r, ' ', "%20" );
174 inline string unEscape( string text_r ) {
175 char * tmp = curl_unescape( text_r.c_str(), 0 );
183 ///////////////////////////////////////////////////////////////////
185 // CLASS NAME : MediaAria2c
187 ///////////////////////////////////////////////////////////////////
189 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
190 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
191 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
193 //check if aria2c is present in the system
195 MediaAria2c::existsAria2cmd()
205 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
207 std::string ariaResponse( aria.receiveLine());
208 string::size_type pos = ariaResponse.find('/', 0 );
209 if( pos != string::npos )
215 const char *const MediaAria2c::agentString()
217 static const std::string _value( str::form( "ZYpp %s (with %s)", VERSION, MediaAria2c::_aria2cVersion.c_str() ));
218 return _value.c_str();
222 MediaAria2c::MediaAria2c( const Url & url_r,
223 const Pathname & attach_point_hint_r )
224 : MediaHandler( url_r, attach_point_hint_r,
225 "/", // urlpath at attachpoint
226 true ) // does_download
230 _curlError[0] = '\0';
234 MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
238 if( !attachPoint().empty())
240 PathInfo ainfo(attachPoint());
241 Pathname apath(attachPoint() + "XXXXXX");
242 char *atemp = ::strdup( apath.asString().c_str());
244 if( !ainfo.isDir() || !ainfo.userMayRWX() ||
245 atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
247 WAR << "attach point " << ainfo.path()
248 << " is not useable for " << url_r.getScheme() << endl;
249 setAttachPoint("", true);
251 else if( atest != NULL)
258 //At this point, we initialize aria2c path
259 _aria2cPath = Pathname( whereisAria2c().asString() );
262 _aria2cVersion = getAria2cVersion();
266 void MediaAria2c::setCookieFile( const Pathname &fileName )
268 _cookieFile = fileName;
272 ///////////////////////////////////////////////////////////////////
275 // METHOD NAME : MediaAria2c::attachTo
276 // METHOD TYPE : PMError
278 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
280 void MediaAria2c::attachTo (bool next)
284 ZYPP_THROW(MediaNotSupportedException(_url));
286 if ( !_url.isValid() )
287 ZYPP_THROW(MediaBadUrlException(_url));
291 CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
293 curl_version_info_data *curl_info = NULL;
294 curl_info = curl_version_info(CURLVERSION_NOW);
295 // curl_info does not need any free (is static)
296 if (curl_info->protocols)
298 const char * const *proto;
299 std::string scheme( _url.getScheme());
301 for(proto=curl_info->protocols; !found && *proto; ++proto)
303 if( scheme == std::string((const char *)*proto))
308 std::string msg("Unsupported protocol '");
311 ZYPP_THROW(MediaBadUrlException(_url, msg));
315 if( !isUseableAttachPoint(attachPoint()))
317 std::string mountpoint = createAttachPoint().asString();
319 if( mountpoint.empty())
320 ZYPP_THROW( MediaBadAttachPointException(url()));
322 setAttachPoint( mountpoint, true);
325 disconnectFrom(); // clean _curl if needed
327 _curl = curl_easy_init();
329 ZYPP_THROW(MediaAria2cInitException(_url));
333 char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
334 _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
337 curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1);
338 curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
339 curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
343 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
346 ZYPP_THROW(MediaAria2cSetOptException(_url, "Error setting error buffer"));
349 ret = curl_easy_setopt( _curl, CURLOPT_FAILONERROR, true );
352 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
355 ret = curl_easy_setopt( _curl, CURLOPT_NOSIGNAL, 1 );
358 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
365 _xfer_timeout = TRANSFER_TIMEOUT;
367 std::string param(_url.getQueryParam("timeout"));
370 long num = str::strtonum<long>( param);
371 if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
379 ret = curl_easy_setopt( _curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
382 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
385 if ( _url.getScheme() == "http" ) {
386 // follow any Location: header that the server sends as part of
387 // an HTTP header (#113275)
388 ret = curl_easy_setopt ( _curl, CURLOPT_FOLLOWLOCATION, true );
391 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
393 ret = curl_easy_setopt ( _curl, CURLOPT_MAXREDIRS, 3L );
396 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
399 ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, agentString() );
404 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
408 if ( _url.getScheme() == "https" )
410 bool verify_peer = false;
411 bool verify_host = false;
413 std::string verify( _url.getQueryParam("ssl_verify"));
414 if( verify.empty() ||
428 std::vector<std::string> flags;
429 std::vector<std::string>::const_iterator flag;
430 str::split( verify, std::back_inserter(flags), ",");
431 for(flag = flags.begin(); flag != flags.end(); ++flag)
445 ZYPP_THROW(MediaBadUrlException(_url, "Unknown ssl_verify flag"));
450 _ca_path = Pathname(_url.getQueryParam("ssl_capath")).asString();
451 if( _ca_path.empty())
453 _ca_path = "/etc/ssl/certs/";
456 if( !PathInfo(_ca_path).isDir() || !Pathname(_ca_path).absolute())
459 ZYPP_THROW(MediaBadUrlException(_url, "Invalid ssl_capath path"));
462 if( verify_peer || verify_host)
464 ret = curl_easy_setopt( _curl, CURLOPT_CAPATH, _ca_path.c_str());
467 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
471 ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L);
474 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
476 ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L);
479 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
482 ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, agentString() );
485 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
490 ---------------------------------------------------------------*
491 CURLOPT_USERPWD: [user name]:[password]
493 Url::username/password -> CURLOPT_USERPWD
494 If not provided, anonymous FTP identification
495 *---------------------------------------------------------------
497 if ( _url.getUsername().empty() ) {
498 if ( _url.getScheme() == "ftp" ) {
499 string id = "yast2@";
501 DBG << "Anonymous FTP identification: '" << id << "'" << endl;
502 _userpwd = "anonymous:" + id;
505 _userpwd = _url.getUsername();
506 if ( _url.getPassword().size() ) {
507 _userpwd += ":" + _url.getPassword();
511 if ( _userpwd.size() ) {
512 _userpwd = unEscape( _userpwd );
513 ret = curl_easy_setopt( _curl, CURLOPT_USERPWD, _userpwd.c_str() );
516 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
519 // HTTP authentication type
520 if(_url.getScheme() == "http" || _url.getScheme() == "https")
522 string use_auth = _url.getQueryParam("auth");
523 if( use_auth.empty())
524 use_auth = "digest,basic";
528 long auth = CurlAuthData::auth_type_str2long(use_auth);
529 if( auth != CURLAUTH_NONE)
531 DBG << "Enabling HTTP authentication methods: " << use_auth
532 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
534 ret = curl_easy_setopt( _curl, CURLOPT_HTTPAUTH, auth);
537 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
541 catch (MediaException & ex_r)
543 string auth_hint = getAuthHint();
545 DBG << "Rethrowing as MediaUnauthorizedException. auth hint: '"
546 << auth_hint << "'" << endl;
548 ZYPP_THROW(MediaUnauthorizedException(
549 _url, ex_r.msg(), _curlError, auth_hint
555 ---------------------------------------------------------------*
556 CURLOPT_PROXY: host[:port]
558 Url::option(proxy and proxyport) -> CURLOPT_PROXY
559 If not provided, /etc/sysconfig/proxy is evaluated
560 *---------------------------------------------------------------*
562 _proxy = _url.getQueryParam( "proxy" );
564 if ( ! _proxy.empty() ) {
565 string proxyport( _url.getQueryParam( "proxyport" ) );
566 if ( ! proxyport.empty() ) {
567 _proxy += ":" + proxyport;
571 ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
573 if ( proxy_info.enabled())
575 bool useproxy = true;
577 std::list<std::string> nope = proxy_info.noProxy();
578 for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
579 it != proxy_info.noProxyEnd();
582 std::string host( str::toLower(_url.getHost()));
583 std::string temp( str::toLower(*it));
585 // no proxy if it points to a suffix
586 // preceeded by a '.', that maches
587 // the trailing portion of the host.
588 if( temp.size() > 1 && temp.at(0) == '.')
590 if(host.size() > temp.size() &&
591 host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
593 DBG << "NO_PROXY: '" << *it << "' matches host '"
594 << host << "'" << endl;
600 // no proxy if we have an exact match
603 DBG << "NO_PROXY: '" << *it << "' matches host '"
604 << host << "'" << endl;
611 _proxy = proxy_info.proxy(_url.getScheme());
617 DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
619 if ( ! _proxy.empty() ) {
621 ret = curl_easy_setopt( _curl, CURLOPT_PROXY, _proxy.c_str() );
624 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
627 *---------------------------------------------------------------*
628 CURLOPT_PROXYUSERPWD: [user name]:[password]
630 Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
631 If not provided, $HOME/.curlrc is evaluated
632 *---------------------------------------------------------------*
634 _proxyuserpwd = _url.getQueryParam( "proxyuser" );
636 if ( ! _proxyuserpwd.empty() ) {
637 string proxypassword( _url.getQueryParam( "proxypassword" ) );
638 if ( ! proxypassword.empty() ) {
639 _proxyuserpwd += ":" + proxypassword;
642 if (curlconf.proxyuserpwd.empty())
643 DBG << "~/.curlrc does not contain the proxy-user option" << endl;
646 _proxyuserpwd = curlconf.proxyuserpwd;
647 DBG << "using proxy-user from ~/.curlrc" << endl;
651 _proxyuserpwd = unEscape( _proxyuserpwd );
652 ret = curl_easy_setopt( _curl, CURLOPT_PROXYUSERPWD, _proxyuserpwd.c_str() );
655 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
659 *---------------------------------------------------------------*
660 *---------------------------------------------------------------*
662 _currentCookieFile = _cookieFile.asString();
664 ret = curl_easy_setopt( _curl, CURLOPT_COOKIEFILE,
665 _currentCookieFile.c_str() );
668 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
671 ret = curl_easy_setopt( _curl, CURLOPT_COOKIEJAR,
672 _currentCookieFile.c_str() );
675 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
678 ret = curl_easy_setopt( _curl, CURLOPT_PROGRESSFUNCTION,
682 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
685 ret = curl_easy_setopt( _curl, CURLOPT_NOPROGRESS, false );
688 ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
691 // FIXME: need a derived class to propelly compare url's
692 MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
693 setMediaSource(media);
698 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
700 return MediaHandler::checkAttachPoint( apoint, true, true);
703 ///////////////////////////////////////////////////////////////////
706 // METHOD NAME : MediaAria2c::disconnectFrom
707 // METHOD TYPE : PMError
709 void MediaAria2c::disconnectFrom()
714 curl_easy_cleanup( _curl );
720 ///////////////////////////////////////////////////////////////////
723 // METHOD NAME : MediaAria2c::releaseFrom
724 // METHOD TYPE : void
726 // DESCRIPTION : Asserted that media is attached.
728 void MediaAria2c::releaseFrom( const std::string & ejectDev )
733 static Url getFileUrl(const Url & url, const Pathname & filename)
736 string path = url.getPathName();
737 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
738 filename.absolute() )
740 // If url has a path with trailing slash, remove the leading slash from
741 // the absolute file name
742 path += filename.asString().substr( 1, filename.asString().size() - 1 );
744 else if ( filename.relative() )
746 // Add trailing slash to path, if not already there
747 if (path.empty()) path = "/";
748 else if (*path.rbegin() != '/' ) path += "/";
749 // Remove "./" from begin of relative file name
750 path += filename.asString().substr( 2, filename.asString().size() - 2 );
754 path += filename.asString();
757 newurl.setPathName(path);
762 ///////////////////////////////////////////////////////////////////
764 // METHOD NAME : MediaAria2c::getFile
765 // METHOD TYPE : void
767 void MediaAria2c::getFile( const Pathname & filename ) const
769 // Use absolute file name to prevent access of files outside of the
770 // hierarchy below the attach point.
771 getFileCopy(filename, localPath(filename).absolutename());
774 ///////////////////////////////////////////////////////////////////
776 // METHOD NAME : MediaAria2c::getFileCopy
777 // METHOD TYPE : void
779 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
781 callback::SendReport<DownloadProgressReport> report;
783 Url fileurl(getFileUrl(_url, filename));
786 CurlAuthData auth_data;
792 report->start(_url, target.asString() );
797 str::form("--user-agent=\"%s\""
798 , agentString()).c_str(),
799 "--summary-interval=1",
800 "--follow-metalink=mem",
801 "--check-integrity=true",
802 str::form("--dir=\"%s\"", target.dirname().c_str()).c_str(),
803 fileurl.asString().c_str(),
807 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
811 for(std::string ariaResponse( aria.receiveLine());
812 ariaResponse.length();
813 ariaResponse = aria.receiveLine())
815 //cout << ariaResponse;
816 if (!ariaResponse.substr(0,9).compare("[#2 SIZE:")) {
820 size_t left_bound = ariaResponse.find('(',0) + 1;
821 size_t count = ariaResponse.find('%',left_bound) - left_bound;
822 //cout << ariaResponse.substr(left_bound, count) << endl;
823 //progressData.toMax();
824 report->progress ( std::atoi(ariaResponse.substr(left_bound, count).c_str()), _url, -1, -1 );
837 report->finish( _url , zypp::media::DownloadProgressReport::NO_ERROR, "");
842 // retry with proper authentication data
843 catch (MediaUnauthorizedException & ex_r)
845 callback::SendReport<AuthenticationReport> auth_report;
849 Here, we can try using this aria2c options - TODO
852 Set HTTP user. This affects all URLs.
855 Set HTTP password. This affects all URLs.
858 //if (!_url.getUsername().empty() && !retry)
859 // auth_data.setUserName(_url.getUsername());
862 //if (retry || !_url.getUsername().empty())
863 // prompt_msg = _("Invalid user name or password.");
864 //else // first prompt
865 // prompt_msg = boost::str(boost::format(
866 // _("Authentication required for '%s'")) % _url.asString());
868 // set available authentication types from the exception
869 //auth_data.setAuthType(ex_r.hint());
871 //if (auth_report->prompt(_url, prompt_msg, auth_data))
873 //DBG << "callback answer: retry" << endl
874 // << "CurlAuthData: " << auth_data << endl;
876 //if (auth_data.valid()) {
877 //_userpwd = auth_data.getUserPwd();
879 // set username and password
880 //CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _userpwd.c_str());
881 //if ( ret != 0 ) ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
884 //ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, auth_data.authType());
885 //if ( ret != 0 ) ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
892 // DBG << "callback answer: cancel" << endl;
893 // report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserString());
894 // ZYPP_RETHROW(ex_r);
897 // unexpected exception
898 catch (MediaException & excpt_r)
900 // FIXME: error number fix
901 report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserString());
902 ZYPP_RETHROW(excpt_r);
907 report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
910 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
915 CurlAuthData auth_data;
921 return doGetDoesFileExist( filename );
923 // authentication problem, retry with proper authentication data
924 catch (MediaUnauthorizedException & ex_r)
926 callback::SendReport<AuthenticationReport> auth_report;
928 if (!_url.getUsername().empty() && !retry)
929 auth_data.setUserName(_url.getUsername());
932 if (retry || !_url.getUsername().empty())
933 prompt_msg = _("Invalid user name or password.");
935 prompt_msg = boost::str(boost::format(
936 _("Authentication required for '%s'")) % _url.asString());
938 // set available authentication types from the exception
939 auth_data.setAuthType(ex_r.hint());
941 if (auth_report->prompt(_url, prompt_msg, auth_data))
943 DBG << "callback answer: retry" << endl
944 << "CurlAuthData: " << auth_data << endl;
946 if (auth_data.valid()) {
947 _userpwd = auth_data.getUserPwd();
949 // set username and password
950 //CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _userpwd.c_str());
951 //if ( ret != 0 ) ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
954 //ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, auth_data.authType());
955 //if ( ret != 0 ) ZYPP_THROW(MediaAria2cSetOptException(_url, _curlError));
962 DBG << "callback answer: cancel" << endl;
966 // unexpected exception
967 catch (MediaException & excpt_r)
969 ZYPP_RETHROW(excpt_r);
979 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
982 DBG << filename.asString() << endl;
985 ZYPP_THROW(MediaBadUrlException(_url));
987 if(_url.getHost().empty())
988 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
990 string path = _url.getPathName();
991 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
992 filename.absolute() ) {
993 // If url has a path with trailing slash, remove the leading slash from
994 // the absolute file name
995 path += filename.asString().substr( 1, filename.asString().size() - 1 );
996 } else if ( filename.relative() ) {
997 // Add trailing slash to path, if not already there
998 if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
999 // Remove "./" from begin of relative file name
1000 path += filename.asString().substr( 2, filename.asString().size() - 2 );
1002 path += filename.asString();
1006 url.setPathName( path );
1008 DBG << "URL: " << url.asString() << endl;
1009 // Use URL without options and without username and passwd
1010 // (some proxies dislike them in the URL).
1011 // Curl seems to need the just scheme, hostname and a path;
1012 // the rest was already passed as curl options (in attachTo).
1015 // Use asString + url::ViewOptions instead?
1016 curlUrl.setUsername( "" );
1017 curlUrl.setPassword( "" );
1018 curlUrl.setPathParams( "" );
1019 curlUrl.setQueryString( "" );
1020 curlUrl.setFragment( "" );
1023 // See also Bug #154197 and ftp url definition in RFC 1738:
1024 // The url "ftp://user@host/foo/bar/file" contains a path,
1025 // that is relative to the user's home.
1026 // The url "ftp://user@host//foo/bar/file" (or also with
1027 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1028 // contains an absolute path.
1030 string urlBuffer( curlUrl.asString());
1031 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1032 urlBuffer.c_str() );
1034 ZYPP_THROW(MediaAria2cSetOptException(url, _curlError));
1037 // instead of returning no data with NOBODY, we return
1038 // little data, that works with broken servers, and
1039 // works for ftp as well, because retrieving only headers
1040 // ftp will return always OK code ?
1041 if ( _url.getScheme() == "http" || _url.getScheme() == "https" )
1042 ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 );
1044 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
1047 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
1048 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1049 yes, this is why we never got to get NOBODY working before,
1050 because setting it changes this option too, and we also
1052 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1054 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
1055 ZYPP_THROW(MediaAria2cSetOptException(url, _curlError));
1059 FILE *file = ::fopen( "/dev/null", "w" );
1062 ERR << "fopen failed for /dev/null" << endl;
1063 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
1064 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1065 yes, this is why we never got to get NOBODY working before,
1066 because setting it changes this option too, and we also
1068 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1070 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
1072 ZYPP_THROW(MediaAria2cSetOptException(url, _curlError));
1074 ZYPP_THROW(MediaWriteException("/dev/null"));
1077 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1080 std::string err( _curlError);
1081 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1082 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
1083 yes, this is why we never got to get NOBODY working before,
1084 because setting it changes this option too, and we also
1086 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1088 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
1090 ZYPP_THROW(MediaAria2cSetOptException(url, _curlError));
1092 ZYPP_THROW(MediaAria2cSetOptException(url, err));
1094 // Set callback and perform.
1095 //ProgressData progressData(_xfer_timeout, url, &report);
1096 //report->start(url, dest);
1097 //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
1098 // WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1101 CURLcode ok = curl_easy_perform( _curl );
1102 MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
1104 // reset curl settings
1105 if ( _url.getScheme() == "http" || _url.getScheme() == "https" )
1107 ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
1108 yes, this is why we never got to get NOBODY working before,
1109 because setting it changes this option too, and we also
1111 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1113 ret = curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
1116 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1120 ZYPP_THROW(MediaAria2cSetOptException(url, _curlError));
1130 bool err_file_not_found = false;
1133 case CURLE_FTP_COULDNT_RETR_FILE:
1134 case CURLE_FTP_ACCESS_DENIED:
1135 err_file_not_found = true;
1137 case CURLE_HTTP_RETURNED_ERROR:
1139 long httpReturnCode = 0;
1140 CURLcode infoRet = curl_easy_getinfo( _curl,
1141 CURLINFO_RESPONSE_CODE,
1143 if ( infoRet == CURLE_OK )
1145 string msg = "HTTP response: " +
1146 str::numstring( httpReturnCode );
1147 if ( httpReturnCode == 401 )
1149 std::string auth_hint = getAuthHint();
1151 DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
1152 DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
1154 ZYPP_THROW(MediaUnauthorizedException(
1155 url, "Login failed.", _curlError, auth_hint
1159 if ( httpReturnCode == 403)
1161 ZYPP_THROW(MediaForbiddenException(url));
1164 if ( httpReturnCode == 404)
1166 err_file_not_found = true;
1171 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1172 ZYPP_THROW(MediaAria2cException(url, msg, _curlError));
1176 string msg = "Unable to retrieve HTTP response:";
1178 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1179 ZYPP_THROW(MediaAria2cException(url, msg, _curlError));
1183 case CURLE_UNSUPPORTED_PROTOCOL:
1184 case CURLE_URL_MALFORMAT:
1185 case CURLE_URL_MALFORMAT_USER:
1186 case CURLE_BAD_PASSWORD_ENTERED:
1187 case CURLE_FTP_USER_PASSWORD_INCORRECT:
1188 err = "Login failed";
1190 case CURLE_COULDNT_RESOLVE_PROXY:
1191 case CURLE_COULDNT_RESOLVE_HOST:
1192 case CURLE_COULDNT_CONNECT:
1193 case CURLE_FTP_CANT_GET_HOST:
1194 err = "Connection failed";
1196 case CURLE_WRITE_ERROR:
1197 err = "Write error";
1199 case CURLE_ABORTED_BY_CALLBACK:
1200 case CURLE_OPERATION_TIMEOUTED:
1201 err = "Timeout reached";
1202 ZYPP_THROW(MediaTimeoutException(url));
1204 case CURLE_SSL_CACERT:
1205 ZYPP_THROW(MediaBadCAException(url,_curlError));
1206 case CURLE_SSL_PEER_CERTIFICATE:
1208 err = curl_easy_strerror(ok);
1210 err = "Unrecognized error";
1214 if( err_file_not_found)
1216 // file does not exists
1221 // there was an error
1222 ZYPP_THROW(MediaAria2cException(url, string(), _curlError));
1225 catch (const MediaException & excpt_r)
1227 ZYPP_RETHROW(excpt_r);
1232 return ( ok == CURLE_OK );
1233 //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
1234 // WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1241 void MediaAria2c::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report) const
1243 DBG << filename.asString() << endl;
1246 ZYPP_THROW(MediaBadUrlException(_url));
1248 if(_url.getHost().empty())
1249 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
1251 Url url(getFileUrl(_url, filename));
1253 Pathname dest = target.absolutename();
1254 if( assert_dir( dest.dirname() ) )
1256 DBG << "assert_dir " << dest.dirname() << " failed" << endl;
1257 ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
1260 DBG << "URL: " << url.asString() << endl;
1261 // Use URL without options and without username and passwd
1262 // (some proxies dislike them in the URL).
1263 // Curl seems to need the just scheme, hostname and a path;
1264 // the rest was already passed as curl options (in attachTo).
1267 // Use asString + url::ViewOptions instead?
1268 curlUrl.setUsername( "" );
1269 curlUrl.setPassword( "" );
1270 curlUrl.setPathParams( "" );
1271 curlUrl.setQueryString( "" );
1272 curlUrl.setFragment( "" );
1275 // See also Bug #154197 and ftp url definition in RFC 1738:
1276 // The url "ftp://user@host/foo/bar/file" contains a path,
1277 // that is relative to the user's home.
1278 // The url "ftp://user@host//foo/bar/file" (or also with
1279 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1280 // contains an absolute path.
1282 string urlBuffer( curlUrl.asString());
1283 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1284 urlBuffer.c_str() );
1286 ZYPP_THROW(MediaAria2cSetOptException(url, _curlError));
1289 // set IFMODSINCE time condition (no download if not modified)
1290 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
1291 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, PathInfo(target).mtime());
1293 string destNew = target.asString() + ".new.zypp.XXXXXX";
1294 char *buf = ::strdup( destNew.c_str());
1297 ERR << "out of memory for temp file name" << endl;
1298 ZYPP_THROW(MediaSystemException(
1299 url, "out of memory for temp file name"
1303 int tmp_fd = ::mkstemp( buf );
1307 ERR << "mkstemp failed for file '" << destNew << "'" << endl;
1308 ZYPP_THROW(MediaWriteException(destNew));
1313 FILE *file = ::fdopen( tmp_fd, "w" );
1316 filesystem::unlink( destNew );
1317 ERR << "fopen failed for file '" << destNew << "'" << endl;
1318 ZYPP_THROW(MediaWriteException(destNew));
1321 DBG << "dest: " << dest << endl;
1322 DBG << "temp: " << destNew << endl;
1324 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1327 filesystem::unlink( destNew );
1328 ZYPP_THROW(MediaAria2cSetOptException(url, _curlError));
1331 // Set callback and perform.
1332 ProgressData progressData(_xfer_timeout, url, &report);
1333 report->start(url, dest);
1334 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
1335 WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1338 ret = curl_easy_perform( _curl );
1340 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
1341 WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1345 ERR << "curl error: " << ret << ": " << _curlError
1346 << ", temp file size " << PathInfo(destNew).size()
1347 << " byte." << endl;
1350 filesystem::unlink( destNew );
1354 bool err_file_not_found = false;
1356 case CURLE_UNSUPPORTED_PROTOCOL:
1357 case CURLE_URL_MALFORMAT:
1358 case CURLE_URL_MALFORMAT_USER:
1360 case CURLE_HTTP_RETURNED_ERROR:
1362 long httpReturnCode = 0;
1363 CURLcode infoRet = curl_easy_getinfo( _curl,
1364 CURLINFO_RESPONSE_CODE,
1366 if ( infoRet == CURLE_OK ) {
1367 string msg = "HTTP response: " +
1368 str::numstring( httpReturnCode );
1369 if ( httpReturnCode == 401 )
1371 std::string auth_hint = getAuthHint();
1373 DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
1374 DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
1376 ZYPP_THROW(MediaUnauthorizedException(
1377 url, "Login failed.", _curlError, auth_hint
1381 if ( httpReturnCode == 403)
1383 ZYPP_THROW(MediaForbiddenException(url));
1386 if ( httpReturnCode == 404)
1388 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1392 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1393 ZYPP_THROW(MediaAria2cException(url, msg, _curlError));
1397 string msg = "Unable to retrieve HTTP response:";
1399 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1400 ZYPP_THROW(MediaAria2cException(url, msg, _curlError));
1404 case CURLE_FTP_COULDNT_RETR_FILE:
1405 case CURLE_FTP_ACCESS_DENIED:
1406 err = "File not found";
1407 err_file_not_found = true;
1409 case CURLE_BAD_PASSWORD_ENTERED:
1410 case CURLE_FTP_USER_PASSWORD_INCORRECT:
1411 err = "Login failed";
1413 case CURLE_COULDNT_RESOLVE_PROXY:
1414 case CURLE_COULDNT_RESOLVE_HOST:
1415 case CURLE_COULDNT_CONNECT:
1416 case CURLE_FTP_CANT_GET_HOST:
1417 err = "Connection failed";
1419 case CURLE_WRITE_ERROR:
1420 err = "Write error";
1422 case CURLE_ABORTED_BY_CALLBACK:
1423 case CURLE_OPERATION_TIMEDOUT:
1424 if( progressData.reached)
1426 err = "Timeout reached";
1427 ZYPP_THROW(MediaTimeoutException(url));
1434 case CURLE_SSL_PEER_CERTIFICATE:
1436 err = "Unrecognized error";
1439 if( err_file_not_found)
1441 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1445 ZYPP_THROW(MediaAria2cException(url, err, _curlError));
1448 catch (const MediaException & excpt_r)
1450 ZYPP_RETHROW(excpt_r);
1453 #if DETECT_DIR_INDEX
1455 if(curlUrl.getScheme() == "http" ||
1456 curlUrl.getScheme() == "https")
1459 // try to check the effective url and set the not_a_file flag
1460 // if the url path ends with a "/", what usually means, that
1461 // we've received a directory index (index.html content).
1463 // Note: This may be dangerous and break file retrieving in
1464 // case of some server redirections ... ?
1466 bool not_a_file = false;
1468 CURLcode ret = curl_easy_getinfo( _curl,
1469 CURLINFO_EFFECTIVE_URL,
1471 if ( ret == CURLE_OK && ptr != NULL)
1476 std::string path( eurl.getPathName());
1477 if( !path.empty() && path != "/" && *path.rbegin() == '/')
1479 DBG << "Effective url ("
1481 << ") seems to provide the index of a directory"
1493 filesystem::unlink( destNew );
1494 ZYPP_THROW(MediaNotAFileException(_url, filename));
1497 #endif // DETECT_DIR_INDEX
1499 long httpReturnCode = 0;
1500 CURLcode infoRet = curl_easy_getinfo(_curl,
1501 CURLINFO_RESPONSE_CODE,
1503 bool modified = true;
1504 if (infoRet == CURLE_OK)
1506 DBG << "HTTP response: " + str::numstring(httpReturnCode);
1507 cout << "HTTP response: " + str::numstring(httpReturnCode);
1508 if ( httpReturnCode == 304 ) // not modified
1510 DBG << " Not modified.";
1517 WAR << "Could not get the reponse code." << endl;
1520 if (modified || infoRet != CURLE_OK)
1523 if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) )
1525 ERR << "Failed to chmod file " << destNew << endl;
1529 // move the temp file into dest
1530 if ( rename( destNew, dest ) != 0 ) {
1531 ERR << "Rename failed" << endl;
1532 ZYPP_THROW(MediaWriteException(dest));
1537 // close and remove the temp file
1539 filesystem::unlink( destNew );
1542 DBG << "done: " << PathInfo(dest) << endl;
1547 ///////////////////////////////////////////////////////////////////
1550 // METHOD NAME : MediaAria2c::getDir
1551 // METHOD TYPE : PMError
1553 // DESCRIPTION : Asserted that media is attached
1555 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
1557 filesystem::DirContent content;
1558 getDirInfo( content, dirname, /*dots*/false );
1560 for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
1561 Pathname filename = dirname + it->name;
1564 switch ( it->type ) {
1565 case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
1566 case filesystem::FT_FILE:
1567 getFile( filename );
1569 case filesystem::FT_DIR: // newer directory.yast contain at least directory info
1571 getDir( filename, recurse_r );
1573 res = assert_dir( localPath( filename ) );
1575 WAR << "Ignore error (" << res << ") on creating local directory '" << localPath( filename ) << "'" << endl;
1580 // don't provide devices, sockets, etc.
1586 ///////////////////////////////////////////////////////////////////
1589 // METHOD NAME : MediaAria2c::getDirInfo
1590 // METHOD TYPE : PMError
1592 // DESCRIPTION : Asserted that media is attached and retlist is empty.
1594 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
1595 const Pathname & dirname, bool dots ) const
1597 getDirectoryYast( retlist, dirname, dots );
1600 ///////////////////////////////////////////////////////////////////
1603 // METHOD NAME : MediaAria2c::getDirInfo
1604 // METHOD TYPE : PMError
1606 // DESCRIPTION : Asserted that media is attached and retlist is empty.
1608 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
1609 const Pathname & dirname, bool dots ) const
1611 getDirectoryYast( retlist, dirname, dots );
1614 ///////////////////////////////////////////////////////////////////
1617 // METHOD NAME : MediaAria2c::getAria2cVersion
1619 // DESCRIPTION : We get Aria2c version
1621 std::string MediaAria2c::getAria2cVersion()
1623 const char* argv[] =
1625 _aria2cPath.c_str(),
1630 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
1632 std::string vResponse = aria.receiveLine();
1637 ///////////////////////////////////////////////////////////////////
1640 // METHOD NAME : MediaAria2c::whereisAria2c
1642 // DESCRIPTION : Get aria2c path
1644 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
1646 Pathname MediaAria2c::whereisAria2c()
1648 Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
1650 const char* argv[] =
1658 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
1660 std::string ariaResponse( aria.receiveLine());
1663 string::size_type pos = ariaResponse.find('/', 0 );
1664 if( pos != string::npos )
1666 aria2cPathr = ariaResponse;
1667 string::size_type pose = ariaResponse.find(' ', pos + 1 );
1668 aria2cPathr = ariaResponse.substr( pos , pose - pos );
1669 MIL << "We will use aria2c located here: " << ariaResponse.substr( pos , pose - pos) << endl;
1673 MIL << "We don't know were is ari2ac binary. We will use aria2c located here: " << aria2cPathr << endl;
1679 ///////////////////////////////////////////////////////////////////
1682 // METHOD NAME : MediaAria2c::progressCallback
1683 // METHOD TYPE : int
1685 // DESCRIPTION : Progress callback triggered from MediaAria2c::getFile
1688 int MediaAria2c::progressCallback( void *clientp,
1689 double dltotal, double dlnow,
1690 double ultotal, double ulnow)
1692 ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
1695 time_t now = time(NULL);
1698 // reset time of last change in case initial time()
1699 // failed or the time was adjusted (goes backward)
1700 if( pdata->ltime <= 0 || pdata->ltime > now)
1705 // start time counting as soon as first data arrives
1706 // (skip the connection / redirection time at begin)
1708 if (dlnow > 0 || ulnow > 0)
1710 dif = (now - pdata->ltime);
1711 dif = dif > 0 ? dif : 0;
1716 // update the drate_avg and drate_period only after a second has passed
1717 // (this callback is called much more often than a second)
1718 // otherwise the values would be far from accurate when measuring
1719 // the time in seconds
1720 //! \todo more accurate download rate computationn, e.g. compute average value from last 5 seconds, or work with milliseconds instead of seconds
1722 if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal ))
1723 pdata->drate_avg = (dlnow / pdata->secs);
1727 pdata->drate_period = ((dlnow - pdata->dload_period) / dif);
1728 pdata->dload_period = dlnow;
1732 // send progress report first, abort transfer if requested
1735 if (!(*(pdata->report))->progress(int( dlnow * 100 / dltotal ),
1738 pdata->drate_period))
1740 return 1; // abort transfer
1744 // check if we there is a timeout set
1745 if( pdata->timeout > 0)
1749 bool progress = false;
1751 // update download data if changed, mark progress
1752 if( dlnow != pdata->dload)
1755 pdata->dload = dlnow;
1758 // update upload data if changed, mark progress
1759 if( ulnow != pdata->uload)
1762 pdata->uload = ulnow;
1766 if( !progress && (now >= (pdata->ltime + pdata->timeout)))
1768 pdata->reached = true;
1769 return 1; // aborts transfer
1778 string MediaAria2c::getAuthHint() const
1780 long auth_info = CURLAUTH_NONE;
1783 curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
1785 if(infoRet == CURLE_OK)
1787 return CurlAuthData::auth_type_long2str(auth_info);
1793 } // namespace media