Change from _ANDROID to ANDROID
[gnash.git] / libbase / curl_adapter.cpp
blobccb61ed1d7d1ab75cc48cb414214425a12171c43
1 // NetworkAdapter.cpp: Interface to libcurl to read HTTP streams, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
25 #ifdef ANDROID
26 #include <sys/select.h>
27 #endif
29 #include "NetworkAdapter.h"
30 #include "utility.h" // UNUSED macro
31 #include "log.h"
32 #include "IOChannel.h"
33 #include "WallClockTimer.h"
34 #include "GnashSleep.h"
36 #include <iostream> // std::cerr
37 #include <boost/thread/mutex.hpp>
38 #include <boost/version.hpp>
39 #include <boost/assign/list_of.hpp>
41 #ifndef USE_CURL
43 namespace gnash {
45 // Stub for warning about access when no libcurl is defined.
47 std::auto_ptr<IOChannel>
48 NetworkAdapter::makeStream(const std::string& /*url*/,
49 const std::string& /*cachefile*/)
51 log_error(_("libcurl is not available, but "
52 "Gnash has attempted to use the curl adapter"));
53 return std::auto_ptr<IOChannel>();
56 std::auto_ptr<IOChannel>
57 NetworkAdapter::makeStream(const std::string& url,
58 const std::string& /*postdata*/,
59 const std::string& cachefile)
61 return makeStream(url, cachefile);
64 std::auto_ptr<IOChannel>
65 NetworkAdapter::makeStream(const std::string& url,
66 const std::string& /*postdata*/,
67 const RequestHeaders& /*headers*/,
68 const std::string& cachefile)
70 return makeStream(url, cachefile);
73 } // namespace gnash
75 #else // def USE_CURL
77 #include <curl/curl.h>
78 #include "utility.h"
79 #include "GnashException.h"
80 #include "rc.h"
81 #include "GnashSystemFDHeaders.h"
83 #include <map>
84 #include <string>
85 #include <sstream>
86 #include <cerrno>
87 #include <cstdio> // cached data uses a *FILE
88 #include <cstdlib> // std::getenv
90 //#define GNASH_CURL_VERBOSE 1
92 // define this if you want seeks back to be reported
93 //#define GNASH_CURL_WARN_SEEKSBACK 1
96 namespace gnash {
98 namespace {
100 /***********************************************************************
102 * CurlSession definition and implementation
104 **********************************************************************/
106 /// A libcurl session consists in a shared handle
107 /// sharing DNS cache and COOKIES.
109 class CurlSession {
111 public:
113 /// Get CurlSession singleton
114 static CurlSession& get();
116 /// Get the shared handle
117 CURLSH* getSharedHandle() { return _shandle; }
119 private:
121 /// Initialize a libcurl session
123 /// A libcurl session consists in a shared handle
124 /// sharing DNS cache and COOKIES.
126 /// Also, global libcurl initialization function will
127 /// be invoked, so MAKE SURE NOT TO CONSTRUCT multiple
128 /// CurlSession instances in a multithreaded environment!
130 /// AGAIN: this is NOT thread-safe !
132 CurlSession();
134 /// Cleanup curl session stuff (including global lib init)
135 ~CurlSession();
138 // the libcurl share handle, for sharing cookies
139 CURLSH* _shandle;
141 // mutex protecting share state
142 boost::mutex _shareMutex;
143 boost::mutex::scoped_lock _shareMutexLock;
145 // mutex protecting shared cookies
146 boost::mutex _cookieMutex;
147 boost::mutex::scoped_lock _cookieMutexLock;
149 // mutex protecting shared dns cache
150 boost::mutex _dnscacheMutex;
151 boost::mutex::scoped_lock _dnscacheMutexLock;
153 /// Import cookies, if requested
155 /// This method will lookup GNASH_COOKIES_IN
156 /// in the environment, and if existing, will
157 /// parse the file sending each line to a fake
158 /// easy handle created ad-hoc
160 void importCookies();
162 /// Export cookies, if requested
164 /// This method will lookup GNASH_COOKIES_OUT
165 /// in the environment, and if existing, will
166 /// create the file writing any cookie currently
167 /// in the jar
169 void exportCookies();
171 /// Shared handle data locking function
172 void lockSharedHandle(CURL* handle, curl_lock_data data,
173 curl_lock_access access);
175 /// Shared handle data unlocking function
176 void unlockSharedHandle(CURL* handle, curl_lock_data data);
178 /// Shared handle locking function
179 static void lockSharedHandleWrapper(CURL* handle, curl_lock_data data,
180 curl_lock_access access, void* userptr)
182 CurlSession* ci = static_cast<CurlSession*>(userptr);
183 ci->lockSharedHandle(handle, data, access);
186 /// Shared handle unlocking function
187 static void unlockSharedHandleWrapper(CURL* handle, curl_lock_data data,
188 void* userptr)
190 // data defines what data libcurl wants to unlock, and you must
191 // make sure that only one lock is given at any time for each kind
192 // of data.
193 // userptr is the pointer you set with CURLSHOPT_USERDATA.
194 CurlSession* ci = static_cast<CurlSession*>(userptr);
195 ci->unlockSharedHandle(handle, data);
200 CurlSession&
201 CurlSession::get()
203 static CurlSession cs;
204 return cs;
207 CurlSession::~CurlSession()
209 log_debug("~CurlSession");
210 exportCookies();
212 CURLSHcode code;
213 int retries=0;
214 while ( (code=curl_share_cleanup(_shandle)) != CURLSHE_OK )
216 if ( ++retries > 10 )
218 log_error("Failed cleaning up share handle: %s. Giving up after "
219 "%d retries.", curl_share_strerror(code), retries);
220 break;
222 log_error("Failed cleaning up share handle: %s. Will try again in "
223 "a second.", curl_share_strerror(code));
224 gnashSleep(1000000);
226 _shandle = 0;
227 curl_global_cleanup();
230 #if BOOST_VERSION < 103500
231 # define GNASH_DEFER_LOCK false
232 #else
233 # define GNASH_DEFER_LOCK boost::defer_lock
234 #endif
236 CurlSession::CurlSession()
238 _shandle(0),
239 _shareMutex(),
240 _shareMutexLock(_shareMutex, GNASH_DEFER_LOCK), // start unlocked
241 _cookieMutex(),
242 _cookieMutexLock(_cookieMutex, GNASH_DEFER_LOCK), // start unlocked
243 _dnscacheMutex(),
244 _dnscacheMutexLock(_dnscacheMutex, GNASH_DEFER_LOCK) // start unlocked
246 // TODO: handle an error here (throw an exception)
247 curl_global_init(CURL_GLOBAL_ALL);
249 _shandle = curl_share_init();
250 if (! _shandle) {
251 throw GnashException("Failure initializing curl share handle");
254 CURLSHcode ccode;
256 // Register share locking function
257 ccode = curl_share_setopt(_shandle, CURLSHOPT_LOCKFUNC,
258 lockSharedHandleWrapper);
259 if ( ccode != CURLSHE_OK ) {
260 throw GnashException(curl_share_strerror(ccode));
263 // Register share unlocking function
264 ccode = curl_share_setopt(_shandle, CURLSHOPT_UNLOCKFUNC,
265 unlockSharedHandleWrapper);
266 if ( ccode != CURLSHE_OK ) {
267 throw GnashException(curl_share_strerror(ccode));
270 // Activate sharing of cookies and DNS cache
271 ccode = curl_share_setopt(_shandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
272 if ( ccode != CURLSHE_OK ) {
273 throw GnashException(curl_share_strerror(ccode));
276 // Activate sharing of DNS cache (since we're there..)
277 ccode = curl_share_setopt(_shandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
278 if ( ccode != CURLSHE_OK ) {
279 throw GnashException(curl_share_strerror(ccode));
282 // Pass ourselves as the userdata
283 ccode = curl_share_setopt(_shandle, CURLSHOPT_USERDATA, this);
284 if ( ccode != CURLSHE_OK ) {
285 throw GnashException(curl_share_strerror(ccode));
288 importCookies();
291 void
292 CurlSession::lockSharedHandle(CURL* handle, curl_lock_data data,
293 curl_lock_access access)
295 UNUSED(handle); // possibly being the 'easy' handle triggering the request ?
297 // data defines what data libcurl wants to lock, and you must make
298 // sure that only one lock is given at any time for each kind of data.
299 // access defines what access type libcurl wants, shared or single.
301 // TODO: see if we may make use of the 'access' parameter
302 UNUSED(access);
304 switch (data)
306 case CURL_LOCK_DATA_DNS:
307 //log_debug("Locking DNS cache mutex");
308 _dnscacheMutexLock.lock();
309 //log_debug("DNS cache mutex locked");
310 break;
311 case CURL_LOCK_DATA_COOKIE:
312 //log_debug("Locking cookies mutex");
313 _cookieMutexLock.lock();
314 //log_debug("Cookies mutex locked");
315 break;
316 case CURL_LOCK_DATA_SHARE:
317 //log_debug("Locking share mutex");
318 _shareMutexLock.lock();
319 //log_debug("Share mutex locked");
320 break;
321 case CURL_LOCK_DATA_SSL_SESSION:
322 log_error("lockSharedHandle: SSL session locking "
323 "unsupported");
324 break;
325 case CURL_LOCK_DATA_CONNECT:
326 log_error("lockSharedHandle: connect locking unsupported");
327 break;
328 case CURL_LOCK_DATA_LAST:
329 log_error("lockSharedHandle: last locking unsupported ?!");
330 break;
331 default:
332 log_error("lockSharedHandle: unknown shared data %d", data);
333 break;
337 void
338 CurlSession::unlockSharedHandle(CURL* handle, curl_lock_data data)
340 UNUSED(handle); // possibly being the 'easy' handle triggering the request ?
342 // data defines what data libcurl wants to lock, and you must make
343 // sure that only one lock is given at any time for each kind of data.
344 switch (data)
346 case CURL_LOCK_DATA_DNS:
347 //log_debug("Unlocking DNS cache mutex");
348 _dnscacheMutexLock.unlock();
349 break;
350 case CURL_LOCK_DATA_COOKIE:
351 //log_debug("Unlocking cookies mutex");
352 _cookieMutexLock.unlock();
353 break;
354 case CURL_LOCK_DATA_SHARE:
355 //log_debug("Unlocking share mutex");
356 _shareMutexLock.unlock();
357 break;
358 case CURL_LOCK_DATA_SSL_SESSION:
359 log_error("unlockSharedHandle: SSL session locking "
360 "unsupported");
361 break;
362 case CURL_LOCK_DATA_CONNECT:
363 log_error("unlockSharedHandle: connect locking unsupported");
364 break;
365 case CURL_LOCK_DATA_LAST:
366 log_error("unlockSharedHandle: last locking unsupported ?!");
367 break;
368 default:
369 std::cerr << "unlockSharedHandle: unknown shared data " <<
370 data << std::endl;
371 break;
376 /***********************************************************************
378 * CurlStreamFile definition
380 **********************************************************************/
382 /// libcurl based IOChannel, for network uri accesses
383 class CurlStreamFile : public IOChannel
386 public:
388 typedef std::map<std::string, std::string> PostData;
390 /// Open a stream from the specified URL
391 CurlStreamFile(const std::string& url, const std::string& cachefile);
393 /// Open a stream from the specified URL posting the specified variables
395 /// @param url
396 /// The url to post to.
398 /// @param vars
399 /// The url-encoded post data.
401 CurlStreamFile(const std::string& url, const std::string& vars,
402 const std::string& cachefile);
404 CurlStreamFile(const std::string& url, const std::string& vars,
405 const NetworkAdapter::RequestHeaders& headers,
406 const std::string& cachefile);
408 ~CurlStreamFile();
410 // See dox in IOChannel.h
411 virtual std::streamsize read(void *dst, std::streamsize bytes);
413 // See dox in IOChannel.h
414 virtual std::streamsize readNonBlocking(void *dst, std::streamsize bytes);
416 // See dox in IOChannel.h
417 virtual bool eof() const;
419 // See dox in IOChannel.h
420 virtual bool bad() const {
421 return _error;
424 /// Report global position within the file
425 virtual std::streampos tell() const;
427 /// Put read pointer at given position
428 virtual bool seek(std::streampos pos);
430 /// Put read pointer at eof
431 virtual void go_to_end();
433 /// Returns the size of the stream
435 /// If size of the stream is unknown, 0 is returned.
436 /// In that case you might try calling this function
437 /// again after filling the cache a bit...
439 /// Another approach might be filling the cache ourselves
440 /// aiming at obtaining a useful value.
442 virtual size_t size() const;
444 private:
446 void init(const std::string& url, const std::string& cachefile);
448 // Use this file to cache data
449 FILE* _cache;
451 // _cache file descriptor
452 int _cachefd;
454 // we keep a copy here to be sure the char*
455 // is alive for the whole CurlStreamFile lifetime
456 // TODO: don't really do this :)
457 std::string _url;
459 // the libcurl easy handle
460 CURL *_handle;
462 // the libcurl multi handle
463 CURLM *_mhandle;
465 // transfer in progress
466 int _running;
468 // stream error
469 // false on no error.
470 // Example of errors would be:
471 // 404 - file not found
472 // timeout occurred
473 bool _error;
475 // Post data. Empty if no POST has been requested
476 std::string _postdata;
478 // Current size of cached data
479 size_t _cached;
481 /// Total stream size.
483 /// This will be 0 until known
485 mutable size_t _size;
487 // Attempt at filling the cache up to the given size.
488 // Will call libcurl routines to fetch data.
489 void fillCache(std::streamsize size);
491 // Process pending curl messages (handles 404)
492 void processMessages();
494 // Filling the cache as much as possible w/out blocking.
495 // Will call libcurl routines to fetch data.
496 void fillCacheNonBlocking();
498 // Append sz bytes to the cache
499 std::streamsize cache(void *from, std::streamsize sz);
501 // Callback for libcurl, will be called
502 // by fillCache() and will call cache()
503 static size_t recv(void *buf, size_t size, size_t nmemb, void *userp);
505 // List of custom headers for this stream.
506 struct curl_slist *_customHeaders;
510 /***********************************************************************
512 * Statics and CurlStreamFile implementation
514 **********************************************************************/
516 /*static private*/
517 size_t
518 CurlStreamFile::recv(void *buf, size_t size, size_t nmemb, void *userp)
520 #ifdef GNASH_CURL_VERBOSE
521 log_debug("curl write callback called for (%d) bytes",
522 size * nmemb);
523 #endif
524 CurlStreamFile* stream = static_cast<CurlStreamFile*>(userp);
525 return stream->cache(buf, size * nmemb);
529 /*private*/
530 std::streamsize
531 CurlStreamFile::cache(void *from, std::streamsize size)
533 // take note of current position
534 long curr_pos = std::ftell(_cache);
536 // seek to the end
537 std::fseek(_cache, 0, SEEK_END);
539 std::streamsize wrote = std::fwrite(from, 1, size, _cache);
540 if ( wrote < 1 )
543 boost::format fmt = boost::format("writing to cache file: requested "
544 "%d, wrote %d (%s)") %
545 size % wrote % std::strerror(errno);
546 throw GnashException(fmt.str());
549 // Set the size of cached data
550 _cached = std::ftell(_cache);
552 // reset position for next read
553 std::fseek(_cache, curr_pos, SEEK_SET);
555 return wrote;
558 /*private*/
559 void
560 CurlStreamFile::fillCacheNonBlocking()
562 if ( ! _running )
564 #if GNASH_CURL_VERBOSE
565 log_debug("Not running: fillCacheNonBlocking returning");
566 #endif
567 return;
570 CURLMcode mcode;
574 mcode = curl_multi_perform(_mhandle, &_running);
575 } while (mcode == CURLM_CALL_MULTI_PERFORM); // && _running ?
577 if (mcode != CURLM_OK)
579 throw GnashException(curl_multi_strerror(mcode));
582 // handle 404
583 processMessages();
587 /*private*/
588 void
589 CurlStreamFile::fillCache(std::streamsize size)
592 #if GNASH_CURL_VERBOSE
593 log_debug("fillCache(%d), called, currently cached: %d", size, _cached);
594 #endif
596 assert(size >= 0);
598 if ( ! _running || _cached >= static_cast<size_t>(size)) {
599 #if GNASH_CURL_VERBOSE
600 if (!_running) log_debug("Not running: returning");
601 else log_debug("Already enough bytes cached: returning");
602 #endif
603 return;
606 fd_set readfd, writefd, exceptfd;
607 int maxfd;
608 CURLMcode mcode;
609 timeval tv;
611 // Hard-coded slect timeout.
612 // This number is kept low to give more thread switch
613 // opportunities while waiting for a load.
614 const long maxSleepUsec = 10000; // 1/100 of a second
616 const unsigned int userTimeout = static_cast<unsigned int>(
617 RcInitFile::getDefaultInstance().getStreamsTimeout()*1000);
619 #ifdef GNASH_CURL_VERBOSE
620 log_debug("User timeout is %u milliseconds", userTimeout);
621 #endif
623 WallClockTimer lastProgress; // timer since last progress
624 while (_running)
627 fillCacheNonBlocking(); // NOTE: will handle 404
629 // Do this here to avoid calling select()
630 // when we have enough bytes anyway, or
631 // we reached EOF
632 if (_cached >= static_cast<size_t>(size) || !_running) break;
634 #if GNASH_CURL_VERBOSE
635 //log_debug("cached: %d, size: %d", _cached, size);
636 #endif
638 mcode = curl_multi_fdset(_mhandle, &readfd, &writefd,
639 &exceptfd, &maxfd);
641 if (mcode != CURLM_OK) {
642 // This is a serious error, not just a failure to add any
643 // fds.
644 throw GnashException(curl_multi_strerror(mcode));
647 #ifdef GNASH_CURL_VERBOSE
648 log_debug("Max fd: %d", maxfd);
649 #endif
651 // A value of -1 means no file descriptors were added.
652 if (maxfd < 0) {
653 #if GNASH_CURL_VERBOSE
654 log_debug("No filedescriptors; breaking");
655 #endif
656 break;
659 FD_ZERO(&readfd);
660 FD_ZERO(&writefd);
661 FD_ZERO(&exceptfd);
663 tv.tv_sec = 0;
664 tv.tv_usec = maxSleepUsec;
666 #ifdef GNASH_CURL_VERBOSE
667 log_debug("select() with %d milliseconds timeout", maxSleepUsec*1000);
668 #endif
670 // Wait for data on the filedescriptors until a timeout set
671 // in gnashrc.
672 int ret = select(maxfd + 1, &readfd, &writefd, &exceptfd, &tv);
674 // select() will always fail on OS/2 and AmigaOS4 as we can't select
675 // on file descriptors, only on sockets
676 #if !defined(__OS2__) && !defined(__amigaos4__)
677 if ( ret == -1 )
679 if ( errno == EINTR )
681 // we got interupted by a signal
682 // let's consider this as a timeout
683 #ifdef GNASH_CURL_VERBOSE
684 log_debug("select() was interrupted by a signal");
685 #endif
686 ret = 0;
688 else
690 // something unexpected happened
691 boost::format fmt = boost::format(
692 "error polling data from connection to %s: %s ")
693 % _url % strerror(errno);
694 throw GnashException(fmt.str());
697 #endif
698 if ( ! ret )
700 // timeout
701 #ifdef GNASH_CURL_VERBOSE
702 log_debug("select() timed out, elapsed is %u",
703 lastProgress.elapsed());
704 #endif
705 if (userTimeout && lastProgress.elapsed() > userTimeout)
707 log_error(_("Timeout (%u milliseconds) while loading "
708 "from url %s"), userTimeout, _url);
709 // TODO: should we set _error here ?
710 return;
713 else
715 #ifdef GNASH_CURL_VERBOSE
716 log_debug("FD activity, resetting progress timer");
717 #endif
718 lastProgress.restart();
722 // TODO: check if we timedout or got some data
723 // if we did timeout, check the clock to see
724 // if we expired the user Timeout, otherwise
725 // reset the timer...
729 // Processing messages is already done by fillCacheNonBlocking,
730 // so we might likely avoid it here.
731 processMessages();
735 /*private*/
736 void
737 CurlStreamFile::processMessages()
739 CURLMsg *curl_msg;
741 // The number of messages left in the queue (not used by us).
742 int msgs;
743 while ((curl_msg = curl_multi_info_read(_mhandle, &msgs)))
746 // Only for completed transactions
747 if (curl_msg->msg == CURLMSG_DONE) {
749 // HTTP transaction succeeded
750 if (curl_msg->data.result == CURLE_OK) {
752 long code;
754 // Check HTTP response
755 curl_easy_getinfo(curl_msg->easy_handle,
756 CURLINFO_RESPONSE_CODE, &code);
758 if ( code >= 400 ) {
759 log_error ("HTTP response %ld from url %s",
760 code, _url);
761 _error = true;
762 _running = false;
764 else {
765 log_debug ("HTTP response %ld from url %s",
766 code, _url);
770 else {
772 // Transaction failed, pass on curl error.
773 log_error("CURL: %s", curl_easy_strerror(
774 curl_msg->data.result));
775 _error = true;
784 /*private*/
785 void
786 CurlStreamFile::init(const std::string& url, const std::string& cachefile)
788 _customHeaders = 0;
790 _url = url;
791 _running = 1;
792 _error = false;
794 _cached = 0;
795 _size = 0;
797 _handle = curl_easy_init();
798 _mhandle = curl_multi_init();
800 const RcInitFile& rcfile = RcInitFile::getDefaultInstance();
802 if (!cachefile.empty()) {
803 _cache = std::fopen(cachefile.c_str(), "w+b");
804 if (!_cache) {
806 log_error("Could not open specified path as cache file. Using "
807 "a temporary file instead");
808 _cache = std::tmpfile();
811 else _cache = std::tmpfile();
813 if ( ! _cache ) {
814 throw GnashException("Could not create temporary cache file");
816 _cachefd = fileno(_cache);
818 CURLcode ccode;
820 // Override cURL's default verification of SSL certificates
821 // This is insecure, so log security warning.
822 // Equivalent to curl -k or curl --insecure.
823 if (rcfile.insecureSSL())
825 log_security(_("Allowing connections to SSL sites with invalid "
826 "certificates"));
828 ccode = curl_easy_setopt(_handle, CURLOPT_SSL_VERIFYPEER, 0);
829 if ( ccode != CURLE_OK ) {
830 throw GnashException(curl_easy_strerror(ccode));
833 ccode = curl_easy_setopt(_handle, CURLOPT_SSL_VERIFYHOST, 0);
834 if ( ccode != CURLE_OK ) {
835 throw GnashException(curl_easy_strerror(ccode));
839 // Get shared data
840 ccode = curl_easy_setopt(_handle, CURLOPT_SHARE,
841 CurlSession::get().getSharedHandle());
843 if ( ccode != CURLE_OK ) {
844 throw GnashException(curl_easy_strerror(ccode));
847 // Set expiration time for DNS cache entries, in seconds
848 // 0 disables caching
849 // -1 makes cache entries never expire
850 // default is 60
852 // NOTE: this snippet is here just as a placeholder for whoever
853 // will feel a need to make this parametrizable
855 ccode = curl_easy_setopt(_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60);
856 if ( ccode != CURLE_OK ) {
857 throw GnashException(curl_easy_strerror(ccode));
860 ccode = curl_easy_setopt(_handle, CURLOPT_USERAGENT, "Gnash-" VERSION);
861 if ( ccode != CURLE_OK ) {
862 throw GnashException(curl_easy_strerror(ccode));
865 #ifdef GNASH_CURL_VERBOSE
866 // for verbose operations
867 ccode = curl_easy_setopt(_handle, CURLOPT_VERBOSE, 1);
868 if ( ccode != CURLE_OK ) {
869 throw GnashException(curl_easy_strerror(ccode));
871 #endif
873 /* from libcurl-tutorial(3)
874 When using multiple threads you should set the CURLOPT_NOSIGNAL option
875 to TRUE for all handles. Everything will work fine except that timeouts
876 are not honored during the DNS lookup - which you can work around by
878 ccode = curl_easy_setopt(_handle, CURLOPT_NOSIGNAL, true);
879 if ( ccode != CURLE_OK ) {
880 throw GnashException(curl_easy_strerror(ccode));
883 // set url
884 ccode = curl_easy_setopt(_handle, CURLOPT_URL, _url.c_str());
885 if ( ccode != CURLE_OK ) {
886 throw GnashException(curl_easy_strerror(ccode));
889 //curl_easy_setopt(_handle, CURLOPT_NOPROGRESS, false);
892 // set write data and function
893 ccode = curl_easy_setopt(_handle, CURLOPT_WRITEDATA, this);
894 if ( ccode != CURLE_OK ) {
895 throw GnashException(curl_easy_strerror(ccode));
898 ccode = curl_easy_setopt(_handle, CURLOPT_WRITEFUNCTION,
899 CurlStreamFile::recv);
900 if ( ccode != CURLE_OK ) {
901 throw GnashException(curl_easy_strerror(ccode));
904 ccode = curl_easy_setopt(_handle, CURLOPT_FOLLOWLOCATION, 1);
905 if ( ccode != CURLE_OK ) {
906 throw GnashException(curl_easy_strerror(ccode));
909 //fillCache(32); // pre-cache 32 bytes
910 //curl_multi_perform(_mhandle, &_running);
913 /*public*/
914 CurlStreamFile::CurlStreamFile(const std::string& url,
915 const std::string& cachefile)
917 log_debug("CurlStreamFile %p created", this);
918 init(url, cachefile);
920 // CURLMcode ret =
921 CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
922 if ( mcode != CURLM_OK ) {
923 throw GnashException(curl_multi_strerror(mcode));
927 /*public*/
928 CurlStreamFile::CurlStreamFile(const std::string& url, const std::string& vars,
929 const std::string& cachefile)
931 log_debug("CurlStreamFile %p created", this);
932 init(url, cachefile);
934 _postdata = vars;
936 CURLcode ccode;
938 ccode = curl_easy_setopt(_handle, CURLOPT_POST, 1);
939 if ( ccode != CURLE_OK ) {
940 throw GnashException(curl_easy_strerror(ccode));
943 // libcurl needs to access the POSTFIELDS during 'perform' operations,
944 // so we must use a string whose lifetime is ensured to be longer then
945 // the multihandle itself.
946 // The _postdata member should meet this requirement
947 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDS, _postdata.c_str());
948 if ( ccode != CURLE_OK ) {
949 throw GnashException(curl_easy_strerror(ccode));
952 // This is to support binary strings as postdata
953 // NOTE: in version 7.11.1 CURLOPT_POSTFIELDSIZE_LARGE was added
954 // this one takes a long, that one takes a curl_off_t
956 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDSIZE, _postdata.size());
957 if ( ccode != CURLE_OK ) {
958 throw GnashException(curl_easy_strerror(ccode));
961 // Disable sending an Expect: header, as some older HTTP/1.1
962 // don't implement them, and some (namely lighttpd/1.4.19,
963 // running on openstreetmap.org at time of writing) return
964 // a '417 Expectance Failure' response on getting that.
965 assert ( ! _customHeaders );
966 _customHeaders = curl_slist_append(_customHeaders, "Expect:");
967 ccode = curl_easy_setopt(_handle, CURLOPT_HTTPHEADER, _customHeaders);
968 if ( ccode != CURLE_OK ) {
969 throw GnashException(curl_easy_strerror(ccode));
972 CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
973 if ( mcode != CURLM_OK ) {
974 throw GnashException(curl_multi_strerror(mcode));
979 /*public*/
980 CurlStreamFile::CurlStreamFile(const std::string& url, const std::string& vars,
981 const NetworkAdapter::RequestHeaders& headers,
982 const std::string& cachefile)
984 log_debug("CurlStreamFile %p created", this);
985 init(url, cachefile);
987 _postdata = vars;
989 // Disable sending an Expect: header, as some older HTTP/1.1
990 // don't implement them, and some (namely lighttpd/1.4.19,
991 // running on openstreetmap.org at time of writing) return
992 // a '417 Expectance Failure' response on getting that.
994 // Do this before adding user-requested headers so user
995 // specified ones take precedence
997 assert ( ! _customHeaders );
998 _customHeaders = curl_slist_append(_customHeaders, "Expect:");
1000 for (NetworkAdapter::RequestHeaders::const_iterator i = headers.begin(),
1001 e = headers.end(); i != e; ++i)
1003 // Check here to see whether header name is allowed.
1004 if (!NetworkAdapter::isHeaderAllowed(i->first)) continue;
1005 std::ostringstream os;
1006 os << i->first << ": " << i->second;
1007 _customHeaders = curl_slist_append(_customHeaders, os.str().c_str());
1010 CURLcode ccode;
1012 ccode = curl_easy_setopt(_handle, CURLOPT_HTTPHEADER, _customHeaders);
1013 if ( ccode != CURLE_OK ) {
1014 throw GnashException(curl_easy_strerror(ccode));
1017 ccode = curl_easy_setopt(_handle, CURLOPT_POST, 1);
1018 if ( ccode != CURLE_OK ) {
1019 throw GnashException(curl_easy_strerror(ccode));
1022 // libcurl needs to access the POSTFIELDS during 'perform' operations,
1023 // so we must use a string whose lifetime is ensured to be longer then
1024 // the multihandle itself.
1025 // The _postdata member should meet this requirement
1026 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDS, _postdata.c_str());
1027 if ( ccode != CURLE_OK ) {
1028 throw GnashException(curl_easy_strerror(ccode));
1031 // This is to support binary strings as postdata
1032 // NOTE: in version 7.11.1 CURLOPT_POSTFIELDSIZE_LARGE was added
1033 // this one takes a long, that one takes a curl_off_t
1035 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDSIZE, _postdata.size());
1036 if ( ccode != CURLE_OK ) {
1037 throw GnashException(curl_easy_strerror(ccode));
1040 CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
1041 if ( mcode != CURLM_OK ) {
1042 throw GnashException(curl_multi_strerror(mcode));
1048 /*public*/
1049 CurlStreamFile::~CurlStreamFile()
1051 log_debug("CurlStreamFile %p deleted", this);
1052 curl_multi_remove_handle(_mhandle, _handle);
1053 curl_easy_cleanup(_handle);
1054 curl_multi_cleanup(_mhandle);
1055 std::fclose(_cache);
1056 if ( _customHeaders ) curl_slist_free_all(_customHeaders);
1059 /*public*/
1060 std::streamsize
1061 CurlStreamFile::read(void *dst, std::streamsize bytes)
1063 if ( eof() || _error ) return 0;
1065 #ifdef GNASH_CURL_VERBOSE
1066 log_debug ("read(%d) called", bytes);
1067 #endif
1069 fillCache(bytes + tell());
1070 if ( _error ) return 0; // error can be set by fillCache
1072 #ifdef GNASH_CURL_VERBOSE
1073 log_debug("_cache.tell = %d", tell());
1074 #endif
1076 return std::fread(dst, 1, bytes, _cache);
1080 /*public*/
1081 std::streamsize
1082 CurlStreamFile::readNonBlocking(void *dst, std::streamsize bytes)
1084 #ifdef GNASH_CURL_VERBOSE
1085 log_debug ("readNonBlocking(%d) called", bytes);
1086 #endif
1088 if ( eof() || _error ) return 0;
1090 fillCacheNonBlocking();
1091 if ( _error )
1093 // I guess an exception would be thrown in this case ?
1094 log_error("curl adaptor's fillCacheNonBlocking set _error "
1095 "rather then throwing an exception");
1096 return 0;
1099 std::streamsize actuallyRead = std::fread(dst, 1, bytes, _cache);
1100 if ( _running )
1102 // if we're still running drop any eof flag
1103 // on the cache
1104 clearerr(_cache);
1107 return actuallyRead;
1111 /*public*/
1112 bool
1113 CurlStreamFile::eof() const
1115 bool ret = ( ! _running && feof(_cache) );
1117 #ifdef GNASH_CURL_VERBOSE
1118 log_debug("eof() returning %d", ret);
1119 #endif
1120 return ret;
1124 /*public*/
1125 std::streampos
1126 CurlStreamFile::tell() const
1128 std::streampos ret = std::ftell(_cache);
1130 #ifdef GNASH_CURL_VERBOSE
1131 log_debug("tell() returning %ld", ret);
1132 #endif
1134 return ret;
1138 /*public*/
1139 bool
1140 CurlStreamFile::seek(std::streampos pos)
1143 assert(pos >= 0);
1145 #ifdef GNASH_CURL_WARN_SEEKSBACK
1146 if ( pos < tell() ) {
1147 log_debug("Warning: seek backward requested (%ld from %ld)",
1148 pos, tell());
1150 #endif
1152 fillCache(pos);
1153 if (_error) return false; // error can be set by fillCache
1155 if (_cached < static_cast<size_t>(pos))
1157 log_error ("Warning: could not cache anough bytes on seek: %d "
1158 "requested, %d cached", pos, _cached);
1159 return false;
1162 if (std::fseek(_cache, pos, SEEK_SET) == -1) {
1163 log_error("Warning: fseek failed");
1164 return false;
1167 return true;
1171 /*public*/
1172 void
1173 CurlStreamFile::go_to_end()
1175 CURLMcode mcode;
1176 while (_running > 0)
1180 mcode=curl_multi_perform(_mhandle, &_running);
1181 } while ( mcode == CURLM_CALL_MULTI_PERFORM );
1183 if ( mcode != CURLM_OK )
1185 throw IOException(curl_multi_strerror(mcode));
1188 long code;
1189 curl_easy_getinfo(_handle, CURLINFO_RESPONSE_CODE, &code);
1190 if (code == 404) // file not found!
1192 throw IOException("File not found");
1197 if (std::fseek(_cache, 0, SEEK_END) == -1) {
1198 throw IOException("NetworkAdapter: fseek to end failed");
1202 /*public*/
1203 size_t
1204 CurlStreamFile::size() const
1206 if ( ! _size )
1208 double size;
1209 CURLcode ret = curl_easy_getinfo(_handle,
1210 CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);
1211 if (ret == CURLE_OK) {
1212 assert(size <= std::numeric_limits<size_t>::max());
1213 _size = static_cast<size_t>(size);
1217 #ifdef GNASH_CURL_VERBOSE
1218 log_debug("get_stream_size() returning %lu", _size);
1219 #endif
1221 return _size;
1225 void
1226 CurlSession::importCookies()
1228 const char* cookiesIn = std::getenv("GNASH_COOKIES_IN");
1229 if ( ! cookiesIn ) return; // nothing to do
1231 ////////////////////////////////////////////////////////////////
1233 // WARNING: what we're doing here is an ugly hack
1235 // We'll be creating a fake easy handle for the sole purpos
1236 // of importing cookies. Tests conducted against 7.15.5-CVS
1237 // resulted in this working if a CURLOPT_URL is given, even
1238 // if invalid (but non-0!), while wouldn't if NO CURLOPT_URL
1239 // is given, with both cases returning the same CURLcode on
1240 // _perform (URL using bad/illegal format or missing URL)
1242 // TODO: instead, we should be reading the input file
1243 // ourselves and use CURLOPT_COOKIELIST to send
1244 // each line. Doing so should not require a
1245 // _perform call.
1247 ////////////////////////////////////////////////////////////////
1249 // Create a fake handle for purpose of importing data
1250 CURL* fakeHandle = curl_easy_init(); // errors to handle here ?
1251 CURLcode ccode;
1253 // Configure the fake handle to use the share (shared cookies in particular..)
1254 ccode = curl_easy_setopt(fakeHandle, CURLOPT_SHARE, getSharedHandle());
1255 if ( ccode != CURLE_OK ) {
1256 throw GnashException(curl_easy_strerror(ccode));
1259 // Configure the fake handle to read cookies from the specified file
1260 ccode = curl_easy_setopt(fakeHandle, CURLOPT_COOKIEFILE, cookiesIn);
1261 if ( ccode != CURLE_OK ) {
1262 throw GnashException(curl_easy_strerror(ccode));
1265 // need to pass a non-zero URL string for COOKIEFILE to
1266 // be really parsed
1267 ccode = curl_easy_setopt(fakeHandle, CURLOPT_URL, "");
1268 if ( ccode != CURLE_OK ) {
1269 throw GnashException(curl_easy_strerror(ccode));
1272 // perform, to activate actual cookie file parsing
1274 // NOTE: curl_easy_perform is expected to return an
1275 // "URL using bad/illegal format or missing URL" error,
1276 // there's no way to detect actual cookies import errors
1277 // other then using curl debugging output routines
1279 log_debug("Importing cookies from file '%s'", cookiesIn);
1280 curl_easy_perform(fakeHandle);
1282 curl_easy_cleanup(fakeHandle);
1286 void
1287 CurlSession::exportCookies()
1289 const char* cookiesOut = std::getenv("GNASH_COOKIES_OUT");
1290 if ( ! cookiesOut ) return; // nothing to do
1292 ////////////////////////////////////////////////////////////////
1294 // WARNING: what we're doing here is an ugly hack
1296 // We'll be creating a fake easy handle for the sole purpose
1297 // of exporting cookies. Tests conducted against 7.15.5-CVS
1298 // resulted in this working w/out a CURLOPT_URL and w/out a
1299 // curl_easy_perform.
1301 // NOTE: the "correct" way would be to use CURLOPT_COOKIELIST
1302 // with the "FLUSH" special string as value, but that'd
1303 // be only supported by version 7.17.1
1305 ////////////////////////////////////////////////////////////////
1307 CURL* fakeHandle = curl_easy_init(); // errors to handle here ?
1308 CURLcode ccode;
1310 // Configure the fake handle to use the share (shared cookies in particular..)
1311 ccode = curl_easy_setopt(fakeHandle, CURLOPT_SHARE, getSharedHandle());
1312 if ( ccode != CURLE_OK ) {
1313 throw GnashException(curl_easy_strerror(ccode));
1316 // Configure the fake handle to write cookies to the specified file
1317 ccode = curl_easy_setopt(fakeHandle, CURLOPT_COOKIEJAR , cookiesOut);
1318 if ( ccode != CURLE_OK ) {
1319 throw GnashException(curl_easy_strerror(ccode));
1322 // Cleanup, to trigger actual cookie file flushing
1323 log_debug("Exporting cookies file '%s'", cookiesOut);
1324 curl_easy_cleanup(fakeHandle);
1329 } // anonymous namespace
1331 //-------------------------------------------
1332 // Exported interfaces
1333 //-------------------------------------------
1335 std::auto_ptr<IOChannel>
1336 NetworkAdapter::makeStream(const std::string& url, const std::string& cachefile)
1338 #ifdef GNASH_CURL_VERBOSE
1339 log_debug("making curl stream for %s", url);
1340 #endif
1342 std::auto_ptr<IOChannel> stream;
1344 try {
1345 stream.reset(new CurlStreamFile(url, cachefile));
1347 catch (const std::exception& ex) {
1348 log_error("curl stream: %s", ex.what());
1350 return stream;
1353 std::auto_ptr<IOChannel>
1354 NetworkAdapter::makeStream(const std::string& url, const std::string& postdata,
1355 const std::string& cachefile)
1357 #ifdef GNASH_CURL_VERBOSE
1358 log_debug("making curl stream for %s", url);
1359 #endif
1361 std::auto_ptr<IOChannel> stream;
1363 try {
1364 stream.reset(new CurlStreamFile(url, postdata, cachefile));
1366 catch (const std::exception& ex) {
1367 log_error("curl stream: %s", ex.what());
1369 return stream;
1372 std::auto_ptr<IOChannel>
1373 NetworkAdapter::makeStream(const std::string& url, const std::string& postdata,
1374 const RequestHeaders& headers, const std::string& cachefile)
1377 std::auto_ptr<IOChannel> stream;
1379 try {
1380 stream.reset(new CurlStreamFile(url, postdata, headers, cachefile));
1382 catch (const std::exception& ex) {
1383 log_error("curl stream: %s", ex.what());
1386 return stream;
1390 const NetworkAdapter::ReservedNames&
1391 NetworkAdapter::reservedNames()
1393 static const ReservedNames names = boost::assign::list_of
1394 ("Accept-Ranges")
1395 ("Age")
1396 ("Allow")
1397 ("Allowed")
1398 ("Connection")
1399 ("Content-Length")
1400 ("Content-Location")
1401 ("Content-Range")
1402 ("ETag")
1403 ("GET")
1404 ("Host")
1405 ("HEAD")
1406 ("Last-Modified")
1407 ("Locations")
1408 ("Max-Forwards")
1409 ("POST")
1410 ("Proxy-Authenticate")
1411 ("Proxy-Authorization")
1412 ("Public")
1413 ("Range")
1414 ("Retry-After")
1415 ("Server")
1416 ("TE")
1417 ("Trailer")
1418 ("Transfer-Encoding")
1419 ("Upgrade")
1420 ("URI")
1421 ("Vary")
1422 ("Via")
1423 ("Warning")
1424 ("WWW-Authenticate");
1426 return names;
1429 } // namespace gnash
1431 #endif // def USE_CURL
1434 // Local Variables:
1435 // mode: C++
1436 // indent-tabs-mode: t
1437 // End: