// See the file "COPYING" in the main distribution directory for copyright.

#pragma once

#include <queue>
#include <string>

#include "zeek/IPAddr.h"
#include "zeek/analyzer/protocol/http/events.bif.h"
#include "zeek/analyzer/protocol/mime/MIME.h"
#include "zeek/analyzer/protocol/pia/PIA.h"
#include "zeek/analyzer/protocol/tcp/ContentLine.h"
#include "zeek/analyzer/protocol/tcp/TCP.h"
#include "zeek/analyzer/protocol/zip/ZIP.h"

namespace zeek::analyzer::http {

enum CHUNKED_TRANSFER_STATE : uint8_t {
    NON_CHUNKED_TRANSFER,
    BEFORE_CHUNK,
    EXPECT_CHUNK_SIZE,
    EXPECT_CHUNK_DATA,
    EXPECT_CHUNK_DATA_CRLF,
    EXPECT_CHUNK_TRAILER,
    EXPECT_NOTHING,
};

class HTTP_Entity;
class HTTP_Message;
class HTTP_Analyzer;

class HTTP_Entity final : public analyzer::mime::MIME_Entity {
public:
    HTTP_Entity(HTTP_Message* msg, analyzer::mime::MIME_Entity* parent_entity, int expect_body);
    ~HTTP_Entity() override {
        if ( zip ) {
            zip->Done();
            delete zip;
        }
    }

    void EndOfData() override;
    void Deliver(int len, const char* data, bool trailing_CRLF) override;
    bool Undelivered(int64_t len);
    int64_t BodyLength() const { return body_length; }
    int64_t HeaderLength() const { return header_length; }
    void SkipBody() { deliver_body = false; }
    const std::string& FileID() const { return precomputed_file_id; }

protected:
    class UncompressedOutput;
    friend class UncompressedOutput;

    HTTP_Message* http_message;
    int chunked_transfer_state;
    int64_t content_length;
    int64_t range_length;
    int64_t expect_data_length;
    int expect_body;
    int64_t body_length;
    int64_t header_length;
    enum : uint8_t { IDENTITY, GZIP, COMPRESS, DEFLATE } encoding;
    analyzer::zip::ZIP_Analyzer* zip;
    bool deliver_body;
    bool is_partial_content;
    uint64_t offset;
    int64_t instance_length; // total length indicated by content-range
    bool send_size;          // whether to send size indication to FAF
    bool expect_100_cont = false;
    std::string precomputed_file_id;

    analyzer::mime::MIME_Entity* NewChildEntity() override { return new HTTP_Entity(http_message, this, 1); }

    void DeliverBody(int len, const char* data, bool trailing_CRLF);
    void DeliverBodyClear(int len, const char* data, bool trailing_CRLF);

    void SubmitData(int len, const char* buf) override;

    void SetPlainDelivery(int64_t length);

    void SubmitHeader(analyzer::mime::MIME_Header* h) override;
    void SubmitAllHeaders() override;
};

enum : uint8_t {
    HTTP_BODY_NOT_EXPECTED,
    HTTP_BODY_EXPECTED,
    HTTP_BODY_MAYBE,
};

// Finishing HTTP Messages:
//
// HTTP_Entity::SubmitAllHeaders	-> EndOfData (no body)
// HTTP_Entity::Deliver	-> EndOfData (end of body)
// HTTP_Analyzer::Done	-> {Request,Reply}Made (connection terminated)
// {Request,Reply}Made	-> HTTP_Message::Done
// HTTP_Message::Done	-> MIME_Message::Done, EndOfData, HTTP_MessageDone
// MIME_Entity::EndOfData	-> Message::EndEntity
// HTTP_Message::EndEntity	-> Message::Done
// HTTP_MessageDone	-> {Request,Reply}Made

class HTTP_Message final : public analyzer::mime::MIME_Message {
    friend class HTTP_Entity;

public:
    HTTP_Message(HTTP_Analyzer* analyzer, analyzer::tcp::ContentLine_Analyzer* cl, bool is_orig, int expect_body,
                 int64_t init_header_length);
    ~HTTP_Message() override;
    void Done(bool interrupted, const char* msg);
    void Done() override { Done(false, "message ends normally"); }

    bool Undelivered(int64_t len);

    void BeginEntity(analyzer::mime::MIME_Entity* /* entity */) override;
    void EndEntity(analyzer::mime::MIME_Entity* entity) override;
    void SubmitHeader(analyzer::mime::MIME_Header* h) override;
    void SubmitAllHeaders(analyzer::mime::MIME_HeaderList& /* hlist */) override;
    void SubmitData(int len, const char* buf) override;
    bool RequestBuffer(int* plen, char** pbuf) override;
    void SubmitAllData();
    void SubmitEvent(int event_type, const char* detail) override;

    void SubmitTrailingHeaders(analyzer::mime::MIME_HeaderList& /* hlist */);
    void SetPlainDelivery(int64_t length);
    void SetDeliverySize(int64_t length);
    void SkipEntityData();

    HTTP_Analyzer* MyHTTP_Analyzer() const { return analyzer; }

    void Weird(const char* msg);
    bool IsOrig() { return is_orig; }

protected:
    HTTP_Analyzer* analyzer;
    analyzer::tcp::ContentLine_Analyzer* content_line;
    bool is_orig;

    char* entity_data_buffer;

    double start_time;

