/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library 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
 * OpenSceneGraph Public License for more details.
*/

#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgDB/Registry>

#include <iostream>
#include <sstream>
#include <fstream>

#include <string.h>

#include <curl/curl.h>

#if LIBCURL_VERSION_NUM < 0x071503
    // types.h has been removed in 7.21.7 so have to protect with version guard
    // it may be possible to remove it completely but can't yet work out when
    // types.h was deprecated so will assume it's still needed in older libcurl versions
    // that OSG users are using.
    #include <curl/types.h>
#endif

#include "ReaderWriterCURL.h"

using namespace osg_curl;


//
//  StreamObject
//
EasyCurl::StreamObject::StreamObject(std::ostream* outputStream, std::istream* inputStream, const std::string& cacheFileName):
    _outputStream(outputStream),
    _inputStream(inputStream),
    _cacheFileName(cacheFileName)
{
    _foutOpened = false;
}

void EasyCurl::StreamObject::write(const char* ptr, size_t realsize)
{
    if (_outputStream) _outputStream->write(ptr, realsize);

    if (!_cacheFileName.empty())
    {
        if (!_foutOpened)
        {
            OSG_INFO<<"Writing to cache: "<<_cacheFileName<<std::endl;
            _fout.open(_cacheFileName.c_str(), std::ios::out | std::ios::binary);
            _foutOpened = true;
        }

        if (_fout)
        {
            _fout.write(ptr, realsize);
        }
    }
}

size_t EasyCurl::StreamObject::read(char* ptr, size_t maxsize)
{
    if (!_inputStream) return 0;
    _inputStream->read(ptr, maxsize);
    size_t realsize = _inputStream->gcount();
    return realsize;
}

std::string EasyCurl::getResultMimeType(const StreamObject& sp) const
{
    return sp._resultMimeType;
}

std::string EasyCurl::getMimeTypeForExtension(const std::string& ext) const
{
    const osgDB::Registry::MimeTypeExtensionMap& mimeMap = osgDB::Registry::instance()->getMimeTypeExtensionMap();
    osgDB::Registry::MimeTypeExtensionMap::const_iterator i;
    for (i = mimeMap.begin(); i != mimeMap.end(); ++i)
    {
        if (ext == i->second) return i->first;
    }
    return "application/octet-stream"; // unknown mime type
}

std::string EasyCurl::getFileNameFromURL(const std::string& url)
{
    // If the URL has query parameter "filename", return its value,
    // otherwise just return url assuming it has a filename at the end.
    // Typically, uploading will require cooperation with a server side
    // script that requires parameters such as filename and/or session
    // and/or authentication information, so in general the filename
    // can not be assumed to be at the tail of the URL.
    std::string::size_type pos = url.find('?');
    if (pos == std::string::npos) return url;
    std::string params = url.substr(pos + 1);
    const char* filenameKey = "filename=";
    pos = params.find(filenameKey);
    if (pos == std::string::npos)
    {
        // No filename param, so just chop off parameters on the url.
        return url.substr(0, url.find('?'));
    }
    std::string fileName = params.substr(pos + strlen(filenameKey));
    pos = fileName.find("&");
    if (pos != std::string::npos)
    {
        // Chop off next url parameter
        fileName = fileName.substr(0, pos);
    }
    return fileName;
}

size_t EasyCurl::StreamMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
    size_t realsize = size * nmemb;
    StreamObject* sp = (StreamObject*)data;

    sp->write((const char*)ptr, realsize);

    return realsize;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//
//  EasyCurl
//
EasyCurl::EasyCurl()
{
    OSG_INFO<<"EasyCurl::EasyCurl()"<<std::endl;

    _previousHttpAuthentication = 0;
    _connectTimeout = 0; // no timeout by default.
    _timeout = 0;
    _sslVerifyPeer = 1L;

    _curl = curl_easy_init();

    curl_easy_setopt(_curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
    curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, StreamMemoryCallback);
    curl_easy_setopt(_curl, CURLOPT_FOLLOWLOCATION, 1L);
}

EasyCurl::~EasyCurl()
{
    OSG_INFO<<"EasyCurl::~EasyCurl()"<<std::endl;

    if (_curl) curl_easy_cleanup(_curl);

    _curl = 0;
}

