root/libbase/curl_adapter.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. makeStream
  2. makeStream
  3. makeStream
  4. lock
  5. unlock
  6. getSharedHandle
  7. lockSharedHandleWrapper
  8. unlockSharedHandleWrapper
  9. get
  10. _dnscacheMutex
  11. lockSharedHandle
  12. unlockSharedHandle
  13. bad
  14. recv
  15. cache
  16. fillCacheNonBlocking
  17. fillCache
  18. processMessages
  19. init
  20. read
  21. readNonBlocking
  22. eof
  23. tell
  24. seek
  25. go_to_end
  26. size
  27. importCookies
  28. exportCookies
  29. makeStream
  30. makeStream
  31. makeStream
  32. reservedNames

// NetworkAdapter.cpp:  Interface to libcurl to read HTTP streams, for Gnash.
//
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
//   Foundation, Inc
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h"
#endif

#ifdef ANDROID
#include <sys/select.h>
#endif

#include "NetworkAdapter.h"
#include "utility.h" // UNUSED macro
#include "log.h"
#include "IOChannel.h"
#include "WallClockTimer.h"
#include "GnashSleep.h"

#include <iostream> // std::cerr

#ifndef USE_CURL

namespace gnash {

// Stub for warning about access when no libcurl is defined.

std::auto_ptr<IOChannel>
NetworkAdapter::makeStream(const std::string& /*url*/, 
        const std::string& /*cachefile*/)
{
    log_error(_("libcurl is not available, but "
                "Gnash has attempted to use the curl adapter"));
    return std::auto_ptr<IOChannel>();
}

std::auto_ptr<IOChannel>
NetworkAdapter::makeStream(const std::string& url,
        const std::string& /*postdata*/,
        const std::string& cachefile)
{
    return makeStream(url, cachefile);
}

std::auto_ptr<IOChannel>
NetworkAdapter::makeStream(const std::string& url,
           const std::string& /*postdata*/,
            const RequestHeaders& /*headers*/,
           const std::string& cachefile)
{
    return makeStream(url, cachefile);
}

} // namespace gnash

#else // def USE_CURL

extern "C" {
#include <curl/curl.h>
}

#include "utility.h"
#include "GnashException.h"
#include "rc.h"
#include "GnashSystemFDHeaders.h"

#include <map>
#include <string>
#include <sstream>
#include <cerrno>
#include <cstdio> // cached data uses a *FILE
#include <cstdlib> // std::getenv

#include <boost/version.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/thread/mutex.hpp>

#if BOOST_VERSION < 103500
# include <boost/thread/detail/lock.hpp>
#endif


//#define GNASH_CURL_VERBOSE 1

// define this if you want seeks back to be reported
//#define GNASH_CURL_WARN_SEEKSBACK 1


namespace gnash {

namespace {

inline
void lock(boost::mutex& mut)
{
#if BOOST_VERSION < 103500
    // see https://savannah.gnu.org/bugs/index.php?32579#comment7
    boost::detail::thread::lock_ops<boost::mutex>::lock(mut);
#else
    mut.lock();
#endif
}

inline
void unlock(boost::mutex& mut)
{
#if BOOST_VERSION < 103500
    // see https://savannah.gnu.org/bugs/index.php?32579#comment7
    boost::detail::thread::lock_ops<boost::mutex>::unlock(mut);
#else
    mut.unlock();
#endif
}


/***********************************************************************
 *
 *  CurlSession definition and implementation
 *
 **********************************************************************/

/// A libcurl session consists in a shared handle
/// sharing DNS cache and COOKIES.
///
class CurlSession {

public:

    /// Get CurlSession singleton
    static CurlSession& get();

    /// Get the shared handle
    CURLSH* getSharedHandle() { return _shandle; }

private:

    /// Initialize a libcurl session
    //
    /// A libcurl session consists in a shared handle
    /// sharing DNS cache and COOKIES.
    ///
    /// Also, global libcurl initialization function will
    /// be invoked, so MAKE SURE NOT TO CONSTRUCT multiple
    /// CurlSession instances in a multithreaded environment!
    ///
    /// AGAIN: this is NOT thread-safe !
    ///
    CurlSession();

    /// Cleanup curl session stuff (including global lib init)
    ~CurlSession();


    // the libcurl share handle, for sharing cookies
    CURLSH* _shandle;

    // mutex protecting share state
    boost::mutex _shareMutex;

    // mutex protecting shared cookies
    boost::mutex _cookieMutex;

    // mutex protecting shared dns cache
    boost::mutex _dnscacheMutex;

    /// Import cookies, if requested
    //
    /// This method will lookup GNASH_COOKIES_IN
    /// in the environment, and if existing, will
    /// parse the file sending each line to a fake
    /// easy handle created ad-hoc
    ///
    void importCookies();

    /// Export cookies, if requested
    //
    /// This method will lookup GNASH_COOKIES_OUT
    /// in the environment, and if existing, will
    /// create the file writing any cookie currently
    /// in the jar
    ///
    void exportCookies();

