libzypp  17.31.31
curlmultiparthandler.cc
Go to the documentation of this file.
1 #include "curlmultiparthandler.h"
2 
4 #include <zypp-core/base/StringV.h>
5 
6 #include <curl/curl.h>
7 
8 namespace 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;
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;
174  _gotContentRangeInfo = true;
175  _currentSrvRange = std::move(r);
176 
177  } else if ( zypp::strv::hasPrefixCI( hdr, "Content-Type:") ) {
178  std::string sep;
179  if ( parseContentTypeMultiRangeHeader( hdr, sep ) ) {
180  _gotContentRangeInfo = true;
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
353  if ( _isMuliPartResponse )
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 < ( _rangeAttemptSize - 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 >= _rangeAttemptSize ) {
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 
644  zypp::str::smatch what;
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 
662  zypp::str::smatch what;
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) )
676  setRangeState(rng, Finished);
677  else
678  setRangeState(rng, Error);
679  } else {
680  if ( rng.len == 0 ? true : rng.bytesWritten == rng.len )
681  setRangeState(rng, Finished);
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
std::optional< size_t > reportedFileSize() const
CurlMultiPartDataReceiver & _receiver
#define MIL
Definition: Logger.h:96
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:36
static std::string digestVectorToString(const UByteArray &vec)
get hex string representation of the digest vector given as parameter
Definition: Digest.cc:241
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:223
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:82
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:97
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:145
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: CodePitfalls.doc:1
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:95
bool parseContentTypeMultiRangeHeader(const std::string_view &line, std::string &boundary)
size_t wrtcallback(char *ptr, size_t size, size_t nmemb)