//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <dirent.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#ifdef __FreeBSD__
#include <sys/uio.h>
#else
#include <sys/sendfile.h>
#endif

#define SERVER     "webserver/1.1"
#define PROTOCOL   "HTTP/1.1"
#define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT"

#include "str.h"
#include "utils.h"
#include "conf.h"
#include "cmds.h"
#include "queue.h"
#include "mutex.h"
#include "thread.h"
#include "list.h"
#include "var.h"
#include "sys_util.h"
#include "pr_web.h"
#include "security.h"
#include "state.h"
#include "gen_html.h"
#include "gen_xml.h"

extern char tmp[MAXMAXLEN];
extern int  gotExitSignal;
extern boolean_t stillRun;

static int       visits       = 0;          // counts client requests
static boolean_t answerReady  = BOOL_NO;
   

typedef struct _WebClientConnection_ {
    int serverPort;    
    int connDescriptor;    
    int runMode;   // SERVER_WEB or SERVER_CMXML 
    _WebConnection* conn;
} _WebClientConnection;

// Global data

#define HTTP_ACTION    "action="
#define HTTP_EDITFIELD "http_editfield="

#define IVIEW_HEAD1 "<gui>\n  <properties>\n    <project>anyRemote GUI</project>\n    <designer>anyRemote</designer>\n    <controlsystem>\n      <hostname>"
#define IVIEW_HEAD2 "</hostname>\n      <commandPort>"
#define IVIEW_HEAD3 "</commandPort>\n    </controlsystem>\n    <size>\n      <portrait width=\"320\" height=\"480\" />\n      <landscape width=\"480\" height=\"320\" />\n    </size>\n    <imagefolder>\n    </imagefolder>\n    <cache images=\"0\" />\n    <debug loaderrors=\"0\" />\n  </properties>"
#define IVIEW_TAIL  "</gui>"

/*int haveClients(_WebConnection* cn)
{
    mutexLock(M_WEB);
    int num = listSingleLength(cn->clientSockets);
    mutexUnlock(M_WEB);
    
    DEBUG2("[WS]: clients #%d", num);
    return (num > 0 ? 1 : 0);
}*/    

static const char* i2string(int data,char* buf)
{
    sprintf(buf,"%d",data);
    return buf;
}

void freeWMessage(void *ptr)
{
    wMessage *wm = (wMessage *) ptr;
    if (wm->string) {
        stringFree(wm->string,  BOOL_YES);
        wm->string = NULL;
    }
    free(wm);
}

static void sendToWebServer(wMessage *buf)
{
    if (queueExists(Q_WEB) == RC_OK && buf) {
        mutexLock(M_WEB);
        DEBUG2("send to web server %d %s", buf->button, (buf->string ? buf->string->str : "no text"));
        queuePush(Q_WEB, buf);
        mutexUnlock(M_WEB);
    }
}

static struct {
    char* ext;
    char* mime;
} _mimeTypes[] = {
    {".html", "text/html"},
    {".htm",  "text/html"},
    {".xml",  "text/xml" },
    {".jpg",  "image/jpeg"},
    {".jpeg", "image/jpeg"},
    {".gif",  "image/gif"},
    {".png",  "image/png"},
    {".css",  "text/css"},
    {".au",   "audio/basic"},
    {".wav",  "audio/wav"},
    {".avi",  "video/x-msvideo"},
    {".mpeg", "video/mpeg"},
    {".mpg",  "video/mpeg"},
    {".mp3",  "audio/mpeg"},
    {NULL,    NULL}
};

static const char *get_mime_type(const char *name)
{
    char *ext = strrchr(name, '.');
    if (!ext) {
        return NULL;
    }
    
    int i=0;
    while (_mimeTypes[i].ext) {
       if (strcmp(ext, _mimeTypes[i].ext) == 0) {
           INFO2("[WS]: Mime type: %s", _mimeTypes[i].mime);
           return _mimeTypes[i].mime;
       }
       i++;
    }
    return NULL;
}

static int sendData(_WebClientConnection* cc, const char *s)
{
    int bytes_total = strlen(s);
    
    int bytes_sent = send(cc->connDescriptor, s, strlen(s), MSG_NOSIGNAL);
    
    if (bytes_sent != bytes_total) {
        ERROR2("[WS]: Error on send data: sent %d from %d", bytes_sent, bytes_total);
        return -1;
    }
    return 0;
}

void sendCookie(_WebClientConnection* cc)
{
    if (cc->conn->secure == NO_COOKIE) {
        return;
    }

    char b[32];
    sendData(cc, "Set-Cookie: anyremote_id=");
    sprintf(b,"%ld",cc->conn->cookie);
    sendData(cc, b);
    sendData(cc, "\r\n");

    INFO2("[WS]: sendCookie %s",b);
}

static void sendHeaders(_WebClientConnection* cc, int status,
                        const char *title, const char *extra, const char *mime,
                        int length, time_t date)
{
    char f[4096];
    //INFO2("[WS]: sendHeaders %s",title);

    sprintf(f, "%s %d %s\r\n", PROTOCOL, status, title);
    sendData(cc, f);

    sprintf(f, "Server: %s\r\n", SERVER);
    sendData(cc, f);

    time_t now = time(NULL);
    char timebuf[128];
    strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&now));

    sprintf(f, "Date: %s\r\n", timebuf);
    sendData(cc, f);

    if (extra) {
        sprintf(f, "%s\r\n", extra);
        sendData(cc, f);
    }
    if (mime) {
        sprintf(f, "Content-Type: %s\r\n", mime);
        sendData(cc, f);
    }

    // Generated images can be saved to the file with the same name
    //sprintf(f, "Cache-Control: public, max-age=36000\r\n");
    sprintf(f, "Cache-Control: no-cache, must-revalidate\r\n");
    sendData(cc, f);

    if (length >= 0) {
        sprintf(f, "Content-Length: %d\r\n", length);
        sendData(cc, f);
    }
    sendCookie(cc);

    if (date != -1) {
        strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&date));
        sprintf(f, "Last-Modified: %s\r\n", timebuf);
        sendData(cc, f);
    }

    sprintf(f, "Connection: close\r\n\r\n");
    sendData(cc, f);
}

static void sendError(_WebClientConnection* cc, int status, char *title, char *extra, char *text)
{
    char f[4096];

    INFO2("[WS]: sendError %d", status);
    sendHeaders(cc, status, title, extra, "text/html", -1, -1);

    sprintf(f, "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\r\n", status, title);
    sendData(cc, f);

    if (status != 304) { // 304  - Not Modified

        sprintf(f, "<BODY><H4>%d %s</H4>\r\n", status, title);
        sendData(cc, f);

        sprintf(f, "%s\r\n", text);
        sendData(cc, f);

        sprintf(f, "</BODY></HTML>");
        sendData(cc, f);

    } else {
        time_t now = time(NULL);
        char timebuf[128];
        strftime(timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&now));

        sprintf(f, "Date: %s\r\n", timebuf);
        sendData(cc, f);
    }


    sprintf(f, "\r\n");
    sendData(cc, f);
}

