/*
    This file is part of Kismet

    Kismet 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 2 of the License, or
    (at your option) any later version.

    Kismet 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 Kismet; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef __KIS_NET_MICROHTTPD__
#define __KIS_NET_MICROHTTPD__

#include "config.h"

#include <atomic>
#include <stdio.h>
#include <time.h>
#include <list>
#include <map>
#include <vector>
#include <algorithm>
#include <string>
#include <sstream>
#include <microhttpd.h>
#include <memory>

#include "globalregistry.h"
#include "kis_mutex.h"
#include "kis_net_microhttpd_handlers.h"
#include "structured.h"
#include "trackedelement.h"

class Kis_Net_Httpd;
class Kis_Net_Httpd_Session;
class Kis_Net_Httpd_Connection;
class Kis_Net_Httpd_Handler;

class EntryTracker;

namespace kishttpd {
    std::string GetSuffix(const std::string& path);
    std::string StripSuffix(const std::string& path);
    std::string EscapeHtml(const std::string& path);

    // Summarize based on a summarization dictionary, if one is present.
    // MAY THROW EXCEPTIONS if summarization is malformed.
    // Calls the standard, nested/vectorization summarization if passed a vector, single summarization
    // if passed a map/trackedcomponent object.
    // Modifies the rename_map field, which must be provided by the caller.
    // Returns a summarized vector (if passed a vector) or summarized device (if passed
    // a summarized device)
    std::shared_ptr<TrackerElement> SummarizeWithStructured(std::shared_ptr<TrackerElement> in_data,
            SharedStructured structured, std::shared_ptr<TrackerElementSerializer::rename_map> rename_map);
};

// Connection data, generated for all requests by the processing system;
// contains per-handler states, request information, request type, session
// data if known, POST variables if the standard POST processing is enabled
class Kis_Net_Httpd_Connection {
public:
    using variable_cache_map = std::map<std::string, std::shared_ptr<std::stringstream>>;

    const static int CONNECTION_GET = 0;
    const static int CONNECTION_POST = 1;

    Kis_Net_Httpd_Connection() {
        httpcode = 200;
        postprocessor = NULL;
        post_complete = false;
        connection_type = CONNECTION_GET;
        httpd = NULL;
        httpdhandler = NULL;
        session = NULL;
        connection = NULL;
        response = NULL;
        custom_extension = NULL;
    }

    // response generated by post
    std::stringstream response_stream;

    // Cache of variables in session
    variable_cache_map variable_cache;

    bool has_cached_variable(const std::string& key) {
        return (variable_cache.find(key) != variable_cache.end());
    }

    template <typename T>
    T variable_cache_as(const std::string& key) {
        T t;

        auto v = variable_cache.find(key);

        if (v == variable_cache.end())
            throw std::runtime_error(fmt::format("variable '{}' not found", kishttpd::EscapeHtml(key)));

        *v->second >> t;

        if (v->second->fail())
            throw std::runtime_error(fmt::format("unable to convert value of '{}'", kishttpd::EscapeHtml(key)));

        return t;
    }

    // Optional alternate filename to pass to the browser for downloading
    std::string optional_filename;

    // HTTP code of response
    int httpcode;

    // URL
    std::string url;

    // URL component considered for mime typing
    std::string mime_url;

    // Post processor struct
    struct MHD_PostProcessor *postprocessor;

    // Is the post complete?
    bool post_complete;

    // Type of request/connection
    int connection_type;

    // httpd parent
    Kis_Net_Httpd *httpd;    

    // Handler
    Kis_Net_Httpd_Handler *httpdhandler;    

    // Login session
    std::shared_ptr<Kis_Net_Httpd_Session> session;

    // Connection
    struct MHD_Connection *connection;

    // Response created elsewhere, if any
    struct MHD_Response *response;

    // Custom arbitrary value inserted by other processors
    void *custom_extension;

    // Integrity locker
    std::mutex connection_mutex;
};

class Kis_Net_Httpd_Session {
public:
    // Session ID
    std::string sessionid;

    // Time session was created
    time_t session_created;

    // Last time the session was seen active
    time_t session_seen;

    // Amount of time session is valid for after last active
    time_t session_lifetime;
};

class Kis_Httpd_Websession;

// Do a simple dump of a tracked object into an endpoint
class Kis_Net_Httpd_Simple_Tracked_Endpoint : public Kis_Net_Httpd_Chain_Stream_Handler {
public:
    using gen_func = std::function<std::shared_ptr<TrackerElement> ()>;

    Kis_Net_Httpd_Simple_Tracked_Endpoint(const std::string& in_uri, 
            std::shared_ptr<TrackerElement> in_content, 
            kis_recursive_timed_mutex *in_mutex);
    Kis_Net_Httpd_Simple_Tracked_Endpoint(const std::string& in_uri, gen_func in_func);
    Kis_Net_Httpd_Simple_Tracked_Endpoint(const std::string& in_uri, gen_func in_func,
            kis_recursive_timed_mutex *in_mutex);

    virtual ~Kis_Net_Httpd_Simple_Tracked_Endpoint() { }

    // HTTP handlers
    virtual bool Httpd_VerifyPath(const char *path, const char *method) override;

    virtual int Httpd_CreateStreamResponse(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection,
            const char *url, const char *method, const char *upload_data,
            size_t *upload_data_size) override;

    virtual int Httpd_PostComplete(Kis_Net_Httpd_Connection *concls) override;

protected:
    std::string uri;
    std::shared_ptr<TrackerElement> content;
    gen_func generator;
    kis_recursive_timed_mutex *mutex;
};

// Do a simple dump of a tracked object into an endpoint, DO NOT require authentication.
// This should be very rarely used.
class Kis_Net_Httpd_Simple_Unauth_Tracked_Endpoint : public Kis_Net_Httpd_Chain_Stream_Handler {
public:
    using gen_func = std::function<std::shared_ptr<TrackerElement> ()>;

    Kis_Net_Httpd_Simple_Unauth_Tracked_Endpoint(const std::string& in_uri, 
            std::shared_ptr<TrackerElement> in_content, 
            kis_recursive_timed_mutex *in_mutex);
    Kis_Net_Httpd_Simple_Unauth_Tracked_Endpoint(const std::string& in_uri, gen_func in_func);
    Kis_Net_Httpd_Simple_Unauth_Tracked_Endpoint(const std::string& in_uri, gen_func in_func,
            kis_recursive_timed_mutex *in_mutex);

    virtual ~Kis_Net_Httpd_Simple_Unauth_Tracked_Endpoint() { }

    // HTTP handlers
    virtual bool Httpd_VerifyPath(const char *path, const char *method) override;

    virtual int Httpd_CreateStreamResponse(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection,
            const char *url, const char *method, const char *upload_data,
            size_t *upload_data_size) override;

    virtual int Httpd_PostComplete(Kis_Net_Httpd_Connection *concls) override;

protected:
    std::string uri;
    std::shared_ptr<TrackerElement> content;
    gen_func generator;
    kis_recursive_timed_mutex *mutex;
};

// A similar simplified endpoint dump but with a callback function for the path processing
// and for the endpoint generation; for more rest-like paths
class Kis_Net_Httpd_Path_Tracked_Endpoint : public Kis_Net_Httpd_Chain_Stream_Handler {
public:
    using gen_func = std::function<std::shared_ptr<TrackerElement> (const std::vector<std::string>&)>;
    using path_func = std::function<bool (const std::vector<std::string>&)>;

    Kis_Net_Httpd_Path_Tracked_Endpoint(path_func in_path, gen_func in_gen);
    Kis_Net_Httpd_Path_Tracked_Endpoint(path_func in_path, gen_func in_gen,
            kis_recursive_timed_mutex *in_mutex);
    virtual ~Kis_Net_Httpd_Path_Tracked_Endpoint() { }

    // HTTP handlers
    virtual bool Httpd_VerifyPath(const char *path, const char *method) override;

    virtual int Httpd_CreateStreamResponse(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection,
            const char *url, const char *method, const char *upload_data,
            size_t *upload_data_size) override;

    virtual int Httpd_PostComplete(Kis_Net_Httpd_Connection *concls) override;

protected:
    path_func path;
    gen_func generator;
    kis_recursive_timed_mutex *mutex;
};


// Extremely simple callback-based POST responder linked to a chainbuf buffer
class Kis_Net_Httpd_Simple_Post_Endpoint : public Kis_Net_Httpd_Chain_Stream_Handler {
public:
    using handler_func = 
        std::function<unsigned int (std::ostream& stream, const std::string& uri, SharedStructured post_structured,
                Kis_Net_Httpd_Connection::variable_cache_map& variable_cache)>;

    Kis_Net_Httpd_Simple_Post_Endpoint(const std::string& in_uri, handler_func in_func,
            kis_recursive_timed_mutex *in_mutex);
    Kis_Net_Httpd_Simple_Post_Endpoint(const std::string& in_uri, handler_func in_func);

    virtual ~Kis_Net_Httpd_Simple_Post_Endpoint() { }

    // HTTP handlers
    virtual bool Httpd_VerifyPath(const char *path, const char *method) override;

    virtual int Httpd_CreateStreamResponse(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection,
            const char *url, const char *method, const char *upload_data,
            size_t *upload_data_size) override;

    virtual int Httpd_PostComplete(Kis_Net_Httpd_Connection *concls) override;

protected:
    std::string uri;
    handler_func generator;
    kis_recursive_timed_mutex *mutex;
};

// Path-based basic post responder linked to a chainbuf buffer
class Kis_Net_Httpd_Path_Post_Endpoint : public Kis_Net_Httpd_Chain_Stream_Handler {
public:
    using path_func = std::function<bool (const std::vector<std::string>& path, const std::string& uri)>;
    using handler_func = 
        std::function<unsigned int (std::ostream& stream, 
                const std::vector<std::string>& path, const std::string& uri, 
                SharedStructured post_structured,
                Kis_Net_Httpd_Connection::variable_cache_map& variable_cache)>;

    Kis_Net_Httpd_Path_Post_Endpoint(path_func in_path, handler_func in_func);
    Kis_Net_Httpd_Path_Post_Endpoint(path_func in_path, handler_func in_func,
            kis_recursive_timed_mutex *in_mutex);

    virtual ~Kis_Net_Httpd_Path_Post_Endpoint() { }

    // HTTP handlers
    virtual bool Httpd_VerifyPath(const char *path, const char *method) override;

    virtual int Httpd_CreateStreamResponse(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection,
            const char *url, const char *method, const char *upload_data,
            size_t *upload_data_size) override;

    virtual int Httpd_PostComplete(Kis_Net_Httpd_Connection *concls) override;

protected:
    path_func path;
    handler_func generator;
    kis_recursive_timed_mutex *mutex;
};

#define KIS_SESSION_COOKIE      "KISMET"
#define KIS_HTTPD_POSTBUFFERSZ  (1024 * 32)

class Kis_Net_Httpd : public LifetimeGlobal {
public:
    static std::string global_name() { return "HTTPD_SERVER"; }

    static std::shared_ptr<Kis_Net_Httpd> create_httpd() {
        std::shared_ptr<Kis_Net_Httpd> mon(new Kis_Net_Httpd());
        Globalreg::globalreg->RegisterLifetimeGlobal(mon);
        Globalreg::globalreg->InsertGlobal(global_name(), mon);
        return mon;
    }

private:
    Kis_Net_Httpd();

public:
    virtual ~Kis_Net_Httpd();

    int StartHttpd();
    int StopHttpd();

    bool HttpdRunning() { return running; }
    unsigned int FetchPort() { return http_port; };
    bool FetchUsingSSL() { return use_ssl; };

    void RegisterSessionHandler(std::shared_ptr<Kis_Httpd_Websession> in_session);

    // All standard handlers require a login
    void RegisterHandler(Kis_Net_Httpd_Handler *in_handler);
    void RemoveHandler(Kis_Net_Httpd_Handler *in_handler);

    // Unauth handlers do not require a login; use of them should be very limited.
    void RegisterUnauthHandler(Kis_Net_Httpd_Handler *in_handler);
    void RemoveUnauthHandler(Kis_Net_Httpd_Handler *in_handler);

    static std::string GetSuffix(std::string url);
    static std::string StripSuffix(std::string url);

    void RegisterMimeType(std::string suffix, std::string mimetype);
    std::string GetMimeType(std::string suffix);

    // Register a static files directory (used for system, home, and plugin data)
    void RegisterStaticDir(std::string in_url_prefix, std::string in_path);

    // Fixed alias/rewrites
    void RegisterAlias(const std::string& in_alias, const std::string& in_dest);
    void RemoveAlias(const std::string& in_alias);

    // Interrogate the session handler and figure out if this connection has a
    // valid session; optionally sends basic auth failure automatically
    bool HasValidSession(Kis_Net_Httpd_Connection *connection, bool send_reject = true);

    // Create a session; if connection is not null, insert session into connection.
    // If response is not null, append to the response
    std::shared_ptr<Kis_Net_Httpd_Session> CreateSession(Kis_Net_Httpd_Connection *connection, 
            struct MHD_Response *response, time_t in_lifetime);

    // Append a session cookie if we have a valid session for this connection
    static void AppendHttpSession(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection);

    // Append timestamp and mime headers
    static void AppendStandardHeaders(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection, const char *url);

    // Queue a http response
    static int SendHttpResponse(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection);

    // Send a standard HTTP response appending the session and standard 
    // headers
    static int SendStandardHttpResponse(Kis_Net_Httpd *httpd,
            Kis_Net_Httpd_Connection *connection, const char *url);

    // Catch MHD panics and try to close more elegantly
    static void MHD_Panic(void *cls, const char *file, unsigned int line,
            const char *reason);

protected:
    unsigned int http_port;
    std::string http_host;

    bool http_serve_files, http_serve_user_files;

    std::string uri_prefix;

    struct MHD_Daemon *microhttpd;

    // Vector of unauthorized handlers that do not need a login; there should be very very few
    // of these.  Static file handlers, and the very basic user name handler to make the initial
    // login display are about the only ones
    std::vector<Kis_Net_Httpd_Handler *> unauth_handler_vec;

    // General handler vec.  All of these require a valid login.
    std::vector<Kis_Net_Httpd_Handler *> handler_vec;

    std::string conf_username, conf_password;

    bool use_ssl;
    char *cert_pem, *cert_key;
    std::string pem_path, key_path;

    bool running;

    std::map<std::string, std::string> mime_type_map;

    std::map<std::string, std::string> alias_rewrite_map;

    class static_dir {
    public:
        static_dir(std::string prefix, std::string path) {
            this->prefix = prefix;
            this->path = path;
        };

        std::string prefix;
        std::string path;
    };

    std::vector<static_dir> static_dir_vec;

    kis_recursive_timed_mutex controller_mutex;
    kis_recursive_timed_mutex session_mutex;

    // Handle the requests and dispatch to controllers
    static int http_request_handler(void *cls, struct MHD_Connection *connection,
            const char *url, const char *method, const char *version,
            const char *upload_data, size_t *upload_data_size, void **ptr);

    static void http_request_completed(void *cls, struct MHD_Connection *connection,
            void **con_cls, enum MHD_RequestTerminationCode toe);

    static int handle_static_file(void *cls, Kis_Net_Httpd_Connection *connection,
            const char *url, const char *method);

    static int http_post_handler(void *coninfo_cls, enum MHD_ValueKind kind, 
            const char *key, const char *filename, const char *content_type,
            const char *transfer_encoding, const char *data, 
            uint64_t off, size_t size);

    char *read_ssl_file(std::string in_fname);

    void AddSession(std::shared_ptr<Kis_Net_Httpd_Session> in_session);
    void DelSession(std::string in_key);
    void DelSession(std::map<std::string, std::shared_ptr<Kis_Net_Httpd_Session>>::iterator in_itr);
    // Find a valid session; will return a session or nullptr if no session key is found, or if the
    // session is found but expired.
    std::shared_ptr<Kis_Net_Httpd_Session> FindSession(const std::string& in_session_key);
    void WriteSessions();

    std::map<std::string, std::shared_ptr<Kis_Net_Httpd_Session>> session_map;

    bool store_sessions;
    std::string sessiondb_file;
    ConfigFile *session_db;

    std::shared_ptr<Kis_Httpd_Websession> websession;
    unsigned int session_timeout;

};

#endif

