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

Generated on Thu May 4 16:03:22 2006 for zypp by  doxygen 1.4.6