static void sendFile(_WebClientConnection* cc, char *path, struct stat *statbuf)
{
    int fd = cc->connDescriptor;
    ssize_t bytes_sent;

    INFO2("[WS]: sendFile %s",path);

    /*FILE *file = fopen(path, "r");
    if (!file) {
        ERROR2("[WS]: Access denied: %s", path);
        sendError(cc, 403, "Forbidden", NULL, "Access denied.");
    */
    
    int fdout = open(path, O_RDONLY);
    if (fdout < 0) {
    
        ERROR2("[WS]: Access denied: %s", path);
        sendError(cc, 403, "Forbidden", NULL, "Access denied.");

    } else {

        int length = S_ISREG(statbuf->st_mode) ? statbuf->st_size : -1;
        sendHeaders(cc, 200, "OK", NULL, get_mime_type(path), length, statbuf->st_mtime);

#ifdef __FreeBSD__
        int err = sendfile(fdout, fd, 0, length, NULL, &bytes_sent, 0);
#else
        bytes_sent = sendfile(fd,fdout,NULL,length);
        int err = 0;
#endif
        /*while ((n = fread(data, 1, sizeof(data), file)) > 0) {
            //INFO2("read %d bytes from file",n);

            bytes_sent = send(fd,data,n,0);
            if (n != bytes_sent) {
                ERROR2("[WS]: Error on send file %s", path);
                break;
            }
        }*/
    
        if (err || length != bytes_sent) {
            ERROR2("[WS]: Error on send file %s", path);
        }

        close(fdout);
    }
    sendData(cc, "\r\n");
}

static void sendGeneratedImage(_WebClientConnection* cc, char *path)
{
    INFO2("[WS]: sendGeneratedImage %s", path);

    const char* home = getenv("HOME");
    if (!home) {
        return;
    }

    struct stat statbuf;

    string_t* img = stringNew(home);
    stringAppend(img,"/.anyRemote/");
    stringAppend(img,path);

    INFO2("[WS]: sendGeneratedImage full name %s", img->str);

    if (stat(img->str, &statbuf) >= 0) {
        sendFile(cc, img->str, &statbuf);
    } else {
        sendError(cc, 404, "Not Found", NULL, "File not found.");
    }
    stringFree(img, BOOL_YES);
}

static void sendIcon(_WebClientConnection* cc, char *path)
{
    INFO2("[WS]: sendIcon %s", path);
    
    struct stat statbuf;

    string_t* icon = stringNew(cc->conn->confDir ? cc->conn->confDir : ".");
    if (getIViewer()) {
        stringAppend(icon,"/Utils/iViewer");
        stringAppend(icon,path);
    } else {
        stringAppend(icon,"/Icons/");

        char b[32];
        stringAppend(icon,i2string(iconSize(),b));
        if (path[0] != '/') {
            stringAppend(icon,"/");
        }
        stringAppend(icon,path);
    }

    INFO2("[WS]: sendIcon full name %s", icon->str);

    if (stat(icon->str, &statbuf) >= 0) {
        sendFile(cc, icon->str, &statbuf);
    } else {
        sendError(cc, 404, "Not Found", NULL, "File not found.");
    }
    stringFree(icon, BOOL_YES);
}

static void sendCover(_WebClientConnection* cc, char *name)
{
    //INFO2("[WS]: sendCover %s", name);
    
    name++;
    char* finish = strstr(name,".cover");
    if (finish) {
         *finish = '\0';
    }
    
    string_t* file = findNamedCover(name);
    if (file) {
        struct stat statbuf;
        if (stat(file->str, &statbuf) >= 0) {
            sendFile(cc, file->str, &statbuf);
        } else {
            sendError(cc, 404, "Not Found", NULL, "File not found.");
        }
        stringFree(file, BOOL_YES); 
    } else {
        sendError(cc, 404, "Not Found", NULL, "File not found.");
    }
}

static void sendFavicon(_WebClientConnection* cc)
{
    struct stat statbuf;

    string_t* icon = stringNew(cc->conn->confDir ? cc->conn->confDir : ".");
    stringAppend(icon,"/Icons/anyRemote.png");

    if (stat(icon->str, &statbuf) >= 0) {
        sendFile(cc, icon->str, &statbuf);
    } else {
        //ERROR2("[WS]: sendFavicon can not open favicon file");
        sendError(cc, 404, "Not Found", NULL, "File not found.");
    }
    stringFree(icon, BOOL_YES);
}

static void sendIViewerGui(_WebClientConnection* cc) 
{
    INFO2("[WS]: sendIViewerGui");
    
    sendData(cc, IVIEW_HEAD1);
    
    if (!cc->conn->serverIP) {
        ERROR2("[WS]: sendIViewerGui can not determine TCP address");
        return;
    }
    sendData(cc, cc->conn->serverIP->str);
    
    sendData(cc, IVIEW_HEAD2);
     
    int tcpPort = getIViewerTcpPort();
    if (tcpPort < 0) {
        ERROR2("[WS]: sendIViewerGui can not determine TCP port");
        return;
    }
    char num[16];
    sprintf(num,"%d", tcpPort);
    
    sendData(cc, num);
    sendData(cc, IVIEW_HEAD3);

    string_t* guiFile = stringNew(cc->conn->confDir ? cc->conn->confDir : ".");
    stringAppend(guiFile,"/Utils/anyremote.gui");
    
    struct stat statbuf;
    if (stat(guiFile->str, &statbuf) >= 0) {
        sendFile(cc, guiFile->str, &statbuf);
    } else {
        ERROR2("[WS]: sendIViewerGui can not open GUI file");
    }
    sendData(cc, IVIEW_TAIL);
}

void addLink(string_t* ip, string_t* addTo, int port, boolean_t trailSlash)
{
    stringAppend(addTo, "http://");
    if (ip) {
        stringAppend(addTo, ip->str);
    } else {
        ERROR2("[WS]: server IP not determined");
    }
    stringAppend(addTo, ":");

    char num[16];
    sprintf(num,"%d", port);

    stringAppend(addTo, num);
    if (trailSlash) {
        stringAppend(addTo, "/");
    }
}

/*void sendFormOld(_WebClientConnection* cc, string_t* httpPageH, string_t* httpPageT, boolean_t cookie)
{
    
    string_t* header = stringNew("HTTP/1.1 200 OK\r\n");

    if (cc->runMode == SERVER_CMXML) {
        stringAppend(header, "Content-type: text/xml; charset=UTF-8\r\nConnection: close\r\n");
    } else {
        stringAppend(header, "Content-type: text/html\r\nCache-Control: no-cache, must-revalidate\r\n");
    }

    if (cc->conn->refreshPage > 0) {
        stringAppend(header, "Refresh: ");

        char num[16];
        sprintf(num,"%d", cc->conn->refreshPage);
        stringAppend(header, num);

        stringAppend(header, "; url=");
        addLink(cc->conn->serverIP, header, cc->port, BOOL_YES);

        stringAppend(header, "\r\n");
    } else {
        if (runMode == SERVER_CMXML) {
            stringAppend(header, "Expires: -1\r\n");
        }
    }

    //printf("HEADER: %s",header->str);
    sendData(cc, header->str);

    if (cookie) {
        sendCookie(cc);
    }

    sendData(cc, "\r\n");

    sendData(cc, httpPageH->str);
    //printf("%s",httpPageH->str);

    //if refresh < 0:
    //    rStr = "%s" % (uploadTmout)
    //    string_t* httpRate = stringNew('HTTP-EQUIV=REFRESH CONTENT=\"' + rStr + ';URL=/\"');
    //sendData(cc, httpRate->str);
    //stringFree(httpRate,  BOOL_YES);

    sendData(cc, httpPageT->str);
    //printf("%s",httpPageT->str);
}*/

void sendForm(_WebClientConnection* cc, string_t* content, boolean_t cookie)
{
    string_t* header = stringNew("HTTP/1.1 200 OK\r\n");

    if (cc->runMode == SERVER_CMXML) {
        stringAppend(header, "Content-type: text/xml; charset=UTF-8\r\nConnection: close\r\n");
    } else {
        stringAppend(header, "Content-type: text/html\r\nCache-Control: no-cache, must-revalidate\r\n");
    }

    if (cc->conn->refreshPage > 0) {
        stringAppend(header, "Refresh: ");

        char num[16];
        sprintf(num,"%d", cc->conn->refreshPage);
        stringAppend(header, num);

        stringAppend(header, "; url=");
        addLink(cc->conn->serverIP, header, cc->serverPort, BOOL_YES);

        stringAppend(header, "\r\n");
    } else {
        if (cc->runMode == SERVER_CMXML) {
            stringAppend(header, "Expires: -1\r\n");
        }
    }

    sendData(cc, header->str); 
    //printf("%s",header->str); 
    
    stringFree(header, BOOL_YES);

    if (cookie) {
        sendCookie(cc);
    }

    sendData(cc, "\r\n");
    //printf("\r\n"); 

    sendData(cc, content->str);
    //printf("%s",content->str);
}

long parseCookie(char* buffer)
{
    //DEBUG2("[WS]: parseCookie line: %s", buffer);
    if (!buffer) {
        return 0;
    }

    char* start = buffer;
    char* startc = NULL;

    while ((start = strstr(start, "anyremote_id="))) {
        startc = start;
        start += 13;  // size("anyremote_id=") == 12

    }
    if (!startc) {
        return 0;
    }
    startc += 13;

    //DEBUG2("[WS]: parseCookie start: %s", startc);

    start = startc;
    while (startc && isdigit(*startc)) {
        startc++;
    }
    *startc = '\0';
    //DEBUG2("[WS]: parseCookie number: %s", start);

    return atol(start);
}

boolean_t handleMenuOnList(int midx, int lidx, int tvisits)
{
    mutexLock(M_STATE);
    
    int iLen = lfSize();
    int mLen = menuSize();
    
    mutexUnlock(M_STATE);

    if (lidx < iLen &&             //  negative list index means empty list
        midx >= 0 && midx < mLen) {

        wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        wm->string = stringNew("Msg:");
        
        mutexLock(M_STATE);
        
        SingleList *menuItem = menuNth(midx);
        stringAppend(wm->string, ((string_t*) menuItem->data)->str);

        stringAppend(wm->string, "(");
        char b[32];
        stringAppend(wm->string, i2string(lidx + 1,b));
        stringAppend(wm->string, ",");

        if (lidx >= 0) {
            SingleList *ptr = lfListNth(lidx);
            stringAppend(wm->string, ((ListItem*) ptr->data)->string->str);
            INFO2("[WS]: choosed list item (%s)", ((ListItem*) ptr->data)->string->str);
        } else {         // list was empty
            INFO2("[WS]: choosed list item in empty list");
        }
        
        mutexUnlock(M_STATE);

        stringAppend(wm->string, ")");

        sendToWebServer(wm);
        return BOOL_YES;
    } else {
        INFO2("[WS]: (%d) Wrong selected index in list, ignore.", tvisits);
    }
    return BOOL_NO;
} 

boolean_t securityCheck(_WebClientConnection* cc, int tvisits, long c, char* path)
{
    boolean_t passed = BOOL_YES;
   
    if (getUsePassword()) {
   
        INFO2("[WS]: (%d) secure mode", tvisits);
       
        if (cc->conn->secure == COOKIE_SENT) {
            
            INFO2("[WS]: (%d) secure mode, test cookie", tvisits);

            if (c != cc->conn->cookie) {
                INFO2("[WS]: (%d) wrong cookie (wait for %ld)", tvisits, cc->conn->cookie);
                passed = BOOL_NO;
            } else {
                INFO2("[WS]: (%d) cookie OK (%ld)", tvisits, cc->conn->cookie);
            }
        } else if (cc->conn->secure == NO_COOKIE) {
        
            INFO2("[WS]: (%d) secure mode, ask for pass", tvisits);
            passed = BOOL_NO;
    
        } else { // cc->conn->secure == NEED_SEND_COOKIE
        
            INFO2("[WS]: (%d) secure mode, retrieve password", tvisits);
        
            char* p = NULL;

            if ((p = strstr(path, HTTP_EDITFIELD))) {  // edit field

                char * item = strstr(path, HTTP_ACTION);

                if (!item) {
                    INFO2("[WS]: (%d) No data in edit field, ignore", tvisits);
                    passed = BOOL_NO;
                } else {

                    item += strlen(HTTP_ACTION);
                    INFO2("[WS]: (%d) edit field %s", tvisits, item);

                    char* plus = NULL;
                    while((plus = strstr(item,"+"))) {
                        *plus = ' ';  // replace "+" back to spaces
                    }

                    char * amp = strstr(p,"&");
                    *amp = '\0';
                    char* index = p + strlen(HTTP_EDITFIELD);

                    if (strcmp("Ok", item) == 0 || strcmp("Select", item) == 0) {              // WEB edit field OK
                        
                        string_t* cmd = stringNew("Msg:_PASSWORD_");
                        stringAppend(cmd, "(,");
                        stringAppend(cmd, index);
                        stringAppend(cmd, ")");

                        if (checkPassword(cmd->str)) {
                            // hack to send response with current form
                            *path = '/';
                            *(path+1) = '\0';

                            srandom((unsigned int) time(NULL));
                            cc->conn->cookie = random();

                        } else {
                            INFO2("[WS]: (%d) secure mode, wrong password", tvisits);
                            passed = BOOL_NO;
                        }
                        stringFree(cmd, BOOL_YES);
                    } else {                                    // WEB edit field Cancel
                        INFO2("[WS]: (%d) secure mode, cancel pressed", tvisits);
                        passed = BOOL_NO;
                    }
                }
        
            } else if ((p = strstr(path,XML_EFIELD_SUBMIT))) {  // CMXML edit field OK

                char * item = p+strlen(XML_EFIELD_SUBMIT);
                INFO2("[WS]: (%d) Entered value %s", tvisits, item);

                string_t* cmd = stringNew("Msg:_PASSWORD_");
                stringAppend(cmd, "(,");
                stringAppend(cmd, item);
                stringAppend(cmd, ")");
        
                if (checkPassword(cmd->str)) {
                    // hack to send response with current form
                    *path = '/';
                    *(path+1) = '\0';

                    srandom((unsigned int) time(NULL));
                    cc->conn->cookie = random();
                } else {
                    passed = BOOL_NO;
                }
                stringFree(cmd, BOOL_YES);

            } else if ((p = strstr(path,XML_EFIELD_CANCEL))) {  // CMXML edit field Cancel
                INFO2("[WS]: (%d) secure mode, cancel pressed XML", tvisits);
                passed = BOOL_NO;
            } else {
                INFO2("[WS]: (%d) secure mode, request password", tvisits);
                passed = BOOL_NO;
            }
        }
    
        if (passed == BOOL_NO) {

            string_t* content = (cc->runMode == SERVER_CMXML ? 
                                 renderPassXMLForm (cc->conn->serverIP, cc->serverPort) : 
                                 renderPassHTMLForm(cc->conn->serverIP, cc->serverPort));
            if (content) {
                cc->conn->secure = NEED_SEND_COOKIE;
                sendForm(cc, content, BOOL_NO);
                stringFree(content, BOOL_YES);
            }
        } else {
            cc->conn->secure = COOKIE_SENT;
        }
    }
    
    return passed;
}

void sendButtonPress(int button)
{
    wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
    wm->button = button;
    wm->string = (void*) NULL;
    
    sendToWebServer(wm);
}
  
