1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaCurl.cc
16 #include "zypp/base/Logger.h"
17 #include "zypp/ExternalProgram.h"
18 #include "zypp/base/String.h"
19 #include "zypp/base/Sysconfig.h"
21 #include "zypp/media/MediaCurl.h"
22 #include "zypp/media/proxyinfo/ProxyInfos.h"
23 #include "zypp/media/ProxyInfo.h"
24 #include "zypp/thread/Once.h"
26 #include <sys/types.h>
28 #include <sys/mount.h>
35 #define DETECT_DIR_INDEX 0
36 #define CONNECT_TIMEOUT 60
37 #define TRANSFER_TIMEOUT 60 * 3
38 #define TRANSFER_TIMEOUT_MAX 60 * 60
42 using namespace zypp::base;
46 zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
47 zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
49 extern "C" void _do_free_once()
51 curl_global_cleanup();
54 extern "C" void globalFreeOnce()
56 zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
59 extern "C" void _do_init_once()
61 CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
64 WAR << "curl global init failed" << endl;
68 // register at exit handler ?
69 // this may cause trouble, because we can protect it
70 // against ourself only.
71 // if the app sets an atexit handler as well, it will
72 // cause a double free while the second of them runs.
74 //std::atexit( globalFreeOnce);
77 inline void globalInitOnce()
79 zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
82 int log_curl(CURL *curl, curl_infotype info,
83 char *ptr, size_t len, void *max_lvl)
89 case CURLINFO_TEXT: lvl = 1; pfx = "*"; break;
90 case CURLINFO_HEADER_IN: lvl = 2; pfx = "<"; break;
91 case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
94 if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
96 std::string msg(ptr, len);
97 std::list<std::string> lines;
98 std::list<std::string>::const_iterator line;
99 zypp::str::split(msg, std::back_inserter(lines), "\r\n");
100 for(line = lines.begin(); line != lines.end(); ++line)
102 DBG << pfx << " " << *line << endl;
115 ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
116 callback::SendReport<DownloadProgressReport> *_report=NULL)
127 callback::SendReport<DownloadProgressReport> *report;
135 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
136 std::string MediaCurl::_agent = "Novell ZYPP Installer";
138 ///////////////////////////////////////////////////////////////////
140 static inline void escape( string & str_r,
141 const char char_r, const string & escaped_r ) {
142 for ( string::size_type pos = str_r.find( char_r );
143 pos != string::npos; pos = str_r.find( char_r, pos ) ) {
144 str_r.replace( pos, 1, escaped_r );
148 static inline string escapedPath( string path_r ) {
149 escape( path_r, ' ', "%20" );
153 static inline string unEscape( string text_r ) {
154 char * tmp = curl_unescape( text_r.c_str(), 0 );
160 ///////////////////////////////////////////////////////////////////
162 // CLASS NAME : MediaCurl
164 ///////////////////////////////////////////////////////////////////
166 MediaCurl::MediaCurl( const Url & url_r,
167 const Pathname & attach_point_hint_r )
168 : MediaHandler( url_r, attach_point_hint_r,
169 "/", // urlpath at attachpoint
170 true ), // does_download
173 _curlError[0] = '\0';
176 MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
180 if( !attachPoint().empty())
182 PathInfo ainfo(attachPoint());
183 Pathname apath(attachPoint() + "XXXXXX");
184 char *atemp = ::strdup( apath.asString().c_str());
186 if( !ainfo.isDir() || !ainfo.userMayRWX() ||
187 atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
189 WAR << "attach point " << ainfo.path()
190 << " is not useable for " << url_r.getScheme() << endl;
191 setAttachPoint("", true);
193 else if( atest != NULL)
201 void MediaCurl::setCookieFile( const Pathname &fileName )
203 _cookieFile = fileName;
206 ///////////////////////////////////////////////////////////////////
209 // METHOD NAME : MediaCurl::attachTo
210 // METHOD TYPE : PMError
212 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
214 void MediaCurl::attachTo (bool next)
217 ZYPP_THROW(MediaNotSupportedException(_url));
219 if ( !_url.isValid() )
220 ZYPP_THROW(MediaBadUrlException(_url));
222 curl_version_info_data *curl_info = NULL;
223 curl_info = curl_version_info(CURLVERSION_NOW);
224 // curl_info does not need any free (is static)
225 if (curl_info->protocols)
227 const char * const *proto;
228 std::string scheme( _url.getScheme());
230 for(proto=curl_info->protocols; !found && *proto; ++proto)
232 if( scheme == std::string((const char *)*proto))
237 std::string msg("Unsupported protocol '");
240 ZYPP_THROW(MediaBadUrlException(_url, msg));
244 if( !isUseableAttachPoint(attachPoint()))
246 std::string mountpoint = createAttachPoint().asString();
248 if( mountpoint.empty())
249 ZYPP_THROW( MediaBadAttachPointException(url()));
251 setAttachPoint( mountpoint, true);
254 disconnectFrom(); // clean _curl if needed
255 _curl = curl_easy_init();
257 ZYPP_THROW(MediaCurlInitException(_url));
261 char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
262 _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
265 curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1);
266 curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
267 curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
271 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
274 ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
277 ret = curl_easy_setopt( _curl, CURLOPT_FAILONERROR, true );
280 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
283 ret = curl_easy_setopt( _curl, CURLOPT_NOSIGNAL, 1 );
286 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
293 _xfer_timeout = TRANSFER_TIMEOUT;
295 std::string param(_url.getQueryParam("timeout"));
298 long num = str::strtonum<long>( param);
299 if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
307 ret = curl_easy_setopt( _curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
310 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
313 if ( _url.getScheme() == "http" ) {
314 // follow any Location: header that the server sends as part of
315 // an HTTP header (#113275)
316 ret = curl_easy_setopt ( _curl, CURLOPT_FOLLOWLOCATION, true );
319 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
321 ret = curl_easy_setopt ( _curl, CURLOPT_MAXREDIRS, 3L );
324 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
326 ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
329 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
333 if ( _url.getScheme() == "https" )
335 bool verify_peer = false;
336 bool verify_host = false;
338 std::string verify( _url.getQueryParam("ssl_verify"));
339 if( verify.empty() ||
353 std::vector<std::string> flags;
354 std::vector<std::string>::const_iterator flag;
355 str::split( verify, std::back_inserter(flags), ",");
356 for(flag = flags.begin(); flag != flags.end(); ++flag)
370 ZYPP_THROW(MediaBadUrlException(_url, "Unknown ssl_verify flag"));
375 _ca_path = Pathname(_url.getQueryParam("ssl_capath")).asString();
376 if( _ca_path.empty())
378 _ca_path = "/etc/ssl/certs/";
381 if( !PathInfo(_ca_path).isDir() || !Pathname(_ca_path).absolute())
384 ZYPP_THROW(MediaBadUrlException(_url, "Invalid ssl_capath path"));
387 if( verify_peer || verify_host)
389 ret = curl_easy_setopt( _curl, CURLOPT_CAPATH, _ca_path.c_str());
392 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
396 ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L);
399 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
401 ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L);
404 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
407 ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
410 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
415 /*---------------------------------------------------------------*
416 CURLOPT_USERPWD: [user name]:[password]
418 Url::username/password -> CURLOPT_USERPWD
419 If not provided, anonymous FTP identification
420 *---------------------------------------------------------------*/
422 if ( _url.getUsername().empty() ) {
423 if ( _url.getScheme() == "ftp" ) {
424 string id = "yast2@";
426 DBG << "Anonymous FTP identification: '" << id << "'" << endl;
427 _userpwd = "anonymous:" + id;
430 _userpwd = _url.getUsername();
431 if ( _url.getPassword().size() ) {
432 _userpwd += ":" + _url.getPassword();
436 if ( _userpwd.size() ) {
437 _userpwd = unEscape( _userpwd );
438 ret = curl_easy_setopt( _curl, CURLOPT_USERPWD, _userpwd.c_str() );
441 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
444 if( !_url.getQueryParam("auth").empty() &&
445 (_url.getScheme() == "http" || _url.getScheme() == "https"))
447 std::vector<std::string> list;
448 std::vector<std::string>::const_iterator it;
449 str::split(_url.getQueryParam("auth"), std::back_inserter(list), ",");
451 long auth = CURLAUTH_NONE;
452 for(it = list.begin(); it != list.end(); ++it)
456 auth |= CURLAUTH_BASIC;
461 auth |= CURLAUTH_DIGEST;
464 if((curl_info && (curl_info->features & CURL_VERSION_NTLM)) &&
467 auth |= CURLAUTH_NTLM;
470 if((curl_info && (curl_info->features & CURL_VERSION_SPNEGO)) &&
471 (*it == "spnego" || *it == "negotiate"))
473 // there is no separate spnego flag for auth
474 auth |= CURLAUTH_GSSNEGOTIATE;
477 if((curl_info && (curl_info->features & CURL_VERSION_GSSNEGOTIATE)) &&
478 (*it == "gssnego" || *it == "negotiate"))
480 auth |= CURLAUTH_GSSNEGOTIATE;
484 std::string msg("Unsupported HTTP authentication method '");
488 ZYPP_THROW(MediaBadUrlException(_url, msg));
492 if( auth != CURLAUTH_NONE)
494 DBG << "Enabling HTTP authentication methods: "
495 << _url.getQueryParam("auth") << std::endl;
497 ret = curl_easy_setopt( _curl, CURLOPT_HTTPAUTH, auth);
500 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
506 /*---------------------------------------------------------------*
507 CURLOPT_PROXY: host[:port]
509 Url::option(proxy and proxyport) -> CURLOPT_PROXY
510 If not provided, /etc/sysconfig/proxy is evaluated
511 *---------------------------------------------------------------*/
513 _proxy = _url.getQueryParam( "proxy" );
515 if ( ! _proxy.empty() ) {
516 string proxyport( _url.getQueryParam( "proxyport" ) );
517 if ( ! proxyport.empty() ) {
518 _proxy += ":" + proxyport;
522 ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
524 if ( proxy_info.enabled())
526 bool useproxy = true;
528 std::list<std::string> nope = proxy_info.noProxy();
529 for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
530 it != proxy_info.noProxyEnd();
533 std::string host( str::toLower(_url.getHost()));
534 std::string temp( str::toLower(*it));
536 // no proxy if it points to a suffix
537 // preceeded by a '.', that maches
538 // the trailing portion of the host.
539 if( temp.size() > 1 && temp.at(0) == '.')
541 if(host.size() > temp.size() &&
542 host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
544 DBG << "NO_PROXY: '" << *it << "' matches host '"
545 << host << "'" << endl;
551 // no proxy if we have an exact match
554 DBG << "NO_PROXY: '" << *it << "' matches host '"
555 << host << "'" << endl;
562 _proxy = proxy_info.proxy(_url.getScheme());
568 DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
570 if ( ! _proxy.empty() ) {
572 ret = curl_easy_setopt( _curl, CURLOPT_PROXY, _proxy.c_str() );
575 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
578 /*---------------------------------------------------------------*
579 CURLOPT_PROXYUSERPWD: [user name]:[password]
581 Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
582 If not provided, $HOME/.curlrc is evaluated
583 *---------------------------------------------------------------*/
585 _proxyuserpwd = _url.getQueryParam( "proxyuser" );
587 if ( ! _proxyuserpwd.empty() ) {
589 string proxypassword( _url.getQueryParam( "proxypassword" ) );
590 if ( ! proxypassword.empty() ) {
591 _proxyuserpwd += ":" + proxypassword;
595 char *home = getenv("HOME");
598 Pathname curlrcFile = string( home ) + string( "/.curlrc" );
600 PathInfo h_info(string(home), PathInfo::LSTAT);
601 PathInfo c_info(curlrcFile, PathInfo::LSTAT);
603 if( h_info.isDir() && h_info.owner() == getuid() &&
604 c_info.isFile() && c_info.owner() == getuid())
606 map<string,string> rc_data = base::sysconfig::read( curlrcFile );
608 map<string,string>::const_iterator it = rc_data.find("proxy-user");
609 if (it != rc_data.end())
610 _proxyuserpwd = it->second;
614 WAR << "Not allowed to parse '" << curlrcFile
615 << "': bad file owner" << std::endl;
620 _proxyuserpwd = unEscape( _proxyuserpwd );
621 ret = curl_easy_setopt( _curl, CURLOPT_PROXYUSERPWD, _proxyuserpwd.c_str() );
624 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
628 /*---------------------------------------------------------------*
629 *---------------------------------------------------------------*/
631 _currentCookieFile = _cookieFile.asString();
633 ret = curl_easy_setopt( _curl, CURLOPT_COOKIEFILE,
634 _currentCookieFile.c_str() );
637 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
640 ret = curl_easy_setopt( _curl, CURLOPT_COOKIEJAR,
641 _currentCookieFile.c_str() );
644 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
647 ret = curl_easy_setopt( _curl, CURLOPT_PROGRESSFUNCTION,
651 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
654 ret = curl_easy_setopt( _curl, CURLOPT_NOPROGRESS, false );
657 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
660 // FIXME: need a derived class to propelly compare url's
661 MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
662 setMediaSource(media);
666 MediaCurl::checkAttachPoint(const Pathname &apoint) const
668 return MediaHandler::checkAttachPoint( apoint, true, true);
671 ///////////////////////////////////////////////////////////////////
674 // METHOD NAME : MediaCurl::disconnectFrom
675 // METHOD TYPE : PMError
677 void MediaCurl::disconnectFrom()
681 curl_easy_cleanup( _curl );
686 ///////////////////////////////////////////////////////////////////
689 // METHOD NAME : MediaCurl::releaseFrom
690 // METHOD TYPE : PMError
692 // DESCRIPTION : Asserted that media is attached.
694 void MediaCurl::releaseFrom( bool eject )
700 ///////////////////////////////////////////////////////////////////
702 // METHOD NAME : MediaCurl::getFile
703 // METHOD TYPE : PMError
706 void MediaCurl::getFile( const Pathname & filename ) const
708 // Use absolute file name to prevent access of files outside of the
709 // hierarchy below the attach point.
710 getFileCopy(filename, localPath(filename).absolutename());
714 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
716 callback::SendReport<DownloadProgressReport> report;
721 doGetFileCopy(filename, target, report);
723 catch (MediaException & excpt_r)
725 // FIXME: this will not match the first URL
726 // FIXME: error number fix
727 report->finish(url, zypp::media::DownloadProgressReport::NOT_FOUND, excpt_r.msg());
728 ZYPP_RETHROW(excpt_r);
730 report->finish(url, zypp::media::DownloadProgressReport::NO_ERROR, "");
733 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
735 DBG << filename.asString() << endl;
738 ZYPP_THROW(MediaBadUrlException(_url));
740 if(_url.getHost().empty())
741 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
743 string path = _url.getPathName();
744 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
745 filename.absolute() ) {
746 // If url has a path with trailing slash, remove the leading slash from
747 // the absolute file name
748 path += filename.asString().substr( 1, filename.asString().size() - 1 );
749 } else if ( filename.relative() ) {
750 // Add trailing slash to path, if not already there
751 if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
752 // Remove "./" from begin of relative file name
753 path += filename.asString().substr( 2, filename.asString().size() - 2 );
755 path += filename.asString();
759 url.setPathName( path );
761 DBG << "URL: " << url.asString() << endl;
762 // Use URL without options and without username and passwd
763 // (some proxies dislike them in the URL).
764 // Curl seems to need the just scheme, hostname and a path;
765 // the rest was already passed as curl options (in attachTo).
768 // Use asString + url::ViewOptions instead?
769 curlUrl.setUsername( "" );
770 curlUrl.setPassword( "" );
771 curlUrl.setPathParams( "" );
772 curlUrl.setQueryString( "" );
773 curlUrl.setFragment( "" );
776 // See also Bug #154197 and ftp url definition in RFC 1738:
777 // The url "ftp://user@host/foo/bar/file" contains a path,
778 // that is relative to the user's home.
779 // The url "ftp://user@host//foo/bar/file" (or also with
780 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
781 // contains an absolute path.
783 string urlBuffer( curlUrl.asString());
784 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
787 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
790 // set no data, because we only want to check if the file exists
791 //ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 );
793 // ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
796 // instead of returning no data with NOBODY, we return
797 // little data, that works with broken servers, and
798 // works for ftp as well, because retrieving only headers
799 // ftp will return always OK code ?
800 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
802 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
805 FILE *file = ::fopen( "/dev/null", "w" );
808 ERR << "fopen failed for /dev/null" << endl;
809 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
811 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
813 ZYPP_THROW(MediaWriteException("/dev/null"));
816 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
819 std::string err( _curlError);
820 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
822 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
824 ZYPP_THROW(MediaCurlSetOptException(url, err));
826 // Set callback and perform.
827 //ProgressData progressData(_xfer_timeout, url, &report);
828 //report->start(url, dest);
829 //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
830 // WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
833 CURLcode ok = curl_easy_perform( _curl );
834 MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
836 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
838 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
841 return ( ok == CURLE_OK );
842 //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
843 // WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
847 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report) const
849 DBG << filename.asString() << endl;
852 ZYPP_THROW(MediaBadUrlException(_url));
854 if(_url.getHost().empty())
855 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
857 string path = _url.getPathName();
858 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
859 filename.absolute() ) {
860 // If url has a path with trailing slash, remove the leading slash from
861 // the absolute file name
862 path += filename.asString().substr( 1, filename.asString().size() - 1 );
863 } else if ( filename.relative() ) {
864 // Add trailing slash to path, if not already there
865 if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
866 // Remove "./" from begin of relative file name
867 path += filename.asString().substr( 2, filename.asString().size() - 2 );
869 path += filename.asString();
873 url.setPathName( path );
875 Pathname dest = target.absolutename();
876 if( assert_dir( dest.dirname() ) )
878 DBG << "assert_dir " << dest.dirname() << " failed" << endl;
879 ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
882 DBG << "URL: " << url.asString() << endl;
883 // Use URL without options and without username and passwd
884 // (some proxies dislike them in the URL).
885 // Curl seems to need the just scheme, hostname and a path;
886 // the rest was already passed as curl options (in attachTo).
889 // Use asString + url::ViewOptions instead?
890 curlUrl.setUsername( "" );
891 curlUrl.setPassword( "" );
892 curlUrl.setPathParams( "" );
893 curlUrl.setQueryString( "" );
894 curlUrl.setFragment( "" );
897 // See also Bug #154197 and ftp url definition in RFC 1738:
898 // The url "ftp://user@host/foo/bar/file" contains a path,
899 // that is relative to the user's home.
900 // The url "ftp://user@host//foo/bar/file" (or also with
901 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
902 // contains an absolute path.
904 string urlBuffer( curlUrl.asString());
905 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
908 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
911 string destNew = target.asString() + ".new.zypp.XXXXXX";
912 char *buf = ::strdup( destNew.c_str());
915 ERR << "out of memory for temp file name" << endl;
916 ZYPP_THROW(MediaSystemException(
917 url, "out of memory for temp file name"
921 int tmp_fd = ::mkstemp( buf );
925 ERR << "mkstemp failed for file '" << destNew << "'" << endl;
926 ZYPP_THROW(MediaWriteException(destNew));
931 FILE *file = ::fdopen( tmp_fd, "w" );
934 filesystem::unlink( destNew );
935 ERR << "fopen failed for file '" << destNew << "'" << endl;
936 ZYPP_THROW(MediaWriteException(destNew));
939 DBG << "dest: " << dest << endl;
940 DBG << "temp: " << destNew << endl;
942 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
945 filesystem::unlink( destNew );
946 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
949 // Set callback and perform.
950 ProgressData progressData(_xfer_timeout, url, &report);
951 report->start(url, dest);
952 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
953 WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
956 ret = curl_easy_perform( _curl );
958 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
959 WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
964 filesystem::unlink( destNew );
965 ERR << "curl error: " << ret << ": " << _curlError << endl;
968 bool err_file_not_found = false;
970 case CURLE_UNSUPPORTED_PROTOCOL:
971 case CURLE_URL_MALFORMAT:
972 case CURLE_URL_MALFORMAT_USER:
974 case CURLE_HTTP_RETURNED_ERROR:
976 long httpReturnCode = 0;
977 CURLcode infoRet = curl_easy_getinfo( _curl,
978 CURLINFO_RESPONSE_CODE,
980 if ( infoRet == CURLE_OK ) {
981 string msg = "HTTP response: " +
982 str::numstring( httpReturnCode );
983 if ( httpReturnCode == 401 )
985 err = " Login failed";
988 if ( httpReturnCode == 404)
990 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
994 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
995 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
999 string msg = "Unable to retrieve HTTP response:";
1001 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1002 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1006 case CURLE_FTP_COULDNT_RETR_FILE:
1007 case CURLE_FTP_ACCESS_DENIED:
1008 err = "File not found";
1009 err_file_not_found = true;
1011 case CURLE_BAD_PASSWORD_ENTERED:
1012 case CURLE_FTP_USER_PASSWORD_INCORRECT:
1013 err = "Login failed";
1015 case CURLE_COULDNT_RESOLVE_PROXY:
1016 case CURLE_COULDNT_RESOLVE_HOST:
1017 case CURLE_COULDNT_CONNECT:
1018 case CURLE_FTP_CANT_GET_HOST:
1019 err = "Connection failed";
1021 case CURLE_WRITE_ERROR:
1022 err = "Write error";
1024 case CURLE_ABORTED_BY_CALLBACK:
1025 if( progressData.reached)
1027 err = "Timeout reached";
1034 case CURLE_SSL_PEER_CERTIFICATE:
1036 err = "Unrecognized error";
1039 if( err_file_not_found)
1041 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1045 ZYPP_THROW(MediaCurlException(url, err, _curlError));
1048 catch (const MediaException & excpt_r)
1050 ZYPP_RETHROW(excpt_r);
1053 #if DETECT_DIR_INDEX
1055 if(curlUrl.getScheme() == "http" ||
1056 curlUrl.getScheme() == "https")
1059 // try to check the effective url and set the not_a_file flag
1060 // if the url path ends with a "/", what usually means, that
1061 // we've received a directory index (index.html content).
1063 // Note: This may be dangerous and break file retrieving in
1064 // case of some server redirections ... ?
1066 bool not_a_file = false;
1068 CURLcode ret = curl_easy_getinfo( _curl,
1069 CURLINFO_EFFECTIVE_URL,
1071 if ( ret == CURLE_OK && ptr != NULL)
1076 std::string path( eurl.getPathName());
1077 if( !path.empty() && path != "/" && *path.rbegin() == '/')
1079 DBG << "Effective url ("
1081 << ") seems to provide the index of a directory"
1093 filesystem::unlink( destNew );
1094 ZYPP_THROW(MediaNotAFileException(_url, filename));
1097 #endif // DETECT_DIR_INDEX
1100 // getumask() would be fine, but does not exist
1101 // [ the linker can't find it in glibc :-( ].
1102 mask = ::umask(0022); ::umask(mask);
1103 if ( ::fchmod( ::fileno(file), 0644 & ~mask))
1105 ERR << "Failed to chmod file " << destNew << endl;
1109 if ( rename( destNew, dest ) != 0 ) {
1110 ERR << "Rename failed" << endl;
1111 ZYPP_THROW(MediaWriteException(dest));
1116 ///////////////////////////////////////////////////////////////////
1119 // METHOD NAME : MediaCurl::getDir
1120 // METHOD TYPE : PMError
1122 // DESCRIPTION : Asserted that media is attached
1124 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
1126 filesystem::DirContent content;
1127 getDirInfo( content, dirname, /*dots*/false );
1129 for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
1130 Pathname filename = dirname + it->name;
1133 switch ( it->type ) {
1134 case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
1135 case filesystem::FT_FILE:
1136 getFile( filename );
1138 case filesystem::FT_DIR: // newer directory.yast contain at least directory info
1140 getDir( filename, recurse_r );
1142 res = assert_dir( localPath( filename ) );
1144 WAR << "Ignore error (" << res << ") on creating local directory '" << localPath( filename ) << "'" << endl;
1149 // don't provide devices, sockets, etc.
1155 ///////////////////////////////////////////////////////////////////
1158 // METHOD NAME : MediaCurl::getDirInfo
1159 // METHOD TYPE : PMError
1161 // DESCRIPTION : Asserted that media is attached and retlist is empty.
1163 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
1164 const Pathname & dirname, bool dots ) const
1166 getDirectoryYast( retlist, dirname, dots );
1169 ///////////////////////////////////////////////////////////////////
1172 // METHOD NAME : MediaCurl::getDirInfo
1173 // METHOD TYPE : PMError
1175 // DESCRIPTION : Asserted that media is attached and retlist is empty.
1177 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
1178 const Pathname & dirname, bool dots ) const
1180 getDirectoryYast( retlist, dirname, dots );
1183 ///////////////////////////////////////////////////////////////////
1186 // METHOD NAME : MediaCurl::progressCallback
1187 // METHOD TYPE : int
1189 // DESCRIPTION : Progress callback triggered from MediaCurl::getFile
1191 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow,
1192 double ultotal, double ulnow )
1194 ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
1197 // send progress report first, abort transfer if requested
1200 if (! (*(pdata->report))->progress(int( dlnow * 100 / dltotal ), pdata->url))
1202 return 1; // abort transfer
1206 // check if we there is a timeout set
1207 if( pdata->timeout > 0)
1209 time_t now = time(NULL);
1212 bool progress = false;
1214 // reset time of last change in case initial time()
1215 // failed or the time was adjusted (goes backward)
1216 if( pdata->ltime <= 0 || pdata->ltime > now)
1219 // update download data if changed, mark progress
1220 if( dlnow != pdata->dload)
1223 pdata->dload = dlnow;
1226 // update upload data if changed, mark progress
1227 if( ulnow != pdata->uload)
1230 pdata->uload = ulnow;
1234 if( !progress && (now >= (pdata->ltime + pdata->timeout)))
1236 pdata->reached = true;
1237 return 1; // aborts transfer
1246 } // namespace media