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

Generated on Wed Feb 16 18:51:44 2011 for zypp by  doxygen 1.4.6