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

Generated on Fri Jul 4 16:57:57 2008 for zypp by  doxygen 1.5.0