boolean_t processHtmlButtonPress(int tvisits, char* p)
{  
    *p = '\0';
    p--;

    while (isdigit(*p)) {
        p--;
    }
    int button = ((++p == '\0') ? -1 : atoi(p)+1);

    INFO2("[WS]: (%d) Got button %d", tvisits, button);
    
    sendButtonPress(button);
    
    return BOOL_YES;
}

boolean_t processXmlButtonPress(int tvisits, char* p)
{ 
    p += strlen(XML_BUTTON_PRESS);

    int button = atoi(p);

    INFO2("[WS]: (%d) Got button XML %d", tvisits, button);

    sendButtonPress(button);
    
    return BOOL_YES;
}

boolean_t processHtmlMenu(int tvisits, char* p)
{ 
    *p = '\0';
    p--;

    while (*p != '/') {
        p--;
    }

    INFO2("[WS]: (%d) Got menu item %s", tvisits, ++p);

    wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
    wm->button = -1;
    wm->string = stringNew("");
    char * ptr = p;
    char * p2 =  NULL;

    while ((p2 = strstr(ptr,"%20"))) {   // replace %20 to space
        *p2 = '\0';
        stringAppend(wm->string, ptr);
        stringAppend(wm->string, " ");

        ptr = (p2 + 3);
    }
    stringAppend(wm->string, ptr);

    sendToWebServer(wm);
    return BOOL_YES;
}

boolean_t processXmlShortMenu(int tvisits, char* p)
{ 
    boolean_t wait = BOOL_NO;
    
    p += strlen(XML_SHORT_MENU);

    INFO2("[WS]: (%d) Got menu item %s", tvisits, p);

    int idx = atoi(p) - 1;
    mutexLock(M_STATE);
    int iLen = menuSize();
    mutexUnlock(M_STATE);

    if (idx >= 0 && idx < iLen) {
        mutexLock(M_STATE);
        
        SingleList *menuItem = menuNth(idx);
        if (menuItem) {

            wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
            wm->button = -1;
            wm->string = stringNew("");
            stringAppend(wm->string, ((string_t*) menuItem->data)->str);

            sendToWebServer(wm);
            wait = BOOL_YES;
        } else {
            ERROR2("[WS]: Can not get list item #%d from list having %d items", idx, iLen);
        }
        mutexUnlock(M_STATE);
    } else {
        INFO2("[WS]: (%d) Wrong selected index in list, ignore..", tvisits);
    }
    
    return wait;
}

boolean_t processXmlListMenu(int tvisits, char* p)
{ 
    p += strlen(XML_LIST_MENU);
    char * q = strstr(p,"?");
    *q = '\0';
    q++;

    INFO2("[WS]: (%d) Got list menu item (%s,%s)", tvisits, p, q);

    int menu_idx = atoi(p) - 1;
    int list_idx = atoi(q);

    return handleMenuOnList(menu_idx, list_idx, tvisits);
}

boolean_t processXmlListMenu2(int tvisits, char* p)
{ 
    boolean_t wait = BOOL_NO;
    
    p += strlen(XML_LIST_MENU2);

    INFO2("[WS]: (%d) Got list menu item %s", tvisits, p);

    char * item = strstr(p,",");
    if (!item) {
        INFO2("[WS]: (%d) Improper item in list, ignore", tvisits);
    } else {

        (*item) = '\0';
        item++;

        int lidx = atoi(p);
        int midx = atoi(item) - 1;

        if (handleMenuOnList(midx, lidx, tvisits)) {
            wait = BOOL_YES;
        }
    }
    return wait;
}

void processXmlLongMenu(_WebClientConnection* cc, int tvisits, char* p)
{ 
    p += strlen(XML_LONG_MENU);

    INFO2("[WS]: (%d) Got menu wrapper %s", tvisits, p);

    string_t* content = sendXMLMenu(atoi(p), cc->conn->serverIP, cc->serverPort, -1);
    if (content) {
        sendForm(cc, content, BOOL_YES);
        stringFree(content, BOOL_YES);
    }
}

void processXmlExtMenu(_WebClientConnection* cc, int tvisits, char* p)
{ 
    p += strlen(XML_LIST_MENU_EXT);
    p++;

    INFO2("[WS]: (%d) Got list item %s", tvisits, p);

    int listItem = atoi(p);

    string_t* content = sendXMLMenu(LI, cc->conn->serverIP, cc->serverPort, listItem);
    if (content) {
        sendForm(cc, content, BOOL_YES);
        stringFree(content, BOOL_YES);
    }
}

boolean_t processHtmlListMenu(int tvisits, char* p, char* path)
{ 
    boolean_t wait = BOOL_NO;
    
    char * item = strstr(path, HTTP_ACTION);

    if (!item) {
        INFO2("[WS]: (%d) No selected item in list, ignore", tvisits);
    } else {

        if (item) {
            item += strlen(HTTP_ACTION);
        }

        char* plus = NULL;
        while((plus = strstr(item,"+"))) {
            *plus = ' ';  // replace "+" back to spaces
        }

        char * amp = strstr(p,"&");
        *amp = '\0';
        char* index = p + 5;  // size("list=") == 5

        int idx = atoi(index);
        mutexLock(M_STATE);
        
        int iLen = lfSize();

        if (idx >= 0 && idx < iLen) {

            wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
            wm->button = -1;
            wm->string = stringNew("Msg:");
            stringAppend(wm->string, item);
            stringAppend(wm->string, "(");
            char b[32];
            stringAppend(wm->string, i2string(idx+1,b));
            stringAppend(wm->string, ",");

            SingleList *ptr = lfListNth(idx);
            stringAppend(wm->string, ((ListItem*) ptr->data)->string->str);
            INFO2("[WS]: choosed list item (%s)", ((ListItem*) ptr->data)->string->str);

            stringAppend(wm->string, ")");

            sendToWebServer(wm);
            wait = BOOL_YES;
        } else {
            INFO2("[WS]: (%d) Wrong selected index in list, ignore...", tvisits);
        }
        
        mutexUnlock(M_STATE);
    }
    
    return wait;
}
       
boolean_t processHtmlEfield(_WebClientConnection* cc, int tvisits, char* p, char* path)
{ 
    boolean_t wait = BOOL_NO;
    
    char * item = strstr(path, HTTP_ACTION);
    if (!item) {
        INFO2("[WS]: (%d) No data in edit field, ignore", tvisits);
    } else {

        item += strlen(HTTP_ACTION);
        INFO2("[WS]: (%d) edit field %s", tvisits, item);

        char* plus = NULL;
        while((plus = strstr(item,"+"))) {
            *plus = ' ';  // replace "+" back to spaces
        }

        char * amp = strstr(p,"&");
        *amp = '\0';
        char* index = p + strlen(HTTP_EDITFIELD);

        wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        
        if (efPassword()) {  // password was asked from cfg.file so do not handle it internally
            
            // reset coockie
            cc->conn->secure = NEED_SEND_COOKIE;
            srandom((unsigned int) time(NULL));
            cc->conn->cookie = random();
           
            if (strcmp("Ok", item) == 0) {
                wm->string = stringNew("Msg:_PASSWORD_(,");
                stringAppend(wm->string, index);
                stringAppend(wm->string, ")");

                setEfPassword(BOOL_NO);
            }
        } else {
            wm->string = stringNew("Msg:");
            stringAppend(wm->string, item);
        
            stringAppend(wm->string, "(,");
            stringAppend(wm->string, index);
            stringAppend(wm->string, ")");
        }
        sendToWebServer(wm);
        wait = BOOL_YES;
    }
    return wait;
}

