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