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   /*
00240   ** Connect timeout
00241   */
00242   ret = curl_easy_setopt( _curl, CURLOPT_CONNECTTIMEOUT, 60);
00243   if ( ret != 0 ) {
00244     disconnectFrom();
00245     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00246   }
00247 
00248   if ( _url.getScheme() == "http" ) {
00249     // follow any Location: header that the server sends as part of
00250     // an HTTP header (#113275)
00251     ret = curl_easy_setopt ( _curl, CURLOPT_FOLLOWLOCATION, true );
00252     if ( ret != 0) {
00253       disconnectFrom();
00254       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00255     }
00256     ret = curl_easy_setopt ( _curl, CURLOPT_MAXREDIRS, 3L );
00257     if ( ret != 0) {
00258       disconnectFrom();
00259       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00260     }
00261     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
00262     if ( ret != 0) {
00263       disconnectFrom();
00264       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00265     }
00266   }
00267 
00268   if ( _url.getScheme() == "https" ) {
00269     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYPEER, 1 );
00270     if ( ret != 0 ) {
00271       disconnectFrom();
00272       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00273     }
00274     ret = curl_easy_setopt( _curl, CURLOPT_CAPATH, "/etc/ssl/certs/" );
00275     if ( ret != 0 ) {
00276       disconnectFrom();
00277       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00278     }
00279     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYHOST, 2 );
00280     if ( ret != 0 ) {
00281       disconnectFrom();
00282       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00283     }
00284     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
00285     if ( ret != 0) {
00286       disconnectFrom();
00287       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00288     }
00289   }
00290 
00291 
00292   /*---------------------------------------------------------------*
00293    CURLOPT_USERPWD: [user name]:[password]
00294 
00295    Url::username/password -> CURLOPT_USERPWD
00296    If not provided, anonymous FTP identification
00297    *---------------------------------------------------------------*/
00298 
00299   if ( _url.getUsername().empty() ) {
00300     if ( _url.getScheme() == "ftp" ) {
00301       string id = "yast2@";
00302       id += VERSION;
00303       DBG << "Anonymous FTP identification: '" << id << "'" << endl;
00304       _userpwd = "anonymous:" + id;
00305     }
00306   } else {
00307     _userpwd = _url.getUsername();
00308     if ( _url.getPassword().size() ) {
00309       _userpwd += ":" + _url.getPassword();
00310     }
00311   }
00312 
00313   if ( _userpwd.size() ) {
00314     _userpwd = unEscape( _userpwd );
00315     ret = curl_easy_setopt( _curl, CURLOPT_USERPWD, _userpwd.c_str() );
00316     if ( ret != 0 ) {
00317       disconnectFrom();
00318       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00319     }
00320 
00321     if( !_url.getQueryParam("auth").empty() &&
00322         (_url.getScheme() == "http" || _url.getScheme() == "https"))
00323     {
00324       std::vector<std::string>                 list;
00325       std::vector<std::string>::const_iterator it;
00326       str::split(_url.getQueryParam("auth"), std::back_inserter(list), ",");
00327 
00328       long auth = CURLAUTH_NONE;
00329       for(it = list.begin(); it != list.end(); ++it)
00330       {
00331         if(*it == "basic")
00332         {
00333           auth |= CURLAUTH_BASIC;
00334         }
00335         else
00336         if(*it == "digest")
00337         {
00338           auth |= CURLAUTH_DIGEST;
00339         }
00340         else
00341         if((curl_info && (curl_info->features & CURL_VERSION_NTLM)) &&
00342            (*it == "ntlm"))
00343         {
00344           auth |= CURLAUTH_NTLM;
00345         }
00346         else
00347         if((curl_info && (curl_info->features & CURL_VERSION_SPNEGO)) &&
00348            (*it == "spnego" || *it == "negotiate"))
00349         {
00350           // there is no separate spnego flag for auth
00351           auth |= CURLAUTH_GSSNEGOTIATE;
00352         }
00353         else
00354         if((curl_info && (curl_info->features & CURL_VERSION_GSSNEGOTIATE)) &&
00355            (*it == "gssnego" || *it == "negotiate"))
00356         {
00357           auth |= CURLAUTH_GSSNEGOTIATE;
00358         }
00359         else
00360         {
00361           std::string msg("Unsupported HTTP authentication method '");
00362           msg += *it;
00363           msg += "'";
00364           disconnectFrom();
00365           ZYPP_THROW(MediaBadUrlException(_url, msg));
00366         }
00367       }
00368 
00369       if( auth != CURLAUTH_NONE)
00370       {
00371         DBG << "Enabling HTTP authentication methods: "
00372             << _url.getQueryParam("auth") << std::endl;
00373 
00374         ret = curl_easy_setopt( _curl, CURLOPT_HTTPAUTH, auth);
00375         if ( ret != 0 ) {
00376           disconnectFrom();
00377           ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00378         }
00379       }
00380     }
00381   }
00382 
00383   /*---------------------------------------------------------------*
00384    CURLOPT_PROXY: host[:port]
00385 
00386    Url::option(proxy and proxyport) -> CURLOPT_PROXY
00387    If not provided, /etc/sysconfig/proxy is evaluated
00388    *---------------------------------------------------------------*/
00389 
00390   _proxy = _url.getQueryParam( "proxy" );
00391 
00392   if ( ! _proxy.empty() ) {
00393     string proxyport( _url.getQueryParam( "proxyport" ) );
00394     if ( ! proxyport.empty() ) {
00395       _proxy += ":" + proxyport;
00396     }
00397   } else {
00398 
00399     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
00400 
00401     if ( proxy_info.enabled())
00402     {
00403       bool useproxy = true;
00404 
00405       std::list<std::string> nope = proxy_info.noProxy();
00406       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
00407            it != proxy_info.noProxyEnd();
00408            it++)
00409       {
00410         std::string host( str::toLower(_url.getHost()));
00411         std::string temp( str::toLower(*it));
00412 
00413         // no proxy if it points to a suffix
00414         // preceeded by a '.', that maches
00415         // the trailing portion of the host.
00416         if( temp.size() > 1 && temp.at(0) == '.')
00417         {
00418           if(host.size() > temp.size() &&
00419              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
00420           {
00421             DBG << "NO_PROXY: '" << *it  << "' matches host '"
00422                                  << host << "'" << endl;
00423             useproxy = false;
00424             break;
00425           }
00426         }
00427         else
00428         // no proxy if we have an exact match
00429         if( host == temp)
00430         {
00431           DBG << "NO_PROXY: '" << *it  << "' matches host '"
00432                                << host << "'" << endl;
00433           useproxy = false;
00434           break;
00435         }
00436       }
00437 
00438       if ( useproxy ) {
00439         _proxy = proxy_info.proxy(_url.getScheme());
00440       }
00441     }
00442   }
00443 
00444 
00445   DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
00446 
00447   if ( ! _proxy.empty() ) {
00448 
00449     ret = curl_easy_setopt( _curl, CURLOPT_PROXY, _proxy.c_str() );
00450     if ( ret != 0 ) {
00451       disconnectFrom();
00452       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00453     }
00454 
00455     /*---------------------------------------------------------------*
00456      CURLOPT_PROXYUSERPWD: [user name]:[password]
00457 
00458      Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
00459      If not provided, $HOME/.curlrc is evaluated
00460      *---------------------------------------------------------------*/
00461 
00462     _proxyuserpwd = _url.getQueryParam( "proxyuser" );
00463 
00464     if ( ! _proxyuserpwd.empty() ) {
00465 
00466       string proxypassword( _url.getQueryParam( "proxypassword" ) );
00467       if ( ! proxypassword.empty() ) {
00468         _proxyuserpwd += ":" + proxypassword;
00469       }
00470 
00471     } else {
00472       char *home = getenv("HOME");
00473       if( home && *home)
00474       {
00475         Pathname curlrcFile = string( home ) + string( "/.curlrc" );
00476 
00477         PathInfo h_info(string(home), PathInfo::LSTAT);
00478         PathInfo c_info(curlrcFile,   PathInfo::LSTAT);
00479 
00480         if( h_info.isDir()  && h_info.owner() == getuid() &&
00481             c_info.isFile() && c_info.owner() == getuid())
00482         {
00483           map<string,string> rc_data = proxyinfo::sysconfigRead(curlrcFile);
00484 
00485           map<string,string>::const_iterator it = rc_data.find("proxy-user");
00486           if (it != rc_data.end())
00487             _proxyuserpwd = it->second;
00488         }
00489         else
00490         {
00491           WAR << "Not allowed to parse '" << curlrcFile
00492               << "': bad file owner" << std::endl;
00493         }
00494       }
00495     }
00496 
00497     _proxyuserpwd = unEscape( _proxyuserpwd );
00498     ret = curl_easy_setopt( _curl, CURLOPT_PROXYUSERPWD, _proxyuserpwd.c_str() );
00499     if ( ret != 0 ) {
00500       disconnectFrom();
00501       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00502     }
00503   }
00504 
00505   /*---------------------------------------------------------------*
00506    *---------------------------------------------------------------*/
00507 
00508   _currentCookieFile = _cookieFile.asString();
00509 
00510   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEFILE,
00511                           _currentCookieFile.c_str() );
00512   if ( ret != 0 ) {
00513     disconnectFrom();
00514     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00515   }
00516 
00517   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEJAR,
00518                           _currentCookieFile.c_str() );
00519   if ( ret != 0 ) {
00520     disconnectFrom();
00521     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00522   }
00523 
00524   ret = curl_easy_setopt( _curl, CURLOPT_PROGRESSFUNCTION,
00525                           &progressCallback );
00526   if ( ret != 0 ) {
00527     disconnectFrom();
00528     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00529   }
00530 
00531   ret = curl_easy_setopt( _curl, CURLOPT_NOPROGRESS, false );
00532   if ( ret != 0 ) {
00533     disconnectFrom();
00534     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00535   }
00536 
00537   // FIXME: need a derived class to propelly compare url's
00538   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
00539   setMediaSource(media);
00540 }
00541 
00542 bool
00543 MediaCurl::checkAttachPoint(const Pathname &apoint) const
00544 {
00545   return MediaHandler::checkAttachPoint( apoint, true, true);
00546 }
00547 
00549 //
00550 //
00551 //      METHOD NAME : MediaCurl::disconnectFrom
00552 //      METHOD TYPE : PMError
00553 //
00554 void MediaCurl::disconnectFrom()
00555 {
00556   if ( _curl )
00557   {
00558     curl_easy_cleanup( _curl );
00559     _curl = NULL;
00560   }
00561 }
00562 
00564 //
00565 //
00566 //      METHOD NAME : MediaCurl::releaseFrom
00567 //      METHOD TYPE : PMError
00568 //
00569 //      DESCRIPTION : Asserted that media is attached.
00570 //
00571 void MediaCurl::releaseFrom( bool eject )
00572 {
00573   disconnect();
00574 }
00575 
00576 
00578 //
00579 //      METHOD NAME : MediaCurl::getFile
00580 //      METHOD TYPE : PMError
00581 //
00582 
00583 void MediaCurl::getFile( const Pathname & filename ) const
00584 {
00585     // Use absolute file name to prevent access of files outside of the
00586     // hierarchy below the attach point.
00587     getFileCopy(filename, localPath(filename).absolutename());
00588 }
00589 
00590 
00591 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
00592 {
00593   callback::SendReport<DownloadProgressReport> report;
00594 
00595   Url url( _url );
00596 
00597   try {
00598     doGetFileCopy(filename, target, report);
00599   }
00600   catch (MediaException & excpt_r)
00601   {
00602     // FIXME: this will not match the first URL
00603     // FIXME: error number fix
00604     report->finish(url, zypp::media::DownloadProgressReport::NOT_FOUND, excpt_r.msg());
00605     ZYPP_RETHROW(excpt_r);
00606   }
00607   report->finish(url, zypp::media::DownloadProgressReport::NO_ERROR, "");
00608 }
00609 
00610 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report) const
00611 {
00612     DBG << filename.asString() << endl;
00613 
00614     if(!_url.isValid())
00615       ZYPP_THROW(MediaBadUrlException(_url));
00616 
00617     if(_url.getHost().empty())
00618       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
00619 
00620     string path = _url.getPathName();
00621     if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
00622          filename.absolute() ) {
00623       // If url has a path with trailing slash, remove the leading slash from
00624       // the absolute file name
00625       path += filename.asString().substr( 1, filename.asString().size() - 1 );
00626     } else if ( filename.relative() ) {
00627       // Add trailing slash to path, if not already there
00628       if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
00629       // Remove "./" from begin of relative file name
00630       path += filename.asString().substr( 2, filename.asString().size() - 2 );
00631     } else {
00632       path += filename.asString();
00633     }
00634 
00635     Url url( _url );
00636     url.setPathName( path );
00637 
00638     Pathname dest = target.absolutename();
00639     if( assert_dir( dest.dirname() ) )
00640     {
00641       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
00642       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
00643     }
00644 
00645     DBG << "URL: " << url.asString() << endl;
00646     // Use URL without options and without username and passwd
00647     // (some proxies dislike them in the URL).
00648     // Curl seems to need the just scheme, hostname and a path;
00649     // the rest was already passed as curl options (in attachTo).
00650     Url curlUrl( url );
00651 
00652     // Use asString + url::ViewOptions instead?
00653     curlUrl.setUsername( "" );
00654     curlUrl.setPassword( "" );
00655     curlUrl.setPathParams( "" );
00656     curlUrl.setQueryString( "" );
00657     curlUrl.setFragment( "" );
00658 
00659     //
00660     // See also Bug #154197 and ftp url definition in RFC 1738:
00661     // The url "ftp://user@host/foo/bar/file" contains a path,
00662     // that is relative to the user's home.
00663     // The url "ftp://user@host//foo/bar/file" (or also with
00664     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
00665     // contains an absolute path.
00666     //
00667     string urlBuffer( curlUrl.asString());
00668     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
00669                                      urlBuffer.c_str() );
00670     if ( ret != 0 ) {
00671       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00672     }
00673 
00674     string destNew = target.asString() + ".new.zypp.XXXXXX";
00675     char *buf = ::strdup( destNew.c_str());
00676     if( !buf)
00677     {
00678       ERR << "out of memory for temp file name" << endl;
00679       ZYPP_THROW(MediaSystemException(
00680         url, "out of memory for temp file name"
00681       ));
00682     }
00683 
00684     int tmp_fd = ::mkstemp( buf );
00685     if( tmp_fd == -1)
00686     {
00687       free( buf);
00688       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
00689       ZYPP_THROW(MediaWriteException(destNew));
00690     }
00691     destNew = buf;
00692     free( buf);
00693 
00694     FILE *file = ::fdopen( tmp_fd, "w" );
00695     if ( !file ) {
00696       ::close( tmp_fd);
00697       filesystem::unlink( destNew );
00698       ERR << "fopen failed for file '" << destNew << "'" << endl;
00699       ZYPP_THROW(MediaWriteException(destNew));
00700     }
00701 
00702     DBG << "dest: " << dest << endl;
00703     DBG << "temp: " << destNew << endl;
00704 
00705     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
00706     if ( ret != 0 ) {
00707       ::fclose( file );
00708       filesystem::unlink( destNew );
00709       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00710     }
00711 
00712     // Set callback and perform.
00713     report->start(url, dest);
00714     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &report ) != 0 ) {
00715       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
00716     }
00717 
00718     ret = curl_easy_perform( _curl );
00719 
00720     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
00721       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
00722     }
00723 
00724     if ( ret != 0 ) {
00725       ::fclose( file );
00726       filesystem::unlink( destNew );
00727       ERR << "curl error: " << ret << ": " << _curlError << endl;
00728       std::string err;
00729       try {
00730        bool err_file_not_found = false;
00731        switch ( ret ) {
00732         case CURLE_UNSUPPORTED_PROTOCOL:
00733         case CURLE_URL_MALFORMAT:
00734         case CURLE_URL_MALFORMAT_USER:
00735           err = " Bad URL";
00736         case CURLE_HTTP_RETURNED_ERROR:
00737           {
00738             long httpReturnCode = 0;
00739             CURLcode infoRet = curl_easy_getinfo( _curl,
00740                                                   CURLINFO_RESPONSE_CODE,
00741                                                   &httpReturnCode );
00742             if ( infoRet == CURLE_OK ) {
00743               string msg = "HTTP response: " +
00744                            str::numstring( httpReturnCode );
00745               if ( httpReturnCode == 401 )
00746               {
00747                 err = " Login failed";
00748               }
00749               else
00750               if ( httpReturnCode == 404)
00751               {
00752                 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
00753               }
00754 
00755               msg += err;
00756               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00757               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00758             }
00759             else
00760             {
00761               string msg = "Unable to retrieve HTTP response:";
00762               msg += err;
00763               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00764               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00765             }
00766           }
00767           break;
00768         case CURLE_FTP_COULDNT_RETR_FILE:
00769         case CURLE_FTP_ACCESS_DENIED:
00770           err = "File not found";
00771           err_file_not_found = true;
00772           break;
00773         case CURLE_BAD_PASSWORD_ENTERED:
00774         case CURLE_FTP_USER_PASSWORD_INCORRECT:
00775           err = "Login failed";
00776           break;
00777         case CURLE_COULDNT_RESOLVE_PROXY:
00778         case CURLE_COULDNT_RESOLVE_HOST:
00779         case CURLE_COULDNT_CONNECT:
00780         case CURLE_FTP_CANT_GET_HOST:
00781           err = "Connection failed";
00782           break;
00783         case CURLE_WRITE_ERROR:
00784           err = "Write error";
00785           break;
00786         case CURLE_ABORTED_BY_CALLBACK:
00787           err = "User abort";
00788           break;
00789         case CURLE_SSL_PEER_CERTIFICATE:
00790         default:
00791           err = "Unrecognized error";
00792           break;
00793        }
00794        if( err_file_not_found)
00795        {
00796          ZYPP_THROW(MediaFileNotFoundException(_url, filename));
00797        }
00798        else
00799        {
00800          ZYPP_THROW(MediaCurlException(url, err, _curlError));
00801        }
00802       }
00803       catch (const MediaException & excpt_r)
00804       {
00805         ZYPP_RETHROW(excpt_r);
00806       }
00807     }
00808 #if DETECT_DIR_INDEX
00809     else
00810     if(curlUrl.getScheme() == "http" ||
00811        curlUrl.getScheme() == "https")
00812     {
00813       //
00814       // try to check the effective url and set the not_a_file flag
00815       // if the url path ends with a "/", what usually means, that
00816       // we've received a directory index (index.html content).
00817       //
00818       // Note: This may be dangerous and break file retrieving in
00819       //       case of some server redirections ... ?
00820       //
00821       bool      not_a_file = false;
00822       char     *ptr = NULL;
00823       CURLcode  ret = curl_easy_getinfo( _curl,
00824                                          CURLINFO_EFFECTIVE_URL,
00825                                          &ptr);
00826       if ( ret == CURLE_OK && ptr != NULL)
00827       {
00828         try
00829         {
00830           Url         eurl( ptr);
00831           std::string path( eurl.getPathName());
00832           if( !path.empty() && path != "/" && *path.rbegin() == '/')
00833           {
00834             DBG << "Effective url ("
00835                 << eurl
00836                 << ") seems to provide the index of a directory"
00837                 << endl;
00838             not_a_file = true;
00839           }
00840         }
00841         catch( ... )
00842         {}
00843       }
00844 
00845       if( not_a_file)
00846       {
00847         ::fclose( file );
00848         filesystem::unlink( destNew );
00849         ZYPP_THROW(MediaNotAFileException(_url, filename));
00850       }
00851     }
00852 #endif // DETECT_DIR_INDEX
00853 
00854     mode_t mask;
00855     // getumask() would be fine, but does not exist
00856     // [ the linker can't find it in glibc :-( ].
00857     mask = ::umask(0022); ::umask(mask);
00858     if ( ::fchmod( ::fileno(file), 0644 & ~mask))
00859     {
00860       ERR << "Failed to chmod file " << destNew << endl;
00861     }
00862     ::fclose( file );
00863 
00864     if ( rename( destNew, dest ) != 0 ) {
00865       ERR << "Rename failed" << endl;
00866       ZYPP_THROW(MediaWriteException(dest));
00867     }
00868 }
00869 
00870 
00872 //
00873 //
00874 //      METHOD NAME : MediaCurl::getDir
00875 //      METHOD TYPE : PMError
00876 //
00877 //      DESCRIPTION : Asserted that media is attached
00878 //
00879 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
00880 {
00881   filesystem::DirContent content;
00882   getDirInfo( content, dirname, /*dots*/false );
00883 
00884   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
00885       Pathname filename = dirname + it->name;
00886       int res = 0;
00887 
00888       switch ( it->type ) {
00889       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
00890       case filesystem::FT_FILE:
00891         getFile( filename );
00892         break;
00893       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
00894         if ( recurse_r ) {
00895           getDir( filename, recurse_r );
00896         } else {
00897           res = assert_dir( localPath( filename ) );
00898           if ( res ) {
00899             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
00900           }
00901         }
00902         break;
00903       default:
00904         // don't provide devices, sockets, etc.
00905         break;
00906       }
00907   }
00908 }
00909 
00911 //
00912 //
00913 //      METHOD NAME : MediaCurl::getDirInfo
00914 //      METHOD TYPE : PMError
00915 //
00916 //      DESCRIPTION : Asserted that media is attached and retlist is empty.
00917 //
00918 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
00919                                const Pathname & dirname, bool dots ) const
00920 {
00921   getDirectoryYast( retlist, dirname, dots );
00922 }
00923 
00925 //
00926 //
00927 //      METHOD NAME : MediaCurl::getDirInfo
00928 //      METHOD TYPE : PMError
00929 //
00930 //      DESCRIPTION : Asserted that media is attached and retlist is empty.
00931 //
00932 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
00933                             const Pathname & dirname, bool dots ) const
00934 {
00935   getDirectoryYast( retlist, dirname, dots );
00936 }
00937 
00939 //
00940 //
00941 //      METHOD NAME : MediaCurl::progressCallback
00942 //      METHOD TYPE : int
00943 //
00944 //      DESCRIPTION : Progress callback triggered from MediaCurl::getFile
00945 //
00946 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow,
00947                                  double ultotal, double ulnow )
00948 {
00949   callback::SendReport<DownloadProgressReport> *report
00950     = reinterpret_cast<callback::SendReport<DownloadProgressReport>*>( clientp );
00951   if (report)
00952   {
00953     // FIXME: empty URL
00954     if (! (*report)->progress(int( dlnow * 100 / dltotal ), Url() ))
00955       return 1;
00956   }
00957   return 0;
00958 }
00959 
00960 
00961   } // namespace media
00962 } // namespace zypp

Generated on Mon Jun 5 19:10:32 2006 for zypp by  doxygen 1.4.6