osgDB::ReaderWriter::ReadResult EasyCurl::read(const std::string& proxyAddress, const std::string& fileName, StreamObject& sp, const osgDB::ReaderWriter::Options *options)
{
    setOptions(proxyAddress, fileName, sp, options);

    CURLcode responseCode = curl_easy_perform(_curl);
    curl_easy_setopt(_curl, CURLOPT_WRITEDATA, (void *)0);

    return processResponse(responseCode, proxyAddress, fileName, sp);
}

osgDB::ReaderWriter::WriteResult EasyCurl::write(const std::string& proxyAddress, const std::string& fileName, StreamObject& sp, const osgDB::ReaderWriter::Options *options)
{
    setOptions(proxyAddress, fileName, sp, options);

    char* postedContent = NULL;

    // Copy data from istream into buffer.
    long contentLength = 0;
    const int bufferSize = 4096;
    while(true)
    {
        postedContent = (char *)realloc(postedContent, contentLength + bufferSize);
        size_t gotBytes = sp.read(postedContent + contentLength, bufferSize);
        if (gotBytes == 0) break;
        contentLength += gotBytes;
    };

    // Extract name and mime type of buffer to upload.
    std::string uploadFileName = getFileNameFromURL(fileName);
    std::string ext = osgDB::getLowerCaseFileExtension(uploadFileName);
    std::string mimeType = getMimeTypeForExtension(ext);

    // Construct "multipart/form-data" (RFC 1867) form elements for file upload.
    struct curl_httppost* post = NULL;
    struct curl_httppost* last = NULL;
    curl_formadd(&post, &last,
        CURLFORM_COPYNAME, "upload",
        CURLFORM_CONTENTTYPE, mimeType.c_str(),
        CURLFORM_BUFFER, uploadFileName.c_str(),
        CURLFORM_BUFFERPTR, postedContent,
        CURLFORM_BUFFERLENGTH, contentLength,
        CURLFORM_END);

    // Tell curl to use HTTP POST to send the form data.
    curl_easy_setopt(_curl, CURLOPT_HTTPPOST, post);

    CURLcode responseCode = curl_easy_perform(_curl);

    if (post) curl_formfree(post);
    if (postedContent) free(postedContent);
    curl_easy_setopt(_curl, CURLOPT_HTTPPOST, (void *)0);
    curl_easy_setopt(_curl, CURLOPT_HTTPGET, 1L);

    curl_easy_setopt(_curl, CURLOPT_WRITEDATA, (void *)0);

    if (processResponse(responseCode, proxyAddress, fileName, sp).success())
    {
        osgDB::ReaderWriter::WriteResult result(osgDB::ReaderWriter::WriteResult::FILE_SAVED);
        std::stringstream* ss = dynamic_cast<std::stringstream*>(sp._outputStream);
        if (ss)
        {
            // Put the server response in the message part of the result object.
            result.message() = ss->str();
        }
        return result;
    }
    else
    {
        return osgDB::ReaderWriter::WriteResult::ERROR_IN_WRITING_FILE;
    }
}

void EasyCurl::setOptions(const std::string& proxyAddress, const std::string& fileName, StreamObject& sp, const osgDB::ReaderWriter::Options *options)
{
    const osgDB::AuthenticationMap* authenticationMap = (options && options->getAuthenticationMap()) ?
            options->getAuthenticationMap() :
            osgDB::Registry::instance()->getAuthenticationMap();

    // Set the timeout value here:
    // Note that this has an effect only in a connection phase.
    // WARNING: here we make the assumption that if someone starts using the timeouts settings
    // he will not try to disable them afterwards (a value must be provided or the previous value is used).
    if(_connectTimeout > 0)
        curl_easy_setopt(_curl, CURLOPT_CONNECTTIMEOUT, _connectTimeout);
    if(_timeout > 0)
        curl_easy_setopt(_curl, CURLOPT_TIMEOUT, _timeout);

    if(!proxyAddress.empty())
    {
        OSG_INFO<<"Setting proxy: "<<proxyAddress<<std::endl;
        curl_easy_setopt(_curl, CURLOPT_PROXY, proxyAddress.c_str()); //Sets proxy address and port on libcurl
    }

    // setting ssl verify peer (default is enabled)
    curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, _sslVerifyPeer);

    const osgDB::AuthenticationDetails* details = authenticationMap ?
        authenticationMap->getAuthenticationDetails(fileName) :
        0;

    // configure/reset authentication if required.
    if (details)
    {
        const std::string colon(":");
        std::string password(details->username + colon + details->password);
        curl_easy_setopt(_curl, CURLOPT_USERPWD, password.c_str());
        _previousPassword = password;

        // use for https.
        // curl_easy_setopt(_curl, CURLOPT_KEYPASSWD, password.c_str());

#if LIBCURL_VERSION_NUM >= 0x070a07
        if (details->httpAuthentication != _previousHttpAuthentication)
        {
            curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, details->httpAuthentication);
            _previousHttpAuthentication = details->httpAuthentication;
        }