    int64_t body_length;   // total length of entity bodies
    int64_t header_length; // total length of headers, including the request/reply line

    // Total length of content gaps that are "successfully" skipped.
    // Note: this might NOT include all content gaps!
    int64_t content_gap_length;

    HTTP_Entity* current_entity;

    RecordValPtr BuildMessageStat(bool interrupted, const char* msg);
};

class HTTP_Analyzer final : public analyzer::tcp::TCP_ApplicationAnalyzer {
public:
    HTTP_Analyzer(Connection* conn);

    void HTTP_Header(bool is_orig, analyzer::mime::MIME_Header* h);
    void HTTP_EntityData(bool is_orig, String* entity_data);
    void HTTP_MessageDone(bool is_orig, HTTP_Message* message);
    void HTTP_Event(const char* category, const char* detail);
    void HTTP_Event(const char* category, StringValPtr detail);

    void SkipEntityData(bool is_orig);

    bool IsConnectionClose() { return connection_close; }
    int HTTP_ReplyCode() const { return reply_code; };

    // Overridden from Analyzer.
    void Done() override;
    void DeliverStream(int len, const u_char* data, bool orig) override;
    void Undelivered(uint64_t seq, int len, bool orig) override;
    void FlipRoles() override;

    // Overridden from analyzer::tcp::TCP_ApplicationAnalyzer
    void EndpointEOF(bool is_orig) override;
    void ConnectionFinished(bool half_finished) override;
    void ConnectionReset() override;
    void PacketWithRST() override;

    struct HTTP_VersionNumber {
        uint8_t major = 0;
        uint8_t minor = 0;

        bool operator==(const HTTP_VersionNumber& other) const { return minor == other.minor && major == other.major; }

        bool operator!=(const HTTP_VersionNumber& other) const { return ! operator==(other); }

        double ToDouble() const { return major + minor * 0.1; }
    };

    double GetRequestVersion() { return request_version.ToDouble(); };
    double GetReplyVersion() { return reply_version.ToDouble(); };
    HTTP_VersionNumber GetRequestVersionNumber() { return request_version; };
    HTTP_VersionNumber GetReplyVersionNumber() { return reply_version; };
    int GetRequestOngoing() { return request_ongoing; };
    int GetReplyOngoing() { return reply_ongoing; };

    static analyzer::Analyzer* Instantiate(Connection* conn) { return new HTTP_Analyzer(conn); }

    static bool Available() {
        return (http_request || http_reply || http_header || http_all_headers || http_begin_entity || http_end_entity ||
                http_content_type || http_entity_data || http_message_done || http_event || http_stats);
    }

protected:
    void GenStats();

    int HTTP_RequestLine(const char* line, const char* end_of_line);
    int HTTP_ReplyLine(const char* line, const char* end_of_line);

    void InitHTTPMessage(analyzer::tcp::ContentLine_Analyzer* cl, HTTP_Message*& message, bool is_orig, int expect_body,
                         int64_t init_header_length);

    const char* PrefixMatch(const char* line, const char* end_of_line, const char* prefix, bool ignore_case = false);
    const char* PrefixWordMatch(const char* line, const char* end_of_line, const char* prefix,
                                bool ignore_case = false);

    bool ParseRequest(const char* line, const char* end_of_line);
    HTTP_VersionNumber HTTP_Version(int len, const char* data);

    void SetVersion(HTTP_VersionNumber* version, HTTP_VersionNumber new_version);

    bool RequestExpected() const { return num_requests == 0 || keep_alive; }

    void HTTP_Request();
    void HTTP_Reply();
    void HTTP_Upgrade();

    void RequestMade(bool interrupted, const char* msg);
    void ReplyMade(bool interrupted, const char* msg);
    void RequestClash(Val* clash_val);

    const String* UnansweredRequestMethod();

    int HTTP_ReplyCode(const char* code_str);
    int ExpectReplyMessageBody();

    StringValPtr TruncateURI(const StringValPtr& uri);

    int request_state, reply_state;
    int num_requests, num_replies;
    int num_request_lines, num_reply_lines;
    HTTP_VersionNumber request_version, reply_version;
    int keep_alive;
    int connection_close;
    int request_ongoing, reply_ongoing;

    bool connect_request;
    analyzer::pia::PIA_TCP* pia;
    // set to true after a connection was upgraded
    bool upgraded;
    // set to true when encountering an "connection" header in a reply.
    bool upgrade_connection;
    // set to the protocol string when encountering an "upgrade" header
    // in a reply.
    std::string upgrade_protocol;

    StringValPtr request_method;

    // request_URI is in the original form (may contain '%<hex><hex>'
    // sequences).
    StringValPtr request_URI;

    // unescaped_URI does not contain escaped sequences.
    StringValPtr unescaped_URI;

    std::queue<StringValPtr> unanswered_requests;

    int reply_code;
    StringValPtr reply_reason_phrase;

    analyzer::tcp::ContentLine_Analyzer* content_line_orig;
    analyzer::tcp::ContentLine_Analyzer* content_line_resp;

    HTTP_Message* request_message;
    HTTP_Message* reply_message;
};

extern bool is_reserved_URI_char(unsigned char ch);
extern bool is_unreserved_URI_char(unsigned char ch);
extern void escape_URI_char(unsigned char ch, unsigned char*& p);
extern String* unescape_URI(const u_char* line, const u_char* line_end, analyzer::Analyzer* analyzer);

} // namespace zeek::analyzer::http
