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
);
77 #include <curl/curl.h>
79 #include "GnashException.h"
81 #include "GnashSystemFDHeaders.h"
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
100 /***********************************************************************
102 * CurlSession definition and implementation
104 **********************************************************************/
106 /// A libcurl session consists in a shared handle
107 /// sharing DNS cache and COOKIES.
113 /// Get CurlSession singleton
114 static CurlSession
& get();
116 /// Get the shared handle
117 CURLSH
* getSharedHandle() { return _shandle
; }
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 !
134 /// Cleanup curl session stuff (including global lib init)
138 // the libcurl share handle, for sharing cookies
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
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
,
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
193 // userptr is the pointer you set with CURLSHOPT_USERDATA.
194 CurlSession
* ci
= static_cast<CurlSession
*>(userptr
);
195 ci
->unlockSharedHandle(handle
, data
);
203 static CurlSession cs
;
207 CurlSession::~CurlSession()
209 log_debug("~CurlSession");
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
);
222 log_error("Failed cleaning up share handle: %s. Will try again in "
223 "a second.", curl_share_strerror(code
));
227 curl_global_cleanup();
230 #if BOOST_VERSION < 103500
231 # define GNASH_DEFER_LOCK false
233 # define GNASH_DEFER_LOCK boost::defer_lock
236 CurlSession::CurlSession()
240 _shareMutexLock(_shareMutex
, GNASH_DEFER_LOCK
), // start unlocked
242 _cookieMutexLock(_cookieMutex
, GNASH_DEFER_LOCK
), // start unlocked
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();
251 throw GnashException("Failure initializing curl share handle");
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
));
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
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.
346 case CURL_LOCK_DATA_DNS
:
347 //log_debug("Unlocking DNS cache mutex");
348 _dnscacheMutexLock
.unlock();
350 case CURL_LOCK_DATA_COOKIE
:
351 //log_debug("Unlocking cookies mutex");
352 _cookieMutexLock
.unlock();
354 case CURL_LOCK_DATA_SHARE
:
355 //log_debug("Unlocking share mutex");
356 _shareMutexLock
.unlock();
358 case CURL_LOCK_DATA_SSL_SESSION
:
359 log_error("unlockSharedHandle: SSL session locking "
362 case CURL_LOCK_DATA_CONNECT
:
363 log_error("unlockSharedHandle: connect locking unsupported");
365 case CURL_LOCK_DATA_LAST
:
366 log_error("unlockSharedHandle: last locking unsupported ?!");
369 std::cerr
<< "unlockSharedHandle: unknown shared data " <<
376 /***********************************************************************
378 * CurlStreamFile definition
380 **********************************************************************/
382 /// libcurl based IOChannel, for network uri accesses
383 class CurlStreamFile
: public IOChannel
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
396 /// The url to post to.
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
);
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 {
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;
446 void init(const std::string
& url
, const std::string
& cachefile
);
448 // Use this file to cache data
451 // _cache file descriptor
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 :)
459 // the libcurl easy handle
462 // the libcurl multi handle
465 // transfer in progress
469 // false on no error.
470 // Example of errors would be:
471 // 404 - file not found
475 // Post data. Empty if no POST has been requested
476 std::string _postdata
;
478 // Current size of cached data
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 **********************************************************************/
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",
524 CurlStreamFile
* stream
= static_cast<CurlStreamFile
*>(userp
);
525 return stream
->cache(buf
, size
* nmemb
);
531 CurlStreamFile::cache(void *from
, std::streamsize size
)
533 // take note of current position
534 long curr_pos
= std::ftell(_cache
);
537 std::fseek(_cache
, 0, SEEK_END
);
539 std::streamsize wrote
= std::fwrite(from
, 1, size
, _cache
);
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
);
560 CurlStreamFile::fillCacheNonBlocking()
564 #if GNASH_CURL_VERBOSE
565 log_debug("Not running: fillCacheNonBlocking returning");
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
));
589 CurlStreamFile::fillCache(std::streamsize size
)
592 #if GNASH_CURL_VERBOSE
593 log_debug("fillCache(%d), called, currently cached: %d", size
, _cached
);
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");
606 fd_set readfd
, writefd
, exceptfd
;
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
);
623 WallClockTimer lastProgress
; // timer since last progress
627 fillCacheNonBlocking(); // NOTE: will handle 404
629 // Do this here to avoid calling select()
630 // when we have enough bytes anyway, or
632 if (_cached
>= static_cast<size_t>(size
) || !_running
) break;
634 #if GNASH_CURL_VERBOSE
635 //log_debug("cached: %d, size: %d", _cached, size);
638 mcode
= curl_multi_fdset(_mhandle
, &readfd
, &writefd
,
641 if (mcode
!= CURLM_OK
) {
642 // This is a serious error, not just a failure to add any
644 throw GnashException(curl_multi_strerror(mcode
));
647 #ifdef GNASH_CURL_VERBOSE
648 log_debug("Max fd: %d", maxfd
);
651 // A value of -1 means no file descriptors were added.
653 #if GNASH_CURL_VERBOSE
654 log_debug("No filedescriptors; breaking");
664 tv
.tv_usec
= maxSleepUsec
;
666 #ifdef GNASH_CURL_VERBOSE
667 log_debug("select() with %d milliseconds timeout", maxSleepUsec
*1000);
670 // Wait for data on the filedescriptors until a timeout set
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__)
679 if ( errno
== EINTR
)
681 // we got interrupted 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");
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());
701 #ifdef GNASH_CURL_VERBOSE
702 log_debug("select() timed out, elapsed is %u",
703 lastProgress
.elapsed());
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 ?
715 #ifdef GNASH_CURL_VERBOSE
716 log_debug("FD activity, resetting progress timer");
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.
737 CurlStreamFile::processMessages()
741 // The number of messages left in the queue (not used by us).
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
) {
754 // Check HTTP response
755 curl_easy_getinfo(curl_msg
->easy_handle
,
756 CURLINFO_RESPONSE_CODE
, &code
);
759 log_error ("HTTP response %ld from url %s",
765 log_debug ("HTTP response %ld from url %s",
772 // Transaction failed, pass on curl error.
773 log_error("CURL: %s", curl_easy_strerror(
774 curl_msg
->data
.result
));
786 CurlStreamFile::init(const std::string
& url
, const std::string
& cachefile
)
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");
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();
814 throw GnashException("Could not create temporary cache file");
816 _cachefd
= fileno(_cache
);
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 "
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
));
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
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
));
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
));
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);
914 CurlStreamFile::CurlStreamFile(const std::string
& url
,
915 const std::string
& cachefile
)
917 log_debug("CurlStreamFile %p created", this);
918 init(url
, cachefile
);
921 CURLMcode mcode
= curl_multi_add_handle(_mhandle
, _handle
);
922 if ( mcode
!= CURLM_OK
) {
923 throw GnashException(curl_multi_strerror(mcode
));
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
);
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
));
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
);
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());
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
));
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
);
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
);
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());
1076 return std::fread(dst
, 1, bytes
, _cache
);
1082 CurlStreamFile::readNonBlocking(void *dst
, std::streamsize bytes
)
1084 #ifdef GNASH_CURL_VERBOSE
1085 log_debug ("readNonBlocking(%d) called", bytes
);
1088 if ( eof() || _error
) return 0;
1090 fillCacheNonBlocking();
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");
1099 std::streamsize actuallyRead
= std::fread(dst
, 1, bytes
, _cache
);
1102 // if we're still running drop any eof flag
1107 return actuallyRead
;
1113 CurlStreamFile::eof() const
1115 bool ret
= ( ! _running
&& feof(_cache
) );
1117 #ifdef GNASH_CURL_VERBOSE
1118 log_debug("eof() returning %d", ret
);
1126 CurlStreamFile::tell() const
1128 std::streampos ret
= std::ftell(_cache
);
1130 #ifdef GNASH_CURL_VERBOSE
1131 log_debug("tell() returning %ld", ret
);
1140 CurlStreamFile::seek(std::streampos pos
)
1145 #ifdef GNASH_CURL_WARN_SEEKSBACK
1146 if ( pos
< tell() ) {
1147 log_debug("Warning: seek backward requested (%ld from %ld)",
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
);
1162 if (std::fseek(_cache
, pos
, SEEK_SET
) == -1) {
1163 log_error("Warning: fseek failed");
1173 CurlStreamFile::go_to_end()
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
));
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");
1204 CurlStreamFile::size() const
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
);
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
1247 ////////////////////////////////////////////////////////////////
1249 // Create a fake handle for purpose of importing data
1250 CURL
* fakeHandle
= curl_easy_init(); // errors to handle here ?
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
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
);
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 ?
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
);
1342 std::auto_ptr
<IOChannel
> stream
;
1345 stream
.reset(new CurlStreamFile(url
, cachefile
));
1347 catch (const std::exception
& ex
) {
1348 log_error("curl stream: %s", ex
.what());
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
);
1361 std::auto_ptr
<IOChannel
> stream
;
1364 stream
.reset(new CurlStreamFile(url
, postdata
, cachefile
));
1366 catch (const std::exception
& ex
) {
1367 log_error("curl stream: %s", ex
.what());
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
;
1380 stream
.reset(new CurlStreamFile(url
, postdata
, headers
, cachefile
));
1382 catch (const std::exception
& ex
) {
1383 log_error("curl stream: %s", ex
.what());
1390 const NetworkAdapter::ReservedNames
&
1391 NetworkAdapter::reservedNames()
1393 static const ReservedNames names
= boost::assign::list_of
1400 ("Content-Location")
1410 ("Proxy-Authenticate")
1411 ("Proxy-Authorization")
1418 ("Transfer-Encoding")
1424 ("WWW-Authenticate");
1429 } // namespace gnash
1431 #endif // def USE_CURL
1436 // indent-tabs-mode: t