libzypp 17.31.23
request.cc
Go to the documentation of this file.
1/*---------------------------------------------------------------------\
2| ____ _ __ __ ___ |
3| |__ / \ / / . \ . \ |
4| / / \ V /| _/ _/ |
5| / /__ | | | | | | |
6| /_____||_| |_| |_| |
7| |
8----------------------------------------------------------------------*/
13#include <zypp-core/zyppng/base/EventDispatcher>
14#include <zypp-core/zyppng/base/private/linuxhelpers_p.h>
15#include <zypp-core/zyppng/core/String>
16#include <zypp-core/fs/PathInfo.h>
18#include <zypp-curl/CurlConfig>
19#include <zypp-curl/auth/CurlAuthData>
20#include <zypp-media/MediaConfig>
21#include <zypp-core/base/String.h>
22#include <zypp-core/base/StringV.h>
23#include <zypp-core/Pathname.h>
24#include <curl/curl.h>
25#include <stdio.h>
26#include <fcntl.h>
27#include <sstream>
28#include <utility>
29
30#include <iostream>
31#include <boost/variant.hpp>
32#include <boost/variant/polymorphic_get.hpp>
33
34
35namespace zyppng {
36
37 namespace {
38 static size_t nwr_headerCallback ( char *ptr, size_t size, size_t nmemb, void *userdata ) {
39 if ( !userdata )
40 return 0;
41
42 NetworkRequestPrivate *that = reinterpret_cast<NetworkRequestPrivate *>( userdata );
43 return that->headerCallback( ptr, size, nmemb );
44 }
45 static size_t nwr_writeCallback ( char *ptr, size_t size, size_t nmemb, void *userdata ) {
46 if ( !userdata )
47 return 0;
48
49 NetworkRequestPrivate *that = reinterpret_cast<NetworkRequestPrivate *>( userdata );
50 return that->writeCallback( ptr, size, nmemb );
51 }
52
53 //helper for std::visit
54 template<class T> struct always_false : std::false_type {};
55 }
56
57 std::vector<char> peek_data_fd( FILE *fd, off_t offset, size_t count )
58 {
59 if ( !fd )
60 return {};
61
62 fflush( fd );
63
64 std::vector<char> data( count + 1 , '\0' );
65
66 ssize_t l = -1;
67 while ((l = pread( fileno( fd ), data.data(), count, offset ) ) == -1 && errno == EINTR)
68 ;
69 if (l == -1)
70 return {};
71
72 return data;
73 }
74
75 NetworkRequest::Range NetworkRequest::Range::make(size_t start, size_t len, zyppng::NetworkRequest::DigestPtr &&digest, zyppng::NetworkRequest::CheckSumBytes &&expectedChkSum, std::any &&userData, std::optional<size_t> digestCompareLen, std::optional<size_t> dataBlockPadding )
76 {
78 .start = start,
79 .len = len,
80 .bytesWritten = 0,
81 ._digest = std::move( digest ),
82 ._checksum = std::move( expectedChkSum ),
83 ._relevantDigestLen = std::move( digestCompareLen ),
84 ._chksumPad = std::move( dataBlockPadding ),
85 .userData = std::move( userData ),
86 ._rangeState = State::Pending
87 };
88 }
89
91 : _outFile( std::move(prevState._outFile) )
92 , _downloaded( prevState._downloaded )
93 , _rangeAttemptIdx( prevState._rangeAttemptIdx )
94 { }
95
97 : _requireStatusPartial( prevState._requireStatusPartial )
98 { }
99
101 : _outFile( std::move(prevState._outFile) )
102 , _requireStatusPartial( true )
103 , _downloaded( prevState._downloaded )
104 , _rangeAttemptIdx( prevState._rangeAttemptIdx )
105 { }
106
108 : BasePrivate(p)
109 , _url ( std::move(url) )
110 , _targetFile ( std::move( targetFile) )
111 , _fMode ( std::move(fMode) )
112 , _headers( std::unique_ptr< curl_slist, decltype (&curl_slist_free_all) >( nullptr, &curl_slist_free_all ) )
113 { }
114
116 {
117 if ( _easyHandle ) {
118 //clean up for now, later we might reuse handles
119 curl_easy_cleanup( _easyHandle );
120 //reset in request but make sure the request was not enqueued again and got a new handle
121 _easyHandle = nullptr;
122 }
123 }
124
125 bool NetworkRequestPrivate::initialize( std::string &errBuf )
126 {
127 reset();
128
129 if ( _easyHandle )
130 //will reset to defaults but keep live connections, session ID and DNS caches
131 curl_easy_reset( _easyHandle );
132 else
133 _easyHandle = curl_easy_init();
134 return setupHandle ( errBuf );
135 }
136
137 bool NetworkRequestPrivate::setupHandle( std::string &errBuf )
138 {
140 curl_easy_setopt( _easyHandle, CURLOPT_ERRORBUFFER, this->_errorBuf.data() );
141
142 const std::string urlScheme = _url.getScheme();
143 if ( urlScheme == "http" || urlScheme == "https" )
145
146 try {
147
148 setCurlOption( CURLOPT_PRIVATE, this );
150 setCurlOption( CURLOPT_XFERINFODATA, this );
151 setCurlOption( CURLOPT_NOPROGRESS, 0L);
152 setCurlOption( CURLOPT_FAILONERROR, 1L);
153 setCurlOption( CURLOPT_NOSIGNAL, 1L);
154
155 std::string urlBuffer( _url.asString() );
156 setCurlOption( CURLOPT_URL, urlBuffer.c_str() );
157
158 setCurlOption( CURLOPT_WRITEFUNCTION, nwr_writeCallback );
159 setCurlOption( CURLOPT_WRITEDATA, this );
160
162 setCurlOption( CURLOPT_CONNECT_ONLY, 1L );
163 setCurlOption( CURLOPT_FRESH_CONNECT, 1L );
164 }
166 // instead of returning no data with NOBODY, we return
167 // little data, that works with broken servers, and
168 // works for ftp as well, because retrieving only headers
169 // ftp will return always OK code ?
170 // See http://curl.haxx.se/docs/knownbugs.html #58
172 setCurlOption( CURLOPT_NOBODY, 1L );
173 else
174 setCurlOption( CURLOPT_RANGE, "0-1" );
175 }
176
178 if ( _requestedRanges.size() ) {
179 if ( ! prepareNextRangeBatch ( errBuf ))
180 return false;
181 } else {
182 std::visit( [&]( auto &arg ){
183 using T = std::decay_t<decltype(arg)>;
184 if constexpr ( std::is_same_v<T, pending_t> ) {
185 arg._requireStatusPartial = false;
186 } else {
187 DBG << _easyHandle << " " << "NetworkRequestPrivate::setupHandle called in unexpected state" << std::endl;
188 }
189 }, _runningMode );
192 }
193 }
194
195 //make a local copy of the settings, so headers are not added multiple times
197
198 if ( _dispatcher ) {
199 locSet.setUserAgentString( _dispatcher->agentString().c_str() );
200
201 // add custom headers as configured (bsc#955801)
202 const auto &cHeaders = _dispatcher->hostSpecificHeaders();
203 if ( auto i = cHeaders.find(_url.getHost()); i != cHeaders.end() ) {
204 for ( const auto &[key, value] : i->second ) {
206 "%s: %s", key.c_str(), value.c_str() )
207 ));
208 }
209 }
210 }
211
212 locSet.addHeader("Pragma:");
213
216 {
217 case 4: setCurlOption( CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); break;
218 case 6: setCurlOption( CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6 ); break;
219 default: break;
220 }
221
222 setCurlOption( CURLOPT_HEADERFUNCTION, &nwr_headerCallback );
223 setCurlOption( CURLOPT_HEADERDATA, this );
224
228 setCurlOption( CURLOPT_CONNECTTIMEOUT, locSet.connectTimeout() );
229 // If a transfer timeout is set, also set CURLOPT_TIMEOUT to an upper limit
230 // just in case curl does not trigger its progress callback frequently
231 // enough.
232 if ( locSet.timeout() )
233 {
234 setCurlOption( CURLOPT_TIMEOUT, 3600L );
235 }
236
237 if ( urlScheme == "https" )
238 {
239#if CURLVERSION_AT_LEAST(7,19,4)
240 // restrict following of redirections from https to https only
241 if ( _url.getHost() == "download.opensuse.org" ) {
242#if CURLVERSION_AT_LEAST(7,85,0)
243 setCurlOption( CURLOPT_REDIR_PROTOCOLS_STR, "http,https" );
244#else
245 setCurlOption( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
246#endif
247 } else {
248#if CURLVERSION_AT_LEAST(7,85,0)
249 setCurlOption( CURLOPT_REDIR_PROTOCOLS_STR, "https" );
250#else
251 setCurlOption( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS );
252#endif
253 }
254#endif // #if CURLVERSION_AT_LEAST(7,19,4)
255
256 if( locSet.verifyPeerEnabled() ||
257 locSet.verifyHostEnabled() )
258 {
259 setCurlOption(CURLOPT_CAPATH, locSet.certificateAuthoritiesPath().c_str());
260 }
261
262 if( ! locSet.clientCertificatePath().empty() )
263 {
264 setCurlOption(CURLOPT_SSLCERT, locSet.clientCertificatePath().c_str());
265 }
266 if( ! locSet.clientKeyPath().empty() )
267 {
268 setCurlOption(CURLOPT_SSLKEY, locSet.clientKeyPath().c_str());
269 }
270
271#ifdef CURLSSLOPT_ALLOW_BEAST
272 // see bnc#779177
273 setCurlOption( CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST );
274#endif
275 setCurlOption(CURLOPT_SSL_VERIFYPEER, locSet.verifyPeerEnabled() ? 1L : 0L);
276 setCurlOption(CURLOPT_SSL_VERIFYHOST, locSet.verifyHostEnabled() ? 2L : 0L);
277 // bnc#903405 - POODLE: libzypp should only talk TLS
278 setCurlOption(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
279 }
280
281 // follow any Location: header that the server sends as part of
282 // an HTTP header (#113275)
283 setCurlOption( CURLOPT_FOLLOWLOCATION, 1L);
284 // 3 redirects seem to be too few in some cases (bnc #465532)
285 setCurlOption( CURLOPT_MAXREDIRS, 6L );
286
287 //set the user agent
288 setCurlOption(CURLOPT_USERAGENT, locSet.userAgentString().c_str() );
289
290
291 /*---------------------------------------------------------------*
292 CURLOPT_USERPWD: [user name]:[password]
293 Url::username/password -> CURLOPT_USERPWD
294 If not provided, anonymous FTP identification
295 *---------------------------------------------------------------*/
296 if ( locSet.userPassword().size() )
297 {
298 setCurlOption(CURLOPT_USERPWD, locSet.userPassword().c_str());
299 std::string use_auth = _settings.authType();
300 if (use_auth.empty())
301 use_auth = "digest,basic"; // our default
303 if( auth != CURLAUTH_NONE)
304 {
305 DBG << _easyHandle << " " << "Enabling HTTP authentication methods: " << use_auth
306 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
307 setCurlOption(CURLOPT_HTTPAUTH, auth);
308 }
309 }
310
311 if ( locSet.proxyEnabled() && ! locSet.proxy().empty() )
312 {
313 DBG << _easyHandle << " " << "Proxy: '" << locSet.proxy() << "'" << std::endl;
314 setCurlOption(CURLOPT_PROXY, locSet.proxy().c_str());
315 setCurlOption(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM );
316
317 /*---------------------------------------------------------------*
318 * CURLOPT_PROXYUSERPWD: [user name]:[password]
319 *
320 * Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
321 * If not provided, $HOME/.curlrc is evaluated
322 *---------------------------------------------------------------*/
323
324 std::string proxyuserpwd = locSet.proxyUserPassword();
325
326 if ( proxyuserpwd.empty() )
327 {
329 zypp::media::CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
330 if ( curlconf.proxyuserpwd.empty() )
331 DBG << _easyHandle << " " << "Proxy: ~/.curlrc does not contain the proxy-user option" << std::endl;
332 else
333 {
334 proxyuserpwd = curlconf.proxyuserpwd;
335 DBG << _easyHandle << " " << "Proxy: using proxy-user from ~/.curlrc" << std::endl;
336 }
337 }
338 else
339 {
340 DBG << _easyHandle << " " << _easyHandle << " " << "Proxy: using provided proxy-user '" << _settings.proxyUsername() << "'" << std::endl;
341 }
342
343 if ( ! proxyuserpwd.empty() )
344 {
345 setCurlOption(CURLOPT_PROXYUSERPWD, ::internal::curlUnEscape( proxyuserpwd ).c_str());
346 }
347 }
348#if CURLVERSION_AT_LEAST(7,19,4)
349 else if ( locSet.proxy() == EXPLICITLY_NO_PROXY )
350 {
351 // Explicitly disabled in URL (see fillSettingsFromUrl()).
352 // This should also prevent libcurl from looking into the environment.
353 DBG << _easyHandle << " " << "Proxy: explicitly NOPROXY" << std::endl;
354 setCurlOption(CURLOPT_NOPROXY, "*");
355 }
356
357#endif
358 // else: Proxy: not explicitly set; libcurl may look into the environment
359
361 if ( locSet.minDownloadSpeed() != 0 )
362 {
363 setCurlOption(CURLOPT_LOW_SPEED_LIMIT, locSet.minDownloadSpeed());
364 // default to 10 seconds at low speed
365 setCurlOption(CURLOPT_LOW_SPEED_TIME, 60L);
366 }
367
368#if CURLVERSION_AT_LEAST(7,15,5)
369 if ( locSet.maxDownloadSpeed() != 0 )
370 setCurlOption(CURLOPT_MAX_RECV_SPEED_LARGE, locSet.maxDownloadSpeed());
371#endif
372
374 if ( zypp::str::strToBool( _url.getQueryParam( "cookies" ), true ) )
375 setCurlOption( CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
376 else
377 MIL << _easyHandle << " " << "No cookies requested" << std::endl;
378 setCurlOption(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
379
380#if CURLVERSION_AT_LEAST(7,18,0)
381 // bnc #306272
382 setCurlOption(CURLOPT_PROXY_TRANSFER_MODE, 1L );
383#endif
384
385 // Append settings custom headers to curl.
386 // TransferSettings assert strings are trimmed (HTTP/2 RFC 9113)
387 for ( const auto &header : locSet.headers() ) {
388 if ( !z_func()->addRequestHeader( header.c_str() ) )
390 }
391
392 if ( _headers )
393 setCurlOption( CURLOPT_HTTPHEADER, _headers.get() );
394
395 return true;
396
397 } catch ( const zypp::Exception &excp ) {
398 ZYPP_CAUGHT(excp);
399 errBuf = excp.asString();
400 }
401 return false;
402 }
403
405 {
406 auto rmode = std::get_if<NetworkRequestPrivate::running_t>( &_runningMode );
407 if ( !rmode ) {
408 DBG << _easyHandle << "Can only create output file in running mode" << std::endl;
409 return false;
410 }
411 // if we have no open file create or open it
412 if ( !rmode->_outFile ) {
413 std::string openMode = "w+b";
415 openMode = "r+b";
416
417 rmode->_outFile = fopen( _targetFile.asString().c_str() , openMode.c_str() );
418
419 //if the file does not exist create a new one
420 if ( !rmode->_outFile && _fMode == NetworkRequest::WriteShared ) {
421 rmode->_outFile = fopen( _targetFile.asString().c_str() , "w+b" );
422 }
423
424 if ( !rmode->_outFile ) {
426 ,zypp::str::Format("Unable to open target file (%1%). Errno: (%2%:%3%)") % _targetFile.asString() % errno % strerr_cxx() );
427 return false;
428 }
429 }
430
431 return true;
432 }
433
435 {
436 // We can recover from RangeFail errors if we have more batch sizes to try
437 auto rmode = std::get_if<NetworkRequestPrivate::running_t>( &_runningMode );
438 if ( rmode->_cachedResult && rmode->_cachedResult->type() == NetworkRequestError::RangeFail )
439 return ( rmode->_rangeAttemptIdx + 1 < sizeof( _rangeAttempt ) ) && hasMoreWork();
440 return false;
441 }
442
444 {
445 auto rmode = std::get_if<NetworkRequestPrivate::running_t>( &_runningMode );
446
447 if ( hasMoreWork() ) {
448 // go to the next range batch level if we are restarted due to a failed range request
449 if ( rmode->_cachedResult && rmode->_cachedResult->type() == NetworkRequestError::RangeFail ) {
450 if ( rmode->_rangeAttemptIdx + 1 >= sizeof( _rangeAttempt ) ) {
451 errBuf = "No more range batch sizes available";
452 return false;
453 }
454 rmode->_rangeAttemptIdx++;
455 }
456
457 _runningMode = prepareNextRangeBatch_t( std::move(std::get<running_t>( _runningMode )) );
458
459 // we reset the handle to default values. We do this to not run into
460 // "transfer closed with outstanding read data remaining" error CURL sometimes returns when
461 // we cancel a connection because of a range error to request a smaller batch.
462 // The error will still happen but much less frequently than without resetting the handle.
463 //
464 // Note: Even creating a new handle will NOT fix the issue
465 curl_easy_reset( _easyHandle );
466 if ( !setupHandle (errBuf) )
467 return false;
468 return true;
469 }
470 errBuf = "Request has no more work";
471 return false;
472
473 }
474
476 {
477 if ( _requestedRanges.size() == 0 ) {
478 errBuf = "Calling the prepareNextRangeBatch function without a range to download is not supported.";
479 return false;
480 }
481
482 std::string rangeDesc;
483 uint rangesAdded = 0;
484 if ( _requestedRanges.size() > 1 && _protocolMode != ProtocolMode::HTTP ) {
485 errBuf = "Using more than one range is not supported with protocols other than HTTP/HTTPS";
486 return false;
487 }
488
489 // check if we have one big range convering the whole file
490 if ( _requestedRanges.size() == 1 && _requestedRanges.front().start == 0 && _requestedRanges.front().len == 0 ) {
491 if ( !std::holds_alternative<pending_t>( _runningMode ) ) {
492 errBuf = zypp::str::Str() << "Unexpected state when calling prepareNextRangeBatch " << _runningMode.index ();
493 return false;
494 }
495
497 std::get<pending_t>( _runningMode )._requireStatusPartial = false;
498
499 } else {
500 std::sort( _requestedRanges.begin(), _requestedRanges.end(), []( const auto &elem1, const auto &elem2 ){
501 return ( elem1.start < elem2.start );
502 });
503
504 if ( std::holds_alternative<pending_t>( _runningMode ) )
505 std::get<pending_t>( _runningMode )._requireStatusPartial = true;
506
507 auto maxRanges = _rangeAttempt[0];
508 if ( std::holds_alternative<prepareNextRangeBatch_t>( _runningMode ) )
509 maxRanges = _rangeAttempt[std::get<prepareNextRangeBatch_t>( _runningMode )._rangeAttemptIdx];
510
511 // helper function to build up the request string for the range
512 auto addRangeString = [ &rangeDesc, &rangesAdded ]( const std::pair<size_t, size_t> &range ) {
513 std::string rangeD = zypp::str::form("%llu-", static_cast<unsigned long long>( range.first ) );
514 if( range.second > 0 )
515 rangeD.append( zypp::str::form( "%llu", static_cast<unsigned long long>( range.second ) ) );
516
517 if ( rangeDesc.size() )
518 rangeDesc.append(",").append( rangeD );
519 else
520 rangeDesc = std::move( rangeD );
521
522 rangesAdded++;
523 };
524
525 std::optional<std::pair<size_t, size_t>> currentZippedRange;
526 bool closedRange = true;
527 for ( auto &range : _requestedRanges ) {
528
529 if ( range._rangeState != NetworkRequest::Pending )
530 continue;
531
532 //reset the download results
533 range.bytesWritten = 0;
534
535 //when we have a open range in the list of ranges we will get from start of range to end of file,
536 //all following ranges would never be marked as valid, so we have to fail early
537 if ( !closedRange ) {
538 errBuf = "It is not supported to request more ranges after a open range.";
539 return false;
540 }
541
542 const auto rangeEnd = range.len > 0 ? range.start + range.len - 1 : 0;
543 closedRange = (rangeEnd > 0);
544
545 // remember this range was already requested
546 range._rangeState = NetworkRequest::Running;
547 range.bytesWritten = 0;
548 if ( range._digest )
549 range._digest->reset();
550
551 // we try to compress the requested ranges into as big chunks as possible for the request,
552 // when receiving we still track the original ranges so we can collect and test their checksums
553 if ( !currentZippedRange ) {
554 currentZippedRange = std::make_pair( range.start, rangeEnd );
555 } else {
556 //range is directly consecutive to the previous range
557 if ( currentZippedRange->second + 1 == range.start ) {
558 currentZippedRange->second = rangeEnd;
559 } else {
560 //this range does not directly follow the previous one, we build the string and start a new one
561 addRangeString( *currentZippedRange );
562 currentZippedRange = std::make_pair( range.start, rangeEnd );
563 }
564 }
565
566 if ( rangesAdded >= maxRanges ) {
567 MIL << _easyHandle << " " << "Reached max nr of ranges (" << maxRanges << "), batching the request to not break the server" << std::endl;
568 break;
569 }
570 }
571
572 // add the last range too
573 if ( currentZippedRange )
574 addRangeString( *currentZippedRange );
575
576 MIL << _easyHandle << " " << "Requesting Ranges: " << rangeDesc << std::endl;
577
578 setCurlOption( CURLOPT_RANGE, rangeDesc.c_str() );
579 }
580
581 return true;
582 }
583
585 {
586 // check if we have ranges that have never been requested
587 return std::any_of( _requestedRanges.begin(), _requestedRanges.end(), []( const auto &range ){ return range._rangeState == NetworkRequest::Pending; });
588 }
589
591 {
592 bool isRangeContinuation = std::holds_alternative<prepareNextRangeBatch_t>( _runningMode );
593 if ( isRangeContinuation ) {
594 MIL << _easyHandle << " " << "Continuing a previously started range batch." << std::endl;
595 _runningMode = running_t( std::move(std::get<prepareNextRangeBatch_t>( _runningMode )) );
596 } else {
597 auto mode = running_t( std::move(std::get<pending_t>( _runningMode )) );
598 if ( _requestedRanges.size() == 1 && _requestedRanges.front().start == 0 && _requestedRanges.front().len == 0 )
599 mode._currentRange = 0;
600
601 _runningMode = std::move(mode);
602 }
603
604 auto &m = std::get<running_t>( _runningMode );
605
606 if ( m._activityTimer ) {
607 DBG_MEDIA << _easyHandle << " Setting activity timeout to: " << _settings.timeout() << std::endl;
608 m._activityTimer->connect( &Timer::sigExpired, *this, &NetworkRequestPrivate::onActivityTimeout );
609 m._activityTimer->start( static_cast<uint64_t>( _settings.timeout() * 1000 ) );
610 }
611
612 if ( !isRangeContinuation )
613 _sigStarted.emit( *z_func() );
614 }
615
617 {
618 if ( std::holds_alternative<running_t>(_runningMode) ) {
619 auto &rmode = std::get<running_t>( _runningMode );
620 // if we still have a current range set it valid by checking the checksum
621 if ( rmode._currentRange >= 0 ) {
622 auto &currR = _requestedRanges[rmode._currentRange];
623 rmode._currentRange = -1;
624 validateRange( currR );
625 }
626 }
627 }
628
630 {
631
632 finished_t resState;
633 resState._result = std::move(err);
634
635 if ( std::holds_alternative<running_t>(_runningMode) ) {
636
637 auto &rmode = std::get<running_t>( _runningMode );
638 rmode._outFile.reset();
639 resState._downloaded = rmode._downloaded;
640 resState._contentLenght = rmode._contentLenght;
641
643 //we have a successful download lets see if we got everything we needed
644 for ( const auto &r : _requestedRanges ) {
645 if ( r._rangeState != NetworkRequest::Finished ) {
646 if ( r.len > 0 && r.bytesWritten != r.len )
647 resState._result = NetworkRequestErrorPrivate::customError( NetworkRequestError::MissingData, (zypp::str::Format("Did not receive all requested data from the server ( off: %1%, req: %2%, recv: %3% ).") % r.start % r.len % r.bytesWritten ) );
648 else if ( r._digest && r._checksum.size() && ! checkIfRangeChkSumIsValid(r) ) {
649 resState._result = NetworkRequestErrorPrivate::customError( NetworkRequestError::InvalidChecksum, (zypp::str::Format("Invalid checksum %1%, expected checksum %2%") % r._digest->digest() % zypp::Digest::digestVectorToString( r._checksum ) ) );
650 } else {
652 }
653 //we only report the first error
654 break;
655 }
656 }
657 }
658 }
659
660 _runningMode = std::move( resState );
661 _sigFinished.emit( *z_func(), std::get<finished_t>(_runningMode)._result );
662 }
663
665 {
667 _headers.reset( nullptr );
668 _errorBuf.fill( 0 );
670 std::for_each( _requestedRanges.begin (), _requestedRanges.end(), []( auto &range ) {
671 range._rangeState = NetworkRequest::Pending;
672 });
673 }
674
676 {
677 auto &m = std::get<running_t>( _runningMode );
678
679 MIL_MEDIA << _easyHandle << " Request timeout interval: " << t.interval()<< " remaining: " << t.remaining() << std::endl;
680 std::map<std::string, boost::any> extraInfo;
681 extraInfo.insert( {"requestUrl", _url } );
682 extraInfo.insert( {"filepath", _targetFile } );
683 _dispatcher->cancel( *z_func(), NetworkRequestErrorPrivate::customError( NetworkRequestError::Timeout, "Download timed out", std::move(extraInfo) ) );
684 }
685
687 {
688 if ( rng._digest && rng._checksum.size() ) {
689 auto bytesHashed = rng._digest->bytesHashed ();
690 if ( rng._chksumPad && *rng._chksumPad > bytesHashed ) {
691 MIL_MEDIA << _easyHandle << " " << "Padding the digest to required block size" << std::endl;
692 zypp::ByteArray padding( *rng._chksumPad - bytesHashed, '\0' );
693 rng._digest->update( padding.data(), padding.size() );
694 }
695 auto digVec = rng._digest->digestVector();
696 if ( rng._relevantDigestLen ) {
697 digVec.resize( *rng._relevantDigestLen );
698 }
699 return ( digVec == rng._checksum );
700 }
701
702 // no checksum required
703 return true;
704 }
705
707 {
708 if ( rng._digest && rng._checksum.size() ) {
709 if ( ( rng.len == 0 || rng.bytesWritten == rng.len ) && checkIfRangeChkSumIsValid(rng) )
711 else
713 } else {
714 if ( rng.len == 0 ? true : rng.bytesWritten == rng.len )
716 else
718 }
719 }
720
721 bool NetworkRequestPrivate::parseContentRangeHeader(const std::string_view &line, size_t &start, size_t &len )
722 { //content-range: bytes 10485760-19147879/19147880
723 static const zypp::str::regex regex("^Content-Range:[[:space:]]+bytes[[:space:]]+([0-9]+)-([0-9]+)\\/([0-9]+)$", zypp::str::regex::rxdefault | zypp::str::regex::icase );
724
726 if( !zypp::str::regex_match( std::string(line), what, regex ) || what.size() != 4 ) {
727 DBG << _easyHandle << " " << "Invalid Content-Range Header format: '" << std::string(line) << std::endl;
728 return false;
729 }
730
731 size_t s = zypp::str::strtonum<size_t>( what[1]);
732 size_t e = zypp::str::strtonum<size_t>( what[2]);
733 start = std::move(s);
734 len = ( e - s ) + 1;
735 return true;
736 }
737
738 bool NetworkRequestPrivate::parseContentTypeMultiRangeHeader(const std::string_view &line, std::string &boundary)
739 {
740 static const zypp::str::regex regex("^Content-Type:[[:space:]]+multipart\\/byteranges;[[:space:]]+boundary=(.*)$", zypp::str::regex::rxdefault | zypp::str::regex::icase );
741
743 if( zypp::str::regex_match( std::string(line), what, regex ) ) {
744 if ( what.size() >= 2 ) {
745 boundary = what[1];
746 return true;
747 }
748 }
749 return false;
750 }
751
753 {
754 return std::string( _errorBuf.data() );
755 }
756
758 {
759 if ( std::holds_alternative<running_t>( _runningMode ) ){
760 auto &rmode = std::get<running_t>( _runningMode );
761 if ( rmode._activityTimer && rmode._activityTimer->isRunning() )
762 rmode._activityTimer->start();
763 }
764 }
765
766 int NetworkRequestPrivate::curlProgressCallback( void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow )
767 {
768 if ( !clientp )
769 return CURLE_OK;
770 NetworkRequestPrivate *that = reinterpret_cast<NetworkRequestPrivate *>( clientp );
771
772 if ( !std::holds_alternative<running_t>(that->_runningMode) ){
773 DBG << that->_easyHandle << " " << "Curl progress callback was called in invalid state "<< that->z_func()->state() << std::endl;
774 return -1;
775 }
776
777 auto &rmode = std::get<running_t>( that->_runningMode );
778
779 //reset the timer
780 that->resetActivityTimer();
781
782 rmode._isInCallback = true;
783 if ( rmode._lastProgressNow != dlnow ) {
784 rmode._lastProgressNow = dlnow;
785 that->_sigProgress.emit( *that->z_func(), dltotal, dlnow, ultotal, ulnow );
786 }
787 rmode._isInCallback = false;
788
789 return rmode._cachedResult ? CURLE_ABORTED_BY_CALLBACK : CURLE_OK;
790 }
791
792 size_t NetworkRequestPrivate::headerCallback(char *ptr, size_t size, size_t nmemb)
793 {
794 //it is valid to call this function with no data to write, just return OK
795 if ( size * nmemb == 0)
796 return 0;
797
799
801
802 std::string_view hdr( ptr, size*nmemb );
803
804 hdr.remove_prefix( std::min( hdr.find_first_not_of(" \t\r\n"), hdr.size() ) );
805 const auto lastNonWhitespace = hdr.find_last_not_of(" \t\r\n");
806 if ( lastNonWhitespace != hdr.npos )
807 hdr.remove_suffix( hdr.size() - (lastNonWhitespace + 1) );
808 else
809 hdr = std::string_view();
810
811 auto &rmode = std::get<running_t>( _runningMode );
812 if ( !hdr.size() ) {
813 return ( size * nmemb );
814 }
815 if ( zypp::strv::hasPrefixCI( hdr, "HTTP/" ) ) {
816
817 long statuscode = 0;
818 (void)curl_easy_getinfo( _easyHandle, CURLINFO_RESPONSE_CODE, &statuscode);
819
820 const auto &doRangeFail = [&](){
821 WAR << _easyHandle << " " << "Range FAIL, trying with a smaller batch" << std::endl;
822 rmode._cachedResult = NetworkRequestErrorPrivate::customError( NetworkRequestError::RangeFail, "Expected range status code 206, but got none." );
823
824 // reset all ranges we requested to pending, we never got the data for them
825 std::for_each( _requestedRanges.begin (), _requestedRanges.end(), []( auto &range ) {
826 if ( range._rangeState == NetworkRequest::Running )
827 range._rangeState = NetworkRequest::Pending;
828 });
829 return 0;
830 };
831
832 // if we have a status 204 we need to create a empty file
833 if( statuscode == 204 && !( _options & NetworkRequest::ConnectionTest ) && !( _options & NetworkRequest::HeadRequest ) )
835
836 if ( rmode._requireStatusPartial ) {
837 // ignore other status codes, maybe we are redirected etc.
838 if ( ( statuscode >= 200 && statuscode <= 299 && statuscode != 206 )
839 || statuscode == 416 ) {
840 return doRangeFail();
841 }
842 }
843
844 } else if ( zypp::strv::hasPrefixCI( hdr, "Location:" ) ) {
845 _lastRedirect = hdr.substr( 9 );
846 DBG << _easyHandle << " " << "redirecting to " << _lastRedirect << std::endl;
847
848 } else if ( zypp::strv::hasPrefixCI( hdr, "Content-Type:") ) {
849 std::string sep;
850 if ( parseContentTypeMultiRangeHeader( hdr, sep ) ) {
851 rmode._gotMultiRangeHeader = true;
852 rmode._seperatorString = "--"+sep;
853 }
854 } else if ( zypp::strv::hasPrefixCI( hdr, "Content-Range:") ) {
856 if ( !parseContentRangeHeader( hdr, r.start, r.len) ) {
857 rmode._cachedResult = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, "Invalid Content-Range header format." );
858 return 0;
859 }
860 DBG << _easyHandle << " " << "Got content range :" << r.start << " len " << r.len << std::endl;
861 rmode._gotContentRangeHeader = true;
862 rmode._currentSrvRange = r;
863
864 } else if ( zypp::strv::hasPrefixCI( hdr, "Content-Length:") ) {
865 auto lenStr = str::trim( hdr.substr( 15 ), zypp::str::TRIM );
866 auto str = std::string ( lenStr.data(), lenStr.length() );
867 auto len = zypp::str::strtonum<typename zypp::ByteCount::SizeType>( str.data() );
868 if ( len > 0 ) {
869 DBG << _easyHandle << " " << "Got Content-Length Header: " << len << std::endl;
870 rmode._contentLenght = zypp::ByteCount(len, zypp::ByteCount::B);
871 }
872 }
873 }
874
875 return ( size * nmemb );
876 }
877
878 size_t NetworkRequestPrivate::writeCallback(char *ptr, size_t size, size_t nmemb)
879 {
880 const auto max = ( size * nmemb );
881
883
884 //it is valid to call this function with no data to write, just return OK
885 if ( max == 0)
886 return 0;
887
888 //in case of a HEAD request, we do not write anything
890 return ( size * nmemb );
891 }
892
893 auto &rmode = std::get<running_t>( _runningMode );
894
895 auto writeDataToFile = [ this, &rmode ]( off_t offset, const char *data, size_t len ) -> off_t {
896
897 if ( rmode._currentRange < 0 ) {
898 DBG << _easyHandle << " " << "Current range is zero in write request" << std::endl;
899 return 0;
900 }
901
902 // if we have no open file create or open it
903 if ( !assertOutputFile() )
904 return 0;
905
906 // seek to the given offset
907 if ( offset >= 0 ) {
908 if ( fseek( rmode._outFile, offset, SEEK_SET ) != 0 ) {
910 "Unable to set output file pointer." );
911 return 0;
912 }
913 }
914
915 auto &rng = _requestedRanges[ rmode._currentRange ];
916 const auto bytesToWrite = rng.len > 0 ? std::min( rng.len - rng.bytesWritten, len ) : len;
917
918 //make sure we do not write after the expected file size
919 if ( _expectedFileSize && _expectedFileSize <= static_cast<zypp::ByteCount::SizeType>(rng.start + rng.bytesWritten + bytesToWrite) ) {
920 rmode._cachedResult = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, "Downloaded data exceeds expected length." );
921 return 0;
922 }
923
924 auto written = fwrite( data, 1, bytesToWrite, rmode._outFile );
925 if ( written == 0 )
926 return 0;
927
928 if ( rng._digest && rng._checksum.size() ) {
929 if ( !rng._digest->update( data, written ) )
930 return 0;
931 }
932
933 rng.bytesWritten += written;
934 if ( rmode._currentSrvRange ) rmode._currentSrvRange->bytesWritten += written;
935
936 if ( rng.len > 0 && rng.bytesWritten >= rng.len ) {
937 rmode._currentRange = -1;
938 validateRange( rng );
939 }
940
941 if ( rmode._currentSrvRange && rmode._currentSrvRange->len > 0 && rmode._currentSrvRange->bytesWritten >= rmode._currentSrvRange->len ) {
942 rmode._currentSrvRange.reset();
943 // we ran out of data in the current chunk, reset the target range as well because next data will be
944 // a chunk header again
945 rmode._currentRange = -1;
946 }
947
948 // count the number of real bytes we have downloaded so far
949 rmode._downloaded += written;
950 _sigBytesDownloaded.emit( *z_func(), rmode._downloaded );
951
952 return written;
953 };
954
955 // we are currenty writing a range, continue until we hit the end of the requested chunk, or if we hit end of data
956 size_t bytesWrittenSoFar = 0;
957
958 while ( bytesWrittenSoFar != max ) {
959
960 off_t seekTo = -1;
961
962 // this is called after all headers have been processed
963 if ( !rmode._allHeadersReceived ) {
964 rmode._allHeadersReceived = true;
965
966 // no ranges at all, must be a normal download
967 if ( !rmode._gotMultiRangeHeader && !rmode._gotContentRangeHeader ) {
968
969 if ( rmode._requireStatusPartial ) {
970 //we got a invalid response, the status code pointed to being partial but we got no range definition
972 "Invalid data from server, range respone was announced but there was no range definiton." );
973 return 0;
974 }
975
976 //we always download a range even if it is not explicitly requested
977 if ( _requestedRanges.empty() ) {
980 }
981
982 rmode._currentRange = 0;
983 seekTo = _requestedRanges[0].start;
984 }
985 }
986
987 if ( rmode._currentSrvRange && rmode._currentRange == -1 ) {
988 //if we enter this branch, we just have finished writing a requested chunk but
989 //are still inside a chunk that was sent by the server, due to the std the server can coalesce requested ranges
990 //to optimize downloads we need to find the best match ( because the current offset might not even be in our requested ranges )
991 //Or we just parsed a Content-Lenght header and start a new block
992
993 std::optional<uint> foundRange;
994 const size_t beginRange = rmode._currentSrvRange->start + rmode._currentSrvRange->bytesWritten;
995 const size_t endRange = beginRange + (rmode._currentSrvRange->len - rmode._currentSrvRange->bytesWritten);
996 auto currDist = ULONG_MAX;
997 for ( uint i = 0; i < _requestedRanges.size(); i++ ) {
998 const auto &currR = _requestedRanges[i];
999
1000 // do not allow double ranges
1001 if ( currR._rangeState == NetworkRequest::Finished || currR._rangeState == NetworkRequest::Error )
1002 continue;
1003
1004 // check if the range was already written
1005 if ( currR.len == currR.bytesWritten )
1006 continue;
1007
1008 const auto currRBegin = currR.start + currR.bytesWritten;
1009 if ( !( beginRange <= currRBegin && endRange >= currRBegin ) )
1010 continue;
1011
1012 // calculate the distance of the current ranges offset+data written to the range we got back from the server
1013 const auto newDist = currRBegin - beginRange;
1014
1015 if ( !foundRange ) {
1016 foundRange = i;
1017 currDist = newDist;
1018 } else {
1019 //pick the range with the closest distance
1020 if ( newDist < currDist ) {
1021 foundRange = i;
1022 currDist = newDist;
1023 }
1024 }
1025 }
1026 if ( !foundRange ) {
1028 , "Unable to find a matching range for data returned by the server." );
1029 return 0;
1030 }
1031
1032 //set the found range as the current one
1033 rmode._currentRange = *foundRange;
1034
1035 //continue writing where we stopped
1036 seekTo = _requestedRanges[*foundRange].start + _requestedRanges[*foundRange].bytesWritten;
1037
1038 //if we skip bytes we need to advance our written bytecount
1039 const auto skipBytes = seekTo - beginRange;
1040 bytesWrittenSoFar += skipBytes;
1041 rmode._currentSrvRange->bytesWritten += skipBytes;
1042 }
1043
1044 if ( rmode._currentRange >= 0 ) {
1045 auto availableData = max - bytesWrittenSoFar;
1046 if ( rmode._currentSrvRange ) {
1047 availableData = std::min( availableData, rmode._currentSrvRange->len - rmode._currentSrvRange->bytesWritten );
1048 }
1049 auto bw = writeDataToFile( seekTo, ptr + bytesWrittenSoFar, availableData );
1050 if ( bw <= 0 )
1051 return 0;
1052
1053 bytesWrittenSoFar += bw;
1054 }
1055
1056 if ( bytesWrittenSoFar == max )
1057 return max;
1058
1059 if ( rmode._currentRange == -1 ) {
1060
1061 // we still are inside the current range from the server
1062 if ( rmode._currentSrvRange )
1063 continue;
1064
1065 std::string_view incoming( ptr + bytesWrittenSoFar, max - bytesWrittenSoFar );
1066 auto hdrEnd = incoming.find("\r\n\r\n");
1067 if ( hdrEnd == incoming.npos ) {
1068 //no header end in the data yet, push to buffer and return
1069 rmode._rangePrefaceBuffer.insert( rmode._rangePrefaceBuffer.end(), incoming.begin(), incoming.end() );
1070 return max;
1071 }
1072
1073 //append the data of the current header to the buffer and parse it
1074 rmode._rangePrefaceBuffer.insert( rmode._rangePrefaceBuffer.end(), incoming.begin(), incoming.begin() + ( hdrEnd + 4 ) );
1075 bytesWrittenSoFar += ( hdrEnd + 4 ); //header data plus header end
1076
1077 std::string_view data( rmode._rangePrefaceBuffer.data(), rmode._rangePrefaceBuffer.size() );
1078 auto sepStrIndex = data.find( rmode._seperatorString );
1079 if ( sepStrIndex == data.npos ) {
1081 "Invalid multirange header format, seperator string missing." );
1082 return 0;
1083 }
1084
1085 auto startOfHeader = sepStrIndex + rmode._seperatorString.length();
1086 std::vector<std::string_view> lines;
1087 zypp::strv::split( data.substr( startOfHeader ), "\r\n", zypp::strv::Trim::trim, [&]( std::string_view strv ) { lines.push_back(strv); } );
1088 for ( const auto &hdrLine : lines ) {
1089 if ( zypp::strv::hasPrefixCI(hdrLine, "Content-Range:") ) {
1091 //if we can not parse the header the message must be broken
1092 if(! parseContentRangeHeader( hdrLine, r.start, r.len ) ) {
1093 rmode._cachedResult = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, "Invalid Content-Range header format." );
1094 return 0;
1095 }
1096 rmode._currentSrvRange = r;
1097 break;
1098 }
1099 }
1100 //clear the buffer again
1101 rmode._rangePrefaceBuffer.clear();
1102 }
1103 }
1104 return bytesWrittenSoFar;
1105 }
1106
1108
1109 NetworkRequest::NetworkRequest(zyppng::Url url, zypp::filesystem::Pathname targetFile, zyppng::NetworkRequest::FileMode fMode)
1110 : Base ( *new NetworkRequestPrivate( std::move(url), std::move(targetFile), std::move(fMode), *this ) )
1111 {
1112 }
1113
1115 {
1116 Z_D();
1117
1118 if ( d->_dispatcher )
1119 d->_dispatcher->cancel( *this, "Request destroyed while still running" );
1120 }
1121
1123 {
1124 d_func()->_expectedFileSize = std::move( expectedFileSize );
1125 }
1126
1127 void NetworkRequest::setPriority( NetworkRequest::Priority prio, bool triggerReschedule )
1128 {
1129 Z_D();
1130 d->_priority = prio;
1131 if ( state() == Pending && triggerReschedule && d->_dispatcher )
1132 d->_dispatcher->reschedule();
1133 }
1134
1136 {
1137 return d_func()->_priority;
1138 }
1139
1140 void NetworkRequest::setOptions( Options opt )
1141 {
1142 d_func()->_options = opt;
1143 }
1144
1145 NetworkRequest::Options NetworkRequest::options() const
1146 {
1147 return d_func()->_options;
1148 }
1149
1150 void NetworkRequest::addRequestRange( size_t start, size_t len, DigestPtr digest, CheckSumBytes expectedChkSum , std::any userData, std::optional<size_t> digestCompareLen, std::optional<size_t> chksumpad )
1151 {
1152 Z_D();
1153 if ( state() == Running )
1154 return;
1155
1156 d->_requestedRanges.push_back( Range::make( start, len, std::move(digest), std::move( expectedChkSum ), std::move( userData ), digestCompareLen, chksumpad ) );
1157 }
1158
1160 {
1161 Z_D();
1162 if ( state() == Running )
1163 return;
1164
1165 d->_requestedRanges.push_back( range );
1166 auto &rng = d->_requestedRanges.back();
1167 rng._rangeState = NetworkRequest::Pending;
1168 rng.bytesWritten = 0;
1169 if ( rng._digest )
1170 rng._digest->reset();
1171 }
1172
1174 {
1175 Z_D();
1176 if ( state() == Running )
1177 return;
1178 d->_requestedRanges.clear();
1179 }
1180
1181 std::vector<NetworkRequest::Range> NetworkRequest::failedRanges() const
1182 {
1183 const auto mystate = state();
1184 if ( mystate != Finished && mystate != Error )
1185 return {};
1186
1187 Z_D();
1188
1189 std::vector<Range> failed;
1190 for ( const auto &r : d->_requestedRanges ) {
1191 if ( r._rangeState != NetworkRequest::Finished )
1192 failed.push_back( r );
1193 }
1194 return failed;
1195 }
1196
1197 const std::vector<NetworkRequest::Range> &NetworkRequest::requestedRanges() const
1198 {
1199 return d_func()->_requestedRanges;
1200 }
1201
1202 const std::string &NetworkRequest::lastRedirectInfo() const
1203 {
1204 return d_func()->_lastRedirect;
1205 }
1206
1208 {
1209 return d_func()->_easyHandle;
1210 }
1211
1212 std::optional<zyppng::NetworkRequest::Timings> NetworkRequest::timings() const
1213 {
1214 const auto myerr = error();
1215 const auto mystate = state();
1216 if ( mystate != Finished )
1217 return {};
1218
1219 Timings t;
1220
1221 auto getMeasurement = [ this ]( const CURLINFO info, std::chrono::microseconds &target ){
1222 using FPSeconds = std::chrono::duration<double, std::chrono::seconds::period>;
1223 double val = 0;
1224 const auto res = curl_easy_getinfo( d_func()->_easyHandle, info, &val );
1225 if ( CURLE_OK == res ) {
1226 target = std::chrono::duration_cast<std::chrono::microseconds>( FPSeconds(val) );
1227 }
1228 };
1229
1230 getMeasurement( CURLINFO_NAMELOOKUP_TIME, t.namelookup );
1231 getMeasurement( CURLINFO_CONNECT_TIME, t.connect);
1232 getMeasurement( CURLINFO_APPCONNECT_TIME, t.appconnect);
1233 getMeasurement( CURLINFO_PRETRANSFER_TIME , t.pretransfer);
1234 getMeasurement( CURLINFO_TOTAL_TIME, t.total);
1235 getMeasurement( CURLINFO_REDIRECT_TIME, t.redirect);
1236
1237 return t;
1238 }
1239
1240 std::vector<char> NetworkRequest::peekData( off_t offset, size_t count ) const
1241 {
1242 Z_D();
1243
1244 if ( !std::holds_alternative<NetworkRequestPrivate::running_t>( d->_runningMode) )
1245 return {};
1246
1247 const auto &rmode = std::get<NetworkRequestPrivate::running_t>( d->_runningMode );
1248 return peek_data_fd( rmode._outFile, offset, count );
1249 }
1250
1252 {
1253 return d_func()->_url;
1254 }
1255
1256 void NetworkRequest::setUrl(const Url &url)
1257 {
1258 Z_D();
1259 if ( state() == NetworkRequest::Running )
1260 return;
1261
1262 d->_url = url;
1263 }
1264
1266 {
1267 return d_func()->_targetFile;
1268 }
1269
1271 {
1272 Z_D();
1273 if ( state() == NetworkRequest::Running )
1274 return;
1275 d->_targetFile = path;
1276 }
1277
1279 {
1280 return d_func()->_fMode;
1281 }
1282
1284 {
1285 Z_D();
1286 if ( state() == NetworkRequest::Running )
1287 return;
1288 d->_fMode = std::move( mode );
1289 }
1290
1292 {
1293 char *ptr = NULL;
1294 if ( curl_easy_getinfo( d_func()->_easyHandle, CURLINFO_CONTENT_TYPE, &ptr ) == CURLE_OK && ptr )
1295 return std::string(ptr);
1296 return std::string();
1297 }
1298
1300 {
1301 return std::visit([](auto& arg) -> zypp::ByteCount {
1302 using T = std::decay_t<decltype(arg)>;
1303 if constexpr (std::is_same_v<T, NetworkRequestPrivate::pending_t> || std::is_same_v<T, NetworkRequestPrivate::prepareNextRangeBatch_t> )
1304 return zypp::ByteCount(0);
1305 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::running_t>
1306 || std::is_same_v<T, NetworkRequestPrivate::finished_t>)
1307 return arg._contentLenght;
1308 else
1309 static_assert(always_false<T>::value, "Unhandled state type");
1310 }, d_func()->_runningMode);
1311 }
1312
1314 {
1315 return std::visit([](auto& arg) -> zypp::ByteCount {
1316 using T = std::decay_t<decltype(arg)>;
1317 if constexpr (std::is_same_v<T, NetworkRequestPrivate::pending_t>)
1318 return zypp::ByteCount();
1319 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::running_t>
1320 || std::is_same_v<T, NetworkRequestPrivate::prepareNextRangeBatch_t>
1321 || std::is_same_v<T, NetworkRequestPrivate::finished_t>)
1322 return arg._downloaded;
1323 else
1324 static_assert(always_false<T>::value, "Unhandled state type");
1325 }, d_func()->_runningMode);
1326 }
1327
1329 {
1330 return d_func()->_settings;
1331 }
1332
1334 {
1335 return std::visit([this](auto& arg) {
1336 using T = std::decay_t<decltype(arg)>;
1337 if constexpr (std::is_same_v<T, NetworkRequestPrivate::pending_t>)
1338 return Pending;
1339 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::running_t> || std::is_same_v<T, NetworkRequestPrivate::prepareNextRangeBatch_t> )
1340 return Running;
1341 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::finished_t>) {
1342 if ( std::get<NetworkRequestPrivate::finished_t>( d_func()->_runningMode )._result.isError() )
1343 return Error;
1344 else
1345 return Finished;
1346 }
1347 else
1348 static_assert(always_false<T>::value, "Unhandled state type");
1349 }, d_func()->_runningMode);
1350 }
1351
1353 {
1354 const auto s = state();
1355 if ( s != Error && s != Finished )
1356 return NetworkRequestError();
1357 return std::get<NetworkRequestPrivate::finished_t>( d_func()->_runningMode)._result;
1358 }
1359
1361 {
1362 if ( !hasError() )
1363 return std::string();
1364
1365 return error().nativeErrorString();
1366 }
1367
1369 {
1370 return error().isError();
1371 }
1372
1373 bool NetworkRequest::addRequestHeader( const std::string &header )
1374 {
1375 Z_D();
1376
1377 curl_slist *res = curl_slist_append( d->_headers ? d->_headers.get() : nullptr, header.c_str() );
1378 if ( !res )
1379 return false;
1380
1381 if ( !d->_headers )
1382 d->_headers = std::unique_ptr< curl_slist, decltype (&curl_slist_free_all) >( res, &curl_slist_free_all );
1383
1384 return true;
1385 }
1386
1388 {
1389 return d_func()->_sigStarted;
1390 }
1391
1393 {
1394 return d_func()->_sigBytesDownloaded;
1395 }
1396
1397 SignalProxy<void (NetworkRequest &req, off_t dltotal, off_t dlnow, off_t ultotal, off_t ulnow)> NetworkRequest::sigProgress()
1398 {
1399 return d_func()->_sigProgress;
1400 }
1401
1403 {
1404 return d_func()->_sigFinished;
1405 }
1406
1407}
ZYppCommitResult & _result
Definition: TargetImpl.cc:1597
Store and operate with byte count.
Definition: ByteCount.h:31
static const Unit B
1 Byte
Definition: ByteCount.h:42
Unit::ValueType SizeType
Definition: ByteCount.h:37
static std::string digestVectorToString(const UByteArray &vec)
get hex string representation of the digest vector given as parameter
Definition: Digest.cc:211
Base class for Exception.
Definition: Exception.h:146
std::string asString() const
Error message provided by dumpOn as string.
Definition: Exception.cc:75
const char * c_str() const
String representation.
Definition: Pathname.h:110
const std::string & asString() const
String representation.
Definition: Pathname.h:91
bool empty() const
Test for an empty path.
Definition: Pathname.h:114
static long auth_type_str2long(std::string &auth_type_str)
Converts a string of comma separated list of authetication type names into a long of ORed CURLAUTH_* ...
Definition: curlauthdata.cc:50
Holds transfer setting.
long maxDownloadSpeed() const
Maximum download speed (bytes per second)
long connectTimeout() const
connection timeout
const std::string & authType() const
get the allowed authentication types
long timeout() const
transfer timeout
const Pathname & clientCertificatePath() const
SSL client certificate file.
std::string userPassword() const
returns the user and password as a user:pass string
long minDownloadSpeed() const
Minimum download speed (bytes per second) until the connection is dropped.
const Headers & headers() const
returns a list of all added headers (trimmed)
const std::string & proxy() const
proxy host
const Pathname & clientKeyPath() const
SSL client key file.
void setUserAgentString(std::string &&val_r)
sets the user agent ie: "Mozilla v3" (trims)
void addHeader(std::string &&val_r)
add a header, on the form "Foo: Bar" (trims)
std::string proxyUserPassword() const
returns the proxy user and password as a user:pass string
bool verifyHostEnabled() const
Whether to verify host for ssl.
const std::string & userAgentString() const
user agent string (trimmed)
bool headRequestsAllowed() const
whether HEAD requests are allowed
bool proxyEnabled() const
proxy is enabled
const std::string & proxyUsername() const
proxy auth username
const Pathname & certificateAuthoritiesPath() const
SSL certificate authorities path ( default: /etc/ssl/certs )
bool verifyPeerEnabled() const
Whether to verify peer for ssl.
Regular expression.
Definition: Regex.h:95
@ icase
Do not differentiate case.
Definition: Regex.h:99
@ rxdefault
These are enforced even if you don't pass them as flag argument.
Definition: Regex.h:103
Regular expression match result.
Definition: Regex.h:168
unsigned size() const
Definition: Regex.cc:106
static zyppng::NetworkRequestError customError(NetworkRequestError::Type t, std::string &&errorMsg="", std::map< std::string, boost::any > &&extraInfo={})
The NetworkRequestError class Represents a error that occured in.
Type type() const
type Returns the type of the error
enum zyppng::NetworkRequestPrivate::ProtocolMode _protocolMode
const std::string _currentCookieFile
Definition: request_p.h:126
zypp::Pathname _targetFile
Definition: request_p.h:116
bool parseContentTypeMultiRangeHeader(const std::string_view &line, std::string &boundary)
Definition: request.cc:738
Signal< void(NetworkRequest &req, zypp::ByteCount count)> _sigBytesDownloaded
Definition: request_p.h:133
NetworkRequestDispatcher * _dispatcher
Definition: request_p.h:129
std::vector< NetworkRequest::Range > _requestedRanges
the requested ranges that need to be downloaded
Definition: request_p.h:120
static int curlProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
Definition: request.cc:766
std::string errorMessage() const
Definition: request.cc:752
Signal< void(NetworkRequest &req)> _sigStarted
Definition: request_p.h:132
NetworkRequest::FileMode _fMode
Definition: request_p.h:122
std::variant< pending_t, running_t, prepareNextRangeBatch_t, finished_t > _runningMode
Definition: request_p.h:211
bool initialize(std::string &errBuf)
Definition: request.cc:125
void validateRange(NetworkRequest::Range &rng)
Definition: request.cc:706
void onActivityTimeout(Timer &)
Definition: request.cc:675
Signal< void(NetworkRequest &req, off_t dltotal, off_t dlnow, off_t ultotal, off_t ulnow)> _sigProgress
Definition: request_p.h:134
size_t writeCallback(char *ptr, size_t size, size_t nmemb)
Definition: request.cc:878
std::string _lastRedirect
to log/report redirections
Definition: request_p.h:125
NetworkRequest::Options _options
Definition: request_p.h:118
static constexpr int _rangeAttempt[]
Definition: request_p.h:148
bool prepareToContinue(std::string &errBuf)
Definition: request.cc:443
bool prepareNextRangeBatch(std::string &errBuf)
Definition: request.cc:475
size_t headerCallback(char *ptr, size_t size, size_t nmemb)
Definition: request.cc:792
void setResult(NetworkRequestError &&err)
Definition: request.cc:629
std::array< char, CURL_ERROR_SIZE+1 > _errorBuf
Definition: request_p.h:104
bool setupHandle(std::string &errBuf)
Definition: request.cc:137
NetworkRequestPrivate(Url &&url, zypp::Pathname &&targetFile, NetworkRequest::FileMode fMode, NetworkRequest &p)
Definition: request.cc:107
TransferSettings _settings
Definition: request_p.h:117
void setCurlOption(CURLoption opt, T data)
Definition: request_p.h:107
zypp::ByteCount _expectedFileSize
Definition: request_p.h:119
Signal< void(NetworkRequest &req, const NetworkRequestError &err)> _sigFinished
Definition: request_p.h:135
std::unique_ptr< curl_slist, decltype(&curl_slist_free_all) > _headers
Definition: request_p.h:141
bool checkIfRangeChkSumIsValid(const NetworkRequest::Range &rng)
Definition: request.cc:686
bool parseContentRangeHeader(const std::string_view &line, size_t &start, size_t &len)
Definition: request.cc:721
zypp::ByteCount reportedByteCount() const
Returns the number of bytes that are reported from the backend as the full download size,...
Definition: request.cc:1299
const zypp::Pathname & targetFilePath() const
Returns the target filename path.
Definition: request.cc:1265
zypp::ByteCount downloadedByteCount() const
Returns the number of already downloaded bytes as reported by the backend.
Definition: request.cc:1313
void setUrl(const Url &url)
This will change the URL of the request.
Definition: request.cc:1256
void setExpectedFileSize(zypp::ByteCount expectedFileSize)
Definition: request.cc:1122
virtual ~NetworkRequest()
Definition: request.cc:1114
void setPriority(Priority prio, bool triggerReschedule=true)
Definition: request.cc:1127
std::vector< char > peekData(off_t offset, size_t count) const
Definition: request.cc:1240
std::string contentType() const
Returns the content type as reported from the server.
Definition: request.cc:1291
void setFileOpenMode(FileMode mode)
Sets the file open mode to mode.
Definition: request.cc:1283
std::shared_ptr< zypp::Digest > DigestPtr
Definition: request.h:46
bool addRequestHeader(const std::string &header)
Definition: request.cc:1373
void setOptions(Options opt)
Definition: request.cc:1140
FileMode fileOpenMode() const
Returns the currently configured file open mode.
Definition: request.cc:1278
bool hasError() const
Checks if there was a error with the request.
Definition: request.cc:1368
State state() const
Returns the current state the HttpDownloadRequest is in.
Definition: request.cc:1333
SignalProxy< void(NetworkRequest &req, const NetworkRequestError &err)> sigFinished()
Signals that the download finished.
Definition: request.cc:1402
UByteArray CheckSumBytes
Definition: request.h:47
Options options() const
Definition: request.cc:1145
SignalProxy< void(NetworkRequest &req, zypp::ByteCount count)> sigBytesDownloaded()
Signals that new data has been downloaded, this is only the payload and does not include control data...
Definition: request.cc:1392
void addRequestRange(size_t start, size_t len=0, DigestPtr digest=nullptr, CheckSumBytes expectedChkSum=CheckSumBytes(), std::any userData=std::any(), std::optional< size_t > digestCompareLen={}, std::optional< size_t > chksumpad={})
Definition: request.cc:1150
std::optional< Timings > timings() const
After the request is finished query the timings that were collected during download.
Definition: request.cc:1212
std::string extendedErrorString() const
In some cases, curl can provide extended error information collected at runtime.
Definition: request.cc:1360
Priority priority() const
Definition: request.cc:1135
NetworkRequestError error() const
Returns the last set Error.
Definition: request.cc:1352
void setTargetFilePath(const zypp::Pathname &path)
Changes the target file path of the download.
Definition: request.cc:1270
void * nativeHandle() const
Definition: request.cc:1207
std::vector< Range > failedRanges() const
Definition: request.cc:1181
const std::vector< Range > & requestedRanges() const
Definition: request.cc:1197
SignalProxy< void(NetworkRequest &req)> sigStarted()
Signals that the dispatcher dequeued the request and actually starts downloading data.
Definition: request.cc:1387
SignalProxy< void(NetworkRequest &req, off_t dltotal, off_t dlnow, off_t ultotal, off_t ulnow)> sigProgress()
Signals if there was data read from the download.
Definition: request.cc:1397
TransferSettings & transferSettings()
Definition: request.cc:1328
const std::string & lastRedirectInfo() const
Definition: request.cc:1202
#define EXPLICITLY_NO_PROXY
Definition: curlhelper_p.h:21
#define MIL_MEDIA
Definition: mediadebug_p.h:29
#define DBG_MEDIA
Definition: mediadebug_p.h:28
std::string curlUnEscape(std::string text_r)
Definition: curlhelper.cc:360
void setupZYPP_MEDIA_CURL_DEBUG(CURL *curl)
Setup CURLOPT_VERBOSE and CURLOPT_DEBUGFUNCTION according to env::ZYPP_MEDIA_CURL_DEBUG.
Definition: curlhelper.cc:124
Definition: Arch.h:361
String related utilities and Regular expression matching.
int ZYPP_MEDIA_CURL_IPRESOLVE()
4/6 to force IPv4/v6
Definition: curlhelper.cc:45
int assert_file_mode(const Pathname &path, unsigned mode)
Like assert_file but enforce mode even if the file already exists.
Definition: PathInfo.cc:1202
constexpr bool always_false
Definition: PathInfo.cc:544
@ TRIM
Definition: String.h:500
bool regex_match(const std::string &s, smatch &matches, const regex &regex)
\relates regex \ingroup ZYPP_STR_REGEX \relates regex \ingroup ZYPP_STR_REGEX
Definition: Regex.h:70
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition: String.cc:36
bool strToBool(const C_Str &str, bool default_r)
Parse str into a bool depending on the default value.
Definition: String.h:429
std::string trim(const std::string &s, const Trim trim_r)
Definition: String.cc:223
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:2
std::vector< char > peek_data_fd(FILE *fd, off_t offset, size_t count)
Definition: request.cc:57
ZYPP_IMPL_PRIVATE(Provide)
Structure holding values of curlrc options.
Definition: curlconfig.h:27
std::string proxyuserpwd
Definition: curlconfig.h:49
static int parseConfig(CurlConfig &config, const std::string &filename="")
Parse a curlrc file and store the result in the config structure.
Definition: curlconfig.cc:24
Convenient building of std::string with boost::format.
Definition: String.h:253
Convenient building of std::string via std::ostringstream Basically a std::ostringstream autoconverti...
Definition: String.h:212
running_t(pending_t &&prevState)
Definition: request.cc:96
CheckSumBytes _checksum
Enables automated checking of downloaded contents against a checksum.
Definition: request.h:86
static Range make(size_t start, size_t len=0, DigestPtr &&digest=nullptr, CheckSumBytes &&expectedChkSum=CheckSumBytes(), std::any &&userData=std::any(), std::optional< size_t > digestCompareLen={}, std::optional< size_t > _dataBlockPadding={})
Definition: request.cc:75
std::optional< size_t > _relevantDigestLen
Definition: request.h:87
std::optional< size_t > _chksumPad
Definition: request.h:88
std::chrono::microseconds appconnect
Definition: request.h:99
std::chrono::microseconds redirect
Definition: request.h:102
std::chrono::microseconds pretransfer
Definition: request.h:100
std::chrono::microseconds total
Definition: request.h:101
std::chrono::microseconds namelookup
Definition: request.h:97
std::chrono::microseconds connect
Definition: request.h:98
#define nullptr
Definition: Easy.h:55
#define ZYPP_CAUGHT(EXCPT)
Drops a logline telling the Exception was caught (in order to handle it).
Definition: Exception.h:436
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:428
#define DBG
Definition: Logger.h:95
#define MIL
Definition: Logger.h:96
#define WAR
Definition: Logger.h:97