libzypp
10.5.0
|
00001 /*---------------------------------------------------------------------\ 00002 | ____ _ __ __ ___ | 00003 | |__ / \ / / . \ . \ | 00004 | / / \ V /| _/ _/ | 00005 | / /__ | | | | | | | 00006 | /_____||_| |_| |_| | 00007 | | 00008 \---------------------------------------------------------------------*/ 00013 #include <iostream> 00014 #include <list> 00015 00016 #include "zypp/base/Logger.h" 00017 #include "zypp/ExternalProgram.h" 00018 #include "zypp/base/String.h" 00019 #include "zypp/base/Gettext.h" 00020 #include "zypp/base/Sysconfig.h" 00021 #include "zypp/base/Gettext.h" 00022 00023 #include "zypp/media/MediaCurl.h" 00024 #include "zypp/media/ProxyInfo.h" 00025 #include "zypp/media/MediaUserAuth.h" 00026 #include "zypp/media/CredentialManager.h" 00027 #include "zypp/media/CurlConfig.h" 00028 #include "zypp/thread/Once.h" 00029 #include "zypp/Target.h" 00030 #include "zypp/ZYppFactory.h" 00031 #include "zypp/ZConfig.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_MAX 60 * 60 00045 00046 #define EXPLICITLY_NO_PROXY "_none_" 00047 00048 #undef CURLVERSION_AT_LEAST 00049 #define CURLVERSION_AT_LEAST(M,N,O) LIBCURL_VERSION_NUM >= ((((M)<<8)+(N))<<8)+(O) 00050 00051 using namespace std; 00052 using namespace zypp::base; 00053 00054 namespace 00055 { 00056 zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT; 00057 zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT; 00058 00059 extern "C" void _do_free_once() 00060 { 00061 curl_global_cleanup(); 00062 } 00063 00064 extern "C" void globalFreeOnce() 00065 { 00066 zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once); 00067 } 00068 00069 extern "C" void _do_init_once() 00070 { 00071 CURLcode ret = curl_global_init( CURL_GLOBAL_ALL ); 00072 if ( ret != 0 ) 00073 { 00074 WAR << "curl global init failed" << endl; 00075 } 00076 00077 // 00078 // register at exit handler ? 00079 // this may cause trouble, because we can protect it 00080 // against ourself only. 00081 // if the app sets an atexit handler as well, it will 00082 // cause a double free while the second of them runs. 00083 // 00084 //std::atexit( globalFreeOnce); 00085 } 00086 00087 inline void globalInitOnce() 00088 { 00089 zypp::thread::callOnce(g_InitOnceFlag, _do_init_once); 00090 } 00091 00092 int log_curl(CURL *curl, curl_infotype info, 00093 char *ptr, size_t len, void *max_lvl) 00094 { 00095 std::string pfx(" "); 00096 long lvl = 0; 00097 switch( info) 00098 { 00099 case CURLINFO_TEXT: lvl = 1; pfx = "*"; break; 00100 case CURLINFO_HEADER_IN: lvl = 2; pfx = "<"; break; 00101 case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break; 00102 default: break; 00103 } 00104 if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl)) 00105 { 00106 std::string msg(ptr, len); 00107 std::list<std::string> lines; 00108 std::list<std::string>::const_iterator line; 00109 zypp::str::split(msg, std::back_inserter(lines), "\r\n"); 00110 for(line = lines.begin(); line != lines.end(); ++line) 00111 { 00112 DBG << pfx << " " << *line << endl; 00113 } 00114 } 00115 return 0; 00116 } 00117 00118 static size_t 00119 log_redirects_curl( 00120 void *ptr, size_t size, size_t nmemb, void *stream) 00121 { 00122 // INT << "got header: " << string((char *)ptr, ((char*)ptr) + size*nmemb) << endl; 00123 00124 char * lstart = (char *)ptr, * lend = (char *)ptr; 00125 size_t pos = 0; 00126 size_t max = size * nmemb; 00127 while (pos + 1 < max) 00128 { 00129 // get line 00130 for (lstart = lend; *lend != '\n' && pos < max; ++lend, ++pos); 00131 00132 // look for "Location" 00133 string line(lstart, lend); 00134 if (line.find("Location") != string::npos) 00135 { 00136 DBG << "redirecting to " << line << endl; 00137 return max; 00138 } 00139 00140 // continue with the next line 00141 if (pos + 1 < max) 00142 { 00143 ++lend; 00144 ++pos; 00145 } 00146 else 00147 break; 00148 } 00149 00150 return max; 00151 } 00152 } 00153 00154 namespace zypp { 00155 namespace media { 00156 00157 namespace { 00158 struct ProgressData 00159 { 00160 ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(), 00161 callback::SendReport<DownloadProgressReport> *_report=NULL) 00162 : timeout(_timeout) 00163 , reached(false) 00164 , report(_report) 00165 , drate_period(-1) 00166 , dload_period(0) 00167 , secs(0) 00168 , drate_avg(-1) 00169 , ltime( time(NULL)) 00170 , dload( 0) 00171 , uload( 0) 00172 , url(_url) 00173 {} 00174 long timeout; 00175 bool reached; 00176 callback::SendReport<DownloadProgressReport> *report; 00177 // download rate of the last period (cca 1 sec) 00178 double drate_period; 00179 // bytes downloaded at the start of the last period 00180 double dload_period; 00181 // seconds from the start of the download 00182 long secs; 00183 // average download rate 00184 double drate_avg; 00185 // last time the progress was reported 00186 time_t ltime; 00187 // bytes downloaded at the moment the progress was last reported 00188 double dload; 00189 // bytes uploaded at the moment the progress was last reported 00190 double uload; 00191 zypp::Url url; 00192 }; 00193 00195 00196 inline void escape( string & str_r, 00197 const char char_r, const string & escaped_r ) { 00198 for ( string::size_type pos = str_r.find( char_r ); 00199 pos != string::npos; pos = str_r.find( char_r, pos ) ) { 00200 str_r.replace( pos, 1, escaped_r ); 00201 } 00202 } 00203 00204 inline string escapedPath( string path_r ) { 00205 escape( path_r, ' ', "%20" ); 00206 return path_r; 00207 } 00208 00209 inline string unEscape( string text_r ) { 00210 char * tmp = curl_unescape( text_r.c_str(), 0 ); 00211 string ret( tmp ); 00212 curl_free( tmp ); 00213 return ret; 00214 } 00215 00216 } 00217 00222 void fillSettingsFromUrl( const Url &url, TransferSettings &s ) 00223 { 00224 std::string param(url.getQueryParam("timeout")); 00225 if( !param.empty()) 00226 { 00227 long num = str::strtonum<long>(param); 00228 if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX) 00229 s.setTimeout(num); 00230 } 00231 00232 if ( ! url.getUsername().empty() ) 00233 { 00234 s.setUsername(url.getUsername()); 00235 if ( url.getPassword().size() ) 00236 s.setPassword(url.getPassword()); 00237 } 00238 else 00239 { 00240 // if there is no username, set anonymous auth 00241 if ( url.getScheme() == "ftp" && s.username().empty() ) 00242 s.setAnonymousAuth(); 00243 } 00244 00245 if ( url.getScheme() == "https" ) 00246 { 00247 s.setVerifyPeerEnabled(false); 00248 s.setVerifyHostEnabled(false); 00249 00250 std::string verify( url.getQueryParam("ssl_verify")); 00251 if( verify.empty() || 00252 verify == "yes") 00253 { 00254 s.setVerifyPeerEnabled(true); 00255 s.setVerifyHostEnabled(true); 00256 } 00257 else if( verify == "no") 00258 { 00259 s.setVerifyPeerEnabled(false); 00260 s.setVerifyHostEnabled(false); 00261 } 00262 else 00263 { 00264 std::vector<std::string> flags; 00265 std::vector<std::string>::const_iterator flag; 00266 str::split( verify, std::back_inserter(flags), ","); 00267 for(flag = flags.begin(); flag != flags.end(); ++flag) 00268 { 00269 if( *flag == "host") 00270 s.setVerifyHostEnabled(true); 00271 else if( *flag == "peer") 00272 s.setVerifyPeerEnabled(true); 00273 else 00274 ZYPP_THROW(MediaBadUrlException(url, "Unknown ssl_verify flag")); 00275 } 00276 } 00277 } 00278 00279 Pathname ca_path( url.getQueryParam("ssl_capath") ); 00280 if( ! ca_path.empty()) 00281 { 00282 if( !PathInfo(ca_path).isDir() || ! ca_path.absolute()) 00283 ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_capath path")); 00284 else 00285 s.setCertificateAuthoritiesPath(ca_path); 00286 } 00287 00288 param = url.getQueryParam( "proxy" ); 00289 if ( ! param.empty() ) 00290 { 00291 if ( param == EXPLICITLY_NO_PROXY ) { 00292 // Workaround TransferSettings shortcoming: With an 00293 // empty proxy string, code will continue to look for 00294 // valid proxy settings. So set proxy to some non-empty 00295 // string, to indicate it has been explicitly disabled. 00296 s.setProxy(EXPLICITLY_NO_PROXY); 00297 s.setProxyEnabled(false); 00298 } 00299 else { 00300 string proxyport( url.getQueryParam( "proxyport" ) ); 00301 if ( ! proxyport.empty() ) { 00302 param += ":" + proxyport; 00303 } 00304 s.setProxy(param); 00305 s.setProxyEnabled(true); 00306 } 00307 } 00308 00309 param = url.getQueryParam( "proxyuser" ); 00310 if ( ! param.empty() ) 00311 { 00312 s.setProxyUsername(param); 00313 s.setProxyPassword(url.getQueryParam( "proxypass" )); 00314 } 00315 00316 // HTTP authentication type 00317 param = url.getQueryParam("auth"); 00318 if (!param.empty() && (url.getScheme() == "http" || url.getScheme() == "https")) 00319 { 00320 try 00321 { 00322 CurlAuthData::auth_type_str2long(param); // check if we know it 00323 } 00324 catch (MediaException & ex_r) 00325 { 00326 DBG << "Rethrowing as MediaUnauthorizedException."; 00327 ZYPP_THROW(MediaUnauthorizedException(url, ex_r.msg(), "", "")); 00328 } 00329 s.setAuthType(param); 00330 } 00331 00332 // workarounds 00333 param = url.getQueryParam("head_requests"); 00334 if( !param.empty() && param == "no" ) 00335 s.setHeadRequestsAllowed(false); 00336 } 00337 00342 void fillSettingsSystemProxy( const Url&url, TransferSettings &s ) 00343 { 00344 ProxyInfo proxy_info; 00345 if ( proxy_info.useProxyFor( url ) ) 00346 { 00347 // We must extract any 'user:pass' from the proxy url 00348 // otherwise they won't make it into curl (.curlrc wins). 00349 try { 00350 Url u( proxy_info.proxy( url ) ); 00351 s.setProxy( u.asString( url::ViewOption::WITH_SCHEME + url::ViewOption::WITH_HOST + url::ViewOption::WITH_PORT ) ); 00352 // don't overwrite explicit auth settings 00353 if ( s.proxyUsername().empty() ) 00354 { 00355 s.setProxyUsername( u.getUsername( url::E_ENCODED ) ); 00356 s.setProxyPassword( u.getPassword( url::E_ENCODED ) ); 00357 } 00358 s.setProxyEnabled( true ); 00359 } 00360 catch (...) {} // no proxy if URL is malformed 00361 } 00362 } 00363 00364 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies"; 00365 00370 static const char *const anonymousIdHeader() 00371 { 00372 // we need to add the release and identifier to the 00373 // agent string. 00374 // The target could be not initialized, and then this information 00375 // is guessed. 00376 static const std::string _value( 00377 str::trim( str::form( 00378 "X-ZYpp-AnonymousId: %s", 00379 Target::anonymousUniqueId( Pathname()/*guess root*/ ).c_str() ) ) 00380 ); 00381 return _value.c_str(); 00382 } 00383 00388 static const char *const distributionFlavorHeader() 00389 { 00390 // we need to add the release and identifier to the 00391 // agent string. 00392 // The target could be not initialized, and then this information 00393 // is guessed. 00394 static const std::string _value( 00395 str::trim( str::form( 00396 "X-ZYpp-DistributionFlavor: %s", 00397 Target::distributionFlavor( Pathname()/*guess root*/ ).c_str() ) ) 00398 ); 00399 return _value.c_str(); 00400 } 00401 00406 static const char *const agentString() 00407 { 00408 // we need to add the release and identifier to the 00409 // agent string. 00410 // The target could be not initialized, and then this information 00411 // is guessed. 00412 static const std::string _value( 00413 str::form( 00414 "ZYpp %s (curl %s) %s" 00415 , VERSION 00416 , curl_version_info(CURLVERSION_NOW)->version 00417 , Target::targetDistribution( Pathname()/*guess root*/ ).c_str() 00418 ) 00419 ); 00420 return _value.c_str(); 00421 } 00422 00423 // we use this define to unbloat code as this C setting option 00424 // and catching exception is done frequently. 00426 #define SET_OPTION(opt,val) do { \ 00427 ret = curl_easy_setopt ( _curl, opt, val ); \ 00428 if ( ret != 0) { \ 00429 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); \ 00430 } \ 00431 } while ( false ) 00432 00433 #define SET_OPTION_OFFT(opt,val) SET_OPTION(opt,(curl_off_t)val) 00434 #define SET_OPTION_LONG(opt,val) SET_OPTION(opt,(long)val) 00435 #define SET_OPTION_VOID(opt,val) SET_OPTION(opt,(void*)val) 00436 00437 MediaCurl::MediaCurl( const Url & url_r, 00438 const Pathname & attach_point_hint_r ) 00439 : MediaHandler( url_r, attach_point_hint_r, 00440 "/", // urlpath at attachpoint 00441 true ), // does_download 00442 _curl( NULL ), 00443 _customHeaders(0L) 00444 { 00445 _curlError[0] = '\0'; 00446 _curlDebug = 0L; 00447 00448 MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl; 00449 00450 globalInitOnce(); 00451 00452 if( !attachPoint().empty()) 00453 { 00454 PathInfo ainfo(attachPoint()); 00455 Pathname apath(attachPoint() + "XXXXXX"); 00456 char *atemp = ::strdup( apath.asString().c_str()); 00457 char *atest = NULL; 00458 if( !ainfo.isDir() || !ainfo.userMayRWX() || 00459 atemp == NULL || (atest=::mkdtemp(atemp)) == NULL) 00460 { 00461 WAR << "attach point " << ainfo.path() 00462 << " is not useable for " << url_r.getScheme() << endl; 00463 setAttachPoint("", true); 00464 } 00465 else if( atest != NULL) 00466 ::rmdir(atest); 00467 00468 if( atemp != NULL) 00469 ::free(atemp); 00470 } 00471 } 00472 00473 Url MediaCurl::clearQueryString(const Url &url) const 00474 { 00475 Url curlUrl (url); 00476 curlUrl.setUsername( "" ); 00477 curlUrl.setPassword( "" ); 00478 curlUrl.setPathParams( "" ); 00479 curlUrl.setFragment( "" ); 00480 curlUrl.delQueryParam("cookies"); 00481 curlUrl.delQueryParam("proxy"); 00482 curlUrl.delQueryParam("proxyport"); 00483 curlUrl.delQueryParam("proxyuser"); 00484 curlUrl.delQueryParam("proxypass"); 00485 curlUrl.delQueryParam("ssl_capath"); 00486 curlUrl.delQueryParam("ssl_verify"); 00487 curlUrl.delQueryParam("timeout"); 00488 curlUrl.delQueryParam("auth"); 00489 curlUrl.delQueryParam("username"); 00490 curlUrl.delQueryParam("password"); 00491 curlUrl.delQueryParam("mediahandler"); 00492 return curlUrl; 00493 } 00494 00495 TransferSettings & MediaCurl::settings() 00496 { 00497 return _settings; 00498 } 00499 00500 00501 void MediaCurl::setCookieFile( const Pathname &fileName ) 00502 { 00503 _cookieFile = fileName; 00504 } 00505 00507 00508 void MediaCurl::checkProtocol(const Url &url) const 00509 { 00510 curl_version_info_data *curl_info = NULL; 00511 curl_info = curl_version_info(CURLVERSION_NOW); 00512 // curl_info does not need any free (is static) 00513 if (curl_info->protocols) 00514 { 00515 const char * const *proto; 00516 std::string scheme( url.getScheme()); 00517 bool found = false; 00518 for(proto=curl_info->protocols; !found && *proto; ++proto) 00519 { 00520 if( scheme == std::string((const char *)*proto)) 00521 found = true; 00522 } 00523 if( !found) 00524 { 00525 std::string msg("Unsupported protocol '"); 00526 msg += scheme; 00527 msg += "'"; 00528 ZYPP_THROW(MediaBadUrlException(_url, msg)); 00529 } 00530 } 00531 } 00532 00533 void MediaCurl::setupEasy() 00534 { 00535 { 00536 char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG"); 00537 _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L; 00538 if( _curlDebug > 0) 00539 { 00540 curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1L); 00541 curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl); 00542 curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug); 00543 } 00544 } 00545 00546 curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, log_redirects_curl); 00547 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError ); 00548 if ( ret != 0 ) { 00549 ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer")); 00550 } 00551 00552 SET_OPTION(CURLOPT_FAILONERROR, 1L); 00553 SET_OPTION(CURLOPT_NOSIGNAL, 1L); 00554 00555 // create non persistant settings 00556 // so that we don't add headers twice 00557 TransferSettings vol_settings(_settings); 00558 00559 // add custom headers 00560 vol_settings.addHeader(anonymousIdHeader()); 00561 vol_settings.addHeader(distributionFlavorHeader()); 00562 vol_settings.addHeader("Pragma:"); 00563 00564 _settings.setTimeout(ZConfig::instance().download_transfer_timeout()); 00565 _settings.setConnectTimeout(CONNECT_TIMEOUT); 00566 00567 _settings.setUserAgentString(agentString()); 00568 00569 // fill some settings from url query parameters 00570 try 00571 { 00572 fillSettingsFromUrl(_url, _settings); 00573 } 00574 catch ( const MediaException &e ) 00575 { 00576 disconnectFrom(); 00577 ZYPP_RETHROW(e); 00578 } 00579 // if the proxy was not set (or explicitly unset) by url, then look... 00580 if ( _settings.proxy().empty() ) 00581 { 00582 // ...at the system proxy settings 00583 fillSettingsSystemProxy(_url, _settings); 00584 } 00585 00589 SET_OPTION(CURLOPT_CONNECTTIMEOUT, _settings.connectTimeout()); 00590 00591 // follow any Location: header that the server sends as part of 00592 // an HTTP header (#113275) 00593 SET_OPTION(CURLOPT_FOLLOWLOCATION, 1L); 00594 // 3 redirects seem to be too few in some cases (bnc #465532) 00595 SET_OPTION(CURLOPT_MAXREDIRS, 6L); 00596 00597 if ( _url.getScheme() == "https" ) 00598 { 00599 #if CURLVERSION_AT_LEAST(7,19,4) 00600 // restrict following of redirections from https to https only 00601 SET_OPTION( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS ); 00602 #endif 00603 00604 if( _settings.verifyPeerEnabled() || 00605 _settings.verifyHostEnabled() ) 00606 { 00607 SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str()); 00608 } 00609 00610 SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L); 00611 SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L); 00612 } 00613 00614 SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() ); 00615 00616 /*---------------------------------------------------------------* 00617 CURLOPT_USERPWD: [user name]:[password] 00618 00619 Url::username/password -> CURLOPT_USERPWD 00620 If not provided, anonymous FTP identification 00621 *---------------------------------------------------------------*/ 00622 00623 if ( _settings.userPassword().size() ) 00624 { 00625 SET_OPTION(CURLOPT_USERPWD, _settings.userPassword().c_str()); 00626 string use_auth = _settings.authType(); 00627 if (use_auth.empty()) 00628 use_auth = "digest,basic"; // our default 00629 long auth = CurlAuthData::auth_type_str2long(use_auth); 00630 if( auth != CURLAUTH_NONE) 00631 { 00632 DBG << "Enabling HTTP authentication methods: " << use_auth 00633 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl; 00634 SET_OPTION(CURLOPT_HTTPAUTH, auth); 00635 } 00636 } 00637 00638 if ( _settings.proxyEnabled() && ! _settings.proxy().empty() ) 00639 { 00640 DBG << "Proxy: '" << _settings.proxy() << "'" << endl; 00641 SET_OPTION(CURLOPT_PROXY, _settings.proxy().c_str()); 00642 SET_OPTION(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM ); 00643 /*---------------------------------------------------------------* 00644 * CURLOPT_PROXYUSERPWD: [user name]:[password] 00645 * 00646 * Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD 00647 * If not provided, $HOME/.curlrc is evaluated 00648 *---------------------------------------------------------------*/ 00649 00650 string proxyuserpwd = _settings.proxyUserPassword(); 00651 00652 if ( proxyuserpwd.empty() ) 00653 { 00654 CurlConfig curlconf; 00655 CurlConfig::parseConfig(curlconf); // parse ~/.curlrc 00656 if ( curlconf.proxyuserpwd.empty() ) 00657 DBG << "Proxy: ~/.curlrc does not contain the proxy-user option" << endl; 00658 else 00659 { 00660 proxyuserpwd = curlconf.proxyuserpwd; 00661 DBG << "Proxy: using proxy-user from ~/.curlrc" << endl; 00662 } 00663 } 00664 else 00665 { 00666 DBG << "Proxy: using provided proxy-user '" << _settings.proxyUsername() << "'" << endl; 00667 } 00668 00669 if ( ! proxyuserpwd.empty() ) 00670 { 00671 SET_OPTION(CURLOPT_PROXYUSERPWD, unEscape( proxyuserpwd ).c_str()); 00672 } 00673 } 00674 #if CURLVERSION_AT_LEAST(7,19,4) 00675 else if ( _settings.proxy() == EXPLICITLY_NO_PROXY ) 00676 { 00677 // Explicitly disabled in URL (see fillSettingsFromUrl()). 00678 // This should also prevent libcurl from looking into the environment. 00679 DBG << "Proxy: explicitly NOPROXY" << endl; 00680 SET_OPTION(CURLOPT_NOPROXY, "*"); 00681 } 00682 #endif 00683 else 00684 { 00685 // libcurl may look into the enviroanment 00686 DBG << "Proxy: not explicitly set" << endl; 00687 } 00688 00690 if ( _settings.minDownloadSpeed() != 0 ) 00691 { 00692 SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed()); 00693 // default to 10 seconds at low speed 00694 SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10L); 00695 } 00696 00697 #if CURLVERSION_AT_LEAST(7,15,5) 00698 if ( _settings.maxDownloadSpeed() != 0 ) 00699 SET_OPTION_OFFT(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed()); 00700 #endif 00701 00702 /*---------------------------------------------------------------* 00703 *---------------------------------------------------------------*/ 00704 00705 _currentCookieFile = _cookieFile.asString(); 00706 if ( str::strToBool( _url.getQueryParam( "cookies" ), true ) ) 00707 SET_OPTION(CURLOPT_COOKIEFILE, _currentCookieFile.c_str() ); 00708 else 00709 MIL << "No cookies requested" << endl; 00710 SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() ); 00711 SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback ); 00712 SET_OPTION(CURLOPT_NOPROGRESS, 0L); 00713 00714 #if CURLVERSION_AT_LEAST(7,18,0) 00715 // bnc #306272 00716 SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1L ); 00717 #endif 00718 // append settings custom headers to curl 00719 for ( TransferSettings::Headers::const_iterator it = vol_settings.headersBegin(); 00720 it != vol_settings.headersEnd(); 00721 ++it ) 00722 { 00723 MIL << "HEADER " << *it << std::endl; 00724 00725 _customHeaders = curl_slist_append(_customHeaders, it->c_str()); 00726 if ( !_customHeaders ) 00727 ZYPP_THROW(MediaCurlInitException(_url)); 00728 } 00729 00730 SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders); 00731 } 00732 00734 00735 00736 void MediaCurl::attachTo (bool next) 00737 { 00738 if ( next ) 00739 ZYPP_THROW(MediaNotSupportedException(_url)); 00740 00741 if ( !_url.isValid() ) 00742 ZYPP_THROW(MediaBadUrlException(_url)); 00743 00744 checkProtocol(_url); 00745 if( !isUseableAttachPoint(attachPoint())) 00746 { 00747 std::string mountpoint = createAttachPoint().asString(); 00748 00749 if( mountpoint.empty()) 00750 ZYPP_THROW( MediaBadAttachPointException(url())); 00751 00752 setAttachPoint( mountpoint, true); 00753 } 00754 00755 disconnectFrom(); // clean _curl if needed 00756 _curl = curl_easy_init(); 00757 if ( !_curl ) { 00758 ZYPP_THROW(MediaCurlInitException(_url)); 00759 } 00760 try 00761 { 00762 setupEasy(); 00763 } 00764 catch (Exception & ex) 00765 { 00766 disconnectFrom(); 00767 ZYPP_RETHROW(ex); 00768 } 00769 00770 // FIXME: need a derived class to propelly compare url's 00771 MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString())); 00772 setMediaSource(media); 00773 } 00774 00775 bool 00776 MediaCurl::checkAttachPoint(const Pathname &apoint) const 00777 { 00778 return MediaHandler::checkAttachPoint( apoint, true, true); 00779 } 00780 00782 00783 void MediaCurl::disconnectFrom() 00784 { 00785 if ( _customHeaders ) 00786 { 00787 curl_slist_free_all(_customHeaders); 00788 _customHeaders = 0L; 00789 } 00790 00791 if ( _curl ) 00792 { 00793 curl_easy_cleanup( _curl ); 00794 _curl = NULL; 00795 } 00796 } 00797 00799 00800 void MediaCurl::releaseFrom( const std::string & ejectDev ) 00801 { 00802 disconnect(); 00803 } 00804 00805 Url MediaCurl::getFileUrl(const Pathname & filename) const 00806 { 00807 Url newurl(_url); 00808 string path = _url.getPathName(); 00809 if ( !path.empty() && path != "/" && *path.rbegin() == '/' && 00810 filename.absolute() ) 00811 { 00812 // If url has a path with trailing slash, remove the leading slash from 00813 // the absolute file name 00814 path += filename.asString().substr( 1, filename.asString().size() - 1 ); 00815 } 00816 else if ( filename.relative() ) 00817 { 00818 // Add trailing slash to path, if not already there 00819 if (path.empty()) path = "/"; 00820 else if (*path.rbegin() != '/' ) path += "/"; 00821 // Remove "./" from begin of relative file name 00822 path += filename.asString().substr( 2, filename.asString().size() - 2 ); 00823 } 00824 else 00825 { 00826 path += filename.asString(); 00827 } 00828 00829 newurl.setPathName(path); 00830 return newurl; 00831 } 00832 00834 00835 void MediaCurl::getFile( const Pathname & filename ) const 00836 { 00837 // Use absolute file name to prevent access of files outside of the 00838 // hierarchy below the attach point. 00839 getFileCopy(filename, localPath(filename).absolutename()); 00840 } 00841 00843 00844 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const 00845 { 00846 callback::SendReport<DownloadProgressReport> report; 00847 00848 Url fileurl(getFileUrl(filename)); 00849 00850 bool retry = false; 00851 00852 do 00853 { 00854 try 00855 { 00856 doGetFileCopy(filename, target, report); 00857 retry = false; 00858 } 00859 // retry with proper authentication data 00860 catch (MediaUnauthorizedException & ex_r) 00861 { 00862 if(authenticate(ex_r.hint(), !retry)) 00863 retry = true; 00864 else 00865 { 00866 report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory()); 00867 ZYPP_RETHROW(ex_r); 00868 } 00869 } 00870 // unexpected exception 00871 catch (MediaException & excpt_r) 00872 { 00873 // FIXME: error number fix 00874 report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory()); 00875 ZYPP_RETHROW(excpt_r); 00876 } 00877 } 00878 while (retry); 00879 00880 report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, ""); 00881 } 00882 00884 00885 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const 00886 { 00887 bool retry = false; 00888 00889 do 00890 { 00891 try 00892 { 00893 return doGetDoesFileExist( filename ); 00894 } 00895 // authentication problem, retry with proper authentication data 00896 catch (MediaUnauthorizedException & ex_r) 00897 { 00898 if(authenticate(ex_r.hint(), !retry)) 00899 retry = true; 00900 else 00901 ZYPP_RETHROW(ex_r); 00902 } 00903 // unexpected exception 00904 catch (MediaException & excpt_r) 00905 { 00906 ZYPP_RETHROW(excpt_r); 00907 } 00908 } 00909 while (retry); 00910 00911 return false; 00912 } 00913 00915 00916 void MediaCurl::evaluateCurlCode( const Pathname &filename, 00917 CURLcode code, 00918 bool timeout_reached ) const 00919 { 00920 if ( code != 0 ) 00921 { 00922 Url url; 00923 if (filename.empty()) 00924 url = _url; 00925 else 00926 url = getFileUrl(filename); 00927 std::string err; 00928 try 00929 { 00930 switch ( code ) 00931 { 00932 case CURLE_UNSUPPORTED_PROTOCOL: 00933 case CURLE_URL_MALFORMAT: 00934 case CURLE_URL_MALFORMAT_USER: 00935 err = " Bad URL"; 00936 break; 00937 case CURLE_LOGIN_DENIED: 00938 ZYPP_THROW( 00939 MediaUnauthorizedException(url, "Login failed.", _curlError, "")); 00940 break; 00941 case CURLE_HTTP_RETURNED_ERROR: 00942 { 00943 long httpReturnCode = 0; 00944 CURLcode infoRet = curl_easy_getinfo( _curl, 00945 CURLINFO_RESPONSE_CODE, 00946 &httpReturnCode ); 00947 if ( infoRet == CURLE_OK ) 00948 { 00949 string msg = "HTTP response: " + str::numstring( httpReturnCode ); 00950 switch ( httpReturnCode ) 00951 { 00952 case 401: 00953 { 00954 string auth_hint = getAuthHint(); 00955 00956 DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl; 00957 DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl; 00958 00959 ZYPP_THROW(MediaUnauthorizedException( 00960 url, "Login failed.", _curlError, auth_hint 00961 )); 00962 } 00963 00964 case 503: // service temporarily unavailable (bnc #462545) 00965 ZYPP_THROW(MediaTemporaryProblemException(url)); 00966 case 504: // gateway timeout 00967 ZYPP_THROW(MediaTimeoutException(url)); 00968 case 403: 00969 { 00970 string msg403; 00971 if (url.asString().find("novell.com") != string::npos) 00972 msg403 = _("Visit the Novell Customer Center to check whether your registration is valid and has not expired."); 00973 ZYPP_THROW(MediaForbiddenException(url, msg403)); 00974 } 00975 case 404: 00976 ZYPP_THROW(MediaFileNotFoundException(_url, filename)); 00977 } 00978 00979 DBG << msg << " (URL: " << url.asString() << ")" << std::endl; 00980 ZYPP_THROW(MediaCurlException(url, msg, _curlError)); 00981 } 00982 else 00983 { 00984 string msg = "Unable to retrieve HTTP response:"; 00985 DBG << msg << " (URL: " << url.asString() << ")" << std::endl; 00986 ZYPP_THROW(MediaCurlException(url, msg, _curlError)); 00987 } 00988 } 00989 break; 00990 case CURLE_FTP_COULDNT_RETR_FILE: 00991 #if CURLVERSION_AT_LEAST(7,16,0) 00992 case CURLE_REMOTE_FILE_NOT_FOUND: 00993 #endif 00994 case CURLE_FTP_ACCESS_DENIED: 00995 err = "File not found"; 00996 ZYPP_THROW(MediaFileNotFoundException(_url, filename)); 00997 break; 00998 case CURLE_BAD_PASSWORD_ENTERED: 00999 case CURLE_FTP_USER_PASSWORD_INCORRECT: 01000 err = "Login failed"; 01001 break; 01002 case CURLE_COULDNT_RESOLVE_PROXY: 01003 case CURLE_COULDNT_RESOLVE_HOST: 01004 case CURLE_COULDNT_CONNECT: 01005 case CURLE_FTP_CANT_GET_HOST: 01006 err = "Connection failed"; 01007 break; 01008 case CURLE_WRITE_ERROR: 01009 err = "Write error"; 01010 break; 01011 case CURLE_PARTIAL_FILE: 01012 case CURLE_OPERATION_TIMEDOUT: 01013 timeout_reached = true; // fall though to TimeoutException 01014 // fall though... 01015 case CURLE_ABORTED_BY_CALLBACK: 01016 if( timeout_reached ) 01017 { 01018 err = "Timeout reached"; 01019 ZYPP_THROW(MediaTimeoutException(url)); 01020 } 01021 else 01022 { 01023 err = "User abort"; 01024 } 01025 break; 01026 case CURLE_SSL_PEER_CERTIFICATE: 01027 default: 01028 err = "Unrecognized error"; 01029 break; 01030 } 01031 01032 // uhm, no 0 code but unknown curl exception 01033 ZYPP_THROW(MediaCurlException(url, err, _curlError)); 01034 } 01035 catch (const MediaException & excpt_r) 01036 { 01037 ZYPP_RETHROW(excpt_r); 01038 } 01039 } 01040 else 01041 { 01042 // actually the code is 0, nothing happened 01043 } 01044 } 01045 01047 01048 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const 01049 { 01050 DBG << filename.asString() << endl; 01051 01052 if(!_url.isValid()) 01053 ZYPP_THROW(MediaBadUrlException(_url)); 01054 01055 if(_url.getHost().empty()) 01056 ZYPP_THROW(MediaBadUrlEmptyHostException(_url)); 01057 01058 Url url(getFileUrl(filename)); 01059 01060 DBG << "URL: " << url.asString() << endl; 01061 // Use URL without options and without username and passwd 01062 // (some proxies dislike them in the URL). 01063 // Curl seems to need the just scheme, hostname and a path; 01064 // the rest was already passed as curl options (in attachTo). 01065 Url curlUrl( clearQueryString(url) ); 01066 01067 // 01068 // See also Bug #154197 and ftp url definition in RFC 1738: 01069 // The url "ftp://user@host/foo/bar/file" contains a path, 01070 // that is relative to the user's home. 01071 // The url "ftp://user@host//foo/bar/file" (or also with 01072 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file" 01073 // contains an absolute path. 01074 // 01075 string urlBuffer( curlUrl.asString()); 01076 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL, 01077 urlBuffer.c_str() ); 01078 if ( ret != 0 ) { 01079 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01080 } 01081 01082 // instead of returning no data with NOBODY, we return 01083 // little data, that works with broken servers, and 01084 // works for ftp as well, because retrieving only headers 01085 // ftp will return always OK code ? 01086 // See http://curl.haxx.se/docs/knownbugs.html #58 01087 if ( (_url.getScheme() == "http" || _url.getScheme() == "https") && 01088 _settings.headRequestsAllowed() ) 01089 ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1L ); 01090 else 01091 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" ); 01092 01093 if ( ret != 0 ) { 01094 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01095 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); 01096 /* yes, this is why we never got to get NOBODY working before, 01097 because setting it changes this option too, and we also 01098 need to reset it 01099 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01100 */ 01101 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); 01102 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01103 } 01104 01105 FILE *file = ::fopen( "/dev/null", "w" ); 01106 if ( !file ) { 01107 ERR << "fopen failed for /dev/null" << endl; 01108 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01109 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); 01110 /* yes, this is why we never got to get NOBODY working before, 01111 because setting it changes this option too, and we also 01112 need to reset it 01113 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01114 */ 01115 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); 01116 if ( ret != 0 ) { 01117 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01118 } 01119 ZYPP_THROW(MediaWriteException("/dev/null")); 01120 } 01121 01122 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file ); 01123 if ( ret != 0 ) { 01124 ::fclose(file); 01125 std::string err( _curlError); 01126 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); 01127 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01128 /* yes, this is why we never got to get NOBODY working before, 01129 because setting it changes this option too, and we also 01130 need to reset it 01131 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01132 */ 01133 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); 01134 if ( ret != 0 ) { 01135 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01136 } 01137 ZYPP_THROW(MediaCurlSetOptException(url, err)); 01138 } 01139 01140 CURLcode ok = curl_easy_perform( _curl ); 01141 MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl; 01142 01143 // reset curl settings 01144 if ( _url.getScheme() == "http" || _url.getScheme() == "https" ) 01145 { 01146 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01147 if ( ret != 0 ) { 01148 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01149 } 01150 01151 /* yes, this is why we never got to get NOBODY working before, 01152 because setting it changes this option too, and we also 01153 need to reset it 01154 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01155 */ 01156 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L); 01157 if ( ret != 0 ) { 01158 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01159 } 01160 01161 } 01162 else 01163 { 01164 // for FTP we set different options 01165 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL); 01166 if ( ret != 0 ) { 01167 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01168 } 01169 } 01170 01171 // if the code is not zero, close the file 01172 if ( ok != 0 ) 01173 ::fclose(file); 01174 01175 // as we are not having user interaction, the user can't cancel 01176 // the file existence checking, a callback or timeout return code 01177 // will be always a timeout. 01178 try { 01179 evaluateCurlCode( filename, ok, true /* timeout */); 01180 } 01181 catch ( const MediaFileNotFoundException &e ) { 01182 // if the file did not exist then we can return false 01183 return false; 01184 } 01185 catch ( const MediaException &e ) { 01186 // some error, we are not sure about file existence, rethrw 01187 ZYPP_RETHROW(e); 01188 } 01189 // exists 01190 return ( ok == CURLE_OK ); 01191 } 01192 01194 01195 01196 #if DETECT_DIR_INDEX 01197 bool MediaCurl::detectDirIndex() const 01198 { 01199 if(_url.getScheme() != "http" && _url.getScheme() != "https") 01200 return false; 01201 // 01202 // try to check the effective url and set the not_a_file flag 01203 // if the url path ends with a "/", what usually means, that 01204 // we've received a directory index (index.html content). 01205 // 01206 // Note: This may be dangerous and break file retrieving in 01207 // case of some server redirections ... ? 01208 // 01209 bool not_a_file = false; 01210 char *ptr = NULL; 01211 CURLcode ret = curl_easy_getinfo( _curl, 01212 CURLINFO_EFFECTIVE_URL, 01213 &ptr); 01214 if ( ret == CURLE_OK && ptr != NULL) 01215 { 01216 try 01217 { 01218 Url eurl( ptr); 01219 std::string path( eurl.getPathName()); 01220 if( !path.empty() && path != "/" && *path.rbegin() == '/') 01221 { 01222 DBG << "Effective url (" 01223 << eurl 01224 << ") seems to provide the index of a directory" 01225 << endl; 01226 not_a_file = true; 01227 } 01228 } 01229 catch( ... ) 01230 {} 01231 } 01232 return not_a_file; 01233 } 01234 #endif 01235 01237 01238 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const 01239 { 01240 Pathname dest = target.absolutename(); 01241 if( assert_dir( dest.dirname() ) ) 01242 { 01243 DBG << "assert_dir " << dest.dirname() << " failed" << endl; 01244 Url url(getFileUrl(filename)); 01245 ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) ); 01246 } 01247 string destNew = target.asString() + ".new.zypp.XXXXXX"; 01248 char *buf = ::strdup( destNew.c_str()); 01249 if( !buf) 01250 { 01251 ERR << "out of memory for temp file name" << endl; 01252 Url url(getFileUrl(filename)); 01253 ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name")); 01254 } 01255 01256 int tmp_fd = ::mkostemp( buf, O_CLOEXEC ); 01257 if( tmp_fd == -1) 01258 { 01259 free( buf); 01260 ERR << "mkstemp failed for file '" << destNew << "'" << endl; 01261 ZYPP_THROW(MediaWriteException(destNew)); 01262 } 01263 destNew = buf; 01264 free( buf); 01265 01266 FILE *file = ::fdopen( tmp_fd, "we" ); 01267 if ( !file ) { 01268 ::close( tmp_fd); 01269 filesystem::unlink( destNew ); 01270 ERR << "fopen failed for file '" << destNew << "'" << endl; 01271 ZYPP_THROW(MediaWriteException(destNew)); 01272 } 01273 01274 DBG << "dest: " << dest << endl; 01275 DBG << "temp: " << destNew << endl; 01276 01277 // set IFMODSINCE time condition (no download if not modified) 01278 if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) ) 01279 { 01280 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); 01281 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime()); 01282 } 01283 else 01284 { 01285 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); 01286 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); 01287 } 01288 try 01289 { 01290 doGetFileCopyFile(filename, dest, file, report, options); 01291 } 01292 catch (Exception &e) 01293 { 01294 ::fclose( file ); 01295 filesystem::unlink( destNew ); 01296 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); 01297 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); 01298 ZYPP_RETHROW(e); 01299 } 01300 01301 long httpReturnCode = 0; 01302 CURLcode infoRet = curl_easy_getinfo(_curl, 01303 CURLINFO_RESPONSE_CODE, 01304 &httpReturnCode); 01305 bool modified = true; 01306 if (infoRet == CURLE_OK) 01307 { 01308 DBG << "HTTP response: " + str::numstring(httpReturnCode); 01309 if ( httpReturnCode == 304 01310 || ( httpReturnCode == 213 && _url.getScheme() == "ftp" ) ) // not modified 01311 { 01312 DBG << " Not modified."; 01313 modified = false; 01314 } 01315 DBG << endl; 01316 } 01317 else 01318 { 01319 WAR << "Could not get the reponse code." << endl; 01320 } 01321 01322 if (modified || infoRet != CURLE_OK) 01323 { 01324 // apply umask 01325 if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) ) 01326 { 01327 ERR << "Failed to chmod file " << destNew << endl; 01328 } 01329 if (::fclose( file )) 01330 { 01331 ERR << "Fclose failed for file '" << destNew << "'" << endl; 01332 ZYPP_THROW(MediaWriteException(destNew)); 01333 } 01334 // move the temp file into dest 01335 if ( rename( destNew, dest ) != 0 ) { 01336 ERR << "Rename failed" << endl; 01337 ZYPP_THROW(MediaWriteException(dest)); 01338 } 01339 } 01340 else 01341 { 01342 // close and remove the temp file 01343 ::fclose( file ); 01344 filesystem::unlink( destNew ); 01345 } 01346 01347 DBG << "done: " << PathInfo(dest) << endl; 01348 } 01349 01351 01352 void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const 01353 { 01354 DBG << filename.asString() << endl; 01355 01356 if(!_url.isValid()) 01357 ZYPP_THROW(MediaBadUrlException(_url)); 01358 01359 if(_url.getHost().empty()) 01360 ZYPP_THROW(MediaBadUrlEmptyHostException(_url)); 01361 01362 Url url(getFileUrl(filename)); 01363 01364 DBG << "URL: " << url.asString() << endl; 01365 // Use URL without options and without username and passwd 01366 // (some proxies dislike them in the URL). 01367 // Curl seems to need the just scheme, hostname and a path; 01368 // the rest was already passed as curl options (in attachTo). 01369 Url curlUrl( clearQueryString(url) ); 01370 01371 // 01372 // See also Bug #154197 and ftp url definition in RFC 1738: 01373 // The url "ftp://user@host/foo/bar/file" contains a path, 01374 // that is relative to the user's home. 01375 // The url "ftp://user@host//foo/bar/file" (or also with 01376 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file" 01377 // contains an absolute path. 01378 // 01379 string urlBuffer( curlUrl.asString()); 01380 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL, 01381 urlBuffer.c_str() ); 01382 if ( ret != 0 ) { 01383 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01384 } 01385 01386 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file ); 01387 if ( ret != 0 ) { 01388 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01389 } 01390 01391 // Set callback and perform. 01392 ProgressData progressData(_settings.timeout(), url, &report); 01393 if (!(options & OPTION_NO_REPORT_START)) 01394 report->start(url, dest); 01395 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) { 01396 WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;; 01397 } 01398 01399 ret = curl_easy_perform( _curl ); 01400 #if CURLVERSION_AT_LEAST(7,19,4) 01401 // bnc#692260: If the client sends a request with an If-Modified-Since header 01402 // with a future date for the server, the server may respond 200 sending a 01403 // zero size file. 01404 // curl-7.19.4 introduces CURLINFO_CONDITION_UNMET to check this condition. 01405 if ( ftell(file) == 0 && ret == 0 ) 01406 { 01407 long httpReturnCode = 33; 01408 if ( curl_easy_getinfo( _curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) == CURLE_OK && httpReturnCode == 200 ) 01409 { 01410 long conditionUnmet = 33; 01411 if ( curl_easy_getinfo( _curl, CURLINFO_CONDITION_UNMET, &conditionUnmet ) == CURLE_OK && conditionUnmet ) 01412 { 01413 WAR << "TIMECONDITION unmet - retry without." << endl; 01414 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); 01415 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); 01416 ret = curl_easy_perform( _curl ); 01417 } 01418 } 01419 } 01420 #endif 01421 01422 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) { 01423 WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;; 01424 } 01425 01426 if ( ret != 0 ) 01427 { 01428 ERR << "curl error: " << ret << ": " << _curlError 01429 << ", temp file size " << ftell(file) 01430 << " bytes." << endl; 01431 01432 // the timeout is determined by the progress data object 01433 // which holds wheter the timeout was reached or not, 01434 // otherwise it would be a user cancel 01435 try { 01436 evaluateCurlCode( filename, ret, progressData.reached); 01437 } 01438 catch ( const MediaException &e ) { 01439 // some error, we are not sure about file existence, rethrw 01440 ZYPP_RETHROW(e); 01441 } 01442 } 01443 01444 #if DETECT_DIR_INDEX 01445 if (!ret && detectDirIndex()) 01446 { 01447 ZYPP_THROW(MediaNotAFileException(_url, filename)); 01448 } 01449 #endif // DETECT_DIR_INDEX 01450 } 01451 01453 01454 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const 01455 { 01456 filesystem::DirContent content; 01457 getDirInfo( content, dirname, /*dots*/false ); 01458 01459 for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) { 01460 Pathname filename = dirname + it->name; 01461 int res = 0; 01462 01463 switch ( it->type ) { 01464 case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all 01465 case filesystem::FT_FILE: 01466 getFile( filename ); 01467 break; 01468 case filesystem::FT_DIR: // newer directory.yast contain at least directory info 01469 if ( recurse_r ) { 01470 getDir( filename, recurse_r ); 01471 } else { 01472 res = assert_dir( localPath( filename ) ); 01473 if ( res ) { 01474 WAR << "Ignore error (" << res << ") on creating local directory '" << localPath( filename ) << "'" << endl; 01475 } 01476 } 01477 break; 01478 default: 01479 // don't provide devices, sockets, etc. 01480 break; 01481 } 01482 } 01483 } 01484 01486 01487 void MediaCurl::getDirInfo( std::list<std::string> & retlist, 01488 const Pathname & dirname, bool dots ) const 01489 { 01490 getDirectoryYast( retlist, dirname, dots ); 01491 } 01492 01494 01495 void MediaCurl::getDirInfo( filesystem::DirContent & retlist, 01496 const Pathname & dirname, bool dots ) const 01497 { 01498 getDirectoryYast( retlist, dirname, dots ); 01499 } 01500 01502 01503 int MediaCurl::progressCallback( void *clientp, 01504 double dltotal, double dlnow, 01505 double ultotal, double ulnow) 01506 { 01507 ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp); 01508 if( pdata) 01509 { 01510 time_t now = time(NULL); 01511 if( now > 0) 01512 { 01513 // reset time of last change in case initial time() 01514 // failed or the time was adjusted (goes backward) 01515 if( pdata->ltime <= 0 || pdata->ltime > now) 01516 { 01517 pdata->ltime = now; 01518 } 01519 01520 // start time counting as soon as first data arrives 01521 // (skip the connection / redirection time at begin) 01522 time_t dif = 0; 01523 if (dlnow > 0 || ulnow > 0) 01524 { 01525 dif = (now - pdata->ltime); 01526 dif = dif > 0 ? dif : 0; 01527 01528 pdata->secs += dif; 01529 } 01530 01531 // update the drate_avg and drate_period only after a second has passed 01532 // (this callback is called much more often than a second) 01533 // otherwise the values would be far from accurate when measuring 01534 // the time in seconds 01536 01537 if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal )) 01538 pdata->drate_avg = (dlnow / pdata->secs); 01539 01540 if ( dif > 0 ) 01541 { 01542 pdata->drate_period = ((dlnow - pdata->dload_period) / dif); 01543 pdata->dload_period = dlnow; 01544 } 01545 } 01546 01547 // send progress report first, abort transfer if requested 01548 if( pdata->report) 01549 { 01550 if (!(*(pdata->report))->progress(int( dltotal ? dlnow * 100 / dltotal : 0 ), 01551 pdata->url, 01552 pdata->drate_avg, 01553 pdata->drate_period)) 01554 { 01555 return 1; // abort transfer 01556 } 01557 } 01558 01559 // check if we there is a timeout set 01560 if( pdata->timeout > 0) 01561 { 01562 if( now > 0) 01563 { 01564 bool progress = false; 01565 01566 // update download data if changed, mark progress 01567 if( dlnow != pdata->dload) 01568 { 01569 progress = true; 01570 pdata->dload = dlnow; 01571 pdata->ltime = now; 01572 } 01573 // update upload data if changed, mark progress 01574 if( ulnow != pdata->uload) 01575 { 01576 progress = true; 01577 pdata->uload = ulnow; 01578 pdata->ltime = now; 01579 } 01580 01581 if( !progress && (now >= (pdata->ltime + pdata->timeout))) 01582 { 01583 pdata->reached = true; 01584 return 1; // aborts transfer 01585 } 01586 } 01587 } 01588 } 01589 return 0; 01590 } 01591 01593 01594 string MediaCurl::getAuthHint() const 01595 { 01596 long auth_info = CURLAUTH_NONE; 01597 01598 CURLcode infoRet = 01599 curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info); 01600 01601 if(infoRet == CURLE_OK) 01602 { 01603 return CurlAuthData::auth_type_long2str(auth_info); 01604 } 01605 01606 return ""; 01607 } 01608 01610 01611 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const 01612 { 01614 Target_Ptr target = zypp::getZYpp()->getTarget(); 01615 CredentialManager cm(CredManagerOptions(target ? target->root() : "")); 01616 CurlAuthData_Ptr credentials; 01617 01618 // get stored credentials 01619 AuthData_Ptr cmcred = cm.getCred(_url); 01620 01621 if (cmcred && firstTry) 01622 { 01623 credentials.reset(new CurlAuthData(*cmcred)); 01624 DBG << "got stored credentials:" << endl << *credentials << endl; 01625 } 01626 // if not found, ask user 01627 else 01628 { 01629 01630 CurlAuthData_Ptr curlcred; 01631 curlcred.reset(new CurlAuthData()); 01632 callback::SendReport<AuthenticationReport> auth_report; 01633 01634 // preset the username if present in current url 01635 if (!_url.getUsername().empty() && firstTry) 01636 curlcred->setUsername(_url.getUsername()); 01637 // if CM has found some credentials, preset the username from there 01638 else if (cmcred) 01639 curlcred->setUsername(cmcred->username()); 01640 01641 // indicate we have no good credentials from CM 01642 cmcred.reset(); 01643 01644 string prompt_msg = boost::str(boost::format( 01646 _("Authentication required for '%s'")) % _url.asString()); 01647 01648 // set available authentication types from the exception 01649 // might be needed in prompt 01650 curlcred->setAuthType(availAuthTypes); 01651 01652 // ask user 01653 if (auth_report->prompt(_url, prompt_msg, *curlcred)) 01654 { 01655 DBG << "callback answer: retry" << endl 01656 << "CurlAuthData: " << *curlcred << endl; 01657 01658 if (curlcred->valid()) 01659 { 01660 credentials = curlcred; 01661 // if (credentials->username() != _url.getUsername()) 01662 // _url.setUsername(credentials->username()); 01670 } 01671 } 01672 else 01673 { 01674 DBG << "callback answer: cancel" << endl; 01675 } 01676 } 01677 01678 // set username and password 01679 if (credentials) 01680 { 01681 // HACK, why is this const? 01682 const_cast<MediaCurl*>(this)->_settings.setUsername(credentials->username()); 01683 const_cast<MediaCurl*>(this)->_settings.setPassword(credentials->password()); 01684 01685 // set username and password 01686 CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str()); 01687 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); 01688 01689 // set available authentication types from the exception 01690 if (credentials->authType() == CURLAUTH_NONE) 01691 credentials->setAuthType(availAuthTypes); 01692 01693 // set auth type (seems this must be set _after_ setting the userpwd) 01694 if (credentials->authType() != CURLAUTH_NONE) 01695 { 01696 // FIXME: only overwrite if not empty? 01697 const_cast<MediaCurl*>(this)->_settings.setAuthType(credentials->authTypeAsString()); 01698 ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType()); 01699 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); 01700 } 01701 01702 if (!cmcred) 01703 { 01704 credentials->setUrl(_url); 01705 cm.addCred(*credentials); 01706 cm.save(); 01707 } 01708 01709 return true; 01710 } 01711 01712 return false; 01713 } 01714 01715 01716 } // namespace media 01717 } // namespace zypp 01718 //