libzypp 17.37.17
curlmultiparthandler.cc
Go to the documentation of this file.
2
5
6#include <curl/curl.h>
7
8#include <utility>
9
10namespace zyppng {
11
12 namespace {
13
14 class CurlMultiPartSetoptError : public zypp::Exception
15 {
16 public:
17 CurlMultiPartSetoptError ( CURLcode code ) : _code(code){};
18 CURLcode code() const;
19 private:
20 CURLcode _code;
21 };
22
23 CURLcode CurlMultiPartSetoptError::code() const {
24 return _code;
25 }
26
27 class CurlMultInitRangeError : public zypp::Exception
28 {
29 public:
30 CurlMultInitRangeError ( std::string what ) : zypp::Exception( std::move(what) ){}
31 };
32
33 }
34
35 size_t CurlMultiPartHandler::curl_hdrcallback(char *ptr, size_t size, size_t nmemb, void *userdata)
36 {
37 if ( !userdata )
38 return 0;
39
40 CurlMultiPartHandler *that = reinterpret_cast<CurlMultiPartHandler *>( userdata );
41 return that->hdrcallback( ptr, size, nmemb );
42 }
43
44 size_t CurlMultiPartHandler::curl_wrtcallback(char *ptr, size_t size, size_t nmemb, void *userdata)
45 {
46 if ( !userdata )
47 return 0;
48
49 CurlMultiPartHandler *that = reinterpret_cast<CurlMultiPartHandler *>( userdata );
50 return that->wrtcallback( ptr, size, nmemb );
51 }
52
53 CurlMultiPartHandler::Range::Range(RangeDesc &&rd, std::optional<zypp::Digest> dig, std::any userD, State rangeState)
54 : RangeDesc( std::move(rd) )
55 , _digest( std::move(dig) )
56 , userData( std::move(userD) )
57 , _rangeState( rangeState )
58 { }
59
66
68 {
69 return Range (
70 RangeDesc {
71 ._start = _start,
72 ._len = _len,
73 ._chksumtype = _chksumtype,
74 ._checksum = _checksum,
75 ._relevantDigestLen = _relevantDigestLen,
76 ._chksumPad = _chksumPad
77 },
78 ( _digest ? _digest->clone() : std::optional<zypp::Digest>{} ),
81 );
82 }
83
84 CurlMultiPartHandler::Range CurlMultiPartHandler::Range::make(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> dataBlockPadding)
85 {
86 std::string chksumtype = ( digest ? digest->name() : std::string() );
87 return Range (
88 RangeDesc {
89 ._start = start,
90 ._len = len,
91 ._chksumtype = std::move(chksumtype),
92 ._checksum = std::move( expectedChkSum ),
93 ._relevantDigestLen = std::move( digestCompareLen ),
94 ._chksumPad = std::move( dataBlockPadding ),
95 },
96 std::move( digest ),
97 std::move( userData ),
99 );
100 }
101
103 : _protocolMode( mode )
105 , _receiver( receiver )
106 , _requestedRanges( ranges )
107 {
108 // non http can only do range by range
110 WAR << "!!!! Downloading ranges without HTTP might be slow !!!!" << std::endl;
112 }
113 }
114
116 {
117 if ( _easyHandle ) {
118 curl_easy_setopt( _easyHandle, CURLOPT_HEADERFUNCTION, nullptr );
119 curl_easy_setopt( _easyHandle, CURLOPT_HEADERDATA, nullptr );
120 curl_easy_setopt( _easyHandle, CURLOPT_WRITEFUNCTION, nullptr );
121 curl_easy_setopt( _easyHandle, CURLOPT_WRITEDATA, nullptr );
122 }
123 }
124
125 size_t CurlMultiPartHandler::hdrcallback(char *ptr, size_t size, size_t nmemb)
126 {
127 // it is valid to call this function with no data to read, just call the given handler
128 // or return ok
129 if ( size * nmemb == 0)
130 return _receiver.headerfunction( ptr, size * nmemb );
131
133
134 std::string_view hdr( ptr, size*nmemb );
135
136 hdr.remove_prefix( std::min( hdr.find_first_not_of(" \t\r\n"), hdr.size() ) );
137 const auto lastNonWhitespace = hdr.find_last_not_of(" \t\r\n");
138 if ( lastNonWhitespace != hdr.npos )
139 hdr.remove_suffix( hdr.size() - (lastNonWhitespace + 1) );
140 else
141 hdr = std::string_view();
142
143 // we just received whitespaces, wait for more
144 if ( !hdr.size() ) {
145 return ( size * nmemb );
146 }
147
148 if ( zypp::strv::hasPrefixCI( hdr, "HTTP/" ) ) {
149
150 long statuscode = 0;
151 (void)curl_easy_getinfo( _easyHandle, CURLINFO_RESPONSE_CODE, &statuscode);
152
153 const auto &doRangeFail = [&](){
154 WAR << _easyHandle << " " << "Range FAIL, trying with a smaller batch" << std::endl;
155
156 setCode ( Code::RangeFail, "Expected range status code 206, but got none." );
157
158 // reset all ranges we requested to pending, we never got the data for them
159 std::for_each( _requestedRanges.begin (), _requestedRanges.end(), []( auto &range ) {
160 if ( range._rangeState == Running )
161 range._rangeState = Pending;
162 });
163 return 0;
164 };
165
166 // ignore other status codes, maybe we are redirected etc.
167 if ( ( statuscode >= 200 && statuscode <= 299 && statuscode != 206 )
168 || statuscode == 416 ) {
169 return doRangeFail();
170 }
171
172 } else if ( zypp::strv::hasPrefixCI( hdr, "Content-Range:") ) {
173 Range r;
174
175 size_t fileLen = 0;
176 if ( !parseContentRangeHeader( hdr, r._start, r._len, fileLen ) ) {
177 //@TODO shouldn't we map this to a extra error? After all this is not our fault
178 setCode( Code::InternalError, "Invalid Content-Range header format." );
179 return 0;
180 }
181
182 if ( !_reportedFileSize ) {
183 WAR << "Setting request filesize to " << fileLen << std::endl;
184 _reportedFileSize = fileLen;
185 }
186
187 DBG << _easyHandle << " " << "Got content range :" << r._start << " len " << r._len << std::endl;
189 _currentSrvRange = std::move(r);
190
191 } else if ( zypp::strv::hasPrefixCI( hdr, "Content-Type:") ) {
192 std::string sep;
193 if ( parseContentTypeMultiRangeHeader( hdr, sep ) ) {
195 _isMuliPartResponse = true;
196 _seperatorString = "--"+sep;
197 }
198 }
199 }
200
201 return _receiver.headerfunction( ptr, size * nmemb );
202 }
203
204 size_t CurlMultiPartHandler::wrtcallback(char *ptr, size_t size, size_t nmemb)
205 {
206 const auto max = ( size * nmemb );
207
208 //it is valid to call this function with no data to write, just call the given handler
209 if ( max == 0)
210 return _receiver.writefunction( ptr, {}, max );
211
212 // try to consume all bytes that have been given to us
213 size_t bytesConsumedSoFar = 0;
214 while ( bytesConsumedSoFar != max ) {
215
216 std::optional<off_t> seekTo;
217
218 // this is called after all headers have been processed
219 if ( !_allHeadersReceived ) {
220 _allHeadersReceived = true;
221
222 // no ranges at all, this is a error
224 //we got a invalid response, the status code pointed to being partial ( 206 ) but we got no range definition
225 setCode( Code::ServerReturnedError, "Invalid data from server, range respone was announced but there was no range definiton." );
226 return 0;
227 }
228 }
229
231 /*
232 this branch is responsible to find a range that overlaps the current server range, so we know which of our ranges to fill
233 Entering here means we are in one of two situations:
234
235 1) we just have finished writing a requested range but
236 still have not completely consumed a range that we have received from the server.
237 Since HTTP std allows the server to coalesce requested ranges in order to optimize downloads
238 we need to find the best match ( because the current offset might not even be in our requested ranges )
239
240 2) we just parsed a Content-Length header and start a new block
241 */
242
243 std::optional<uint> foundRange;
244 const size_t beginSrvRange = _currentSrvRange->_start + _currentSrvRange->bytesWritten;
245 const size_t endSrvRange = beginSrvRange + (_currentSrvRange->_len - _currentSrvRange->bytesWritten);
246 auto currDist = ULONG_MAX;
247 for ( uint i = 0; i < _requestedRanges.size(); i++ ) {
248 const auto &currR = _requestedRanges[i];
249
250 // do not allow double ranges
251 if ( currR._rangeState == Finished || currR._rangeState == Error )
252 continue;
253
254 // check if the range was already written
255 if ( currR._len && currR._len == currR.bytesWritten )
256 continue;
257
258 const auto currRBegin = currR._start + currR.bytesWritten;
259 if ( !( beginSrvRange <= currRBegin && endSrvRange >= currRBegin ) )
260 continue;
261
262 // calculate the distance of the current ranges offset+data written to the range we got back from the server
263 const auto newDist = currRBegin - beginSrvRange;
264
265 // exact match
266 if ( currRBegin == beginSrvRange && currR._len == _currentSrvRange->_len ) {
267 foundRange = i;
268 break;
269 }
270
271 if ( !foundRange ) {
272 foundRange = i;
273 currDist = newDist;
274 } else {
275 //pick the range with the closest distance
276 if ( newDist < currDist ) {
277 foundRange = i;
278 currDist = newDist;
279 }
280 }
281 }
282
283 if ( !foundRange ) {
284 // @TODO shouldn't we simply consume the rest of the range here and see if future data will contain a matchable range again?
285 setCode( Code::InternalError, "Unable to find a matching range for data returned by the server.");
286 return 0;
287 }
288
289 //set the found range as the current one
290 _currentRange = *foundRange;
291
292 //continue writing where we stopped
293 seekTo = _requestedRanges[*foundRange]._start + _requestedRanges[*foundRange].bytesWritten;
294
295 //if we skip bytes we need to advance our written bytecount
296 const auto skipBytes = *seekTo - beginSrvRange;
297 bytesConsumedSoFar += skipBytes;
298 _currentSrvRange->bytesWritten += skipBytes;
299
300 std::string errBuf = "Receiver cancelled starting the current range.";
301 if ( !_receiver.beginRange (*_currentRange, errBuf) ) {
302 setCode( Code::InternalError, "Receiver cancelled starting the current range.");
303 return 0;
304 }
305
306 } else if ( _protocolMode != ProtocolMode::HTTP && !_currentRange ) {
307 // if we are not running in HTTP mode we can only request a single range, that means we get our data
308 // in one continous stream. Since our
309 // ranges are ordered by start, we just pick the first one that is marked as running
310 if ( !_currentRange ) {
311 const auto i = std::find_if( _requestedRanges.begin (), _requestedRanges.end(), []( const Range &r){ return r._rangeState == Running; });
312 if ( i == _requestedRanges.end() ) {
313 setCode( Code::InternalError, "Received data but no range was marked as requested." );
314 return 0;
315 }
316
317 _currentRange = std::distance( _requestedRanges.begin(), i );
318
319 //continue writing where we stopped
320 seekTo = _requestedRanges[*_currentRange]._start + _requestedRanges[*_currentRange].bytesWritten;
321 }
322 }
323
324 if ( _currentRange ) {
325 /*
326 * We have data to write and know the target range
327 */
328
329 // make sure we do not read over the current server range
330 auto availableData = max - bytesConsumedSoFar;
331 if ( _currentSrvRange ) {
332 availableData = std::min( availableData, _currentSrvRange->_len - _currentSrvRange->bytesWritten );
333 }
334
335 auto &rng = _requestedRanges[*_currentRange];
336
337 // do only write what we need until the range is full
338 const auto bytesToWrite = rng._len > 0 ? std::min( rng._len - rng.bytesWritten, availableData ) : availableData;
339
340 auto written = _receiver.writefunction( ptr + bytesConsumedSoFar, seekTo, bytesToWrite );
341 if ( written <= 0 )
342 return 0;
343
344 if ( rng._digest && rng._checksum.size() ) {
345 if ( !rng._digest->update( ptr + bytesConsumedSoFar, written ) )
346 return 0;
347 }
348
349 rng.bytesWritten += written;
350 if ( _currentSrvRange ) _currentSrvRange->bytesWritten += written;
351
352 // range is done
353 if ( rng._len > 0 && rng.bytesWritten >= rng._len ) {
354 std::string errBuf = "Receiver cancelled after finishing the current range.";
355 bool rngIsValid = validateRange( rng );
356 if ( !_receiver.finishedRange (*_currentRange, rngIsValid, errBuf) ) {
357 setCode( Code::InternalError, "Receiver cancelled starting the current range.");
358 return 0;
359 }
360 _currentRange.reset();
361 }
362
363 if ( _currentSrvRange && _currentSrvRange->_len > 0 && _currentSrvRange->bytesWritten >= _currentSrvRange->_len ) {
364 _currentSrvRange.reset();
365 // we ran out of data in the current chunk, reset the target range if it is not a open range as well, because next data will be
366 // a chunk header again
368 _currentRange.reset();
369 }
370
371 bytesConsumedSoFar += written;
372 }
373
374 if ( bytesConsumedSoFar == max )
375 return max;
376
378 /*
379 This branch is reponsible to parse the multibyte response we got from the
380 server and parse the next target range from it
381 */
382
383 std::string_view incoming( ptr + bytesConsumedSoFar, max - bytesConsumedSoFar );
384 auto hdrEnd = incoming.find("\r\n\r\n");
385 if ( hdrEnd == incoming.npos ) {
386 //no header end in the data yet, push to buffer and return
387 _rangePrefaceBuffer.insert( _rangePrefaceBuffer.end(), incoming.begin(), incoming.end() );
388 return max;
389 }
390
391 //append the data of the current header to the buffer and parse it
392 _rangePrefaceBuffer.insert( _rangePrefaceBuffer.end(), incoming.begin(), incoming.begin() + ( hdrEnd + 4 ) );
393 bytesConsumedSoFar += ( hdrEnd + 4 ); //header data plus header end
394
395 std::string_view data( _rangePrefaceBuffer.data(), _rangePrefaceBuffer.size() );
396 auto sepStrIndex = data.find( _seperatorString );
397 if ( sepStrIndex == data.npos ) {
398 setCode( Code::InternalError, "Invalid multirange header format, seperator string missing." );
399 return 0;
400 }
401
402 auto startOfHeader = sepStrIndex + _seperatorString.length();
403 std::vector<std::string_view> lines;
404 zypp::strv::split( data.substr( startOfHeader ), "\r\n", zypp::strv::Trim::trim, [&]( std::string_view strv ) { lines.push_back(strv); } );
405 for ( const auto &hdrLine : lines ) {
406 if ( zypp::strv::hasPrefixCI(hdrLine, "Content-Range:") ) {
407 size_t fileLen = 0;
408 Range r;
409 //if we can not parse the header the message must be broken
410 if(! parseContentRangeHeader( hdrLine, r._start, r._len, fileLen ) ) {
411 setCode( Code::InternalError, "Invalid Content-Range header format." );
412 return 0;
413 }
414 if ( !_reportedFileSize ) {
415 _reportedFileSize = fileLen;
416 }
417 _currentSrvRange = std::move(r);
418 break;
419 }
420 }
421
422 if ( !_currentSrvRange ){
423 setCode( Code::InternalError, "Could not read a server range from the response." );
424 return 0;
425 }
426
427 //clear the buffer again
428 _rangePrefaceBuffer.clear();
429 }
430 }
431
432 return bytesConsumedSoFar;
433 }
434
436 {
437 return _easyHandle;
438 }
439
441 {
442 // We can recover from RangeFail errors if we have more batch sizes to try
443 // we never auto downgrade to last range set ( which is 1 ) because in that case
444 // just downloading the full file is usually faster.
445 if ( _lastCode == Code::RangeFail )
446 return ( _rangeAttemptIdx + 1 < ( _rangeAttemptSize - 1 ) ) && hasMoreWork();
447 return false;
448 }
449
451 {
452 // check if we have ranges that have never been requested
453 return std::any_of( _requestedRanges.begin(), _requestedRanges.end(), []( const auto &range ){ return range._rangeState == Pending; });
454 }
455
457 {
458 return _lastCode != Code::NoError;
459 }
460
465
466 const std::string &CurlMultiPartHandler::lastErrorMessage() const
467 {
468 return _lastErrorMsg;
469 }
470
472 {
473 if ( hasMoreWork() ) {
474 // go to the next range batch level if we are restarted due to a failed range request
475 if ( _lastCode == Code::RangeFail ) {
476 if ( _rangeAttemptIdx + 1 >= _rangeAttemptSize ) {
477 setCode ( Code::RangeFail, "No more range batch sizes available", true );
478 return false;
479 }
481 }
482 return true;
483 }
484
485 setCode ( Code::NoError, "Request has no more work", true );
486 return false;
487
488 }
489
491 {
492 // if we still have a current range set it valid by checking the checksum
493 if ( _currentRange ) {
494 auto &currR = _requestedRanges[*_currentRange];
495 std::string errBuf;
496 bool rngIsValid = validateRange( currR );
497 _receiver.finishedRange (*_currentRange, rngIsValid, errBuf);
498 _currentRange.reset();
499 }
500 }
501
503 {
504 finalize();
505
506 for ( auto &r : _requestedRanges ) {
507 if ( r._rangeState != CurlMultiPartHandler::Finished ) {
508 if ( r._len > 0 && r.bytesWritten != r._len )
509 setCode( Code::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 ) );
510 else if ( r._digest && r._checksum.size() && !checkIfRangeChkSumIsValid(r) ) {
511 setCode( Code::InvalidChecksum, (zypp::str::Format("Invalid checksum %1%, expected checksum %2%") % r._digest->digest() % zypp::Digest::digestVectorToString( r._checksum ) ) );
512 } else {
513 setCode( Code::InternalError, (zypp::str::Format("Download of block failed.") ) );
514 }
515 //we only report the first error
516 break;
517 }
518 }
519 return ( _lastCode == Code::NoError );
520 }
521
523 {
524 _lastCode = Code::NoError;
525 _lastErrorMsg.clear();
526 _seperatorString.clear();
527 _currentSrvRange.reset();
528 _reportedFileSize.reset();
529 _gotContentRangeInfo = false;
530 _allHeadersReceived = false;
531 _isMuliPartResponse = false;
532
533 if ( _requestedRanges.size() == 0 ) {
534 setCode( Code::InternalError, "Calling the CurlMultiPartHandler::prepare function without a range to download is not supported.");
535 return false;
536 }
537
538 const auto setCurlOption = [&]( CURLoption opt, auto &&data )
539 {
540 auto ret = curl_easy_setopt( _easyHandle, opt, data );
541 if ( ret != 0 ) {
542 throw CurlMultiPartSetoptError(ret);
543 }
544 };
545
546 try {
547 setCurlOption( CURLOPT_HEADERFUNCTION, CurlMultiPartHandler::curl_hdrcallback );
548 setCurlOption( CURLOPT_HEADERDATA, this );
549 setCurlOption( CURLOPT_WRITEFUNCTION, CurlMultiPartHandler::curl_wrtcallback );
550 setCurlOption( CURLOPT_WRITEDATA, this );
551
552 std::string rangeDesc;
553 uint rangesAdded = 0;
554 auto maxRanges = _rangeAttempt[_rangeAttemptIdx];
555
556 // helper function to build up the request string for the range
557 auto addRangeString = [ &rangeDesc, &rangesAdded ]( const std::pair<size_t, size_t> &range ) {
558 std::string rangeD = zypp::str::form("%llu-", static_cast<unsigned long long>( range.first ) );
559 if( range.second > 0 )
560 rangeD.append( zypp::str::form( "%llu", static_cast<unsigned long long>( range.second ) ) );
561
562 if ( rangeDesc.size() )
563 rangeDesc.append(",").append( rangeD );
564 else
565 rangeDesc = std::move( rangeD );
566
567 rangesAdded++;
568 };
569
570 std::optional<std::pair<size_t, size_t>> currentZippedRange;
571 bool closedRange = true;
572 for ( auto &range : _requestedRanges ) {
573
574 if ( range._rangeState != Pending )
575 continue;
576
577 //reset the download results
578 range.bytesWritten = 0;
579
580 //when we have a open range in the list of ranges we will get from start of range to end of file,
581 //all following ranges would never be marked as valid, so we have to fail early
582 if ( !closedRange )
583 throw CurlMultInitRangeError("It is not supported to request more ranges after a open range.");
584
585 const auto rangeEnd = range._len > 0 ? range._start + range._len - 1 : 0;
586 closedRange = (rangeEnd > 0);
587
588 bool added = false;
589
590 // we try to compress the requested ranges into as big chunks as possible for the request,
591 // when receiving we still track the original ranges so we can collect and test their checksums
592 if ( !currentZippedRange ) {
593 added = true;
594 currentZippedRange = std::make_pair( range._start, rangeEnd );
595 } else {
596 //range is directly consecutive to the previous range
597 if ( currentZippedRange->second + 1 == range._start ) {
598 added = true;
599 currentZippedRange->second = rangeEnd;
600 } else {
601 //this range does not directly follow the previous one, we build the string and start a new one
602 if ( rangesAdded +1 >= maxRanges ) break;
603 added = true;
604 addRangeString( *currentZippedRange );
605 currentZippedRange = std::make_pair( range._start, rangeEnd );
606 }
607 }
608
609 // remember range was already requested
610 if ( added ) {
611 setRangeState( range, Running );
612 range.bytesWritten = 0;
613 if ( range._digest )
614 range._digest->reset();
615 }
616
617 if ( rangesAdded >= maxRanges ) {
618 MIL << _easyHandle << " " << "Reached max nr of ranges (" << maxRanges << "), batching the request to not break the server" << std::endl;
619 break;
620 }
621 }
622
623 // add the last range too
624 if ( currentZippedRange )
625 addRangeString( *currentZippedRange );
626
627 MIL << _easyHandle << " " << "Requesting Ranges: " << rangeDesc << std::endl;
628
629 setCurlOption( CURLOPT_RANGE, rangeDesc.c_str() );
630
631 } catch( const CurlMultiPartSetoptError &err ) {
632 setCode( Code::InternalError, "" );
633 } catch( const CurlMultInitRangeError &err ) {
634 setCode( Code::InternalError, err.asUserString() );
635 } catch( const zypp::Exception &err ) {
636 setCode( Code::InternalError, err.asUserString() );
637 } catch( const std::exception &err ) {
638 setCode( Code::InternalError, zypp::str::asString(err.what()) );
639 }
640 return ( _lastCode == Code::NoError );
641 }
642
643 void CurlMultiPartHandler::setCode(Code c, std::string msg , bool force)
644 {
645 // never overwrite a error, this is reset when we restart
646 if ( _lastCode != Code::NoError && !force )
647 return;
648
649 _lastCode = c;
650 _lastErrorMsg = std::move(msg);
651 _receiver.notifyErrorCodeChanged();
652 }
653
654 bool CurlMultiPartHandler::parseContentRangeHeader( const std::string_view &line, size_t &start, size_t &len, size_t &fileLen )
655 {
656 //content-range: bytes 10485760-19147879/19147880
657 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 );
658
660 if( !zypp::str::regex_match( std::string(line), what, regex ) || what.size() != 4 ) {
661 DBG << _easyHandle << " " << "Invalid Content-Range Header format: '" << std::string(line) << std::endl;
662 return false;
663 }
664
665 size_t s = zypp::str::strtonum<size_t>( what[1]);
666 size_t e = zypp::str::strtonum<size_t>( what[2]);
667 fileLen = zypp::str::strtonum<size_t>( what[3]);
668 start = s;
669 len = ( e - s ) + 1;
670 return true;
671 }
672
673 bool CurlMultiPartHandler::parseContentTypeMultiRangeHeader( const std::string_view &line, std::string &boundary )
674 {
675 static const zypp::str::regex regex("^Content-Type:[[:space:]]+multipart\\/byteranges;[[:space:]]+boundary=(.*)$", zypp::str::regex::rxdefault | zypp::str::regex::icase );
676
678 if( zypp::str::regex_match( std::string(line), what, regex ) ) {
679 if ( what.size() >= 2 ) {
680 boundary = what[1];
681 return true;
682 }
683 }
684 return false;
685 }
686
688 {
689 if ( rng._digest && rng._checksum.size() ) {
690 if ( ( rng._len == 0 || rng.bytesWritten == rng._len ) && checkIfRangeChkSumIsValid(rng) )
692 else
693 setRangeState(rng, Error);
694 } else {
695 if ( rng._len == 0 ? true : rng.bytesWritten == rng._len )
697 else
698 setRangeState(rng, Error);
699 }
700 return ( rng._rangeState == Finished );
701 }
702
704 {
705 if ( rng._digest && rng._checksum.size() ) {
706 auto bytesHashed = rng._digest->bytesHashed ();
707 if ( rng._chksumPad && *rng._chksumPad > bytesHashed ) {
708 MIL_MEDIA << _easyHandle << " " << "Padding the digest to required block size" << std::endl;
709 zypp::ByteArray padding( *rng._chksumPad - bytesHashed, '\0' );
710 rng._digest->update( padding.data(), padding.size() );
711 }
712 auto digVec = rng._digest->digestVector();
713 if ( rng._relevantDigestLen ) {
714 digVec.resize( *rng._relevantDigestLen );
715 }
716 return ( digVec == rng._checksum );
717 }
718
719 // no checksum required
720 return true;
721 }
722
724 {
725 if ( rng._rangeState != state ) {
726 rng._rangeState = state;
727 }
728 }
729
730 std::optional<off_t> CurlMultiPartHandler::currentRange() const
731 {
732 return _currentRange;
733 }
734
735 std::optional<size_t> CurlMultiPartHandler::reportedFileSize() const
736 {
737 return _reportedFileSize;
738 }
739
740} // namespace zyppng
static std::string digestVectorToString(const UByteArray &vec)
get hex string representation of the digest vector given as parameter
Definition Digest.cc:244
Base class for Exception.
Definition Exception.h:153
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
size_t wrtcallback(char *ptr, size_t size, size_t nmemb)
static constexpr unsigned _rangeAttemptSize
CurlMultiPartHandler(ProtocolMode mode, void *easyHandle, std::vector< Range > &ranges, CurlMultiPartDataReceiver &receiver)
std::optional< Range > _currentSrvRange
std::string _seperatorString
The seperator string for multipart responses as defined in RFC 7233 Section 4.1.
NetworkRequestError::Type Code
static size_t curl_wrtcallback(char *ptr, size_t size, size_t nmemb, void *userdata)
static size_t curl_hdrcallback(char *ptr, size_t size, size_t nmemb, void *userdata)
const std::string & lastErrorMessage() const
static constexpr unsigned _rangeAttempt[]
bool parseContentRangeHeader(const std::string_view &line, size_t &start, size_t &len, size_t &fileLen)
void setCode(Code c, std::string msg, bool force=false)
std::vector< char > _rangePrefaceBuffer
Here we buffer.
std::optional< off_t > currentRange() const
std::optional< size_t > _reportedFileSize
Filesize as reported by the content range or byte range headers.
size_t hdrcallback(char *ptr, size_t size, size_t nmemb)
CurlMultiPartDataReceiver & _receiver
void setRangeState(Range &rng, State state)
std::optional< size_t > reportedFileSize() const
bool parseContentTypeMultiRangeHeader(const std::string_view &line, std::string &boundary)
std::vector< Range > & _requestedRanges
the requested ranges that need to be downloaded
std::optional< off_t > _currentRange
#define MIL_MEDIA
Definition Arch.h:364
const std::string & asString(const std::string &t)
Global asString() that works with std::string too.
Definition String.h:140
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:39
TInt strtonum(const C_Str &str)
Parsing numbers from string.
Convenient building of std::string with boost::format.
Definition String.h:254
std::optional< zypp::Digest > _digest
Enables automated checking of downloaded contents against a checksum.
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::string _chksumtype
Enables automated checking of downloaded contents against a checksum.
Definition rangedesc.h:35
UByteArray _checksum
Definition rangedesc.h:36
std::optional< size_t > _relevantDigestLen
Definition rangedesc.h:37
std::optional< size_t > _chksumPad
Definition rangedesc.h:38
#define DBG
Definition Logger.h:99
#define MIL
Definition Logger.h:100
#define WAR
Definition Logger.h:101