boolean_t processXmlEfieldSubmit(_WebClientConnection* cc, int tvisits, char* p)
{ 
    char * item = p+strlen(XML_EFIELD_SUBMIT);
    INFO2("[WS]: (%d) Entered value %s", tvisits, item);

    wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
    wm->button = -1;
    if (efPassword()) {
    
        // reset coockie
        cc->conn->secure = NEED_SEND_COOKIE;
        srandom((unsigned int) time(NULL));
        cc->conn->cookie = random();
        
        wm->string = stringNew("Msg:_PASSWORD_");
        setEfPassword(BOOL_NO);
    } else {
        wm->string = stringNew("Msg:Ok");
    }
    stringAppend(wm->string, "(,");
    stringAppend(wm->string, item);
    stringAppend(wm->string, ")");

    sendToWebServer(wm);
    return  BOOL_YES;
          
}
boolean_t processXmlEfieldCancel()
{           
    wMessage* wm = (wMessage*) malloc(sizeof(wMessage));
    wm->button = -1;
    wm->string = stringNew("Msg:Back(,)");
    sendToWebServer(wm);
    
    return BOOL_YES;
} 

static struct {
    string_t* (*hooks[6]) (string_t*, int) ; // CF,TX,LI,FM,WM,EF
} _renderHooks[] = {
    //                  CF                  TX                  LI                  FM    WM                  EF 
    /* SERVER_WEB  */ {{renderCtrlHTMLForm, renderTextHTMLForm, renderListHTMLForm, NULL, renderWmanHTMLForm, renderEditHTMLForm}},
    /* SERVER_CMXML*/ {{renderCtrlXMLForm,  renderTextXMLForm,  renderListXMLForm,  NULL, renderWmanXMLForm,  renderEditXMLForm }}
};


static void renderForm(int tvisits, _WebClientConnection* cc)
{
    int f   = curForm() - 1;
    int idx = (cc->runMode == SERVER_CMXML ? 1 : 0);
    
    INFO2("[WS]: (%d) renderForm %d", tvisits, f);
    
    if (_renderHooks[idx].hooks[f]) {
        string_t* content = _renderHooks[idx].hooks[f](cc->conn->serverIP, cc->serverPort);
        if (content) {
             sendForm(cc, content, BOOL_YES);
             stringFree(content, BOOL_YES);
        }
    }
}

