MediaCurl.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00013 #include <iostream>
00014 #include <list>
00015 
00016 #include "zypp/base/Logger.h"
00017 #include "zypp/ExternalProgram.h"
00018 #include "zypp/base/String.h"
00019 #include "zypp/base/Gettext.h"
00020 #include "zypp/base/Sysconfig.h"
00021 #include "zypp/base/Gettext.h"
00022 
00023 #include "zypp/media/MediaCurl.h"
00024 #include "zypp/media/proxyinfo/ProxyInfos.h"
00025 #include "zypp/media/ProxyInfo.h"
00026 #include "zypp/media/MediaUserAuth.h"
00027 #include "zypp/media/CredentialManager.h"
00028 #include "zypp/media/CurlConfig.h"
00029 #include "zypp/thread/Once.h"
00030 #include "zypp/Target.h"
00031 #include "zypp/ZYppFactory.h"
00032 #include "zypp/ZConfig.h"
00033 
00034 #include <cstdlib>
00035 #include <sys/types.h>
00036 #include <sys/stat.h>
00037 #include <sys/mount.h>
00038 #include <errno.h>
00039 #include <dirent.h>
00040 #include <unistd.h>
00041 #include <boost/format.hpp>
00042 
00043 #define  DETECT_DIR_INDEX       0
00044 #define  CONNECT_TIMEOUT        60
00045 #define  TRANSFER_TIMEOUT_MAX   60 * 60
00046 
00047 #define EXPLICITLY_NO_PROXY "_none_"
00048 
00049 #undef CURLVERSION_AT_LEAST
00050 #define CURLVERSION_AT_LEAST(M,N,O) LIBCURL_VERSION_NUM >= ((((M)<<8)+(N))<<8)+(O)
00051 
00052 using namespace std;
00053 using namespace zypp::base;
00054 
00055 namespace
00056 {
00057   zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
00058   zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
00059 
00060   extern "C" void _do_free_once()
00061   {
00062     curl_global_cleanup();
00063   }
00064 
00065   extern "C" void globalFreeOnce()
00066   {
00067     zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
00068   }
00069 
00070   extern "C" void _do_init_once()
00071   {
00072     CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
00073     if ( ret != 0 )
00074     {
00075       WAR << "curl global init failed" << endl;
00076     }
00077 
00078     //
00079     // register at exit handler ?
00080     // this may cause trouble, because we can protect it
00081     // against ourself only.
00082     // if the app sets an atexit handler as well, it will
00083     // cause a double free while the second of them runs.
00084     //
00085     //std::atexit( globalFreeOnce);
00086   }
00087 
00088   inline void globalInitOnce()
00089   {
00090     zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
00091   }
00092 
00093   int log_curl(CURL *curl, curl_infotype info,
00094                char *ptr, size_t len, void *max_lvl)
00095   {
00096     std::string pfx(" ");
00097     long        lvl = 0;
00098     switch( info)
00099     {
00100       case CURLINFO_TEXT:       lvl = 1; pfx = "*"; break;
00101       case CURLINFO_HEADER_IN:  lvl = 2; pfx = "<"; break;
00102       case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
00103       default:                                      break;
00104     }
00105     if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
00106     {
00107       std::string                            msg(ptr, len);
00108       std::list<std::string>                 lines;
00109       std::list<std::string>::const_iterator line;
00110       zypp::str::split(msg, std::back_inserter(lines), "\r\n");
00111       for(line = lines.begin(); line != lines.end(); ++line)
00112       {
00113         DBG << pfx << " " << *line << endl;
00114       }
00115     }
00116     return 0;
00117   }
00118 
00119   static size_t
00120   log_redirects_curl(
00121       void *ptr, size_t size, size_t nmemb, void *stream)
00122   {
00123     // INT << "got header: " << string((char *)ptr, ((char*)ptr) + size*nmemb) << endl;
00124 
00125     char * lstart = (char *)ptr, * lend = (char *)ptr;
00126     size_t pos = 0;
00127     size_t max = size * nmemb;
00128     while (pos + 1 < max)
00129     {
00130       // get line
00131       for (lstart = lend; *lend != '\n' && pos < max; ++lend, ++pos);
00132 
00133       // look for "Location"
00134       string line(lstart, lend);
00135       if (line.find("Location") != string::npos)
00136       {
00137         DBG << "redirecting to " << line << endl;
00138         return max;
00139       }
00140 
00141       // continue with the next line
00142       if (pos + 1 < max)
00143       {
00144         ++lend;
00145         ++pos;
00146       }
00147       else
00148         break;
00149     }
00150 
00151     return max;
00152   }
00153 }
00154 
00155 namespace zypp {
00156   namespace media {
00157 
00158   namespace {
00159     struct ProgressData
00160     {
00161       ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
00162                    callback::SendReport<DownloadProgressReport> *_report=NULL)
00163         : timeout(_timeout)
00164         , reached(false)
00165         , report(_report)
00166         , drate_period(-1)
00167         , dload_period(0)
00168         , secs(0)
00169         , drate_avg(-1)
00170         , ltime( time(NULL))
00171         , dload( 0)
00172         , uload( 0)
00173         , url(_url)
00174       {}
00175       long                                          timeout;
00176       bool                                          reached;
00177       callback::SendReport<DownloadProgressReport> *report;
00178       // download rate of the last period (cca 1 sec)
00179       double                                        drate_period;
00180       // bytes downloaded at the start of the last period
00181       double                                        dload_period;
00182       // seconds from the start of the download
00183       long                                          secs;
00184       // average download rate
00185       double                                        drate_avg;
00186       // last time the progress was reported
00187       time_t                                        ltime;
00188       // bytes downloaded at the moment the progress was last reported
00189       double                                        dload;
00190       // bytes uploaded at the moment the progress was last reported
00191       double                                        uload;
00192       zypp::Url                                     url;
00193     };
00194 
00196 
00197     inline void escape( string & str_r,
00198                         const char char_r, const string & escaped_r ) {
00199       for ( string::size_type pos = str_r.find( char_r );
00200             pos != string::npos; pos = str_r.find( char_r, pos ) ) {
00201               str_r.replace( pos, 1, escaped_r );
00202             }
00203     }
00204 
00205     inline string escapedPath( string path_r ) {
00206       escape( path_r, ' ', "%20" );
00207       return path_r;
00208     }
00209 
00210     inline string unEscape( string text_r ) {
00211       char * tmp = curl_unescape( text_r.c_str(), 0 );
00212       string ret( tmp );
00213       curl_free( tmp );
00214       return ret;
00215     }
00216 
00217   }
00218 
00223 void fillSettingsFromUrl( const Url &url, TransferSettings &s )
00224 {
00225     std::string param(url.getQueryParam("timeout"));
00226     if( !param.empty())
00227     {
00228       long num = str::strtonum<long>(param);
00229       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
00230           s.setTimeout(num);
00231     }
00232 
00233     if ( ! url.getUsername().empty() )
00234     {
00235         s.setUsername(url.getUsername());
00236         if ( url.getPassword().size() )
00237             s.setPassword(url.getPassword());
00238     }
00239     else
00240     {
00241         // if there is no username, set anonymous auth
00242         if ( url.getScheme() == "ftp" && s.username().empty() )
00243             s.setAnonymousAuth();
00244     }
00245 
00246     if ( url.getScheme() == "https" )
00247     {
00248         s.setVerifyPeerEnabled(false);
00249         s.setVerifyHostEnabled(false);
00250 
00251         std::string verify( url.getQueryParam("ssl_verify"));
00252         if( verify.empty() ||
00253             verify == "yes")
00254         {
00255             s.setVerifyPeerEnabled(true);
00256             s.setVerifyHostEnabled(true);
00257         }
00258         else if( verify == "no")
00259         {
00260             s.setVerifyPeerEnabled(false);
00261             s.setVerifyHostEnabled(false);
00262         }
00263         else
00264         {
00265             std::vector<std::string>                 flags;
00266             std::vector<std::string>::const_iterator flag;
00267             str::split( verify, std::back_inserter(flags), ",");
00268             for(flag = flags.begin(); flag != flags.end(); ++flag)
00269             {
00270                 if( *flag == "host")
00271                     s.setVerifyHostEnabled(true);
00272                 else if( *flag == "peer")
00273                     s.setVerifyPeerEnabled(true);
00274                 else
00275                     ZYPP_THROW(MediaBadUrlException(url, "Unknown ssl_verify flag"));
00276             }
00277         }
00278     }
00279 
00280     Pathname ca_path = Pathname(url.getQueryParam("ssl_capath")).asString();
00281     if( ! ca_path.empty())
00282     {
00283         if( !PathInfo(ca_path).isDir() || !Pathname(ca_path).absolute())
00284             ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_capath path"));
00285         else
00286             s.setCertificateAuthoritiesPath(ca_path);
00287     }
00288 
00289     string proxy = url.getQueryParam( "proxy" );
00290     if ( ! proxy.empty() )
00291     {
00292         if ( proxy == EXPLICITLY_NO_PROXY ) {
00293             // Workaround TransferSettings shortcoming: With an
00294             // empty proxy string, code will continue to look for
00295             // valid proxy settings. So set proxy to some non-empty
00296             // string, to indicate it has been explicitly disabled.
00297             s.setProxy(EXPLICITLY_NO_PROXY);
00298             s.setProxyEnabled(false);
00299         }
00300         else {
00301             string proxyport( url.getQueryParam( "proxyport" ) );
00302             if ( ! proxyport.empty() ) {
00303                 proxy += ":" + proxyport;
00304             }
00305             s.setProxy(proxy);
00306             s.setProxyEnabled(true);
00307         }
00308     }
00309 
00310     // HTTP authentication type
00311     string use_auth = url.getQueryParam("auth");
00312     if (!use_auth.empty() && (url.getScheme() == "http" || url.getScheme() == "https"))
00313     {
00314         try
00315         {
00316             CurlAuthData::auth_type_str2long(use_auth); // check if we know it
00317         }
00318         catch (MediaException & ex_r)
00319         {
00320             DBG << "Rethrowing as MediaUnauthorizedException.";
00321             ZYPP_THROW(MediaUnauthorizedException(url, ex_r.msg(), "", ""));
00322         }
00323         s.setAuthType(use_auth);
00324     }
00325 
00326     // workarounds
00327     std::string head_requests( url.getQueryParam("head_requests"));
00328     if( !head_requests.empty() && head_requests == "no")
00329         s.setHeadRequestsAllowed(false);
00330 }
00331 
00336 void fillSettingsSystemProxy( const Url&url, TransferSettings &s )
00337 {
00338     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
00339     s.setProxyEnabled( proxy_info.useProxyFor( url ) );
00340     if ( s.proxyEnabled() )
00341       s.setProxy(proxy_info.proxy(url.getScheme()));
00342 }
00343 
00344 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
00345 
00350 static const char *const anonymousIdHeader()
00351 {
00352   // we need to add the release and identifier to the
00353   // agent string.
00354   // The target could be not initialized, and then this information
00355   // is guessed.
00356   static const std::string _value(
00357       str::trim( str::form(
00358           "X-ZYpp-AnonymousId: %s",
00359           Target::anonymousUniqueId( Pathname()/*guess root*/ ).c_str() ) )
00360   );
00361   return _value.c_str();
00362 }
00363 
00368 static const char *const distributionFlavorHeader()
00369 {
00370   // we need to add the release and identifier to the
00371   // agent string.
00372   // The target could be not initialized, and then this information
00373   // is guessed.
00374   static const std::string _value(
00375       str::trim( str::form(
00376           "X-ZYpp-DistributionFlavor: %s",
00377           Target::distributionFlavor( Pathname()/*guess root*/ ).c_str() ) )
00378   );
00379   return _value.c_str();
00380 }
00381 
00386 static const char *const agentString()
00387 {
00388   // we need to add the release and identifier to the
00389   // agent string.
00390   // The target could be not initialized, and then this information
00391   // is guessed.
00392   static const std::string _value(
00393     str::form(
00394        "ZYpp %s (curl %s) %s"
00395        , VERSION
00396        , curl_version_info(CURLVERSION_NOW)->version
00397        , Target::targetDistribution( Pathname()/*guess root*/ ).c_str()
00398     )
00399   );
00400   return _value.c_str();
00401 }
00402 
00403 // we use this define to unbloat code as this C setting option
00404 // and catching exception is done frequently.
00406 #define SET_OPTION(opt,val) do { \
00407     ret = curl_easy_setopt ( _curl, opt, val ); \
00408     if ( ret != 0) { \
00409       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); \
00410     } \
00411   } while ( false )
00412 
00413 #define SET_OPTION_OFFT(opt,val) SET_OPTION(opt,(curl_off_t)val)
00414 #define SET_OPTION_LONG(opt,val) SET_OPTION(opt,(long)val)
00415 #define SET_OPTION_VOID(opt,val) SET_OPTION(opt,(void*)val)
00416 
00417 MediaCurl::MediaCurl( const Url &      url_r,
00418                       const Pathname & attach_point_hint_r )
00419     : MediaHandler( url_r, attach_point_hint_r,
00420                     "/", // urlpath at attachpoint
00421                     true ), // does_download
00422       _curl( NULL ),
00423       _customHeaders(0L)
00424 {
00425   _curlError[0] = '\0';
00426   _curlDebug = 0L;
00427 
00428   MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
00429 
00430   globalInitOnce();
00431 
00432   if( !attachPoint().empty())
00433   {
00434     PathInfo ainfo(attachPoint());
00435     Pathname apath(attachPoint() + "XXXXXX");
00436     char    *atemp = ::strdup( apath.asString().c_str());
00437     char    *atest = NULL;
00438     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
00439          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
00440     {
00441       WAR << "attach point " << ainfo.path()
00442           << " is not useable for " << url_r.getScheme() << endl;
00443       setAttachPoint("", true);
00444     }
00445     else if( atest != NULL)
00446       ::rmdir(atest);
00447 
00448     if( atemp != NULL)
00449       ::free(atemp);
00450   }
00451 }
00452 
00453 TransferSettings & MediaCurl::settings()
00454 {
00455     return _settings;
00456 }
00457 
00458 
00459 void MediaCurl::setCookieFile( const Pathname &fileName )
00460 {
00461   _cookieFile = fileName;
00462 }
00463 
00465 
00466 void MediaCurl::checkProtocol(const Url &url) const
00467 {
00468   curl_version_info_data *curl_info = NULL;
00469   curl_info = curl_version_info(CURLVERSION_NOW);
00470   // curl_info does not need any free (is static)
00471   if (curl_info->protocols)
00472   {
00473     const char * const *proto;
00474     std::string        scheme( url.getScheme());
00475     bool               found = false;
00476     for(proto=curl_info->protocols; !found && *proto; ++proto)
00477     {
00478       if( scheme == std::string((const char *)*proto))
00479         found = true;
00480     }
00481     if( !found)
00482     {
00483       std::string msg("Unsupported protocol '");
00484       msg += scheme;
00485       msg += "'";
00486       ZYPP_THROW(MediaBadUrlException(_url, msg));
00487     }
00488   }
00489 }
00490 
00491 void MediaCurl::setupEasy()
00492 {
00493   {
00494     char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
00495     _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
00496     if( _curlDebug > 0)
00497     {
00498       curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1L);
00499       curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
00500       curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
00501     }
00502   }
00503 
00504   curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, log_redirects_curl);
00505   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
00506   if ( ret != 0 ) {
00507     ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
00508   }
00509 
00510   SET_OPTION(CURLOPT_FAILONERROR, 1L);
00511   SET_OPTION(CURLOPT_NOSIGNAL, 1L);
00512 
00513   // create non persistant settings
00514   // so that we don't add headers twice
00515   TransferSettings vol_settings(_settings);
00516 
00517   // add custom headers
00518   vol_settings.addHeader(anonymousIdHeader());
00519   vol_settings.addHeader(distributionFlavorHeader());
00520   vol_settings.addHeader("Pragma:");
00521 
00522   _settings.setTimeout(ZConfig::instance().download_transfer_timeout());
00523   _settings.setConnectTimeout(CONNECT_TIMEOUT);
00524 
00525   _settings.setUserAgentString(agentString());
00526 
00527   // fill some settings from url query parameters
00528   try
00529   {
00530       fillSettingsFromUrl(_url, _settings);
00531   }
00532   catch ( const MediaException &e )
00533   {
00534       disconnectFrom();
00535       ZYPP_RETHROW(e);
00536   }
00537   // if the proxy was not set (or explicitly unset) by url, then look...
00538   if ( _settings.proxy().empty() )
00539   {
00540       // ...at the system proxy settings
00541       fillSettingsSystemProxy(_url, _settings);
00542   }
00543 
00547   SET_OPTION(CURLOPT_CONNECTTIMEOUT, _settings.connectTimeout());
00548 
00549   // follow any Location: header that the server sends as part of
00550   // an HTTP header (#113275)
00551   SET_OPTION(CURLOPT_FOLLOWLOCATION, 1L);
00552   // 3 redirects seem to be too few in some cases (bnc #465532)
00553   SET_OPTION(CURLOPT_MAXREDIRS, 6L);
00554 
00555   if ( _url.getScheme() == "https" )
00556   {
00557 #if CURLVERSION_AT_LEAST(7,19,4)
00558     // restrict following of redirections from https to https only
00559     SET_OPTION( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS );
00560 #endif
00561 
00562     if( _settings.verifyPeerEnabled() ||
00563         _settings.verifyHostEnabled() )
00564     {
00565       SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str());
00566     }
00567 
00568     SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L);
00569     SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L);
00570   }
00571 
00572   SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
00573 
00574   /*---------------------------------------------------------------*
00575    CURLOPT_USERPWD: [user name]:[password]
00576 
00577    Url::username/password -> CURLOPT_USERPWD
00578    If not provided, anonymous FTP identification
00579    *---------------------------------------------------------------*/
00580 
00581   if ( _settings.userPassword().size() )
00582   {
00583     SET_OPTION(CURLOPT_USERPWD, _settings.userPassword().c_str());
00584     string use_auth = _settings.authType();
00585     if (use_auth.empty())
00586       use_auth = "digest,basic";        // our default
00587     long auth = CurlAuthData::auth_type_str2long(use_auth);
00588     if( auth != CURLAUTH_NONE)
00589     {
00590       DBG << "Enabling HTTP authentication methods: " << use_auth
00591           << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
00592       SET_OPTION(CURLOPT_HTTPAUTH, auth);
00593     }
00594   }
00595 
00596   if ( _settings.proxyEnabled() )
00597   {
00598     DBG << "Proxy: '" << _settings.proxy() << "'" << endl;
00599     if ( ! _settings.proxy().empty() )
00600     {
00601       SET_OPTION(CURLOPT_PROXY, _settings.proxy().c_str());
00602       SET_OPTION(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM );
00603       /*---------------------------------------------------------------*
00604         CURLOPT_PROXYUSERPWD: [user name]:[password]
00605 
00606         Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
00607         If not provided, $HOME/.curlrc is evaluated
00608         *---------------------------------------------------------------*/
00609 
00610       string proxyuserpwd = _settings.proxyUserPassword();
00611 
00612       if ( proxyuserpwd.empty() )
00613       {
00614         CurlConfig curlconf;
00615         CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
00616         if (curlconf.proxyuserpwd.empty())
00617           DBG << "~/.curlrc does not contain the proxy-user option" << endl;
00618         else
00619         {
00620           proxyuserpwd = curlconf.proxyuserpwd;
00621           DBG << "using proxy-user from ~/.curlrc" << endl;
00622         }
00623       }
00624 
00625       proxyuserpwd = unEscape( proxyuserpwd );
00626       if ( ! proxyuserpwd.empty() )
00627         SET_OPTION(CURLOPT_PROXYUSERPWD, proxyuserpwd.c_str());
00628     }
00629   }
00630 #if CURLVERSION_AT_LEAST(7,19,4)
00631   else if ( _settings.proxy() == EXPLICITLY_NO_PROXY )
00632   {
00633     // Explicitly disabled in URL (see fillSettingsFromUrl()).
00634     // This should also prevent libcurl from looking into the environment.
00635     DBG << "Proxy: explicitly NOPROXY" << endl;
00636     SET_OPTION(CURLOPT_NOPROXY, "*");
00637   }
00638 #endif
00639   else
00640   {
00641     // libcurl may look into the enviroanment
00642     DBG << "Proxy: not explicitly set" << endl;
00643   }
00644 
00646   if ( _settings.minDownloadSpeed() != 0 )
00647   {
00648       SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed());
00649       // default to 10 seconds at low speed
00650       SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10L);
00651   }
00652 
00653 #if CURLVERSION_AT_LEAST(7,15,5)
00654   if ( _settings.maxDownloadSpeed() != 0 )
00655       SET_OPTION_OFFT(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed());
00656 #endif
00657 
00658   /*---------------------------------------------------------------*
00659    *---------------------------------------------------------------*/
00660 
00661   _currentCookieFile = _cookieFile.asString();
00662   if ( str::strToBool( _url.getQueryParam( "cookies" ), true ) )
00663     SET_OPTION(CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
00664   else
00665     MIL << "No cookies requested" << endl;
00666   SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
00667   SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback );
00668   SET_OPTION(CURLOPT_NOPROGRESS, 0L);
00669 
00670 #if CURLVERSION_AT_LEAST(7,18,0)
00671   // bnc #306272
00672     SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1L );
00673 #endif
00674   // append settings custom headers to curl
00675   for ( TransferSettings::Headers::const_iterator it = vol_settings.headersBegin();
00676         it != vol_settings.headersEnd();
00677         ++it )
00678   {
00679       MIL << "HEADER " << *it << std::endl;
00680 
00681       _customHeaders = curl_slist_append(_customHeaders, it->c_str());
00682       if ( !_customHeaders )
00683           ZYPP_THROW(MediaCurlInitException(_url));
00684   }
00685 
00686   SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders);
00687 }
00688 
00690 
00691 
00692 void MediaCurl::attachTo (bool next)
00693 {
00694   if ( next )
00695     ZYPP_THROW(MediaNotSupportedException(_url));
00696 
00697   if ( !_url.isValid() )
00698     ZYPP_THROW(MediaBadUrlException(_url));
00699 
00700   checkProtocol(_url);
00701   if( !isUseableAttachPoint(attachPoint()))
00702   {
00703     std::string mountpoint = createAttachPoint().asString();
00704 
00705     if( mountpoint.empty())
00706       ZYPP_THROW( MediaBadAttachPointException(url()));
00707 
00708     setAttachPoint( mountpoint, true);
00709   }
00710 
00711   disconnectFrom(); // clean _curl if needed
00712   _curl = curl_easy_init();
00713   if ( !_curl ) {
00714     ZYPP_THROW(MediaCurlInitException(_url));
00715   }
00716   try
00717     {
00718       setupEasy();
00719     }
00720   catch (Exception & ex)
00721     {
00722       disconnectFrom();
00723       ZYPP_RETHROW(ex);
00724     }
00725 
00726   // FIXME: need a derived class to propelly compare url's
00727   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
00728   setMediaSource(media);
00729 }
00730 
00731 bool
00732 MediaCurl::checkAttachPoint(const Pathname &apoint) const
00733 {
00734   return MediaHandler::checkAttachPoint( apoint, true, true);
00735 }
00736 
00738 
00739 void MediaCurl::disconnectFrom()
00740 {
00741   if ( _customHeaders )
00742   {
00743     curl_slist_free_all(_customHeaders);
00744     _customHeaders = 0L;
00745   }
00746 
00747   if ( _curl )
00748   {
00749     curl_easy_cleanup( _curl );
00750     _curl = NULL;
00751   }
00752 }
00753 
00755 
00756 void MediaCurl::releaseFrom( const std::string & ejectDev )
00757 {
00758   disconnect();
00759 }
00760 
00761 Url MediaCurl::getFileUrl( const Pathname & filename_r ) const
00762 {
00763   std::string path( _url.getPathName() );
00764   // Simply extend the URLs pathname. An 'absolute' URL path
00765   // is achieved by encoding the 2nd '/' in the URL:
00766   //   URL: ftp://user@server   -> ~user
00767   //   URL: ftp://user@server/  -> ~user
00768   //   URL: ftp://user@server// -> /
00769   //                         ^- this '/' is just a separator
00770   if ( path.empty() ||  path == "/" )   // empty URL path; the '/' is just a separator
00771   {
00772     path = filename_r.absolutename().asString();
00773   }
00774   else if ( *path.rbegin() == '/' )
00775   {
00776     path += filename_r.absolutename().asString().substr(1);
00777   }
00778   else
00779   {
00780     path += filename_r.absolutename().asString();
00781   }
00782   Url newurl( _url );
00783   newurl.setPathName( path );
00784   return newurl;
00785 }
00786 
00788 
00789 void MediaCurl::getFile( const Pathname & filename ) const
00790 {
00791     // Use absolute file name to prevent access of files outside of the
00792     // hierarchy below the attach point.
00793     getFileCopy(filename, localPath(filename).absolutename());
00794 }
00795 
00797 
00798 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
00799 {
00800   callback::SendReport<DownloadProgressReport> report;
00801 
00802   Url fileurl(getFileUrl(filename));
00803 
00804   bool retry = false;
00805 
00806   do
00807   {
00808     try
00809     {
00810       doGetFileCopy(filename, target, report);
00811       retry = false;
00812     }
00813     // retry with proper authentication data
00814     catch (MediaUnauthorizedException & ex_r)
00815     {
00816       if(authenticate(ex_r.hint(), !retry))
00817         retry = true;
00818       else
00819       {
00820         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
00821         ZYPP_RETHROW(ex_r);
00822       }
00823     }
00824     // unexpected exception
00825     catch (MediaException & excpt_r)
00826     {
00827       // FIXME: error number fix
00828       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
00829       ZYPP_RETHROW(excpt_r);
00830     }
00831   }
00832   while (retry);
00833 
00834   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
00835 }
00836 
00838 
00839 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
00840 {
00841   bool retry = false;
00842 
00843   do
00844   {
00845     try
00846     {
00847       return doGetDoesFileExist( filename );
00848     }
00849     // authentication problem, retry with proper authentication data
00850     catch (MediaUnauthorizedException & ex_r)
00851     {
00852       if(authenticate(ex_r.hint(), !retry))
00853         retry = true;
00854       else
00855         ZYPP_RETHROW(ex_r);
00856     }
00857     // unexpected exception
00858     catch (MediaException & excpt_r)
00859     {
00860       ZYPP_RETHROW(excpt_r);
00861     }
00862   }
00863   while (retry);
00864 
00865   return false;
00866 }
00867 
00869 
00870 void MediaCurl::evaluateCurlCode( const Pathname &filename,
00871                                   CURLcode code,
00872                                   bool timeout_reached ) const
00873 {
00874   if ( code != 0 )
00875   {
00876     Url url;
00877     if (filename.empty())
00878       url = _url;
00879     else
00880       url = getFileUrl(filename);
00881     std::string err;
00882     try
00883     {
00884       switch ( code )
00885       {
00886       case CURLE_UNSUPPORTED_PROTOCOL:
00887       case CURLE_URL_MALFORMAT:
00888       case CURLE_URL_MALFORMAT_USER:
00889           err = " Bad URL";
00890           break;
00891       case CURLE_LOGIN_DENIED:
00892           ZYPP_THROW(
00893               MediaUnauthorizedException(url, "Login failed.", _curlError, ""));
00894           break;
00895       case CURLE_HTTP_RETURNED_ERROR:
00896       {
00897         long httpReturnCode = 0;
00898         CURLcode infoRet = curl_easy_getinfo( _curl,
00899                                               CURLINFO_RESPONSE_CODE,
00900                                               &httpReturnCode );
00901         if ( infoRet == CURLE_OK )
00902         {
00903           string msg = "HTTP response: " + str::numstring( httpReturnCode );
00904           switch ( httpReturnCode )
00905           {
00906           case 401:
00907           {
00908             string auth_hint = getAuthHint();
00909 
00910             DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
00911             DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
00912 
00913             ZYPP_THROW(MediaUnauthorizedException(
00914                            url, "Login failed.", _curlError, auth_hint
00915                            ));
00916           }
00917 
00918           case 503: // service temporarily unavailable (bnc #462545)
00919             ZYPP_THROW(MediaTemporaryProblemException(url));
00920           case 504: // gateway timeout
00921             ZYPP_THROW(MediaTimeoutException(url));
00922           case 403:
00923           {
00924             string msg403;
00925             if (url.asString().find("novell.com") != string::npos)
00926               msg403 = _("Visit the Novell Customer Center to check whether your registration is valid and has not expired.");
00927             ZYPP_THROW(MediaForbiddenException(url, msg403));
00928           }
00929           case 404:
00930               ZYPP_THROW(MediaFileNotFoundException(_url, filename));
00931           }
00932 
00933           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00934           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00935         }
00936         else
00937         {
00938           string msg = "Unable to retrieve HTTP response:";
00939           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00940           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00941         }
00942       }
00943       break;
00944       case CURLE_FTP_COULDNT_RETR_FILE:
00945 #if CURLVERSION_AT_LEAST(7,16,0)
00946       case CURLE_REMOTE_FILE_NOT_FOUND:
00947 #endif
00948       case CURLE_FTP_ACCESS_DENIED:
00949         err = "File not found";
00950         ZYPP_THROW(MediaFileNotFoundException(_url, filename));
00951         break;
00952       case CURLE_BAD_PASSWORD_ENTERED:
00953       case CURLE_FTP_USER_PASSWORD_INCORRECT:
00954           err = "Login failed";
00955           break;
00956       case CURLE_COULDNT_RESOLVE_PROXY:
00957       case CURLE_COULDNT_RESOLVE_HOST:
00958       case CURLE_COULDNT_CONNECT:
00959       case CURLE_FTP_CANT_GET_HOST:
00960         err = "Connection failed";
00961         break;
00962       case CURLE_WRITE_ERROR:
00963         err = "Write error";
00964         break;
00965       case CURLE_PARTIAL_FILE:
00966       case CURLE_OPERATION_TIMEDOUT:
00967         timeout_reached = true; // fall though to TimeoutException
00968         // fall though...
00969       case CURLE_ABORTED_BY_CALLBACK:
00970          if( timeout_reached )
00971         {
00972           err  = "Timeout reached";
00973           ZYPP_THROW(MediaTimeoutException(url));
00974         }
00975         else
00976         {
00977           err = "User abort";
00978         }
00979         break;
00980       case CURLE_SSL_PEER_CERTIFICATE:
00981       default:
00982         err = "Unrecognized error";
00983         break;
00984       }
00985 
00986       // uhm, no 0 code but unknown curl exception
00987       ZYPP_THROW(MediaCurlException(url, err, _curlError));
00988     }
00989     catch (const MediaException & excpt_r)
00990     {
00991       ZYPP_RETHROW(excpt_r);
00992     }
00993   }
00994   else
00995   {
00996     // actually the code is 0, nothing happened
00997   }
00998 }
00999 
01001 
01002 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
01003 {
01004   DBG << filename.asString() << endl;
01005 
01006   if(!_url.isValid())
01007     ZYPP_THROW(MediaBadUrlException(_url));
01008 
01009   if(_url.getHost().empty())
01010     ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
01011 
01012   Url url(getFileUrl(filename));
01013 
01014   DBG << "URL: " << url.asString() << endl;
01015     // Use URL without options and without username and passwd
01016     // (some proxies dislike them in the URL).
01017     // Curl seems to need the just scheme, hostname and a path;
01018     // the rest was already passed as curl options (in attachTo).
01019   Url curlUrl( url );
01020 
01021   // Use asString + url::ViewOptions instead?
01022   curlUrl.setUsername( "" );
01023   curlUrl.setPassword( "" );
01024   curlUrl.setPathParams( "" );
01025   curlUrl.setQueryString( "" );
01026   curlUrl.setFragment( "" );
01027 
01028   //
01029     // See also Bug #154197 and ftp url definition in RFC 1738:
01030     // The url "ftp://user@host/foo/bar/file" contains a path,
01031     // that is relative to the user's home.
01032     // The url "ftp://user@host//foo/bar/file" (or also with
01033     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
01034     // contains an absolute path.
01035   //
01036   string urlBuffer( curlUrl.asString());
01037   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
01038                                    urlBuffer.c_str() );
01039   if ( ret != 0 ) {
01040     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01041   }
01042 
01043   // instead of returning no data with NOBODY, we return
01044   // little data, that works with broken servers, and
01045   // works for ftp as well, because retrieving only headers
01046   // ftp will return always OK code ?
01047   // See http://curl.haxx.se/docs/knownbugs.html #58
01048   if (  (_url.getScheme() == "http" ||  _url.getScheme() == "https") &&
01049         _settings.headRequestsAllowed() )
01050     ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1L );
01051   else
01052     ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
01053 
01054   if ( ret != 0 ) {
01055     curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
01056     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
01057     /* yes, this is why we never got to get NOBODY working before,
01058        because setting it changes this option too, and we also
01059        need to reset it
01060        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
01061     */
01062     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
01063     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01064   }
01065 
01066   FILE *file = ::fopen( "/dev/null", "w" );
01067   if ( !file ) {
01068       ERR << "fopen failed for /dev/null" << endl;
01069       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
01070       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
01071       /* yes, this is why we never got to get NOBODY working before,
01072        because setting it changes this option too, and we also
01073        need to reset it
01074        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
01075       */
01076       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
01077       if ( ret != 0 ) {
01078           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01079       }
01080       ZYPP_THROW(MediaWriteException("/dev/null"));
01081   }
01082 
01083   ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
01084   if ( ret != 0 ) {
01085       ::fclose(file);
01086       std::string err( _curlError);
01087       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
01088       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
01089       /* yes, this is why we never got to get NOBODY working before,
01090        because setting it changes this option too, and we also
01091        need to reset it
01092        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
01093       */
01094       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
01095       if ( ret != 0 ) {
01096           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01097       }
01098       ZYPP_THROW(MediaCurlSetOptException(url, err));
01099   }
01100 
01101   CURLcode ok = curl_easy_perform( _curl );
01102   MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
01103 
01104   // reset curl settings
01105   if (  _url.getScheme() == "http" ||  _url.getScheme() == "https" )
01106   {
01107     curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
01108     if ( ret != 0 ) {
01109       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01110     }
01111 
01112     /* yes, this is why we never got to get NOBODY working before,
01113        because setting it changes this option too, and we also
01114        need to reset it
01115        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
01116     */
01117     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L);
01118     if ( ret != 0 ) {
01119       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01120     }
01121 
01122   }
01123   else
01124   {
01125     // for FTP we set different options
01126     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL);
01127     if ( ret != 0 ) {
01128       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01129     }
01130   }
01131 
01132   // if the code is not zero, close the file
01133   if ( ok != 0 )
01134       ::fclose(file);
01135 
01136   // as we are not having user interaction, the user can't cancel
01137   // the file existence checking, a callback or timeout return code
01138   // will be always a timeout.
01139   try {
01140       evaluateCurlCode( filename, ok, true /* timeout */);
01141   }
01142   catch ( const MediaFileNotFoundException &e ) {
01143       // if the file did not exist then we can return false
01144       return false;
01145   }
01146   catch ( const MediaException &e ) {
01147       // some error, we are not sure about file existence, rethrw
01148       ZYPP_RETHROW(e);
01149   }
01150   // exists
01151   return ( ok == CURLE_OK );
01152 }
01153 
01155 
01156 
01157 #if DETECT_DIR_INDEX
01158 bool MediaCurl::detectDirIndex() const
01159 {
01160   if(_url.getScheme() != "http" && _url.getScheme() != "https")
01161     return false;
01162   //
01163   // try to check the effective url and set the not_a_file flag
01164   // if the url path ends with a "/", what usually means, that
01165   // we've received a directory index (index.html content).
01166   //
01167   // Note: This may be dangerous and break file retrieving in
01168   //       case of some server redirections ... ?
01169   //
01170   bool      not_a_file = false;
01171   char     *ptr = NULL;
01172   CURLcode  ret = curl_easy_getinfo( _curl,
01173                                      CURLINFO_EFFECTIVE_URL,
01174                                      &ptr);
01175   if ( ret == CURLE_OK && ptr != NULL)
01176   {
01177     try
01178     {
01179       Url         eurl( ptr);
01180       std::string path( eurl.getPathName());
01181       if( !path.empty() && path != "/" && *path.rbegin() == '/')
01182       {
01183         DBG << "Effective url ("
01184             << eurl
01185             << ") seems to provide the index of a directory"
01186             << endl;
01187         not_a_file = true;
01188       }
01189     }
01190     catch( ... )
01191     {}
01192   }
01193   return not_a_file;
01194 }
01195 #endif
01196 
01198 
01199 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
01200 {
01201     Pathname dest = target.absolutename();
01202     if( assert_dir( dest.dirname() ) )
01203     {
01204       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
01205       Url url(getFileUrl(filename));
01206       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
01207     }
01208     string destNew = target.asString() + ".new.zypp.XXXXXX";
01209     char *buf = ::strdup( destNew.c_str());
01210     if( !buf)
01211     {
01212       ERR << "out of memory for temp file name" << endl;
01213       Url url(getFileUrl(filename));
01214       ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name"));
01215     }
01216 
01217     int tmp_fd = ::mkstemp( buf );
01218     if( tmp_fd == -1)
01219     {
01220       free( buf);
01221       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
01222       ZYPP_THROW(MediaWriteException(destNew));
01223     }
01224     destNew = buf;
01225     free( buf);
01226 
01227     FILE *file = ::fdopen( tmp_fd, "w" );
01228     if ( !file ) {
01229       ::close( tmp_fd);
01230       filesystem::unlink( destNew );
01231       ERR << "fopen failed for file '" << destNew << "'" << endl;
01232       ZYPP_THROW(MediaWriteException(destNew));
01233     }
01234 
01235     DBG << "dest: " << dest << endl;
01236     DBG << "temp: " << destNew << endl;
01237 
01238     // set IFMODSINCE time condition (no download if not modified)
01239     if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) )
01240     {
01241       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
01242       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime());
01243     }
01244     else
01245     {
01246       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
01247       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
01248     }
01249     try
01250     {
01251       doGetFileCopyFile(filename, dest, file, report, options);
01252     }
01253     catch (Exception &e)
01254     {
01255       ::fclose( file );
01256       filesystem::unlink( destNew );
01257       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
01258       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
01259       ZYPP_RETHROW(e);
01260     }
01261 
01262     long httpReturnCode = 0;
01263     CURLcode infoRet = curl_easy_getinfo(_curl,
01264                                          CURLINFO_RESPONSE_CODE,
01265                                          &httpReturnCode);
01266     bool modified = true;
01267     if (infoRet == CURLE_OK)
01268     {
01269       DBG << "HTTP response: " + str::numstring(httpReturnCode);
01270       if ( httpReturnCode == 304
01271            || ( httpReturnCode == 213 && _url.getScheme() == "ftp" ) ) // not modified
01272       {
01273         DBG << " Not modified.";
01274         modified = false;
01275       }
01276       DBG << endl;
01277     }
01278     else
01279     {
01280       WAR << "Could not get the reponse code." << endl;
01281     }
01282 
01283     if (modified || infoRet != CURLE_OK)
01284     {
01285       // apply umask
01286       if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) )
01287       {
01288         ERR << "Failed to chmod file " << destNew << endl;
01289       }
01290       if (::fclose( file ))
01291       {
01292         ERR << "Fclose failed for file '" << destNew << "'" << endl;
01293         ZYPP_THROW(MediaWriteException(destNew));
01294       }
01295       // move the temp file into dest
01296       if ( rename( destNew, dest ) != 0 ) {
01297         ERR << "Rename failed" << endl;
01298         ZYPP_THROW(MediaWriteException(dest));
01299       }
01300     }
01301     else
01302     {
01303       // close and remove the temp file
01304       ::fclose( file );
01305       filesystem::unlink( destNew );
01306     }
01307 
01308     DBG << "done: " << PathInfo(dest) << endl;
01309 }
01310 
01312 
01313 void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
01314 {
01315     DBG << filename.asString() << endl;
01316 
01317     if(!_url.isValid())
01318       ZYPP_THROW(MediaBadUrlException(_url));
01319 
01320     if(_url.getHost().empty())
01321       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
01322 
01323     Url url(getFileUrl(filename));
01324 
01325     DBG << "URL: " << url.asString() << endl;
01326     // Use URL without options and without username and passwd
01327     // (some proxies dislike them in the URL).
01328     // Curl seems to need the just scheme, hostname and a path;
01329     // the rest was already passed as curl options (in attachTo).
01330     Url curlUrl( url );
01331 
01332     // Use asString + url::ViewOptions instead?
01333     curlUrl.setUsername( "" );
01334     curlUrl.setPassword( "" );
01335     curlUrl.setPathParams( "" );
01336     curlUrl.setQueryString( "" );
01337     curlUrl.setFragment( "" );
01338 
01339     //
01340     // See also Bug #154197 and ftp url definition in RFC 1738:
01341     // The url "ftp://user@host/foo/bar/file" contains a path,
01342     // that is relative to the user's home.
01343     // The url "ftp://user@host//foo/bar/file" (or also with
01344     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
01345     // contains an absolute path.
01346     //
01347     string urlBuffer( curlUrl.asString());
01348     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
01349                                      urlBuffer.c_str() );
01350     if ( ret != 0 ) {
01351       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01352     }
01353 
01354     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
01355     if ( ret != 0 ) {
01356       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01357     }
01358 
01359     // Set callback and perform.
01360     ProgressData progressData(_settings.timeout(), url, &report);
01361     if (!(options & OPTION_NO_REPORT_START))
01362       report->start(url, dest);
01363     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
01364       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01365     }
01366 
01367     ret = curl_easy_perform( _curl );
01368 
01369     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
01370       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01371     }
01372 
01373     if ( ret != 0 )
01374     {
01375       ERR << "curl error: " << ret << ": " << _curlError
01376           << ", temp file size " << ftell(file)
01377           << " bytes." << endl;
01378 
01379       // the timeout is determined by the progress data object
01380       // which holds wheter the timeout was reached or not,
01381       // otherwise it would be a user cancel
01382       try {
01383         evaluateCurlCode( filename, ret, progressData.reached);
01384       }
01385       catch ( const MediaException &e ) {
01386         // some error, we are not sure about file existence, rethrw
01387         ZYPP_RETHROW(e);
01388       }
01389     }
01390 
01391 #if DETECT_DIR_INDEX
01392     if (!ret && detectDirIndex())
01393       {
01394         ZYPP_THROW(MediaNotAFileException(_url, filename));
01395       }
01396 #endif // DETECT_DIR_INDEX
01397 }
01398 
01400 
01401 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
01402 {
01403   filesystem::DirContent content;
01404   getDirInfo( content, dirname, /*dots*/false );
01405 
01406   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
01407       Pathname filename = dirname + it->name;
01408       int res = 0;
01409 
01410       switch ( it->type ) {
01411       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
01412       case filesystem::FT_FILE:
01413         getFile( filename );
01414         break;
01415       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
01416         if ( recurse_r ) {
01417           getDir( filename, recurse_r );
01418         } else {
01419           res = assert_dir( localPath( filename ) );
01420           if ( res ) {
01421             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
01422           }
01423         }
01424         break;
01425       default:
01426         // don't provide devices, sockets, etc.
01427         break;
01428       }
01429   }
01430 }
01431 
01433 
01434 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
01435                                const Pathname & dirname, bool dots ) const
01436 {
01437   getDirectoryYast( retlist, dirname, dots );
01438 }
01439 
01441 
01442 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
01443                             const Pathname & dirname, bool dots ) const
01444 {
01445   getDirectoryYast( retlist, dirname, dots );
01446 }
01447 
01449 
01450 int MediaCurl::progressCallback( void *clientp,
01451                                  double dltotal, double dlnow,
01452                                  double ultotal, double ulnow)
01453 {
01454   ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
01455   if( pdata)
01456   {
01457     time_t now   = time(NULL);
01458     if( now > 0)
01459     {
01460         // reset time of last change in case initial time()
01461         // failed or the time was adjusted (goes backward)
01462         if( pdata->ltime <= 0 || pdata->ltime > now)
01463         {
01464           pdata->ltime = now;
01465         }
01466 
01467         // start time counting as soon as first data arrives
01468         // (skip the connection / redirection time at begin)
01469         time_t dif = 0;
01470         if (dlnow > 0 || ulnow > 0)
01471         {
01472           dif = (now - pdata->ltime);
01473           dif = dif > 0 ? dif : 0;
01474 
01475           pdata->secs += dif;
01476         }
01477 
01478         // update the drate_avg and drate_period only after a second has passed
01479         // (this callback is called much more often than a second)
01480         // otherwise the values would be far from accurate when measuring
01481         // the time in seconds
01483 
01484         if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal ))
01485           pdata->drate_avg = (dlnow / pdata->secs);
01486 
01487         if ( dif > 0 )
01488         {
01489           pdata->drate_period = ((dlnow - pdata->dload_period) / dif);
01490           pdata->dload_period = dlnow;
01491         }
01492     }
01493 
01494     // send progress report first, abort transfer if requested
01495     if( pdata->report)
01496     {
01497       if (!(*(pdata->report))->progress(int( dltotal ? dlnow * 100 / dltotal : 0 ),
01498                                         pdata->url,
01499                                         pdata->drate_avg,
01500                                         pdata->drate_period))
01501       {
01502         return 1; // abort transfer
01503       }
01504     }
01505 
01506     // check if we there is a timeout set
01507     if( pdata->timeout > 0)
01508     {
01509       if( now > 0)
01510       {
01511         bool progress = false;
01512 
01513         // update download data if changed, mark progress
01514         if( dlnow != pdata->dload)
01515         {
01516           progress     = true;
01517           pdata->dload = dlnow;
01518           pdata->ltime = now;
01519         }
01520         // update upload data if changed, mark progress
01521         if( ulnow != pdata->uload)
01522         {
01523           progress     = true;
01524           pdata->uload = ulnow;
01525           pdata->ltime = now;
01526         }
01527 
01528         if( !progress && (now >= (pdata->ltime + pdata->timeout)))
01529         {
01530           pdata->reached = true;
01531           return 1; // aborts transfer
01532         }
01533       }
01534     }
01535   }
01536   return 0;
01537 }
01538 
01540 
01541 string MediaCurl::getAuthHint() const
01542 {
01543   long auth_info = CURLAUTH_NONE;
01544 
01545   CURLcode infoRet =
01546     curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
01547 
01548   if(infoRet == CURLE_OK)
01549   {
01550     return CurlAuthData::auth_type_long2str(auth_info);
01551   }
01552 
01553   return "";
01554 }
01555 
01557 
01558 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const
01559 {
01561   Target_Ptr target = zypp::getZYpp()->getTarget();
01562   CredentialManager cm(CredManagerOptions(target ? target->root() : ""));
01563   CurlAuthData_Ptr credentials;
01564 
01565   // get stored credentials
01566   AuthData_Ptr cmcred = cm.getCred(_url);
01567 
01568   if (cmcred && firstTry)
01569   {
01570     credentials.reset(new CurlAuthData(*cmcred));
01571     DBG << "got stored credentials:" << endl << *credentials << endl;
01572   }
01573   // if not found, ask user
01574   else
01575   {
01576 
01577     CurlAuthData_Ptr curlcred;
01578     curlcred.reset(new CurlAuthData());
01579     callback::SendReport<AuthenticationReport> auth_report;
01580 
01581     // preset the username if present in current url
01582     if (!_url.getUsername().empty() && firstTry)
01583       curlcred->setUsername(_url.getUsername());
01584     // if CM has found some credentials, preset the username from there
01585     else if (cmcred)
01586       curlcred->setUsername(cmcred->username());
01587 
01588     // indicate we have no good credentials from CM
01589     cmcred.reset();
01590 
01591     string prompt_msg = boost::str(boost::format(
01593       _("Authentication required for '%s'")) % _url.asString());
01594 
01595     // set available authentication types from the exception
01596     // might be needed in prompt
01597     curlcred->setAuthType(availAuthTypes);
01598 
01599     // ask user
01600     if (auth_report->prompt(_url, prompt_msg, *curlcred))
01601     {
01602       DBG << "callback answer: retry" << endl
01603           << "CurlAuthData: " << *curlcred << endl;
01604 
01605       if (curlcred->valid())
01606       {
01607         credentials = curlcred;
01608           // if (credentials->username() != _url.getUsername())
01609           //   _url.setUsername(credentials->username());
01617       }
01618     }
01619     else
01620     {
01621       DBG << "callback answer: cancel" << endl;
01622     }
01623   }
01624 
01625   // set username and password
01626   if (credentials)
01627   {
01628     // HACK, why is this const?
01629     const_cast<MediaCurl*>(this)->_settings.setUsername(credentials->username());
01630     const_cast<MediaCurl*>(this)->_settings.setPassword(credentials->password());
01631 
01632     // set username and password
01633     CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str());
01634     if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
01635 
01636     // set available authentication types from the exception
01637     if (credentials->authType() == CURLAUTH_NONE)
01638       credentials->setAuthType(availAuthTypes);
01639 
01640     // set auth type (seems this must be set _after_ setting the userpwd)
01641     if (credentials->authType() != CURLAUTH_NONE)
01642     {
01643       // FIXME: only overwrite if not empty?
01644       const_cast<MediaCurl*>(this)->_settings.setAuthType(credentials->authTypeAsString());
01645       ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType());
01646       if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
01647     }
01648 
01649     if (!cmcred)
01650     {
01651       credentials->setUrl(_url);
01652       cm.addCred(*credentials);
01653       cm.save();
01654     }
01655 
01656     return true;
01657   }
01658 
01659   return false;
01660 }
01661 
01662 
01663   } // namespace media
01664 } // namespace zypp
01665 //

doxygen