00001
00002
00003
00004
00005
00006
00007
00008
00013 #include <iostream>
00014 #include <list>
00015
00016 #include "zypp/base/Logger.h"
00017 #include "zypp/ExternalProgram.h"
00018 #include "zypp/base/String.h"
00019 #include "zypp/base/Gettext.h"
00020 #include "zypp/base/Sysconfig.h"
00021 #include "zypp/base/Gettext.h"
00022
00023 #include "zypp/media/MediaCurl.h"
00024 #include "zypp/media/proxyinfo/ProxyInfos.h"
00025 #include "zypp/media/ProxyInfo.h"
00026 #include "zypp/media/MediaUserAuth.h"
00027 #include "zypp/media/CredentialManager.h"
00028 #include "zypp/media/CurlConfig.h"
00029 #include "zypp/thread/Once.h"
00030 #include "zypp/Target.h"
00031 #include "zypp/ZYppFactory.h"
00032
00033 #include <cstdlib>
00034 #include <sys/types.h>
00035 #include <sys/stat.h>
00036 #include <sys/mount.h>
00037 #include <errno.h>
00038 #include <dirent.h>
00039 #include <unistd.h>
00040 #include <boost/format.hpp>
00041
00042 #define DETECT_DIR_INDEX 0
00043 #define CONNECT_TIMEOUT 60
00044 #define TRANSFER_TIMEOUT 60 * 3
00045 #define TRANSFER_TIMEOUT_MAX 60 * 60
00046
00047
00048 using namespace std;
00049 using namespace zypp::base;
00050
00051 namespace
00052 {
00053 zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
00054 zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
00055
00056 extern "C" void _do_free_once()
00057 {
00058 curl_global_cleanup();
00059 }
00060
00061 extern "C" void globalFreeOnce()
00062 {
00063 zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
00064 }
00065
00066 extern "C" void _do_init_once()
00067 {
00068 CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
00069 if ( ret != 0 )
00070 {
00071 WAR << "curl global init failed" << endl;
00072 }
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082 }
00083
00084 inline void globalInitOnce()
00085 {
00086 zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
00087 }
00088
00089 int log_curl(CURL *curl, curl_infotype info,
00090 char *ptr, size_t len, void *max_lvl)
00091 {
00092 std::string pfx(" ");
00093 long lvl = 0;
00094 switch( info)
00095 {
00096 case CURLINFO_TEXT: lvl = 1; pfx = "*"; break;
00097 case CURLINFO_HEADER_IN: lvl = 2; pfx = "<"; break;
00098 case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
00099 default: break;
00100 }
00101 if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
00102 {
00103 std::string msg(ptr, len);
00104 std::list<std::string> lines;
00105 std::list<std::string>::const_iterator line;
00106 zypp::str::split(msg, std::back_inserter(lines), "\r\n");
00107 for(line = lines.begin(); line != lines.end(); ++line)
00108 {
00109 DBG << pfx << " " << *line << endl;
00110 }
00111 }
00112 return 0;
00113 }
00114
00115 static size_t
00116 log_redirects_curl(
00117 void *ptr, size_t size, size_t nmemb, void *stream)
00118 {
00119
00120
00121 char * lstart = (char *)ptr, * lend = (char *)ptr;
00122 size_t pos = 0;
00123 size_t max = size * nmemb;
00124 while (pos + 1 < max)
00125 {
00126
00127 for (lstart = lend; *lend != '\n' && pos < max; ++lend, ++pos);
00128
00129
00130 string line(lstart, lend);
00131 if (line.find("Location") != string::npos)
00132 {
00133 DBG << "redirecting to " << line << endl;
00134 return max;
00135 }
00136
00137
00138 if (pos + 1 < max)
00139 {
00140 ++lend;
00141 ++pos;
00142 }
00143 else
00144 break;
00145 }
00146
00147 return max;
00148 }
00149 }
00150
00151 namespace zypp {
00152 namespace media {
00153
00154 namespace {
00155 struct ProgressData
00156 {
00157 ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
00158 callback::SendReport<DownloadProgressReport> *_report=NULL)
00159 : timeout(_timeout)
00160 , reached(false)
00161 , report(_report)
00162 , drate_period(-1)
00163 , dload_period(0)
00164 , secs(0)
00165 , drate_avg(-1)
00166 , ltime( time(NULL))
00167 , dload( 0)
00168 , uload( 0)
00169 , url(_url)
00170 {}
00171 long timeout;
00172 bool reached;
00173 callback::SendReport<DownloadProgressReport> *report;
00174
00175 double drate_period;
00176
00177 double dload_period;
00178
00179 long secs;
00180
00181 double drate_avg;
00182
00183 time_t ltime;
00184
00185 double dload;
00186
00187 double uload;
00188 zypp::Url url;
00189 };
00190
00192
00193 inline void escape( string & str_r,
00194 const char char_r, const string & escaped_r ) {
00195 for ( string::size_type pos = str_r.find( char_r );
00196 pos != string::npos; pos = str_r.find( char_r, pos ) ) {
00197 str_r.replace( pos, 1, escaped_r );
00198 }
00199 }
00200
00201 inline string escapedPath( string path_r ) {
00202 escape( path_r, ' ', "%20" );
00203 return path_r;
00204 }
00205
00206 inline string unEscape( string text_r ) {
00207 char * tmp = curl_unescape( text_r.c_str(), 0 );
00208 string ret( tmp );
00209 curl_free( tmp );
00210 return ret;
00211 }
00212
00213 }
00214
00219 void fillSettingsFromUrl( const Url &url, TransferSettings &s )
00220 {
00221 std::string param(url.getQueryParam("timeout"));
00222 if( !param.empty())
00223 {
00224 long num = str::strtonum<long>(param);
00225 if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
00226 s.setTimeout(num);
00227 }
00228
00229 if ( ! url.getUsername().empty() )
00230 {
00231 s.setUsername(url.getUsername());
00232 if ( url.getPassword().size() )
00233 s.setPassword(url.getPassword());
00234 }
00235 else
00236 {
00237
00238 if ( url.getScheme() == "ftp" && s.username().empty() )
00239 s.setAnonymousAuth();
00240 }
00241
00242 if ( url.getScheme() == "https" )
00243 {
00244 s.setVerifyPeerEnabled(false);
00245 s.setVerifyHostEnabled(false);
00246
00247 std::string verify( url.getQueryParam("ssl_verify"));
00248 if( verify.empty() ||
00249 verify == "yes")
00250 {
00251 s.setVerifyPeerEnabled(true);
00252 s.setVerifyHostEnabled(true);
00253 }
00254 else if( verify == "no")
00255 {
00256 s.setVerifyPeerEnabled(false);
00257 s.setVerifyHostEnabled(false);
00258 }
00259 else
00260 {
00261 std::vector<std::string> flags;
00262 std::vector<std::string>::const_iterator flag;
00263 str::split( verify, std::back_inserter(flags), ",");
00264 for(flag = flags.begin(); flag != flags.end(); ++flag)
00265 {
00266 if( *flag == "host")
00267 s.setVerifyHostEnabled(true);
00268 else if( *flag == "peer")
00269 s.setVerifyPeerEnabled(true);
00270 else
00271 ZYPP_THROW(MediaBadUrlException(url, "Unknown ssl_verify flag"));
00272 }
00273 }
00274 }
00275
00276 Pathname ca_path = Pathname(url.getQueryParam("ssl_capath")).asString();
00277 if( ! ca_path.empty())
00278 {
00279 if( !PathInfo(ca_path).isDir() || !Pathname(ca_path).absolute())
00280 ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_capath path"));
00281 else
00282 s.setCertificateAuthoritiesPath(ca_path);
00283 }
00284
00285 string proxy = url.getQueryParam( "proxy" );
00286 if ( ! proxy.empty() )
00287 {
00288 string proxyport( url.getQueryParam( "proxyport" ) );
00289 if ( ! proxyport.empty() ) {
00290 proxy += ":" + proxyport;
00291 }
00292 s.setProxy(proxy);
00293 s.setProxyEnabled(true);
00294 }
00295 }
00296
00301 void fillSettingsSystemProxy( const Url&url, TransferSettings &s )
00302 {
00303 ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
00304 s.setProxyEnabled( proxy_info.useProxyFor( url ) );
00305 if ( s.proxyEnabled() )
00306 s.setProxy(proxy_info.proxy(url.getScheme()));
00307 }
00308
00309 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
00310
00315 static const char *const anonymousIdHeader()
00316 {
00317
00318
00319
00320
00321 static const std::string _value(
00322 str::trim( str::form(
00323 "X-ZYpp-AnonymousId: %s",
00324 Target::anonymousUniqueId( Pathname() ).c_str() ) )
00325 );
00326 return _value.c_str();
00327 }
00328
00333 static const char *const distributionFlavorHeader()
00334 {
00335
00336
00337
00338
00339 static const std::string _value(
00340 str::trim( str::form(
00341 "X-ZYpp-DistributionFlavor: %s",
00342 Target::distributionFlavor( Pathname() ).c_str() ) )
00343 );
00344 return _value.c_str();
00345 }
00346
00351 static const char *const agentString()
00352 {
00353
00354
00355
00356
00357 static const std::string _value(
00358 str::form(
00359 "ZYpp %s (curl %s) %s"
00360 , VERSION
00361 , curl_version_info(CURLVERSION_NOW)->version
00362 , Target::targetDistribution( Pathname() ).c_str()
00363 )
00364 );
00365 return _value.c_str();
00366 }
00367
00368
00369
00371 #define SET_OPTION(opt,val) do { \
00372 ret = curl_easy_setopt ( _curl, opt, val ); \
00373 if ( ret != 0) { \
00374 disconnectFrom(); \
00375 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); \
00376 } \
00377 } while ( false )
00378
00379 #define SET_OPTION_OFFT(opt,val) SET_OPTION(opt,(curl_off_t)val)
00380 #define SET_OPTION_LONG(opt,val) SET_OPTION(opt,(LONG)val)
00381 #define SET_OPTION_VOID(opt,val) SET_OPTION(opt,(void*)val)
00382
00383 MediaCurl::MediaCurl( const Url & url_r,
00384 const Pathname & attach_point_hint_r )
00385 : MediaHandler( url_r, attach_point_hint_r,
00386 "/",
00387 true ),
00388 _curl( NULL ),
00389 _customHeaders(0L)
00390 {
00391 _curlError[0] = '\0';
00392 _curlDebug = 0L;
00393
00394 MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
00395
00396 globalInitOnce();
00397
00398 if( !attachPoint().empty())
00399 {
00400 PathInfo ainfo(attachPoint());
00401 Pathname apath(attachPoint() + "XXXXXX");
00402 char *atemp = ::strdup( apath.asString().c_str());
00403 char *atest = NULL;
00404 if( !ainfo.isDir() || !ainfo.userMayRWX() ||
00405 atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
00406 {
00407 WAR << "attach point " << ainfo.path()
00408 << " is not useable for " << url_r.getScheme() << endl;
00409 setAttachPoint("", true);
00410 }
00411 else if( atest != NULL)
00412 ::rmdir(atest);
00413
00414 if( atemp != NULL)
00415 ::free(atemp);
00416 }
00417 }
00418
00419 void MediaCurl::setCookieFile( const Pathname &fileName )
00420 {
00421 _cookieFile = fileName;
00422 }
00423
00425
00426 void MediaCurl::attachTo (bool next)
00427 {
00428 if ( next )
00429 ZYPP_THROW(MediaNotSupportedException(_url));
00430
00431 if ( !_url.isValid() )
00432 ZYPP_THROW(MediaBadUrlException(_url));
00433
00434 CurlConfig curlconf;
00435 CurlConfig::parseConfig(curlconf);
00436
00437 curl_version_info_data *curl_info = NULL;
00438 curl_info = curl_version_info(CURLVERSION_NOW);
00439
00440 if (curl_info->protocols)
00441 {
00442 const char * const *proto;
00443 std::string scheme( _url.getScheme());
00444 bool found = false;
00445 for(proto=curl_info->protocols; !found && *proto; ++proto)
00446 {
00447 if( scheme == std::string((const char *)*proto))
00448 found = true;
00449 }
00450 if( !found)
00451 {
00452 std::string msg("Unsupported protocol '");
00453 msg += scheme;
00454 msg += "'";
00455 ZYPP_THROW(MediaBadUrlException(_url, msg));
00456 }
00457 }
00458
00459 if( !isUseableAttachPoint(attachPoint()))
00460 {
00461 std::string mountpoint = createAttachPoint().asString();
00462
00463 if( mountpoint.empty())
00464 ZYPP_THROW( MediaBadAttachPointException(url()));
00465
00466 setAttachPoint( mountpoint, true);
00467 }
00468
00469 disconnectFrom();
00470 _curl = curl_easy_init();
00471 if ( !_curl ) {
00472 ZYPP_THROW(MediaCurlInitException(_url));
00473 }
00474
00475 {
00476 char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
00477 _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
00478 if( _curlDebug > 0)
00479 {
00480 curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1);
00481 curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
00482 curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
00483 }
00484 }
00485
00486 curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, log_redirects_curl);
00487
00488 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
00489 if ( ret != 0 ) {
00490 disconnectFrom();
00491 ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
00492 }
00493
00494 SET_OPTION(CURLOPT_FAILONERROR,true);
00495 SET_OPTION(CURLOPT_NOSIGNAL, 1);
00496
00497
00498 _settings.reset();
00499
00500
00501 _settings.addHeader(anonymousIdHeader());
00502 _settings.addHeader(distributionFlavorHeader());
00503 _settings.addHeader("Pragma:");
00504
00505 _settings.setTimeout(TRANSFER_TIMEOUT);
00506 _settings.setConnectTimeout(CONNECT_TIMEOUT);
00507
00508 _settings.setUserAgentString(agentString());
00509
00510
00511 try
00512 {
00513 fillSettingsFromUrl(_url, _settings);
00514 }
00515 catch ( const MediaException &e )
00516 {
00517 disconnectFrom();
00518 ZYPP_RETHROW(e);
00519 }
00520
00521
00522 if ( _settings.proxy().empty() )
00523 {
00524
00525 fillSettingsSystemProxy(_url, _settings);
00526 }
00527
00528 DBG << "Proxy: " << (_settings.proxy().empty() ? "-none-" : _settings.proxy()) << endl;
00529
00533 SET_OPTION(CURLOPT_CONNECTTIMEOUT, _settings.connectTimeout());
00534
00535
00536
00537 SET_OPTION(CURLOPT_FOLLOWLOCATION, true);
00538
00539 SET_OPTION(CURLOPT_MAXREDIRS, 6L);
00540
00541 if ( _url.getScheme() == "https" )
00542 {
00543
00544 SET_OPTION( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS );
00545
00546 if( _settings.verifyPeerEnabled() ||
00547 _settings.verifyHostEnabled() )
00548 {
00549 SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str());
00550 }
00551
00552 SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L);
00553 SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L);
00554 }
00555
00556 SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
00557
00558
00559
00560
00561
00562
00563
00564
00565 if ( _settings.userPassword().size() )
00566 {
00567 SET_OPTION(CURLOPT_USERPWD, unEscape(_settings.userPassword()).c_str());
00568
00569
00570
00571
00572
00573 if(_url.getScheme() == "http" || _url.getScheme() == "https")
00574 {
00575 string use_auth = _url.getQueryParam("auth");
00576 if( use_auth.empty())
00577 use_auth = "digest,basic";
00578
00579 try
00580 {
00581 long auth = CurlAuthData::auth_type_str2long(use_auth);
00582 if( auth != CURLAUTH_NONE)
00583 {
00584 DBG << "Enabling HTTP authentication methods: " << use_auth
00585 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
00586
00587 SET_OPTION(CURLOPT_HTTPAUTH, auth);
00588 }
00589 }
00590 catch (MediaException & ex_r)
00591 {
00592 string auth_hint = getAuthHint();
00593
00594 DBG << "Rethrowing as MediaUnauthorizedException. auth hint: '"
00595 << auth_hint << "'" << endl;
00596
00597 ZYPP_THROW(MediaUnauthorizedException(
00598 _url, ex_r.msg(), _curlError, auth_hint
00599 ));
00600 }
00601 }
00602 }
00603
00604 if ( _settings.proxyEnabled() )
00605 {
00606 if ( ! _settings.proxy().empty() )
00607 {
00608 SET_OPTION(CURLOPT_PROXY, _settings.proxy().c_str());
00609
00610
00611
00612
00613
00614
00615
00616 string proxyuserpwd = _settings.proxyUserPassword();
00617
00618 if ( proxyuserpwd.empty() )
00619 {
00620 if (curlconf.proxyuserpwd.empty())
00621 DBG << "~/.curlrc does not contain the proxy-user option" << endl;
00622 else
00623 {
00624 proxyuserpwd = curlconf.proxyuserpwd;
00625 DBG << "using proxy-user from ~/.curlrc" << endl;
00626 }
00627 }
00628
00629 proxyuserpwd = unEscape( proxyuserpwd );
00630 if ( ! proxyuserpwd.empty() )
00631 SET_OPTION(CURLOPT_PROXYUSERPWD, proxyuserpwd.c_str());
00632 }
00633 }
00634
00636 if ( _settings.minDownloadSpeed() != 0 )
00637 {
00638 SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed());
00639
00640 SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10);
00641 }
00642
00643 if ( _settings.maxDownloadSpeed() != 0 )
00644 SET_OPTION_OFFT(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed());
00645
00646
00647
00648
00649 _currentCookieFile = _cookieFile.asString();
00650 if ( str::strToBool( _url.getQueryParam( "cookies" ), true ) )
00651 SET_OPTION(CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
00652 else
00653 MIL << "No cookies requested" << endl;
00654 SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
00655 SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback );
00656 SET_OPTION(CURLOPT_NOPROGRESS, false );
00657
00658
00659 SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1 );
00660
00661
00662 for ( TransferSettings::Headers::const_iterator it = _settings.headersBegin();
00663 it != _settings.headersEnd();
00664 ++it )
00665 {
00666
00667 _customHeaders = curl_slist_append(_customHeaders, it->c_str());
00668 if ( !_customHeaders )
00669 ZYPP_THROW(MediaCurlInitException(_url));
00670 }
00671
00672 SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders);
00673
00674
00675 MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
00676 setMediaSource(media);
00677 }
00678
00679 bool
00680 MediaCurl::checkAttachPoint(const Pathname &apoint) const
00681 {
00682 return MediaHandler::checkAttachPoint( apoint, true, true);
00683 }
00684
00686
00687 void MediaCurl::disconnectFrom()
00688 {
00689 if ( _customHeaders )
00690 {
00691 curl_slist_free_all(_customHeaders);
00692 _customHeaders = 0L;
00693 }
00694
00695 if ( _curl )
00696 {
00697 curl_easy_cleanup( _curl );
00698 _curl = NULL;
00699 }
00700 }
00701
00703
00704 void MediaCurl::releaseFrom( const std::string & ejectDev )
00705 {
00706 disconnect();
00707 }
00708
00709 static Url getFileUrl(const Url & url, const Pathname & filename)
00710 {
00711 Url newurl(url);
00712 string path = url.getPathName();
00713 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
00714 filename.absolute() )
00715 {
00716
00717
00718 path += filename.asString().substr( 1, filename.asString().size() - 1 );
00719 }
00720 else if ( filename.relative() )
00721 {
00722
00723 if (path.empty()) path = "/";
00724 else if (*path.rbegin() != '/' ) path += "/";
00725
00726 path += filename.asString().substr( 2, filename.asString().size() - 2 );
00727 }
00728 else
00729 {
00730 path += filename.asString();
00731 }
00732
00733 newurl.setPathName(path);
00734 return newurl;
00735 }
00736
00738
00739 void MediaCurl::getFile( const Pathname & filename ) const
00740 {
00741
00742
00743 getFileCopy(filename, localPath(filename).absolutename());
00744 }
00745
00747
00748 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
00749 {
00750 callback::SendReport<DownloadProgressReport> report;
00751
00752 Url fileurl(getFileUrl(_url, filename));
00753
00754 bool retry = false;
00755
00756 do
00757 {
00758 try
00759 {
00760 doGetFileCopy(filename, target, report);
00761 retry = false;
00762 }
00763
00764 catch (MediaUnauthorizedException & ex_r)
00765 {
00766 if(authenticate(ex_r.hint(), !retry))
00767 retry = true;
00768 else
00769 {
00770 report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
00771 ZYPP_RETHROW(ex_r);
00772 }
00773 }
00774
00775 catch (MediaException & excpt_r)
00776 {
00777
00778 report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
00779 ZYPP_RETHROW(excpt_r);
00780 }
00781 }
00782 while (retry);
00783
00784 report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
00785 }
00786
00788
00789 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
00790 {
00791 bool retry = false;
00792
00793 do
00794 {
00795 try
00796 {
00797 return doGetDoesFileExist( filename );
00798 }
00799
00800 catch (MediaUnauthorizedException & ex_r)
00801 {
00802 if(authenticate(ex_r.hint(), !retry))
00803 retry = true;
00804 else
00805 ZYPP_RETHROW(ex_r);
00806 }
00807
00808 catch (MediaException & excpt_r)
00809 {
00810 ZYPP_RETHROW(excpt_r);
00811 }
00812 }
00813 while (retry);
00814
00815 return false;
00816 }
00817
00819
00820 void MediaCurl::evaluateCurlCode( const Pathname &filename,
00821 CURLcode code,
00822 bool timeout_reached ) const
00823 {
00824 Url url(getFileUrl(_url, filename));
00825
00826 if ( code != 0 )
00827 {
00828 std::string err;
00829 try
00830 {
00831 switch ( code )
00832 {
00833 case CURLE_UNSUPPORTED_PROTOCOL:
00834 case CURLE_URL_MALFORMAT:
00835 case CURLE_URL_MALFORMAT_USER:
00836 err = " Bad URL";
00837 break;
00838 case CURLE_LOGIN_DENIED:
00839 ZYPP_THROW(
00840 MediaUnauthorizedException(url, "Login failed.", _curlError, ""));
00841 break;
00842 case CURLE_HTTP_RETURNED_ERROR:
00843 {
00844 long httpReturnCode = 0;
00845 CURLcode infoRet = curl_easy_getinfo( _curl,
00846 CURLINFO_RESPONSE_CODE,
00847 &httpReturnCode );
00848 if ( infoRet == CURLE_OK )
00849 {
00850 string msg = "HTTP response: " + str::numstring( httpReturnCode );
00851 switch ( httpReturnCode )
00852 {
00853 case 401:
00854 {
00855 string auth_hint = getAuthHint();
00856
00857 DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
00858 DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
00859
00860 ZYPP_THROW(MediaUnauthorizedException(
00861 url, "Login failed.", _curlError, auth_hint
00862 ));
00863 }
00864
00865 case 503:
00866 ZYPP_THROW(MediaTemporaryProblemException(url));
00867 case 504:
00868 ZYPP_THROW(MediaTimeoutException(url));
00869 case 403:
00870 {
00871 string msg403;
00872 if (url.asString().find("novell.com") != string::npos)
00873 msg403 = _("Visit the Novell Customer Center to check whether your registration is valid and has not expired.");
00874 ZYPP_THROW(MediaForbiddenException(url, msg403));
00875 }
00876 case 404:
00877 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
00878 }
00879
00880 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00881 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00882 }
00883 else
00884 {
00885 string msg = "Unable to retrieve HTTP response:";
00886 DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00887 ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00888 }
00889 }
00890 break;
00891 case CURLE_FTP_COULDNT_RETR_FILE:
00892 case CURLE_REMOTE_FILE_NOT_FOUND:
00893 case CURLE_FTP_ACCESS_DENIED:
00894 err = "File not found";
00895 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
00896 break;
00897 case CURLE_BAD_PASSWORD_ENTERED:
00898 case CURLE_FTP_USER_PASSWORD_INCORRECT:
00899 err = "Login failed";
00900 break;
00901 case CURLE_COULDNT_RESOLVE_PROXY:
00902 case CURLE_COULDNT_RESOLVE_HOST:
00903 case CURLE_COULDNT_CONNECT:
00904 case CURLE_FTP_CANT_GET_HOST:
00905 err = "Connection failed";
00906 break;
00907 case CURLE_WRITE_ERROR:
00908 err = "Write error";
00909 break;
00910 case CURLE_PARTIAL_FILE:
00911 case CURLE_ABORTED_BY_CALLBACK:
00912 case CURLE_OPERATION_TIMEDOUT:
00913 if( timeout_reached)
00914 {
00915 err = "Timeout reached";
00916 ZYPP_THROW(MediaTimeoutException(url));
00917 }
00918 else
00919 {
00920 err = "User abort";
00921 }
00922 break;
00923 case CURLE_SSL_PEER_CERTIFICATE:
00924 default:
00925 err = "Unrecognized error";
00926 break;
00927 }
00928
00929
00930 ZYPP_THROW(MediaCurlException(url, err, _curlError));
00931 }
00932 catch (const MediaException & excpt_r)
00933 {
00934 ZYPP_RETHROW(excpt_r);
00935 }
00936 }
00937 else
00938 {
00939
00940 }
00941 }
00942
00944
00945 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
00946 {
00947 DBG << filename.asString() << endl;
00948
00949 if(!_url.isValid())
00950 ZYPP_THROW(MediaBadUrlException(_url));
00951
00952 if(_url.getHost().empty())
00953 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
00954
00955 Url url(getFileUrl(_url, filename));
00956
00957 DBG << "URL: " << url.asString() << endl;
00958
00959
00960
00961
00962 Url curlUrl( url );
00963
00964
00965 curlUrl.setUsername( "" );
00966 curlUrl.setPassword( "" );
00967 curlUrl.setPathParams( "" );
00968 curlUrl.setQueryString( "" );
00969 curlUrl.setFragment( "" );
00970
00971
00972
00973
00974
00975
00976
00977
00978
00979 string urlBuffer( curlUrl.asString());
00980 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
00981 urlBuffer.c_str() );
00982 if ( ret != 0 ) {
00983 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00984 }
00985
00986
00987
00988
00989
00990
00991 if ( _url.getScheme() == "http" || _url.getScheme() == "https" )
00992 ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 );
00993 else
00994 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
00995
00996 if ( ret != 0 ) {
00997 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
00998 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
00999
01000
01001
01002
01003
01004 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
01005 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01006 }
01007
01008 FILE *file = ::fopen( "/dev/null", "w" );
01009 if ( !file ) {
01010 ERR << "fopen failed for /dev/null" << endl;
01011 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
01012 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
01013
01014
01015
01016
01017
01018 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
01019 if ( ret != 0 ) {
01020 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01021 }
01022 ZYPP_THROW(MediaWriteException("/dev/null"));
01023 }
01024
01025 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
01026 if ( ret != 0 ) {
01027 ::fclose(file);
01028 std::string err( _curlError);
01029 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
01030 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
01031
01032
01033
01034
01035
01036 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
01037 if ( ret != 0 ) {
01038 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01039 }
01040 ZYPP_THROW(MediaCurlSetOptException(url, err));
01041 }
01042
01043 CURLcode ok = curl_easy_perform( _curl );
01044 MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
01045
01046
01047 if ( _url.getScheme() == "http" || _url.getScheme() == "https" )
01048 {
01049 curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
01050 if ( ret != 0 ) {
01051 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01052 }
01053
01054
01055
01056
01057
01058
01059 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1);
01060 if ( ret != 0 ) {
01061 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01062 }
01063
01064 }
01065 else
01066 {
01067
01068 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL);
01069 if ( ret != 0 ) {
01070 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01071 }
01072 }
01073
01074
01075 if ( ok != 0 )
01076 ::fclose(file);
01077
01078
01079
01080
01081 try {
01082 evaluateCurlCode( filename, ok, true );
01083 }
01084 catch ( const MediaFileNotFoundException &e ) {
01085
01086 return false;
01087 }
01088 catch ( const MediaException &e ) {
01089
01090 ZYPP_RETHROW(e);
01091 }
01092
01093 return ( ok == CURLE_OK );
01094 }
01095
01097
01098 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
01099 {
01100 DBG << filename.asString() << endl;
01101
01102 if(!_url.isValid())
01103 ZYPP_THROW(MediaBadUrlException(_url));
01104
01105 if(_url.getHost().empty())
01106 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
01107
01108 Url url(getFileUrl(_url, filename));
01109
01110 Pathname dest = target.absolutename();
01111 if( assert_dir( dest.dirname() ) )
01112 {
01113 DBG << "assert_dir " << dest.dirname() << " failed" << endl;
01114 ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
01115 }
01116
01117 DBG << "URL: " << url.asString() << endl;
01118
01119
01120
01121
01122 Url curlUrl( url );
01123
01124
01125 curlUrl.setUsername( "" );
01126 curlUrl.setPassword( "" );
01127 curlUrl.setPathParams( "" );
01128 curlUrl.setQueryString( "" );
01129 curlUrl.setFragment( "" );
01130
01131
01132
01133
01134
01135
01136
01137
01138
01139 string urlBuffer( curlUrl.asString());
01140 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
01141 urlBuffer.c_str() );
01142 if ( ret != 0 ) {
01143 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01144 }
01145
01146
01147 if( PathInfo(target).isExist() )
01148 {
01149 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
01150 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, PathInfo(target).mtime());
01151 }
01152 else
01153 {
01154 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
01155 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0);
01156 }
01157
01158 string destNew = target.asString() + ".new.zypp.XXXXXX";
01159 char *buf = ::strdup( destNew.c_str());
01160 if( !buf)
01161 {
01162 ERR << "out of memory for temp file name" << endl;
01163 ZYPP_THROW(MediaSystemException(
01164 url, "out of memory for temp file name"
01165 ));
01166 }
01167
01168 int tmp_fd = ::mkstemp( buf );
01169 if( tmp_fd == -1)
01170 {
01171 free( buf);
01172 ERR << "mkstemp failed for file '" << destNew << "'" << endl;
01173 ZYPP_THROW(MediaWriteException(destNew));
01174 }
01175 destNew = buf;
01176 free( buf);
01177
01178 FILE *file = ::fdopen( tmp_fd, "w" );
01179 if ( !file ) {
01180 ::close( tmp_fd);
01181 filesystem::unlink( destNew );
01182 ERR << "fopen failed for file '" << destNew << "'" << endl;
01183 ZYPP_THROW(MediaWriteException(destNew));
01184 }
01185
01186 DBG << "dest: " << dest << endl;
01187 DBG << "temp: " << destNew << endl;
01188
01189 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
01190 if ( ret != 0 ) {
01191 ::fclose( file );
01192 filesystem::unlink( destNew );
01193 ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01194 }
01195
01196
01197 ProgressData progressData(_settings.timeout(), url, &report);
01198 report->start(url, dest);
01199 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
01200 WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01201 }
01202
01203 ret = curl_easy_perform( _curl );
01204
01205 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
01206 WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01207 }
01208
01209 if ( ret != 0 )
01210 {
01211 ERR << "curl error: " << ret << ": " << _curlError
01212 << ", temp file size " << PathInfo(destNew).size()
01213 << " byte." << endl;
01214
01215 ::fclose( file );
01216 filesystem::unlink( destNew );
01217
01218
01219
01220
01221 try {
01222 evaluateCurlCode( filename, ret, progressData.reached);
01223 }
01224 catch ( const MediaException &e ) {
01225
01226 ZYPP_RETHROW(e);
01227 }
01228 }
01229
01230 #if DETECT_DIR_INDEX
01231 else
01232 if(curlUrl.getScheme() == "http" ||
01233 curlUrl.getScheme() == "https")
01234 {
01235
01236
01237
01238
01239
01240
01241
01242
01243 bool not_a_file = false;
01244 char *ptr = NULL;
01245 CURLcode ret = curl_easy_getinfo( _curl,
01246 CURLINFO_EFFECTIVE_URL,
01247 &ptr);
01248 if ( ret == CURLE_OK && ptr != NULL)
01249 {
01250 try
01251 {
01252 Url eurl( ptr);
01253 std::string path( eurl.getPathName());
01254 if( !path.empty() && path != "/" && *path.rbegin() == '/')
01255 {
01256 DBG << "Effective url ("
01257 << eurl
01258 << ") seems to provide the index of a directory"
01259 << endl;
01260 not_a_file = true;
01261 }
01262 }
01263 catch( ... )
01264 {}
01265 }
01266
01267 if( not_a_file)
01268 {
01269 ::fclose( file );
01270 filesystem::unlink( destNew );
01271 ZYPP_THROW(MediaNotAFileException(_url, filename));
01272 }
01273 }
01274 #endif // DETECT_DIR_INDEX
01275
01276 long httpReturnCode = 0;
01277 CURLcode infoRet = curl_easy_getinfo(_curl,
01278 CURLINFO_RESPONSE_CODE,
01279 &httpReturnCode);
01280 bool modified = true;
01281 if (infoRet == CURLE_OK)
01282 {
01283 DBG << "HTTP response: " + str::numstring(httpReturnCode);
01284 if ( httpReturnCode == 304
01285 || ( httpReturnCode == 213 && _url.getScheme() == "ftp" ) )
01286 {
01287 DBG << " Not modified.";
01288 modified = false;
01289 }
01290 DBG << endl;
01291 }
01292 else
01293 {
01294 WAR << "Could not get the reponse code." << endl;
01295 }
01296
01297 if (modified || infoRet != CURLE_OK)
01298 {
01299
01300 if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) )
01301 {
01302 ERR << "Failed to chmod file " << destNew << endl;
01303 }
01304 ::fclose( file );
01305
01306
01307 if ( rename( destNew, dest ) != 0 ) {
01308 ERR << "Rename failed" << endl;
01309 ZYPP_THROW(MediaWriteException(dest));
01310 }
01311 }
01312 else
01313 {
01314
01315 ::fclose( file );
01316 filesystem::unlink( destNew );
01317 }
01318
01319 DBG << "done: " << PathInfo(dest) << endl;
01320 }
01321
01323
01324 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
01325 {
01326 filesystem::DirContent content;
01327 getDirInfo( content, dirname, false );
01328
01329 for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
01330 Pathname filename = dirname + it->name;
01331 int res = 0;
01332
01333 switch ( it->type ) {
01334 case filesystem::FT_NOT_AVAIL:
01335 case filesystem::FT_FILE:
01336 getFile( filename );
01337 break;
01338 case filesystem::FT_DIR:
01339 if ( recurse_r ) {
01340 getDir( filename, recurse_r );
01341 } else {
01342 res = assert_dir( localPath( filename ) );
01343 if ( res ) {
01344 WAR << "Ignore error (" << res << ") on creating local directory '" << localPath( filename ) << "'" << endl;
01345 }
01346 }
01347 break;
01348 default:
01349
01350 break;
01351 }
01352 }
01353 }
01354
01356
01357 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
01358 const Pathname & dirname, bool dots ) const
01359 {
01360 getDirectoryYast( retlist, dirname, dots );
01361 }
01362
01364
01365 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
01366 const Pathname & dirname, bool dots ) const
01367 {
01368 getDirectoryYast( retlist, dirname, dots );
01369 }
01370
01372
01373 int MediaCurl::progressCallback( void *clientp,
01374 double dltotal, double dlnow,
01375 double ultotal, double ulnow)
01376 {
01377 ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
01378 if( pdata)
01379 {
01380 time_t now = time(NULL);
01381 if( now > 0)
01382 {
01383
01384
01385 if( pdata->ltime <= 0 || pdata->ltime > now)
01386 {
01387 pdata->ltime = now;
01388 }
01389
01390
01391
01392 time_t dif = 0;
01393 if (dlnow > 0 || ulnow > 0)
01394 {
01395 dif = (now - pdata->ltime);
01396 dif = dif > 0 ? dif : 0;
01397
01398 pdata->secs += dif;
01399 }
01400
01401
01402
01403
01404
01406
01407 if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal ))
01408 pdata->drate_avg = (dlnow / pdata->secs);
01409
01410 if ( dif > 0 )
01411 {
01412 pdata->drate_period = ((dlnow - pdata->dload_period) / dif);
01413 pdata->dload_period = dlnow;
01414 }
01415 }
01416
01417
01418 if( pdata->report)
01419 {
01420 if (!(*(pdata->report))->progress(int( dlnow * 100 / dltotal ),
01421 pdata->url,
01422 pdata->drate_avg,
01423 pdata->drate_period))
01424 {
01425 return 1;
01426 }
01427 }
01428
01429
01430 if( pdata->timeout > 0)
01431 {
01432 if( now > 0)
01433 {
01434 bool progress = false;
01435
01436
01437 if( dlnow != pdata->dload)
01438 {
01439 progress = true;
01440 pdata->dload = dlnow;
01441 pdata->ltime = now;
01442 }
01443
01444 if( ulnow != pdata->uload)
01445 {
01446 progress = true;
01447 pdata->uload = ulnow;
01448 pdata->ltime = now;
01449 }
01450
01451 if( !progress && (now >= (pdata->ltime + pdata->timeout)))
01452 {
01453 pdata->reached = true;
01454 return 1;
01455 }
01456 }
01457 }
01458 }
01459 return 0;
01460 }
01461
01463
01464 string MediaCurl::getAuthHint() const
01465 {
01466 long auth_info = CURLAUTH_NONE;
01467
01468 CURLcode infoRet =
01469 curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
01470
01471 if(infoRet == CURLE_OK)
01472 {
01473 return CurlAuthData::auth_type_long2str(auth_info);
01474 }
01475
01476 return "";
01477 }
01478
01480
01481 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const
01482 {
01484 Target_Ptr target = zypp::getZYpp()->getTarget();
01485 CredentialManager cm(CredManagerOptions(target ? target->root() : ""));
01486 CurlAuthData_Ptr credentials;
01487
01488
01489 AuthData_Ptr cmcred = cm.getCred(_url);
01490
01491 if (cmcred && firstTry)
01492 {
01493 credentials.reset(new CurlAuthData(*cmcred));
01494 DBG << "got stored credentials:" << endl << *credentials << endl;
01495 }
01496
01497 else
01498 {
01499
01500 CurlAuthData_Ptr curlcred;
01501 curlcred.reset(new CurlAuthData());
01502 callback::SendReport<AuthenticationReport> auth_report;
01503
01504
01505 if (!_url.getUsername().empty() && firstTry)
01506 curlcred->setUsername(_url.getUsername());
01507
01508 else if (cmcred)
01509 curlcred->setUsername(cmcred->username());
01510
01511
01512 cmcred.reset();
01513
01514 string prompt_msg = boost::str(boost::format(
01516 _("Authentication required for '%s'")) % _url.asString());
01517
01518
01519
01520 curlcred->setAuthType(availAuthTypes);
01521
01522
01523 if (auth_report->prompt(_url, prompt_msg, *curlcred))
01524 {
01525 DBG << "callback answer: retry" << endl
01526 << "CurlAuthData: " << *curlcred << endl;
01527
01528 if (curlcred->valid())
01529 {
01530 credentials = curlcred;
01531
01532
01540 }
01541 }
01542 else
01543 {
01544 DBG << "callback answer: cancel" << endl;
01545 }
01546 }
01547
01548
01549 if (credentials)
01550 {
01551
01552 const_cast<MediaCurl*>(this)->_settings.setUsername(credentials->username());
01553 const_cast<MediaCurl*>(this)->_settings.setPassword(credentials->password());
01554
01555
01556 CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str());
01557 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
01558
01559
01560 if (credentials->authType() == CURLAUTH_NONE)
01561 credentials->setAuthType(availAuthTypes);
01562
01563
01564 if (credentials->authType() != CURLAUTH_NONE)
01565 {
01566 ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType());
01567 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
01568 }
01569
01570 if (!cmcred)
01571 {
01572 credentials->setUrl(_url);
01573 cm.addCred(*credentials);
01574 cm.save();
01575 }
01576
01577 return true;
01578 }
01579
01580 return false;
01581 }
01582
01583
01584 }
01585 }
01586