// separate thread for each request
pointer_t serveRequest(pointer_t data)
{
    char buf[4096];
    char *method = NULL;
    char *path   = NULL;
    struct stat statbuf;
    
    signal(SIGPIPE, SIG_IGN);  // why id did not inherit this ?

    _WebClientConnection* cc = (_WebClientConnection*) data;

    int tvisits;

    INFO2("[WS]: -------------------------------------------------------");

    mutexLock(M_WEB);
    tvisits = ++visits;
    mutexUnlock(M_WEB);
    
    INFO2("[WS]: (%d) process request", tvisits);

    /*if (answerReady == BOOL_NO) {
        INFO2("[WS]: (%d) drop request", tvisits);
        sendError(cc, 304, "Please wait", NULL, "Please wait.");
        return (void*) -1;
    }*/

    int wasRead = read(cc->connDescriptor, buf, sizeof(buf)-1);
    if (wasRead<0) {
        free(data);
        return (void*) -1;
    }
    buf[wasRead] = '\0';

    INFO2("[WS]: (%d) URL: %s", tvisits, buf);

    char * cookieLine = NULL;

    char* firstLine = strtok(buf, "\r");

    int rq = RQ_UNKNOWN;
    if (!firstLine) {
        INFO2("[WS]: non-valid request >%s<",buf);
        free(data);
        return (void*) -1;
    }

    if (strstr(firstLine,"POST ")) {
        rq = RQ_POST;
    } else if (strstr(firstLine,"GET ")) {
        rq = RQ_GET;
    }

    char * line       = NULL;
    char * screenDef  = NULL;
    char * screenDef2 = NULL;
    while ((line = strtok(NULL, "\r"))) {
        if (strstr(line,"Cookie:")) {
            cookieLine = line;
        }
        if (rq == RQ_POST) {
            path = line;  // it will be last one
        }

        if (cc->runMode == SERVER_CMXML && strstr(line,"x-CiscoIPPhoneDisplay:")) {
            screenDef = line;
        }
        if (cc->runMode == SERVER_CMXML && strstr(line,"x-CiscoIPPhoneModelName:")) {
            screenDef2 = line;
        }
    }

    //parse path for GET request" GET /path HTTP/1.1
    method = strtok(firstLine, " ");
    if (rq == RQ_GET) {
        //path   = strtok(NULL, " ");
        path = firstLine + 4; // "GET "
        char *ptr = strstr(path," HTTP");
        if (ptr) {
            *ptr = '\0';
        }
    }

    long c = parseCookie(cookieLine);

    INFO2("[WS]: (%d) Path: >%s<",  tvisits, path);
    INFO2("[WS]: (%d) Cookie: %ld", tvisits, c);

    if (screenDef) {
        parseScreenDef(screenDef);
    }
    if (screenDef2) {
        parseCiscoModel(screenDef2);
    }

    char* p = NULL;

    boolean_t wait    = BOOL_NO;

    boolean_t allowed = securityCheck(cc, tvisits, c, path);  // if fail, generates edit field to ask pass inside

    if (allowed) {

        if (rq == RQ_UNKNOWN) {

            ERROR2("[WS]: (%d) Method %s is not supported", tvisits, (method?method:"UNKNOWN"));
            sendError(cc, 501, "Not supported", NULL, "Method is not supported.");

        } else if (strcmp(path,"/")  == 0 || strcmp(path,"\n") == 0) {

            renderForm(tvisits, cc);
            //wait = BOOL_YES;

        } else if (strncmp(path,"/?name=SEP", 10) == 0) {
        
            // URL: GET /?name=SEP0014F2C23169&locale=United_States_English HTTP/1.0
            renderForm(tvisits, cc);
            //wait = BOOL_YES;

        } else if (strcmp(path,"/favicon.ico") == 0) {

            sendFavicon(cc);

        } else if (strstr((path+1),"/")  == NULL && strstr(path,".png")) {    // icon

            sendIcon(cc, path);

        } else if (strstr((path+1),"/")  == NULL && strstr(path,".cover")) {  // named cover

            sendCover(cc, path);
            
        } else if ((p = strstr(path,".key"))) {  // button pressed, web server

            wait = processHtmlButtonPress(tvisits,p);

        } else if ((p = strstr(path,XML_BUTTON_PRESS))) {  // button pressed, cm-xml

            wait = processXmlButtonPress(tvisits,p);

        } else if ((p = strstr(path,".menu"))) {  // menu in text screen

            wait = processHtmlMenu(tvisits, p);

        } else if ((p = strstr(path,XML_SHORT_MENU))) {  // menu (shorter than XML_SOFTKEY_NUM items) in CMXML

            wait = processXmlShortMenu(tvisits, p);

        } else if ((p = strstr(path,XML_LIST_MENU))) {  // list short menu handling in CMXML
             
            wait = processXmlListMenu(tvisits, p);
        
        } else if ((p = strstr(path,XML_LIST_MENU2))) {  // list menu handling (2nd step) in CMXML

           wait = processXmlListMenu2(tvisits, p);

        } else if ((p = strstr(path, XML_LONG_MENU))) {  // menu (more than XML_SOFTKEY_NUM items) in CMXML

            processXmlLongMenu(cc, tvisits, p);
        
        } else if ((p = strstr(path, XML_LIST_MENU_EXT))) {  // list menu extension in CMXML

            processXmlExtMenu(cc, tvisits, p);

        } else if ((p = strstr(path,"list="))) {  // menu in list screen, list item choosed

           wait = processHtmlListMenu(tvisits, p, path);
        
        } else if ((p = strstr(path, HTTP_EDITFIELD))) {  // edit field

            wait = processHtmlEfield(cc, tvisits, p, path);
        
        } else if ((p = strstr(path,XML_EFIELD_SUBMIT))) {  // CMXML edit field OK

            wait = processXmlEfieldSubmit(cc, tvisits, p);

        } else if ((p = strstr(path,XML_EFIELD_CANCEL))) {  // CMXML edit field Cancel

            wait = processXmlEfieldCancel();

        } else if ((p = strstr(path, HTTP_ACTION))) {  // menu in list screen, no list item choosed
            
            // or key press
            p += strlen(HTTP_ACTION);

            char* btn = p;
            while (isdigit(*btn)) {
                btn++;
            }

            int button = -1;
            if (btn != p) {
                *btn = '\0';
                button = atoi(p);
            }

            wMessage* wm = NULL;

            if (button > 0) {

                INFO2("[WS]: (%d) Got button %d", tvisits, button);

                wm = (wMessage*) malloc(sizeof(wMessage));
                wm->button = button;
                wm->string = (void*) NULL;

            } else {
                char* plus = NULL;
                while((plus = strstr(p,"+"))) {
                    *plus = ' ';  // replace "+" back to spaces
                }

                wm = (wMessage*) malloc(sizeof(wMessage));
                wm->button = -1;
                wm->string = stringNew("Msg:");
                stringAppend(wm->string, p);
                stringAppend(wm->string, "(,)");
            }

            if (wm) {
                sendToWebServer(wm);
                wait = BOOL_YES;
            }

        } else if (strstr(path,"xml_layout")) {  // ControlForm as image in CMXML

            sendGeneratedImage(cc, "layout_xml.png");

        } else if (strstr(path,"xml_image")) {   // WindowManager screen as image in CMXML

            renderXMLImage();
            sendGeneratedImage(cc, "image_xml.png");

        } else if (strstr(path,"anyremote.gui")) {   // iViewer GUI file reguest
        
            sendIViewerGui(cc);
       
        } else if (strstr(path,"generated_cover")) {    // cover for iViewer
            
            sendGeneratedImage(cc, "generated_cover");
     
        } else { // treat as file/dir name

            string_t* fpath = stringNew("");

            if (getIViewer()) {   // search images in $(CfgData)/Icons/iViewer

                stringAppend(fpath, cc->conn->confDir);
                stringAppend(fpath,"/Utils/iViewer/");
            }

            stringAppend(fpath, path);

            if (stat(fpath->str, &statbuf) < 0) {
                ERROR2("[WS]: (%d) File %s not found", tvisits, fpath->str);
                sendError(cc, 404, "Not Found", NULL, "File not found.");

                /*} else if (S_ISDIR(statbuf.st_mode)) {

                    //INFO2("[WS]: (%d)  Directory listing requested", tvisits);

                    len = fpath->len;
                    if (len == 0 || fpath->str[len - 1] != '/') {

                        snprintf(pathbuf, sizeof(pathbuf), "Location: %s/", fpath->str);
                        sendError(cc, 302, "Found", pathbuf, "Directories must end with a slash.");

                    } else {
                        snprintf(pathbuf, sizeof(pathbuf), "%sindex.html", path);
                        if (stat(pathbuf, &statbuf) >= 0) {

                            sendFile(cc, pathbuf, &statbuf);

                        } else {
                            DIR *dir;
                            struct dirent *de;

                            sendHeaders(cc, 200, "OK", NULL, "text/html", -1, statbuf.st_mtime);
                            sprintf(f, "<HTML><HEAD><TITLE>Index of %s</TITLE></HEAD>\r\n<BODY>", fpath->str);
                            bytes_sent = send(fd,f,strlen(f),0);

                            sprintf(f, "<H4>Index of %s</H4>\r\n<PRE>\n", path);
                            sendData(cc, f);

                            sendData(cc, "Name Last Modified Size\r\n");
                            sendData(cc, "<HR>\r\n");

                            //if (len > 1) {
                            //    sprintf(f, "<A HREF=\"..\">..</A>\r\n");
                            //    sendData(fd, f);
                        //}
                            //INFO2("[Thread] Parse %s\n", path);

                            dir = opendir(fpath->str);
                            while ((de = readdir(dir)) != NULL) {
                                char timebuf[32];
                                struct tm *tm;
                                strcpy(pathbuf, path);
                                strcat(pathbuf, de->d_name);

                                stat(pathbuf, &statbuf);
                                tm = gmtime(&statbuf.st_mtime);
                                strftime(timebuf, sizeof(timebuf), "%d-%b-%Y %H:%M:%S", tm);

                                sprintf(f, "<A HREF=\"%s%s\">", de->d_name, S_ISDIR(statbuf.st_mode) ? "/" : "");
                                sendData(cc, f);

                                sprintf(f, "%s%s", de->d_name, S_ISDIR(statbuf.st_mode) ? "/</A>" : "</A> ");
                                sendData(cc, f);

                                if (_D_EXACT_NAMLEN(de) < 32) {
                                    sprintf(f, "%*s", (int) (32 - _D_EXACT_NAMLEN(de)), "");
                                    sendData(cc, f);
                                }
                                if (S_ISDIR(statbuf.st_mode)) {
                                    sprintf(f, "%s\r\n", timebuf);
                                    sendData(cc, f);
                                } else {
                                    sprintf(f, "%s %10d\r\n", timebuf, (int) statbuf.st_size);
                                    sendData(cc, f);
                                }
                            }
                            closedir(dir);

                            sprintf(f, "</PRE>\r\n<HR>\r\n<ADDRESS>%s</ADDRESS>\r\n</BODY></HTML>\r\n", SERVER);
                        }
                    }*/

            } else {

                INFO2("[WS] (%d) Send file", tvisits);
                sendFile(cc, fpath->str, &statbuf);
            }

            stringFree(fpath, BOOL_YES);
        }
    }
    
    if (wait) {
        mutexLock(M_WEB);
        answerReady = BOOL_NO;
        mutexUnlock(M_WEB);

        int waitingTime = 0;

        while (!answerReady && waitingTime < 1500) {    // 1/50*1500 = 30 sec
            // Main loop timer (1/50 of second)
            usleep(20000);
            waitingTime++;
        }

        INFO2("[WS]: (%d) Wait answer for %d (1/50 sec)", tvisits, waitingTime);

        renderForm(tvisits, cc);
    }

    close(cc->connDescriptor);

    mutexLock(M_WEB);
    
    //INFO2("[WS]: (%d) clientSockets before remove #%d", tvisits, listSingleLength(cc->conn->clientSockets));
    cc->conn->clientSockets = listSingleRemove(cc->conn->clientSockets, CAST_INT_TO_POINTER(cc->connDescriptor));
    //INFO2("[WS]: (%d) clientSockets after remove #%d", tvisits, listSingleLength(cc->conn->clientSockets));
    
    free(data);

    mutexUnlock(M_WEB);

    INFO2("[WS]: (%d) Request processed", tvisits);
    threadExit(T_MAX);

    return (void*)0;
}

