/*
 * Copyright (c) 2017-2024 OARC, Inc.
 * Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
 * All rights reserved.
 *
 * This file is part of PacketQ.
 *
 * PacketQ 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.
 *
 * PacketQ 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 PacketQ.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "packetq.h"
#include "pcap.h"
#include "reader.h"

#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <list>
#include <map>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#define MAXHOSTNAME 256

namespace packetq {
namespace httpd {
    class Socket;
    class Server;

    static char redirect[] = "HTTP/1.1 307 Temporary Redirect\r\n"
                             "Location: /\r\n"
                             "Connection: close\r\n"
                             "Content-Type: text/html\r\n"
                             "\r\n<html><head><title>moved</title></head><body><h1>moved</h1>this page has moved to /</body></html>";

    static char header[] = "HTTP/1.1 200 OK\r\n"
                           "Server: PacketQ builtin\r\n"
                           "Connection: close\r\n"
                           "Access-Control-Allow-Origin: *\r\n"
                           "Content-Type: %s\r\n"
                           "\r\n";

    static Server* g_server = 0;

    class SocketPool {
    public:
        SocketPool()
        {
            m_free = 0;
            for (int i = 0; i < FD_SETSIZE; i++) {
                m_sockets[i] = 0;
            }
            m_socket_count = 0;
        }
        int add(Socket* s)
        {
            if (m_free < FD_SETSIZE)
                m_sockets[m_free] = s;
            else
                return -1;
            int idx = m_free;
            while (m_free < FD_SETSIZE && m_sockets[m_free])
                m_free++;
            m_socket_count++;
            return idx;
        }
        void remove(int s)
        {
            m_sockets[s] = 0;
            if (s < m_free)
                m_free = s;
            m_socket_count--;
        }

        int  get_sockets() { return m_socket_count; }
        void select();

        fd_set  m_readset;
        fd_set  m_writeset;
        int     m_free;
        int     m_socket_count;
        Socket* m_sockets[FD_SETSIZE];
    };

    SocketPool g_pool;

    class Stream {
    public:
        class Buffer {
        private:
            Buffer&       operator=(const Buffer& other);
            Buffer const& operator=(Buffer&& other);

        public:
            Buffer(const unsigned char* buf, int len)
            {
                m_buf = new unsigned char[len];
                memcpy(m_buf, buf, len);
                m_len = len;
                m_pos = 0;
            }
            Buffer(Buffer&& other) noexcept
            {
                m_buf       = other.m_buf;
                m_len       = other.m_len;
                m_pos       = other.m_pos;
                other.m_buf = 0;
                other.m_len = 0;
                other.m_pos = 0;
            }
            ~Buffer()
            {
                m_len = 0;
                delete[] m_buf;
            }
            unsigned char* m_buf;
            int            m_len;
            int            m_pos;
        };
        Stream()
        {
            m_len = 0;
        }
        void push_front(unsigned char* data, int len)
        {
            m_stream.push_front(Buffer(data, len));
            m_len += len;
        }
        void write(unsigned char* data, int len)
        {
            m_stream.push_back(Buffer(data, len));
            m_len += len;
        }
        int read(unsigned char* data, int maxlen)
        {
            int p = 0;
            while (p < maxlen && m_len > 0) {
                Buffer& buf = m_stream.front();
                int     l   = maxlen - p;
                if (l > buf.m_len - buf.m_pos)
                    l = buf.m_len - buf.m_pos;
                for (int i = 0; i < l; i++) {
                    data[p++] = buf.m_buf[buf.m_pos++];
                }
                m_len -= l;
                if (l == 0) {
                    m_stream.pop_front();
                }
            }
            return p;
        }
        int len()
        {
            return m_len;
        }
        int get()
        {
            unsigned char c = '@';
            int           r = read(&c, 1);
            if (r == 0)
                return -1;
            return c;
        }

    private:
        int                       m_len;
        std::list<Stream::Buffer> m_stream;
    };

    class Socket {
    private:
        Socket& operator=(const Socket& other);
        Socket(Socket&& other) noexcept;
        Socket const& operator=(Socket&& other);

    public:
        Socket(int s, bool serv)
            : m_want_write(false)
        {
            int flags;

            // Add non-blocking flag
            if ((flags = fcntl(s, F_GETFL)) == -1) {
                syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", s, errno);
                exit(-1);
            }
            if (fcntl(s, F_SETFL, flags | O_NONBLOCK)) {
                syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", s, errno, flags | O_NONBLOCK);
                exit(-1);
            }

            m_bytes_written    = 0;
            m_bytes_read       = 0;
            m_serv             = serv;
            m_socket           = s;
            m_sidx             = g_pool.add(this);
            m_close_when_empty = false;
            m_file             = 0;
        }
        virtual ~Socket()
        {
            g_pool.remove(m_sidx);
            close(m_socket);
        }
        void process(bool read)
        {
            // m_serv means this is a listening socket
            if (m_serv)
                return;
            if (!read) {
                on_write();
                unsigned char ptr[4096];
                int           len;

                while ((len = m_write.read(ptr, sizeof(ptr)))) {
                    int res = write(m_socket, ptr, len);
#if EAGAIN != EWOULDBLOCK
                    if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
#else
                    if (res == -1 && (errno == EAGAIN)) {
#endif
                        m_write.push_front(ptr, len);
                        set_want_write();
                        return;
                    }
                    if (res < 0) {
                        delete this;
                        return;
                    }
                    m_bytes_written += res;
                    if (res < len) {
                        m_write.push_front(&ptr[res], len - res);
                        set_want_write();
                        return;
                    }
                }
                if (m_close_when_empty) {
                    shutdown(m_socket, SHUT_WR);
                    //                    fflush(m_socket);
                    delete this;
                }
            }
            if (read) {
                int avail = read_sock();

                if (avail)
                    on_read();
            }
        }
        virtual void file_write()
        {
        }
        virtual void file_read()
        {
        }
        void set_delete()
        {
            m_close_when_empty = true;
            m_want_write       = false;
        }
        virtual void on_write()
        {
        }
        virtual void on_read()
        {
        }
        int read_sock()
        {
            unsigned char buf[2000];
            int           len = 0;
            if ((len = recv(m_socket, buf, sizeof(buf) - 1, 0)) > 0) {
                m_bytes_read += len;
                buf[len] = 0;
                m_read.write(buf, len);
            }
            if (len < 0) {
                delete this;
                return 0;
            }
            return len;
        }
        bool failed()
        {
            return m_sidx == -1;
        }
        bool has_data()
        {
            return m_want_write || m_write.len() > 0;
        }
        void set_want_write(bool set = true)
        {
            m_want_write = set;
        }

        int  m_bytes_written;
        int  m_bytes_read;
        int  m_socket;
        int  m_file;
        int  m_sidx;
        bool m_serv;
        bool m_want_write;
        bool m_close_when_empty;
        char m_buffer[4096];

        Stream m_read, m_write;
    };

    void SocketPool::select()
    {
        FD_ZERO(&m_readset);
        FD_ZERO(&m_writeset);
        int max = 0;
        for (int i = 0; i < FD_SETSIZE; i++) {
            if (m_sockets[i]) {
                if (max < m_sockets[i]->m_socket)
                    max = m_sockets[i]->m_socket;
                FD_SET(m_sockets[i]->m_socket, &m_readset);
                if (m_sockets[i]->has_data())
                    FD_SET(m_sockets[i]->m_socket, &m_writeset);
                if (max < m_sockets[i]->m_file)
                    max = m_sockets[i]->m_file;
                FD_SET(m_sockets[i]->m_file, &m_readset);
            }
        }
        timeval timeout;
        timeout.tv_sec  = 30;
        timeout.tv_usec = 0;

        int sel = ::select(max + 1, &m_readset, &m_writeset, 0, &timeout);
        if (sel < 0) {
            syslog(LOG_ERR | LOG_USER, "sel -1 errno = %d %s max:%d", errno, strerror(errno), max);
            exit(-1);
        }
        if (sel) {
            for (int i = 0; i < FD_SETSIZE; i++) {
                if (m_sockets[i] && m_sockets[i]->m_file) {
                    if (FD_ISSET(m_sockets[i]->m_file, &m_readset)) {
                        m_sockets[i]->file_read();
                    }
                    if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_file, &m_writeset)) {
                        m_sockets[i]->file_write();
                    }
                }
                if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_socket, &m_readset)) {
                    m_sockets[i]->process(true);
                }
                if (m_sockets[i] && FD_ISSET(m_sockets[i]->m_socket, &m_writeset)) {
                    m_sockets[i]->process(false);
                }
            }
        }
    }

    class Server {
    public:
        Socket m_socket;

        Server(int port, const std::string& pcaproot, const std::string& webroot)
            : m_socket(establish(port), true)
            , m_pcaproot(pcaproot)
            , m_webroot(webroot)
        {
            if (m_socket.m_socket < 0) {
                if (m_socket.m_socket == EADDRINUSE)
                    syslog(LOG_ERR | LOG_USER, "Fail EADDRINUSE (%d)\n", m_socket.m_socket);
                else
                    syslog(LOG_ERR | LOG_USER, "Fail %d port:%d\n", m_socket.m_socket, port);
                exit(-1);
            }
        }

        ~Server()
        {
        }

        int get_connection()
        {
            int s = m_socket.m_socket;
            int t; /* socket of connection */
            if ((t = accept(s, NULL, NULL)) < 0) /* accept connection if there is one */
                return (-1);

            return (t);
        }

        int establish(unsigned short portnum)
        {
            int         s, res;
            sockaddr_in sa;
            memset(&sa, 0, sizeof(struct sockaddr_in));

            sa.sin_family      = AF_INET;
            sa.sin_addr.s_addr = htonl(INADDR_ANY);

            sa.sin_port = htons(portnum);

            if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
                return (-2);
            int on = 1;

            res = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

            if ((res = bind(s, (const sockaddr*)&sa, sizeof(struct sockaddr_in))) < 0) {
                close(s);
                return (res); /* bind address to socket */
            }

            // Add non-blocking flag
            int flags;
            if ((flags = fcntl(s, F_GETFL)) == -1) {
                syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", s, errno);
                exit(-1);
            }
            if (fcntl(s, F_SETFL, flags | O_NONBLOCK)) {
                syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", s, errno, flags | O_NONBLOCK);
                exit(-1);
            }

            listen(s, 511); /* max # of queued connects */
            return (s);
        }

        std::string m_pcaproot;
        std::string m_webroot;
    };

    class Url {
    public:
        Url(const char* url)
            : m_full(url)
        {
            int i = 0;
            for (; i < m_full.length();) {
                char c = m_full.c_str()[i];
                if (c == '?') {
                    i++;
                    break;
                }
                m_path += c;
                i++;
            }
            m_path = decode(m_path);
            if (m_path == "")
                m_path = "/";
            decode_params(m_full.substr(i).c_str());
        }
        void decode_params(const char* params)
        {
            if (!params)
                return;
            std::string str = params;
            for (int i = 0; i < str.length();) {
                std::string param = "", value = "";
                for (; i < str.length();) {
                    char c = str.c_str()[i];
                    if (c == '=' || c == '&') {
                        i++;
                        break;
                    }
                    param += c;
                    i++;
                }
                for (; i < str.length();) {
                    char c = str.c_str()[i];
                    if (c == '&') {
                        i++;
                        break;
                    }
                    value += c;
                    i++;
                }
                add_param(decode(param), decode(value));
            }
        }
        const char* get_param(const char* param)
        {
            auto it = m_params.find(std::string(param));
            if (it == m_params.end())
                return 0;
            return it->second.c_str();
        }
        void add_param(std::string key, std::string val)
        {
            auto it = m_params.find(key);
            if (it == m_params.end()) {
                m_params[key] = val;
                m_counts[key] = 1;
                return;
            }
            char cnt[100];
            int  n        = m_counts[key];
            m_counts[key] = n + 1;

            snprintf(cnt, sizeof(cnt) - 1, "%d", n);
            cnt[99]          = 0;
            std::string keyn = key;
            keyn += cnt;
            m_params[keyn] = val;
        }

        std::string decode(std::string str)
        {
            std::string dst;
            int         percent_state = 0;
            int         code          = 0;
            for (int i = 0; i < str.length(); i++) {
                char c = str.c_str()[i];
                if (percent_state) {
                    int n = 0;
                    if (c >= '0' && c <= '9')
                        n = c - '0';
                    if (c >= 'a' && c <= 'f')
                        n = c - 'a' + 10;
                    if (c >= 'A' && c <= 'F')
                        n = c - 'A' + 10;
                    code = (code << 4) | n;
                    percent_state--;
                    if (!percent_state) {
                        dst += char(code);
                        code = 0;
                    }
                } else {
                    if (c == '%') {
                        percent_state = 2;
                    } else
                        dst += c;
                }
            }
            return dst;
        }

        std::string get_full()
        {
            return m_full;
        }
        std::string get_path()
        {
            return m_path;
        }

        std::string                        m_full;
        std::string                        m_path;
        std::map<std::string, std::string> m_params;
        std::map<std::string, int>         m_counts;
    };

    class Page {
    public:
        Page(const char* url, const char* body)
            : m_url(url)
        {
            m_url.decode_params(body);
        }

        void process()
        {

            if (m_url.get_path().compare("/query") == 0) {
                if (!m_url.get_param("file")) {
                    printf(header, "text/plain");
                    printf("no file selected\n");
                    return;
                }

                printf(header, "text/plain");
                if (m_url.get_param("sql"))
                    query(m_url.get_param("sql"));
                else
                    printf("no query defined \n");
            } else if (m_url.get_path().substr(0, 8).compare("/resolve") == 0) {
                resolve();
            } else if (m_url.get_path().substr(0, 5).compare("/list") == 0) {
                serve_dir();
            } else {
                serve_static();
            }

            delete g_app;
        }
        static std::string join_path(const std::string& a, const std::string& b)
        {
            if (b.find("..") != std::string::npos)
                return a;
            if (a.length() == 0)
                return b;
            if (b.length() == 0)
                return a;
            if (a[a.length() - 1] != '/' && b[0] != '/')
                return a + std::string("/") + b;
            return a + b;
        }
        const char* get_mimetype(const std::string& file)
        {
            int p = file.find_last_of('.');
            if (p == std::string::npos || p + 1 >= file.length())
                return 0;
            std::string suff = file.substr(p + 1);
            if (suff.compare("js") == 0)
                return "application/x-javascript";
            if (suff.compare("jpg") == 0)
                return "image/jpeg";
            if (suff.compare("html") == 0)
                return "text/html";
            if (suff.compare("htm") == 0)
                return "text/html";
            if (suff.compare("txt") == 0)
                return "text/plain";
            if (suff.compare("png") == 0)
                return "image/png";
            if (suff.compare("gif") == 0)
                return "image/gif";
            if (suff.compare("ico") == 0)
                return "image/x-icon";
            if (suff.compare("json") == 0)
                return "application/json";
            if (suff.compare("css") == 0)
                return "text/css";

            return 0;
        }
        bool serve_file(const std::string& file)
        {
            const char* mimetype = get_mimetype(file);

            if (mimetype) {
                FILE* fp = fopen(file.c_str(), "rb");
                if (fp) {
                    printf(header, mimetype);
                    char buffer[8192];
                    int  len;
                    while ((len = fread(buffer, 1, 200, fp)) > 0) {
                        fwrite(buffer, 1, len, stdout);
                    }
                    fclose(fp);
                    return true;
                }
            }
            return false;
        }
        void serve_static()
        {
            if (g_server->m_webroot == "") {
                printf(header, "text/html");
                printf("<h2>This server is not configured to serve static pages</h2>");
                printf("Start using the -w option to set a html directory");
                return;
            }
            std::string file = join_path(g_server->m_webroot, m_url.get_path());

            if (serve_file(file))
                return;
            if (serve_file(join_path(file, "index.html")))
                return;

            if (m_url.get_path().compare("/") != 0) {
                printf("%s", redirect);
                return;
            }

            printf(header, "text/html");
            printf("<h2>It works !</h2><br>\n");
            printf("%s", "<a href=\"/query?file=sample.pcap&sql=select%20qr,qname,protocol%20from%20dns%20limit%2018;\">Test query</a><br/>\n");
            printf("%s", "<a href=\"/list\">list available files</a><br/>\n");
        }

        void resolve()
        {
            const char* ip   = m_url.get_param("ip");
            const char* name = m_url.get_param("name");

            if (ip) {

                printf(header, "application/json");

                printf("[");

                struct addrinfo* result;
                struct addrinfo* res;
                int              error;

                error = getaddrinfo(ip, NULL, NULL, &result);
                if (error == 0) {
                    for (res = result; res != NULL; res = res->ai_next) {
                        char hostname[NI_MAXHOST] = "";

                        error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0);
                        if (error != 0) {
                            continue;
                        }
                        if (*hostname != '\0') {
                            printf("\"%s\"", hostname);
                            break;
                        }
                    }
                    freeaddrinfo(result);
                }
                printf("]\n");
            } else if (name) {
                char tmp[100];
                printf(header, "application/json");

                printf("[");

                struct addrinfo* result;
                struct addrinfo* res;
                int              error;

                error         = getaddrinfo(name, NULL, NULL, &result);
                char  empty[] = "", line[] = ",\n";
                char* sep = empty;
                if (error == 0) {
                    for (res = result; res != NULL; res = res->ai_next) {
                        void* ptr = &((struct sockaddr_in*)res->ai_addr)->sin_addr;
                        if (res->ai_family == AF_INET6)
                            ptr = &((struct sockaddr_in6*)res->ai_addr)->sin6_addr;
                        tmp[0] = 0;
                        inet_ntop(res->ai_family, ptr, tmp, sizeof(tmp));
                        printf("%s\"%s\"", sep, tmp);
                        sep = line;
                    }
                    freeaddrinfo(result);
                }
                printf("]\n");
            } else
                printf("[]\n");
        }

        void serve_dir()
        {
            if (g_server->m_pcaproot == "") {
                printf(header, "text/html");
                printf("<h2>This server is not configured to list pcapfiles</h2>");
                printf("Start using the -r option to set a pcap directory");
                return;
            }
            std::string directory = join_path(g_server->m_pcaproot, m_url.get_path().substr(5));

            DIR* dir = opendir(directory.c_str());
            if (!dir) {
                printf("%s", redirect);
                return;
            }

            printf(header, "application/json");

            printf("[\n");
            struct dirent* d;
            struct stat    statbuf;

            char comma = ' ';

            while ((d = readdir(dir)) != 0) {
                std::string subject = join_path(directory, d->d_name);
                int         fd      = open(subject.c_str(), O_RDONLY);

                if (fd < 0)
                    continue;
                if (fstat(fd, &statbuf) == -1) {
                    close(fd);
                    continue;
                }
                if (S_ISDIR(statbuf.st_mode)) {
                    if ((strcmp(d->d_name, ".") != 0) && (strcmp(d->d_name, "..") != 0)) {
                        printf("  %c{\n     \"data\" : \"%s\",\n     \"attr\" : { \"id\": \"%s\" },\n   \"children\" : [], \"state\" : \"closed\"  }\n",
                            comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str());
                        comma = ',';
                    }
                } else {
                    bool  found = false;
                    FILE* fp    = fdopen(fd, "rb");
                    if (fp) {
                        Pcap_file pfile(fp);
                        if (pfile.get_header()) {
                            unsigned char* data = 0;
                            int            s    = 0, us, len;
                            data                = pfile.get_packet(len, s, us);
                            if (data) {
                                printf("  %c{\n     \"data\" : \"%s\",\n     \"attr\" : { \"id\" : \"%s\", \"size\": %d, \"time\": %d,\"type\": \"pcap\" },\n    \"children\" : []  }\n",
                                    comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str(), int(statbuf.st_size), s);
                                comma = ',';
                                found = true;
                            }
                        }
                        fclose(fp);
                    }
                    if (!found) {
                        std::string str = subject;
                        transform(str.begin(), str.end(), str.begin(), tolower);
                        if (str.rfind(".json") == str.length() - 5) {
                            printf("  %c{\n     \"data\" : \"%s\",\n     \"attr\" : { \"id\" : \"%s\", \"size\": %d, \"type\": \"json\" },\n    \"children\" : []  }\n",
                                comma, d->d_name, join_path(m_url.get_path(), d->d_name).substr(5).c_str(), int(statbuf.st_size));
                            comma = ',';
                        }
                    }
                }
                close(fd);
            }

            printf("]\n");

            closedir(dir);
        }

        void query(const char* sql)
        {
            Query query("result", sql);

            query.parse();

            std::vector<std::string> in_files;

            int i = 0;
            while (true) {
                char param[50] = "file";

                std::string par = "file";
                if (i > 0) {
                    snprintf(param, sizeof(param) - 1, "file%d", i);
                    param[49] = 0;
                }
                i++;
                const char* f = m_url.get_param(param);
                if (!f)
                    break;
                std::string file = join_path(g_server->m_pcaproot, f);
                in_files.push_back(file);
            }

            Reader reader(in_files, g_app->get_limit());

            query.execute(reader);
            query.m_result->json(false);
        }
        Url m_url;
    };

    class Http_socket : public Socket {
    private:
        Http_socket& operator=(const Http_socket& other);
        Http_socket(Http_socket&& other) noexcept;
        Http_socket const& operator=(Http_socket&& other);

    public:
        enum State {
            get_post,
            header,
            body,
            error,
            wait_child,
            done
        };
        Http_socket(int socket)
            : Socket(socket, false)
            , m_http_version(0)
            , m_emptyline(0)
        {
            m_state       = get_post;
            m_nextc       = -1;
            m_cr          = false;
            m_line        = "";
            m_url         = "";
            m_child_fd    = 0;
            m_child_pid   = 0;
            m_child_read  = 0;
            m_body_cnt    = -1;
            m_content_len = 0;
        }
        ~Http_socket()
        {
            if (m_child_pid) {
                kill(m_child_pid, SIGHUP);
                int status;
                waitpid(m_child_pid, &status, 0);
            }
            if (m_child_fd)
                close(m_child_fd);
            m_file = 0;
        }
        inline void print(const char* fmt, ...)
        {
            char    string[4096];
            va_list ap;

            va_start(ap, fmt);
            vsnprintf(string, sizeof(string), fmt, ap);
            va_end(ap);

            m_write.write((unsigned char*)string, strlen(string));
        }

        int peek()
        {
            if (m_nextc >= 0)
                return m_nextc;
            m_nextc = m_read.get();
            return m_nextc;
        }
        int getc()
        {
            int c   = peek();
            m_nextc = -1;
            return c;
        }

        void on_read()
        {
            while (true) {
                int c = peek();
                if (c == -1)
                    return;
                if (m_body_cnt >= 0) {
                    c = getc();
                    if (!(m_body_cnt == 0 && c == 10)) {
                        m_line += char(c);
                        m_content_len--;
                    }
                    m_body_cnt++;
                    if (m_content_len == 0)
                        parseline();
                    continue;
                }
                c = getc();
                if (c != 13 && c != 10) {
                    m_line += char(c);
                    m_cr = false;
                } else {
                    bool cr = m_cr;
                    m_cr    = false;
                    if (c == 10 && cr) {
                        continue;
                    }
                    if (c == 13)
                        m_cr = true;
                    parseline();
                }
            }
        }
        virtual void file_read()
        {
            set_want_write();
        }
        virtual void file_write()
        {
        }
        void on_write()
        {
            set_want_write(false);
            if (m_state == wait_child) {
                unsigned char buffer[4096];
                int           status;
                bool          done = true;
                if (0 == waitpid(m_child_pid, &status, WNOHANG)) {
                    done = false;
                }
                if (m_child_fd) {
                    // Add non-blocking flag
                    int flags;
                    if ((flags = fcntl(m_child_fd, F_GETFL)) == -1) {
                        syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", m_child_fd, errno);
                        exit(-1);
                    }
                    if (fcntl(m_child_fd, F_SETFL, flags | O_NONBLOCK)) {
                        syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", m_child_fd, errno, flags | O_NONBLOCK);
                        exit(-1);
                    }

                    size_t res;
                    pollfd pfd;
                    pfd.fd      = m_child_fd;
                    pfd.events  = POLLIN;
                    pfd.revents = 0;
                    if (1 == poll(&pfd, 1, 0) && (pfd.revents & POLLIN) != 0) {
                        if ((res = read(m_child_fd, buffer, (int)sizeof(buffer))) > 0) {
                            done = false;
                            m_child_read += res;
                            m_write.write(buffer, res);
                        }
                    }
                }
                if (done) {
                    m_child_pid = 0;
                    if (m_child_fd) {
                        close(m_child_fd);
                        m_child_fd = 0;
                        m_file     = 0;
                    }
                    set_delete();
                }
            }
        }
        void parseline()
        {
            switch (m_state) {
            case (get_post): {
                m_state = error;
                syslog(LOG_INFO | LOG_USER, "%s\n", m_line.c_str());
                int p = 0;
                if (m_line.find("GET ") != -1) {
                    if ((p = m_line.find(" HTTP/1.1")) != -1) {
                        m_http_version = 1;
                    } else if ((p = m_line.find(" HTTP/1.0")) != -1) {
                        m_http_version = 0;
                    } else {
                        return;
                    }
                    m_url   = m_line.substr(4, p - 4);
                    m_state = header;
                } else if (m_line.find("POST ") != -1) {
                    if ((p = m_line.find(" HTTP/1.1")) != -1) {
                        m_http_version = 1;
                    } else if ((p = m_line.find(" HTTP/1.0")) != -1) {
                        m_http_version = 0;
                    } else {
                        return;
                    }
                    m_url   = m_line.substr(5, p - 5);
                    m_state = header;
                }
            } break;
            case (header):
                if (m_line.length() == 0) {
                    m_body_cnt = 0;
                    m_state    = body;
                } else {
                    int         colon = m_line.find(": ");
                    std::string key   = m_line.substr(0, colon);
                    std::string val   = m_line.substr(colon + 2);
                    if (key == "Content-Length") {
                        if (val.length() > 0)
                            m_content_len = atoi(val.c_str());
                    }
                }
                break;
            case (body):
                m_body = m_line;
                header_done();
                break;
            default:
                printf("error line: %s !\n", m_line.c_str());
                break;
            }
            m_line = "";
        }
        void header_done()
        {
            fflush(stdout); // required before fork or any unflushed output will go to the client
            int fd[2];
            if (pipe(fd) < 0)
                return;

            // Add non-blocking flag
            int flags;
            if ((flags = fcntl(fd[0], F_GETFL)) == -1) {
                syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_GETFL) failed: %d\n", fd[0], errno);
                exit(-1);
            }
            if (fcntl(fd[0], F_SETFL, flags | O_NONBLOCK)) {
                syslog(LOG_ERR | LOG_USER, "fcntl(%d, F_SETFL, 0x%x) failed: %d\n", fd[0], errno, flags | O_NONBLOCK);
                exit(-1);
            }

            m_child_pid = fork();
            if (m_child_pid < 0) {
                print("Internal error");
                set_delete();
                return;
            }
            if (m_child_pid == 0) {
                //////////      child code /////////
                dup2(fd[1], fileno(stdout));
                dup2(fd[1], fileno(stderr));
                close(fd[1]);

                Page page(m_url.c_str(), m_body.c_str());
                page.process();
                fflush(stdout);
                exit(0);
                ///////////// child exit() ///////////////
            } else {
                close(fd[1]);
                m_child_fd = fd[0];
                m_file     = m_child_fd;
                m_state    = wait_child;
            }
            set_want_write();
        }
        State m_state;
        bool  m_cr;
        int   m_body_cnt;
        int   m_content_len;
        int   m_nextc;
        int   m_child_pid;
        int   m_child_fd;

        int m_child_read;

        int m_http_version; // 0 = HTTP/1.0 1 = HTTP/1.1

        int         m_emptyline;
        std::string m_line;
        std::string m_url;
        std::string m_body;
    };

} // namespace httpd

