MediaCurl.cc

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

Generated on Thu Jul 6 00:07:21 2006 for zypp by  doxygen 1.4.6