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