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 
00022 #include "zypp/media/MediaCurl.h"
00023 #include "zypp/media/proxyinfo/ProxyInfos.h"
00024 #include "zypp/media/ProxyInfo.h"
00025 #include "zypp/thread/Once.h"
00026 #include <cstdlib>
00027 #include <sys/types.h>
00028 #include <sys/stat.h>
00029 #include <sys/mount.h>
00030 #include <errno.h>
00031 #include <dirent.h>
00032 #include <unistd.h>
00033 
00034 #include "config.h"
00035 
00036 #define  DETECT_DIR_INDEX       0
00037 #define  CONNECT_TIMEOUT        60
00038 #define  TRANSFER_TIMEOUT       60 * 3
00039 #define  TRANSFER_TIMEOUT_MAX   60 * 60
00040 
00041 
00042 using namespace std;
00043 using namespace zypp::base;
00044 
00045 namespace
00046 {
00047   zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
00048   zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
00049 
00050   extern "C" void _do_free_once()
00051   {
00052     curl_global_cleanup();
00053   }
00054 
00055   extern "C" void globalFreeOnce()
00056   {
00057     zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
00058   }
00059 
00060   extern "C" void _do_init_once()
00061   {
00062     CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
00063     if ( ret != 0 )
00064     {
00065       WAR << "curl global init failed" << endl;
00066     }
00067 
00068     //
00069     // register at exit handler ?
00070     // this may cause trouble, because we can protect it
00071     // against ourself only.
00072     // if the app sets an atexit handler as well, it will
00073     // cause a double free while the second of them runs.
00074     //
00075     //std::atexit( globalFreeOnce);
00076   }
00077 
00078   inline void globalInitOnce()
00079   {
00080     zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
00081   }
00082 
00083   int log_curl(CURL *curl, curl_infotype info,
00084                char *ptr, size_t len, void *max_lvl)
00085   {
00086     std::string pfx(" ");
00087     long        lvl = 0;
00088     switch( info)
00089     {
00090       case CURLINFO_TEXT:       lvl = 1; pfx = "*"; break;
00091       case CURLINFO_HEADER_IN:  lvl = 2; pfx = "<"; break;
00092       case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
00093       default:                                      break;
00094     }
00095     if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
00096     {
00097       std::string                            msg(ptr, len);
00098       std::list<std::string>                 lines;
00099       std::list<std::string>::const_iterator line;
00100       zypp::str::split(msg, std::back_inserter(lines), "\r\n");
00101       for(line = lines.begin(); line != lines.end(); ++line)
00102       {
00103         DBG << pfx << " " << *line << endl;
00104       }
00105     }
00106     return 0;
00107   }
00108 }
00109 
00110 namespace zypp {
00111   namespace media {
00112 
00113   namespace {
00114     struct ProgressData
00115     {
00116       ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
00117                    callback::SendReport<DownloadProgressReport> *_report=NULL)
00118         : timeout(_timeout)
00119         , reached(false)
00120         , report(_report)
00121         , ltime( time(NULL))
00122         , dload( 0)
00123         , uload( 0)
00124         , url(_url)
00125       {}
00126       long                                          timeout;
00127       bool                                          reached;
00128       callback::SendReport<DownloadProgressReport> *report;
00129       time_t                                        ltime;
00130       double                                        dload;
00131       double                                        uload;
00132       zypp::Url                                     url;
00133     };
00134   }
00135 
00136 Pathname    MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
00137 std::string MediaCurl::_agent = "Novell ZYPP Installer";
00138 
00140 
00141 static inline void escape( string & str_r,
00142                            const char char_r, const string & escaped_r ) {
00143   for ( string::size_type pos = str_r.find( char_r );
00144         pos != string::npos; pos = str_r.find( char_r, pos ) ) {
00145     str_r.replace( pos, 1, escaped_r );
00146   }
00147 }
00148 
00149 static inline string escapedPath( string path_r ) {
00150   escape( path_r, ' ', "%20" );
00151   return path_r;
00152 }
00153 
00154 static inline string unEscape( string text_r ) {
00155   char * tmp = curl_unescape( text_r.c_str(), 0 );
00156   string ret( tmp );
00157   curl_free( tmp );
00158   return ret;
00159 }
00160 
00162 //
00163 //        CLASS NAME : MediaCurl
00164 //
00166 
00167 MediaCurl::MediaCurl( const Url &      url_r,
00168                       const Pathname & attach_point_hint_r )
00169     : MediaHandler( url_r, attach_point_hint_r,
00170                     "/", // urlpath at attachpoint
00171                     true ), // does_download
00172       _curl( NULL )
00173 {
00174   _curlError[0] = '\0';
00175   _curlDebug = 0L;
00176 
00177   MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
00178 
00179   globalInitOnce();
00180 
00181   if( !attachPoint().empty())
00182   {
00183     PathInfo ainfo(attachPoint());
00184     Pathname apath(attachPoint() + "XXXXXX");
00185     char    *atemp = ::strdup( apath.asString().c_str());
00186     char    *atest = NULL;
00187     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
00188          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
00189     {
00190       WAR << "attach point " << ainfo.path()
00191           << " is not useable for " << url_r.getScheme() << endl;
00192       setAttachPoint("", true);
00193     }
00194     else if( atest != NULL)
00195       ::rmdir(atest);
00196 
00197     if( atemp != NULL)
00198       ::free(atemp);
00199   }
00200 }
00201 
00202 void MediaCurl::setCookieFile( const Pathname &fileName )
00203 {
00204   _cookieFile = fileName;
00205 }
00206 
00208 //
00209 //
00210 //        METHOD NAME : MediaCurl::attachTo
00211 //        METHOD TYPE : PMError
00212 //
00213 //        DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
00214 //
00215 void MediaCurl::attachTo (bool next)
00216 {
00217   if ( next )
00218     ZYPP_THROW(MediaNotSupportedException(_url));
00219 
00220   if ( !_url.isValid() )
00221     ZYPP_THROW(MediaBadUrlException(_url));
00222 
00223   curl_version_info_data *curl_info = NULL;
00224   curl_info = curl_version_info(CURLVERSION_NOW);
00225   // curl_info does not need any free (is static)
00226   if (curl_info->protocols)
00227   {
00228     const char * const *proto;
00229     std::string        scheme( _url.getScheme());
00230     bool               found = false;
00231     for(proto=curl_info->protocols; !found && *proto; ++proto)
00232     {
00233       if( scheme == std::string((const char *)*proto))
00234         found = true;
00235     }
00236     if( !found)
00237     {
00238       std::string msg("Unsupported protocol '");
00239       msg += scheme;
00240       msg += "'";
00241       ZYPP_THROW(MediaBadUrlException(_url, msg));
00242     }
00243   }
00244 
00245   if( !isUseableAttachPoint(attachPoint()))
00246   {
00247     std::string mountpoint = createAttachPoint().asString();
00248 
00249     if( mountpoint.empty())
00250       ZYPP_THROW( MediaBadAttachPointException(url()));
00251 
00252     setAttachPoint( mountpoint, true);
00253   }
00254 
00255   disconnectFrom(); // clean _curl if needed
00256   _curl = curl_easy_init();
00257   if ( !_curl ) {
00258     ZYPP_THROW(MediaCurlInitException(_url));
00259   }
00260 
00261   {
00262     char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
00263     _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
00264     if( _curlDebug > 0)
00265     {
00266       curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1);
00267       curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
00268       curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
00269     }
00270   }
00271 
00272   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
00273   if ( ret != 0 ) {
00274     disconnectFrom();
00275     ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
00276   }
00277 
00278   ret = curl_easy_setopt( _curl, CURLOPT_FAILONERROR, true );
00279   if ( ret != 0 ) {
00280     disconnectFrom();
00281     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00282   }
00283 
00284   ret = curl_easy_setopt( _curl, CURLOPT_NOSIGNAL, 1 );
00285   if ( ret != 0 ) {
00286     disconnectFrom();
00287     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00288   }
00289 
00293   {
00294     _xfer_timeout = TRANSFER_TIMEOUT;
00295 
00296     std::string param(_url.getQueryParam("timeout"));
00297     if( !param.empty())
00298     {
00299       long num = str::strtonum<long>( param);
00300       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
00301         _xfer_timeout = num;
00302     }
00303   }
00304 
00305   /*
00306   ** Connect timeout
00307   */
00308   ret = curl_easy_setopt( _curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
00309   if ( ret != 0 ) {
00310     disconnectFrom();
00311     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00312   }
00313 
00314   if ( _url.getScheme() == "http" ) {
00315     // follow any Location: header that the server sends as part of
00316     // an HTTP header (#113275)
00317     ret = curl_easy_setopt ( _curl, CURLOPT_FOLLOWLOCATION, true );
00318     if ( ret != 0) {
00319       disconnectFrom();
00320       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00321     }
00322     ret = curl_easy_setopt ( _curl, CURLOPT_MAXREDIRS, 3L );
00323     if ( ret != 0) {
00324       disconnectFrom();
00325       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00326     }
00327     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
00328     if ( ret != 0) {
00329       disconnectFrom();
00330       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00331     }
00332   }
00333 
00334   if ( _url.getScheme() == "https" )
00335   {
00336     bool verify_peer = false;
00337     bool verify_host = false;
00338 
00339     std::string verify( _url.getQueryParam("ssl_verify"));
00340     if( verify.empty() ||
00341         verify == "yes")
00342     {
00343       verify_peer = true;
00344       verify_host = true;
00345     }
00346     else
00347     if( verify == "no")
00348     {
00349       verify_peer = false;
00350       verify_host = false;
00351     }
00352     else
00353     {
00354       std::vector<std::string>                 flags;
00355       std::vector<std::string>::const_iterator flag;
00356       str::split( verify, std::back_inserter(flags), ",");
00357       for(flag = flags.begin(); flag != flags.end(); ++flag)
00358       {
00359         if( *flag == "host")
00360         {
00361           verify_host = true;
00362         }
00363         else
00364         if( *flag == "peer")
00365         {
00366           verify_peer = true;
00367         }
00368         else
00369         {
00370                 disconnectFrom();
00371           ZYPP_THROW(MediaBadUrlException(_url, "Unknown ssl_verify flag"));
00372         }
00373       }
00374     }
00375 
00376     _ca_path = Pathname(_url.getQueryParam("ssl_capath")).asString();
00377     if( _ca_path.empty())
00378     {
00379         _ca_path = "/etc/ssl/certs/";
00380     }
00381     else
00382     if( !PathInfo(_ca_path).isDir() || !Pathname(_ca_path).absolute())
00383     {
00384         disconnectFrom();
00385         ZYPP_THROW(MediaBadUrlException(_url, "Invalid ssl_capath path"));
00386     }
00387 
00388     if( verify_peer || verify_host)
00389     {
00390       ret = curl_easy_setopt( _curl, CURLOPT_CAPATH, _ca_path.c_str());
00391       if ( ret != 0 ) {
00392         disconnectFrom();
00393         ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00394       }
00395     }
00396 
00397     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L);
00398     if ( ret != 0 ) {
00399       disconnectFrom();
00400       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00401     }
00402     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L);
00403     if ( ret != 0 ) {
00404       disconnectFrom();
00405       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00406     }
00407 
00408     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
00409     if ( ret != 0) {
00410       disconnectFrom();
00411       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00412     }
00413   }
00414 
00415 
00416   /*---------------------------------------------------------------*
00417    CURLOPT_USERPWD: [user name]:[password]
00418 
00419    Url::username/password -> CURLOPT_USERPWD
00420    If not provided, anonymous FTP identification
00421    *---------------------------------------------------------------*/
00422 
00423   if ( _url.getUsername().empty() ) {
00424     if ( _url.getScheme() == "ftp" ) {
00425       string id = "yast2@";
00426       id += VERSION;
00427       DBG << "Anonymous FTP identification: '" << id << "'" << endl;
00428       _userpwd = "anonymous:" + id;
00429     }
00430   } else {
00431     _userpwd = _url.getUsername();
00432     if ( _url.getPassword().size() ) {
00433       _userpwd += ":" + _url.getPassword();
00434     }
00435   }
00436 
00437   if ( _userpwd.size() ) {
00438     _userpwd = unEscape( _userpwd );
00439     ret = curl_easy_setopt( _curl, CURLOPT_USERPWD, _userpwd.c_str() );
00440     if ( ret != 0 ) {
00441       disconnectFrom();
00442       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00443     }
00444 
00445     if(_url.getScheme() == "http" || _url.getScheme() == "https")
00446     {
00447       std::vector<std::string>                 list;
00448       std::vector<std::string>::const_iterator it;
00449 
00450       string use_auth = _url.getQueryParam("auth");
00451       if( use_auth.empty())
00452         use_auth = "digest,basic";
00453 
00454       str::split(use_auth, std::back_inserter(list), ",");
00455 
00456       long auth = CURLAUTH_NONE;
00457       for(it = list.begin(); it != list.end(); ++it)
00458       {
00459         if(*it == "basic")
00460         {
00461           auth |= CURLAUTH_BASIC;
00462         }
00463         else
00464         if(*it == "digest")
00465         {
00466           auth |= CURLAUTH_DIGEST;
00467         }
00468         else
00469         if((curl_info && (curl_info->features & CURL_VERSION_NTLM)) &&
00470            (*it == "ntlm"))
00471         {
00472           auth |= CURLAUTH_NTLM;
00473         }
00474         else
00475         if((curl_info && (curl_info->features & CURL_VERSION_SPNEGO)) &&
00476            (*it == "spnego" || *it == "negotiate"))
00477         {
00478           // there is no separate spnego flag for auth
00479           auth |= CURLAUTH_GSSNEGOTIATE;
00480         }
00481         else
00482         if((curl_info && (curl_info->features & CURL_VERSION_GSSNEGOTIATE)) &&
00483            (*it == "gssnego" || *it == "negotiate"))
00484         {
00485           auth |= CURLAUTH_GSSNEGOTIATE;
00486         }
00487         else
00488         {
00489           std::string msg("Unsupported HTTP authentication method '");
00490           msg += *it;
00491           msg += "'";
00492                 disconnectFrom();
00493           ZYPP_THROW(MediaBadUrlException(_url, msg));
00494         }
00495       }
00496 
00497       if( auth != CURLAUTH_NONE)
00498       {
00499         DBG << "Enabling HTTP authentication methods: " << use_auth
00500           << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
00501 
00502         ret = curl_easy_setopt( _curl, CURLOPT_HTTPAUTH, auth);
00503         if ( ret != 0 ) {
00504                 disconnectFrom();
00505           ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00506         }
00507       }
00508     }
00509   }
00510 
00511   /*---------------------------------------------------------------*
00512    CURLOPT_PROXY: host[:port]
00513 
00514    Url::option(proxy and proxyport) -> CURLOPT_PROXY
00515    If not provided, /etc/sysconfig/proxy is evaluated
00516    *---------------------------------------------------------------*/
00517 
00518   _proxy = _url.getQueryParam( "proxy" );
00519 
00520   if ( ! _proxy.empty() ) {
00521     string proxyport( _url.getQueryParam( "proxyport" ) );
00522     if ( ! proxyport.empty() ) {
00523       _proxy += ":" + proxyport;
00524     }
00525   } else {
00526 
00527     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
00528 
00529     if ( proxy_info.enabled())
00530     {
00531       bool useproxy = true;
00532 
00533       std::list<std::string> nope = proxy_info.noProxy();
00534       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
00535            it != proxy_info.noProxyEnd();
00536            it++)
00537       {
00538         std::string host( str::toLower(_url.getHost()));
00539         std::string temp( str::toLower(*it));
00540 
00541         // no proxy if it points to a suffix
00542         // preceeded by a '.', that maches
00543         // the trailing portion of the host.
00544         if( temp.size() > 1 && temp.at(0) == '.')
00545         {
00546           if(host.size() > temp.size() &&
00547              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
00548           {
00549             DBG << "NO_PROXY: '" << *it  << "' matches host '"
00550                                  << host << "'" << endl;
00551             useproxy = false;
00552             break;
00553           }
00554         }
00555         else
00556         // no proxy if we have an exact match
00557         if( host == temp)
00558         {
00559           DBG << "NO_PROXY: '" << *it  << "' matches host '"
00560                                << host << "'" << endl;
00561           useproxy = false;
00562           break;
00563         }
00564       }
00565 
00566       if ( useproxy ) {
00567         _proxy = proxy_info.proxy(_url.getScheme());
00568       }
00569     }
00570   }
00571 
00572 
00573   DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
00574 
00575   if ( ! _proxy.empty() ) {
00576 
00577     ret = curl_easy_setopt( _curl, CURLOPT_PROXY, _proxy.c_str() );
00578     if ( ret != 0 ) {
00579       disconnectFrom();
00580       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00581     }
00582 
00583     /*---------------------------------------------------------------*
00584      CURLOPT_PROXYUSERPWD: [user name]:[password]
00585 
00586      Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
00587      If not provided, $HOME/.curlrc is evaluated
00588      *---------------------------------------------------------------*/
00589 
00590     _proxyuserpwd = _url.getQueryParam( "proxyuser" );
00591 
00592     if ( ! _proxyuserpwd.empty() ) {
00593 
00594       string proxypassword( _url.getQueryParam( "proxypassword" ) );
00595       if ( ! proxypassword.empty() ) {
00596         _proxyuserpwd += ":" + proxypassword;
00597       }
00598 
00599     } else {
00600       char *home = getenv("HOME");
00601       if( home && *home)
00602       {
00603               Pathname curlrcFile = string( home ) + string( "/.curlrc" );
00604 
00605         PathInfo h_info(string(home), PathInfo::LSTAT);
00606         PathInfo c_info(curlrcFile,   PathInfo::LSTAT);
00607 
00608         if( h_info.isDir()  && h_info.owner() == getuid() &&
00609             c_info.isFile() && c_info.owner() == getuid())
00610         {
00611                 map<string,string> rc_data = base::sysconfig::read( curlrcFile );
00612 
00613                 map<string,string>::const_iterator it = rc_data.find("proxy-user");
00614                 if (it != rc_data.end())
00615             _proxyuserpwd = it->second;
00616         }
00617         else
00618         {
00619           WAR << "Not allowed to parse '" << curlrcFile
00620               << "': bad file owner" << std::endl;
00621         }
00622       }
00623     }
00624 
00625     _proxyuserpwd = unEscape( _proxyuserpwd );
00626     ret = curl_easy_setopt( _curl, CURLOPT_PROXYUSERPWD, _proxyuserpwd.c_str() );
00627     if ( ret != 0 ) {
00628       disconnectFrom();
00629       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00630     }
00631   }
00632 
00633   /*---------------------------------------------------------------*
00634    *---------------------------------------------------------------*/
00635 
00636   _currentCookieFile = _cookieFile.asString();
00637 
00638   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEFILE,
00639                           _currentCookieFile.c_str() );
00640   if ( ret != 0 ) {
00641     disconnectFrom();
00642     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00643   }
00644 
00645   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEJAR,
00646                           _currentCookieFile.c_str() );
00647   if ( ret != 0 ) {
00648     disconnectFrom();
00649     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00650   }
00651 
00652   ret = curl_easy_setopt( _curl, CURLOPT_PROGRESSFUNCTION,
00653                           &progressCallback );
00654   if ( ret != 0 ) {
00655     disconnectFrom();
00656     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00657   }
00658 
00659   ret = curl_easy_setopt( _curl, CURLOPT_NOPROGRESS, false );
00660   if ( ret != 0 ) {
00661     disconnectFrom();
00662     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00663   }
00664 
00665   // FIXME: need a derived class to propelly compare url's
00666   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
00667   setMediaSource(media);
00668 }
00669 
00670 bool
00671 MediaCurl::checkAttachPoint(const Pathname &apoint) const
00672 {
00673   return MediaHandler::checkAttachPoint( apoint, true, true);
00674 }
00675 
00677 //
00678 //
00679 //        METHOD NAME : MediaCurl::disconnectFrom
00680 //        METHOD TYPE : PMError
00681 //
00682 void MediaCurl::disconnectFrom()
00683 {
00684   if ( _curl )
00685   {
00686     curl_easy_cleanup( _curl );
00687     _curl = NULL;
00688   }
00689 }
00690 
00692 //
00693 //
00694 //        METHOD NAME : MediaCurl::releaseFrom
00695 //        METHOD TYPE : PMError
00696 //
00697 //        DESCRIPTION : Asserted that media is attached.
00698 //
00699 void MediaCurl::releaseFrom( bool eject )
00700 {
00701   disconnect();
00702 }
00703 
00704 
00706 //
00707 //        METHOD NAME : MediaCurl::getFile
00708 //        METHOD TYPE : PMError
00709 //
00710 
00711 void MediaCurl::getFile( const Pathname & filename ) const
00712 {
00713     // Use absolute file name to prevent access of files outside of the
00714     // hierarchy below the attach point.
00715     getFileCopy(filename, localPath(filename).absolutename());
00716 }
00717 
00718 
00719 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
00720 {
00721   callback::SendReport<DownloadProgressReport> report;
00722 
00723   Url url( _url );
00724 
00725   try {
00726     doGetFileCopy(filename, target, report);
00727   }
00728   catch (MediaException & excpt_r)
00729   {
00730     // FIXME: this will not match the first URL
00731     // FIXME: error number fix
00732     report->finish(url, zypp::media::DownloadProgressReport::NOT_FOUND, excpt_r.msg());
00733     ZYPP_RETHROW(excpt_r);
00734   }
00735   report->finish(url, zypp::media::DownloadProgressReport::NO_ERROR, "");
00736 }
00737 
00738 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
00739 {
00740   DBG << filename.asString() << endl;
00741 
00742   if(!_url.isValid())
00743     ZYPP_THROW(MediaBadUrlException(_url));
00744 
00745   if(_url.getHost().empty())
00746     ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
00747   
00748   string path = _url.getPathName();
00749   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
00750         filename.absolute() ) {
00751       // If url has a path with trailing slash, remove the leading slash from
00752       // the absolute file name
00753     path += filename.asString().substr( 1, filename.asString().size() - 1 );
00754   } else if ( filename.relative() ) {
00755       // Add trailing slash to path, if not already there
00756     if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
00757     // Remove "./" from begin of relative file name
00758     path += filename.asString().substr( 2, filename.asString().size() - 2 );
00759   } else {
00760     path += filename.asString();
00761   }
00762 
00763   Url url( _url );
00764   url.setPathName( path );
00765 
00766   DBG << "URL: " << url.asString() << endl;
00767     // Use URL without options and without username and passwd
00768     // (some proxies dislike them in the URL).
00769     // Curl seems to need the just scheme, hostname and a path;
00770     // the rest was already passed as curl options (in attachTo).
00771   Url curlUrl( url );
00772 
00773     // Use asString + url::ViewOptions instead?
00774   curlUrl.setUsername( "" );
00775   curlUrl.setPassword( "" );
00776   curlUrl.setPathParams( "" );
00777   curlUrl.setQueryString( "" );
00778   curlUrl.setFragment( "" );
00779   
00780   //
00781     // See also Bug #154197 and ftp url definition in RFC 1738:
00782     // The url "ftp://user@host/foo/bar/file" contains a path,
00783     // that is relative to the user's home.
00784     // The url "ftp://user@host//foo/bar/file" (or also with
00785     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
00786     // contains an absolute path.
00787   //
00788   string urlBuffer( curlUrl.asString());
00789   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
00790                                    urlBuffer.c_str() );
00791   if ( ret != 0 ) {
00792     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00793   }
00794   
00795   // set no data, because we only want to check if the file exists
00796   //ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 );
00797   //if ( ret != 0 ) {
00798   //    ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00799   //}    
00800   
00801   // instead of returning no data with NOBODY, we return 
00802   // little data, that works with broken servers, and
00803   // works for ftp as well, because retrieving only headers
00804   // ftp will return always OK code ?
00805   ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
00806   if ( ret != 0 ) {
00807       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00808   }    
00809   
00810   FILE *file = ::fopen( "/dev/null", "w" );
00811   if ( !file ) {
00812       ::fclose(file);
00813       ERR << "fopen failed for /dev/null" << endl;
00814       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
00815       if ( ret != 0 ) {
00816           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00817       }
00818       ZYPP_THROW(MediaWriteException("/dev/null"));
00819   }
00820 
00821   ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
00822   if ( ret != 0 ) {
00823       ::fclose(file);
00824       std::string err( _curlError);
00825       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
00826       if ( ret != 0 ) {
00827           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00828       }
00829       ZYPP_THROW(MediaCurlSetOptException(url, err));
00830   }
00831     // Set callback and perform.
00832   //ProgressData progressData(_xfer_timeout, url, &report);
00833   //report->start(url, dest);
00834   //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
00835   //  WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
00836   //}
00837 
00838   CURLcode ok = curl_easy_perform( _curl );
00839   MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
00840 
00841   // reset curl settings
00842   ret = curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
00843   if ( ret != 0 )
00844   {
00845     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00846   }
00847   
00848   if ( ok != 0 )
00849   {
00850     ::fclose( file );
00851     
00852     std::string err;
00853     try
00854     {
00855       bool err_file_not_found = false;
00856       switch ( ok )
00857       {
00858       case CURLE_FTP_COULDNT_RETR_FILE:
00859       case CURLE_FTP_ACCESS_DENIED: 
00860         err_file_not_found = true;
00861         break;
00862       case CURLE_HTTP_RETURNED_ERROR:
00863         {
00864           long httpReturnCode = 0;
00865           CURLcode infoRet = curl_easy_getinfo( _curl,
00866                                                 CURLINFO_RESPONSE_CODE,
00867                                                 &httpReturnCode );
00868           if ( infoRet == CURLE_OK )
00869           {
00870             string msg = "HTTP response: " +
00871                           str::numstring( httpReturnCode );
00872             if ( httpReturnCode == 401 )
00873             {
00874               err = " Login failed";
00875             }
00876             else
00877             if ( httpReturnCode == 403)
00878             {
00879                string msg403;
00880                if (url.asString().find("novell.com") != string::npos)
00881                  msg403 = str::form(_(
00882                    "Permission to access '%s' denied.\n\n"
00883                    "Visit the Novell Customer Center to check whether"
00884                    " your registration is valid and has not expired."),
00885                    url.asString().c_str());
00886 
00887                ZYPP_THROW(MediaForbiddenException(url, msg403));
00888             }
00889             else
00890             if ( httpReturnCode == 404)
00891             {
00892                err_file_not_found = true;
00893                break;
00894             }
00895 
00896             msg += err;
00897             DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00898             ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00899           }
00900           else
00901           {
00902             string msg = "Unable to retrieve HTTP response:";
00903             msg += err;
00904             DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00905             ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00906           }
00907         }
00908         break;
00909       case CURLE_UNSUPPORTED_PROTOCOL:
00910       case CURLE_URL_MALFORMAT:
00911       case CURLE_URL_MALFORMAT_USER:
00912       case CURLE_BAD_PASSWORD_ENTERED:
00913       case CURLE_FTP_USER_PASSWORD_INCORRECT:
00914       case CURLE_COULDNT_RESOLVE_PROXY:
00915       case CURLE_COULDNT_RESOLVE_HOST:
00916       case CURLE_COULDNT_CONNECT:
00917       case CURLE_FTP_CANT_GET_HOST:
00918       case CURLE_WRITE_ERROR:
00919       case CURLE_ABORTED_BY_CALLBACK:
00920       case CURLE_SSL_PEER_CERTIFICATE:
00921       default:
00922         err = curl_easy_strerror(ok);
00923         if (err.empty())
00924           err = "Unrecognized error";
00925         break;
00926       }
00927       
00928       if( err_file_not_found)
00929       {
00930         // file does not exists
00931         return false;
00932       }
00933       else
00934       {
00935         // there was an error
00936         ZYPP_THROW(MediaCurlException(url, string(), _curlError));
00937       }
00938     }
00939     catch (const MediaException & excpt_r)
00940     {
00941       ZYPP_RETHROW(excpt_r);
00942     }
00943   } 
00944   
00945   // exists
00946   return ( ok == CURLE_OK );
00947   //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
00948   //  WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
00949   //}
00950 }    
00951 
00952 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report) const
00953 {
00954     DBG << filename.asString() << endl;
00955 
00956     if(!_url.isValid())
00957       ZYPP_THROW(MediaBadUrlException(_url));
00958 
00959     if(_url.getHost().empty())
00960       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
00961 
00962     string path = _url.getPathName();
00963     if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
00964          filename.absolute() ) {
00965       // If url has a path with trailing slash, remove the leading slash from
00966       // the absolute file name
00967       path += filename.asString().substr( 1, filename.asString().size() - 1 );
00968     } else if ( filename.relative() ) {
00969       // Add trailing slash to path, if not already there
00970       if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
00971       // Remove "./" from begin of relative file name
00972       path += filename.asString().substr( 2, filename.asString().size() - 2 );
00973     } else {
00974       path += filename.asString();
00975     }
00976 
00977     Url url( _url );
00978     url.setPathName( path );
00979 
00980     Pathname dest = target.absolutename();
00981     if( assert_dir( dest.dirname() ) )
00982     {
00983       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
00984       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
00985     }
00986 
00987     DBG << "URL: " << url.asString() << endl;
00988     // Use URL without options and without username and passwd
00989     // (some proxies dislike them in the URL).
00990     // Curl seems to need the just scheme, hostname and a path;
00991     // the rest was already passed as curl options (in attachTo).
00992     Url curlUrl( url );
00993 
00994     // Use asString + url::ViewOptions instead?
00995     curlUrl.setUsername( "" );
00996     curlUrl.setPassword( "" );
00997     curlUrl.setPathParams( "" );
00998     curlUrl.setQueryString( "" );
00999     curlUrl.setFragment( "" );
01000 
01001     //
01002     // See also Bug #154197 and ftp url definition in RFC 1738:
01003     // The url "ftp://user@host/foo/bar/file" contains a path,
01004     // that is relative to the user's home.
01005     // The url "ftp://user@host//foo/bar/file" (or also with
01006     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
01007     // contains an absolute path.
01008     //
01009     string urlBuffer( curlUrl.asString());
01010     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
01011                                      urlBuffer.c_str() );
01012     if ( ret != 0 ) {
01013       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01014     }
01015 
01016     string destNew = target.asString() + ".new.zypp.XXXXXX";
01017     char *buf = ::strdup( destNew.c_str());
01018     if( !buf)
01019     {
01020       ERR << "out of memory for temp file name" << endl;
01021       ZYPP_THROW(MediaSystemException(
01022         url, "out of memory for temp file name"
01023       ));
01024     }
01025 
01026     int tmp_fd = ::mkstemp( buf );
01027     if( tmp_fd == -1)
01028     {
01029       free( buf);
01030       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
01031       ZYPP_THROW(MediaWriteException(destNew));
01032     }
01033     destNew = buf;
01034     free( buf);
01035 
01036     FILE *file = ::fdopen( tmp_fd, "w" );
01037     if ( !file ) {
01038       ::close( tmp_fd);
01039       filesystem::unlink( destNew );
01040       ERR << "fopen failed for file '" << destNew << "'" << endl;
01041       ZYPP_THROW(MediaWriteException(destNew));
01042     }
01043 
01044     DBG << "dest: " << dest << endl;
01045     DBG << "temp: " << destNew << endl;
01046 
01047     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
01048     if ( ret != 0 ) {
01049       ::fclose( file );
01050       filesystem::unlink( destNew );
01051       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01052     }
01053 
01054     // Set callback and perform.
01055     ProgressData progressData(_xfer_timeout, url, &report);
01056     report->start(url, dest);
01057     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
01058       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01059     }
01060 
01061     ret = curl_easy_perform( _curl );
01062 
01063     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
01064       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01065     }
01066 
01067     if ( ret != 0 ) {
01068       ERR << "curl error: " << ret << ": " << _curlError
01069           << ", temp file size " << PathInfo(destNew).size()
01070           << " byte." << endl;
01071 
01072       ::fclose( file );
01073       filesystem::unlink( destNew );
01074 
01075       std::string err;
01076       try {
01077        bool err_file_not_found = false;
01078        switch ( ret ) {
01079         case CURLE_UNSUPPORTED_PROTOCOL:
01080         case CURLE_URL_MALFORMAT:
01081         case CURLE_URL_MALFORMAT_USER:
01082           err = " Bad URL";
01083         case CURLE_HTTP_RETURNED_ERROR:
01084           {
01085             long httpReturnCode = 0;
01086             CURLcode infoRet = curl_easy_getinfo( _curl,
01087                                                   CURLINFO_RESPONSE_CODE,
01088                                                   &httpReturnCode );
01089             if ( infoRet == CURLE_OK ) {
01090               string msg = "HTTP response: " +
01091                            str::numstring( httpReturnCode );
01092               if ( httpReturnCode == 401 )
01093               {
01094                 err = " Login failed";
01095               }
01096               else
01097               if ( httpReturnCode == 403)
01098               {
01099                  string msg403;
01100                  if (url.asString().find("novell.com") != string::npos)
01101                    msg403 = str::form(_(
01102                      "Permission to access '%s' denied.\n\n"
01103                      "Visit the Novell Customer Center to check whether"
01104                      " your registration is valid and has not expired."),
01105                      url.asString().c_str());
01106 
01107                  ZYPP_THROW(MediaForbiddenException(url, msg403));
01108               }
01109               else
01110               if ( httpReturnCode == 404)
01111               {
01112                  ZYPP_THROW(MediaFileNotFoundException(_url, filename));
01113               }
01114 
01115               msg += err;
01116               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
01117               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
01118             }
01119             else
01120             {
01121               string msg = "Unable to retrieve HTTP response:";
01122               msg += err;
01123               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
01124               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
01125             }
01126           }
01127           break;
01128         case CURLE_FTP_COULDNT_RETR_FILE:
01129         case CURLE_FTP_ACCESS_DENIED:
01130           err = "File not found";
01131           err_file_not_found = true;
01132           break;
01133         case CURLE_BAD_PASSWORD_ENTERED:
01134         case CURLE_FTP_USER_PASSWORD_INCORRECT:
01135           err = "Login failed";
01136           break;
01137         case CURLE_COULDNT_RESOLVE_PROXY:
01138         case CURLE_COULDNT_RESOLVE_HOST:
01139         case CURLE_COULDNT_CONNECT:
01140         case CURLE_FTP_CANT_GET_HOST:
01141           err = "Connection failed";
01142           break;
01143         case CURLE_WRITE_ERROR:
01144           err = "Write error";
01145           break;
01146         case CURLE_ABORTED_BY_CALLBACK:
01147           if( progressData.reached)
01148           {
01149             err  = "Timeout reached";
01150           }
01151           else
01152           {
01153             err = "User abort";
01154           }
01155           break;
01156         case CURLE_SSL_PEER_CERTIFICATE:
01157         default:
01158           err = "Unrecognized error";
01159           break;
01160        }
01161        if( err_file_not_found)
01162        {
01163          ZYPP_THROW(MediaFileNotFoundException(_url, filename));
01164        }
01165        else
01166        {
01167          ZYPP_THROW(MediaCurlException(url, err, _curlError));
01168        }
01169       }
01170       catch (const MediaException & excpt_r)
01171       {
01172         ZYPP_RETHROW(excpt_r);
01173       }
01174     }
01175 #if DETECT_DIR_INDEX
01176     else
01177     if(curlUrl.getScheme() == "http" ||
01178        curlUrl.getScheme() == "https")
01179     {
01180       //
01181       // try to check the effective url and set the not_a_file flag
01182       // if the url path ends with a "/", what usually means, that
01183       // we've received a directory index (index.html content).
01184       //
01185       // Note: This may be dangerous and break file retrieving in
01186       //       case of some server redirections ... ?
01187       //
01188       bool      not_a_file = false;
01189       char     *ptr = NULL;
01190       CURLcode  ret = curl_easy_getinfo( _curl,
01191                                          CURLINFO_EFFECTIVE_URL,
01192                                          &ptr);
01193       if ( ret == CURLE_OK && ptr != NULL)
01194       {
01195         try
01196         {
01197           Url         eurl( ptr);
01198           std::string path( eurl.getPathName());
01199           if( !path.empty() && path != "/" && *path.rbegin() == '/')
01200           {
01201             DBG << "Effective url ("
01202                 << eurl
01203                 << ") seems to provide the index of a directory"
01204                 << endl;
01205             not_a_file = true;
01206           }
01207         }
01208         catch( ... )
01209         {}
01210       }
01211 
01212       if( not_a_file)
01213       {
01214         ::fclose( file );
01215         filesystem::unlink( destNew );
01216         ZYPP_THROW(MediaNotAFileException(_url, filename));
01217       }
01218     }
01219 #endif // DETECT_DIR_INDEX
01220 
01221     mode_t mask;
01222     // getumask() would be fine, but does not exist
01223     // [ the linker can't find it in glibc :-( ].
01224     mask = ::umask(0022); ::umask(mask);
01225     if ( ::fchmod( ::fileno(file), 0644 & ~mask))
01226     {
01227       ERR << "Failed to chmod file " << destNew << endl;
01228     }
01229     ::fclose( file );
01230 
01231     if ( rename( destNew, dest ) != 0 ) {
01232       ERR << "Rename failed" << endl;
01233       ZYPP_THROW(MediaWriteException(dest));
01234     }
01235     DBG << "done: " << PathInfo(dest) << endl;
01236 }
01237 
01238 
01240 //
01241 //
01242 //        METHOD NAME : MediaCurl::getDir
01243 //        METHOD TYPE : PMError
01244 //
01245 //        DESCRIPTION : Asserted that media is attached
01246 //
01247 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
01248 {
01249   filesystem::DirContent content;
01250   getDirInfo( content, dirname, /*dots*/false );
01251 
01252   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
01253       Pathname filename = dirname + it->name;
01254       int res = 0;
01255 
01256       switch ( it->type ) {
01257       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
01258       case filesystem::FT_FILE:
01259         getFile( filename );
01260         break;
01261       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
01262         if ( recurse_r ) {
01263           getDir( filename, recurse_r );
01264         } else {
01265           res = assert_dir( localPath( filename ) );
01266           if ( res ) {
01267             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
01268           }
01269         }
01270         break;
01271       default:
01272         // don't provide devices, sockets, etc.
01273         break;
01274       }
01275   }
01276 }
01277 
01279 //
01280 //
01281 //        METHOD NAME : MediaCurl::getDirInfo
01282 //        METHOD TYPE : PMError
01283 //
01284 //        DESCRIPTION : Asserted that media is attached and retlist is empty.
01285 //
01286 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
01287                                const Pathname & dirname, bool dots ) const
01288 {
01289   getDirectoryYast( retlist, dirname, dots );
01290 }
01291 
01293 //
01294 //
01295 //        METHOD NAME : MediaCurl::getDirInfo
01296 //        METHOD TYPE : PMError
01297 //
01298 //        DESCRIPTION : Asserted that media is attached and retlist is empty.
01299 //
01300 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
01301                             const Pathname & dirname, bool dots ) const
01302 {
01303   getDirectoryYast( retlist, dirname, dots );
01304 }
01305 
01307 //
01308 //
01309 //        METHOD NAME : MediaCurl::progressCallback
01310 //        METHOD TYPE : int
01311 //
01312 //        DESCRIPTION : Progress callback triggered from MediaCurl::getFile
01313 //
01314 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow,
01315                                  double ultotal, double ulnow )
01316 {
01317   ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
01318   if( pdata)
01319   {
01320     // send progress report first, abort transfer if requested
01321     if( pdata->report)
01322     {
01323       if (! (*(pdata->report))->progress(int( dlnow * 100 / dltotal ), pdata->url))
01324       {
01325         return 1; // abort transfer
01326       }
01327     }
01328 
01329     // check if we there is a timeout set
01330     if( pdata->timeout > 0)
01331     {
01332       time_t now = time(NULL);
01333       if( now > 0)
01334       {
01335         bool progress = false;
01336 
01337         // reset time of last change in case initial time()
01338         // failed or the time was adjusted (goes backward)
01339         if( pdata->ltime <= 0 || pdata->ltime > now)
01340           pdata->ltime = now;
01341 
01342         // update download data if changed, mark progress
01343         if( dlnow != pdata->dload)
01344         {
01345           progress     = true;
01346           pdata->dload = dlnow;
01347           pdata->ltime = now;
01348         }
01349         // update upload data if changed, mark progress
01350         if( ulnow != pdata->uload)
01351         {
01352           progress     = true;
01353           pdata->uload = ulnow;
01354           pdata->ltime = now;
01355         }
01356 
01357         if( !progress && (now >= (pdata->ltime + pdata->timeout)))
01358         {
01359           pdata->reached = true;
01360           return 1; // aborts transfer
01361         }
01362       }
01363     }
01364   }
01365   return 0;
01366 }
01367 
01368 
01369   } // namespace media
01370 } // namespace zypp

Generated on Thu Apr 24 02:24:49 2008 for zypp by  doxygen 1.4.6