int webFD(ConnectInfo* conn)
{
    _WebConnection* cn = (_WebConnection*) conn->connectionData;
    if (!cn) {
        return -1;
    }
    return (conn->state == PEER_WAIT_ACCEPT || 
            conn->state == PEER_WAIT_LISTEN ? cn->serverFileDescriptor : -1);
}

static void freeConn(ConnectInfo* conn)
{
    INFO2("[WS]: freeConn");
    
    _WebConnection* cn = (_WebConnection*) conn->connectionData;
    
    mutexLock(M_WEB);
    SingleList* list = cn->clientSockets;
    while (list) {

        int clientFD = CAST_POINTER_TO_INT(list->data);
        if (clientFD > 0) {
            close(clientFD);
        }
        list = listSingleNext(list);
    }

    listSingleFree(cn->clientSockets);
    cn->clientSockets = NULL;
    
    mutexUnlock(M_WEB);

    if (cn->serverFileDescriptor != -1) {
        close(cn->serverFileDescriptor);
        cn->serverFileDescriptor = -1;
    }

    if (cn->serverIP) {
        stringFree(cn->serverIP, BOOL_YES);
        cn->serverIP = NULL;
    }
    
    if (cn->confDir) {
        free(cn->confDir);
        cn->confDir = NULL;
    }
    
    free(conn->connectionData);
    conn->connectionData = NULL;
}

