//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012- OpenVPN Inc.
//
//    SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
//

#ifndef OPENVPN_PKI_CCLIST_H
#define OPENVPN_PKI_CCLIST_H

#include <string>
#include <sstream>
#include <fstream>

#include <openvpn/common/size.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/file.hpp>
#include <openvpn/common/string.hpp>

namespace openvpn {

// Parse a concatenated list of certs and CRLs (PEM format).
// Abstracts CertList and CRLList, so can be used with any crypto lib.
// CertList and CRLList must define Item type.
template <typename CertList, typename CRLList>
class CertCRLListTemplate
{
  public:
    OPENVPN_EXCEPTION(parse_cert_crl_error);

    CertCRLListTemplate()
    {
    }

    explicit CertCRLListTemplate(const std::string &content, const std::string &title)
    {
        from_string(content, title, &certs, &crls);
    }

    void parse_pem(const std::string &content, const std::string &title)
    {
        from_string(content, title, &certs, &crls);
    }

    void parse_pem_file(const std::string &filename)
    {
        from_file(filename, &certs, &crls);
    }

    std::string render_pem() const
    {
        return certs.render_pem() + crls.render_pem();
    }

    static void from_istream(std::istream &in, const std::string &title, CertList *cert_list, CRLList *crl_list)
    {
        static const char cert_start[] = "-----BEGIN CERTIFICATE-----";
        static const char cert_end[] = "-----END CERTIFICATE-----";
        static const char crl_start[] = "-----BEGIN X509 CRL-----";
        static const char crl_end[] = "-----END X509 CRL-----";

        enum
        {
            S_OUTSIDE, // outside of CERT or CRL block
            S_IN_CERT, // in CERT block
            S_IN_CRL,  // in CRL block
        };

        std::string line;
        int state = S_OUTSIDE;
        std::string item = "";
        int line_num = 0;

        while (std::getline(in, line))
        {
            line_num++;
            string::trim(line);
            if (state == S_OUTSIDE)
            {
                if (line == cert_start)
                {
                    if (!cert_list)
                        OPENVPN_THROW(parse_cert_crl_error, title << ":" << line_num << " : not expecting a CERT");
                    state = S_IN_CERT;
                }
                else if (line == crl_start)
                {
                    if (!crl_list)
                        OPENVPN_THROW(parse_cert_crl_error, title << ":" << line_num << " : not expecting a CRL");
                    state = S_IN_CRL;
                }
            }
            if (state != S_OUTSIDE)
            {
                item += line;
                item += "\n";
            }
            if (state == S_IN_CERT && line == cert_end)
            {
                try
                {
                    cert_list->emplace_back(item, title);
                }
                catch (const std::exception &e)
                {
                    OPENVPN_THROW(parse_cert_crl_error, title << ":" << line_num << " : error parsing CERT: " << e.what());
                }
                state = S_OUTSIDE;
                item = "";
            }
            if (state == S_IN_CRL && line == crl_end)
            {
                try
                {
                    crl_list->emplace_back(item);
                }
                catch (const std::exception &e)
                {
                    OPENVPN_THROW(parse_cert_crl_error, title << ":" << line_num << " : error parsing CRL: " << e.what());
                }
                state = S_OUTSIDE;
                item = "";
            }
        }
        if (state != S_OUTSIDE)
            OPENVPN_THROW(parse_cert_crl_error, title << " : CERT/CRL content ended unexpectedly without END marker");
    }

    static void from_string(const std::string &content, const std::string &title, CertList *cert_list, CRLList *crl_list = nullptr)
    {
        std::stringstream in(content);
        from_istream(in, title, cert_list, crl_list);
    }

    static void from_file(const std::string &filename, CertList *cert_list, CRLList *crl_list = nullptr)
    {
        std::ifstream ifs(filename.c_str());
        if (!ifs)
            OPENVPN_THROW(open_file_error, "cannot open CERT/CRL file " << filename);
        from_istream(ifs, filename, cert_list, crl_list);
        if (ifs.bad())
            OPENVPN_THROW(open_file_error, "cannot read CERT/CRL file " << filename);
    }

    CertList certs;
    CRLList crls;
};

} // namespace openvpn

#endif // OPENVPN_PKI_CCLIST_H