#endif

    }
    else
    {
        if (!_previousPassword.empty())
        {
            curl_easy_setopt(_curl, CURLOPT_USERPWD, 0);
            _previousPassword.clear();
        }

#if LIBCURL_VERSION_NUM >= 0x070a07
        // need to reset if previously set.
        if (_previousHttpAuthentication!=0)
        {
            curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, 0);
            _previousHttpAuthentication = 0;
        }
#endif
    }

    curl_easy_setopt(_curl, CURLOPT_URL, fileName.c_str());
    curl_easy_setopt(_curl, CURLOPT_WRITEDATA, (void *)&sp);
}

osgDB::ReaderWriter::ReadResult EasyCurl::processResponse(CURLcode res, const std::string& proxyAddress, const std::string& fileName, StreamObject& sp)
{
    if (res==0)
    {

#if LIBCURL_VERSION_NUM >= 0x070a07
        long code;
        if(!proxyAddress.empty())
        {
            curl_easy_getinfo(_curl, CURLINFO_HTTP_CONNECTCODE, &code);
        }
        else
        {
            curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &code);
        }

        //If the code is greater than 400, there was an error
        if (code >= 400)
        {
            osgDB::ReaderWriter::ReadResult::ReadStatus status;

            //Distinguish between a client error and a server error
            if (code < 500)
            {
                //A 400 level error indicates a client error
                status = osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
            }
            else
            {
                //A 500 level error indicates a server error
                status = osgDB::ReaderWriter::ReadResult::ERROR_IN_READING_FILE;
            }

            osgDB::ReaderWriter::ReadResult rr(status);

            //Add the error code to the ReadResult
            std::stringstream message;
            message << "error code = " << code;

            rr.message() = message.str();

            return rr;
        }
#endif

        // Store the mime-type, if any. (Note: CURL manages the buffer returned by
        // this call.)
        char* ctbuf = NULL;
        if ( curl_easy_getinfo(_curl, CURLINFO_CONTENT_TYPE, &ctbuf) == 0 && ctbuf )
        {
            sp._resultMimeType = ctbuf;
        }


        return osgDB::ReaderWriter::ReadResult::FILE_LOADED;

    }
    else
    {

#if LIBCURL_VERSION_NUM >= 0x070c00
         OSG_NOTICE<<"Error: libcurl read error, file="<<fileName<<" error = "<<curl_easy_strerror(res)<<std::endl;
#else
         OSG_NOTICE<<"Error: libcurl read error, file="<<fileName<<" error no = "<<res<<std::endl;
#endif
        return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
//
//  ReaderWriterCURL
//

ReaderWriterCURL::ReaderWriterCURL()
{
    // initialize curl to ensure it's done single threaded
    curl_global_init(CURL_GLOBAL_ALL);

    supportsProtocol("http","Read from http port using libcurl.");
    supportsProtocol("https","Read from https port using libcurl.");
    supportsProtocol("ftp","Read from ftp port using libcurl.");
    supportsProtocol("ftps","Read from ftps port using libcurl.");

    supportsExtension("curl","Pseudo file extension, used to select curl plugin.");
    supportsExtension("*","Passes all read files to other plugins to handle actual model loading.");
    supportsOption("OSG_CURL_PROXY","Specify the http proxy.");
    supportsOption("OSG_CURL_PROXYPORT","Specify the http proxy port.");
    supportsOption("OSG_CURL_CONNECTTIMEOUT","Specify the connection timeout duration in seconds [default = 0 = not set].");
    supportsOption("OSG_CURL_TIMEOUT","Specify the timeout duration of the whole transfer in seconds [default = 0 = not set].");
    supportsOption("OSG_CURL_SSL_VERIFYPEER","Specify ssl verification peer [default = 1 = set].");
}

ReaderWriterCURL::~ReaderWriterCURL()
{
    //OSG_NOTICE<<"ReaderWriterCURL::~ReaderWriterCURL()"<<std::endl;

    _threadCurlMap.clear();

    // clean up curl
    curl_global_cleanup();
}

osgDB::ReaderWriter::WriteResult ReaderWriterCURL::writeFile(const osg::Object& obj, osgDB::ReaderWriter* rw, std::ostream& fout, const osgDB::ReaderWriter::Options *options) const
{
    const osg::HeightField* heightField = dynamic_cast<const osg::HeightField*>(&obj);
    if (heightField) return rw->writeHeightField(*heightField, fout, options);

    const osg::Node* node = dynamic_cast<const osg::Node*>(&obj);
    if (node) return rw->writeNode(*node, fout, options);

    const osg::Image* image = dynamic_cast<const osg::Image*>(&obj);
    if (image) return rw->writeImage(*image, fout, options);

    return rw->writeObject(obj, fout, options);
}

osgDB::ReaderWriter::WriteResult ReaderWriterCURL::writeFile(const osg::Object& obj, const std::string& fullFileName, const Options *options) const
{
    if (!osgDB::containsServerAddress(fullFileName))
    {
        return WriteResult::FILE_NOT_HANDLED;
    }

    std::stringstream requestBuffer; // Buffer to be filled then output via http request.
    std::stringstream responseBuffer; // Buffer to contain content of http response.

    // Serialize obj into an std::stringstream buffer which will be uploaded via HTTP post request.
    std::string fileName = EasyCurl::getFileNameFromURL(fullFileName);
    std::string ext = osgDB::getLowerCaseFileExtension(fileName);
    if (ext == "curl") 
    {
        fileName = osgDB::getNameLessExtension(fileName);
        ext = osgDB::getLowerCaseFileExtension(fileName);
    }

    osgDB::ReaderWriter* writer = osgDB::Registry::instance()->getReaderWriterForExtension(ext);
    if (!writer) return WriteResult::FILE_NOT_HANDLED;
    osgDB::ReaderWriter::WriteResult result = writeFile(obj, writer, requestBuffer, options);
    if (!result.success()) return result;

    // Configure curl connection options.
    std::string proxyAddress;
    long connectTimeout = 0;
    long timeout = 0;
    long sslVerifyPeer = 1;
    getConnectionOptions(options, proxyAddress, connectTimeout, timeout, sslVerifyPeer);
    EasyCurl::StreamObject sp(&responseBuffer, &requestBuffer, std::string());
    EasyCurl& easyCurl = getEasyCurl();
    easyCurl.setConnectionTimeout(connectTimeout);
    easyCurl.setTimeout(timeout);
    easyCurl.setSSLVerifyPeer(sslVerifyPeer);

    // Output requestBuffer via curl, and return responseBuffer in message of result.
    return easyCurl.write(proxyAddress, fullFileName, sp, options);
}

osgDB::ReaderWriter::ReadResult ReaderWriterCURL::readFile(ObjectType objectType, osgDB::ReaderWriter* rw, std::istream& fin, const osgDB::ReaderWriter::Options *options) const
{
    switch(objectType)
    {
    case(OBJECT): return rw->readObject(fin,options);
    case(ARCHIVE): return rw->openArchive(fin,options);
    case(IMAGE): return rw->readImage(fin,options);
    case(HEIGHTFIELD): return rw->readHeightField(fin,options);
    case(NODE): return rw->readNode(fin,options);
    default: break;
    }
    return ReadResult::FILE_NOT_HANDLED;
}

void ReaderWriterCURL::getConnectionOptions(const osgDB::ReaderWriter::Options *options,
    std::string& proxyAddress,
    long& connectTimeout,
    long& timeout,
    long& sslVerifyPeer) const
{
    if (options)
    {
        std::istringstream iss(options->getOptionString());
        std::string opt, optProxy, optProxyPort;
        while (iss >> opt)
        {
            int index = opt.find( "=" );
            if( opt.substr( 0, index ) == "OSG_CURL_PROXY" )
                optProxy = opt.substr( index+1 );
            else if( opt.substr( 0, index ) == "OSG_CURL_PROXYPORT" )
                optProxyPort = opt.substr( index+1 );
            else if( opt.substr( 0, index ) == "OSG_CURL_CONNECTTIMEOUT" )
                connectTimeout = atol(opt.substr( index+1 ).c_str()); // this will return 0 in case of improper format.
            else if( opt.substr( 0, index ) == "OSG_CURL_TIMEOUT" )
                timeout = atol(opt.substr( index+1 ).c_str()); // this will return 0 in case of improper format.
            else if( opt.substr(0, index) == "OSG_CURL_SSL_VERIFYPEER" )
                sslVerifyPeer = atol(opt.substr( index+1 ).c_str()); // this will return 0 in case of improper format.
        }


        //Setting Proxy by OSG Options
        if(!optProxy.empty())
        {
            if(!optProxyPort.empty())
                proxyAddress = optProxy + ":" + optProxyPort;
            else
                proxyAddress = optProxy + ":8080"; //Port not found, using default
        }
    }

    const char* proxyEnvAddress = getenv("OSG_CURL_PROXY");
    if (proxyEnvAddress) //Env Proxy Settings
    {
        const char* proxyEnvPort = getenv("OSG_CURL_PROXYPORT"); //Searching Proxy Port on Env

        if(proxyEnvPort)
            proxyAddress = std::string(proxyEnvAddress) + ":" + std::string(proxyEnvPort);
        else
            proxyAddress = std::string(proxyEnvAddress) + ":8080"; //Default
    }

}

osgDB::ReaderWriter::ReadResult ReaderWriterCURL::readFile(ObjectType objectType, const std::string& fullFileName, const osgDB::ReaderWriter::Options *options) const
{
    std::string fileName(fullFileName);
    std::string ext = osgDB::getFileExtension(fullFileName);
    bool curl_ext = ext=="curl";
    if (curl_ext)
    {
        fileName = osgDB::getNameLessExtension(fullFileName);
        ext = osgDB::getFileExtension(fileName);
    }

    if (!osgDB::containsServerAddress(fileName))
    {
        if (options && !options->getDatabasePathList().empty())
        {
            if (osgDB::containsServerAddress(options->getDatabasePathList().front()))
            {
                std::string newFileName = options->getDatabasePathList().front() + "/" + fileName;

                return readFile(objectType, newFileName,options);
            }
        }

        // if user has explicitly specified curl then we don't about at this point,
        // instead assume the curl can read it any way, if it doesn't explicitly
        // specify curl then we assume that the file is a local file and not appropriate
        // for the curl plugin to load.
        if (!curl_ext) return ReadResult::FILE_NOT_HANDLED;
    }

    OSG_INFO<<"ReaderWriterCURL::readFile("<<fullFileName<<")"<<std::endl;

    std::string proxyAddress;
    long connectTimeout = 0;
    long timeout = 0;
    long sslVerifyPeer = 1;
    getConnectionOptions(options, proxyAddress, connectTimeout, timeout, sslVerifyPeer);

    bool uncompress = false;

    if (ext=="gz" || ext=="osgz" || ext=="ivez")
    {
        OSG_INFO<<"CURL: Compressed file type "<<ext<<std::endl;

        #ifndef USE_ZLIB
            // don't have zlib so can't compile compressed formats
            return ReadResult::FILE_NOT_HANDLED;
        #endif

        uncompress = true;

        if (ext=="gz")
        {
            ext = osgDB::getFileExtension(osgDB::getNameLessExtension(fileName));
        }
        else if (ext=="osgz")
        {
            ext = "osg";
        }
        else if (ext=="ivez")
        {
            ext = "ive";
        }

        OSG_INFO<<"CURL: assuming file type "<<ext<<std::endl;
    }

    std::stringstream buffer;

    EasyCurl::StreamObject sp(&buffer, NULL, std::string());
    EasyCurl& easyCurl = getEasyCurl();

    // setup the timeouts:
    easyCurl.setConnectionTimeout(connectTimeout);
    easyCurl.setTimeout(timeout);
    easyCurl.setSSLVerifyPeer(sslVerifyPeer);

    ReadResult curlResult = easyCurl.read(proxyAddress, fileName, sp, options);

    if (curlResult.status()==ReadResult::FILE_LOADED)
    {
        OSG_INFO<<"CURL: ReadResult::FILE_LOADED "<<std::endl;

        // Try to find a reader by file extension. If this fails, we will fetch the file
        // anyway and try to get a reader via mime-type.
        osgDB::ReaderWriter *reader =
            osgDB::Registry::instance()->getReaderWriterForExtension( ext );

        // If we do not already have a ReaderWriter, try to find one based on the
        // mime-type:
        if ( !reader )
        {
            std::string mimeType = easyCurl.getResultMimeType(sp);
            OSG_INFO << "CURL: Looking up extension for mime-type " << mimeType << std::endl;
            if ( mimeType.length() > 0 )
            {
                reader = osgDB::Registry::instance()->getReaderWriterForMimeType(mimeType);
            }
        }

        // If there is still no reader, fail.
        if ( !reader )
        {
            OSG_NOTICE<<"Error: No ReaderWriter for file "<<fileName<<std::endl;
            return ReadResult::FILE_NOT_HANDLED;
        }

        osg::ref_ptr<Options> local_opt = options ?
            static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) :
            new Options;

        local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName));
        local_opt->setPluginStringData("STREAM_FILENAME",osgDB::getSimpleFileName(fileName));
        local_opt->setPluginStringData("filename",fileName);

        if (uncompress)
        {
            OSG_INFO<<"Curl:: plugin uncompressing "<<fileName<<std::endl;

            std::string uncompressed;
            if (!read(buffer, uncompressed))
            {
                return ReadResult::FILE_NOT_HANDLED;
            }

            buffer.str(uncompressed);
        }

        ReadResult readResult = readFile(objectType, reader, buffer, local_opt.get() );

        local_opt->getDatabasePathList().pop_front();

        return readResult;
    }
    else
    {
        OSG_INFO<<"CURL: not loading successfully "<<std::endl;
        return curlResult;
    }
}

