1 // NetworkAdapter.cpp: Interface to libcurl to read HTTP streams, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
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
22 #include "gnashconfig.h"
26 #include <sys/select.h>
29 #include "NetworkAdapter.h"
30 #include "utility.h" // UNUSED macro
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>
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
);
78 #include <curl/curl.h>
82 #include "GnashException.h"
84 #include "GnashSystemFDHeaders.h"
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
103 /***********************************************************************
105 * CurlSession definition and implementation
107 **********************************************************************/
109 /// A libcurl session consists in a shared handle
110 /// sharing DNS cache and COOKIES.
116 /// Get CurlSession singleton
117 static CurlSession
& get();
119 /// Get the shared handle
120 CURLSH
* getSharedHandle() { return _shandle
; }
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 !
137 /// Cleanup curl session stuff (including global lib init)
141 // the libcurl share handle, for sharing cookies
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
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
,
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
196 // userptr is the pointer you set with CURLSHOPT_USERDATA.
197 CurlSession
* ci
= static_cast<CurlSession
*>(userptr
);
198 ci
->unlockSharedHandle(handle
, data
);
206 static CurlSession cs
;
210 CurlSession::~CurlSession()
212 log_debug("~CurlSession");
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
);
223 log_error("Failed cleaning up share handle: %s. Will try again in "
224 "a second.", curl_share_strerror(code
));
228 curl_global_cleanup();
231 #if BOOST_VERSION < 103500
232 # define GNASH_DEFER_LOCK false
234 # define GNASH_DEFER_LOCK boost::defer_lock
237 CurlSession::CurlSession()
241 _shareMutexLock(_shareMutex
, GNASH_DEFER_LOCK
), // start unlocked
243 _cookieMutexLock(_cookieMutex
, GNASH_DEFER_LOCK
), // start unlocked
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();
252 throw GnashException("Failure initializing curl share handle");
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
));
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
306 case CURL_LOCK_DATA_DNS
:
307 //log_debug("Locking DNS cache mutex");
308 _dnscacheMutexLock
.lock();
309 //log_debug("DNS cache mutex locked");
311 case CURL_LOCK_DATA_COOKIE
:
312 //log_debug("Locking cookies mutex");
313 _cookieMutexLock
.lock();
314 //log_debug("Cookies mutex locked");
316 case CURL_LOCK_DATA_SHARE
:
317 //log_debug("Locking share mutex");
318 _shareMutexLock
.lock();
319 //log_debug("Share mutex locked");
321 case CURL_LOCK_DATA_SSL_SESSION
:
322 log_error("lockSharedHandle: SSL session locking "
325 case CURL_LOCK_DATA_CONNECT
:
326 log_error("lockSharedHandle: connect locking unsupported");
328 case CURL_LOCK_DATA_LAST
:
329 log_error("lockSharedHandle: last locking unsupported ?!");
332 log_error("lockSharedHandle: unknown shared data %d", data
);
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.
345 case CURL_LOCK_DATA_DNS
:
346 //log_debug("Unlocking DNS cache mutex");
347 _dnscacheMutexLock
.unlock();
349 case CURL_LOCK_DATA_COOKIE
:
350 //log_debug("Unlocking cookies mutex");
351 _cookieMutexLock
.unlock();
353 case CURL_LOCK_DATA_SHARE
:
354 //log_debug("Unlocking share mutex");
355 _shareMutexLock
.unlock();
357 case CURL_LOCK_DATA_SSL_SESSION
:
358 log_error("unlockSharedHandle: SSL session locking "
361 case CURL_LOCK_DATA_CONNECT
:
362 log_error("unlockSharedHandle: connect locking unsupported");
364 case CURL_LOCK_DATA_LAST
:
365 log_error("unlockSharedHandle: last locking unsupported ?!");
368 std::cerr
<< "unlockSharedHandle: unknown shared data " <<
375 /***********************************************************************
377 * CurlStreamFile definition
379 **********************************************************************/
381 /// libcurl based IOChannel, for network uri accesses
382 class CurlStreamFile
: public IOChannel
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
395 /// The url to post to.
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
);
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 {
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;
445 void init(const std::string
& url
, const std::string
& cachefile
);
447 // Use this file to cache data
450 // _cache file descriptor
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 :)
458 // the libcurl easy handle
461 // the libcurl multi handle
464 // transfer in progress
468 // false on no error.
469 // Example of errors would be:
470 // 404 - file not found
474 // Post data. Empty if no POST has been requested
475 std::string _postdata
;
477 // Current size of cached data
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 **********************************************************************/
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",
523 CurlStreamFile
* stream
= static_cast<CurlStreamFile
*>(userp
);
524 return stream
->cache(buf
, size
* nmemb
);
530 CurlStreamFile::cache(void *from
, std::streamsize size
)
532 // take note of current position
533 long curr_pos
= std::ftell(_cache
);
536 std::fseek(_cache
, 0, SEEK_END
);
538 std::streamsize wrote
= std::fwrite(from
, 1, size
, _cache
);
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
);
557 CurlStreamFile::fillCacheNonBlocking()
560 #if GNASH_CURL_VERBOSE
561 log_debug("Not running: fillCacheNonBlocking returning");
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
));
583 CurlStreamFile::fillCache(std::streamsize size
)
586 #if GNASH_CURL_VERBOSE
587 log_debug("fillCache(%d), called, currently cached: %d", size
, _cached
);
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");
600 fd_set readfd
, writefd
, exceptfd
;
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
);
617 WallClockTimer lastProgress
; // timer since last progress
619 fillCacheNonBlocking(); // NOTE: will handle 404
621 // Do this here to avoid calling select()
622 // when we have enough bytes anyway, or
624 if (_cached
>= static_cast<size_t>(size
) || !_running
) break;
626 #if GNASH_CURL_VERBOSE
627 //log_debug("cached: %d, size: %d", _cached, size);
630 // Zero these out _before_ calling curl_multi_fdset!
635 mcode
= curl_multi_fdset(_mhandle
, &readfd
, &writefd
,
638 if (mcode
!= CURLM_OK
) {
639 // This is a serious error, not just a failure to add any
641 throw GnashException(curl_multi_strerror(mcode
));
644 #ifdef GNASH_CURL_VERBOSE
645 log_debug("Max fd: %d", maxfd
);
648 // A value of -1 means no file descriptors were added.
650 #if GNASH_CURL_VERBOSE
651 log_debug("curl_multi_fdset: maxfd == %1%", maxfd
);
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 ?
668 tv
.tv_usec
= maxSleepUsec
;
670 #ifdef GNASH_CURL_VERBOSE
671 log_debug("select() with %d milliseconds timeout", maxSleepUsec
*1000);
674 // Wait for data on the filedescriptors until a timeout set
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)
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");
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());
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());
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 ?
712 // Activity, reset the timer...
713 #ifdef GNASH_CURL_VERBOSE
714 log_debug("FD activity, resetting progress timer");
716 lastProgress
.restart();
721 // Processing messages is already done by fillCacheNonBlocking,
722 // so we might likely avoid it here.
729 CurlStreamFile::processMessages()
733 // The number of messages left in the queue (not used by us).
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
) {
744 // Check HTTP response
745 curl_easy_getinfo(curl_msg
->easy_handle
,
746 CURLINFO_RESPONSE_CODE
, &code
);
749 log_error ("HTTP response %ld from url %s",
754 log_debug ("HTTP response %ld from url %s",
759 // Transaction failed, pass on curl error.
760 log_error("CURL: %s", curl_easy_strerror(
761 curl_msg
->data
.result
));
773 CurlStreamFile::init(const std::string
& url
, const std::string
& cachefile
)
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");
793 log_error("Could not open specified path as cache file. Using "
794 "a temporary file instead");
795 _cache
= std::tmpfile();
798 _cache
= std::tmpfile();
802 throw GnashException("Could not create temporary cache file");
804 _cachefd
= fileno(_cache
);
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 "
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
));
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
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
));
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
));
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);
901 CurlStreamFile::CurlStreamFile(const std::string
& url
,
902 const std::string
& cachefile
)
904 log_debug("CurlStreamFile %p created", this);
905 init(url
, cachefile
);
908 CURLMcode mcode
= curl_multi_add_handle(_mhandle
, _handle
);
909 if ( mcode
!= CURLM_OK
) {
910 throw GnashException(curl_multi_strerror(mcode
));
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
);
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
));
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
);
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());
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
));
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
);
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
);
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());
1062 return std::fread(dst
, 1, bytes
, _cache
);
1068 CurlStreamFile::readNonBlocking(void *dst
, std::streamsize bytes
)
1070 #ifdef GNASH_CURL_VERBOSE
1071 log_debug ("readNonBlocking(%d) called", bytes
);
1074 if ( eof() || _error
) return 0;
1076 fillCacheNonBlocking();
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");
1084 std::streamsize actuallyRead
= std::fread(dst
, 1, bytes
, _cache
);
1086 // if we're still running drop any eof flag
1091 return actuallyRead
;
1097 CurlStreamFile::eof() const
1099 bool ret
= ( ! _running
&& feof(_cache
) );
1101 #ifdef GNASH_CURL_VERBOSE
1102 log_debug("eof() returning %d", ret
);
1110 CurlStreamFile::tell() const
1112 std::streampos ret
= std::ftell(_cache
);
1114 #ifdef GNASH_CURL_VERBOSE
1115 log_debug("tell() returning %ld", ret
);
1124 CurlStreamFile::seek(std::streampos pos
)
1128 std::ostringstream os
;
1129 os
<< "CurlStreamFile: can't seek to negative absolute position "
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)",
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
);
1150 if (std::fseek(_cache
, pos
, SEEK_SET
) == -1) {
1151 log_error("Warning: fseek failed");
1161 CurlStreamFile::go_to_end()
1164 while (_running
> 0) {
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
));
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");
1188 CurlStreamFile::size() const
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
);
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
1230 ////////////////////////////////////////////////////////////////
1232 // Create a fake handle for purpose of importing data
1233 CURL
* fakeHandle
= curl_easy_init(); // errors to handle here ?
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
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
);
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 ?
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
);
1325 std::auto_ptr
<IOChannel
> stream
;
1328 stream
.reset(new CurlStreamFile(url
, cachefile
));
1330 catch (const std::exception
& ex
) {
1331 log_error("curl stream: %s", ex
.what());
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
);
1344 std::auto_ptr
<IOChannel
> stream
;
1347 stream
.reset(new CurlStreamFile(url
, postdata
, cachefile
));
1349 catch (const std::exception
& ex
) {
1350 log_error("curl stream: %s", ex
.what());
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
;
1363 stream
.reset(new CurlStreamFile(url
, postdata
, headers
, cachefile
));
1365 catch (const std::exception
& ex
) {
1366 log_error("curl stream: %s", ex
.what());
1373 const NetworkAdapter::ReservedNames
&
1374 NetworkAdapter::reservedNames()
1376 static const ReservedNames names
= boost::assign::list_of
1383 ("Content-Location")
1393 ("Proxy-Authenticate")
1394 ("Proxy-Authorization")
1401 ("Transfer-Encoding")
1407 ("WWW-Authenticate");
1412 } // namespace gnash
1414 #endif // def USE_CURL
1419 // indent-tabs-mode: null