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