/* bzflag
 * Copyright (c) 1993-2025 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// common-interface headers
#include "global.h"

#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <sstream>
#include <time.h>
#include <cstdlib>

// implementation wrapers for all the bzf_ API functions
#include "bzfsHTTPAPI.h"
#include "WorldEventManager.h"
#include "TextUtils.h"
#include "TimeKeeper.h"
#include "base64.h"
#include "Permissions.h"

// only include this if we're going to need plugins
#ifdef BZ_PLUGINS
#include "bzfsPlugins.h"
#endif

std::string ServerVersion;
std::string ServerHostname;
std::string ServerPort;
std::string ServerHostPort;
std::string BaseURL;

#ifdef _WIN32
#define _DirDelim '\\'
#else
#define _DirDelim '/'
#endif

#define SESSION_COOKIE "BZFS_SESSION_ID"

std::string trimLeadingWhitespace(const char* t)
{
    std::string text;
    while (*t)
    {
        if (!TextUtils::isWhitespace(*t))
        {
            text = t;
            break;
        }
        t++;
    }
    return text;
}

// ensures all the delims are constant
std::string convertPathToDelims(const char* file)
{
    if (!file)
        return std::string();

    std::string delim;
    delim += _DirDelim;
    return TextUtils::replace_all(TextUtils::replace_all(file,"/",delim),"\\",delim);
}

std::string getPathForOS(const char* file)
{
    return convertPathToDelims(file);
}

std::string concatPaths ( const char* path1, const char* path2 )
{
    std::string ret = getPathForOS(path1);
    ret += getPathForOS(path2);

    return ret;
}

bool fileExits ( const char* c)
{
    if (!c)
        return false;

    FILE* fp = fopen(c,"rb");
    if (!fp)
        return false;
    fclose(fp);
    return true;
}

bzhttp_eRequestType LineIsHTTPRequest ( const std::string & str )
{
    if (str == "GET")
        return eHTTPGet;
    else  if (str == "POST")
        return eHTTPPost;
    else if (str == "HEAD")
        return eHTTPHead;
    else if (str == "PUT")
        return eHTTPPut;
    else if (str == "DELETE")
        return eHTTPDelete;
    else if (str == "TRACE")
        return eHTTPTrace;
    else if (str == "OPTIONS")
        return eHTTPOptions;
    else  if (str == "CONNECT")
        return eHTTPConnect;

    return eHTTPUnknown;
}

//-- session data

class bzhttp_SessionData_Data
{
public:
    std::map<std::string,std::string> GlobalData;
    std::map<std::string,std::map<std::string,std::string> > PrivateData;
    std::string CurrentVdir;
    double LastUpdateTime;

    std::string IP;

    bzhttp_SessionData_Data()
    {
        LastUpdateTime = 0;
    }
};

#define VDIR_SESSION_PTR (static_cast<bzhttp_SessionData_Data*>(pimple))
#define VDIR_SESSION(n) bzhttp_SessionData_Data *n = VDIR_SESSION_PTR
#define VDIR_SESSION_CLASS(c,n) bzhttp_SessionData_Data *n = ((bzhttp_SessionData_Data*)(c))

bzhttp_SessionData::bzhttp_SessionData()
{
    pimple = new bzhttp_SessionData_Data();

    VDIR_SESSION(d);
    d->PrivateData[std::string("")] = std::map<std::string,std::string>();
}

bzhttp_SessionData::~bzhttp_SessionData()
{
    delete VDIR_SESSION_PTR;
}
std::map<std::string,std::string> emptyMap;

std::map<std::string,std::string> &GetPrivateData ( bzhttp_SessionData_Data * data )
{
    if (!data || data->CurrentVdir == "" || data->PrivateData.find(data->CurrentVdir) == data->PrivateData.end())
        return emptyMap;

    return data->PrivateData[data->CurrentVdir];
}

std::map<std::string,std::string> & GetServerSessionData ( bzhttp_SessionData &d )
{
    VDIR_SESSION_CLASS(d.pimple,p);
    return p->PrivateData[std::string("")];
}

const char* bzhttp_SessionData::GetPrivateItem ( const char* name )
{
    std::map<std::string,std::string> &items = GetPrivateData((bzhttp_SessionData_Data *)pimple);

    std::map<std::string,std::string>::iterator itr = items.find(TextUtils::toupper(name));
    if (itr != items.end())
        return itr->second.c_str();
    return NULL;
}

void bzhttp_SessionData::SetPrivateItem ( const char * n, const char* value )
{
    std::map<std::string,std::string> &items = GetPrivateData((bzhttp_SessionData_Data *)pimple);
    std::string name = TextUtils::toupper(n);

    std::map<std::string,std::string>::iterator itr = items.find(name);
    if (itr != items.end())
        return;

    items[name] = std::string(value);
}

void bzhttp_SessionData::ClearPrivateItem ( const char * name )
{
    std::map<std::string,std::string> &items = GetPrivateData((bzhttp_SessionData_Data *)pimple);
    std::map<std::string,std::string>::iterator itr = items.find(TextUtils::toupper(name));
    if (itr != items.end())
        items.erase(itr);
}

void bzhttp_SessionData::FlushPrivateItems ( void )
{
    std::map<std::string,std::string> &items = GetPrivateData((bzhttp_SessionData_Data *)pimple);
    items.clear();
}

const char* bzhttp_SessionData::GetGlobalItem ( const char* name )
{
    VDIR_SESSION(data);
    std::map<std::string,std::string>::iterator itr = data->GlobalData.find(TextUtils::toupper(name));
    if (itr != data->GlobalData.end())
        return itr->second.c_str();
    return NULL;
}

void bzhttp_SessionData::SetGlobalItem ( const char * n, const char* value )
{
    VDIR_SESSION(data);
    std::string name = TextUtils::toupper(n);

    std::map<std::string,std::string>::iterator itr = data->GlobalData.find(name);
    if (itr != data->GlobalData.end())
        return;

    data->GlobalData[name] = std::string(value);
}

void bzhttp_SessionData::ClearGlobalItem ( const char * name )
{
    VDIR_SESSION(data);
    std::map<std::string,std::string>::iterator itr = data->GlobalData.find(TextUtils::toupper(name));
    if (itr != data->GlobalData.end())
        data->GlobalData.erase(itr);
}

std::map<int,bzhttp_SessionData*> Sessions;

bzhttp_SessionData& GetSession ( unsigned int id, const char* ip )
{
    if (id < 10 || Sessions.find(id) == Sessions.end())
    {
        bzhttp_SessionData *newSession = new bzhttp_SessionData();
        unsigned int newID = rand();
        while (Sessions.find(newID) != Sessions.end() && newID < 10)
            newID = rand();

        newSession->SessionID = newID;
        VDIR_SESSION_CLASS(newSession->pimple,data);
        data->LastUpdateTime = TimeKeeper::getCurrent().getSeconds();
        data->IP = ip;
        Sessions[newID] = newSession;

        return *Sessions[newID];
    }
    else
    {
        VDIR_SESSION_CLASS(Sessions[id]->pimple,data);
        data->LastUpdateTime = TimeKeeper::getCurrent().getSeconds();
        data->IP = ip;
        return *Sessions[id];
    }
}

bzhttp_SessionData& GetSessionByID ( const char *_id, const char* ip )
{
    if (!_id || !ip)
        return GetSession(0,ip);

    unsigned int id = atoi(_id);

    if (id < 10 || Sessions.find(id) == Sessions.end())
        return GetSession(0,ip);

    VDIR_SESSION_CLASS(Sessions[id]->pimple,data);
    if (data->IP == ip)
    {
        data->LastUpdateTime = TimeKeeper::getCurrent().getSeconds();
        data->IP = ip;
        return *Sessions[id];
    }
    return GetSession(0,ip);
}


//----bzhttp_VDir

class bzhttp_VDir_Data
{
public:
    std::map<std::string,std::string> MimeTypes;
};

#define VDIR_DATA_PTR ((bzhttp_VDir_Data*)pimple)
#define VDIR_DATA(n) bzhttp_VDir_Data *n = ((bzhttp_VDir_Data*)pimple)
#define VDIR_DATA_CLASS(c,n) bzhttp_VDir_Data *n = ((bzhttp_VDir_Data*)(c))

bzhttp_VDir::bzhttp_VDir()
{
    pimple = new bzhttp_VDir_Data;
    RequiredAuthentiction = eNoAuth;
    CacheAuthentication = false;
    MaxRequestSize = -1;
    MaxRequestBody = -1;
}

bzhttp_VDir::~bzhttp_VDir()
{
    delete(VDIR_DATA_PTR);
}

void bzhttp_VDir::AddMimeType(const char* e, const char* m )
{
    VDIR_DATA(data);

    std::string ext = TextUtils::toupper(e);

    if (data->MimeTypes.find(ext) == data->MimeTypes.end())
        data->MimeTypes[ext] = std::string(m);
}

void bzhttp_VDir::AddStandardTypes ()
{
    AddMimeType("htm","text/html");
    AddMimeType("html","text/html");
    AddMimeType("txt","text/plain");
    AddMimeType("png","image/png");
    AddMimeType("ico","image/vnd.microsoft.icon");
    AddMimeType("jpg","image/jpeg");
    AddMimeType("jpeg","image/jpeg");
    AddMimeType("gif","image/gif");
    AddMimeType("js","application/javascript");
    AddMimeType("json","application/json");
}

//----VDir
class VDir
{
public:
    bz_Plugin *plugin;
    bzhttp_VDir *vdir;
    std::string name;
};

std::map<std::string,VDir> VDirs;

BZF_API bool bzhttp_RegisterVDir (bz_Plugin* plugin, bzhttp_VDir *vdir )
{
    if (!plugin || !vdir)
        return false;
    VDir dir;

    dir.name =  TextUtils::toupper(vdir->VDirName());
    if (VDirs.find(dir.name) != VDirs.end())
        return false;

    dir.plugin = plugin;
    dir.vdir = vdir;

    std::string vdirURL = BaseURL + vdir->VDirName();
    vdirURL += "/";

    vdir->BaseURL = vdirURL;

    VDirs[dir.name] = dir;
    return true;
}

BZF_API bool bzhttp_RemoveVDir (bz_Plugin* plugin, bzhttp_VDir *vdir )
{
    if (!plugin || !vdir)
        return false;
    VDir dir;

    std::map<std::string,VDir>::iterator itr = VDirs.find(dir.name);
    if (itr == VDirs.end())
        return false;

    if (itr->second.plugin != plugin)
        return false;

    VDirs.erase(itr);
    return true;
}

BZF_API bool bzhttp_RemoveAllVdirs (bz_Plugin* plugin )
{
    std::vector<std::string> names;
    std::map<std::string,VDir>::iterator itr = VDirs.begin();
    while (itr != VDirs.end())
    {
        if (itr->second.plugin == plugin)
            names.push_back(itr->second.name);
    }

    if (names.empty())
        return false;

    for ( size_t i = 0; i < names.size(); i++)
        VDirs.erase(VDirs.find(names[i]));

    return true;
}

void NewHTTPConnection ( bz_EventData *eventData );
void CheckForZombies  ( void );

class ConnectionEvent : public bz_EventHandler
{
public:
    ConnectionEvent()
    {
        plugin = NULL;
    }
    virtual void process ( bz_EventData *eventData )
    {
        if (eventData->eventType == bz_eTickEvent)
            CheckForZombies();
        else
            NewHTTPConnection(eventData);
    }
};

class TickEvent : public bz_EventHandler
{
public:
    TickEvent()
    {
        plugin = NULL;
    }
    virtual void process (bz_EventData *)
    {
        CheckForZombies();
    }
};

ConnectionEvent *con = NULL;
TickEvent *tick = NULL;

//---- Response----
class bzhttp_Response_Data
{
public:
    std::map<std::string,std::string> Headers;
    std::map<std::string,std::string> Cookies;
    std::string Body;
};

#define RESPONSE_DATA_PTR ((bzhttp_Response_Data*)pimple)
#define RESPONSE_DATA(n) bzhttp_Response_Data *n = ((bzhttp_Response_Data*)pimple)
#define RESPONSE_DATA_CLASS(c,n) bzhttp_Response_Data *n = ((bzhttp_Response_Data*)(c))

bzhttp_Response::bzhttp_Response()
{
    Version = 1;
    pimple = new bzhttp_Response_Data;
    ReturnCode = e404NotFound;
    DocumentType = eHTML;
    ForceNoCache = false;
}

bzhttp_Response::~bzhttp_Response()
{
    delete(RESPONSE_DATA_PTR);
}

void bzhttp_Response::AddHeader ( const char* n, const char* v)
{
    RESPONSE_DATA(data);

    std::string name = n;
    data->Headers[name] = std::string(v);
}

void bzhttp_Response::AddCookies ( const char* n, const char* v)
{
    RESPONSE_DATA(data);

    std::string name = n;
    data->Cookies[name] = std::string(v);
}

void bzhttp_Response::AddBodyData ( const char* v)
{
    RESPONSE_DATA(data);

    if (v)
        data->Body += v;
}

void bzhttp_Response::AddBodyData ( const void* v, size_t size)
{
    RESPONSE_DATA(data);

    if (v && size)
        data->Body += std::string((const char*)v,size);
}

//---- Request----
class bzhttp_Request_Data
{
public:
    std::map<std::string,std::string> Headers;
    std::map<std::string,std::string> Cookies;
    std::map<std::string,std::string> Paramaters;
};

#define REQUEST_DATA_PTR ((bzhttp_Request_Data*)pimple)
#define REQUEST_DATA(n) bzhttp_Request_Data *n = ((bzhttp_Request_Data*)pimple)

bzhttp_Request::bzhttp_Request(): Session(NULL)
{
    Version = 1;
    pimple = new bzhttp_Request_Data;
}

bzhttp_Request::~bzhttp_Request()
{
    delete(REQUEST_DATA_PTR);
}

bool bzhttp_Request::UserHasPerm ( const char* perm ) const
{
    if (!BZID.size() || !BZIDCallsign.size() || !BZIDGroups.size() || !perm)
        return false;

    std::string permName = TextUtils::toupper(perm);
    PlayerAccessInfo::AccessPerm realPerm =  permFromName(permName);

    for ( size_t i = 0; i < BZIDGroups.size(); i++ )
    {
        std::string groupName = BZIDGroups.get(i).c_str();
        groupName = TextUtils::toupper(groupName);
        PlayerAccessMap::iterator itr = groupAccess.find(groupName);
        if (itr == groupAccess.end())
            continue;

        if (itr->second.explicitAllows.test(realPerm) && !itr->second.explicitDenys.test(realPerm) )
            return true;

        for (unsigned int c = 0; c < itr->second.customPerms.size(); c++)
        {
            if (permName == TextUtils::toupper(itr->second.customPerms[c]))
                return true;
        }
    }

    return false;
}

void bzhttp_Request::AddHeader ( const char* n, const char* v)
{
    REQUEST_DATA(data);

    std::string name = TextUtils::toupper(n);
    if (data->Headers.find(name) == data->Headers.end())
        data->Headers[name] = std::string(v);
}

const char* bzhttp_Request::GetHeader ( const char* n) const
{
    REQUEST_DATA(data);

    std::string name = TextUtils::toupper(n);
    if (data->Headers.find(name) == data->Headers.end())
        return NULL;

    return data->Headers[name].c_str();
}

const char* bzhttp_Request::GetHeader ( size_t index ) const
{
    REQUEST_DATA(data);
    if (index >= data->Headers.size())
        return NULL;

    size_t i =0;
    std::map<std::string,std::string>::iterator itr = data->Headers.begin();
    while (itr != data->Headers.end())
    {
        if (index == i)
            return itr->second.c_str();
        i++;
        ++itr;
    }
    return NULL;
}

size_t bzhttp_Request::GetHeaderCount () const
{
    REQUEST_DATA(data);
    return data->Headers.size();
}

void bzhttp_Request::AddCookie ( const char* n, const char* v)
{
    REQUEST_DATA(data);

    std::string name = TextUtils::toupper(n);
    if (data->Headers.find(name) == data->Headers.end())
        data->Headers[name] = std::string(v);
}

const char* bzhttp_Request::GetCookie ( const char* n) const
{
    REQUEST_DATA(data);

    std::string name = TextUtils::toupper(n);
    if (data->Cookies.find(name) == data->Cookies.end())
        return NULL;

    return data->Cookies[name].c_str();
}

const char* bzhttp_Request::GetCookie ( size_t index ) const
{
    REQUEST_DATA(data);
    if (index >= data->Cookies.size())
        return NULL;

    size_t i =0;
    std::map<std::string,std::string>::iterator itr = data->Cookies.begin();
    while (itr != data->Cookies.end())
    {
        if (index == i)
            return itr->second.c_str();
        i++;
        ++itr;
    }
    return NULL;
}

size_t bzhttp_Request::GetCookieCount () const
{
    REQUEST_DATA(data);
    return data->Cookies.size();
}

void bzhttp_Request::AddParamater ( const char* n, const char* v)
{
    REQUEST_DATA(data);

    std::string name = TextUtils::toupper(n);
    if (data->Paramaters.find(name) == data->Paramaters.end())
        data->Paramaters[name] = std::string(v);
}

const char* bzhttp_Request::GetParamater ( const char* n) const
{
    REQUEST_DATA(data);

    std::string name = TextUtils::toupper(n);
    if (data->Paramaters.find(name) == data->Paramaters.end())
        return NULL;

    return data->Paramaters[name].c_str();
}

const char* bzhttp_Request::GetParamater ( size_t index ) const
{
    REQUEST_DATA(data);
    if (index >= data->Paramaters.size())
        return NULL;

    size_t i =0;
    std::map<std::string,std::string>::iterator itr = data->Paramaters.begin();
    while (itr != data->Paramaters.end())
    {
        if (index == i)
            return itr->second.c_str();
        i++;
        ++itr;
    }
    return NULL;
}

size_t bzhttp_Request::GetParamaterCount () const
{
    REQUEST_DATA(data);
    return data->Paramaters.size();
}

class HTTPConnectedPeer : public bz_NonPlayerConnectionHandler, public bz_BaseURLHandler
{
public:
    static HTTPConnectedPeer* Current;

    bool Killme;
    bzhttp_VDir *vDir;

    std::string Resource;
    std::string HTTPVersion;

    size_t ContentSize,HeaderSize;

    bool RequestComplete;

    bzhttp_Request  Request;
    bzhttp_Response Response;

    bool Authenticated;

    std::string bzIDAuthURL;
    std::string token;
    std::string callsign;
    std::string bzID;
    std::vector<std::string> bzGroups;

    std::string bzAuthReturnData;

    bool bzIDAuthFailed;
    bool bzIDAuthComplete;
    bool firedOffBZIDTokenCheck;

    HTTPConnectedPeer() : bz_NonPlayerConnectionHandler(), bz_BaseURLHandler()
    {
        firedOffBZIDTokenCheck = false;
        bzIDAuthFailed = false;
        bzIDAuthComplete = false;
        Authenticated = false;
        RequestComplete = false;
        Killme = false;
        vDir = NULL;
        Request.RequestType = eHTTPUnknown;
        ContentSize = 0;
        HeaderSize = 0;
    }

    std::string GetAuthSessionData( const char* name )
    {
        if (!vDir || !name || !Request.Session)
            return std::string();

        std::string n = TextUtils::format("%s_%s",vDir->VDirName(),name);

        std::map<std::string,std::string> &list = GetServerSessionData(*Request.Session);
        std::map<std::string,std::string>::iterator itr = list.find(n);
        if (itr == list.end())
            return std::string();

        return itr->second;
    }

    void SetAuthSessionData( const char* name, const char* d )
    {
        if (!vDir || !name || !Request.Session)
            return;

        std::string n = TextUtils::format("%s_%s",vDir->VDirName(),name);

        std::map<std::string,std::string> &list = GetServerSessionData(*Request.Session);
        if (!d)
        {
            std::map<std::string,std::string>::iterator itr = list.find(n);
            if (itr != list.end())
                list.erase(itr);
        }
        else
            list[n] = std::string(d);
    }

    virtual void URLDone (const char*, const void *data, unsigned int size, bool complete)
    {
        if (data && size)
            bzAuthReturnData += std::string((const char*)data,size);

        if (complete)
        {
            bzIDAuthComplete = true;
            bzIDAuthURL.clear();

            bzAuthReturnData = TextUtils::replace_all(bzAuthReturnData,"\r","");

            std::vector<std::string> lines = TextUtils::tokenize(bzAuthReturnData,"\n");

            if (lines.size() < 2)
                bzIDAuthFailed = true;
            else
            {
                std::vector<std::string> toks = TextUtils::tokenize(lines[1],":",2);
                if (toks.size() > 1 && toks[0] == "TOKGOOD")
                {
                    Authenticated = true;
                    SetAuthSessionData("bzidauthstatus","complete");

                    std::string groupList;
                    std::vector<std::string> groupToks = TextUtils::tokenize(lines[0]," ");
                    if ( groupToks.size() > 5)
                    {
                        for (size_t i = 5; i < groupToks.size(); i++)
                        {
                            std::vector<std::string> groups = TextUtils::tokenize(groupToks[i],"=");
                            bzGroups.push_back(groups[1]);
                            groupList += groups[1] + " ";

                        }
                        SetAuthSessionData("bzidauthgroups",groupList.c_str());

                    }
                    bzID = "";
                    // get the BZID
                    std::vector<std::string> chunks = TextUtils::tokenize(lines[2]," ");
                    if (chunks.size() > 1 && chunks[0] == "BZID:")
                        bzID = chunks[1];
                    else
                        bzIDAuthFailed = true;

                    SetAuthSessionData("bzid",bzID.c_str());
                    SetAuthSessionData("bzidcallsign",callsign.c_str());
                }
                else
                    bzIDAuthFailed = true;
            }
        }
    }

    virtual void URLTimeout ( const char* UNUSED(URL), int UNUSED(errorCode) )
    {
        bzIDAuthComplete = true;
        bzIDAuthFailed = true;
    }

    virtual void URLError ( const char* UNUSED(URL), int UNUSED(errorCode), const char * UNUSED(errorString) )
    {
        bzIDAuthComplete = true;
        bzIDAuthFailed = true;
    }

    virtual void pending(int connectionID, void *data, unsigned int size)
    {
        RequestData.append(static_cast<char const*>(data), size);

        // know our limits
        size_t maxContentSize( 1024*1536 );
        size_t maxBufferSize( 1024*2048 );

        if (bz_BZDBItemHasValue("_MaxHTTPContentSize"))
            maxBufferSize = bz_getBZDBInt("_MaxHTTPContentSize");

        if (vDir && vDir->MaxRequestBody > (int)maxContentSize)
            maxContentSize = vDir->MaxRequestBody;

        if (bz_BZDBItemHasValue("_MaxHTTPRequestSize"))
            maxBufferSize = bz_getBZDBInt("_MaxHTTPRequestSize");

        if (maxBufferSize < maxContentSize)
            maxBufferSize = maxContentSize;

        if (vDir && vDir->MaxRequestSize > (int)maxBufferSize)
            maxBufferSize = vDir->MaxRequestSize;

        // check to see if we have too much data
        if (RequestData.size() > maxBufferSize)
        {
            send501Error(connectionID);
            return;
        }

        if (Request.RequestType == eHTTPUnknown)
        {
            std::stringstream stream(RequestData);

            std::string request, httpVersion;
            stream >> request >> Resource >> httpVersion;

            Request.RequestType = LineIsHTTPRequest(request);
        }

        // easy outs
        if (Request.RequestType == eHTTPDelete || Request.RequestType == eHTTPConnect || (Request.RequestType == eHTTPPut
                && (vDir && !vDir->SupportPut())))
        {
            send501Error(connectionID);
            return;
        }

        if (Request.RequestType == eHTTPOptions)
        {
            sendOptions(connectionID,vDir ? vDir->SupportPut() : false);
            return;
        }

        // see if we want to get it

        if (HeaderSize == 0)
        {
            size_t headerEnd = TextUtils::find_first_substr(RequestData,"\r\n\r\n");
            if (headerEnd != std::string::npos)
                HeaderSize = headerEnd + 4;
        }

        unsigned int sessionID = 0;
        if (HeaderSize && !Request.GetHeaderCount())
        {
            std::string headerSubStir = RequestData.substr(0,HeaderSize);
            std::vector<std::string> headers = TextUtils::tokenize(headerSubStir,"\r\n");

            for ( size_t i = 1; i < headers.size(); i++ )
            {
                std::vector<std::string> headerParts = TextUtils::tokenize(headers[i],": ",2);
                if (headerParts.size() > 1)
                {
                    Request.AddHeader(headerParts[0].c_str(),headerParts[1].c_str());
                    std::string name = TextUtils::toupper(headerParts[0]);
                    if (name == "CONTENT-LENGTH")
                    {
                        int size1 = strtol(headerParts[1].c_str(), NULL, 0);
                        ContentSize = (unsigned int)size1;
                        if (ContentSize > maxContentSize)
                        {
                            send501Error(connectionID);
                            return;
                        }
                    }
                    if (name == "COOKIE")
                    {
                        std::vector<std::string> cookieParts = TextUtils::tokenize(headerParts[1],"=",2);
                        if (cookieParts.size() > 1)
                        {
                            Request.AddCookie(cookieParts[0].c_str(),cookieParts[1].c_str());
                            if (cookieParts[0] == SESSION_COOKIE)
                                sessionID = atoi(cookieParts[1].c_str());
                        }
                    }
                }
            }
        }

        if (HeaderSize && (Request.RequestType == eHTTPGet || Request.RequestType == eHTTPHead))
            RequestComplete = true;
        else
        {
            if (ContentSize)
            {
                if (RequestData.size() >= (ContentSize+HeaderSize))
                    RequestComplete = true;
            }
        }

        if (RequestComplete)
        {
            // parse up the paramaters;

            size_t question = Resource.find_first_of('?');

            std::string resource = Resource;

            if (question != std::string::npos)
            {
                parseParams(Resource.substr(question+1,Resource.length()-(question+1)));
                resource = Resource.substr(0,question);
            }
            std::string IP = bz_getNonPlayerConnectionIP(connectionID);

            Request.URL = Resource;
            Request.Resource = resource;

            Request.RequesterIP = IP.c_str();
            if (bz_getNonPlayerConnectionHost(connectionID))
                Request.RequesterHost = bz_getNonPlayerConnectionHost(connectionID);

            if (Request.RequestType == eHTTPPost)
            {
                std::string body = RequestData.substr(HeaderSize,ContentSize);
                Request.Body = body;

                // parse the body for params
                parseParams(body);
            }
            else if (Request.RequestType == eHTTPPut)
                Request.Body = RequestData.substr(HeaderSize,ContentSize);


            if (sessionID == 0 && Request.GetParamater("SSID") != NULL)
                Request.Session = &GetSessionByID(Request.GetParamater("SSID"),IP.c_str());
            else
                Request.Session = &GetSession(sessionID,IP.c_str());

            // check and see if we have authentication to do

            bzhttp_eAuthenticationStatus authStatus = eAuthFail;

            if (vDir && vDir->RequiredAuthentiction != eNoAuth)
            {
                Authenticated = false;

                // check and see if we are cached
                if (vDir->CacheAuthentication)
                {
                    if (vDir->RequiredAuthentiction == eBZID)
                    {
                        std::string id = GetAuthSessionData("bzid");
                        if (id.size())
                        {
                            Authenticated = true;
                            bzID = id;
                            callsign = GetAuthSessionData("bzidcallsign");

                            std::vector<std::string> groups = TextUtils::tokenize(GetAuthSessionData("bzidauthgroups")," ");
                            for (size_t i = 0; i < groups.size(); i++)
                            {
                                if (groups[i] != callsign)
                                    bzGroups.push_back(groups[i]);
                            }
                        }
                    }
                    else
                    {
                        std::string status = GetAuthSessionData("authstatus");
                        if (status == "complete")
                            Authenticated = true;
                    }
                }

                if (!Authenticated)
                {
                    if (vDir->RequiredAuthentiction == eBZID)
                    {
                        std::string status = GetAuthSessionData("bzidauthstatus");
                        if (status == "redired")
                        {
                            // see if they've got the stuff
                            const char *t = Request.GetParamater("bzauthtoken");
                            const char *c = Request.GetParamater("bzauthcallsign");

                            if (t && c)
                            {
                                SetAuthSessionData("bzidauthstatus","requested");
                                token = t;
                                callsign = c;

                                bzIDAuthURL = "https://my.bzflag.org/db/";
                                if (bz_BZDBItemHasValue("_WebAuthCheckURL"))
                                    bzIDAuthURL = bz_getBZDBString("_WebAuthCheckURL").c_str();

                                bzIDAuthURL += "?action=CHECKTOKENS&checktokens=";
                                bzIDAuthURL += TextUtils::url_encode(callsign);
                                bzIDAuthURL += "%3D";
                                bzIDAuthURL += TextUtils::url_encode(token);

                                std::string groups;
                                // get the groups list
                                PlayerAccessMap::iterator itr = groupAccess.begin();
                                while (itr != groupAccess.end())
                                {
                                    groups += TextUtils::url_encode(itr->first) + "%0D%0A";
                                    ++itr;
                                }

                                for (size_t i = 0; i < vDir->BZIDAuthenicationGroups.size(); i++)
                                    groups += TextUtils::url_encode(vDir->BZIDAuthenicationGroups.get(i).c_str()) + "%0D%0A";

                                if (groups.size())
                                {
                                    groups.erase(groups.size()-6,6);
                                    bzIDAuthURL += "&groups=" + groups;
                                }
                                firedOffBZIDTokenCheck = true;
                                bzIDAuthComplete = false;
#ifdef BZ_PLUGINS
                                PushPendingHTTPWait();
#endif
                                bz_setNonPlayerDisconnectOnSend(connectionID,false);
                                bz_setNonPlayerInactivityTimeout(connectionID,120);
                                bzAuthReturnData = "";
                                bz_addURLJob(bzIDAuthURL.c_str(),this);

                                //bz_startu
                                authStatus = eNotAuthedYet;
                            }
                        }
                        else
                        {
                            std::string authURL = "https://my.bzflag.org/weblogin.php";
                            if (bz_BZDBItemHasValue("_WebAuthURL"))
                                authURL = bz_getBZDBString("_WebAuthURL").c_str();

                            authURL += "?action=weblogin&url=";
                            std::string redirURL = vDir->BaseURL.c_str();

                            std::string vname = "/";
                            vname += vDir->VDirName();
                            vname += "/";

                            if (Resource.size() > vname.size())
                                redirURL += Resource.c_str()+vname.size();

                            if (redirURL.find_last_of('?') == std::string::npos)
                                redirURL += "?";

                            redirURL += TextUtils::format("bzauthtoken=%%TOKEN%%&bzauthcallsign=%%USERNAME%%&SSID=%d",Request.Session->SessionID);

                            authURL += TextUtils::url_encode(redirURL);

                            // authStatus = eNotAuthedYet;
                            Response.ReturnCode = e302Found;
                            Response.RedirectLocation = authURL.c_str();
                            SetAuthSessionData("bzidauthstatus","redired");
                        }

                        if (Authenticated && vDir->CacheAuthentication)
                            SetAuthSessionData("bzid", Request.BZID.c_str());
                    }
                    else
                    {
                        const char* authHeader = Request.GetHeader("Authorization");
                        if (authHeader || vDir->RequiredAuthentiction == eHTTPOther)
                        {
                            if (authHeader)
                            {
                                std::vector<std::string> parts = TextUtils::tokenize(base64_decode(trimLeadingWhitespace(authHeader)),":",2);
                                if (parts.size() > 1)
                                {
                                    authStatus = vDir->AuthenticateHTTPUser(bz_getNonPlayerConnectionIP(connectionID),parts[0].c_str(),parts[1].c_str(),
                                                                            Request);
                                    if (authStatus == eAuthOK)
                                        Authenticated = true;
                                }
                                else
                                {
                                    authStatus = vDir->AuthenticateHTTPUser(bz_getNonPlayerConnectionIP(connectionID),NULL,NULL,Request);
                                    if (authStatus == eAuthOK)
                                        Authenticated = true;
                                }
                            }

                            if (!Authenticated)
                            {
                                if (!vDir->GenerateNoAuthPage(Request,Response) && authStatus == eAuthFail)
                                    Response.ReturnCode = e403Forbiden;
                            }

                            if (Authenticated && vDir->CacheAuthentication)
                                SetAuthSessionData("authstatus", "complete");
                        }
                        else
                            Response.ReturnCode = e401Unauthorized;
                    }
                }

                if (!Authenticated && authStatus == eAuthFail)
                {
                    GenerateResponse(connectionID);
                    Killme = true;
                    return;
                }
            }
            else
                Authenticated = true;

            Think(connectionID);
        }
        else
        {
            if (Request.RequestType == eHTTPPost || Request.RequestType == eHTTPPut)
                send100Continue(connectionID);
        }
    }

    virtual bzhttp_ePageGenStatus GetPage ()
    {
        if (!vDir)
            return eNoPage;

        VDIR_SESSION_CLASS(Request.Session->pimple,session);
        session->CurrentVdir = vDir->VDirName();

        // load up the auth data
        Request.BZID = bzID.c_str();
        Request.BZIDCallsign = callsign;

        for (size_t i =0; i < bzGroups.size(); i++)
            Request.BZIDGroups.push_back(bzGroups[i]);

        bzhttp_ePageGenStatus status = vDir->GeneratePage(Request,Response);

        session->CurrentVdir = "";

        return status;
    }

    void Think ( int connectionID )
    {
        HTTPConnectedPeer::Current = this;
        if (RequestComplete && !Killme)
        {
            if (vDir && !Authenticated)
            {
                if (vDir->RequiredAuthentiction == eHTTPOther)
                {
                    bzhttp_eAuthenticationStatus authStatus = vDir->AuthenticateHTTPUser(bz_getNonPlayerConnectionIP(connectionID),NULL,
                            NULL,Request);
                    if (authStatus == eAuthOK)
                    {
                        Authenticated = true;
                        if (vDir->CacheAuthentication)
                            SetAuthSessionData("authstatus", "complete");
                    }
                    else
                    {
                        if (!vDir->GenerateNoAuthPage(Request,Response) && authStatus == eAuthFail)
                        {
                            Response.ReturnCode = e403Forbiden;
                            GenerateResponse(connectionID);
                            Killme = true;
                        }
                    }
                }
                else if (vDir->RequiredAuthentiction == eBZID && bzIDAuthComplete) // check to see if we have authed yet or not
                {
                    // if we failed then we suck
                    if (bzIDAuthFailed)
                    {
                        if (!vDir->GenerateNoAuthPage(Request,Response))
                            Response.ReturnCode = e403Forbiden;
                        GenerateResponse(connectionID);
                        Killme = true;
                    }
                }
            }

            if (firedOffBZIDTokenCheck && bzIDAuthComplete)
            {
#ifdef BZ_PLUGINS
                PopPendingHTTPWait();
#endif
                firedOffBZIDTokenCheck = false;
            }

            if (!vDir || Authenticated)
            {
                bzhttp_ePageGenStatus status = GetPage();

                if (status == eNoPage)
                    send404Error(connectionID);
                else if (status == ePageDone)
                    GenerateResponse(connectionID);
            }
        }
        HTTPConnectedPeer::Current = NULL;

    }

    const char* getMimeType(bzhttp_eDocumentType docType)
    {
        switch (docType)
        {
        case eOctetStream:
            return "application/octet-stream";

        case eBinary:
            return "application/binary";

        case eHTML:
            return "text/html";

        case eCSS:
            return "text/css";

        case eXML:
            return "application/xml";

        case eJSON:
            return "application/json";

        default:
            break;
        }
        return "text/plain";
    }


    void appendTime(std::string & text, bz_Time *ts, const char* _zoneofTime)
    {
        switch (ts->dayofweek)
        {
        case 1:
            text += "Mon";
            break;
        case 2:
            text += "Tue";
            break;
        case 3:
            text += "Wed";
            break;
        case 4:
            text += "Thu";
            break;
        case 5:
            text += "Fri";
            break;
        case 6:
            text += "Sat";
            break;
        case 0:
            text += "Sun";
            break;
        }

        text += TextUtils::format(", %d ",ts->day);

        switch (ts->month)
        {
        case 0:
            text += "Jan";
            break;
        case 1:
            text += "Feb";
            break;
        case 2:
            text += "Mar";
            break;
        case 3:
            text += "Apr";
            break;
        case 4:
            text += "May";
            break;
        case 5:
            text += "Jun";
            break;
        case 6:
            text += "Jul";
            break;
        case 7:
            text += "Aug";
            break;
        case 8:
            text += "Sep";
            break;
        case 9:
            text += "Oct";
            break;
        case 10:
            text += "Nov";
            break;
        case 11:
            text += "Dec";
            break;
        }

        text += TextUtils::format(" %d %d:%d:%d ",ts->year,ts->hour,ts->minute,ts->second);
        if (_zoneofTime)
            text += _zoneofTime;
        else
            text += "GMT";
    }

    std::string printTime(bz_Time *ts, const char* _zoneofTime)
    {
        std::string time;
        appendTime(time,ts,_zoneofTime);
        return time;
    }

    void GenerateResponse (int connectionID)
    {
        RESPONSE_DATA_CLASS(Response.pimple,data);

        // set the session cookie
        Response.AddCookies(SESSION_COOKIE,TextUtils::format("%d",Request.Session->SessionID).c_str());

        std::string pageBuffer = "HTTP/1.1";

        bool redirected = false;

        switch (Response.ReturnCode)
        {
        case e200OK:
            pageBuffer += " 200 OK\n";
            break;
        case e301Redirect:
            if (Response.RedirectLocation.size())
            {
                redirected = true;
                pageBuffer += " 301 Moved Permanently\n";
                pageBuffer += "Location: " + std::string(Response.RedirectLocation.c_str()) + "\n";
            }
            else
                pageBuffer += " 500 Server Error\n";

            pageBuffer += "Host: " + ServerHostPort + "\n";
            break;

        case e302Found:
            if (Response.RedirectLocation.size())
            {
                redirected = true;
                pageBuffer += " 302 Found\n";
                pageBuffer += "Location: " + std::string(Response.RedirectLocation.c_str()) + "\n";
            }
            else
                pageBuffer += " 500 Server Error\n";

            pageBuffer += "Host: " + ServerHostPort + "\n";
            break;

        case e401Unauthorized:
            if (!vDir || vDir->RequiredAuthentiction == eBZID)
                pageBuffer += " 403 Forbidden\n";
            else
            {
                pageBuffer += " 401 Unauthorized\n";
                pageBuffer += "WWW-Authenticate: ";

                if (vDir->RequiredAuthentiction == eHTTPOther && vDir->OtherAuthenicationMethod.size())
                    pageBuffer += vDir->OtherAuthenicationMethod.c_str();
                else
                    pageBuffer += "Basic";

                pageBuffer += " realm=\"";
                if (vDir->HTTPAuthenicationRelalm.size())
                    pageBuffer += vDir->HTTPAuthenicationRelalm.c_str();
                else
                    pageBuffer += ServerHostPort;
                pageBuffer += "\"\n";
            }
            break;

        case e403Forbiden:
            pageBuffer += " 403 Forbidden\n";
            break;

        case e404NotFound:
            pageBuffer += " 404 Not Found\n";
            break;

        case e418IAmATeapot:
            pageBuffer += " 418 I Am A Teapot\n";
            break;

        case e500ServerError:
            pageBuffer += " 500 Server Error\n";
            break;
        }

        pageBuffer += "Connection: close\n";

        if (data->Body.size())
        {
            pageBuffer += TextUtils::format("Content-Length: %zu\n", data->Body.size());

            pageBuffer += "Content-Type: ";
            if (Response.ReturnCode == e200OK)
            {
                if (Response.DocumentType == eOther && Response.MimeType.size())
                    pageBuffer += Response.MimeType.c_str();
                else
                    pageBuffer += getMimeType(Response.DocumentType);
            }
            else
                pageBuffer += getMimeType(eHTML);

            pageBuffer += "\n";
        }

        if (Response.ForceNoCache)
            pageBuffer += "Cache-Control: no-cache\n";

        if (Response.MD5Hash.size())
            pageBuffer += "Content-MD5: " + std::string(Response.MD5Hash.c_str()) + "\n";

        pageBuffer += "Server: " + ServerVersion + "\n";

        bool didDate = false;

        std::map<std::string,std::string>::iterator itr = data->Headers.begin();
        while (itr != data->Headers.end())
        {
            pageBuffer += itr->first + ":" + itr->second + "\n";
            if (itr->first == "Date")
                didDate = true;
            ++itr;
        }

        if (!didDate)
        {
            bz_Time ts;
            bz_getUTCtime (&ts);
            pageBuffer += "Date: ";
            pageBuffer += printTime(&ts,"UTC");
            pageBuffer += "\n";
        }

        std::string cookieDomain;
        std::string cookiePath;

        if (Response.CookieDomain.size())
            cookieDomain = Response.CookieDomain.c_str();
        else
            cookieDomain = ServerHostname;

        if (Response.CookiePath.size())
            cookiePath = Response.CookiePath.c_str();
        else
        {
            if (vDir)
                cookiePath = TextUtils::format("/%s",vDir->VDirName());
            else
                cookiePath = "/";
        }


        //  if (Response.ReturnCode == e200OK)
        {
            itr = data->Cookies.begin();
            while (itr != data->Cookies.end())
            {
                std::string d,p;

                if (itr->first == SESSION_COOKIE)
                {

                    d = ServerHostname;
                    p = "/";
                }
                else
                {
                    d = cookieDomain;
                    p = cookiePath;
                }

                pageBuffer +=  TextUtils::format("Set-Cookie: %s=%s; Domain=%s; Path=%s; Max-Age=3600; HttpOnly\n",itr->first.c_str(),
                                                 itr->second.c_str(),d.c_str(),p.c_str());
                ++itr;
            }
        }

        pageBuffer += "\n";

        if (Request.RequestType != eHTTPHead && data->Body.size())
            pageBuffer += data->Body;

        pageBuffer += "\n\n";

        bz_setNonPlayerDisconnectOnSend(connectionID,redirected);

        // build up the buffers
        size_t i = 0;
        size_t bufferSize = 1024;

        while ( i < pageBuffer.size())
        {
            size_t thisBuffer = bufferSize;
            if (i + thisBuffer >= pageBuffer.size())
                thisBuffer = pageBuffer.size()-i;

            bz_sendNonPlayerData(connectionID, pageBuffer.c_str() + i, thisBuffer);
            i += thisBuffer;
        }

        Killme = true;
    }

    void parseParams ( const std::string & str )
    {
        std::vector<std::string> v = TextUtils::tokenize(str,"&");
        for ( size_t i = 0; i < v.size(); i++ )
        {
            if (v[i].size())
            {
                std::vector<std::string> t = TextUtils::tokenize(v[i],"=");
                if (!t.empty())
                    Request.AddParamater(TextUtils::url_decode(t[0]).c_str(), t.size() > 1 ? TextUtils::url_decode(t[1]).c_str() : "");
            }
        }
    }

    virtual void disconnect(int connectionID)
    {
        if (!connectionID)
            return;
        if (bzIDAuthURL.size())
            bz_removeURLJob(bzIDAuthURL.c_str());

        Killme = true;
    }

    void send100Continue(int connectionID)
    {
        std::string httpHeaders;
        httpHeaders += "HTTP/1.1 100 Continue\n\n";

        bz_sendNonPlayerData(connectionID, httpHeaders.c_str(), (unsigned int)httpHeaders.size());
    }

    void send404Error(int connectionID)
    {
        std::string httpHeaders;
        httpHeaders += "HTTP/1.1 404 Not Found\n\n";

        bz_sendNonPlayerData(connectionID, httpHeaders.c_str(), (unsigned int)httpHeaders.size());
    }

    void send501Error(int connectionID)
    {
        std::string httpHeaders;
        httpHeaders += "HTTP/1.1 501 Not Implemented\n\n";

        bz_sendNonPlayerData(connectionID, httpHeaders.c_str(), (unsigned int)httpHeaders.size());
        Killme = true;
    }

    void sendOptions(int connectionID, bool put)
    {
        std::string httpHeaders;
        httpHeaders += "HTTP/1.1 200 Ok\n";
        httpHeaders += "Allow: GET, HEAD, POST, OPTIONS";
        if (put)
            httpHeaders += ", PUT";
        httpHeaders += "\n\n";

        bz_sendNonPlayerData(connectionID, httpHeaders.c_str(), (unsigned int)httpHeaders.size());
        Killme = true;
    }

protected:
    std::string RequestData;
};

class ResourcePeer : public HTTPConnectedPeer
{
public:
    std::string File;
    std::string Mime;

    ResourcePeer() : HTTPConnectedPeer()
    {
        Mime = "application/octet-stream";
    }

    virtual bzhttp_ePageGenStatus GetPage ()
    {
        if (!File.size())
            return eNoPage;

        FILE* fp = fopen(File.c_str(),"rb");
        if (!fp)
            return eNoPage;

        size_t size = 0;
        fseek(fp,0,SEEK_END);
        size = ftell(fp);
        fseek(fp,0,SEEK_SET);
        void *p = malloc(size);
        size_t item_read = fread(p,size,1,fp);
        fclose(fp);

        Response.MD5Hash = bz_MD5(p,size);

        if (Request.RequestType != eHTTPHead)
            Response.AddBodyData(p,size);
        free(p);

        if (item_read != 1)
            return eNoPage;

        Response.ReturnCode = e200OK;
        Response.DocumentType = eOther;
        Response.MimeType = Mime.c_str();

        return ePageDone;
    }

    static std::string FindResourcePath ( const std::string &file, const bz_APIStringList &dirs )
    {
        for ( size_t i = 0; i < dirs.size(); i++)
        {
            std::string path = concatPaths(dirs.get(i).c_str(),file.c_str());
            if (fileExits(path.c_str()))
                return path;
        }
        return std::string();
    }
};

std::map<int,HTTPConnectedPeer*> HTTPPeers;

// index handler
class HTTPIndexHandler: public bzhttp_VDir
{
public:
    HTTPIndexHandler() : bzhttp_VDir()
    {
        if (bz_BZDBItemHasValue("_HTTPIndexResourceDir"))
            ResourceDirs.push_back(bz_getBZDBString("_HTTPIndexResourceDir"));

        AddStandardTypes();
    }

    virtual const char* VDirName()
    {
        return "INDEX";
    }

    virtual bzhttp_ePageGenStatus GeneratePage (const bzhttp_Request &, bzhttp_Response &response)
    {
        response.ReturnCode = e200OK;
        response.DocumentType = eHTML;
        response.AddBodyData("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\"><html><head>");
        response.AddBodyData("<title>Index page for ");
        response.AddBodyData(ServerHostPort.c_str());
        response.AddBodyData("</title></head><body>");

        if (VDirs.empty())
            response.AddBodyData("No HTTP Services are running on this server");
        else
        {
            std::map<std::string,VDir>::iterator itr = VDirs.begin();
            while (itr != VDirs.end())
            {
                std::string vdirName = itr->second.vdir->VDirName();
                std::string vDirDescription = itr->second.vdir->VDirDescription();
                std::string line =  "<a href=\"/" + vdirName + "/\">" + vdirName +"</a>&nbsp;" +vDirDescription +"<br/>";
                response.AddBodyData(line.c_str());
                itr++;
            }
        }

        response.AddBodyData("</body></html>");

        return ePageDone;
    }

    virtual bool AllowResourceDownloads ( void )
    {
        return ResourceDirs.size() > 0;
    }
};

HTTPIndexHandler *indexHandler;

HTTPConnectedPeer* HTTPConnectedPeer::Current = NULL;

void InitHTTP()
{
    indexHandler = new HTTPIndexHandler();
    VDirs.clear();
    if (con || tick)
    {
        worldEventManager.removeEvent(bz_eTickEvent,tick);
        worldEventManager.removeEvent(bz_eNewNonPlayerConnection,con);
        delete(con);
        delete(tick);
    }
    con = new ConnectionEvent();
    tick = new TickEvent();

    worldEventManager.addEvent(bz_eNewNonPlayerConnection,con);
    worldEventManager.addEvent(bz_eTickEvent,tick);

    ServerHostname = "localhost";
    if (bz_getPublicAddr().size())
        ServerHostname = bz_getPublicAddr().c_str();

    // make sure it has the port
    size_t pos;
    if ((pos = ServerHostname.find(':')) != std::string::npos)
        ServerHostname = ServerHostname.substr(0, pos);
    ServerPort = TextUtils::format("%d",bz_getPublicPort());

    ServerHostPort = ServerHostname + ":" + ServerPort;

    ServerVersion = bz_getServerVersion();

    BaseURL = "http://";
    BaseURL += ServerHostPort + "/";

    indexHandler->BaseURL = BaseURL.c_str();
}

void KillHTTP()
{
    std::map<int,HTTPConnectedPeer*>::iterator itr = HTTPPeers.begin();
    while (itr != HTTPPeers.end())
    {
        bz_disconnectNonPlayerConnection(itr->first);
        delete(itr->second);
        ++itr;
    }

    HTTPPeers.clear();

    if (tick)
    {
        worldEventManager.removeHandler(tick);
        delete(tick);
        tick = NULL;
    }

    if (con)
    {
        worldEventManager.removeHandler(con);
        delete(con);
        con = NULL;
    }

    VDirs.clear();
    delete(indexHandler);
}

void NewHTTPConnection ( bz_EventData *eventData )
{
    if (VDirs.empty())
        return;

    bz_NewNonPlayerConnectionEventData_V1 *connData = (bz_NewNonPlayerConnectionEventData_V1*)eventData;

    if (!connData->size)
        return;

    const char* data = (char*)connData->data;
    std::string sentData = data;

    std::stringstream stream(sentData);

    std::string request, resource, httpVersion;
    stream >> request >> resource >> httpVersion;

    if (!request.size() || !resource.size() || !httpVersion.size())
        return;

    bzhttp_eRequestType requestType = LineIsHTTPRequest(TextUtils::toupper(request));

    if (requestType == eHTTPUnknown)
        return;

    if (httpVersion != "HTTP/1.1" && httpVersion != "HTTP/1.0")
        return;

    // figure out who gets it
    // count the /s if there is more then one then the first part is the vdir
    // otherwise it's an index request
    bzhttp_VDir * dir = NULL;

    if (resource.size() > 0)
    {
        // trim it at the ? if it has one
        size_t question = resource.find_first_of('?');
        if (question != std::string::npos)
            resource = resource.substr(0,question);

        // find the first / ( really this should be at 0 )
        size_t firstSlash = resource.find_first_of('/');
        if (firstSlash != std::string::npos && firstSlash == 0)
        {
            std::string dirName;

            size_t secondSlash = resource.find_first_of('/',firstSlash+1);
            if (secondSlash != std::string::npos)
                dirName = resource.substr(firstSlash+1,secondSlash-firstSlash-1);
            else
            {
                if (firstSlash+1 < resource.size())
                    dirName = resource.substr(firstSlash+1,resource.size()-firstSlash);
            }

            if (dirName.size())
            {
                std::map<std::string,VDir>::iterator itr = VDirs.find(TextUtils::toupper(dirName));
                if (itr != VDirs.end())
                    dir = itr->second.vdir;
            }
        }

        HTTPConnectedPeer *peer = new HTTPConnectedPeer();
        if (!dir)
            peer->vDir = indexHandler;
        else
            peer->vDir = dir;

        // check and see if it's a resource

        if (peer->vDir->AllowResourceDownloads())
        {
            if (resource.size())
            {
                size_t dot = resource.find_last_of('.');
                if (dot != std::string::npos)
                {
                    std::string path = TextUtils::url_decode(resource.substr(0,dot));
                    if (TextUtils::find_first_substr(path,"..") == std::string::npos) // don't do paths that have .. ANYWHERE
                    {
                        std::string ext = TextUtils::toupper(resource.substr(dot+1,resource.size()-dot-1));
                        VDIR_DATA_CLASS(peer->vDir->pimple,vdata);
                        if (vdata->MimeTypes.find(ext) != vdata->MimeTypes.end())
                        {
                            ResourcePeer *p = new ResourcePeer();
                            p->File = ResourcePeer::FindResourcePath(resource,peer->vDir->ResourceDirs);
                            p->Mime = vdata->MimeTypes[ext];
                            delete(peer);
                            peer = p;
                        }
                    }
                }
            }
        }

        bz_registerNonPlayerConnectionHandler(connData->connectionID,peer);

        HTTPPeers[connData->connectionID] = peer;
        peer->pending(connData->connectionID,connData->data,connData->size);
    }
    //connData->data
}

void CheckForZombies ( void )
{
    std::map<int,HTTPConnectedPeer*>::iterator itr = HTTPPeers.begin();
    std::vector<int> toKill;

    while (itr != HTTPPeers.end())
    {
        if (itr->second->Killme)
        {
            if ( bz_getNonPlayerConnectionOutboundPacketCount(itr->first))
            {
                bz_disconnectNonPlayerConnection(itr->first);
                delete(itr->second);
                toKill.push_back(itr->first);
            }
        }
        else
            itr->second->Think(itr->first);

        itr++;
    }

    for (size_t i = 0; i < toKill.size(); i++)
        HTTPPeers.erase(HTTPPeers.find(toKill[i]));

    toKill.clear();

    double sessionTimeOut = 10*60;
    double rightNow = TimeKeeper::getCurrent().getSeconds();
    std::map<int,bzhttp_SessionData*>::iterator sessionItr = Sessions.begin();
    while (sessionItr != Sessions.end())
    {
        VDIR_SESSION_CLASS(sessionItr->second->pimple,data);
        if (data->LastUpdateTime + sessionTimeOut < rightNow)
        {
            delete (sessionItr->second);
            toKill.push_back(sessionItr->first);
        }
        ++sessionItr;
    }

    for (size_t i = 0; i < toKill.size(); i++)
        Sessions.erase(Sessions.find(toKill[i]));
}

// templates

class bzhttp_TMD_Data
{
public:
    std::map< std::string, std::vector<std::string> > Items;
};

#define TMD_DATA_PTR ((bzhttp_TMD_Data*)pimple)
#define TMD_DATA(n) bzhttp_TMD_Data *n = ((bzhttp_TMD_Data*)pimple)
#define TMD_DATA_CLASS(c,n) bzhttp_TMD_Data *n = ((bzhttp_TMD_Data*)(c))

bzhttp_TemplateMetaData::bzhttp_TemplateMetaData()
{
    pimple = new bzhttp_TMD_Data();
}

bzhttp_TemplateMetaData::bzhttp_TemplateMetaData( const bzhttp_TemplateMetaData& r)
{
    pimple = new bzhttp_TMD_Data();

    TMD_DATA(data);

    TMD_DATA_CLASS(r.pimple,otherData);
    data->Items = otherData->Items;
}

bzhttp_TemplateMetaData::~bzhttp_TemplateMetaData()
{
    delete(TMD_DATA_PTR);
}

const char * bzhttp_TemplateMetaData::Get ( const char* key, unsigned int index )
{
    if (!key)
        return NULL;

    TMD_DATA(data);
    std::map< std::string, std::vector<std::string> >::iterator itr = data->Items.find(key);
    if (itr == data->Items.end())
        return NULL;
    if (index >= itr->second.size())
        return NULL;
    return itr->second[index].c_str();
}

unsigned int bzhttp_TemplateMetaData::Count ( const char* key )
{
    if (!key)
        return 0;

    TMD_DATA(data);
    std::map< std::string, std::vector<std::string> >::iterator itr = data->Items.find(key);
    if (itr == data->Items.end())
        return 0;
    return itr->second.size();
}

void bzhttp_TemplateMetaData::Add ( const char* key, const char* val )
{
    if (!key || !val)
        return;

    TMD_DATA(data);
    std::map< std::string, std::vector<std::string> >::iterator itr = data->Items.find(key);
    if (itr == data->Items.end())
        data->Items[key] = std::vector<std::string>();

    data->Items[key].push_back(val);
}


class TemplateInfo
{
public:
    bzhttp_TemplateCallback* Callback;
    std::string PathSet;
    std::string RootPath;
};

std::string processTemplate ( const std::string &templateText, TemplateInfo& info );

std::string getStringRange ( const std::string &find, size_t start, size_t end )
{
    std::string ret;

    if (end <= start || start > find.size() || end > find.size())
        return ret;

    for ( size_t p = start; p <= end; p++)
        ret += find[p];

    return ret;
}

std::string readFileText ( const char* file )
{
    FILE *fp = fopen(file,"rb");
    if (!fp)
        return std::string();

    fseek(fp,0,SEEK_END);
    size_t s = ftell(fp);
    fseek(fp,0,SEEK_SET);

    char* m = (char*)malloc(s+1);
    size_t item_read = fread(m, s, 1, fp);
    m[s] = 0;
    fclose(fp);

    std::string ret(m);
    free(m);

    if (item_read != 1)
        return std::string();

    if (ret.find_first_of('\n') != std::string::npos)
        return ret;

    // HTTP likes /r/n not just /r
    return TextUtils::replace_all(ret,"\r","\r\n");
}

void GetTemplateMetaData( const std::string &templateText, bzhttp_TemplateMetaData& metaData)
{
    size_t pos = 0;
    while ( pos < templateText.size() && pos != std::string::npos)
    {
        pos = TextUtils::find_first_substr(templateText,std::string("[#"),pos);
        if ( pos < templateText.size() && pos != std::string::npos )
        {
            size_t start = pos;

            pos = TextUtils::find_first_substr(templateText,std::string("]"),pos);
            if ( pos < templateText.size() && pos != std::string::npos )
            {
                std::string dataKey = getStringRange(templateText,start+2,pos-1);

                std::vector<std::string> chunks = TextUtils::tokenize(dataKey,std::string(":"),0,true);
                if (chunks.size() > 1)
                    metaData.Add(chunks[0].c_str(),chunks[1].c_str());
            }
        }
    }
}

void makelower ( std::string &str)
{
    str = TextUtils::tolower(str);
}

std::string getDirName ( const std::string& file )
{
    std::string f = file;
    size_t s = f.find_last_of(_DirDelim);
    if (s != std::string::npos)
        f.erase(s+1,f.size()-s-1);

    return f;
}

std::string::const_iterator readKey ( std::string &key, std::string::const_iterator inItr, const std::string &str )
{
    std::string::const_iterator itr = inItr;

    while ( itr != str.end() )
    {
        if (*itr != ']')
        {
            key += *itr;
            ++itr;
        }
        else
        {
            // go past the code
            ++itr;
            key = TextUtils::tolower(key);
            return itr;
        }
    }
    return itr;
}


std::string::const_iterator findNextTag ( const std::vector<std::string> &keys, std::string &endKey, std::string &code,
        std::string::const_iterator inItr, const std::string &str )
{
    if (keys.empty())
        return inItr;

    std::string::const_iterator itr = inItr;

    while (1)
    {
        itr = std::find(itr,str.end(),'[');
        if (itr == str.end())
            return itr;

        // save off the itr in case this is the one, so we can copy to this point
        std::string::const_iterator keyStartItr = itr;

        ++itr;
        if (itr == str.end())
            return itr;

        std::string key;
        itr = readKey(key,itr,str);

        for ( size_t i = 0; i < keys.size(); i++ )
        {
            if ( key == keys[i])
            {
                endKey = key;
                code.resize(keyStartItr - inItr);
                std::copy(inItr,keyStartItr,code.begin());
                return itr;
            }
        }
    }
    return itr;
}

double startTime;

std::string CallKey ( std::string& key, bzhttp_TemplateCallback* callback)
{
    if (key == "date")
    {
        bz_Time time;
        bz_getLocaltime(&time);
        return TextUtils::format("%d/%d/%d",time.month,time.day,time.year).c_str();
    }
    else if (key == "time")
    {
        bz_Time time;
        bz_getLocaltime(&time);
        TextUtils::format("%d:%d:%d",time.hour,time.minute,time.second);
    }
    else if (key == "hostname")
    {
        std::string data = bz_getPublicAddr();
        if (!data.size())
            data = TextUtils::format("localhost:%d",bz_getPublicPort());
        return data;
    }
    else if (key == "pagetime")
        return TextUtils::format("%.3f",bz_getCurrentTime()-startTime).c_str();
    else if (key == "baseurl")
        return HTTPConnectedPeer::Current->vDir->BaseURL.c_str();
    else if (key == "pluginname")
        return VDirs[HTTPConnectedPeer::Current->vDir->VDirName()].plugin ?
               VDirs[HTTPConnectedPeer::Current->vDir->VDirName()].plugin->Name() : "";
    else if (callback)
        return callback->GetTemplateKey(key.c_str());

    return "";
}

void replaceVar ( std::string &code, std::string::const_iterator &itr, const std::string &str, TemplateInfo& info )
{
    // find the end of the ]]
    std::string key;

    itr = readKey(key,itr,str);

    if (itr != str.end())
    {
        std::string lowerKey(TextUtils::tolower(key));
        code += CallKey(lowerKey,info.Callback);
    }
}

void processLoop ( std::string &code, std::string::const_iterator &inItr, const std::string &str, TemplateInfo& info )
{
    std::string key,loopSection,emptySection,param;

    // read the rest of the key
    std::string::const_iterator itr = readKey(key,inItr,str);

    std::vector<std::string> commandParts = TextUtils::tokenize(key,std::string(" "),0,0);
    if (commandParts.size() < 2)
    {
        inItr = itr;
        return;
    }

    // check the params
    makelower(commandParts[0]);
    makelower(commandParts[1]);

    if (commandParts.size() > 2)
        param = commandParts[2];

    if ( commandParts[0] != "start" )
    {
        inItr = itr;
        return;
    }

    std::vector<std::string> checkKeys;
    checkKeys.push_back(TextUtils::format("*end %s",commandParts[1].c_str()));

    std::string keyFound;
    itr = findNextTag(checkKeys,keyFound,loopSection,itr,str);

    if (itr == str.end())
    {
        inItr = itr;
        return;
    }

    // do the empty section
    // loops have to have both
    checkKeys.clear();
    checkKeys.push_back(TextUtils::format("*empty %s",commandParts[1].c_str()));
    itr = findNextTag(checkKeys,keyFound,emptySection,itr,str);

    if (info.Callback && info.Callback->GetTemplateLoop(commandParts[1].c_str(),param.c_str()))
    {
        code += processTemplate(loopSection,info);

        while (info.Callback && info.Callback->GetTemplateLoop(commandParts[1].c_str(),param.c_str()))
            code += processTemplate(loopSection,info);
    }
    else
        code += processTemplate(emptySection,info);
    inItr = itr;
}

bool CallIF ( const std::string &key, const std::string &param, bzhttp_TemplateCallback* callback )
{
    if (key == "public")
        return bz_getPublic();

    if (callback)
        return callback->GetTemplateIF(key.c_str(),param.c_str());
    return false;
}

void processIF ( std::string &code, std::string::const_iterator &inItr, const std::string &str, TemplateInfo& info )
{
    std::string key;

    // read the rest of the key
    std::string::const_iterator itr = readKey(key,inItr,str);

    std::vector<std::string> commandParts = TextUtils::tokenize(key,std::string(" "),0,0);
    if (commandParts.size() < 2)
    {
        inItr = itr;
        return;
    }

    // check the params
    makelower(commandParts[0]);
    makelower(commandParts[1]);

    if ( commandParts[0] != "if" )
    {
        inItr = itr;
        return;
    }

    std::string param;
    if (commandParts.size() > 2)
        param = commandParts[2];

    // now get the code for the next section
    std::string trueSection,elseSection;

    std::vector<std::string> checkKeys;
    checkKeys.push_back(TextUtils::format("?else %s",commandParts[1].c_str()));
    checkKeys.push_back(TextUtils::format("?end %s",commandParts[1].c_str()));

    std::string keyFound;
    itr = findNextTag(checkKeys,keyFound,trueSection,itr,str);

    if (keyFound == checkKeys[0])   // we hit an else, so we need to check for it
    {
        // it was the else, so go and find the end too
        if (itr == str.end())
        {
            inItr = itr;
            return;
        }

        checkKeys.erase(checkKeys.begin());// kill the else, find the end
        itr = findNextTag(checkKeys,keyFound,elseSection,itr,str);
    }

    // test the if, stuff that dosn't exist is false
    if (CallIF(commandParts[1],param,info.Callback))
        code += processTemplate(trueSection,info);
    else
        code += processTemplate(elseSection,info);

    inItr = itr;
}

void processComment ( std::string & UNUSED(code), std::string::const_iterator &inItr, const std::string &str,
                      TemplateInfo& UNUSED(info) )
{
    std::string key;
    inItr = readKey(key,inItr,str);
}

void processInclude ( std::string &code, std::string::const_iterator &inItr, const std::string &str,
                      TemplateInfo& info )
{
    std::string key;
    inItr = readKey(key,inItr,str);

    // check the search paths for the include file

    std::string templatePath;

    if (info.RootPath.size())
    {
        std::string temp = concatPaths(info.RootPath.c_str(),key.c_str());
        if (fileExits(temp.c_str()))
            templatePath = temp;
    }
    if (!templatePath.size() || info.PathSet.size())
    {
        const char* t = bzhttp_FindFile(info.PathSet.c_str(),key.c_str());
        if (t)
            templatePath = t;
    }

    if (!templatePath.size() || fileExits(key.c_str()))
        templatePath = key;

    if (!templatePath.size())
        return;

    bzhttp_TemplateMetaData *oldMeta = NULL;
    bzhttp_TemplateMetaData *newMeta = NULL;

    std::string templateContents = readFileText(templatePath.c_str());

    if (info.Callback)
    {
        oldMeta = info.Callback->MetaData;
        newMeta = new bzhttp_TemplateMetaData(*oldMeta);
        GetTemplateMetaData(templateContents,*newMeta);
        info.Callback->MetaData = newMeta;
    }

    TemplateInfo includeInfo = info;
    includeInfo.RootPath = getDirName(templatePath);

    code += processTemplate(templateContents,includeInfo);

    if (info.Callback)
    {
        info.Callback->MetaData = oldMeta;
        delete(newMeta);
    }
}

std::string processTemplate ( const std::string &templateText, TemplateInfo& info )
{
    std::string code;

    std::string::const_iterator templateItr = templateText.begin();

    while ( templateItr != templateText.end() )
    {
        if ( *templateItr != '[' )
        {
            code += *templateItr;
            ++templateItr;
        }
        else
        {
            ++templateItr;

            if (templateItr == templateText.end())
                code += '[';
            else
            {
                switch (*templateItr)
                {
                default: // it's not a code, so just let the next loop hit it and output it
                    break;

                case '$':
                    replaceVar(code,++templateItr,templateText,info);
                    break;

                case '*':
                    processLoop(code,++templateItr,templateText,info);
                    break;

                case '?':
                    processIF(code,++templateItr,templateText,info);
                    break;
                case '-':
                case '#': // treat metadata as comments when parsing
                    processComment(code,++templateItr,templateText,info);
                    break;
                case '!':
                    processInclude(code,++templateItr,templateText,info);
                    break;
                }
            }
        }
    }
    return code;
}

BZF_API bz_ApiString bzhttp_RenderTemplate ( const char* file, bzhttp_TemplateCallback* callback, const char *pathSet)
{
    if (!file)
        return bz_ApiString();

    std::string templateText = readFileText(file);

    TemplateInfo info;
    info.Callback = callback;
    if (pathSet)
        info.PathSet = pathSet;
    info.RootPath = getDirName(std::string(file));

    startTime = TimeKeeper::getCurrent().getSeconds();

    bzhttp_TemplateMetaData meta;
    GetTemplateMetaData(templateText,meta);
    if (callback)
        callback->MetaData = &meta;

    std::string code = processTemplate(templateText,info);

    if (callback)
        callback->MetaData = NULL;

    return bz_ApiString(code);
}


BZF_API bz_ApiString bzhttp_RenderTemplateFromText ( const char* text, bzhttp_TemplateCallback* callback,
        const char* pathSet )
{
    if (!text || !*text)
        return bz_ApiString();

    TemplateInfo info;
    info.Callback = callback;
    if (pathSet)
        info.PathSet = pathSet;

    startTime = TimeKeeper::getCurrent().getSeconds();

    std::string templateText = text;

    bzhttp_TemplateMetaData meta;
    GetTemplateMetaData(templateText,meta);
    if (callback)
        callback->MetaData = &meta;

    std::string code = processTemplate(templateText,info);

    if (callback)
        callback->MetaData = NULL;

    return bz_ApiString(code);
}

BZF_API bzhttp_TemplateMetaData bzhttp_GetTemplateMetaData( const char* file )
{
    bzhttp_TemplateMetaData data;
    GetTemplateMetaData(readFileText(file),data);
    return data;
}

// path utils
std::map<std::string,std::vector<std::string> > PathSets;

BZF_API bool bzhttp_AddSearchPath ( const char* pathSet, const char* path )
{
    if (!pathSet || !path)
        return false;

    std::string name = std::string(pathSet);

    if (PathSets.find(name) == PathSets.end())
        PathSets[name] = std::vector<std::string>();

    PathSets[name].push_back(std::string(path));
    return true;
}

BZF_API const char* bzhttp_FindFile ( const char* pathSet, const char* filename )
{
    if (!pathSet || !filename)
        return NULL;

    std::string name = std::string(pathSet);

    if (PathSets.find(name) == PathSets.end())
        return NULL;

    std::vector<std::string> &list = PathSets[name];
    for ( size_t i = 0; i < list.size(); i++)
    {
        static const std::string path = concatPaths(list[i].c_str(),filename);
        if (fileExits(path.c_str()))
            return path.c_str();
    }
    return NULL;
}

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
