libzypp 17.37.17
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>
15#include <zypp-core/zyppng/core/String>
18#include <zypp-curl/CurlConfig>
19#include <zypp-curl/auth/CurlAuthData>
20#include <zypp-media/MediaConfig>
24#include <zypp-core/Pathname.h>
25#include <curl/curl.h>
26#include <stdio.h>
27#include <fcntl.h>
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->headerfunction( 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->writefunction( ptr, {}, size * nmemb );
51 }
52
53 //helper for std::visit
54 template<class T> struct always_false : std::false_type {};
55 }
56
62
66
68 : _outFile( std::move(prevState._outFile) )
69 , _partialHelper( std::move(prevState._partialHelper) )
70 , _downloaded( prevState._downloaded )
71 { }
72
74 : BasePrivate(p)
75 , _url ( std::move(url) )
76 , _targetFile ( std::move( targetFile) )
77 , _fMode ( std::move(fMode) )
78 , _headers( std::unique_ptr< curl_slist, decltype (&curl_slist_free_all) >( nullptr, &curl_slist_free_all ) )
79 { }
80
82 {
83 if ( _easyHandle ) {
84 //clean up for now, later we might reuse handles
85 curl_easy_cleanup( _easyHandle );
86 MIL << _easyHandle << " curl_easy_cleanup" << std::endl;
87 //reset in request but make sure the request was not enqueued again and got a new handle
88 _easyHandle = nullptr;
89 }
90 }
91
92 bool NetworkRequestPrivate::initialize( std::string &errBuf )
93 {
94 reset();
95
96 if ( _easyHandle ) {
97 //will reset to defaults but keep live connections, session ID and DNS caches
98 curl_easy_reset( _easyHandle );
99 MIL << _easyHandle << " curl_easy_reset" << std::endl;
100 } else
101 _easyHandle = curl_easy_init();
102 return setupHandle ( errBuf );
103 }
104
105 bool NetworkRequestPrivate::setupHandle( std::string &errBuf )
106 {
108 curl_easy_setopt( _easyHandle, CURLOPT_ERRORBUFFER, this->_errorBuf.data() );
109 MIL << _easyHandle << " " << "URL: " << _url << std::endl;
110
111 const std::string urlScheme = _url.getScheme();
112 if ( urlScheme == "http" || urlScheme == "https" )
114
115 try {
116
117 setCurlOption( CURLOPT_PRIVATE, this );
119 setCurlOption( CURLOPT_XFERINFODATA, this );
120 setCurlOption( CURLOPT_NOPROGRESS, 0L);
121 setCurlOption( CURLOPT_FAILONERROR, 1L);
122 setCurlOption( CURLOPT_NOSIGNAL, 1L);
123
124 std::string urlBuffer( _url.asString() );
125 setCurlOption( CURLOPT_URL, urlBuffer.c_str() );
126
127 setCurlOption( CURLOPT_WRITEFUNCTION, nwr_writeCallback );
128 setCurlOption( CURLOPT_WRITEDATA, this );
129
131 setCurlOption( CURLOPT_CONNECT_ONLY, 1L );
132 setCurlOption( CURLOPT_FRESH_CONNECT, 1L );
133 }
135 // instead of returning no data with NOBODY, we return
136 // little data, that works with broken servers, and
137 // works for ftp as well, because retrieving only headers
138 // ftp will return always OK code ?
139 // See http://curl.haxx.se/docs/knownbugs.html #58
140 if ( _protocolMode == ProtocolMode::HTTP && _settings.headRequestsAllowed() )
141 setCurlOption( CURLOPT_NOBODY, 1L );
142 else
143 setCurlOption( CURLOPT_RANGE, "0-1" );
144 }
145
146 //make a local copy of the settings, so headers are not added multiple times
148
149 if ( _dispatcher ) {
150 locSet.setUserAgentString( _dispatcher->agentString().c_str() );
151
152 // add custom headers as configured (bsc#955801)
153 const auto &cHeaders = _dispatcher->hostSpecificHeaders();
154 if ( auto i = cHeaders.find(_url.getHost()); i != cHeaders.end() ) {
155 for ( const auto &[key, value] : i->second ) {
157 "%s: %s", key.c_str(), value.c_str() )
158 ));
159 }
160 }
161 }
162
163 locSet.addHeader("Pragma:");
164
167 {
168 case 4: setCurlOption( CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); break;
169 case 6: setCurlOption( CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6 ); break;
170 default: break;
171 }
172
173 setCurlOption( CURLOPT_HEADERFUNCTION, &nwr_headerCallback );
174 setCurlOption( CURLOPT_HEADERDATA, this );
175
179 setCurlOption( CURLOPT_CONNECTTIMEOUT, locSet.connectTimeout() );
180 // If a transfer timeout is set, also set CURLOPT_TIMEOUT to an upper limit
181 // just in case curl does not trigger its progress callback frequently
182 // enough.
183 if ( locSet.timeout() )
184 {
185 setCurlOption( CURLOPT_TIMEOUT, 3600L );
186 }
187
188 if ( urlScheme == "https" )
189 {
190 if ( :: internal::setCurlRedirProtocols ( _easyHandle ) != CURLE_OK ) {
192 }
193
194 if( locSet.verifyPeerEnabled() ||
195 locSet.verifyHostEnabled() )
196 {
197 setCurlOption(CURLOPT_CAPATH, locSet.certificateAuthoritiesPath().c_str());
198 }
199
200 if( ! locSet.clientCertificatePath().empty() )
201 {
202 setCurlOption(CURLOPT_SSLCERT, locSet.clientCertificatePath().c_str());
203 }
204 if( ! locSet.clientKeyPath().empty() )
205 {
206 setCurlOption(CURLOPT_SSLKEY, locSet.clientKeyPath().c_str());
207 }
208
209#ifdef CURLSSLOPT_ALLOW_BEAST
210 // see bnc#779177
211 setCurlOption( CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST );
212#endif
213 setCurlOption(CURLOPT_SSL_VERIFYPEER, locSet.verifyPeerEnabled() ? 1L : 0L);
214 setCurlOption(CURLOPT_SSL_VERIFYHOST, locSet.verifyHostEnabled() ? 2L : 0L);
215 // bnc#903405 - POODLE: libzypp should only talk TLS
216 setCurlOption(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
217 }
218
219 // follow any Location: header that the server sends as part of
220 // an HTTP header (#113275)
221 setCurlOption( CURLOPT_FOLLOWLOCATION, 1L);
222 // 3 redirects seem to be too few in some cases (bnc #465532)
223 setCurlOption( CURLOPT_MAXREDIRS, 6L );
224
225 //set the user agent
226 setCurlOption(CURLOPT_USERAGENT, locSet.userAgentString().c_str() );
227
228
229 /*---------------------------------------------------------------*
230 CURLOPT_USERPWD: [user name]:[password]
231 Url::username/password -> CURLOPT_USERPWD
232 If not provided, anonymous FTP identification
233 *---------------------------------------------------------------*/
234 if ( locSet.userPassword().size() )
235 {
236 setCurlOption(CURLOPT_USERPWD, locSet.userPassword().c_str());
237 std::string use_auth = _settings.authType();
238 if (use_auth.empty())
239 use_auth = "digest,basic"; // our default
241 if( auth != CURLAUTH_NONE)
242 {
243 DBG << _easyHandle << " " << "Enabling HTTP authentication methods: " << use_auth
244 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
245 setCurlOption(CURLOPT_HTTPAUTH, auth);
246 }
247 }
248
249 if ( locSet.proxyEnabled() && ! locSet.proxy().empty() )
250 {
251 DBG << _easyHandle << " " << "Proxy: '" << locSet.proxy() << "'" << std::endl;
252 setCurlOption(CURLOPT_PROXY, locSet.proxy().c_str());
253 setCurlOption(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM );
254
255 /*---------------------------------------------------------------*
256 * CURLOPT_PROXYUSERPWD: [user name]:[password]
257 *
258 * Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
259 * If not provided, $HOME/.curlrc is evaluated
260 *---------------------------------------------------------------*/
261
262 std::string proxyuserpwd = locSet.proxyUserPassword();
263
264 if ( proxyuserpwd.empty() )
265 {
267 zypp::media::CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
268 if ( curlconf.proxyuserpwd.empty() )
269 DBG << _easyHandle << " " << "Proxy: ~/.curlrc does not contain the proxy-user option" << std::endl;
270 else
271 {
272 proxyuserpwd = curlconf.proxyuserpwd;
273 DBG << _easyHandle << " " << "Proxy: using proxy-user from ~/.curlrc" << std::endl;
274 }
275 }
276 else
277 {
278 DBG << _easyHandle << " " << "Proxy: using provided proxy-user '" << _settings.proxyUsername() << "'" << std::endl;
279 }
280
281 if ( ! proxyuserpwd.empty() )
282 {
283 setCurlOption(CURLOPT_PROXYUSERPWD, ::internal::curlUnEscape( proxyuserpwd ).c_str());
284 }
285 }
286#if CURLVERSION_AT_LEAST(7,19,4)
287 else if ( locSet.proxy() == EXPLICITLY_NO_PROXY )
288 {
289 // Explicitly disabled in URL (see fillSettingsFromUrl()).
290 // This should also prevent libcurl from looking into the environment.
291 DBG << _easyHandle << " " << "Proxy: explicitly NOPROXY" << std::endl;
292 setCurlOption(CURLOPT_NOPROXY, "*");
293 }
294
295#endif
296 // else: Proxy: not explicitly set; libcurl may look into the environment
297
299 if ( locSet.minDownloadSpeed() != 0 )
300 {
301 setCurlOption(CURLOPT_LOW_SPEED_LIMIT, locSet.minDownloadSpeed());
302 // default to 10 seconds at low speed
303 setCurlOption(CURLOPT_LOW_SPEED_TIME, 60L);
304 }
305
306#if CURLVERSION_AT_LEAST(7,15,5)
307 if ( locSet.maxDownloadSpeed() != 0 )
308 setCurlOption(CURLOPT_MAX_RECV_SPEED_LARGE, locSet.maxDownloadSpeed());
309#endif
310
311 if ( locSet.cookieFileEnabled() ) {
312 DBG << _easyHandle << " " << "Cookie file enabled: " << _currentCookieFile << std::endl;
314 setCurlOption( CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
315 setCurlOption(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
316 }
317
318#if CURLVERSION_AT_LEAST(7,18,0)
319 // bnc #306272
320 setCurlOption(CURLOPT_PROXY_TRANSFER_MODE, 1L );
321#endif
322
323 // Append settings custom headers to curl.
324 // TransferSettings assert strings are trimmed (HTTP/2 RFC 9113)
325 for ( const auto &header : locSet.headers() ) {
326 if ( !z_func()->addRequestHeader( header.c_str() ) )
328 }
329
330 if ( _headers )
331 setCurlOption( CURLOPT_HTTPHEADER, _headers.get() );
332
333 // set up ranges if required
335 if ( _requestedRanges.size() ) {
336
337 std::sort( _requestedRanges.begin(), _requestedRanges.end(), []( const auto &elem1, const auto &elem2 ){
338 return ( elem1._start < elem2._start );
339 });
340
341 CurlMultiPartHandler *helper = nullptr;
342 if ( auto initState = std::get_if<pending_t>(&_runningMode) ) {
343
345 initState->_partialHelper = std::make_unique<CurlMultiPartHandler>(
346 multiPartMode
349 , *this
350 );
351 helper = initState->_partialHelper.get();
352
353 } else if ( auto pendingState = std::get_if<prepareNextRangeBatch_t>(&_runningMode) ) {
354 helper = pendingState->_partialHelper.get();
355 } else {
356 errBuf = "Request is in invalid state to call setupHandle";
357 return false;
358 }
359
360 if ( !helper->prepare () ) {
361 errBuf = helper->lastErrorMessage ();
362 return false;
363 }
364 }
365 }
366
367 return true;
368
369 } catch ( const zypp::Exception &excp ) {
370 ZYPP_CAUGHT(excp);
371 errBuf = excp.asString();
372 }
373 return false;
374 }
375
377 {
378 auto rmode = std::get_if<NetworkRequestPrivate::running_t>( &_runningMode );
379 if ( !rmode ) {
380 DBG << _easyHandle << "Can only create output file in running mode" << std::endl;
381 return false;
382 }
383 // if we have no open file create or open it
384 if ( !rmode->_outFile ) {
385 std::string openMode = "w+b";
387 openMode = "r+b";
388
389 rmode->_outFile = fopen( _targetFile.asString().c_str() , openMode.c_str() );
390
391 //if the file does not exist create a new one
392 if ( !rmode->_outFile && _fMode == NetworkRequest::WriteShared ) {
393 rmode->_outFile = fopen( _targetFile.asString().c_str() , "w+b" );
394 }
395
396 if ( !rmode->_outFile ) {
398 ,zypp::str::Format("Unable to open target file (%1%). Errno: (%2%:%3%)") % _targetFile.asString() % errno % strerr_cxx() );
399 return false;
400 }
401 }
402
403 return true;
404 }
405
407 {
408 // We can recover from RangeFail errors if the helper indicates it
409 auto rmode = std::get_if<NetworkRequestPrivate::running_t>( &_runningMode );
410 if ( rmode->_partialHelper ) return rmode->_partialHelper->canRecover();
411 return false;
412 }
413
415 {
416 auto rmode = std::get_if<NetworkRequestPrivate::running_t>( &_runningMode );
417 if ( !rmode ) {
418 errBuf = "NetworkRequestPrivate::prepareToContinue called in invalid state";
419 return false;
420 }
421
422 if ( rmode->_partialHelper && rmode->_partialHelper->hasMoreWork() ) {
423
424 bool hadRangeFail = rmode->_partialHelper->lastError () == NetworkRequestError::RangeFail;
425
426 _runningMode = prepareNextRangeBatch_t( std::move(std::get<running_t>( _runningMode )) );
427
428 auto &prepMode = std::get<prepareNextRangeBatch_t>(_runningMode);
429 if ( !prepMode._partialHelper->prepareToContinue() ) {
430 errBuf = prepMode._partialHelper->lastErrorMessage();
431 return false;
432 }
433
434 if ( hadRangeFail ) {
435 // we reset the handle to default values. We do this to not run into
436 // "transfer closed with outstanding read data remaining" error CURL sometimes returns when
437 // we cancel a connection because of a range error to request a smaller batch.
438 // The error will still happen but much less frequently than without resetting the handle.
439 //
440 // Note: Even creating a new handle will NOT fix the issue
441 curl_easy_reset( _easyHandle );
442 MIL << _easyHandle << " curl_easy_reset after hadRangeFail" << std::endl;
443 }
444 if ( !setupHandle(errBuf))
445 return false;
446
447 return true;
448 }
449 errBuf = "Request has no more work";
450 return false;
451
452 }
453
455 {
456 // check if we have ranges that have never been requested
457 return std::any_of( _requestedRanges.begin(), _requestedRanges.end(), []( const auto &range ){ return range._rangeState == CurlMultiPartHandler::Pending; });
458 }
459
461 {
462 bool isRangeContinuation = std::holds_alternative<prepareNextRangeBatch_t>( _runningMode );
463 if ( isRangeContinuation ) {
464 MIL << _easyHandle << " " << "Continuing a previously started range batch." << std::endl;
465 _runningMode = running_t( std::move(std::get<prepareNextRangeBatch_t>( _runningMode )) );
466 } else {
467 _runningMode = running_t( std::move(std::get<pending_t>( _runningMode )) );
468 }
469
470 auto &m = std::get<running_t>( _runningMode );
471
472 if ( m._activityTimer ) {
473 DBG_MEDIA << _easyHandle << " Setting activity timeout to: " << _settings.timeout() << std::endl;
474 m._activityTimer->connect( &Timer::sigExpired, *this, &NetworkRequestPrivate::onActivityTimeout );
475 m._activityTimer->start( static_cast<uint64_t>( _settings.timeout() * 1000 ) );
476 }
477
478 if ( !isRangeContinuation )
479 _sigStarted.emit( *z_func() );
480 }
481
483 {
484 if ( std::holds_alternative<running_t>(_runningMode) ) {
485 auto &rmode = std::get<running_t>( _runningMode );
486 if ( rmode._partialHelper )
487 rmode._partialHelper->finalize();
488 }
489 }
490
492 {
493 finished_t resState;
494 resState._result = std::move(err);
495
496 if ( std::holds_alternative<running_t>(_runningMode) ) {
497
498 auto &rmode = std::get<running_t>( _runningMode );
499 resState._downloaded = rmode._downloaded;
500 resState._contentLenght = rmode._contentLenght;
501
503 if ( _requestedRanges.size( ) ) {
504 //we have a successful download lets see if we got everything we needed
505 if ( !rmode._partialHelper->verifyData() ){
506 NetworkRequestError::Type err = rmode._partialHelper->lastError();
507 resState._result = NetworkRequestErrorPrivate::customError( err, std::string(rmode._partialHelper->lastErrorMessage()) );
508 }
509
510 // if we have ranges we need to fill our digest from the full file
512 if ( fseek( rmode._outFile, 0, SEEK_SET ) != 0 ) {
513 resState._result = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, "Unable to set output file pointer." );
514 } else {
515 constexpr size_t bufSize = 4096;
516 char buf[bufSize];
517 while( auto cnt = fread(buf, 1, bufSize, rmode._outFile ) > 0 ) {
518 _fileVerification->_fileDigest.update(buf, cnt);
519 }
520 }
521 }
522 } // if ( _requestedRanges.size( ) )
523 }
524
525 // finally check the file digest if we have one
527 const UByteArray &calcSum = _fileVerification->_fileDigest.digestVector ();
528 const UByteArray &expSum = zypp::Digest::hexStringToUByteArray( _fileVerification->_fileChecksum.checksum () );
529 if ( calcSum != expSum ) {
532 , (zypp::str::Format("Invalid file checksum %1%, expected checksum %2%")
533 % _fileVerification->_fileDigest.digest()
534 % _fileVerification->_fileChecksum.checksum () ) );
535 }
536 }
537
538 rmode._outFile.reset();
539 }
540
541 _runningMode = std::move( resState );
542 _sigFinished.emit( *z_func(), std::get<finished_t>(_runningMode)._result );
543 }
544
546 {
548 _headers.reset( nullptr );
549 _errorBuf.fill( 0 );
551
552 if ( _fileVerification )
553 _fileVerification->_fileDigest.reset ();
554
555 std::for_each( _requestedRanges.begin (), _requestedRanges.end(), []( CurlMultiPartHandler::Range &range ) {
556 range.restart();
557 });
558 }
559
561 {
562 MIL_MEDIA << _easyHandle << " Request timeout interval: " << t.interval()<< " remaining: " << t.remaining() << std::endl;
563 std::map<std::string, boost::any> extraInfo;
564 extraInfo.insert( {"requestUrl", _url } );
565 extraInfo.insert( {"filepath", _targetFile } );
566 _dispatcher->cancel( *z_func(), NetworkRequestErrorPrivate::customError( NetworkRequestError::Timeout, "Download timed out", std::move(extraInfo) ) );
567 }
568
570 {
571 return std::string( _errorBuf.data() );
572 }
573
575 {
576 if ( std::holds_alternative<running_t>( _runningMode ) ){
577 auto &rmode = std::get<running_t>( _runningMode );
578 if ( rmode._activityTimer && rmode._activityTimer->isRunning() )
579 rmode._activityTimer->start();
580 }
581 }
582
583 int NetworkRequestPrivate::curlProgressCallback( void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow )
584 {
585 if ( !clientp )
586 return CURLE_OK;
587 NetworkRequestPrivate *that = reinterpret_cast<NetworkRequestPrivate *>( clientp );
588
589 if ( !std::holds_alternative<running_t>(that->_runningMode) ){
590 DBG << that->_easyHandle << " " << "Curl progress callback was called in invalid state "<< that->z_func()->state() << std::endl;
591 return -1;
592 }
593 auto &rmode = std::get<running_t>( that->_runningMode );
594
595 //reset the timer
596 that->resetActivityTimer();
597
598 rmode._isInCallback = true;
599 if ( rmode._lastProgressNow != dlnow ) {
600 rmode._lastProgressNow = dlnow;
601 that->_sigProgress.emit( *that->z_func(), dltotal, dlnow, ultotal, ulnow );
602 }
603 rmode._isInCallback = false;
604
605 return rmode._cachedResult ? CURLE_ABORTED_BY_CALLBACK : CURLE_OK;
606 }
607
608 size_t NetworkRequestPrivate::headerfunction( char *ptr, size_t bytes )
609 {
610 //it is valid to call this function with no data to write, just return OK
611 if ( bytes == 0)
612 return 0;
613
615
617
618 std::string_view hdr( ptr, bytes );
619
620 hdr.remove_prefix( std::min( hdr.find_first_not_of(" \t\r\n"), hdr.size() ) );
621 const auto lastNonWhitespace = hdr.find_last_not_of(" \t\r\n");
622 if ( lastNonWhitespace != hdr.npos )
623 hdr.remove_suffix( hdr.size() - (lastNonWhitespace + 1) );
624 else
625 hdr = std::string_view();
626
627 if ( !std::holds_alternative<running_t>(_runningMode) ){
628 DBG << _easyHandle << " " << "Curl headerfunction callback was called in invalid state "<< z_func()->state() << std::endl;
629 return -1;
630 }
631 auto &rmode = std::get<running_t>( _runningMode );
632 if ( !hdr.size() ) {
633 return ( bytes );
634 }
635 if ( _expectedFileSize && rmode._partialHelper ) {
636 const auto &repSize = rmode._partialHelper->reportedFileSize ();
637 if ( repSize && repSize != _expectedFileSize ) {
638 rmode._cachedResult = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, "Reported downloaded data length is not what was expected." );
639 return 0;
640 }
641 }
642 if ( zypp::strv::hasPrefixCI( hdr, "HTTP/" ) ) {
643
644 long statuscode = 0;
645 (void)curl_easy_getinfo( _easyHandle, CURLINFO_RESPONSE_CODE, &statuscode);
646
647 // if we have a status 204 we need to create a empty file
648 if( statuscode == 204 && !( _options & NetworkRequest::ConnectionTest ) && !( _options & NetworkRequest::HeadRequest ) )
650
651 } else if ( zypp::strv::hasPrefixCI( hdr, "Location:" ) ) {
652 _lastRedirect = hdr.substr( 9 );
653 DBG << _easyHandle << " " << "redirecting to " << _lastRedirect << std::endl;
654
655 } else if ( zypp::strv::hasPrefixCI( hdr, "Content-Length:") ) {
656 auto lenStr = str::trim( hdr.substr( 15 ), zypp::str::TRIM );
657 auto str = std::string ( lenStr.data(), lenStr.length() );
659 if ( len > 0 ) {
660 DBG << _easyHandle << " " << "Got Content-Length Header: " << len << std::endl;
661 rmode._contentLenght = zypp::ByteCount(len, zypp::ByteCount::B);
662 }
663 }
664 }
665
666 return bytes;
667 }
668
669 size_t NetworkRequestPrivate::writefunction( char *data, std::optional<off_t> offset, size_t max )
670 {
671 //it is valid to call this function with no data to write, just return OK
672 if ( max == 0)
673 return 0;
674
676
677 //in case of a HEAD request, we do not write anything
679 return ( max );
680 }
681
682 if ( !std::holds_alternative<running_t>(_runningMode) ){
683 DBG << _easyHandle << " " << "Curl writefunction callback was called in invalid state "<< z_func()->state() << std::endl;
684 return -1;
685 }
686 auto &rmode = std::get<running_t>( _runningMode );
687
688 // if we have no open file create or open it
689 if ( !assertOutputFile() )
690 return 0;
691
692 if ( offset ) {
693 // seek to the given offset
694 if ( fseek( rmode._outFile, *offset, SEEK_SET ) != 0 ) {
696 "Unable to set output file pointer." );
697 return 0;
698 }
699 rmode._currentFileOffset = *offset;
700 }
701
702 if ( _expectedFileSize && rmode._partialHelper ) {
703 const auto &repSize = rmode._partialHelper->reportedFileSize ();
704 if ( repSize && repSize != _expectedFileSize ) {
705 rmode._cachedResult = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, "Reported downloaded data length is not what was expected." );
706 return 0;
707 }
708 }
709
710 //make sure we do not write after the expected file size
711 if ( _expectedFileSize && static_cast<zypp::ByteCount::SizeType>( rmode._currentFileOffset + max) > _expectedFileSize ) {
712 rmode._cachedResult = NetworkRequestErrorPrivate::customError( NetworkRequestError::ExceededMaxLen, "Downloaded data exceeds expected length." );
713 return 0;
714 }
715
716 auto written = fwrite( data, 1, max, rmode._outFile );
717 if ( written == 0 )
718 return 0;
719
720 // if we are not downloading in ranges, we can update the file digest on the fly if we have one
721 if ( !rmode._partialHelper && _fileVerification ) {
722 _fileVerification->_fileDigest.update( data, written );
723 }
724
725 rmode._currentFileOffset += written;
726
727 // count the number of real bytes we have downloaded so far
728 rmode._downloaded += written;
729 _sigBytesDownloaded.emit( *z_func(), rmode._downloaded );
730
731 return written;
732 }
733
735 {
736 auto rmode = std::get_if<NetworkRequestPrivate::running_t>( &_runningMode );
737 if ( !rmode || !rmode->_partialHelper || !rmode->_partialHelper->hasError() )
738 return;
739
740 // oldest cached result wins
741 if ( rmode->_cachedResult )
742 return;
743
744 auto lastErr = NetworkRequestErrorPrivate::customError( rmode->_partialHelper->lastError() , std::string(rmode->_partialHelper->lastErrorMessage()) );
745 MIL_MEDIA << _easyHandle << " Multipart handler announced error code " << lastErr.toString () << std::endl;
746 rmode->_cachedResult = lastErr;
747 }
748
750
752 : Base ( *new NetworkRequestPrivate( std::move(url), std::move(targetFile), std::move(fMode), *this ) )
753 {
754 }
755
757 {
758 Z_D();
759
760 if ( d->_dispatcher )
761 d->_dispatcher->cancel( *this, "Request destroyed while still running" );
762 }
763
765 {
766 d_func()->_expectedFileSize = std::move( expectedFileSize );
767 }
768
770 {
771 return d_func()->_expectedFileSize;
772 }
773
774 void NetworkRequest::setPriority( NetworkRequest::Priority prio, bool triggerReschedule )
775 {
776 Z_D();
777 d->_priority = prio;
778 if ( state() == Pending && triggerReschedule && d->_dispatcher )
779 d->_dispatcher->reschedule();
780 }
781
783 {
784 return d_func()->_priority;
785 }
786
787 void NetworkRequest::setOptions( Options opt )
788 {
789 d_func()->_options = opt;
790 }
791
792 NetworkRequest::Options NetworkRequest::options() const
793 {
794 return d_func()->_options;
795 }
796
797 void NetworkRequest::addRequestRange(size_t start, size_t len, std::optional<zypp::Digest> &&digest, CheckSumBytes expectedChkSum , std::any userData, std::optional<size_t> digestCompareLen, std::optional<size_t> chksumpad )
798 {
799 Z_D();
800 if ( state() == Running )
801 return;
802
803 d->_requestedRanges.push_back( Range::make( start, len, std::move(digest), std::move( expectedChkSum ), std::move( userData ), digestCompareLen, chksumpad ) );
804 }
805
807 {
808 Z_D();
809 if ( state() == Running )
810 return;
811
812 d->_requestedRanges.push_back( std::move(range) );
813 auto &rng = d->_requestedRanges.back();
814 rng._rangeState = CurlMultiPartHandler::Pending;
815 rng.bytesWritten = 0;
816 if ( rng._digest )
817 rng._digest->reset();
818 }
819
821 {
822 Z_D();
823 if ( state() == Running )
824 return false;
825
826 zypp::Digest fDig;
827 if ( !fDig.create( expected.type () ) )
828 return false;
829
830 d->_fileVerification = NetworkRequestPrivate::FileVerifyInfo{
831 ._fileDigest = std::move(fDig),
832 ._fileChecksum = expected
833 };
834 return true;
835 }
836
838 {
839 Z_D();
840 if ( state() == Running )
841 return;
842 d->_requestedRanges.clear();
843 }
844
845 std::vector<NetworkRequest::Range> NetworkRequest::failedRanges() const
846 {
847 const auto mystate = state();
848 if ( mystate != Finished && mystate != Error )
849 return {};
850
851 Z_D();
852
853 std::vector<Range> failed;
854 for ( auto &r : d->_requestedRanges ) {
855 if ( r._rangeState != CurlMultiPartHandler::Finished ) {
856 failed.push_back( r.clone() );
857 }
858 }
859 return failed;
860 }
861
862 const std::vector<NetworkRequest::Range> &NetworkRequest::requestedRanges() const
863 {
864 return d_func()->_requestedRanges;
865 }
866
867 const std::string &NetworkRequest::lastRedirectInfo() const
868 {
869 return d_func()->_lastRedirect;
870 }
871
873 {
874 return d_func()->_easyHandle;
875 }
876
877 std::optional<zyppng::NetworkRequest::Timings> NetworkRequest::timings() const
878 {
879 const auto myerr = error();
880 const auto mystate = state();
881 if ( mystate != Finished )
882 return {};
883
884 Timings t;
885
886 auto getMeasurement = [ this ]( const CURLINFO info, std::chrono::microseconds &target ){
887 using FPSeconds = std::chrono::duration<double, std::chrono::seconds::period>;
888 double val = 0;
889 const auto res = curl_easy_getinfo( d_func()->_easyHandle, info, &val );
890 if ( CURLE_OK == res ) {
891 target = std::chrono::duration_cast<std::chrono::microseconds>( FPSeconds(val) );
892 }
893 };
894
895 getMeasurement( CURLINFO_NAMELOOKUP_TIME, t.namelookup );
896 getMeasurement( CURLINFO_CONNECT_TIME, t.connect);
897 getMeasurement( CURLINFO_APPCONNECT_TIME, t.appconnect);
898 getMeasurement( CURLINFO_PRETRANSFER_TIME , t.pretransfer);
899 getMeasurement( CURLINFO_TOTAL_TIME, t.total);
900 getMeasurement( CURLINFO_REDIRECT_TIME, t.redirect);
901
902 return t;
903 }
904
905 std::vector<char> NetworkRequest::peekData( off_t offset, size_t count ) const
906 {
907 Z_D();
908
909 if ( !std::holds_alternative<NetworkRequestPrivate::running_t>( d->_runningMode) )
910 return {};
911
912 const auto &rmode = std::get<NetworkRequestPrivate::running_t>( d->_runningMode );
913 return zypp::io::peek_data_fd( rmode._outFile, offset, count );
914 }
915
917 {
918 return d_func()->_url;
919 }
920
922 {
923 Z_D();
925 return;
926
927 d->_url = url;
928 }
929
931 {
932 return d_func()->_targetFile;
933 }
934
936 {
937 Z_D();
939 return;
940 d->_targetFile = path;
941 }
942
944 {
945 return d_func()->_fMode;
946 }
947
949 {
950 Z_D();
952 return;
953 d->_fMode = std::move( mode );
954 }
955
956 std::string NetworkRequest::contentType() const
957 {
958 char *ptr = NULL;
959 if ( curl_easy_getinfo( d_func()->_easyHandle, CURLINFO_CONTENT_TYPE, &ptr ) == CURLE_OK && ptr )
960 return std::string(ptr);
961 return std::string();
962 }
963
965 {
966 return std::visit([](auto& arg) -> zypp::ByteCount {
967 using T = std::decay_t<decltype(arg)>;
968 if constexpr (std::is_same_v<T, NetworkRequestPrivate::pending_t> || std::is_same_v<T, NetworkRequestPrivate::prepareNextRangeBatch_t> )
969 return zypp::ByteCount(0);
970 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::running_t>
971 || std::is_same_v<T, NetworkRequestPrivate::finished_t>)
972 return arg._contentLenght;
973 else
974 static_assert(always_false<T>::value, "Unhandled state type");
975 }, d_func()->_runningMode);
976 }
977
979 {
980 return std::visit([](auto& arg) -> zypp::ByteCount {
981 using T = std::decay_t<decltype(arg)>;
982 if constexpr (std::is_same_v<T, NetworkRequestPrivate::pending_t>)
983 return zypp::ByteCount();
984 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::running_t>
985 || std::is_same_v<T, NetworkRequestPrivate::prepareNextRangeBatch_t>
986 || std::is_same_v<T, NetworkRequestPrivate::finished_t>)
987 return arg._downloaded;
988 else
989 static_assert(always_false<T>::value, "Unhandled state type");
990 }, d_func()->_runningMode);
991 }
992
994 {
995 return d_func()->_settings;
996 }
997
999 {
1000 return std::visit([this](auto& arg) {
1001 using T = std::decay_t<decltype(arg)>;
1002 if constexpr (std::is_same_v<T, NetworkRequestPrivate::pending_t>)
1003 return Pending;
1004 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::running_t> || std::is_same_v<T, NetworkRequestPrivate::prepareNextRangeBatch_t> )
1005 return Running;
1006 else if constexpr (std::is_same_v<T, NetworkRequestPrivate::finished_t>) {
1007 if ( std::get<NetworkRequestPrivate::finished_t>( d_func()->_runningMode )._result.isError() )
1008 return Error;
1009 else
1010 return Finished;
1011 }
1012 else
1013 static_assert(always_false<T>::value, "Unhandled state type");
1014 }, d_func()->_runningMode);
1015 }
1016
1018 {
1019 const auto s = state();
1020 if ( s != Error && s != Finished )
1021 return NetworkRequestError();
1022 return std::get<NetworkRequestPrivate::finished_t>( d_func()->_runningMode)._result;
1023 }
1024
1026 {
1027 if ( !hasError() )
1028 return std::string();
1029
1030 return error().nativeErrorString();
1031 }
1032
1034 {
1035 return error().isError();
1036 }
1037
1038 bool NetworkRequest::addRequestHeader( const std::string &header )
1039 {
1040 Z_D();
1041
1042 curl_slist *res = curl_slist_append( d->_headers ? d->_headers.get() : nullptr, header.c_str() );
1043 if ( !res )
1044 return false;
1045
1046 if ( !d->_headers )
1047 d->_headers = std::unique_ptr< curl_slist, decltype (&curl_slist_free_all) >( res, &curl_slist_free_all );
1048
1049 return true;
1050 }
1051
1053 {
1054 return d_func()->_currentCookieFile;
1055 }
1056
1058 {
1059 d_func()->_currentCookieFile = std::move(cookieFile);
1060 }
1061
1063 {
1064 return d_func()->_sigStarted;
1065 }
1066
1068 {
1069 return d_func()->_sigBytesDownloaded;
1070 }
1071
1072 SignalProxy<void (NetworkRequest &req, off_t dltotal, off_t dlnow, off_t ultotal, off_t ulnow)> NetworkRequest::sigProgress()
1073 {
1074 return d_func()->_sigProgress;
1075 }
1076
1078 {
1079 return d_func()->_sigFinished;
1080 }
1081
1082}
Store and operate with byte count.
Definition ByteCount.h:32
Unit::ValueType SizeType
Definition ByteCount.h:38
static const Unit B
1 Byte
Definition ByteCount.h:43
Compute Message Digests (MD5, SHA1 etc)
Definition Digest.h:38
bool create(const std::string &name)
initialize creation of a new message digest
Definition Digest.cc:198
Base class for Exception.
Definition Exception.h:153
std::string asString() const
Error message provided by dumpOn as string.
Definition Exception.cc:124
Url manipulation class.
Definition Url.h:93
const char * c_str() const
String representation.
Definition Pathname.h:112
bool empty() const
Test for an empty path.
Definition Pathname.h:116
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_* ...
long maxDownloadSpeed() const
Maximum download speed (bytes per second)
long connectTimeout() const
connection timeout
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 proxyEnabled() const
proxy is enabled
const Pathname & certificateAuthoritiesPath() const
SSL certificate authorities path ( default: /etc/ssl/certs )
bool verifyPeerEnabled() const
Whether to verify peer for ssl.
BasePrivate(Base &b)
Definition base_p.h:17
The CurlMultiPartHandler class.
const std::string & lastErrorMessage() const
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
std::string nativeErrorString() const
bool isError() const
isError Will return true if this is a actual error
size_t headerfunction(char *ptr, size_t bytes) override
Definition request.cc:608
std::optional< FileVerifyInfo > _fileVerification
The digest for the full file.
Definition request_p.h:117
enum zyppng::NetworkRequestPrivate::ProtocolMode _protocolMode
void notifyErrorCodeChanged() override
Definition request.cc:734
zypp::Pathname _currentCookieFile
Definition request_p.h:123
Signal< void(NetworkRequest &req, zypp::ByteCount count)> _sigBytesDownloaded
Definition request_p.h:130
NetworkRequestDispatcher * _dispatcher
Definition request_p.h:126
std::vector< NetworkRequest::Range > _requestedRanges
the requested ranges that need to be downloaded
Definition request_p.h:111
size_t writefunction(char *ptr, std::optional< off_t > offset, size_t bytes) override
Definition request.cc:669
static int curlProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
Definition request.cc:583
std::string errorMessage() const
Definition request.cc:569
Signal< void(NetworkRequest &req)> _sigStarted
Definition request_p.h:129
NetworkRequest::FileMode _fMode
Definition request_p.h:119
std::variant< pending_t, running_t, prepareNextRangeBatch_t, finished_t > _runningMode
Definition request_p.h:188
bool initialize(std::string &errBuf)
Definition request.cc:92
void onActivityTimeout(Timer &)
Definition request.cc:560
Signal< void(NetworkRequest &req, off_t dltotal, off_t dlnow, off_t ultotal, off_t ulnow)> _sigProgress
Definition request_p.h:131
std::string _lastRedirect
to log/report redirections
Definition request_p.h:122
NetworkRequest::Options _options
Definition request_p.h:109
bool prepareToContinue(std::string &errBuf)
Definition request.cc:414
void setResult(NetworkRequestError &&err)
Definition request.cc:491
~NetworkRequestPrivate() override
Definition request.cc:81
std::array< char, CURL_ERROR_SIZE+1 > _errorBuf
Definition request_p.h:95
bool setupHandle(std::string &errBuf)
Definition request.cc:105
NetworkRequestPrivate(Url &&url, zypp::Pathname &&targetFile, NetworkRequest::FileMode fMode, NetworkRequest &p)
Definition request.cc:73
TransferSettings _settings
Definition request_p.h:108
void setCurlOption(CURLoption opt, T data)
Definition request_p.h:98
zypp::ByteCount _expectedFileSize
Definition request_p.h:110
Signal< void(NetworkRequest &req, const NetworkRequestError &err)> _sigFinished
Definition request_p.h:132
std::unique_ptr< curl_slist, decltype(&curl_slist_free_all) > _headers
Definition request_p.h:140
bool setExpectedFileChecksum(const zypp::CheckSum &expected)
Definition request.cc:820
zypp::ByteCount reportedByteCount() const
Returns the number of bytes that are reported from the backend as the full download size,...
Definition request.cc:964
const zypp::Pathname & targetFilePath() const
Returns the target filename path.
Definition request.cc:930
zypp::ByteCount downloadedByteCount() const
Returns the number of already downloaded bytes as reported by the backend.
Definition request.cc:978
void setUrl(const Url &url)
This will change the URL of the request.
Definition request.cc:921
void setExpectedFileSize(zypp::ByteCount expectedFileSize)
Definition request.cc:764
void setPriority(Priority prio, bool triggerReschedule=true)
Definition request.cc:774
std::vector< char > peekData(off_t offset, size_t count) const
Definition request.cc:905
std::string contentType() const
Returns the content type as reported from the server.
Definition request.cc:956
void setFileOpenMode(FileMode mode)
Sets the file open mode to mode.
Definition request.cc:948
bool addRequestHeader(const std::string &header)
Definition request.cc:1038
~NetworkRequest() override
Definition request.cc:756
void setOptions(Options opt)
Definition request.cc:787
FileMode fileOpenMode() const
Returns the currently configured file open mode.
Definition request.cc:943
zypp::ByteCount expectedFileSize() const
Definition request.cc:769
bool hasError() const
Checks if there was a error with the request.
Definition request.cc:1033
State state() const
Returns the current state the HttpDownloadRequest is in.
Definition request.cc:998
SignalProxy< void(NetworkRequest &req, const NetworkRequestError &err)> sigFinished()
Signals that the download finished.
Definition request.cc:1077
UByteArray CheckSumBytes
Definition request.h:49
Options options() const
Definition request.cc:792
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:1067
std::optional< Timings > timings() const
After the request is finished query the timings that were collected during download.
Definition request.cc:877
std::string extendedErrorString() const
In some cases, curl can provide extended error information collected at runtime.
Definition request.cc:1025
NetworkRequest(Url url, zypp::Pathname targetFile, FileMode fMode=WriteExclusive)
Definition request.cc:751
Priority priority() const
Definition request.cc:782
NetworkRequestError error() const
Returns the last set Error.
Definition request.cc:1017
void setTargetFilePath(const zypp::Pathname &path)
Changes the target file path of the download.
Definition request.cc:935
const zypp::Pathname & cookieFile() const
Definition request.cc:1052
void * nativeHandle() const
Definition request.cc:872
void setCookieFile(zypp::Pathname cookieFile)
Definition request.cc:1057
void addRequestRange(size_t start, size_t len=0, std::optional< zypp::Digest > &&digest={}, CheckSumBytes expectedChkSum=CheckSumBytes(), std::any userData=std::any(), std::optional< size_t > digestCompareLen={}, std::optional< size_t > chksumpad={})
Definition request.cc:797
std::vector< Range > failedRanges() const
Definition request.cc:845
const std::vector< Range > & requestedRanges() const
Definition request.cc:862
SignalProxy< void(NetworkRequest &req)> sigStarted()
Signals that the dispatcher dequeued the request and actually starts downloading data.
Definition request.cc:1062
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:1072
TransferSettings & transferSettings()
Definition request.cc:993
CurlMultiPartHandler::Range Range
Definition request.h:76
const std::string & lastRedirectInfo() const
Definition request.cc:867
The Timer class provides repetitive and single-shot timers.
Definition timer.h:45
SignalProxy< void(Timer &t)> sigExpired()
This signal is always emitted when the timer expires.
Definition timer.cc:120
uint64_t remaining() const
Definition timer.cc:99
uint64_t interval() const
Definition timer.cc:94
#define EXPLICITLY_NO_PROXY
#define MIL_MEDIA
#define DBG_MEDIA
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)
Definition Arch.h:364
typename decay< T >::type decay_t
Definition TypeTraits.h:42
String related utilities and Regular expression matching.
int ZYPP_MEDIA_CURL_IPRESOLVE()
4/6 to force IPv4/v6
Definition curlhelper.cc:45
Types and functions for filesystem operations.
Definition Glob.cc:24
int assert_file_mode(const Pathname &path, unsigned mode)
Like assert_file but enforce mode even if the file already exists.
Definition PathInfo.cc:1210
std::vector< char > peek_data_fd(FILE *fd, off_t offset, size_t count)
Definition IOTools.cc:171
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition String.cc:39
TInt strtonum(const C_Str &str)
Parsing numbers from string.
std::string trim(const std::string &s, const Trim trim_r)
Definition String.cc:226
Url details namespace.
Definition UrlBase.cc:58
Easy-to use interface to the ZYPP dependency resolver.
T trim(StrType &&s, const Trim trim_r)
Definition string.h:35
zypp::media::TransferSettings TransferSettings
std::string strerr_cxx(const int err=-1)
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:254
static Range make(size_t start, size_t len=0, std::optional< zypp::Digest > &&digest={}, CheckSumBytes &&expectedChkSum=CheckSumBytes(), std::any &&userData=std::any(), std::optional< size_t > digestCompareLen={}, std::optional< size_t > _dataBlockPadding={})
std::unique_ptr< CurlMultiPartHandler > _partialHelper
Definition request_p.h:156
std::unique_ptr< CurlMultiPartHandler > _partialHelper
Definition request_p.h:166
running_t(pending_t &&prevState)
Definition request.cc:63
std::chrono::microseconds appconnect
Definition request.h:81
std::chrono::microseconds redirect
Definition request.h:84
std::chrono::microseconds pretransfer
Definition request.h:82
std::chrono::microseconds total
Definition request.h:83
std::chrono::microseconds namelookup
Definition request.h:79
std::chrono::microseconds connect
Definition request.h:80
#define ZYPP_CAUGHT(EXCPT)
Drops a logline telling the Exception was caught (in order to handle it).
Definition Exception.h:475
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:459
#define DBG
Definition Logger.h:99
#define MIL
Definition Logger.h:100
#define ZYPP_IMPL_PRIVATE(Class)
Definition zyppglobal.h:92
#define Z_D()
Definition zyppglobal.h:105