1 /**
2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
6 *
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
10 * License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
19 */
20
21 /**
22 * xmltooling/util/CurlURLInputStream.cpp
23 *
24 * Asynchronous use of curl to fetch data from a URL.
25 */
26
27 #include "internal.h"
28
29 #include <xmltooling/util/CurlURLInputStream.h>
30 #include <xmltooling/util/ParserPool.h>
31 #include <xmltooling/util/XMLHelper.h>
32
33 #include <openssl/ssl.h>
34 #include <xercesc/util/XercesDefs.hpp>
35 #include <xercesc/util/XMLNetAccessor.hpp>
36 #include <xercesc/util/XMLString.hpp>
37 #include <xercesc/util/XMLExceptMsgs.hpp>
38 #include <xercesc/util/Janitor.hpp>
39 #include <xercesc/util/XMLUniDefs.hpp>
40 #include <xercesc/util/TransService.hpp>
41 #include <xercesc/util/TranscodingException.hpp>
42 #include <xercesc/util/PlatformUtils.hpp>
43
44 using namespace xmltooling;
45 using namespace xercesc;
46 using namespace std;
47
48 namespace {
49 static const XMLCh _CURL[] = UNICODE_LITERAL_4(C,U,R,L);
50 static const XMLCh _OpenSSL[] = UNICODE_LITERAL_7(O,p,e,n,S,S,L);
51 static const XMLCh _option[] = UNICODE_LITERAL_6(o,p,t,i,o,n);
52 static const XMLCh _provider[] = UNICODE_LITERAL_8(p,r,o,v,i,d,e,r);
53 static const XMLCh TransportOption[] = UNICODE_LITERAL_15(T,r,a,n,s,p,o,r,t,O,p,t,i,o,n);
54 static const XMLCh uri[] = UNICODE_LITERAL_3(u,r,i);
55 static const XMLCh url[] = UNICODE_LITERAL_3(u,r,l);
56 static const XMLCh verifyHost[] = UNICODE_LITERAL_10(v,e,r,i,f,y,H,o,s,t);
57
58 // callback to invoke a caller-defined SSL callback
ssl_ctx_callback(CURL * curl,SSL_CTX * ssl_ctx,void * userptr)59 CURLcode ssl_ctx_callback(CURL* curl, SSL_CTX* ssl_ctx, void* userptr)
60 {
61 CurlURLInputStream* str = reinterpret_cast<CurlURLInputStream*>(userptr);
62
63 // Default flags manually disable SSLv2 so we're not dependent on libcurl to do it.
64 // Also disable the ticket option where implemented, since this breaks a variety
65 // of servers. Newer libcurl also does this for us.
66 #ifdef SSL_OP_NO_TICKET
67 SSL_CTX_set_options(ssl_ctx, str->getOpenSSLOps()|SSL_OP_NO_TICKET);
68 #else
69 SSL_CTX_set_options(ssl_ctx, str->getOpenSSLOps());
70 #endif
71
72 return CURLE_OK;
73 }
74
curl_header_hook(void * ptr,size_t size,size_t nmemb,void * stream)75 size_t curl_header_hook(void* ptr, size_t size, size_t nmemb, void* stream)
76 {
77 // only handle single-byte data
78 if (size != 1 || nmemb < 5 || !stream)
79 return nmemb;
80 string* cacheTag = reinterpret_cast<string*>(stream);
81 const char* hdr = reinterpret_cast<char*>(ptr);
82 if (strncmp(hdr, "ETag:", 5) == 0) {
83 hdr += 5;
84 size_t remaining = nmemb - 5;
85 // skip leading spaces
86 while (remaining > 0) {
87 if (*hdr == ' ') {
88 ++hdr;
89 --remaining;
90 continue;
91 }
92 break;
93 }
94 // append until whitespace
95 cacheTag->erase();
96 while (remaining > 0) {
97 if (!isspace(*hdr)) {
98 (*cacheTag) += *hdr++;
99 --remaining;
100 continue;
101 }
102 break;
103 }
104
105 if (!cacheTag->empty())
106 *cacheTag = "If-None-Match: " + *cacheTag;
107 }
108 else if (cacheTag->empty() && strncmp(hdr, "Last-Modified:", 14) == 0) {
109 hdr += 14;
110 size_t remaining = nmemb - 14;
111 // skip leading spaces
112 while (remaining > 0) {
113 if (*hdr == ' ') {
114 ++hdr;
115 --remaining;
116 continue;
117 }
118 break;
119 }
120 // append until data's gone or we see a CR/LF
121 while (remaining > 0) {
122 if (*hdr != '\r' && *hdr != '\n') {
123 (*cacheTag) += *hdr++;
124 --remaining;
125 continue;
126 }
127 break;
128 }
129
130 if (!cacheTag->empty())
131 *cacheTag = "If-Modified-Since: " + *cacheTag;
132 }
133
134 return nmemb;
135 }
136 }
137
CurlURLInputStream(const char * url,string * cacheTag)138 CurlURLInputStream::CurlURLInputStream(const char* url, string* cacheTag)
139 : fLog(logging::Category::getInstance(XMLTOOLING_LOGCAT ".libcurl.InputStream"))
140 , fCacheTag(cacheTag)
141 , fURL(url ? url : "")
142 , fOpenSSLOps(SSL_OP_ALL|SSL_OP_NO_SSLv2)
143 , fMulti(0)
144 , fEasy(0)
145 , fHeaders(0)
146 , fTotalBytesRead(0)
147 , fWritePtr(0)
148 , fBytesRead(0)
149 , fBytesToRead(0)
150 , fDataAvailable(false)
151 , fBuffer(0)
152 , fBufferHeadPtr(0)
153 , fBufferTailPtr(0)
154 , fBufferSize(0)
155 , fContentType(0)
156 , fStatusCode(200)
157 {
158 if (fURL.empty())
159 throw IOException("No URL supplied to CurlURLInputStream constructor.");
160 init();
161 }
162
CurlURLInputStream(const XMLCh * url,string * cacheTag)163 CurlURLInputStream::CurlURLInputStream(const XMLCh* url, string* cacheTag)
164 : fLog(logging::Category::getInstance(XMLTOOLING_LOGCAT ".libcurl.InputStream"))
165 , fCacheTag(cacheTag)
166 , fOpenSSLOps(SSL_OP_ALL|SSL_OP_NO_SSLv2)
167 , fMulti(0)
168 , fEasy(0)
169 , fHeaders(0)
170 , fTotalBytesRead(0)
171 , fWritePtr(0)
172 , fBytesRead(0)
173 , fBytesToRead(0)
174 , fDataAvailable(false)
175 , fBuffer(0)
176 , fBufferHeadPtr(0)
177 , fBufferTailPtr(0)
178 , fBufferSize(0)
179 , fContentType(0)
180 , fStatusCode(200)
181 {
182 if (url) {
183 auto_ptr_char temp(url);
184 fURL = temp.get();
185 }
186 if (fURL.empty())
187 throw IOException("No URL supplied to CurlURLInputStream constructor.");
188 init();
189 }
190
CurlURLInputStream(const DOMElement * e,string * cacheTag)191 CurlURLInputStream::CurlURLInputStream(const DOMElement* e, string* cacheTag)
192 : fLog(logging::Category::getInstance(XMLTOOLING_LOGCAT ".libcurl.InputStream"))
193 , fCacheTag(cacheTag)
194 , fOpenSSLOps(SSL_OP_ALL|SSL_OP_NO_SSLv2)
195 , fMulti(0)
196 , fEasy(0)
197 , fHeaders(0)
198 , fTotalBytesRead(0)
199 , fWritePtr(0)
200 , fBytesRead(0)
201 , fBytesToRead(0)
202 , fDataAvailable(false)
203 , fBuffer(0)
204 , fBufferHeadPtr(0)
205 , fBufferTailPtr(0)
206 , fBufferSize(0)
207 , fContentType(0)
208 , fStatusCode(200)
209 {
210 const XMLCh* attr = e->getAttributeNS(nullptr, url);
211 if (!attr || !*attr) {
212 attr = e->getAttributeNS(nullptr, uri);
213 if (!attr || !*attr)
214 throw IOException("No URL supplied via DOM to CurlURLInputStream constructor.");
215 }
216
217 auto_ptr_char temp(attr);
218 fURL = temp.get();
219 init(e);
220 }
221
~CurlURLInputStream()222 CurlURLInputStream::~CurlURLInputStream()
223 {
224 if (fEasy) {
225 // Remove the easy handle from the multi stack
226 curl_multi_remove_handle(fMulti, fEasy);
227
228 // Cleanup the easy handle
229 curl_easy_cleanup(fEasy);
230 }
231
232 if (fMulti) {
233 // Cleanup the multi handle
234 curl_multi_cleanup(fMulti);
235 }
236
237 if (fHeaders) {
238 curl_slist_free_all(fHeaders);
239 }
240
241 XMLString::release(&fContentType);
242 free(fBuffer);
243 }
244
init(const DOMElement * e)245 void CurlURLInputStream::init(const DOMElement* e)
246 {
247 // Allocate the curl multi handle
248 fMulti = curl_multi_init();
249
250 // Allocate the curl easy handle
251 fEasy = curl_easy_init();
252
253 if (!fMulti || !fEasy)
254 throw IOException("Failed to allocate libcurl handles.");
255
256 curl_easy_setopt(fEasy, CURLOPT_URL, fURL.c_str());
257
258 // Set up a way to recieve the data
259 curl_easy_setopt(fEasy, CURLOPT_WRITEDATA, this); // Pass this pointer to write function
260 curl_easy_setopt(fEasy, CURLOPT_WRITEFUNCTION, staticWriteCallback); // Our static write function
261
262 // Do redirects
263 curl_easy_setopt(fEasy, CURLOPT_FOLLOWLOCATION, 1);
264 curl_easy_setopt(fEasy, CURLOPT_MAXREDIRS, 6);
265
266 // Default settings.
267 curl_easy_setopt(fEasy, CURLOPT_CONNECTTIMEOUT, 10);
268 curl_easy_setopt(fEasy, CURLOPT_TIMEOUT, 60);
269 curl_easy_setopt(fEasy, CURLOPT_HTTPAUTH, 0);
270 curl_easy_setopt(fEasy, CURLOPT_USERPWD, 0);
271 curl_easy_setopt(fEasy, CURLOPT_SSL_VERIFYHOST, 2);
272 curl_easy_setopt(fEasy, CURLOPT_SSL_VERIFYPEER, 0);
273 curl_easy_setopt(fEasy, CURLOPT_CAINFO, 0);
274 curl_easy_setopt(fEasy, CURLOPT_SSL_CIPHER_LIST, "ALL:!aNULL:!LOW:!EXPORT:!SSLv2");
275 curl_easy_setopt(fEasy, CURLOPT_NOPROGRESS, 1);
276 curl_easy_setopt(fEasy, CURLOPT_NOSIGNAL, 1);
277 curl_easy_setopt(fEasy, CURLOPT_FAILONERROR, 1);
278 #if HAVE_DECL_CURLOPT_ACCEPT_ENCODING
279 curl_easy_setopt(fEasy, CURLOPT_ACCEPT_ENCODING, "");
280 #else
281 curl_easy_setopt(fEasy, CURLOPT_ENCODING, "");
282 #endif
283
284 // Install SSL callback.
285 curl_easy_setopt(fEasy, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback);
286 curl_easy_setopt(fEasy, CURLOPT_SSL_CTX_DATA, this);
287
288 fError[0] = 0;
289 curl_easy_setopt(fEasy, CURLOPT_ERRORBUFFER, fError);
290
291 // Check for cache tag.
292 if (fCacheTag) {
293 // Outgoing tag.
294 if (!fCacheTag->empty()) {
295 fHeaders = curl_slist_append(fHeaders, fCacheTag->c_str());
296 }
297 // Incoming tag.
298 curl_easy_setopt(fEasy, CURLOPT_HEADERFUNCTION, curl_header_hook);
299 curl_easy_setopt(fEasy, CURLOPT_HEADERDATA, fCacheTag);
300 }
301
302 // Add User-Agent as a header for now. TODO: Add private member to hold the
303 // value for the standard UA option.
304 string ua = string("User-Agent: ") + XMLToolingConfig::getConfig().user_agent +
305 " libcurl/" + LIBCURL_VERSION + ' ' + OPENSSL_VERSION_TEXT;
306 fHeaders = curl_slist_append(fHeaders, ua.c_str());
307
308 fHeaders = curl_slist_append(fHeaders, "Expect:");
309
310 // Add User-Agent and cache headers.
311 curl_easy_setopt(fEasy, CURLOPT_HTTPHEADER, fHeaders);
312
313 if (e) {
314 const XMLCh* flag = e->getAttributeNS(nullptr, verifyHost);
315 if (flag && (*flag == chLatin_f || *flag == chDigit_0))
316 curl_easy_setopt(fEasy, CURLOPT_SSL_VERIFYHOST, 0);
317
318 // Process TransportOption elements.
319 bool success;
320 DOMElement* child = XMLHelper::getLastChildElement(e, TransportOption);
321 while (child) {
322 if (child->hasChildNodes() && XMLString::equals(child->getAttributeNS(nullptr,_provider), _OpenSSL)) {
323 auto_ptr_char option(child->getAttributeNS(nullptr,_option));
324 auto_ptr_char value(child->getFirstChild()->getNodeValue());
325 if (option.get() && value.get() && !strcmp(option.get(), "SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION") &&
326 (*value.get()=='1' || *value.get()=='t')) {
327 // If the new option to enable buggy rengotiation is available, set it.
328 // Otherwise, signal false if this is newer than 0.9.8k, because that
329 // means it's 0.9.8l, which blocks renegotiation, and therefore will
330 // not honor this request. Older versions are buggy, so behave as though
331 // the flag was set anyway, so we signal true.
332 #if defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)
333 fOpenSSLOps |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
334 success = true;
335 #elif (OPENSSL_VERSION_NUMBER > 0x009080bfL)
336 success = false;
337 #else
338 success = true;
339 #endif
340 }
341 else {
342 success = false;
343 }
344 if (!success)
345 fLog.error("failed to set OpenSSL transport option (%s)", option.get());
346 }
347 else if (child->hasChildNodes() && XMLString::equals(child->getAttributeNS(nullptr,_provider), _CURL)) {
348 auto_ptr_char option(child->getAttributeNS(nullptr,_option));
349 auto_ptr_char value(child->getFirstChild()->getNodeValue());
350 if (option.get() && *option.get() && value.get() && *value.get()) {
351 // For libcurl, the option is an enum and the value type depends on the option.
352 CURLoption opt = static_cast<CURLoption>(strtol(option.get(), nullptr, 10));
353 if (opt < CURLOPTTYPE_OBJECTPOINT)
354 success = (curl_easy_setopt(fEasy, opt, strtol(value.get(), nullptr, 10)) == CURLE_OK);
355 #ifdef CURLOPTTYPE_OFF_T
356 else if (opt < CURLOPTTYPE_OFF_T) {
357 fSavedOptions.push_back(value.get());
358 success = (curl_easy_setopt(fEasy, opt, fSavedOptions.back().c_str()) == CURLE_OK);
359 }
360 # ifdef HAVE_CURL_OFF_T
361 else if (sizeof(curl_off_t) == sizeof(long))
362 success = (curl_easy_setopt(fEasy, opt, strtol(value.get(), nullptr, 10)) == CURLE_OK);
363 # else
364 else if (sizeof(off_t) == sizeof(long))
365 success = (curl_easy_setopt(fEasy, opt, strtol(value.get(), nullptr, 10)) == CURLE_OK);
366 # endif
367 else
368 success = false;
369 #else
370 else {
371 fSavedOptions.push_back(value.get());
372 success = (curl_easy_setopt(fEasy, opt, fSavedOptions.back().c_str()) == CURLE_OK);
373 }
374 #endif
375 if (!success)
376 fLog.error("failed to set CURL transport option (%s)", option.get());
377 }
378 }
379 child = XMLHelper::getPreviousSiblingElement(child, TransportOption);
380 }
381 }
382
383 // Add easy handle to the multi stack
384 curl_multi_add_handle(fMulti, fEasy);
385
386 fLog.debug("libcurl trying to fetch %s", fURL.c_str());
387
388 // Start reading, to get the content type
389 while(fBufferHeadPtr == fBuffer) {
390 int runningHandles = 0;
391 try {
392 readMore(&runningHandles);
393 }
394 catch (XMLException&) {
395 curl_multi_remove_handle(fMulti, fEasy);
396 curl_easy_cleanup(fEasy);
397 fEasy = nullptr;
398 curl_multi_cleanup(fMulti);
399 fMulti = nullptr;
400 throw;
401 }
402 if(runningHandles == 0) break;
403 }
404
405 // Check for a response code.
406 if (curl_easy_getinfo(fEasy, CURLINFO_RESPONSE_CODE, &fStatusCode) == CURLE_OK) {
407 if (fStatusCode >= 300 ) {
408 // Short-circuit usual processing by storing a special XML document in the buffer.
409 ostringstream specialdoc;
410 specialdoc << '<' << URLInputSource::asciiStatusCodeElementName << " xmlns=\"http://www.opensaml.org/xmltooling\">"
411 << fStatusCode
412 << "</" << URLInputSource::asciiStatusCodeElementName << '>';
413 string specialxml = specialdoc.str();
414 fBufferTailPtr = fBuffer = reinterpret_cast<XMLByte*>(malloc(specialxml.length()));
415 if (!fBuffer) {
416 curl_multi_remove_handle(fMulti, fEasy);
417 curl_easy_cleanup(fEasy);
418 fEasy = nullptr;
419 curl_multi_cleanup(fMulti);
420 fMulti = nullptr;
421 throw bad_alloc();
422 }
423 memcpy(fBuffer, specialxml.c_str(), specialxml.length());
424 fBufferHeadPtr = fBuffer + specialxml.length();
425 }
426 }
427 else {
428 fStatusCode = 200; // reset to 200 to ensure no special processing occurs
429 }
430
431 // Find the content type
432 char* contentType8 = nullptr;
433 if(curl_easy_getinfo(fEasy, CURLINFO_CONTENT_TYPE, &contentType8) == CURLE_OK && contentType8)
434 fContentType = XMLString::transcode(contentType8);
435 }
436
437
staticWriteCallback(char * buffer,size_t size,size_t nitems,void * outstream)438 size_t CurlURLInputStream::staticWriteCallback(char* buffer, size_t size, size_t nitems, void* outstream)
439 {
440 return ((CurlURLInputStream*)outstream)->writeCallback(buffer, size, nitems);
441 }
442
writeCallback(char * buffer,size_t size,size_t nitems)443 size_t CurlURLInputStream::writeCallback(char* buffer, size_t size, size_t nitems)
444 {
445 size_t cnt = size * nitems;
446 size_t totalConsumed = 0;
447
448 // Consume as many bytes as possible immediately into the buffer
449 size_t consume = (cnt > fBytesToRead) ? fBytesToRead : cnt;
450 memcpy(fWritePtr, buffer, consume);
451 fWritePtr += consume;
452 fBytesRead += consume;
453 fTotalBytesRead += consume;
454 fBytesToRead -= consume;
455
456 fLog.debug("write callback consuming %u bytes", consume);
457
458 // If bytes remain, rebuffer as many as possible into our holding buffer
459 buffer += consume;
460 totalConsumed += consume;
461 cnt -= consume;
462 if (cnt > 0)
463 {
464 size_t bufAvail = fBufferSize - (fBufferHeadPtr - fBuffer);
465 if (bufAvail < cnt) {
466 // Enlarge the buffer. TODO: limit max size
467 XMLByte* newbuf = reinterpret_cast<XMLByte*>(realloc(fBuffer, fBufferSize + (cnt - bufAvail)));
468 if (newbuf) {
469 fBufferSize = fBufferSize + (cnt - bufAvail);
470 fLog.debug("enlarged buffer to %u bytes", fBufferSize);
471 fBufferHeadPtr = newbuf + (fBufferHeadPtr - fBuffer);
472 fBuffer = fBufferTailPtr = newbuf;
473 }
474 }
475 memcpy(fBufferHeadPtr, buffer, cnt);
476 fBufferHeadPtr += cnt;
477 buffer += cnt;
478 totalConsumed += cnt;
479 fLog.debug("write callback rebuffering %u bytes", cnt);
480 }
481
482 // Return the total amount we've consumed. If we don't consume all the bytes
483 // then an error will be generated. Since our buffer size is equal to the
484 // maximum size that curl will write, this should never happen unless there
485 // is a logic error somewhere here.
486 return totalConsumed;
487 }
488
readMore(int * runningHandles)489 bool CurlURLInputStream::readMore(int* runningHandles)
490 {
491 // Ask the curl to do some work
492 CURLMcode curlResult = curl_multi_perform(fMulti, runningHandles);
493
494 // Process messages from curl
495 int msgsInQueue = 0;
496 for (CURLMsg* msg = nullptr; (msg = curl_multi_info_read(fMulti, &msgsInQueue)) != nullptr; )
497 {
498 fLog.debug("msg %d, %d from curl", msg->msg, msg->data.result);
499
500 if (msg->msg != CURLMSG_DONE)
501 return true;
502
503 switch (msg->data.result)
504 {
505 case CURLE_OK:
506 // We completed successfully. runningHandles should have dropped to zero, so we'll bail out below...
507 break;
508
509 case CURLE_UNSUPPORTED_PROTOCOL:
510 ThrowXML(MalformedURLException, XMLExcepts::URL_UnsupportedProto);
511 break;
512
513 case CURLE_COULDNT_RESOLVE_HOST:
514 case CURLE_COULDNT_RESOLVE_PROXY:
515 ThrowXML1(NetAccessorException, XMLExcepts::NetAcc_TargetResolution, fURL.c_str());
516 break;
517
518 case CURLE_COULDNT_CONNECT:
519 ThrowXML1(NetAccessorException, XMLExcepts::NetAcc_ConnSocket, fURL.c_str());
520 break;
521
522 case CURLE_OPERATION_TIMEDOUT:
523 ThrowXML1(NetAccessorException, XMLExcepts::NetAcc_ConnSocket, fURL.c_str());
524 break;
525
526 case CURLE_RECV_ERROR:
527 ThrowXML1(NetAccessorException, XMLExcepts::NetAcc_ReadSocket, fURL.c_str());
528 break;
529
530 default:
531 fLog.error("error while fetching %s: (%d) %s", fURL.c_str(), msg->data.result, fError);
532 if (msg->data.result == CURLE_SSL_CIPHER) {
533 fLog.error("on Red Hat 6+, make sure libcurl used is built with OpenSSL");
534 }
535 ThrowXML1(NetAccessorException, XMLExcepts::NetAcc_InternalError, fURL.c_str());
536 break;
537 }
538 }
539
540 // If nothing is running any longer, bail out
541 if(*runningHandles == 0)
542 return false;
543
544 // If there is no further data to read, and we haven't
545 // read any yet on this invocation, call select to wait for data
546 if (curlResult != CURLM_CALL_MULTI_PERFORM && fBytesRead == 0)
547 {
548 fd_set readSet;
549 fd_set writeSet;
550 fd_set exceptSet;
551 int fdcnt=0;
552
553 FD_ZERO(&readSet);
554 FD_ZERO(&writeSet);
555 FD_ZERO(&exceptSet);
556
557 // Ask curl for the file descriptors to wait on
558 curl_multi_fdset(fMulti, &readSet, &writeSet, &exceptSet, &fdcnt);
559
560 // Wait on the file descriptors
561 timeval tv;
562
563 long multi_timeout = 0;
564 curl_multi_timeout(fMulti, &multi_timeout);
565 if (multi_timeout < 0)
566 multi_timeout = 1000;
567
568 tv.tv_sec = multi_timeout / 1000;
569 tv.tv_usec = (multi_timeout % 1000) * 1000;
570
571 select(fdcnt+1, &readSet, &writeSet, &exceptSet, &tv);
572 }
573
574 return curlResult == CURLM_CALL_MULTI_PERFORM;
575 }
576
readBytes(XMLByte * const toFill,const XMLSize_t maxToRead)577 XMLSize_t CurlURLInputStream::readBytes(XMLByte* const toFill, const XMLSize_t maxToRead)
578 {
579 fBytesRead = 0;
580 fBytesToRead = maxToRead;
581 fWritePtr = toFill;
582
583 for (bool tryAgain = true; fBytesToRead > 0 && (tryAgain || fBytesRead == 0); )
584 {
585 // First, any buffered data we have available
586 size_t bufCnt = fBufferHeadPtr - fBufferTailPtr;
587 bufCnt = (bufCnt > fBytesToRead) ? fBytesToRead : bufCnt;
588 if (bufCnt > 0)
589 {
590 memcpy(fWritePtr, fBufferTailPtr, bufCnt);
591 fWritePtr += bufCnt;
592 fBytesRead += bufCnt;
593 fTotalBytesRead += bufCnt;
594 fBytesToRead -= bufCnt;
595
596 fBufferTailPtr += bufCnt;
597 if (fBufferTailPtr == fBufferHeadPtr)
598 fBufferHeadPtr = fBufferTailPtr = fBuffer;
599
600 fLog.debug("consuming %d buffered bytes", bufCnt);
601
602 tryAgain = true;
603 continue;
604 }
605
606 // Check for a non-2xx status that means to ignore the curl response.
607 if (fStatusCode >= 300)
608 break;
609
610 // Ask the curl to do some work
611 int runningHandles = 0;
612 tryAgain = readMore(&runningHandles);
613
614 // If nothing is running any longer, bail out
615 if (runningHandles == 0)
616 break;
617 }
618
619 return fBytesRead;
620 }
621