static int openWebInternal(ConnectInfo* conn)
{
    INFO2("[WS]: openWebInternal");
    mutexNew(M_WEB); 
    queueNew(Q_WEB);
    
    if (conn->connectionData) {
        freeConn(conn);
    }
    
    conn->connectionData = (_WebConnection*) malloc(sizeof(_WebConnection));
    _WebConnection* cn = (_WebConnection*) conn->connectionData;
    
    cn->serverFileDescriptor = -1;
    cn->cookie         = 0;
    cn->secure         = NO_COOKIE;
    cn->confDir        = dupVarValue(VAR_CFGDIR);
    cn->clientSockets  = NULL;
    cn->serverIP       = NULL;

    if (conn->mode == SERVER_WEB) {
        initHtmlGenerator();
    }
    
    char* v2 = dupVarValue("RefreshPage");
    if (v2) {
        cn->refreshPage = atoi(v2);
        free(v2);
        if (cn->refreshPage <= 0) {
            cn->refreshPage = -1;
        }
    } else {
        cn->refreshPage = -1;
    }
    INFO2("[WS]: $(RefreshPage) = %d", cn->refreshPage);
    
    char* v3 = dupVarValue("IpAddr");
    if (v3 != NULL) {
        INFO2("[WS]: $(IpAddr) = %s", v3);
        cn->serverIP = stringNew(v3);
        free(v3);
    } else { // try to get it ourselves

        string_t* ip = getLocalIP();
        if (ip) {
            cn->serverIP = stringNew(ip->str);
            stringFree(ip,BOOL_YES);
        }

    }
    INFO2("[WS]: set Server IP as = %s", (cn->serverIP ? cn->serverIP->str : "not determined"));

    struct sockaddr_in sin;
    memset((void *) &sin, 0, sizeof(sin));

    cn->serverFileDescriptor = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (cn->serverFileDescriptor < 0) {
        logger(L_ERR, "[WS]: Failed to create a socket for web server");
        return -1;
    }

    int optval = 1;
    setsockopt(cn->serverFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(conn->port);
    int ret = bind(cn->serverFileDescriptor, (struct sockaddr *) &sin, sizeof(sin));
    if (ret < 0) {
        logger(L_ERR, "[WS]: webServerMain: Failed to bind a socket");
        printf("ERROR: on binding %d->%s\n", errno, strerror(errno));
        return -1;
    }
    

    return 0;
}

int openWeb(ConnectInfo* conn)
{
    DEBUG2("[DS]: Web Server mode. Use port %d", conn->port);
    if (openWebInternal(conn) < 0) {
        conn->state = PEER_DISCONNECTED;
        return EXIT_NOK;
    }
     
    conn->state = PEER_WAIT_LISTEN;
    return EXIT_OK;
}

int listenWeb(ConnectInfo* conn)
{
    logger(L_INF, "[DS]: Server mode: listen from web peer ");
    
    _WebConnection* cn = (_WebConnection*) conn->connectionData;
    if (!cn) {
        return -1;
    }
    
    int ret = listen(cn->serverFileDescriptor, 100);  // allow many request at once
    if (ret >= 0) {
        conn->state = PEER_WAIT_ACCEPT;
    }
    return (ret < 0 ? -1 : 1);
}

//
// Wait for incoming connection
//
int acceptWeb(ConnectInfo* conn)
{
    logger(L_INF, "[WS]: acceptWeb");
    
    _WebConnection* cn = (_WebConnection*) conn->connectionData;
    if (!cn) {
        return -1;
    }
    
    while (stillRun) {

        logger(L_INF, "[WS]: HTTP server ready to accept connection");

        int s = accept(cn->serverFileDescriptor, NULL, NULL);
        if (s < 0) {
            errnoDebug("[WS]: accept() ",errno);  // testing debug
            if (errno == EAGAIN) {
                continue;
            } else {
                return -1;
            }
        }

        char buf[INET6_ADDRSTRLEN];
        peerName(s,buf,INET6_ADDRSTRLEN);
 
        INFO2("[WS]: acceptWeb: HTTP server accepts connection from %s", buf);
        if (isAllowed(buf)) {
            INFO2("[WS]: acceptWeb: connection accepted");
             
            _WebClientConnection* ptr = (_WebClientConnection*) malloc(sizeof(_WebClientConnection));
            ptr->serverPort     = conn->port;
            ptr->connDescriptor = s;
            ptr->runMode        = conn->mode;
            ptr->conn           = cn;

            mutexLock(M_WEB);
            cn->clientSockets = listSingleAppend(cn->clientSockets, CAST_INT_TO_POINTER(s));
            mutexUnlock(M_WEB);

            int rq = threadNew(T_MAX, serveRequest, (void *) ptr, DETACHED);
            if (rq != RC_OK) {
                logger(L_ERR, "[WS]: acceptWeb: Can not run processing thread");
            }
        } else {
            INFO2("[WS]: acceptWeb: host %s is not in the list of accepted host, skip connection", buf);
            close(s);
        }
        break;
    }
    
    logger(L_INF, "[WS]: acceptWeb OK");
    return 1;
}

int checkWebPort(char* buf, int capacity)
{
    //logger(L_DBG,"[DS]: checkWebPort");

    if (queueExists(Q_WEB) != RC_OK) {
        return 0;
    }

    //logger(L_DBG,"[WS]: checkWebPort queueExists");

    int l = 0;

    // Verify commands from queue (timeout about 1/2 sec)
    wMessage* wm = (wMessage*) queuePop(Q_WEB);
    if (wm != NULL) {
        logger(L_DBG, "[DS]: Got event");

        if (wm->button > 0) {
            INFO2("[DS]: Button pressed %d", wm->button);
            char bbuf[16];
            if (wm->button == 10) {
                sprintf(bbuf,"Msg:%c",'*');
            } else if (wm->button == 11) {
                sprintf(bbuf,"Msg:%c",'0');
            } else if (wm->button == 12) {
                sprintf(bbuf,"Msg:%c",'#');
            } else {
                sprintf(bbuf,"Msg:%d",wm->button);
            }
            strncpy(buf,bbuf,capacity);
            l = strlen(buf);
        } else if (wm->string) {
            INFO2("[DS]: Data %s", wm->string->str);

            strncpy(buf,wm->string->str,capacity);
            l = strlen(wm->string->str);
        }
        freeWMessage(wm);
    }
    return l;
}


void webClose(ConnectInfo* conn, int final)
{
    INFO2("[WS]: webClose %d", final);
    
    //if (final) {  -- if app started then it will keep HTTP/XML connection opened
    
        _WebConnection* cn = (_WebConnection*) conn->connectionData;
        if (cn) {
            INFO2("[WS]: webClose serverIP %s", 
                  cn->serverIP && cn->serverIP->str ? cn->serverIP->str : "NULL");

            // close all sockets

            mutexLock(M_WEB);

            INFO2("[WS]: clientSockets before cleanup #%d", listSingleLength(cn->clientSockets));

            SingleList* list = cn->clientSockets;
            while (list) {

                int clientFD = CAST_POINTER_TO_INT(list->data);
                if (clientFD > 0) {
                    close(clientFD);
                }
                list = listSingleNext(list);
            }

            listSingleFree(cn->clientSockets);
            cn->clientSockets = NULL;

            mutexUnlock(M_WEB);

            if (cn->serverFileDescriptor >= 0) {
                if (final) {
                    logger(L_INF, "closeSocket close web server socket");
                }
                close(cn->serverFileDescriptor);
                cn->serverFileDescriptor = -1;
            }

            if (cn->serverIP) {
                INFO2("[WS]: serverIP %s", cn->serverIP->str);
                stringFree(cn->serverIP, BOOL_YES);
            }

            if (cn->confDir) {
                free(cn->confDir);
                cn->confDir = NULL;
            }
            mutexRemove(M_WEB);
            queueRemove(Q_WEB, freeWMessage);

            free(conn->connectionData);
            conn->connectionData = NULL;
        }
    //}
    
    conn->state = PEER_DISCONNECTED;
    return;
}

void webReset(ConnectInfo* conn)
{
    webClose(conn, 0);
}

int writeWebConnStr(const char* value, int mode)
{
    DEBUG2("[WS]: writeWebConnStr %d", (value ? (int) strlen(value) : -1));

    wMessage* wm  = NULL;
    wMessage* wm2 = NULL;

    char * cmd = strdup(value);

    //boolean_t skipSpaces = BOOL_NO;
    //INFO2("[WS]: parse lenght %d", (cmd ? (int) strlen(cmd) : 0));

    stripCommandEnding(cmd);

    if (strlen(cmd) < MAXMAXLEN) {
        INFO2("[WS]: parse %d %s", curForm(), cmd);
    } else {
        INFO2("[WS]: parse %d ... command too long ...", curForm());
    }

    char* token = strtok(cmd,",");
    while (isspace(*token)) {
        ++token;
    }

    mutexLock(M_WEB);

    if (strncmp(token,"Get(password",12) == 0) {
        // user can add Get(password) inside cfg.file, so handle it also
        setPassField();
    } else if (strncmp(token,"Get(screen_size",16) == 0) {

        int w = htmlScreenWidth ();
        int h = htmlScreenHeight();
        if  (mode == SERVER_CMXML) {
            w = xmlScreenWidth ();
            h = xmlScreenHeight();
        }
        
        char b[32];

        wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        wm->string = stringNew("Msg:SizeX(");
    
        const char *buf = i2string(w,b);
        stringAppend(wm->string,buf);
    
        stringAppend(wm->string,",)");

        wm2 = (wMessage*) malloc(sizeof(wMessage));
        wm2->button = -1;
    
        wm2->string = stringNew("Msg:SizeY(");
    
        buf = i2string(h,b);
        stringAppend(wm2->string,buf);
    
        stringAppend(wm2->string,",)");

    } else if (strncmp(token,"Get(icon_padding",16) == 0) {

        char b[32];
        const char *buf = i2string(iconPadding(),b);

        wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        wm->string = stringNew("Msg:IconPadding(");
        stringAppend(wm->string,buf);
        stringAppend(wm->string,",)");

    } else if (strncmp(token,"Get(model",9) == 0) {

        wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        wm->string = stringNew("WebInterface");

    } else if (strncmp(token,"Get(is_exists",13) == 0) {

        char* what = strtok(NULL,",");
        char* sz   = strtok(NULL,",");
          
        if (sz && what) {
            
            int isCover = strcmp("cover",what);

            while (isspace(*sz)) {
                ++sz;
            }
            while (isspace(*what)) {
                ++what;
            }
            
            char* icon = NULL;
            if (isCover != 0) {
                icon = strtok(NULL,",");
            }

            wm = (wMessage*) malloc(sizeof(wMessage));
            wm->button = -1;
            
            if (isCover  == 0) {
             
                wm->string = stringNew("Msg:CoverExists(,");
                stringAppend(wm->string,sz);
           
            } else if (icon) {
            
                while (isspace(*icon)) {
                    ++icon;
                }
                
                wm->string = stringNew("Msg:IconExists(");
                stringAppend(wm->string,sz);
                stringAppend(wm->string,",");
                stringAppend(wm->string,icon);
            }
            stringAppend(wm->string,")");
        }

    } else if (strncmp(token,"Get(cover_size",14) == 0) {

        wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        wm->string = stringNew("Msg:CoverSize(");
        char b[32];

        stringAppend(wm->string,
                     i2string((mode == SERVER_CMXML ? xmlScreenHeight() : (htmlScreenHeight()*8)/10),b));

        stringAppend(wm->string,",)");

    } else if (strncmp(token,"Get(version",11) == 0) {

        wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        wm->string = stringNew("Msg:Version(,web_interface)");

    } else if (strncmp(token,"Get(cursor",10) == 0) {
        // ignore
    } else if (strncmp(token,"Get(ping",8) == 0) {

        wm = (wMessage*) malloc(sizeof(wMessage));
        wm->button = -1;
        wm->string = stringNew("Msg:Ping");

    } else if (strncmp(token,"End(",4) == 0) {
        answerReady = BOOL_YES;
    } else {
        ERROR2("[WS]: Unknown command %s", token);
    }

    mutexUnlock(M_WEB);

    if (wm) {
        sendToWebServer(wm);
    }
    if (wm2) {
        sendToWebServer(wm2);
    }

    free(cmd);
    INFO2("[WS]: parsed %d", curForm());
    return EXIT_OK;
}

int writeWebConn(const dMessage* dm, int mode)
{
    INFO2("[WS]: writeWebConn %d (%d)", dm->type, dm->size);
    if (dm->type == DM_SET ||
        dm->type == DM_GET) {

        if (strncmp(dm->value,"Get(",4) == 0 ||   // Set(...) command updates state inside peer.c : writePeers()
            strncmp(dm->value,"End();",6) == 0) {  
            return writeWebConnStr(dm->value, mode);
        }

    } else if (dm->type == DM_SETFILE) {

        // Set(cover|image...) command updates state inside peer.c : writePeers() 

    } else {
        ERROR2("[WS]: Not supported");
    }
    return EXIT_OK;
}

//
// Special case for Web/CMXML servers
//
int writeWeb(ConnectInfo* peer, const dMessage* dm)
{
    INFO2("[WS]: writeWeb (%d)", dm->size);
    // suppose peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML
    if (dm->type == DM_SENDB || dm->type == DM_SENDS) {
        INFO2("[WS]: writeWeb SKIP");
        return EXIT_NOK;  // skip that
    }
    
    if (dm->type == DM_SET) {
        if (isDataOld(peer, dm->subtype, dm->value, dm->size)) {
            INFO2("[DS]: Skip to send the same data to WEB/CMXML peer");
            return EXIT_OK;
        }
    }

    return writeWebConn(dm, peer->mode);
}