    /// Shared handle data locking function
    void lockSharedHandle(CURL* handle, curl_lock_data data,
            curl_lock_access access);

    /// Shared handle data unlocking function
    void unlockSharedHandle(CURL* handle, curl_lock_data data);

    /// Shared handle locking function
    static void lockSharedHandleWrapper(CURL* handle, curl_lock_data data,
            curl_lock_access access, void* userptr)
    {
        CurlSession* ci = static_cast<CurlSession*>(userptr);
        ci->lockSharedHandle(handle, data, access);
    }

    /// Shared handle unlocking function
    static void unlockSharedHandleWrapper(CURL* handle, curl_lock_data data,
            void* userptr)
    {
        // data defines what data libcurl wants to unlock, and you must
        // make sure that only one lock is given at any time for each kind
        // of data.
        // userptr is the pointer you set with CURLSHOPT_USERDATA. 
        CurlSession* ci = static_cast<CurlSession*>(userptr);
        ci->unlockSharedHandle(handle, data);
    }

};

CurlSession&
CurlSession::get()
{
    static CurlSession cs;
    return cs;
}

CurlSession::~CurlSession()
{
    log_debug("~CurlSession");
    exportCookies();

    CURLSHcode code;
    int retries=0;
    while ( (code=curl_share_cleanup(_shandle)) != CURLSHE_OK ) {
        if ( ++retries > 10 ) {
            log_error("Failed cleaning up share handle: %s. Giving up after "
                      "%d retries.", curl_share_strerror(code), retries);
            break;
        }
        log_error("Failed cleaning up share handle: %s. Will try again in "
                  "a second.", curl_share_strerror(code));
        gnashSleep(1000000);
    }
    _shandle = 0;
    curl_global_cleanup();
}

#if BOOST_VERSION < 103500
# define GNASH_DEFER_LOCK false
#else
# define GNASH_DEFER_LOCK boost::defer_lock
#endif

CurlSession::CurlSession()
    :
    _shandle(0),
    _shareMutex(),
    _cookieMutex(),
    _dnscacheMutex()
{
    // TODO: handle an error here (throw an exception)
    curl_global_init(CURL_GLOBAL_ALL);

    _shandle = curl_share_init();
    if (! _shandle) {
        throw GnashException("Failure initializing curl share handle");
    }

    CURLSHcode ccode;

    // Register share locking function
    ccode = curl_share_setopt(_shandle, CURLSHOPT_LOCKFUNC,
            lockSharedHandleWrapper);
    if ( ccode != CURLSHE_OK ) {
        throw GnashException(curl_share_strerror(ccode));
    }

    // Register share unlocking function
    ccode = curl_share_setopt(_shandle, CURLSHOPT_UNLOCKFUNC,
            unlockSharedHandleWrapper);
    if ( ccode != CURLSHE_OK ) {
        throw GnashException(curl_share_strerror(ccode));
    }

    // Activate sharing of cookies and DNS cache
    ccode = curl_share_setopt(_shandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
    if ( ccode != CURLSHE_OK ) {
        throw GnashException(curl_share_strerror(ccode));
    }

    // Activate sharing of DNS cache (since we're there..)
    ccode = curl_share_setopt(_shandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
    if ( ccode != CURLSHE_OK ) {
        throw GnashException(curl_share_strerror(ccode));
    }

    // Pass ourselves as the userdata
    ccode = curl_share_setopt(_shandle, CURLSHOPT_USERDATA, this);
    if ( ccode != CURLSHE_OK ) {
        throw GnashException(curl_share_strerror(ccode));
    }

    importCookies();
}

void
CurlSession::lockSharedHandle(CURL* handle, curl_lock_data data,
        curl_lock_access access)
{
    UNUSED(handle); // possibly being the 'easy' handle triggering the request ?

    // data defines what data libcurl wants to lock, and you must make
    // sure that only one lock is given at any time for each kind of data.
    // access defines what access type libcurl wants, shared or single.

    // TODO: see if we may make use of the 'access' parameter
    UNUSED(access);

    switch (data) {
    case CURL_LOCK_DATA_DNS:
        //log_debug("Locking DNS cache mutex");
        lock(_dnscacheMutex);
        //log_debug("DNS cache mutex locked");
        break;
    case CURL_LOCK_DATA_COOKIE:
        //log_debug("Locking cookies mutex");
        lock(_cookieMutex); 
        //log_debug("Cookies mutex locked");
            break;
    case CURL_LOCK_DATA_SHARE:
        //log_debug("Locking share mutex");
        lock(_shareMutex); 
        //log_debug("Share mutex locked");
        break;
    case CURL_LOCK_DATA_SSL_SESSION:
        log_error("lockSharedHandle: SSL session locking "
                  "unsupported");
        break;
    case CURL_LOCK_DATA_CONNECT:
        log_error("lockSharedHandle: connect locking unsupported");
        break;
    case CURL_LOCK_DATA_LAST:
        log_error("lockSharedHandle: last locking unsupported ?!");
        break;
    default:
        log_error("lockSharedHandle: unknown shared data %d", data);
        break;
    }
}

void
CurlSession::unlockSharedHandle(CURL* handle, curl_lock_data data)
{
    UNUSED(handle); // possibly being the 'easy' handle triggering the request ?

    // data defines what data libcurl wants to lock, and you must make
    // sure that only one lock is given at any time for each kind of data.
    switch (data) {
    case CURL_LOCK_DATA_DNS:
        //log_debug("Unlocking DNS cache mutex");
        unlock(_dnscacheMutex);
        break;
    case CURL_LOCK_DATA_COOKIE:
        //log_debug("Unlocking cookies mutex");
        unlock(_cookieMutex);
        break;
    case CURL_LOCK_DATA_SHARE:
        //log_debug("Unlocking share mutex");
        unlock(_shareMutex);
        break;
    case CURL_LOCK_DATA_SSL_SESSION:
        log_error("unlockSharedHandle: SSL session locking "
                  "unsupported");
        break;
    case CURL_LOCK_DATA_CONNECT:
        log_error("unlockSharedHandle: connect locking unsupported");
        break;
    case CURL_LOCK_DATA_LAST:
        log_error("unlockSharedHandle: last locking unsupported ?!");
        break;
    default:
        std::cerr << "unlockSharedHandle: unknown shared data " <<
            data << std::endl;
        break;
    }
}


/***********************************************************************
 *
 *  CurlStreamFile definition
 *
 **********************************************************************/

/// libcurl based IOChannel, for network uri accesses
class CurlStreamFile : public IOChannel
{

public:

    typedef std::map<std::string, std::string> PostData;

    /// Open a stream from the specified URL
    CurlStreamFile(const std::string& url, const std::string& cachefile);

    /// Open a stream from the specified URL posting the specified variables
    //
    /// @param url
    ///    The url to post to.
    ///
    /// @param vars
    ///    The url-encoded post data.
    ///
    CurlStreamFile(const std::string& url, const std::string& vars,
            const std::string& cachefile);
    
    CurlStreamFile(const std::string& url, const std::string& vars,
                   const NetworkAdapter::RequestHeaders& headers,
                   const std::string& cachefile);

    ~CurlStreamFile();

    // See dox in IOChannel.h
    virtual std::streamsize read(void *dst, std::streamsize bytes);

    // See dox in IOChannel.h
    virtual std::streamsize readNonBlocking(void *dst, std::streamsize bytes);

    // See dox in IOChannel.h
    virtual bool eof() const;

    // See dox in IOChannel.h
    virtual bool bad() const {
        return _error;
    }

    /// Report global position within the file
    virtual std::streampos tell() const;

    /// Put read pointer at given position
    virtual bool seek(std::streampos pos);

    /// Put read pointer at eof
    virtual void go_to_end();

    /// Returns the size of the stream
    //
    /// If size of the stream is unknown, 0 is returned.
    /// In that case you might try calling this function
    /// again after filling the cache a bit...
    ///
    /// Another approach might be filling the cache ourselves
    /// aiming at obtaining a useful value.
    ///
    virtual size_t size() const;

private:

    void init(const std::string& url, const std::string& cachefile);

    // Use this file to cache data
    FILE* _cache;

    // _cache file descriptor
    int _cachefd;

    // we keep a copy here to be sure the char*
    // is alive for the whole CurlStreamFile lifetime
    // TODO: don't really do this :)
    std::string _url;

    // the libcurl easy handle
    CURL *_handle;

    // the libcurl multi handle
    CURLM *_mhandle;

    // transfer in progress
    int _running;

    // stream error
    // false on no error.
    // Example of errors would be:
    //    404 - file not found
    //    timeout occurred
    bool _error;

    // Post data. Empty if no POST has been requested
    std::string _postdata;

    // Current size of cached data
    std::streampos _cached;

    /// Total stream size.
    //
    /// This will be 0 until known
    ///
    mutable size_t _size;

    // Attempt at filling the cache up to the given size.
    // Will call libcurl routines to fetch data.
    void fillCache(std::streampos size);

    // Process pending curl messages (handles 404)
    void processMessages();

    // Filling the cache as much as possible w/out blocking.
    // Will call libcurl routines to fetch data.
    void fillCacheNonBlocking();

    // Append sz bytes to the cache
    std::streamsize cache(void *from, std::streamsize sz);

    // Callback for libcurl, will be called
    // by fillCache() and will call cache()
    static size_t recv(void *buf, size_t size, size_t nmemb, void *userp);

    // List of custom headers for this stream.
    struct curl_slist *_customHeaders;
};


/***********************************************************************
 *
 *  Statics and CurlStreamFile implementation
 *
 **********************************************************************/

/*static private*/
size_t
CurlStreamFile::recv(void *buf, size_t size, size_t nmemb, void *userp)
{
#ifdef GNASH_CURL_VERBOSE
    log_debug("curl write callback called for (%d) bytes",
        size * nmemb);
#endif
    CurlStreamFile* stream = static_cast<CurlStreamFile*>(userp);
    return stream->cache(buf, size * nmemb);
}


/*private*/
std::streamsize
CurlStreamFile::cache(void *from, std::streamsize size)
{
    // take note of current position
    long curr_pos = std::ftell(_cache);

    // seek to the end
    std::fseek(_cache, 0, SEEK_END);

    std::streamsize wrote = std::fwrite(from, 1, size, _cache);
    if ( wrote < 1 ) {
        boost::format fmt = boost::format("writing to cache file: requested "
                                          "%d, wrote %d (%s)") %
                                          size % wrote % std::strerror(errno);
        throw GnashException(fmt.str());
    }

    // Set the size of cached data
    _cached = std::ftell(_cache);

    // reset position for next read
    std::fseek(_cache, curr_pos, SEEK_SET);

    return wrote;
}

/*private*/
void
CurlStreamFile::fillCacheNonBlocking()
{
    if ( ! _running ) {
#if GNASH_CURL_VERBOSE
        log_debug("Not running: fillCacheNonBlocking returning");
#endif
        return;
    }

    CURLMcode mcode;

    do {
        mcode = curl_multi_perform(_mhandle, &_running);
    } while (mcode == CURLM_CALL_MULTI_PERFORM); // && _running ?

    if (mcode != CURLM_OK) {
        throw GnashException(curl_multi_strerror(mcode));
    }

    // handle 404
    processMessages();
}


/*private*/
void
CurlStreamFile::fillCache(std::streampos size)
{

#if GNASH_CURL_VERBOSE
    log_debug("fillCache(%d), called, currently cached: %d", size, _cached);
#endif 

    assert(size >= 0);

    if ( ! _running || _cached >= size) {
#if GNASH_CURL_VERBOSE
        if (!_running) log_debug("Not running: returning");
        else log_debug("Already enough bytes cached: returning");
#endif
        return;
    }

    fd_set readfd, writefd, exceptfd;
    int maxfd;
    CURLMcode mcode;
    timeval tv;

    // Hard-coded slect timeout.
    // This number is kept low to give more thread switch
    // opportunities while waiting for a load.
    const long maxSleepUsec = 10000;  // 1/100 of a second

    const unsigned int userTimeout = static_cast<unsigned int>(
            RcInitFile::getDefaultInstance().getStreamsTimeout()*1000);

#ifdef GNASH_CURL_VERBOSE
        log_debug("User timeout is %u milliseconds", userTimeout);
#endif

    WallClockTimer lastProgress; // timer since last progress
    while (_running) {
        fillCacheNonBlocking(); // NOTE: will handle 404
        
        // Do this here to avoid calling select()
        // when we have enough bytes anyway, or
        // we reached EOF
        if (_cached >= size || !_running) break; 
        
#if GNASH_CURL_VERBOSE
        //log_debug("cached: %d, size: %d", _cached, size);
#endif
        
        // Zero these out _before_ calling curl_multi_fdset!
        FD_ZERO(&readfd);
        FD_ZERO(&writefd);
        FD_ZERO(&exceptfd);

        mcode = curl_multi_fdset(_mhandle, &readfd, &writefd, 
                &exceptfd, &maxfd);

        if (mcode != CURLM_OK) {
            // This is a serious error, not just a failure to add any
            // fds.
            throw GnashException(curl_multi_strerror(mcode));
        }

#ifdef GNASH_CURL_VERBOSE
        log_debug("Max fd: %d", maxfd);
#endif

        // A value of -1 means no file descriptors were added.
        if (maxfd < 0) {
#if GNASH_CURL_VERBOSE
            log_debug("curl_multi_fdset: maxfd == %1%", maxfd);
#endif
            // As of libcurl 7.21.x, the DNS resolving appears to be going
            // on in the background, so curl_multi_fdset fails to return
            // anything useful. So we use the user timeout value to
            // give DNS enough time to resolve the lookup.
            if (userTimeout && lastProgress.elapsed() > userTimeout) {
                log_error(_("FIXME: Timeout (%u milliseconds) while loading "
                            "from url %s"), userTimeout, _url);
                // TODO: should we set _error here ?
                return;
            } else {
                continue;
            }
        }

        tv.tv_sec = 0;
        tv.tv_usec = maxSleepUsec;

#ifdef GNASH_CURL_VERBOSE
        log_debug("select() with %d milliseconds timeout", maxSleepUsec*1000);
#endif

        // Wait for data on the filedescriptors until a timeout set
        // in gnashrc.
        int ret = select(maxfd + 1, &readfd, &writefd, &exceptfd, &tv);

// select() will always fail on OS/2 and AmigaOS4 as we can't select
// on file descriptors, only on sockets
#if !defined(__OS2__) && !defined(__amigaos4__) && !defined(WIN32)
        if ( ret == -1 ) {
            if ( errno == EINTR ) {
                // we got interrupted by a signal
                // let's consider this as a timeout
#ifdef GNASH_CURL_VERBOSE
                log_debug("select() was interrupted by a signal");
#endif
                ret = 0;
            } else {
                // something unexpected happened
                boost::format fmt = boost::format(
                    "error polling data from connection to %s: %s ")
                    % _url % strerror(errno);
                throw GnashException(fmt.str());
            }
        }
#endif
        if ( ! ret ) {
            // Timeout, check the clock to see
            // if we expired the user Timeout
#ifdef GNASH_CURL_VERBOSE
            log_debug("select() timed out, elapsed is %u",
                      lastProgress.elapsed());
#endif
            if (userTimeout && lastProgress.elapsed() > userTimeout) {
                log_error(_("Timeout (%u milliseconds) while loading "
                            "from url %s"), userTimeout, _url);
                // TODO: should we set _error here ?
                return;
            }
        } else {
            // Activity, reset the timer...
#ifdef GNASH_CURL_VERBOSE
            log_debug("FD activity, resetting progress timer");
#endif
            lastProgress.restart();
        }

    }

    // Processing messages is already done by fillCacheNonBlocking,
    // so we might likely avoid it here.
    processMessages();

}

/*private*/
void
CurlStreamFile::processMessages()
{
    CURLMsg *curl_msg;
    
    // The number of messages left in the queue (not used by us).
    int msgs;
    while ((curl_msg = curl_multi_info_read(_mhandle, &msgs))) {
        // Only for completed transactions
        if (curl_msg->msg == CURLMSG_DONE) {
            
            // HTTP transaction succeeded
            if (curl_msg->data.result == CURLE_OK) {
                
                long code;
                
                // Check HTTP response
                curl_easy_getinfo(curl_msg->easy_handle,
                                  CURLINFO_RESPONSE_CODE, &code);
                
                if ( code >= 400 ) {
                    log_error ("HTTP response %ld from url %s",
                                        code, _url);
                    _error = true;
                    _running = false;
                } else {
                    log_debug ("HTTP response %ld from url %s",
                               code, _url);
                }

            } else {
                // Transaction failed, pass on curl error.
                log_error("CURL: %s", curl_easy_strerror(
                                    curl_msg->data.result));
                _error = true;
            }

        }

    }
}


/*private*/
void
CurlStreamFile::init(const std::string& url, const std::string& cachefile)
{
    _customHeaders = 0;

    _url = url;
    _running = 1;
    _error = false;

    _cached = 0;
    _size = 0;

    _handle = curl_easy_init();
    _mhandle = curl_multi_init();

    const RcInitFile& rcfile = RcInitFile::getDefaultInstance();
    
    if (!cachefile.empty()) {
        _cache = std::fopen(cachefile.c_str(), "w+b");
        if (!_cache) {

            log_error("Could not open specified path as cache file. Using "
                    "a temporary file instead");
            _cache = std::tmpfile();
        }
    } else {
        _cache = std::tmpfile();
    }

    if ( ! _cache ) {
        throw GnashException("Could not create temporary cache file");
    }
    _cachefd = fileno(_cache);

    CURLcode ccode;

    // Override cURL's default verification of SSL certificates
    // This is insecure, so log security warning.
    // Equivalent to curl -k or curl --insecure.
    if (rcfile.insecureSSL()) {
        log_security(_("Allowing connections to SSL sites with invalid "
                       "certificates"));        
        
        ccode = curl_easy_setopt(_handle, CURLOPT_SSL_VERIFYPEER, 0);
        if ( ccode != CURLE_OK ) {
            throw GnashException(curl_easy_strerror(ccode));
        }
        
        ccode = curl_easy_setopt(_handle, CURLOPT_SSL_VERIFYHOST, 0);
        if ( ccode != CURLE_OK ) {
            throw GnashException(curl_easy_strerror(ccode));
        }
    }

    // Get shared data
    ccode = curl_easy_setopt(_handle, CURLOPT_SHARE,
            CurlSession::get().getSharedHandle());

    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // Set expiration time for DNS cache entries, in seconds
    // 0 disables caching
    // -1 makes cache entries never expire
    // default is 60
    //
    // NOTE: this snippet is here just as a placeholder for whoever
    //       will feel a need to make this parametrizable
    //
    ccode = curl_easy_setopt(_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    ccode = curl_easy_setopt(_handle, CURLOPT_USERAGENT, "Gnash-" VERSION);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

#ifdef GNASH_CURL_VERBOSE
    // for verbose operations
    ccode = curl_easy_setopt(_handle, CURLOPT_VERBOSE, 1);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }
#endif

/* from libcurl-tutorial(3)
When using multiple threads you should set the CURLOPT_NOSIGNAL  option
to TRUE for all handles. Everything will work fine except that timeouts
are not honored during the DNS lookup - which you can  work  around  by
*/
    ccode = curl_easy_setopt(_handle, CURLOPT_NOSIGNAL, true);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // set url
    ccode = curl_easy_setopt(_handle, CURLOPT_URL, _url.c_str());
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    //curl_easy_setopt(_handle, CURLOPT_NOPROGRESS, false);


    // set write data and function
    ccode = curl_easy_setopt(_handle, CURLOPT_WRITEDATA, this);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    ccode = curl_easy_setopt(_handle, CURLOPT_WRITEFUNCTION,
        CurlStreamFile::recv);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    ccode = curl_easy_setopt(_handle, CURLOPT_FOLLOWLOCATION, 1);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    //fillCache(32); // pre-cache 32 bytes
    //curl_multi_perform(_mhandle, &_running);
}

/*public*/
CurlStreamFile::CurlStreamFile(const std::string& url,
        const std::string& cachefile)
{
    log_debug("CurlStreamFile %p created", this);
    init(url, cachefile);

    // CURLMcode ret =
    CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
    if ( mcode != CURLM_OK ) {
        throw GnashException(curl_multi_strerror(mcode));
    }
}

/*public*/
CurlStreamFile::CurlStreamFile(const std::string& url, const std::string& vars,
       const std::string& cachefile)
{
    log_debug("CurlStreamFile %p created", this);
    init(url, cachefile);

    _postdata = vars;

    CURLcode ccode;

    ccode = curl_easy_setopt(_handle, CURLOPT_POST, 1);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // libcurl needs to access the POSTFIELDS during 'perform' operations,
    // so we must use a string whose lifetime is ensured to be longer then
    // the multihandle itself.
    // The _postdata member should meet this requirement
    ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDS, _postdata.c_str());
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // This is to support binary strings as postdata
    // NOTE: in version 7.11.1 CURLOPT_POSTFIELDSIZE_LARGE was added
    //       this one takes a long, that one takes a curl_off_t
    //
    ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDSIZE, _postdata.size());
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // Disable sending an Expect: header, as some older HTTP/1.1
    // don't implement them, and some (namely lighttpd/1.4.19,
    // running on openstreetmap.org at time of writing) return
    // a '417 Expectance Failure' response on getting that.
    assert ( ! _customHeaders );
    _customHeaders = curl_slist_append(_customHeaders, "Expect:");
    ccode = curl_easy_setopt(_handle, CURLOPT_HTTPHEADER, _customHeaders);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
    if ( mcode != CURLM_OK ) {
        throw GnashException(curl_multi_strerror(mcode));
    }

}

/*public*/
CurlStreamFile::CurlStreamFile(const std::string& url, const std::string& vars,
        const NetworkAdapter::RequestHeaders& headers,
        const std::string& cachefile)
{
    log_debug("CurlStreamFile %p created", this);
    init(url, cachefile);

    _postdata = vars;

    // Disable sending an Expect: header, as some older HTTP/1.1
    // don't implement them, and some (namely lighttpd/1.4.19,
    // running on openstreetmap.org at time of writing) return
    // a '417 Expectance Failure' response on getting that.
    //
    // Do this before adding user-requested headers so user
    // specified ones take precedence
    //
    assert ( ! _customHeaders );
    _customHeaders = curl_slist_append(_customHeaders, "Expect:");

    for (NetworkAdapter::RequestHeaders::const_iterator i = headers.begin(),
             e = headers.end(); i != e; ++i) {
        // Check here to see whether header name is allowed.
        if (!NetworkAdapter::isHeaderAllowed(i->first)) continue;
        std::ostringstream os;
        os << i->first << ": " << i->second;
        _customHeaders = curl_slist_append(_customHeaders, os.str().c_str());
    }

    CURLcode ccode;

    ccode = curl_easy_setopt(_handle, CURLOPT_HTTPHEADER, _customHeaders);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    ccode = curl_easy_setopt(_handle, CURLOPT_POST, 1);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // libcurl needs to access the POSTFIELDS during 'perform' operations,
    // so we must use a string whose lifetime is ensured to be longer then
    // the multihandle itself.
    // The _postdata member should meet this requirement
    ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDS, _postdata.c_str());
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // This is to support binary strings as postdata
    // NOTE: in version 7.11.1 CURLOPT_POSTFIELDSIZE_LARGE was added
    //       this one takes a long, that one takes a curl_off_t
    //
    ccode = curl_easy_setopt(_handle, CURLOPT_POSTFIELDSIZE, _postdata.size());
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    CURLMcode mcode = curl_multi_add_handle(_mhandle, _handle);
    if ( mcode != CURLM_OK ) {
        throw GnashException(curl_multi_strerror(mcode));
    }

}


/*public*/
CurlStreamFile::~CurlStreamFile()
{
    log_debug("CurlStreamFile %p deleted", this);
    curl_multi_remove_handle(_mhandle, _handle);
    curl_easy_cleanup(_handle);
    curl_multi_cleanup(_mhandle);
    std::fclose(_cache);
    if ( _customHeaders ) curl_slist_free_all(_customHeaders); 
}

/*public*/
std::streamsize
CurlStreamFile::read(void *dst, std::streamsize bytes)
{
    if ( eof() || _error ) return 0;

#ifdef GNASH_CURL_VERBOSE
    log_debug ("read(%d) called", bytes);
#endif

    fillCache(bytes + tell());
    if ( _error ) return 0; // error can be set by fillCache

#ifdef GNASH_CURL_VERBOSE
    log_debug("_cache.tell = %d", tell());
#endif

    return std::fread(dst, 1, bytes, _cache);

}

/*public*/
std::streamsize
CurlStreamFile::readNonBlocking(void *dst, std::streamsize bytes)
{
#ifdef GNASH_CURL_VERBOSE
    log_debug ("readNonBlocking(%d) called", bytes);
#endif

    if ( eof() || _error ) return 0;

    fillCacheNonBlocking();
    if ( _error ) {
        // I guess an exception would be thrown in this case ?
        log_error("curl adaptor's fillCacheNonBlocking set _error "
                  "rather then throwing an exception");
        return 0; 
    }

    std::streamsize actuallyRead = std::fread(dst, 1, bytes, _cache);
    if ( _running ) {
        // if we're still running drop any eof flag
        // on the cache
        clearerr(_cache);
    }

    return actuallyRead;

}

/*public*/
bool
CurlStreamFile::eof() const
{
    bool ret = ( ! _running && feof(_cache) );

#ifdef GNASH_CURL_VERBOSE
    log_debug("eof() returning %d", ret);
#endif
    return ret;

}

/*public*/
std::streampos
CurlStreamFile::tell() const
{
    std::streampos ret = std::ftell(_cache);

#ifdef GNASH_CURL_VERBOSE
    log_debug("tell() returning %ld", ret);
#endif

    return ret;

}

/*public*/
bool
CurlStreamFile::seek(std::streampos pos)
{

    if ( pos < 0 ) {
        std::ostringstream os;
        os << "CurlStreamFile: can't seek to negative absolute position "
           << pos;
        throw IOException(os.str());
    }

#ifdef GNASH_CURL_WARN_SEEKSBACK
    if ( pos < tell() ) {
        log_debug("Warning: seek backward requested (%ld from %ld)",
            pos, tell());
    }
#endif

    fillCache(pos);
    if (_error) return false; // error can be set by fillCache

    if (_cached < pos) {
        log_error ("Warning: could not cache enough bytes on seek: %d "
                   "requested, %d cached", pos, _cached);
        return false; 
    }

    if (std::fseek(_cache, pos, SEEK_SET) == -1) {
        log_error("Warning: fseek failed");
        return false;
    }
   
    return true;

}

/*public*/
void
CurlStreamFile::go_to_end()
{
    CURLMcode mcode;
    while (_running > 0) {
        do {
            mcode=curl_multi_perform(_mhandle, &_running);
        } while ( mcode == CURLM_CALL_MULTI_PERFORM );

        if ( mcode != CURLM_OK ) {
            throw IOException(curl_multi_strerror(mcode));
        }
        
        long code;
        curl_easy_getinfo(_handle, CURLINFO_RESPONSE_CODE, &code);
        if (code == 404) {      // file not found!
            throw IOException("File not found");
        }

    }

    if (std::fseek(_cache, 0, SEEK_END) == -1) {
        throw IOException("NetworkAdapter: fseek to end failed");
    } 
}

/*public*/
size_t
CurlStreamFile::size() const
{
    if ( ! _size ) {
        double size;
        CURLcode ret = curl_easy_getinfo(_handle,
                CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size);
        if (ret == CURLE_OK) {
            assert(size <= std::numeric_limits<size_t>::max());
            _size = static_cast<size_t>(size);
        }
    }

#ifdef GNASH_CURL_VERBOSE
    log_debug("get_stream_size() returning %lu", _size);
#endif

    return _size;

}

void
CurlSession::importCookies()
{
    const char* cookiesIn = std::getenv("GNASH_COOKIES_IN");
    if ( ! cookiesIn ) return; // nothing to do

    ////////////////////////////////////////////////////////////////
    //
    // WARNING: what we're doing here is an ugly hack
    //
    // We'll be creating a fake easy handle for the sole purpos
    // of importing cookies. Tests conducted against 7.15.5-CVS
    // resulted in this working if a CURLOPT_URL is given, even
    // if invalid (but non-0!), while wouldn't if NO CURLOPT_URL
    // is given, with both cases returning the same CURLcode on
    // _perform (URL using bad/illegal format or missing URL)
    //
    // TODO: instead, we should be reading the input file
    //       ourselves and use CURLOPT_COOKIELIST to send
    //       each line. Doing so should not require a
    //       _perform call.
    //
    ////////////////////////////////////////////////////////////////

    // Create a fake handle for purpose of importing data
    CURL* fakeHandle = curl_easy_init(); // errors to handle here ?
    CURLcode ccode;

    // Configure the fake handle to use the share (shared cookies in particular..)
    ccode = curl_easy_setopt(fakeHandle, CURLOPT_SHARE, getSharedHandle());
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // Configure the fake handle to read cookies from the specified file
    ccode = curl_easy_setopt(fakeHandle, CURLOPT_COOKIEFILE, cookiesIn);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // need to pass a non-zero URL string for COOKIEFILE to
    // be really parsed
    ccode = curl_easy_setopt(fakeHandle, CURLOPT_URL, "");
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // perform, to activate actual cookie file parsing
    //
    // NOTE: curl_easy_perform is expected to return an
    //       "URL using bad/illegal format or missing URL" error,
    //       there's no way to detect actual cookies import errors
    //       other then using curl debugging output routines
    //
    log_debug("Importing cookies from file '%s'", cookiesIn);
    curl_easy_perform(fakeHandle);

    curl_easy_cleanup(fakeHandle);

}

void
CurlSession::exportCookies()
{
    const char* cookiesOut = std::getenv("GNASH_COOKIES_OUT");
    if ( ! cookiesOut ) return; // nothing to do

    ////////////////////////////////////////////////////////////////
    //
    // WARNING: what we're doing here is an ugly hack
    //
    // We'll be creating a fake easy handle for the sole purpose
    // of exporting cookies. Tests conducted against 7.15.5-CVS
    // resulted in this working w/out a CURLOPT_URL and w/out a
    // curl_easy_perform.
    // 
    // NOTE: the "correct" way would be to use CURLOPT_COOKIELIST
    //       with the "FLUSH" special string as value, but that'd
    //       be only supported by version 7.17.1
    //
    ////////////////////////////////////////////////////////////////

    CURL* fakeHandle = curl_easy_init(); // errors to handle here ?
    CURLcode ccode;

    // Configure the fake handle to use the share (shared cookies in particular..)
    ccode = curl_easy_setopt(fakeHandle, CURLOPT_SHARE, getSharedHandle());
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // Configure the fake handle to write cookies to the specified file
    ccode = curl_easy_setopt(fakeHandle, CURLOPT_COOKIEJAR , cookiesOut);
    if ( ccode != CURLE_OK ) {
        throw GnashException(curl_easy_strerror(ccode));
    }

    // Cleanup, to trigger actual cookie file flushing
    log_debug("Exporting cookies file '%s'", cookiesOut);
    curl_easy_cleanup(fakeHandle);

}


} // anonymous namespace