#ifdef USE_ZLIB

#include <zlib.h>

bool ReaderWriterCURL::read(std::istream& fin, std::string& destination) const
{
    #define CHUNK 16384

    int ret;
    unsigned have;
    z_stream strm;
    unsigned char in[CHUNK];
    unsigned char out[CHUNK];

    /* allocate inflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = 0;
    strm.next_in = Z_NULL;
    ret = inflateInit2(&strm,
                       15 + 32 // autodected zlib or gzip header
                       );
    if (ret != Z_OK)
        return false;

    /* decompress until deflate stream ends or end of file */
    do {

        strm.avail_in = fin.readsome((char*)in, CHUNK);

        if (fin.fail())
        {
            (void)inflateEnd(&strm);
            return false;
        }
        if (strm.avail_in == 0)
            break;
        strm.next_in = in;

        /* run inflate() on input until output buffer not full */
        do {
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = inflate(&strm, Z_NO_FLUSH);

            switch (ret) {
            case Z_NEED_DICT:
            case Z_DATA_ERROR:
            case Z_MEM_ERROR:
                (void)inflateEnd(&strm);
                return false;
            }
            have = CHUNK - strm.avail_out;

            destination.append((char*)out, have);

        } while (strm.avail_out == 0);

        /* done when inflate() says it's done */
    } while (ret != Z_STREAM_END);

    /* clean up and return */
    (void)inflateEnd(&strm);
    return ret == Z_STREAM_END ? true : false;
}
#else
bool ReaderWriterCURL::read(std::istream& fin, std::string& destination) const
{
    return false;
}
#endif

size_t empty_write_data(const char* /*buffer*/, size_t size, size_t nmemb, char* /*userp*/)
{
    return size*nmemb;
}

bool ReaderWriterCURL::fileExists(const std::string& filename, const osgDB::Options* options) const
{
    if (osgDB::containsServerAddress(filename))
    {
        std::string data;
        OSG_NOTICE<<"Checking if file exists using curl plugin: "<<filename<<std::endl;
        CURL* curl_handle = curl_easy_init();
        curl_easy_setopt(curl_handle, CURLOPT_URL, filename.c_str());
        curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1);
        curl_easy_setopt(curl_handle, CURLOPT_HEADER, 1);
        curl_easy_setopt(curl_handle, CURLOPT_FILETIME, 1);
        curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, &empty_write_data);

        int result = curl_easy_perform(curl_handle);

        long http_return_code(0);
        curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_return_code);

        curl_easy_cleanup(curl_handle);

        return ((result == 0) && ((http_return_code == 200) || (http_return_code == 0)));
    }
    else
    {
        return ReaderWriter::fileExists(filename, options);
    }
}


// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(curl, ReaderWriterCURL)
