22#include <zypp-core/parser/Sysconfig>
28#include <zypp-curl/ProxyInfo>
29#include <zypp-curl/auth/CurlAuthData>
30#include <zypp-media/auth/CredentialManager>
31#include <zypp-curl/CurlConfig>
57 void updateStats( curl_off_t dltotal = 0.0, curl_off_t dlnow = 0.0 );
123 if ( dlnow && dlnow !=
_dnlNow )
176 const std::string & err_r,
177 const std::string & msg_r )
198#define SET_OPTION(opt,val) do { \
199 ret = curl_easy_setopt ( curl, opt, val ); \
201 ZYPP_THROW(MediaCurlSetOptException(_origin.at(rData.mirror).url(), _curlError)); \
205#define SET_OPTION_OFFT(opt,val) SET_OPTION(opt,(curl_off_t)val)
206#define SET_OPTION_LONG(opt,val) SET_OPTION(opt,(long)val)
207#define SET_OPTION_VOID(opt,val) SET_OPTION(opt,(void*)val)
210 const Pathname & attach_point_hint_r )
216 _multi = curl_multi_init();
220 MIL <<
"MediaCurl::MediaCurl(" << origin_r.
authority().
url() <<
", " << attach_point_hint_r <<
")" << endl;
228 char *atemp = ::strdup( apath.
asString().c_str());
231 atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
233 WAR <<
"attach point " << ainfo.
path()
237 else if( atest != NULL)
247 try {
release(); }
catch(...) {}
249 curl_multi_cleanup(
_multi);
268 curl_version_info_data *curl_info = NULL;
269 curl_info = curl_version_info(CURLVERSION_NOW);
271 if (curl_info->protocols)
273 const char *
const *
proto =
nullptr;
274 std::string scheme(
url.getScheme());
278 if( scheme == std::string((
const char *)*
proto))
283 std::string msg(
"Unsupported protocol '");
293 CURL *curl = rData.
curl;
296 curl_easy_reset ( curl );
301 CURLcode ret = curl_easy_setopt( curl, CURLOPT_ERRORBUFFER,
_curlError );
312 case 4:
SET_OPTION(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
break;
313 case 6:
SET_OPTION(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
break;
355#ifdef CURLSSLOPT_ALLOW_BEAST
357 ret = curl_easy_setopt( curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST );
366 SET_OPTION(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
384 if ( cred && cred->valid() ) {
401 std::string use_auth = settings.
authType();
402 if (use_auth.empty())
403 use_auth =
"digest,basic";
405 if( auth != CURLAUTH_NONE)
407 DBG <<
"Enabling HTTP authentication methods: " << use_auth
408 <<
" (CURLOPT_HTTPAUTH=" << auth <<
")" << std::endl;
415 DBG <<
"Proxy: '" << settings.
proxy() <<
"'" << endl;
417 SET_OPTION(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM );
427 if ( proxyuserpwd.empty() )
432 DBG <<
"Proxy: ~/.curlrc does not contain the proxy-user option" << endl;
436 DBG <<
"Proxy: using proxy-user from ~/.curlrc" << endl;
441 DBG <<
"Proxy: using provided proxy-user '" << settings.
proxyUsername() <<
"'" << endl;
444 if ( ! proxyuserpwd.empty() )
449#if CURLVERSION_AT_LEAST(7,19,4)
454 DBG <<
"Proxy: explicitly NOPROXY" << endl;
460 DBG <<
"Proxy: not explicitly set" << endl;
461 DBG <<
"Proxy: libcurl may look into the environment" << endl;
472#if CURLVERSION_AT_LEAST(7,15,5)
484 const auto &cookieFileParam =
_origin.at(rData.
mirror).url().getQueryParam(
"cookies" );
485 if ( !cookieFileParam.empty() &&
str::strToBool( cookieFileParam,
true ) )
488 MIL <<
"No cookies requested" << endl;
493#if CURLVERSION_AT_LEAST(7,18,0)
503 for (
const auto &header : settings.
headers() ) {
536 auto that =
const_cast<MediaCurl *
>(
this);
537 std::exception_ptr lastErr;
539 for (
unsigned mirr : mirrOrder ) {
541 return that->getFileCopyFromMirror ( mirr, srcFile,
target );
560 const auto &filename = srcFile.
filename();
569 AutoDispose<CURL*> curl( curl_easy_init(), []( CURL *hdl ) {
if ( hdl ) { curl_easy_cleanup(hdl); } } );
572 rData.mirror = mirror;
573 rData.curl = curl.
value ();
575 if( !myUrl.url().isValid() )
578 if( myUrl.url().getHost().empty() )
583 bool firstAuth =
true;
584 unsigned internalTry = 0;
585 static constexpr unsigned maxInternalTry = 3;
592 if( assert_dir( dest.
dirname() ) )
594 DBG <<
"assert_dir " << dest.
dirname() <<
" failed" << endl;
604 ERR <<
"out of memory for temp file name" << endl;
608 AutoFD tmp_fd { ::mkostemp( buf, O_CLOEXEC ) };
611 ERR <<
"mkstemp failed for file '" << destNew <<
"'" << endl;
616 file = ::fdopen( tmp_fd,
"we" );
619 ERR <<
"fopen failed for file '" << destNew <<
"'" << endl;
625 DBG <<
"dest: " << dest << endl;
626 DBG <<
"temp: " << destNew << endl;
633 curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
634 curl_easy_setopt(curl, CURLOPT_TIMEVALUE, (
long)
PathInfo(
target).mtime());
638 curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
639 curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 0L);
643 curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
644 curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 0L);
665 std::string urlBuffer( curlUrl.
asString());
666 CURLcode ret = curl_easy_setopt( curl, CURLOPT_URL,
672 ret = curl_easy_setopt( curl, CURLOPT_WRITEDATA, file.
value() );
679 report->start(fileurl, dest);
681 if ( curl_easy_setopt( curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
682 WAR <<
"Can't set CURLOPT_PROGRESSDATA: " <<
_curlError << endl;;
686 #if CURLVERSION_AT_LEAST(7,19,4)
691 if ( ftell(file) == 0 && ret == 0 )
693 long httpReturnCode = 33;
694 if ( curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) == CURLE_OK && httpReturnCode == 200 )
696 long conditionUnmet = 33;
697 if ( curl_easy_getinfo( curl, CURLINFO_CONDITION_UNMET, &conditionUnmet ) == CURLE_OK && conditionUnmet )
699 WAR <<
"TIMECONDITION unmet - retry without." << endl;
700 curl_easy_setopt( curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
701 curl_easy_setopt( curl, CURLOPT_TIMEVALUE, 0L);
708 if ( curl_easy_setopt( curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
709 WAR <<
"Can't unset CURLOPT_PROGRESSDATA: " <<
_curlError << endl;;
714 <<
", temp file size " << ftell(file)
715 <<
" bytes." << endl;
727 long httpReturnCode = 0;
728 CURLcode infoRet = curl_easy_getinfo(curl,
729 CURLINFO_RESPONSE_CODE,
731 bool modified =
true;
732 if (infoRet == CURLE_OK)
735 if ( httpReturnCode == 304
736 || ( httpReturnCode == 213 && (myUrl.url().getScheme() ==
"ftp" || myUrl.url().getScheme() ==
"tftp") ) )
738 DBG <<
" Not modified.";
745 WAR <<
"Could not get the response code." << endl;
748 if (modified || infoRet != CURLE_OK)
753 ERR <<
"Failed to chmod file " << destNew << endl;
757 if ( ::fclose( file ) )
759 ERR <<
"Fclose failed for file '" << destNew <<
"'" << endl;
764 if ( rename( destNew, dest ) != 0 ) {
765 ERR <<
"Rename failed" << endl;
790 if ( internalTry < maxInternalTry ) {
817 auto that =
const_cast<MediaCurl *
>(
this);
819 std::exception_ptr lastErr;
822 return that->doGetDoesFileExist( i, filename );
847 bool timeout_reached)
const
853 if (filename.
empty())
854 url = baseMirr.url();
862 case CURLE_UNSUPPORTED_PROTOCOL:
863 err =
" Unsupported protocol";
866 err +=
" or redirect (";
871 case CURLE_URL_MALFORMAT:
872 case CURLE_URL_MALFORMAT_USER:
875 case CURLE_LOGIN_DENIED:
879 case CURLE_HTTP_RETURNED_ERROR:
881 long httpReturnCode = 0;
882 CURLcode infoRet = curl_easy_getinfo( rData.
curl,
883 CURLINFO_RESPONSE_CODE,
885 if ( infoRet == CURLE_OK )
887 std::string msg =
"HTTP response: " +
str::numstring( httpReturnCode );
888 switch ( httpReturnCode )
894 DBG << msg <<
" Login failed (URL: " <<
url.asString() <<
")" << std::endl;
895 DBG <<
"MediaUnauthorizedException auth hint: '" << auth_hint <<
"'" << std::endl;
910 if (
url.getHost().find(
".suse.com") != std::string::npos )
911 msg403 =
_(
"Visit the SUSE Customer Center to check whether your registration is valid and has not expired.");
912 else if (
url.asString().find(
"novell.com") != std::string::npos)
913 msg403 =
_(
"Visit the Novell Customer Center to check whether your registration is valid and has not expired.");
921 DBG << msg <<
" (URL: " <<
url.asString() <<
")" << std::endl;
926 std::string msg =
"Unable to retrieve HTTP response:";
927 DBG << msg <<
" (URL: " <<
url.asString() <<
")" << std::endl;
932 case CURLE_FTP_COULDNT_RETR_FILE:
933#if CURLVERSION_AT_LEAST(7,16,0)
934 case CURLE_REMOTE_FILE_NOT_FOUND:
936 case CURLE_FTP_ACCESS_DENIED:
937 case CURLE_TFTP_NOTFOUND:
938 err =
"File not found";
941 case CURLE_BAD_PASSWORD_ENTERED:
942 case CURLE_FTP_USER_PASSWORD_INCORRECT:
943 err =
"Login failed";
945 case CURLE_COULDNT_RESOLVE_PROXY:
946 case CURLE_COULDNT_RESOLVE_HOST:
947 case CURLE_COULDNT_CONNECT:
948 case CURLE_FTP_CANT_GET_HOST:
949 err =
"Connection failed";
951 case CURLE_WRITE_ERROR:
954 case CURLE_PARTIAL_FILE:
955 case CURLE_OPERATION_TIMEDOUT:
956 timeout_reached =
true;
958 case CURLE_ABORTED_BY_CALLBACK:
959 if( timeout_reached )
961 err =
"Timeout reached";
991 AutoDispose<CURL*> curl( curl_easy_init(), []( CURL *hdl ) {
if ( hdl ) { curl_easy_cleanup(hdl); } } );
993 rData.mirror = mirror;
994 rData.curl = curl.
value ();
998 if( !myUrl.url().isValid() )
1001 if( myUrl.url().getHost().empty() )
1006 DBG <<
"URL: " <<
url.asString() << endl;
1021 std::string urlBuffer( curlUrl.
asString());
1024 bool canRetry =
true;
1025 bool firstAuth =
true;
1028 while ( canRetry ) {
1032 CURLcode ret = curl_easy_setopt( curl, CURLOPT_URL,
1033 urlBuffer.c_str() );
1038 AutoFILE file { ::fopen(
"/dev/null",
"w" ) };
1040 ERR <<
"fopen failed for /dev/null" << endl;
1044 ret = curl_easy_setopt( curl, CURLOPT_WRITEDATA, (*file) );
1055 const bool doHeadRequest = (myUrl.url().getScheme() ==
"http" || myUrl.url().getScheme() ==
"https") && settings.headRequestsAllowed();
1056 if ( doHeadRequest ) {
1057 curl_easy_setopt( curl, CURLOPT_NOBODY, 1L );
1059 curl_easy_setopt( curl, CURLOPT_RANGE,
"0-1" );
1064 MIL <<
"perform code: " << ok <<
" [ " << curl_easy_strerror(ok) <<
" ]" << endl;
1076 if (
authenticate( myUrl.url(), settings, e.hint(), firstAuth ) ) {
1084 return ( ok == CURLE_OK );
1112 long httpReturnCode = 0;
1113 if ( curl_easy_getinfo( pdata->
curl(), CURLINFO_RESPONSE_CODE, &httpReturnCode ) != CURLE_OK || httpReturnCode == 0 ) {
1114 return aliveCallback( clientp, dltotal, dlnow, ultotal, ulnow );
1121 if ( ( scheme ==
"http" || scheme ==
"https" ) && ( httpReturnCode < 200 || httpReturnCode > 299 ) ) {
1123 curl_easy_getinfo(pdata->
curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl);
1127 MIL <<
"Content length for HTTP message: " << httpReturnCode <<
" is bigger than our expected filesize, adjusting" << std::endl;
1130 MIL <<
"Content length for HTTP message: " << httpReturnCode <<
" is smaller or equal to our expected filesize, ignoring" << std::endl;
1133 MIL <<
"Content length for HTTP message: " << httpReturnCode <<
" is not known, setting it to max 2MB!" << std::endl;
1147 return pdata ? pdata->
curl() : 0;
1154 long auth_info = CURLAUTH_NONE;
1157 curl_easy_getinfo(curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
1159 if(infoRet == CURLE_OK)
1186 CURL *curl = rData.
curl;
1195 if ( curl_multi_add_handle(
_multi, curl ) != CURLM_OK )
1203 if (mcode != CURLM_OK)
1206 bool canContinue =
true;
1207 while ( canContinue ) {
1209 CURLMsg *msg =
nullptr;
1211 while ((msg = curl_multi_info_read(
_multi, &nqueue)) != 0) {
1212 if ( msg->msg != CURLMSG_DONE )
continue;
1213 if ( msg->easy_handle != curl )
continue;
1215 return msg->data.result;
1219 std::vector<GPollFD> requestedFds = _curlHelper.
socks;
1228 if (mcode != CURLM_OK)
1232 if (mcode != CURLM_OK)
Reference counted access to a Tp object calling a custom Dispose function when the last AutoDispose h...
reference value() const
Reference to the Tp object.
void resetDispose()
Set no dispose function.
Store and operate with byte count.
static const Unit MB
1000^2 Byte
std::string asUserHistory() const
A single (multiline) string composed of asUserString and historyAsString.
void addHistory(const std::string &msg_r)
Add some message text to the history.
Manages a data source characterized by an authoritative URL and a list of mirror URLs.
const OriginEndpoint & authority() const
const zypp::Url & url() const
ProgressData()
Ctor no range [0,0](0).
std::string getScheme() const
Returns the scheme name of the URL.
std::string asString() const
Returns a default string representation of the Url object.
static ZConfig & instance()
Singleton ctor.
Wrapper class for stat/lstat.
const Pathname & path() const
Return current Pathname.
Pathname dirname() const
Return all but the last component od this path.
const char * c_str() const
String representation.
const std::string & asString() const
String representation.
bool empty() const
Test for an empty path.
#define EXPLICITLY_NO_PROXY
size_t log_redirects_curl(char *ptr, size_t size, size_t nmemb, void *userdata)
void globalInitCurlOnce()
std::string curlUnEscape(const std::string &text_r)
void setupZYPP_MEDIA_CURL_DEBUG(CURL *curl)
Setup CURLOPT_VERBOSE and CURLOPT_DEBUGFUNCTION according to env::ZYPP_MEDIA_CURL_DEBUG.
CURLcode setCurlRedirProtocols(CURL *curl)
int ZYPP_MEDIA_CURL_IPRESOLVE()
4/6 to force IPv4/v6
mode_t applyUmaskTo(mode_t mode_r)
Modify mode_r according to the current umask ( mode_r & ~getUmask() ).
int assert_file_mode(const Pathname &path, unsigned mode)
Like assert_file but enforce mode even if the file already exists.
int unlink(const Pathname &path)
Like 'unlink'.
std::string numstring(char n, int w=0)
bool strToBool(const C_Str &str, bool default_r)
Parse str into a bool depending on the default value.
int zypp_poll(std::vector< GPollFD > &fds, int timeout)
Small wrapper around g_poll that additionally listens to the shutdown FD returned by ZYpp::shutdownSi...
Easy-to use interface to the ZYPP dependency resolver.
AutoDispose< const Pathname > ManagedFile
A Pathname plus associated cleanup code to be executed when path is no longer needed.
AutoDispose< void > OnScopeExit
CURLMcode handleSocketActions(const std::vector< GPollFD > &actionsFds, int first=0)
std::vector< GPollFD > socks
std::optional< long > timeout_ms
Bottleneck filtering all DownloadProgressReport issued from Media[Muli]Curl.
ByteCount _expectedFileSize
curl_off_t _dnlNow
Bytes downloaded now.
int _dnlPercent
Percent completed or 0 if _dnlTotal is unknown.
time_t _timeRcv
Start of no-data timeout.
ByteCount expectedFileSize() const
time_t _timeLast
Start last period(~1sec)
int reportProgress() const
double _drateLast
Download rate in last period.
bool timeoutReached() const
void expectedFileSize(ByteCount newval_r)
curl_off_t _dnlLast
Bytes downloaded at period start.
bool fileSizeExceeded() const
void updateStats(curl_off_t dltotal=0.0, curl_off_t dlnow=0.0)
double _drateTotal
Download rate so far.
zypp::callback::SendReport< zypp::media::DownloadProgressReport > * report
curl_off_t _dnlTotal
Bytes to download or 0 if unknown.
time_t _timeStart
Start total stats.
ProgressData(CURL *curl, time_t timeout=0, zypp::Url url=zypp::Url(), zypp::ByteCount expectedFileSize_r=0, zypp::callback::SendReport< zypp::media::DownloadProgressReport > *_report=nullptr)
AutoDispose<int> calling close
AutoDispose<FILE*> calling fclose
#define ZYPP_RETHROW(EXCPT)
Drops a logline and rethrows, updating the CodeLocation.
#define ZYPP_CAUGHT(EXCPT)
Drops a logline telling the Exception was caught (in order to handle it).
#define ZYPP_FWD_CURRENT_EXCPT()
Drops a logline and returns the current Exception as a std::exception_ptr.
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.