using namespace httpd;

void start_server(int port, bool fork_me, const std::string& pcaproot, const std::string& webroot, int max_conn)
{
    pid_t pid, sid;
    bool  fg = !fork_me;

    printf("listening on port %d\n", port);

    if (!fg) {
        pid = fork();

        if (pid < 0) {
            exit(EXIT_FAILURE);
        } else if (pid > 0) {
            exit(EXIT_SUCCESS);
        }

        sid = setsid();

        if (sid < 0) {
            exit(EXIT_FAILURE);
        }
    }
    openlog("packetq", LOG_PID, LOG_USER);

    httpd::Server server(port, pcaproot, webroot);
    g_server = &server;

    while (true) {
        httpd::g_pool.select();
        int cnt = g_pool.get_sockets();
        if (cnt < max_conn) {
            int c = server.get_connection();
            if (c > -1) {
                Http_socket* s = new (std::nothrow) Http_socket(c);
                if (s && s->failed()) {
                    syslog(LOG_ERR | LOG_USER, "failed to create socket");
                    delete s;
                }
            }
        }
        usleep(1000);
    }
    // loop will never break
    // g_server = 0;
    // syslog(LOG_INFO | LOG_USER, "exiting");
    // exit(EXIT_SUCCESS);
}

} // namespace packetq
