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"
20 #include "zypp/base/Gettext.h"
22 #include "zypp/media/MediaCurl.h"
23 #include "zypp/media/proxyinfo/ProxyInfos.h"
24 #include "zypp/media/ProxyInfo.h"
25 #include "zypp/media/MediaUserAuth.h"
26 #include "zypp/media/CurlConfig.h"
27 #include "zypp/thread/Once.h"
29 #include <sys/types.h>
31 #include <sys/mount.h>
35 #include <boost/format.hpp>
37 #define DETECT_DIR_INDEX 0
38 #define CONNECT_TIMEOUT 60
39 #define TRANSFER_TIMEOUT 60 * 3
40 #define TRANSFER_TIMEOUT_MAX 60 * 60
44 using namespace zypp::base;
48 zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
49 zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
51 extern "C" void _do_free_once()
53 curl_global_cleanup();
56 extern "C" void globalFreeOnce()
58 zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
61 extern "C" void _do_init_once()
63 CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
66 WAR << "curl global init failed" << endl;
70 // register at exit handler ?
71 // this may cause trouble, because we can protect it
72 // against ourself only.
73 // if the app sets an atexit handler as well, it will
74 // cause a double free while the second of them runs.
76 //std::atexit( globalFreeOnce);
79 inline void globalInitOnce()
81 zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
84 int log_curl(CURL *curl, curl_infotype info,
85 char *ptr, size_t len, void *max_lvl)
91 case CURLINFO_TEXT: lvl = 1; pfx = "*"; break;
92 case CURLINFO_HEADER_IN: lvl = 2; pfx = "<"; break;
93 case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
96 if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
98 std::string msg(ptr, len);
99 std::list<std::string> lines;
100 std::list<std::string>::const_iterator line;
101 zypp::str::split(msg, std::back_inserter(lines), "\r\n");
102 for(line = lines.begin(); line != lines.end(); ++line)
104 DBG << pfx << " " << *line << endl;
117 ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
118 callback::SendReport<DownloadProgressReport> *_report=NULL)
129 callback::SendReport<DownloadProgressReport> *report;
137 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
138 std::string MediaCurl::_agent = "Novell ZYPP Installer";
140 ///////////////////////////////////////////////////////////////////
142 static inline void escape( string & str_r,
143 const char char_r, const string & escaped_r ) {
144 for ( string::size_type pos = str_r.find( char_r );
145 pos != string::npos; pos = str_r.find( char_r, pos ) ) {
146 str_r.replace( pos, 1, escaped_r );
150 static inline string escapedPath( string path_r ) {
151 escape( path_r, ' ', "%20" );
155 static inline string unEscape( string text_r ) {
156 char * tmp = curl_unescape( text_r.c_str(), 0 );
162 ///////////////////////////////////////////////////////////////////
164 // CLASS NAME : MediaCurl
166 ///////////////////////////////////////////////////////////////////
168 MediaCurl::MediaCurl( const Url & url_r,
169 const Pathname & attach_point_hint_r )
170 : MediaHandler( url_r, attach_point_hint_r,
171 "/", // urlpath at attachpoint
172 true ), // does_download
175 _curlError[0] = '\0';
178 MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
182 if( !attachPoint().empty())
184 PathInfo ainfo(attachPoint());
185 Pathname apath(attachPoint() + "XXXXXX");
186 char *atemp = ::strdup( apath.asString().c_str());
188 if( !ainfo.isDir() || !ainfo.userMayRWX() ||
189 atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
191 WAR << "attach point " << ainfo.path()
192 << " is not useable for " << url_r.getScheme() << endl;
193 setAttachPoint("", true);
195 else if( atest != NULL)
203 void MediaCurl::setCookieFile( const Pathname &fileName )
205 _cookieFile = fileName;
208 ///////////////////////////////////////////////////////////////////
211 // METHOD NAME : MediaCurl::attachTo
212 // METHOD TYPE : PMError
214 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
216 void MediaCurl::attachTo (bool next)
219 ZYPP_THROW(MediaNotSupportedException(_url));
221 if ( !_url.isValid() )
222 ZYPP_THROW(MediaBadUrlException(_url));
225 CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
227 curl_version_info_data *curl_info = NULL;
228 curl_info = curl_version_info(CURLVERSION_NOW);
229 // curl_info does not need any free (is static)
230 if (curl_info->protocols)
232 const char * const *proto;
233 std::string scheme( _url.getScheme());
235 for(proto=curl_info->protocols; !found && *proto; ++proto)
237 if( scheme == std::string((const char *)*proto))
242 std::string msg("Unsupported protocol '");
245 ZYPP_THROW(MediaBadUrlException(_url, msg));
249 if( !isUseableAttachPoint(attachPoint()))
251 std::string mountpoint = createAttachPoint().asString();
253 if( mountpoint.empty())
254 ZYPP_THROW( MediaBadAttachPointException(url()));
256 setAttachPoint( mountpoint, true);
259 disconnectFrom(); // clean _curl if needed
260 _curl = curl_easy_init();
262 ZYPP_THROW(MediaCurlInitException(_url));
266 char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
267 _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
270 curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1);
271 curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
272 curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
276 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
279 ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
282 ret = curl_easy_setopt( _curl, CURLOPT_FAILONERROR, true );
285 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
288 ret = curl_easy_setopt( _curl, CURLOPT_NOSIGNAL, 1 );
291 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
298 _xfer_timeout = TRANSFER_TIMEOUT;
300 std::string param(_url.getQueryParam("timeout"));
303 long num = str::strtonum<long>( param);
304 if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
312 ret = curl_easy_setopt( _curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
315 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
318 if ( _url.getScheme() == "http" ) {
319 // follow any Location: header that the server sends as part of
320 // an HTTP header (#113275)
321 ret = curl_easy_setopt ( _curl, CURLOPT_FOLLOWLOCATION, true );
324 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
326 ret = curl_easy_setopt ( _curl, CURLOPT_MAXREDIRS, 3L );
329 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
331 ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
334 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
338 if ( _url.getScheme() == "https" )
340 bool verify_peer = false;
341 bool verify_host = false;
343 std::string verify( _url.getQueryParam("ssl_verify"));
344 if( verify.empty() ||
358 std::vector<std::string> flags;
359 std::vector<std::string>::const_iterator flag;
360 str::split( verify, std::back_inserter(flags), ",");
361 for(flag = flags.begin(); flag != flags.end(); ++flag)
375 ZYPP_THROW(MediaBadUrlException(_url, "Unknown ssl_verify flag"));
380 _ca_path = Pathname(_url.getQueryParam("ssl_capath")).asString();
381 if( _ca_path.empty())
383 _ca_path = "/etc/ssl/certs/";
386 if( !PathInfo(_ca_path).isDir() || !Pathname(_ca_path).absolute())
389 ZYPP_THROW(MediaBadUrlException(_url, "Invalid ssl_capath path"));
392 if( verify_peer || verify_host)
394 ret = curl_easy_setopt( _curl, CURLOPT_CAPATH, _ca_path.c_str());
397 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
401 ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L);
404 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
406 ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L);
409 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
412 ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
415 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
420 /*---------------------------------------------------------------*
421 CURLOPT_USERPWD: [user name]:[password]
423 Url::username/password -> CURLOPT_USERPWD
424 If not provided, anonymous FTP identification
425 *---------------------------------------------------------------*/
427 if ( _url.getUsername().empty() ) {
428 if ( _url.getScheme() == "ftp" ) {
429 string id = "yast2@";
431 DBG << "Anonymous FTP identification: '" << id << "'" << endl;
432 _userpwd = "anonymous:" + id;
435 _userpwd = _url.getUsername();
436 if ( _url.getPassword().size() ) {
437 _userpwd += ":" + _url.getPassword();
441 if ( _userpwd.size() ) {
442 _userpwd = unEscape( _userpwd );
443 ret = curl_easy_setopt( _curl, CURLOPT_USERPWD, _userpwd.c_str() );
446 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
449 // HTTP authentication type
450 if(_url.getScheme() == "http" || _url.getScheme() == "https")
452 string use_auth = _url.getQueryParam("auth");
453 if( use_auth.empty())
454 use_auth = "digest,basic";
458 long auth = CurlAuthData::auth_type_str2long(use_auth);
459 if( auth != CURLAUTH_NONE)
461 DBG << "Enabling HTTP authentication methods: " << use_auth
462 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
464 ret = curl_easy_setopt( _curl, CURLOPT_HTTPAUTH, auth);
467 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
471 catch (MediaException & ex_r)
473 string auth_hint = getAuthHint();
475 DBG << "Rethrowing as MediaUnauthorizedException. auth hint: '"
476 << auth_hint << "'" << endl;
478 ZYPP_THROW(MediaUnauthorizedException(
479 _url, ex_r.msg(), _curlError, auth_hint
485 /*---------------------------------------------------------------*
486 CURLOPT_PROXY: host[:port]
488 Url::option(proxy and proxyport) -> CURLOPT_PROXY
489 If not provided, /etc/sysconfig/proxy is evaluated
490 *---------------------------------------------------------------*/
492 _proxy = _url.getQueryParam( "proxy" );
494 if ( ! _proxy.empty() ) {
495 string proxyport( _url.getQueryParam( "proxyport" ) );
496 if ( ! proxyport.empty() ) {
497 _proxy += ":" + proxyport;
501 ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
503 if ( proxy_info.enabled())
505 bool useproxy = true;
507 std::list<std::string> nope = proxy_info.noProxy();
508 for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
509 it != proxy_info.noProxyEnd();
512 std::string host( str::toLower(_url.getHost()));
513 std::string temp( str::toLower(*it));
515 // no proxy if it points to a suffix
516 // preceeded by a '.', that maches
517 // the trailing portion of the host.
518 if( temp.size() > 1 && temp.at(0) == '.')
520 if(host.size() > temp.size() &&
521 host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
523 DBG << "NO_PROXY: '" << *it << "' matches host '"
524 << host << "'" << endl;
530 // no proxy if we have an exact match
533 DBG << "NO_PROXY: '" << *it << "' matches host '"
534 << host << "'" << endl;
541 _proxy = proxy_info.proxy(_url.getScheme());
547 DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
549 if ( ! _proxy.empty() ) {
551 ret = curl_easy_setopt( _curl, CURLOPT_PROXY, _proxy.c_str() );
554 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
557 /*---------------------------------------------------------------*
558 CURLOPT_PROXYUSERPWD: [user name]:[password]
560 Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
561 If not provided, $HOME/.curlrc is evaluated
562 *---------------------------------------------------------------*/
564 _proxyuserpwd = _url.getQueryParam( "proxyuser" );
566 if ( ! _proxyuserpwd.empty() ) {
567 string proxypassword( _url.getQueryParam( "proxypassword" ) );
568 if ( ! proxypassword.empty() ) {
569 _proxyuserpwd += ":" + proxypassword;
572 if (curlconf.proxyuserpwd.empty())
573 DBG << "~/.curlrc does not contain the proxy-user option" << endl;
576 _proxyuserpwd = curlconf.proxyuserpwd;
577 DBG << "using proxy-user from ~/.curlrc" << endl;
581 _proxyuserpwd = unEscape( _proxyuserpwd );
582 ret = curl_easy_setopt( _curl, CURLOPT_PROXYUSERPWD, _proxyuserpwd.c_str() );
585 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
589 /*---------------------------------------------------------------*
590 *---------------------------------------------------------------*/
592 _currentCookieFile = _cookieFile.asString();
594 ret = curl_easy_setopt( _curl, CURLOPT_COOKIEFILE,
595 _currentCookieFile.c_str() );
598 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
601 ret = curl_easy_setopt( _curl, CURLOPT_COOKIEJAR,
602 _currentCookieFile.c_str() );
605 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
608 ret = curl_easy_setopt( _curl, CURLOPT_PROGRESSFUNCTION,
612 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
615 ret = curl_easy_setopt( _curl, CURLOPT_NOPROGRESS, false );
618 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
621 // FIXME: need a derived class to propelly compare url's
622 MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
623 setMediaSource(media);
627 MediaCurl::checkAttachPoint(const Pathname &apoint) const
629 return MediaHandler::checkAttachPoint( apoint, true, true);
632 ///////////////////////////////////////////////////////////////////
635 // METHOD NAME : MediaCurl::disconnectFrom
636 // METHOD TYPE : PMError
638 void MediaCurl::disconnectFrom()
642 curl_easy_cleanup( _curl );
647 ///////////////////////////////////////////////////////////////////
650 // METHOD NAME : MediaCurl::releaseFrom
651 // METHOD TYPE : PMError
653 // DESCRIPTION : Asserted that media is attached.
655 void MediaCurl::releaseFrom( bool eject )
661 ///////////////////////////////////////////////////////////////////
663 // METHOD NAME : MediaCurl::getFile
664 // METHOD TYPE : PMError
667 void MediaCurl::getFile( const Pathname & filename ) const
669 // Use absolute file name to prevent access of files outside of the
670 // hierarchy below the attach point.
671 getFileCopy(filename, localPath(filename).absolutename());
675 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
677 callback::SendReport<DownloadProgressReport> report;
682 CurlAuthData auth_data;
688 doGetFileCopy(filename, target, report);
691 // retry with proper authentication data
692 catch (MediaUnauthorizedException & ex_r)
694 callback::SendReport<AuthenticationReport> auth_report;
696 if (!_url.getUsername().empty() && !retry)
697 auth_data.setUserName(_url.getUsername());
700 if (retry || !_url.getUsername().empty())
701 prompt_msg = _("Invalid user name or password.");
703 prompt_msg = boost::str(boost::format(
704 _("Authentication required for '%s'")) % _url.asString());
706 // set available authentication types from the exception
707 auth_data.setAuthType(ex_r.hint());
709 if (auth_report->prompt(_url, prompt_msg, auth_data))
711 DBG << "callback answer: retry" << endl
712 << "CurlAuthData: " << auth_data << endl;
714 if (auth_data.valid()) {
715 _userpwd = auth_data.getUserPwd();
717 // set username and password
718 CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _userpwd.c_str());
719 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
722 ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, auth_data.authType());
723 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
730 DBG << "callback answer: cancel" << endl;
731 report->finish(url, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserString());
735 // unexpected exception
736 catch (MediaException & excpt_r)
738 // FIXME: this will not match the first URL
739 // FIXME: error number fix
740 report->finish(url, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserString());
741 ZYPP_RETHROW(excpt_r);
746 report->finish(url, zypp::media::DownloadProgressReport::NO_ERROR, "");
749 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
752 CurlAuthData auth_data;
758 return doGetDoesFileExist( filename );
760 // authentication problem, retry with proper authentication data
761 catch (MediaUnauthorizedException & ex_r)
763 callback::SendReport<AuthenticationReport> auth_report;
765 if (!_url.getUsername().empty() && !retry)
766 auth_data.setUserName(_url.getUsername());
769 if (retry || !_url.getUsername().empty())
770 prompt_msg = _("Invalid user name or password.");
772 prompt_msg = boost::str(boost::format(
773 _("Authentication required for '%s'")) % _url.asString());
775 // set available authentication types from the exception
776 auth_data.setAuthType(ex_r.hint());
778 if (auth_report->prompt(_url, prompt_msg, auth_data))
780 DBG << "callback answer: retry" << endl
781 << "CurlAuthData: " << auth_data << endl;
783 if (auth_data.valid()) {
784 _userpwd = auth_data.getUserPwd();
786 // set username and password
787 CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _userpwd.c_str());
788 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
791 ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, auth_data.authType());
792 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
799 DBG << "callback answer: cancel" << endl;
803 // unexpected exception
804 catch (MediaException & excpt_r)
806 ZYPP_RETHROW(excpt_r);
814 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
816 DBG << filename.asString() << endl;
819 ZYPP_THROW(MediaBadUrlException(_url));
821 if(_url.getHost().empty())
822 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
824 string path = _url.getPathName();
825 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
826 filename.absolute() ) {
827 // If url has a path with trailing slash, remove the leading slash from
828 // the absolute file name
829 path += filename.asString().substr( 1, filename.asString().size() - 1 );
830 } else if ( filename.relative() ) {
831 // Add trailing slash to path, if not already there
832 if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
833 // Remove "./" from begin of relative file name
834 path += filename.asString().substr( 2, filename.asString().size() - 2 );
836 path += filename.asString();
840 url.setPathName( path );
842 DBG << "URL: " << url.asString() << endl;
843 // Use URL without options and without username and passwd
844 // (some proxies dislike them in the URL).
845 // Curl seems to need the just scheme, hostname and a path;
846 // the rest was already passed as curl options (in attachTo).
849 // Use asString + url::ViewOptions instead?
850 curlUrl.setUsername( "" );
851 curlUrl.setPassword( "" );
852 curlUrl.setPathParams( "" );
853 curlUrl.setQueryString( "" );
854 curlUrl.setFragment( "" );
857 // See also Bug #154197 and ftp url definition in RFC 1738:
858 // The url "ftp://user@host/foo/bar/file" contains a path,
859 // that is relative to the user's home.
860 // The url "ftp://user@host//foo/bar/file" (or also with
861 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
862 // contains an absolute path.
864 string urlBuffer( curlUrl.asString());
865 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
868 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
871 // instead of returning no data with NOBODY, we return
872 // little data, that works with broken servers, and
873 // works for ftp as well, because retrieving only headers
874 // ftp will return always OK code ?
875 if ( _url.getScheme() == "http" || _url.getScheme() == "https" )
876 ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 );
878 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
881 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
882 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
883 /* yes, this is why we never got to get NOBODY working before,
884 because setting it changes this option too, and we also
886 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
888 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
889 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
893 FILE *file = ::fopen( "/dev/null", "w" );
896 ERR << "fopen failed for /dev/null" << endl;
897 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
898 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
899 /* yes, this is why we never got to get NOBODY working before,
900 because setting it changes this option too, and we also
902 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
904 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
906 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
908 ZYPP_THROW(MediaWriteException("/dev/null"));
911 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
914 std::string err( _curlError);
915 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
916 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
917 /* yes, this is why we never got to get NOBODY working before,
918 because setting it changes this option too, and we also
920 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
922 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
924 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
926 ZYPP_THROW(MediaCurlSetOptException(url, err));
928 // Set callback and perform.
929 //ProgressData progressData(_xfer_timeout, url, &report);
930 //report->start(url, dest);
931 //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
932 // WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
935 CURLcode ok = curl_easy_perform( _curl );
936 MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
938 // reset curl settings
939 if ( _url.getScheme() == "http" || _url.getScheme() == "https" )
941 ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
942 /* yes, this is why we never got to get NOBODY working before,
943 because setting it changes this option too, and we also
945 See: http://curl.haxx.se/mail/archive-2005-07/0073.html
947 ret = curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
950 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
954 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
964 bool err_file_not_found = false;
967 case CURLE_FTP_COULDNT_RETR_FILE:
968 case CURLE_FTP_ACCESS_DENIED:
969 err_file_not_found = true;
971 case CURLE_HTTP_RETURNED_ERROR:
973 long httpReturnCode = 0;
974 CURLcode infoRet = curl_easy_getinfo( _curl,
975 CURLINFO_RESPONSE_CODE,
977 if ( infoRet == CURLE_OK )
979 string msg = "HTTP response: " +
980 str::numstring( httpReturnCode );
981 if ( httpReturnCode == 401 )
983 std::string auth_hint = getAuthHint();
985 DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
986 DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
988 ZYPP_THROW(MediaUnauthorizedException(
989 url, "Login failed.", _curlError, auth_hint
993 if ( httpReturnCode == 404)
995 err_file_not_found = true;
1000 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1001 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1005 string msg = "Unable to retrieve HTTP response:";
1007 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1008 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1012 case CURLE_UNSUPPORTED_PROTOCOL:
1013 case CURLE_URL_MALFORMAT:
1014 case CURLE_URL_MALFORMAT_USER:
1015 case CURLE_BAD_PASSWORD_ENTERED:
1016 case CURLE_FTP_USER_PASSWORD_INCORRECT:
1017 case CURLE_COULDNT_RESOLVE_PROXY:
1018 case CURLE_COULDNT_RESOLVE_HOST:
1019 case CURLE_COULDNT_CONNECT:
1020 case CURLE_FTP_CANT_GET_HOST:
1021 case CURLE_WRITE_ERROR:
1022 case CURLE_ABORTED_BY_CALLBACK:
1023 case CURLE_SSL_PEER_CERTIFICATE:
1025 err = curl_easy_strerror(ok);
1027 err = "Unrecognized error";
1031 if( err_file_not_found)
1033 // file does not exists
1038 // there was an error
1039 ZYPP_THROW(MediaCurlException(url, string(), _curlError));
1042 catch (const MediaException & excpt_r)
1044 ZYPP_RETHROW(excpt_r);
1049 return ( ok == CURLE_OK );
1050 //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
1051 // WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1055 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report) const
1057 DBG << filename.asString() << endl;
1060 ZYPP_THROW(MediaBadUrlException(_url));
1062 if(_url.getHost().empty())
1063 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
1065 string path = _url.getPathName();
1066 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
1067 filename.absolute() ) {
1068 // If url has a path with trailing slash, remove the leading slash from
1069 // the absolute file name
1070 path += filename.asString().substr( 1, filename.asString().size() - 1 );
1071 } else if ( filename.relative() ) {
1072 // Add trailing slash to path, if not already there
1073 if (path.empty()) path = "/";
1074 else if (*path.rbegin() != '/' ) path += "/";
1075 // Remove "./" from begin of relative file name
1076 path += filename.asString().substr( 2, filename.asString().size() - 2 );
1078 path += filename.asString();
1082 url.setPathName( path );
1084 Pathname dest = target.absolutename();
1085 if( assert_dir( dest.dirname() ) )
1087 DBG << "assert_dir " << dest.dirname() << " failed" << endl;
1088 ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
1091 DBG << "URL: " << url.asString() << endl;
1092 // Use URL without options and without username and passwd
1093 // (some proxies dislike them in the URL).
1094 // Curl seems to need the just scheme, hostname and a path;
1095 // the rest was already passed as curl options (in attachTo).
1098 // Use asString + url::ViewOptions instead?
1099 curlUrl.setUsername( "" );
1100 curlUrl.setPassword( "" );
1101 curlUrl.setPathParams( "" );
1102 curlUrl.setQueryString( "" );
1103 curlUrl.setFragment( "" );
1106 // See also Bug #154197 and ftp url definition in RFC 1738:
1107 // The url "ftp://user@host/foo/bar/file" contains a path,
1108 // that is relative to the user's home.
1109 // The url "ftp://user@host//foo/bar/file" (or also with
1110 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1111 // contains an absolute path.
1113 string urlBuffer( curlUrl.asString());
1114 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1115 urlBuffer.c_str() );
1117 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1120 // set IFMODSINCE time condition (no download if not modified)
1121 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
1122 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, PathInfo(target).mtime());
1124 string destNew = target.asString() + ".new.zypp.XXXXXX";
1125 char *buf = ::strdup( destNew.c_str());
1128 ERR << "out of memory for temp file name" << endl;
1129 ZYPP_THROW(MediaSystemException(
1130 url, "out of memory for temp file name"
1134 int tmp_fd = ::mkstemp( buf );
1138 ERR << "mkstemp failed for file '" << destNew << "'" << endl;
1139 ZYPP_THROW(MediaWriteException(destNew));
1144 FILE *file = ::fdopen( tmp_fd, "w" );
1147 filesystem::unlink( destNew );
1148 ERR << "fopen failed for file '" << destNew << "'" << endl;
1149 ZYPP_THROW(MediaWriteException(destNew));
1152 DBG << "dest: " << dest << endl;
1153 DBG << "temp: " << destNew << endl;
1155 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1158 filesystem::unlink( destNew );
1159 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1162 // Set callback and perform.
1163 ProgressData progressData(_xfer_timeout, url, &report);
1164 report->start(url, dest);
1165 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
1166 WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1169 ret = curl_easy_perform( _curl );
1171 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
1172 WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1176 ERR << "curl error: " << ret << ": " << _curlError
1177 << ", temp file size " << PathInfo(destNew).size()
1178 << " byte." << endl;
1181 filesystem::unlink( destNew );
1185 bool err_file_not_found = false;
1187 case CURLE_UNSUPPORTED_PROTOCOL:
1188 case CURLE_URL_MALFORMAT:
1189 case CURLE_URL_MALFORMAT_USER:
1191 case CURLE_HTTP_RETURNED_ERROR:
1193 long httpReturnCode = 0;
1194 CURLcode infoRet = curl_easy_getinfo( _curl,
1195 CURLINFO_RESPONSE_CODE,
1197 if ( infoRet == CURLE_OK ) {
1198 string msg = "HTTP response: " +
1199 str::numstring( httpReturnCode );
1200 if ( httpReturnCode == 401 )
1202 std::string auth_hint = getAuthHint();
1204 DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
1205 DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
1207 ZYPP_THROW(MediaUnauthorizedException(
1208 url, "Login failed.", _curlError, auth_hint
1212 if ( httpReturnCode == 404)
1214 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1218 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1219 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1223 string msg = "Unable to retrieve HTTP response:";
1225 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1226 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1230 case CURLE_FTP_COULDNT_RETR_FILE:
1231 case CURLE_FTP_ACCESS_DENIED:
1232 err = "File not found";
1233 err_file_not_found = true;
1235 case CURLE_BAD_PASSWORD_ENTERED:
1236 case CURLE_FTP_USER_PASSWORD_INCORRECT:
1237 err = "Login failed";
1239 case CURLE_COULDNT_RESOLVE_PROXY:
1240 case CURLE_COULDNT_RESOLVE_HOST:
1241 case CURLE_COULDNT_CONNECT:
1242 case CURLE_FTP_CANT_GET_HOST:
1243 err = "Connection failed";
1245 case CURLE_WRITE_ERROR:
1246 err = "Write error";
1248 case CURLE_ABORTED_BY_CALLBACK:
1249 if( progressData.reached)
1251 err = "Timeout reached";
1258 case CURLE_SSL_PEER_CERTIFICATE:
1260 err = "Unrecognized error";
1263 if( err_file_not_found)
1265 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1269 ZYPP_THROW(MediaCurlException(url, err, _curlError));
1272 catch (const MediaException & excpt_r)
1274 ZYPP_RETHROW(excpt_r);
1277 #if DETECT_DIR_INDEX
1279 if(curlUrl.getScheme() == "http" ||
1280 curlUrl.getScheme() == "https")
1283 // try to check the effective url and set the not_a_file flag
1284 // if the url path ends with a "/", what usually means, that
1285 // we've received a directory index (index.html content).
1287 // Note: This may be dangerous and break file retrieving in
1288 // case of some server redirections ... ?
1290 bool not_a_file = false;
1292 CURLcode ret = curl_easy_getinfo( _curl,
1293 CURLINFO_EFFECTIVE_URL,
1295 if ( ret == CURLE_OK && ptr != NULL)
1300 std::string path( eurl.getPathName());
1301 if( !path.empty() && path != "/" && *path.rbegin() == '/')
1303 DBG << "Effective url ("
1305 << ") seems to provide the index of a directory"
1317 filesystem::unlink( destNew );
1318 ZYPP_THROW(MediaNotAFileException(_url, filename));
1321 #endif // DETECT_DIR_INDEX
1323 long httpReturnCode = 0;
1324 CURLcode infoRet = curl_easy_getinfo(_curl,
1325 CURLINFO_RESPONSE_CODE,
1327 bool modified = true;
1328 if (infoRet == CURLE_OK)
1330 DBG << "HTTP response: " + str::numstring(httpReturnCode);
1331 if ( httpReturnCode == 304 ) // not modified
1333 DBG << " Not modified.";
1340 WAR << "Could not get the reponse code." << endl;
1343 if (modified || infoRet != CURLE_OK)
1346 if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) )
1348 ERR << "Failed to chmod file " << destNew << endl;
1352 // move the temp file into dest
1353 if ( rename( destNew, dest ) != 0 ) {
1354 ERR << "Rename failed" << endl;
1355 ZYPP_THROW(MediaWriteException(dest));
1360 // close and remove the temp file
1362 filesystem::unlink( destNew );
1365 DBG << "done: " << PathInfo(dest) << endl;
1369 ///////////////////////////////////////////////////////////////////
1372 // METHOD NAME : MediaCurl::getDir
1373 // METHOD TYPE : PMError
1375 // DESCRIPTION : Asserted that media is attached
1377 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
1379 filesystem::DirContent content;
1380 getDirInfo( content, dirname, /*dots*/false );
1382 for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
1383 Pathname filename = dirname + it->name;
1386 switch ( it->type ) {
1387 case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
1388 case filesystem::FT_FILE:
1389 getFile( filename );
1391 case filesystem::FT_DIR: // newer directory.yast contain at least directory info
1393 getDir( filename, recurse_r );
1395 res = assert_dir( localPath( filename ) );
1397 WAR << "Ignore error (" << res << ") on creating local directory '" << localPath( filename ) << "'" << endl;
1402 // don't provide devices, sockets, etc.
1408 ///////////////////////////////////////////////////////////////////
1411 // METHOD NAME : MediaCurl::getDirInfo
1412 // METHOD TYPE : PMError
1414 // DESCRIPTION : Asserted that media is attached and retlist is empty.
1416 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
1417 const Pathname & dirname, bool dots ) const
1419 getDirectoryYast( retlist, dirname, dots );
1422 ///////////////////////////////////////////////////////////////////
1425 // METHOD NAME : MediaCurl::getDirInfo
1426 // METHOD TYPE : PMError
1428 // DESCRIPTION : Asserted that media is attached and retlist is empty.
1430 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
1431 const Pathname & dirname, bool dots ) const
1433 getDirectoryYast( retlist, dirname, dots );
1436 ///////////////////////////////////////////////////////////////////
1439 // METHOD NAME : MediaCurl::progressCallback
1440 // METHOD TYPE : int
1442 // DESCRIPTION : Progress callback triggered from MediaCurl::getFile
1444 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow,
1445 double ultotal, double ulnow )
1447 ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
1450 // send progress report first, abort transfer if requested
1453 if (! (*(pdata->report))->progress(int( dlnow * 100 / dltotal ), pdata->url))
1455 return 1; // abort transfer
1459 // check if we there is a timeout set
1460 if( pdata->timeout > 0)
1462 time_t now = time(NULL);
1465 bool progress = false;
1467 // reset time of last change in case initial time()
1468 // failed or the time was adjusted (goes backward)
1469 if( pdata->ltime <= 0 || pdata->ltime > now)
1472 // update download data if changed, mark progress
1473 if( dlnow != pdata->dload)
1476 pdata->dload = dlnow;
1479 // update upload data if changed, mark progress
1480 if( ulnow != pdata->uload)
1483 pdata->uload = ulnow;
1487 if( !progress && (now >= (pdata->ltime + pdata->timeout)))
1489 pdata->reached = true;
1490 return 1; // aborts transfer
1498 string MediaCurl::getAuthHint() const
1500 long auth_info = CURLAUTH_NONE;
1503 curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
1505 if(infoRet == CURLE_OK)
1507 return CurlAuthData::auth_type_long2str(auth_info);
1513 } // namespace media