Plug more leaks (in the test, not the core)
[gnash.git] / libbase / curl_adapter.cpp
blobbf5fa9157a319d701d561de0e66d2513c24bae15
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 extern "C" {
78 #include <curl/curl.h>
81 #include "utility.h"
82 #include "GnashException.h"
83 #include "rc.h"
84 #include "GnashSystemFDHeaders.h"
86 #include <map>
87 #include <string>
88 #include <sstream>
89 #include <cerrno>
90 #include <cstdio> // cached data uses a *FILE
91 #include <cstdlib> // std::getenv
93 //#define GNASH_CURL_VERBOSE 1
95 // define this if you want seeks back to be reported
96 //#define GNASH_CURL_WARN_SEEKSBACK 1
99 namespace gnash {
101 namespace {
103 /***********************************************************************
105 * CurlSession definition and implementation
107 **********************************************************************/
109 /// A libcurl session consists in a shared handle
110 /// sharing DNS cache and COOKIES.
112 class CurlSession {
114 public:
116 /// Get CurlSession singleton
117 static CurlSession& get();
119 /// Get the shared handle
120 CURLSH* getSharedHandle() { return _shandle; }
122 private:
124 /// Initialize a libcurl session
126 /// A libcurl session consists in a shared handle
127 /// sharing DNS cache and COOKIES.
129 /// Also, global libcurl initialization function will
130 /// be invoked, so MAKE SURE NOT TO CONSTRUCT multiple
131 /// CurlSession instances in a multithreaded environment!
133 /// AGAIN: this is NOT thread-safe !
135 CurlSession();
137 /// Cleanup curl session stuff (including global lib init)
138 ~CurlSession();
141 // the libcurl share handle, for sharing cookies
142 CURLSH* _shandle;
144 // mutex protecting share state
145 boost::mutex _shareMutex;
146 boost::mutex::scoped_lock _shareMutexLock;
148 // mutex protecting shared cookies
149 boost::mutex _cookieMutex;
150 boost::mutex::scoped_lock _cookieMutexLock;
152 // mutex protecting shared dns cache
153 boost::mutex _dnscacheMutex;
154 boost::mutex::scoped_lock _dnscacheMutexLock;
156 /// Import cookies, if requested
158 /// This method will lookup GNASH_COOKIES_IN
159 /// in the environment, and if existing, will
160 /// parse the file sending each line to a fake
161 /// easy handle created ad-hoc
163 void importCookies();
165 /// Export cookies, if requested
167 /// This method will lookup GNASH_COOKIES_OUT
168 /// in the environment, and if existing, will
169 /// create the file writing any cookie currently
170 /// in the jar
172 void exportCookies();
174 /// Shared handle data locking function
175 void lockSharedHandle(CURL* handle, curl_lock_data data,
176 curl_lock_access access);
178 /// Shared handle data unlocking function
179 void unlockSharedHandle(CURL* handle, curl_lock_data data);
181 /// Shared handle locking function
182 static void lockSharedHandleWrapper(CURL* handle, curl_lock_data data,
183 curl_lock_access access, void* userptr)
185 CurlSession* ci = static_cast<CurlSession*>(userptr);
186 ci->lockSharedHandle(handle, data, access);
189 /// Shared handle unlocking function
190 static void unlockSharedHandleWrapper(CURL* handle, curl_lock_data data,
191 void* userptr)
193 // data defines what data libcurl wants to unlock, and you must
194 // make sure that only one lock is given at any time for each kind
195 // of data.
196 // userptr is the pointer you set with CURLSHOPT_USERDATA.
197 CurlSession* ci = static_cast<CurlSession*>(userptr);
198 ci->unlockSharedHandle(handle, data);
203 CurlSession&
204 CurlSession::get()
206 static CurlSession cs;
207 return cs;
210 CurlSession::~CurlSession()
212 log_debug("~CurlSession");
213 exportCookies();
215 CURLSHcode code;
216 int retries=0;
217 while ( (code=curl_share_cleanup(_shandle)) != CURLSHE_OK ) {
218 if ( ++retries > 10 ) {
219 log_error("Failed cleaning up share handle: %s. Giving up after "
220 "%d retries.", curl_share_strerror(code), retries);
221 break;
223 log_error("Failed cleaning up share handle: %s. Will try again in "
224 "a second.", curl_share_strerror(code));
225 gnashSleep(1000000);
227 _shandle = 0;
228 curl_global_cleanup();
231 #if BOOST_VERSION < 103500
232 # define GNASH_DEFER_LOCK false
233 #else
234 # define GNASH_DEFER_LOCK boost::defer_lock
235 #endif
237 CurlSession::CurlSession()
239 _shandle(0),
240 _shareMutex(),
241 _shareMutexLock(_shareMutex, GNASH_DEFER_LOCK), // start unlocked
242 _cookieMutex(),
243 _cookieMutexLock(_cookieMutex, GNASH_DEFER_LOCK), // start unlocked
244 _dnscacheMutex(),
245 _dnscacheMutexLock(_dnscacheMutex, GNASH_DEFER_LOCK) // start unlocked
247 // TODO: handle an error here (throw an exception)
248 curl_global_init(CURL_GLOBAL_ALL);
250 _shandle = curl_share_init();
251 if (! _shandle) {
252 throw GnashException("Failure initializing curl share handle");
255 CURLSHcode ccode;
257 // Register share locking function
258 ccode = curl_share_setopt(_shandle, CURLSHOPT_LOCKFUNC,
259 lockSharedHandleWrapper);
260 if ( ccode != CURLSHE_OK ) {
261 throw GnashException(curl_share_strerror(ccode));
264 // Register share unlocking function
265 ccode = curl_share_setopt(_shandle, CURLSHOPT_UNLOCKFUNC,
266 unlockSharedHandleWrapper);
267 if ( ccode != CURLSHE_OK ) {
268 throw GnashException(curl_share_strerror(ccode));
271 // Activate sharing of cookies and DNS cache
272 ccode = curl_share_setopt(_shandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
273 if ( ccode != CURLSHE_OK ) {
274 throw GnashException(curl_share_strerror(ccode));
277 // Activate sharing of DNS cache (since we're there..)
278 ccode = curl_share_setopt(_shandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
279 if ( ccode != CURLSHE_OK ) {
280 throw GnashException(curl_share_strerror(ccode));
283 // Pass ourselves as the userdata
284 ccode = curl_share_setopt(_shandle, CURLSHOPT_USERDATA, this);
285 if ( ccode != CURLSHE_OK ) {
286 throw GnashException(curl_share_strerror(ccode));
289 importCookies();
292 void
293 CurlSession::lockSharedHandle(CURL* handle, curl_lock_data data,
294 curl_lock_access access)
296 UNUSED(handle); // possibly being the 'easy' handle triggering the request ?
298 // data defines what data libcurl wants to lock, and you must make
299 // sure that only one lock is given at any time for each kind of data.
300 // access defines what access type libcurl wants, shared or single.
302 // TODO: see if we may make use of the 'access' parameter
303 UNUSED(access);
305 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) {
345 case CURL_LOCK_DATA_DNS:
346 //log_debug("Unlocking DNS cache mutex");
347 _dnscacheMutexLock.unlock();
348 break;
349 case CURL_LOCK_DATA_COOKIE:
350 //log_debug("Unlocking cookies mutex");
351 _cookieMutexLock.unlock();
352 break;
353 case CURL_LOCK_DATA_SHARE:
354 //log_debug("Unlocking share mutex");
355 _shareMutexLock.unlock();
356 break;
357 case CURL_LOCK_DATA_SSL_SESSION:
358 log_error("unlockSharedHandle: SSL session locking "
359 "unsupported");
360 break;
361 case CURL_LOCK_DATA_CONNECT:
362 log_error("unlockSharedHandle: connect locking unsupported");
363 break;
364 case CURL_LOCK_DATA_LAST:
365 log_error("unlockSharedHandle: last locking unsupported ?!");
366 break;
367 default:
368 std::cerr << "unlockSharedHandle: unknown shared data " <<
369 data << std::endl;
370 break;
375 /***********************************************************************
377 * CurlStreamFile definition
379 **********************************************************************/
381 /// libcurl based IOChannel, for network uri accesses
382 class CurlStreamFile : public IOChannel
385 public:
387 typedef std::map<std::string, std::string> PostData;
389 /// Open a stream from the specified URL
390 CurlStreamFile(const std::string& url, const std::string& cachefile);
392 /// Open a stream from the specified URL posting the specified variables
394 /// @param url
395 /// The url to post to.
397 /// @param vars
398 /// The url-encoded post data.
400 CurlStreamFile(const std::string& url, const std::string& vars,
401 const std::string& cachefile);
403 CurlStreamFile(const std::string& url, const std::string& vars,
404 const NetworkAdapter::RequestHeaders& headers,
405 const std::string& cachefile);
407 ~CurlStreamFile();
409 // See dox in IOChannel.h
410 virtual std::streamsize read(void *dst, std::streamsize bytes);
412 // See dox in IOChannel.h
413 virtual std::streamsize readNonBlocking(void *dst, std::streamsize bytes);
415 // See dox in IOChannel.h
416 virtual bool eof() const;
418 // See dox in IOChannel.h
419 virtual bool bad() const {
420 return _error;
423 /// Report global position within the file
424 virtual std::streampos tell() const;
426 /// Put read pointer at given position
427 virtual bool seek(std::streampos pos);
429 /// Put read pointer at eof
430 virtual void go_to_end();
432 /// Returns the size of the stream
434 /// If size of the stream is unknown, 0 is returned.
435 /// In that case you might try calling this function
436 /// again after filling the cache a bit...
438 /// Another approach might be filling the cache ourselves
439 /// aiming at obtaining a useful value.
441 virtual size_t size() const;
443 private:
445 void init(const std::string& url, const std::string& cachefile);
447 // Use this file to cache data
448 FILE* _cache;
450 // _cache file descriptor
451 int _cachefd;
453 // we keep a copy here to be sure the char*
454 // is alive for the whole CurlStreamFile lifetime
455 // TODO: don't really do this :)
456 std::string _url;
458 // the libcurl easy handle
459 CURL *_handle;
461 // the libcurl multi handle
462 CURLM *_mhandle;
464 // transfer in progress
465 int _running;
467 // stream error
468 // false on no error.
469 // Example of errors would be:
470 // 404 - file not found
471 // timeout occurred
472 bool _error;
474 // Post data. Empty if no POST has been requested
475 std::string _postdata;
477 // Current size of cached data
478 size_t _cached;
480 /// Total stream size.
482 /// This will be 0 until known
484 mutable size_t _size;
486 // Attempt at filling the cache up to the given size.
487 // Will call libcurl routines to fetch data.
488 void fillCache(std::streamsize size);
490 // Process pending curl messages (handles 404)
491 void processMessages();
493 // Filling the cache as much as possible w/out blocking.
494 // Will call libcurl routines to fetch data.
495 void fillCacheNonBlocking();
497 // Append sz bytes to the cache
498 std::streamsize cache(void *from, std::streamsize sz);
500 // Callback for libcurl, will be called
501 // by fillCache() and will call cache()
502 static size_t recv(void *buf, size_t size, size_t nmemb, void *userp);
504 // List of custom headers for this stream.
505 struct curl_slist *_customHeaders;
509 /***********************************************************************
511 * Statics and CurlStreamFile implementation
513 **********************************************************************/
515 /*static private*/
516 size_t
517 CurlStreamFile::recv(void *buf, size_t size, size_t nmemb, void *userp)
519 #ifdef GNASH_CURL_VERBOSE
520 log_debug("curl write callback called for (%d) bytes",
521 size * nmemb);
522 #endif
523 CurlStreamFile* stream = static_cast<CurlStreamFile*>(userp);
524 return stream->cache(buf, size * nmemb);
528 /*private*/
529 std::streamsize
530 CurlStreamFile::cache(void *from, std::streamsize size)
532 // take note of current position
533 long curr_pos = std::ftell(_cache);
535 // seek to the end
536 std::fseek(_cache, 0, SEEK_END);
538 std::streamsize wrote = std::fwrite(from, 1, size, _cache);
539 if ( wrote < 1 ) {
540 boost::format fmt = boost::format("writing to cache file: requested "
541 "%d, wrote %d (%s)") %
542 size % wrote % std::strerror(errno);
543 throw GnashException(fmt.str());
546 // Set the size of cached data
547 _cached = std::ftell(_cache);
549 // reset position for next read
550 std::fseek(_cache, curr_pos, SEEK_SET);
552 return wrote;
555 /*private*/
556 void
557 CurlStreamFile::fillCacheNonBlocking()
559 if ( ! _running ) {
560 #if GNASH_CURL_VERBOSE
561 log_debug("Not running: fillCacheNonBlocking returning");
562 #endif
563 return;
566 CURLMcode mcode;
568 do {
569 mcode = curl_multi_perform(_mhandle, &_running);
570 } while (mcode == CURLM_CALL_MULTI_PERFORM); // && _running ?
572 if (mcode != CURLM_OK) {
573 throw GnashException(curl_multi_strerror(mcode));
576 // handle 404
577 processMessages();
581 /*private*/
582 void
583 CurlStreamFile::fillCache(std::streamsize size)
586 #if GNASH_CURL_VERBOSE
587 log_debug("fillCache(%d), called, currently cached: %d", size, _cached);
588 #endif
590 assert(size >= 0);
592 if ( ! _running || _cached >= static_cast<size_t>(size)) {
593 #if GNASH_CURL_VERBOSE
594 if (!_running) log_debug("Not running: returning");
595 else log_debug("Already enough bytes cached: returning");
596 #endif
597 return;
600 fd_set readfd, writefd, exceptfd;
601 int maxfd;
602 CURLMcode mcode;
603 timeval tv;
605 // Hard-coded slect timeout.
606 // This number is kept low to give more thread switch
607 // opportunities while waiting for a load.
608 const long maxSleepUsec = 10000; // 1/100 of a second
610 const unsigned int userTimeout = static_cast<unsigned int>(
611 RcInitFile::getDefaultInstance().getStreamsTimeout()*1000);
613 #ifdef GNASH_CURL_VERBOSE
614 log_debug("User timeout is %u milliseconds", userTimeout);
615 #endif
617 WallClockTimer lastProgress; // timer since last progress
618 while (_running) {
619 fillCacheNonBlocking(); // NOTE: will handle 404
621 // Do this here to avoid calling select()
622 // when we have enough bytes anyway, or
623 // we reached EOF
624 if (_cached >= static_cast<size_t>(size) || !_running) break;
626 #if GNASH_CURL_VERBOSE
627 //log_debug("cached: %d, size: %d", _cached, size);
628 #endif
630 // Zero these out _before_ calling curl_multi_fdset!
631 FD_ZERO(&readfd);
632 FD_ZERO(&writefd);
633 FD_ZERO(&exceptfd);
635 mcode = curl_multi_fdset(_mhandle, &readfd, &writefd,
636 &exceptfd, &maxfd);
638 if (mcode != CURLM_OK) {
639 // This is a serious error, not just a failure to add any
640 // fds.
641 throw GnashException(curl_multi_strerror(mcode));
644 #ifdef GNASH_CURL_VERBOSE
645 log_debug("Max fd: %d", maxfd);
646 #endif
648 // A value of -1 means no file descriptors were added.
649 if (maxfd < 0) {
650 #if GNASH_CURL_VERBOSE
651 log_debug("curl_multi_fdset: maxfd == %1%", maxfd);
652 #endif
653 // As of libcurl 7.21.x, the DNS resolving appears to be going
654 // on in the background, so curl_multi_fdset fails to return
655 // anything useful. So we use the user timeout value to
656 // give DNS enough time to resolve the lookup.
657 if (userTimeout && lastProgress.elapsed() > userTimeout) {
658 log_error(_("FIXME: Timeout (%u milliseconds) while loading "
659 "from url %s"), userTimeout, _url);
660 // TODO: should we set _error here ?
661 return;
662 } else {
663 continue;
667 tv.tv_sec = 0;
668 tv.tv_usec = maxSleepUsec;
670 #ifdef GNASH_CURL_VERBOSE
671 log_debug("select() with %d milliseconds timeout", maxSleepUsec*1000);
672 #endif
674 // Wait for data on the filedescriptors until a timeout set
675 // in gnashrc.
676 int ret = select(maxfd + 1, &readfd, &writefd, &exceptfd, &tv);
678 // select() will always fail on OS/2 and AmigaOS4 as we can't select
679 // on file descriptors, only on sockets
680 #if !defined(__OS2__) && !defined(__amigaos4__) && !defined(WIN32)
681 if ( ret == -1 ) {
682 if ( errno == EINTR ) {
683 // we got interrupted by a signal
684 // let's consider this as a timeout
685 #ifdef GNASH_CURL_VERBOSE
686 log_debug("select() was interrupted by a signal");
687 #endif
688 ret = 0;
689 } 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 ) {
699 // Timeout, check the clock to see
700 // if we expired the user 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) {
706 log_error(_("Timeout (%u milliseconds) while loading "
707 "from url %s"), userTimeout, _url);
708 // TODO: should we set _error here ?
709 return;
711 } else {
712 // Activity, reset the timer...
713 #ifdef GNASH_CURL_VERBOSE
714 log_debug("FD activity, resetting progress timer");
715 #endif
716 lastProgress.restart();
721 // Processing messages is already done by fillCacheNonBlocking,
722 // so we might likely avoid it here.
723 processMessages();
727 /*private*/
728 void
729 CurlStreamFile::processMessages()
731 CURLMsg *curl_msg;
733 // The number of messages left in the queue (not used by us).
734 int msgs;
735 while ((curl_msg = curl_multi_info_read(_mhandle, &msgs))) {
736 // Only for completed transactions
737 if (curl_msg->msg == CURLMSG_DONE) {
739 // HTTP transaction succeeded
740 if (curl_msg->data.result == CURLE_OK) {
742 long code;
744 // Check HTTP response
745 curl_easy_getinfo(curl_msg->easy_handle,
746 CURLINFO_RESPONSE_CODE, &code);
748 if ( code >= 400 ) {
749 log_error ("HTTP response %ld from url %s",
750 code, _url);
751 _error = true;
752 _running = false;
753 } else {
754 log_debug ("HTTP response %ld from url %s",
755 code, _url);
758 } else {
759 // Transaction failed, pass on curl error.
760 log_error("CURL: %s", curl_easy_strerror(
761 curl_msg->data.result));
762 _error = true;
771 /*private*/
772 void
773 CurlStreamFile::init(const std::string& url, const std::string& cachefile)
775 _customHeaders = 0;
777 _url = url;
778 _running = 1;
779 _error = false;
781 _cached = 0;
782 _size = 0;
784 _handle = curl_easy_init();
785 _mhandle = curl_multi_init();
787 const RcInitFile& rcfile = RcInitFile::getDefaultInstance();
789 if (!cachefile.empty()) {
790 _cache = std::fopen(cachefile.c_str(), "w+b");
791 if (!_cache) {
793 log_error("Could not open specified path as cache file. Using "
794 "a temporary file instead");
795 _cache = std::tmpfile();
797 } else {
798 _cache = std::tmpfile();
801 if ( ! _cache ) {
802 throw GnashException("Could not create temporary cache file");
804 _cachefd = fileno(_cache);
806 CURLcode ccode;
808 // Override cURL's default verification of SSL certificates
809 // This is insecure, so log security warning.
810 // Equivalent to curl -k or curl --insecure.
811 if (rcfile.insecureSSL()) {
812 log_security(_("Allowing connections to SSL sites with invalid "
813 "certificates"));
815 ccode = curl_easy_setopt(_handle, CURLOPT_SSL_VERIFYPEER, 0);
816 if ( ccode != CURLE_OK ) {
817 throw GnashException(curl_easy_strerror(ccode));
820 ccode = curl_easy_setopt(_handle, CURLOPT_SSL_VERIFYHOST, 0);
821 if ( ccode != CURLE_OK ) {
822 throw GnashException(curl_easy_strerror(ccode));
826 // Get shared data
827 ccode = curl_easy_setopt(_handle, CURLOPT_SHARE,
828 CurlSession::get().getSharedHandle());
830 if ( ccode != CURLE_OK ) {
831 throw GnashException(curl_easy_strerror(ccode));
834 // Set expiration time for DNS cache entries, in seconds
835 // 0 disables caching
836 // -1 makes cache entries never expire
837 // default is 60
839 // NOTE: this snippet is here just as a placeholder for whoever
840 // will feel a need to make this parametrizable
842 ccode = curl_easy_setopt(_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60);
843 if ( ccode != CURLE_OK ) {
844 throw GnashException(curl_easy_strerror(ccode));
847 ccode = curl_easy_setopt(_handle, CURLOPT_USERAGENT, "Gnash-" VERSION);
848 if ( ccode != CURLE_OK ) {
849 throw GnashException(curl_easy_strerror(ccode));
852 #ifdef GNASH_CURL_VERBOSE
853 // for verbose operations
854 ccode = curl_easy_setopt(_handle, CURLOPT_VERBOSE, 1);
855 if ( ccode != CURLE_OK ) {
856 throw GnashException(curl_easy_strerror(ccode));
858 #endif
860 /* from libcurl-tutorial(3)
861 When using multiple threads you should set the CURLOPT_NOSIGNAL option
862 to TRUE for all handles. Everything will work fine except that timeouts
863 are not honored during the DNS lookup - which you can work around by
865 ccode = curl_easy_setopt(_handle, CURLOPT_NOSIGNAL, true);
866 if ( ccode != CURLE_OK ) {
867 throw GnashException(curl_easy_strerror(ccode));
870 // set url
871 ccode = curl_easy_setopt(_handle, CURLOPT_URL, _url.c_str());
872 if ( ccode != CURLE_OK ) {
873 throw GnashException(curl_easy_strerror(ccode));
876 //curl_easy_setopt(_handle, CURLOPT_NOPROGRESS, false);
879 // set write data and function
880 ccode = curl_easy_setopt(_handle, CURLOPT_WRITEDATA, this);
881 if ( ccode != CURLE_OK ) {
882 throw GnashException(curl_easy_strerror(ccode));
885 ccode = curl_easy_setopt(_handle, CURLOPT_WRITEFUNCTION,
886 CurlStreamFile::recv);
887 if ( ccode != CURLE_OK ) {
888 throw GnashException(curl_easy_strerror(ccode));
891 ccode = curl_easy_setopt(_handle, CURLOPT_FOLLOWLOCATION, 1);
892 if ( ccode != CURLE_OK ) {
893 throw GnashException(curl_easy_strerror(ccode));
896 //fillCache(32); // pre-cache 32 bytes
897 //curl_multi_perform(_mhandle, &_running);
900 /*public*/
901 CurlStreamFile::CurlStreamFile(const std::string& url,
902 const std::string& cachefile)
904 log_debug("CurlStreamFile %p created", this);
905 init(url, cachefile);
907 // CURLMcode ret =
908 CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
909 if ( mcode != CURLM_OK ) {
910 throw GnashException(curl_multi_strerror(mcode));
914 /*public*/
915 CurlStreamFile::CurlStreamFile(const std::string& url, const std::string& vars,
916 const std::string& cachefile)
918 log_debug("CurlStreamFile %p created", this);
919 init(url, cachefile);
921 _postdata = vars;
923 CURLcode ccode;
925 ccode = curl_easy_setopt(_handle, CURLOPT_POST, 1);
926 if ( ccode != CURLE_OK ) {
927 throw GnashException(curl_easy_strerror(ccode));
930 // libcurl needs to access the POSTFIELDS during 'perform' operations,
931 // so we must use a string whose lifetime is ensured to be longer then
932 // the multihandle itself.
933 // The _postdata member should meet this requirement
934 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDS, _postdata.c_str());
935 if ( ccode != CURLE_OK ) {
936 throw GnashException(curl_easy_strerror(ccode));
939 // This is to support binary strings as postdata
940 // NOTE: in version 7.11.1 CURLOPT_POSTFIELDSIZE_LARGE was added
941 // this one takes a long, that one takes a curl_off_t
943 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDSIZE, _postdata.size());
944 if ( ccode != CURLE_OK ) {
945 throw GnashException(curl_easy_strerror(ccode));
948 // Disable sending an Expect: header, as some older HTTP/1.1
949 // don't implement them, and some (namely lighttpd/1.4.19,
950 // running on openstreetmap.org at time of writing) return
951 // a '417 Expectance Failure' response on getting that.
952 assert ( ! _customHeaders );
953 _customHeaders = curl_slist_append(_customHeaders, "Expect:");
954 ccode = curl_easy_setopt(_handle, CURLOPT_HTTPHEADER, _customHeaders);
955 if ( ccode != CURLE_OK ) {
956 throw GnashException(curl_easy_strerror(ccode));
959 CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
960 if ( mcode != CURLM_OK ) {
961 throw GnashException(curl_multi_strerror(mcode));
966 /*public*/
967 CurlStreamFile::CurlStreamFile(const std::string& url, const std::string& vars,
968 const NetworkAdapter::RequestHeaders& headers,
969 const std::string& cachefile)
971 log_debug("CurlStreamFile %p created", this);
972 init(url, cachefile);
974 _postdata = vars;
976 // Disable sending an Expect: header, as some older HTTP/1.1
977 // don't implement them, and some (namely lighttpd/1.4.19,
978 // running on openstreetmap.org at time of writing) return
979 // a '417 Expectance Failure' response on getting that.
981 // Do this before adding user-requested headers so user
982 // specified ones take precedence
984 assert ( ! _customHeaders );
985 _customHeaders = curl_slist_append(_customHeaders, "Expect:");
987 for (NetworkAdapter::RequestHeaders::const_iterator i = headers.begin(),
988 e = headers.end(); i != e; ++i) {
989 // Check here to see whether header name is allowed.
990 if (!NetworkAdapter::isHeaderAllowed(i->first)) continue;
991 std::ostringstream os;
992 os << i->first << ": " << i->second;
993 _customHeaders = curl_slist_append(_customHeaders, os.str().c_str());
996 CURLcode ccode;
998 ccode = curl_easy_setopt(_handle, CURLOPT_HTTPHEADER, _customHeaders);
999 if ( ccode != CURLE_OK ) {
1000 throw GnashException(curl_easy_strerror(ccode));
1003 ccode = curl_easy_setopt(_handle, CURLOPT_POST, 1);
1004 if ( ccode != CURLE_OK ) {
1005 throw GnashException(curl_easy_strerror(ccode));
1008 // libcurl needs to access the POSTFIELDS during 'perform' operations,
1009 // so we must use a string whose lifetime is ensured to be longer then
1010 // the multihandle itself.
1011 // The _postdata member should meet this requirement
1012 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDS, _postdata.c_str());
1013 if ( ccode != CURLE_OK ) {
1014 throw GnashException(curl_easy_strerror(ccode));
1017 // This is to support binary strings as postdata
1018 // NOTE: in version 7.11.1 CURLOPT_POSTFIELDSIZE_LARGE was added
1019 // this one takes a long, that one takes a curl_off_t
1021 ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDSIZE, _postdata.size());
1022 if ( ccode != CURLE_OK ) {
1023 throw GnashException(curl_easy_strerror(ccode));
1026 CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
1027 if ( mcode != CURLM_OK ) {
1028 throw GnashException(curl_multi_strerror(mcode));
1034 /*public*/
1035 CurlStreamFile::~CurlStreamFile()
1037 log_debug("CurlStreamFile %p deleted", this);
1038 curl_multi_remove_handle(_mhandle, _handle);
1039 curl_easy_cleanup(_handle);
1040 curl_multi_cleanup(_mhandle);
1041 std::fclose(_cache);
1042 if ( _customHeaders ) curl_slist_free_all(_customHeaders);
1045 /*public*/
1046 std::streamsize
1047 CurlStreamFile::read(void *dst, std::streamsize bytes)
1049 if ( eof() || _error ) return 0;
1051 #ifdef GNASH_CURL_VERBOSE
1052 log_debug ("read(%d) called", bytes);
1053 #endif
1055 fillCache(bytes + tell());
1056 if ( _error ) return 0; // error can be set by fillCache
1058 #ifdef GNASH_CURL_VERBOSE
1059 log_debug("_cache.tell = %d", tell());
1060 #endif
1062 return std::fread(dst, 1, bytes, _cache);
1066 /*public*/
1067 std::streamsize
1068 CurlStreamFile::readNonBlocking(void *dst, std::streamsize bytes)
1070 #ifdef GNASH_CURL_VERBOSE
1071 log_debug ("readNonBlocking(%d) called", bytes);
1072 #endif
1074 if ( eof() || _error ) return 0;
1076 fillCacheNonBlocking();
1077 if ( _error ) {
1078 // I guess an exception would be thrown in this case ?
1079 log_error("curl adaptor's fillCacheNonBlocking set _error "
1080 "rather then throwing an exception");
1081 return 0;
1084 std::streamsize actuallyRead = std::fread(dst, 1, bytes, _cache);
1085 if ( _running ) {
1086 // if we're still running drop any eof flag
1087 // on the cache
1088 clearerr(_cache);
1091 return actuallyRead;
1095 /*public*/
1096 bool
1097 CurlStreamFile::eof() const
1099 bool ret = ( ! _running && feof(_cache) );
1101 #ifdef GNASH_CURL_VERBOSE
1102 log_debug("eof() returning %d", ret);
1103 #endif
1104 return ret;
1108 /*public*/
1109 std::streampos
1110 CurlStreamFile::tell() const
1112 std::streampos ret = std::ftell(_cache);
1114 #ifdef GNASH_CURL_VERBOSE
1115 log_debug("tell() returning %ld", ret);
1116 #endif
1118 return ret;
1122 /*public*/
1123 bool
1124 CurlStreamFile::seek(std::streampos pos)
1127 if ( pos < 0 ) {
1128 std::ostringstream os;
1129 os << "CurlStreamFile: can't seek to negative absolute position "
1130 << pos;
1131 throw IOException(os.str());
1134 #ifdef GNASH_CURL_WARN_SEEKSBACK
1135 if ( pos < tell() ) {
1136 log_debug("Warning: seek backward requested (%ld from %ld)",
1137 pos, tell());
1139 #endif
1141 fillCache(pos);
1142 if (_error) return false; // error can be set by fillCache
1144 if (_cached < static_cast<size_t>(pos)) {
1145 log_error ("Warning: could not cache enough bytes on seek: %d "
1146 "requested, %d cached", pos, _cached);
1147 return false;
1150 if (std::fseek(_cache, pos, SEEK_SET) == -1) {
1151 log_error("Warning: fseek failed");
1152 return false;
1155 return true;
1159 /*public*/
1160 void
1161 CurlStreamFile::go_to_end()
1163 CURLMcode mcode;
1164 while (_running > 0) {
1165 do {
1166 mcode=curl_multi_perform(_mhandle, &_running);
1167 } while ( mcode == CURLM_CALL_MULTI_PERFORM );
1169 if ( mcode != CURLM_OK ) {
1170 throw IOException(curl_multi_strerror(mcode));
1173 long code;
1174 curl_easy_getinfo(_handle, CURLINFO_RESPONSE_CODE, &code);
1175 if (code == 404) { // file not found!
1176 throw IOException("File not found");
1181 if (std::fseek(_cache, 0, SEEK_END) == -1) {
1182 throw IOException("NetworkAdapter: fseek to end failed");
1186 /*public*/
1187 size_t
1188 CurlStreamFile::size() const
1190 if ( ! _size ) {
1191 double size;
1192 CURLcode ret = curl_easy_getinfo(_handle,
1193 CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);
1194 if (ret == CURLE_OK) {
1195 assert(size <= std::numeric_limits<size_t>::max());
1196 _size = static_cast<size_t>(size);
1200 #ifdef GNASH_CURL_VERBOSE
1201 log_debug("get_stream_size() returning %lu", _size);
1202 #endif
1204 return _size;
1208 void
1209 CurlSession::importCookies()
1211 const char* cookiesIn = std::getenv("GNASH_COOKIES_IN");
1212 if ( ! cookiesIn ) return; // nothing to do
1214 ////////////////////////////////////////////////////////////////
1216 // WARNING: what we're doing here is an ugly hack
1218 // We'll be creating a fake easy handle for the sole purpos
1219 // of importing cookies. Tests conducted against 7.15.5-CVS
1220 // resulted in this working if a CURLOPT_URL is given, even
1221 // if invalid (but non-0!), while wouldn't if NO CURLOPT_URL
1222 // is given, with both cases returning the same CURLcode on
1223 // _perform (URL using bad/illegal format or missing URL)
1225 // TODO: instead, we should be reading the input file
1226 // ourselves and use CURLOPT_COOKIELIST to send
1227 // each line. Doing so should not require a
1228 // _perform call.
1230 ////////////////////////////////////////////////////////////////
1232 // Create a fake handle for purpose of importing data
1233 CURL* fakeHandle = curl_easy_init(); // errors to handle here ?
1234 CURLcode ccode;
1236 // Configure the fake handle to use the share (shared cookies in particular..)
1237 ccode = curl_easy_setopt(fakeHandle, CURLOPT_SHARE, getSharedHandle());
1238 if ( ccode != CURLE_OK ) {
1239 throw GnashException(curl_easy_strerror(ccode));
1242 // Configure the fake handle to read cookies from the specified file
1243 ccode = curl_easy_setopt(fakeHandle, CURLOPT_COOKIEFILE, cookiesIn);
1244 if ( ccode != CURLE_OK ) {
1245 throw GnashException(curl_easy_strerror(ccode));
1248 // need to pass a non-zero URL string for COOKIEFILE to
1249 // be really parsed
1250 ccode = curl_easy_setopt(fakeHandle, CURLOPT_URL, "");
1251 if ( ccode != CURLE_OK ) {
1252 throw GnashException(curl_easy_strerror(ccode));
1255 // perform, to activate actual cookie file parsing
1257 // NOTE: curl_easy_perform is expected to return an
1258 // "URL using bad/illegal format or missing URL" error,
1259 // there's no way to detect actual cookies import errors
1260 // other then using curl debugging output routines
1262 log_debug("Importing cookies from file '%s'", cookiesIn);
1263 curl_easy_perform(fakeHandle);
1265 curl_easy_cleanup(fakeHandle);
1269 void
1270 CurlSession::exportCookies()
1272 const char* cookiesOut = std::getenv("GNASH_COOKIES_OUT");
1273 if ( ! cookiesOut ) return; // nothing to do
1275 ////////////////////////////////////////////////////////////////
1277 // WARNING: what we're doing here is an ugly hack
1279 // We'll be creating a fake easy handle for the sole purpose
1280 // of exporting cookies. Tests conducted against 7.15.5-CVS
1281 // resulted in this working w/out a CURLOPT_URL and w/out a
1282 // curl_easy_perform.
1284 // NOTE: the "correct" way would be to use CURLOPT_COOKIELIST
1285 // with the "FLUSH" special string as value, but that'd
1286 // be only supported by version 7.17.1
1288 ////////////////////////////////////////////////////////////////
1290 CURL* fakeHandle = curl_easy_init(); // errors to handle here ?
1291 CURLcode ccode;
1293 // Configure the fake handle to use the share (shared cookies in particular..)
1294 ccode = curl_easy_setopt(fakeHandle, CURLOPT_SHARE, getSharedHandle());
1295 if ( ccode != CURLE_OK ) {
1296 throw GnashException(curl_easy_strerror(ccode));
1299 // Configure the fake handle to write cookies to the specified file
1300 ccode = curl_easy_setopt(fakeHandle, CURLOPT_COOKIEJAR , cookiesOut);
1301 if ( ccode != CURLE_OK ) {
1302 throw GnashException(curl_easy_strerror(ccode));
1305 // Cleanup, to trigger actual cookie file flushing
1306 log_debug("Exporting cookies file '%s'", cookiesOut);
1307 curl_easy_cleanup(fakeHandle);
1312 } // anonymous namespace
1314 //-------------------------------------------
1315 // Exported interfaces
1316 //-------------------------------------------
1318 std::auto_ptr<IOChannel>
1319 NetworkAdapter::makeStream(const std::string& url, const std::string& cachefile)
1321 #ifdef GNASH_CURL_VERBOSE
1322 log_debug("making curl stream for %s", url);
1323 #endif
1325 std::auto_ptr<IOChannel> stream;
1327 try {
1328 stream.reset(new CurlStreamFile(url, cachefile));
1330 catch (const std::exception& ex) {
1331 log_error("curl stream: %s", ex.what());
1333 return stream;
1336 std::auto_ptr<IOChannel>
1337 NetworkAdapter::makeStream(const std::string& url, const std::string& postdata,
1338 const std::string& cachefile)
1340 #ifdef GNASH_CURL_VERBOSE
1341 log_debug("making curl stream for %s", url);
1342 #endif
1344 std::auto_ptr<IOChannel> stream;
1346 try {
1347 stream.reset(new CurlStreamFile(url, postdata, cachefile));
1349 catch (const std::exception& ex) {
1350 log_error("curl stream: %s", ex.what());
1352 return stream;
1355 std::auto_ptr<IOChannel>
1356 NetworkAdapter::makeStream(const std::string& url, const std::string& postdata,
1357 const RequestHeaders& headers, const std::string& cachefile)
1360 std::auto_ptr<IOChannel> stream;
1362 try {
1363 stream.reset(new CurlStreamFile(url, postdata, headers, cachefile));
1365 catch (const std::exception& ex) {
1366 log_error("curl stream: %s", ex.what());
1369 return stream;
1373 const NetworkAdapter::ReservedNames&
1374 NetworkAdapter::reservedNames()
1376 static const ReservedNames names = boost::assign::list_of
1377 ("Accept-Ranges")
1378 ("Age")
1379 ("Allow")
1380 ("Allowed")
1381 ("Connection")
1382 ("Content-Length")
1383 ("Content-Location")
1384 ("Content-Range")
1385 ("ETag")
1386 ("GET")
1387 ("Host")
1388 ("HEAD")
1389 ("Last-Modified")
1390 ("Locations")
1391 ("Max-Forwards")
1392 ("POST")
1393 ("Proxy-Authenticate")
1394 ("Proxy-Authorization")
1395 ("Public")
1396 ("Range")
1397 ("Retry-After")
1398 ("Server")
1399 ("TE")
1400 ("Trailer")
1401 ("Transfer-Encoding")
1402 ("Upgrade")
1403 ("URI")
1404 ("Vary")
1405 ("Via")
1406 ("Warning")
1407 ("WWW-Authenticate");
1409 return names;
1412 } // namespace gnash
1414 #endif // def USE_CURL
1417 // Local Variables:
1418 // mode: C++
1419 // indent-tabs-mode: null
1420 // End: