/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * $Id$
 */


#include <xercesc/util/NetAccessors/WinSock/BinHTTPURLInputStream.hpp>
#include <windows.h>

#ifdef WITH_IPV6
#include <ws2tcpip.h>
#endif

#include <stdio.h>
#include <string.h>

#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XMLNetAccessor.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/XMLExceptMsgs.hpp>
#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/XMLUniDefs.hpp>
#include <xercesc/util/Mutexes.hpp>

XERCES_CPP_NAMESPACE_BEGIN

typedef u_short (WSAAPI * LPFN_HTONS)(u_short hostshort);
typedef SOCKET (WSAAPI * LPFN_SOCKET)(int af, int type, int protocol);
typedef int (WSAAPI * LPFN_CONNECT)(SOCKET s, const struct sockaddr* name, int namelen);
typedef int (WSAAPI * LPFN_SEND)(SOCKET s, const char* buf, int len, int flags);
typedef int (WSAAPI * LPFN_RECV)(SOCKET s, char* buf, int len, int flags);
typedef int (WSAAPI * LPFN_SHUTDOWN)(SOCKET s, int how);
typedef int (WSAAPI * LPFN_CLOSESOCKET)(SOCKET s);
typedef int (WSAAPI * LPFN_WSACLEANUP)();
typedef int (WSAAPI * LPFN_WSASTARTUP)(WORD wVersionRequested, LPWSADATA lpWSAData);
#ifdef WITH_IPV6
typedef int (WSAAPI * LPFN_GETADDRINFO)(const char* nodename, const char * servname, const struct addrinfo * hints, struct addrinfo ** res);
typedef void (WSAAPI * LPFN_FREEADDRINFO)(struct addrinfo * ai);
#else
typedef struct hostent *(WSAAPI * LPFN_GETHOSTBYNAME)(const char* name);
typedef struct hostent *(WSAAPI * LPFN_GETHOSTBYADDR)(const char* addr, int len, int type);
typedef unsigned long (WSAAPI * LPFN_INET_ADDR)(const char* cp);
#endif

static HMODULE gWinsockLib = NULL;
static LPFN_HTONS gWShtons = NULL;
static LPFN_SOCKET gWSsocket = NULL;
static LPFN_CONNECT gWSconnect = NULL;
static LPFN_SEND gWSsend = NULL;
static LPFN_RECV gWSrecv = NULL;
static LPFN_SHUTDOWN gWSshutdown = NULL;
static LPFN_CLOSESOCKET gWSclosesocket = NULL;
static LPFN_WSACLEANUP gWSACleanup = NULL;
#ifdef WITH_IPV6
static LPFN_GETADDRINFO gWSgetaddrinfo = NULL;
static LPFN_FREEADDRINFO gWSfreeaddrinfo = NULL;
#else
static LPFN_GETHOSTBYNAME gWSgethostbyname = NULL;
static LPFN_GETHOSTBYADDR gWSgethostbyaddr = NULL;
static LPFN_INET_ADDR gWSinet_addr = NULL;
#endif

static u_short wrap_htons(u_short hostshort)
{
    return (*gWShtons)(hostshort);
}

static SOCKET wrap_socket(int af,int type,int protocol)
{
    return (*gWSsocket)(af,type,protocol);
}

static int wrap_connect(SOCKET s,const struct sockaddr* name,int namelen)
{
    return (*gWSconnect)(s,name,namelen);
}

static int wrap_send(SOCKET s,const char* buf,int len,int flags)
{
    return (*gWSsend)(s,buf,len,flags);
}

static int wrap_recv(SOCKET s,char* buf,int len,int flags)
{
    return (*gWSrecv)(s,buf,len,flags);
}

static int wrap_shutdown(SOCKET s,int how)
{
    return (*gWSshutdown)(s,how);
}

static int wrap_closesocket(SOCKET socket)
{
    return (*gWSclosesocket)(socket);
}

#ifdef WITH_IPV6
static int wrap_getaddrinfo(const char* nodename,const char* servname,const struct addrinfo* hints,struct addrinfo** res)
{
   return (*gWSgetaddrinfo)(nodename,servname,hints,res);
}

static void wrap_freeaddrinfo(struct addrinfo* ai)
{
    (*gWSfreeaddrinfo)(ai);
}
#else
static struct hostent* wrap_gethostbyname(const char* name)
{
    return (*gWSgethostbyname)(name);
}

static struct hostent* wrap_gethostbyaddr(const char* addr,int len,int type)
{
    return (*gWSgethostbyaddr)(addr,len,type);
}

static unsigned long wrap_inet_addr(const char* cp)
{
    return (*gWSinet_addr)(cp);
}

#endif


class SocketJanitor
{
public:
    // -----------------------------------------------------------------------
    //  Constructors and Destructor
    // -----------------------------------------------------------------------
    SocketJanitor(SOCKET* toDelete) : fData(toDelete) {}
    ~SocketJanitor() { reset(); }

    SOCKET* get() const { return fData; }
    SOCKET* release() {    SOCKET* p = fData; fData = 0; return p; }

    void reset(SOCKET* p = 0)
    {
        if(fData) {
            wrap_shutdown(*fData, SD_BOTH);
            wrap_closesocket(*fData);
        }
        fData = p;
    }
    bool isDataNull() { return (fData == 0); }

private :
    // -----------------------------------------------------------------------
    //  Unimplemented constructors and operators
    // -----------------------------------------------------------------------
    SocketJanitor();
    SocketJanitor(const SocketJanitor&);
    SocketJanitor& operator=(const SocketJanitor&);

    // -----------------------------------------------------------------------
    //  Private data members
    //
    //  fData
    //      This is the pointer to the socket that must be closed when
    //      this object is destroyed.
    // -----------------------------------------------------------------------
    SOCKET*  fData;
};

bool BinHTTPURLInputStream::fInitialized = false;

void BinHTTPURLInputStream::Initialize(MemoryManager* const manager)
{
    //
    // Initialize the WinSock library here.
    //
    WORD        wVersionRequested;
    WSADATA     wsaData;

    LPFN_WSASTARTUP startup = NULL;
    if(gWinsockLib == NULL)
    {
#ifdef WITH_IPV6
      gWinsockLib = LoadLibraryA("WS2_32");
#else
      gWinsockLib = LoadLibraryA("WSOCK32");
#endif
      if(gWinsockLib == NULL)
      {
          ThrowXMLwithMemMgr(NetAccessorException, XMLExcepts::NetAcc_InitFailed, manager);
      }
      else
      {
          startup = (LPFN_WSASTARTUP) GetProcAddress(gWinsockLib,"WSAStartup");
          gWSACleanup = (LPFN_WSACLEANUP) GetProcAddress(gWinsockLib,"WSACleanup");
          gWShtons = (LPFN_HTONS) GetProcAddress(gWinsockLib,"htons");
          gWSsocket = (LPFN_SOCKET) GetProcAddress(gWinsockLib,"socket");
          gWSconnect = (LPFN_CONNECT) GetProcAddress(gWinsockLib,"connect");
          gWSsend = (LPFN_SEND) GetProcAddress(gWinsockLib,"send");
          gWSrecv = (LPFN_RECV) GetProcAddress(gWinsockLib,"recv");
          gWSshutdown = (LPFN_SHUTDOWN) GetProcAddress(gWinsockLib,"shutdown");
          gWSclosesocket = (LPFN_CLOSESOCKET) GetProcAddress(gWinsockLib,"closesocket");
#ifdef WITH_IPV6
          gWSgetaddrinfo = (LPFN_GETADDRINFO) GetProcAddress(gWinsockLib,"getaddrinfo");
          gWSfreeaddrinfo = (LPFN_FREEADDRINFO) GetProcAddress(gWinsockLib,"freeaddrinfo");
#else
          gWSgethostbyname = (LPFN_GETHOSTBYNAME) GetProcAddress(gWinsockLib,"gethostbyname");
          gWSgethostbyaddr = (LPFN_GETHOSTBYADDR) GetProcAddress(gWinsockLib,"gethostbyaddr");
          gWSinet_addr = (LPFN_INET_ADDR) GetProcAddress(gWinsockLib,"inet_addr");
#endif

          if(startup == NULL
             || gWSACleanup == NULL
             || gWShtons == NULL
             || gWSsocket == NULL
             || gWSconnect == NULL
             || gWSsend == NULL
             || gWSrecv == NULL
             || gWSshutdown == NULL
             || gWSclosesocket == NULL
#ifdef WITH_IPV6
             || gWSgetaddrinfo == NULL
             || gWSfreeaddrinfo == NULL
#else
             || gWSgethostbyname == NULL
             || gWSgethostbyaddr == NULL
             || gWSinet_addr == NULL
#endif
          )
          {
              gWSACleanup = NULL;
              Cleanup();
              ThrowXMLwithMemMgr(NetAccessorException, XMLExcepts::NetAcc_InitFailed, manager);
          }
      }
    }

    wVersionRequested = MAKEWORD( 2, 2 );

    int err = (*startup)(wVersionRequested, &wsaData);
    if (err != 0)
    {
        // Call WSAGetLastError() to get the last error.
        ThrowXMLwithMemMgr(NetAccessorException, XMLExcepts::NetAcc_InitFailed, manager);
    }
    fInitialized = true;
}

void BinHTTPURLInputStream::Cleanup()
{
    XMLMutexLock lock(XMLPlatformUtils::fgAtomicMutex);

    if(fInitialized)
    {
        if(gWSACleanup) (*gWSACleanup)();
        gWSACleanup = NULL;
        FreeLibrary(gWinsockLib);
        gWinsockLib = NULL;
        gWShtons = NULL;
        gWSsocket = NULL;
        gWSconnect = NULL;
        gWSsend = NULL;
        gWSrecv = NULL;
        gWSshutdown = NULL;
        gWSclosesocket = NULL;
#ifdef WITH_IPV6
        gWSgetaddrinfo = NULL;
        gWSfreeaddrinfo = NULL;
#else
        gWSgethostbyname = NULL;
        gWSgethostbyaddr = NULL;
        gWSinet_addr = NULL;
#endif

        fInitialized = false;
    }
}

BinHTTPURLInputStream::BinHTTPURLInputStream(const XMLURL& urlSource, const XMLNetHTTPInfo* httpInfo /*=0*/)
      : BinHTTPInputStreamCommon(urlSource.getMemoryManager())
      , fSocketHandle(0)
{
    MemoryManager *memoryManager = urlSource.getMemoryManager();

    // Check if we need to load the winsock library. While locking the
    // mutex every time may be somewhat slow, we don't care in this
    // particular case since the next operation will most likely be
    // the network access which is a lot slower.
    //
    {
        XMLMutexLock lock(XMLPlatformUtils::fgAtomicMutex);

        if (!fInitialized)
          Initialize(memoryManager);
    }

    //
    // Pull all of the parts of the URL out of th urlSource object, and transcode them
    //   and transcode them back to ASCII.
    //
    const XMLCh*        hostName = urlSource.getHost();

    if (hostName == 0)
      ThrowXMLwithMemMgr1(NetAccessorException,
                          XMLExcepts::File_CouldNotOpenFile,
                          urlSource.getURLText(),
                          memoryManager);

    char*               hostNameAsCharStar = XMLString::transcode(hostName, memoryManager);
    ArrayJanitor<char>  janHostNameAsCharStar(hostNameAsCharStar, memoryManager);

    XMLURL url(urlSource);
    int redirectCount = 0;
    SocketJanitor janSock(0);

    do {
        //
        // Set up a socket.
        //

#ifdef WITH_IPV6
        struct addrinfo hints, *res, *ai;

        CharBuffer portBuffer(10, memoryManager);
        portBuffer.appendDecimalNumber(url.getPortNum());

        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = PF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        int n = wrap_getaddrinfo(hostNameAsCharStar,portBuffer.getRawBuffer(),&hints, &res);
        if(n != 0)
        {
            hints.ai_flags = AI_NUMERICHOST;
            n = wrap_getaddrinfo(hostNameAsCharStar,(const char*)tempbuf,&hints, &res);
            if(n != 0)
                ThrowXMLwithMemMgr1(NetAccessorException, XMLExcepts::NetAcc_TargetResolution, hostName, memoryManager);
        }
        janSock.reset();
        for (ai = res; ai != NULL; ai = ai->ai_next) {
            // Open a socket with the correct address family for this address.
            fSocketHandle = wrap_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
            if (fSocketHandle == INVALID_SOCKET)
                continue;
            janSock.reset(&fSocketHandle);
            if (wrap_connect(fSocketHandle, ai->ai_addr, (int)ai->ai_addrlen) == SOCKET_ERROR)
            {
                wrap_freeaddrinfo(res);
                // Call WSAGetLastError() to get the error number.
                ThrowXMLwithMemMgr1(NetAccessorException,
                         XMLExcepts::NetAcc_ConnSocket, url.getURLText(), memoryManager);
            }
            break;
        }
        wrap_freeaddrinfo(res);
        if (fSocketHandle == INVALID_SOCKET)
        {
            // Call WSAGetLastError() to get the error number.
            ThrowXMLwithMemMgr1(NetAccessorException,
                     XMLExcepts::NetAcc_CreateSocket, url.getURLText(), memoryManager);
        }
#else
        struct hostent*     hostEntPtr = 0;
        struct sockaddr_in  sa;


        if ((hostEntPtr = wrap_gethostbyname(hostNameAsCharStar)) == NULL)
        {
            unsigned long  numAddress = wrap_inet_addr(hostNameAsCharStar);
            if (numAddress == INADDR_NONE)
            {
                // Call WSAGetLastError() to get the error number.
                ThrowXMLwithMemMgr1(NetAccessorException,
                    XMLExcepts::NetAcc_TargetResolution, hostName, memoryManager);
            }
            if ((hostEntPtr =
                    wrap_gethostbyaddr((const char *) &numAddress,
                        sizeof(unsigned long), AF_INET)) == NULL)
            {
                // Call WSAGetLastError() to get the error number.
                ThrowXMLwithMemMgr1(NetAccessorException,
                    XMLExcepts::NetAcc_TargetResolution, hostName, memoryManager);
            }
        }

        memcpy((void *) &sa.sin_addr,
            (const void *) hostEntPtr->h_addr, hostEntPtr->h_length);
        sa.sin_family = hostEntPtr->h_addrtype;
        sa.sin_port = wrap_htons((unsigned short)url.getPortNum());

        janSock.reset();
        fSocketHandle = wrap_socket(hostEntPtr->h_addrtype, SOCK_STREAM, 0);
        if (fSocketHandle == INVALID_SOCKET)
        {
            // Call WSAGetLastError() to get the error number.
            ThrowXMLwithMemMgr1(NetAccessorException,
                XMLExcepts::NetAcc_CreateSocket, url.getURLText(), memoryManager);
        }
        janSock.reset(&fSocketHandle);

        if (wrap_connect(fSocketHandle, (struct sockaddr *) &sa, sizeof(sa)) == SOCKET_ERROR)
        {
            // Call WSAGetLastError() to get the error number.
            ThrowXMLwithMemMgr1(NetAccessorException,
                XMLExcepts::NetAcc_ConnSocket, url.getURLText(), memoryManager);
        }

#endif

        int status = sendRequest(url, httpInfo);

        if(status == 200) {
            // HTTP 200 OK response means we're done.
            // We're done
            break;
        }
        // a 3xx response means there was an HTTP redirect
        else if(status >= 300 && status <= 307) {
            redirectCount++;

            XMLCh *newURLString = findHeader("Location");
            ArrayJanitor<XMLCh> janNewURLString(newURLString, memoryManager);

            if(newURLString == 0 || *newURLString == 0) {
                ThrowXMLwithMemMgr1(NetAccessorException, XMLExcepts::File_CouldNotOpenFile, url.getURLText(), memoryManager);
            }

            XMLURL newURL(memoryManager);
            newURL.setURL(url, newURLString);
            if(newURL.getProtocol() != XMLURL::HTTP) {
                ThrowXMLwithMemMgr1(NetAccessorException, XMLExcepts::File_CouldNotOpenFile, newURL.getURLText(), memoryManager);
            }

            url = newURL;
            hostName = newURL.getHost();

            if (hostName == 0)
              ThrowXMLwithMemMgr1(NetAccessorException,
                                  XMLExcepts::File_CouldNotOpenFile,
                                  newURL.getURLText(),
                                  memoryManager);

            janHostNameAsCharStar.release();
            hostNameAsCharStar = XMLString::transcode(hostName, memoryManager);
            janHostNameAsCharStar.reset(hostNameAsCharStar, memoryManager);
        }
        else {
            // Most likely a 404 Not Found error.
            ThrowXMLwithMemMgr1(NetAccessorException, XMLExcepts::File_CouldNotOpenFile, url.getURLText(), memoryManager);
        }
    } while(redirectCount < 6);

    janSock.release();
}

BinHTTPURLInputStream::~BinHTTPURLInputStream()
{
    wrap_shutdown(fSocketHandle, SD_BOTH);
    wrap_closesocket(fSocketHandle);
}

bool BinHTTPURLInputStream::send(const char *buf, XMLSize_t len)
{
    XMLSize_t done = 0;
    int ret;

    while(done < len) {
        ret = wrap_send(fSocketHandle, buf + done, (int)(len - done), 0);
        if(ret == SOCKET_ERROR) return false;
        done += ret;
    }

    return true;
}

int BinHTTPURLInputStream::receive(char *buf, XMLSize_t len)
{
    int iLen = wrap_recv(fSocketHandle, buf, (int)len, 0);
    if (iLen == SOCKET_ERROR) return -1;
    return iLen;
}

XERCES_CPP_NAMESPACE_END