//-------------------------------------------
// Exported interfaces
//-------------------------------------------

std::auto_ptr<IOChannel>
NetworkAdapter::makeStream(const std::string& url, const std::string& cachefile)
{
#ifdef GNASH_CURL_VERBOSE
    log_debug("making curl stream for %s", url);
#endif

    std::auto_ptr<IOChannel> stream;

    try {
        stream.reset(new CurlStreamFile(url, cachefile));
    }
    catch (const std::exception& ex) {
        log_error("curl stream: %s", ex.what());
    }
    return stream;
}

std::auto_ptr<IOChannel>
NetworkAdapter::makeStream(const std::string& url, const std::string& postdata,
        const std::string& cachefile)
{
#ifdef GNASH_CURL_VERBOSE
    log_debug("making curl stream for %s", url);
#endif

    std::auto_ptr<IOChannel> stream;

    try {
        stream.reset(new CurlStreamFile(url, postdata, cachefile));
    }
    catch (const std::exception& ex) {
        log_error("curl stream: %s", ex.what());
    }
    return stream;
}

std::auto_ptr<IOChannel>
NetworkAdapter::makeStream(const std::string& url, const std::string& postdata,
        const RequestHeaders& headers, const std::string& cachefile)
{

    std::auto_ptr<IOChannel> stream;

    try {
        stream.reset(new CurlStreamFile(url, postdata, headers, cachefile));
    }
    catch (const std::exception& ex) {
        log_error("curl stream: %s", ex.what());
    }

    return stream;

}

const NetworkAdapter::ReservedNames&
NetworkAdapter::reservedNames()
{
    static const ReservedNames names = boost::assign::list_of
    ("Accept-Ranges")
    ("Age")
    ("Allow")
    ("Allowed")
    ("Connection")
    ("Content-Length")
    ("Content-Location")
    ("Content-Range")
    ("ETag")
    ("GET")
    ("Host")
    ("HEAD")
    ("Last-Modified")
    ("Locations")
    ("Max-Forwards")
    ("POST")
    ("Proxy-Authenticate")
    ("Proxy-Authorization")
    ("Public")
    ("Range")
    ("Retry-After")
    ("Server")
    ("TE")
    ("Trailer")
    ("Transfer-Encoding")
    ("Upgrade")
    ("URI")
    ("Vary")
    ("Via")
    ("Warning")
    ("WWW-Authenticate");

    return names;
}

} // namespace gnash

#endif // def USE_CURL


// Local Variables:
// mode: C++
// indent-tabs-mode: null
// End:

/* [<][>][^][v][top][bottom][index][help] */