#include "Python.h"
#include "../_ssl.h"

#include "openssl/err.h"
#include "openssl/bio.h"
#include "openssl/pem.h"
#include "openssl/x509.h"

/*[clinic input]
module _ssl
class _ssl.Certificate "PySSLCertificate *" "PySSLCertificate_Type"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=780fc647948cfffc]*/

#include "clinic/cert.c.h"

static PyObject *
newCertificate(PyTypeObject *type, X509 *cert, int upref)
{
    PySSLCertificate *self;

    assert(type != NULL && type->tp_alloc != NULL);
    assert(cert != NULL);

    self = (PySSLCertificate *) type->tp_alloc(type, 0);
    if (self == NULL) {
        return NULL;
    }
    if (upref == 1) {
        X509_up_ref(cert);
    }
    self->cert = cert;
    self->hash = -1;

    return (PyObject *) self;
}

static PyObject *
_PySSL_CertificateFromX509(_sslmodulestate *state, X509 *cert, int upref)
{
    return newCertificate(state->PySSLCertificate_Type, cert, upref);
}

static PyObject*
_PySSL_CertificateFromX509Stack(_sslmodulestate *state, STACK_OF(X509) *stack, int upref)
{
    int len, i;
    PyObject *result = NULL;

    len = sk_X509_num(stack);
    result = PyList_New(len);
    if (result == NULL) {
        return NULL;
    }
    for (i = 0; i < len; i++) {
        X509 *cert = sk_X509_value(stack, i);
        PyObject *ocert = _PySSL_CertificateFromX509(state, cert, upref);
        if (ocert == NULL) {
            Py_DECREF(result);
            return NULL;
        }
        PyList_SetItem(result, i, ocert);
    }
    return result;
}

/*[clinic input]
_ssl.Certificate.public_bytes
    format: int(c_default="PY_SSL_ENCODING_PEM") = Encoding.PEM

[clinic start generated code]*/

static PyObject *
_ssl_Certificate_public_bytes_impl(PySSLCertificate *self, int format)
/*[clinic end generated code: output=c01ddbb697429e12 input=4d38c45e874b0e64]*/
{
    BIO *bio;
    int retcode;
    PyObject *result;
    _sslmodulestate *state = get_state_cert(self);

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        PyErr_SetString(state->PySSLErrorObject,
                        "failed to allocate BIO");
        return NULL;
    }
    switch(format) {
    case PY_SSL_ENCODING_PEM:
        retcode = PEM_write_bio_X509(bio, self->cert);
        break;
    case PY_SSL_ENCODING_PEM_AUX:
        retcode = PEM_write_bio_X509_AUX(bio, self->cert);
        break;
    case PY_SSL_ENCODING_DER:
        retcode = i2d_X509_bio(bio, self->cert);
        break;
    default:
        PyErr_SetString(PyExc_ValueError, "Unsupported format");
        BIO_free(bio);
        return NULL;
    }
    if (retcode != 1) {
        BIO_free(bio);
        _setSSLError(state, NULL, 0, __FILE__, __LINE__);
        return NULL;
    }
    if (format == PY_SSL_ENCODING_DER) {
        result = _PySSL_BytesFromBIO(state, bio);
    } else {
        result = _PySSL_UnicodeFromBIO(state, bio, "error");
    }
    BIO_free(bio);
    return result;
}


/*[clinic input]
_ssl.Certificate.get_info

[clinic start generated code]*/

static PyObject *
_ssl_Certificate_get_info_impl(PySSLCertificate *self)
/*[clinic end generated code: output=0f0deaac54f4408b input=ba2c1694b39d0778]*/
{
    return _decode_certificate(get_state_cert(self), self->cert);
}

static PyObject*
_x509name_print(_sslmodulestate *state, X509_NAME *name, int indent, unsigned long flags)
{
    PyObject *res;
    BIO *biobuf;

    biobuf = BIO_new(BIO_s_mem());
    if (biobuf == NULL) {
        PyErr_SetString(PyExc_MemoryError, "failed to allocate BIO");
        return NULL;
    }

    if (X509_NAME_print_ex(biobuf, name, indent, flags) <= 0) {
        _setSSLError(state, NULL, 0, __FILE__, __LINE__);
        BIO_free(biobuf);
        return NULL;
    }
    res = _PySSL_UnicodeFromBIO(state, biobuf, "strict");
    BIO_free(biobuf);
    return res;
}

/* ************************************************************************
 * PySSLCertificate_Type
 */

#define PySSLCertificate_CAST(op)   ((PySSLCertificate *)(op))

static PyObject *
certificate_repr(PyObject *op)
{
    PyObject *osubject, *result;
    PySSLCertificate *self = PySSLCertificate_CAST(op);

    /* subject string is ASCII encoded, UTF-8 chars are quoted */
    osubject = _x509name_print(
        get_state_cert(self),
        X509_get_subject_name(self->cert),
        0,
        XN_FLAG_RFC2253
    );
    if (osubject == NULL)
        return NULL;
    result = PyUnicode_FromFormat(
        "<%s '%U'>",
        Py_TYPE(self)->tp_name, osubject
    );
    Py_DECREF(osubject);
    return result;
}

static Py_hash_t
certificate_hash(PyObject *op)
{
    PySSLCertificate *self = PySSLCertificate_CAST(op);
    if (self->hash == (Py_hash_t)-1) {
        unsigned long hash;
        hash = X509_subject_name_hash(self->cert);
        if ((Py_hash_t)hash == (Py_hash_t)-1) {
            self->hash = -2;
        } else {
            self->hash = (Py_hash_t)hash;
        }
    }
    return self->hash;
}

static PyObject *
certificate_richcompare(PyObject *lhs, PyObject *rhs, int op)
{
    int cmp;
    PySSLCertificate *self = PySSLCertificate_CAST(lhs);
    _sslmodulestate *state = get_state_cert(self);

    if (Py_TYPE(rhs) != state->PySSLCertificate_Type) {
        Py_RETURN_NOTIMPLEMENTED;
    }
    /* only support == and != */
    if ((op != Py_EQ) && (op != Py_NE)) {
        Py_RETURN_NOTIMPLEMENTED;
    }
    cmp = X509_cmp(self->cert, ((PySSLCertificate*)rhs)->cert);
    if (((op == Py_EQ) && (cmp == 0)) || ((op == Py_NE) && (cmp != 0))) {
        Py_RETURN_TRUE;
    } else {
        Py_RETURN_FALSE;
    }
}

static void
certificate_dealloc(PyObject *op)
{
    PySSLCertificate *self = PySSLCertificate_CAST(op);
    PyTypeObject *tp = Py_TYPE(self);
    X509_free(self->cert);
    (void)Py_TYPE(self)->tp_free(self);
    Py_DECREF(tp);
}

static PyMethodDef certificate_methods[] = {
    /* methods */
    _SSL_CERTIFICATE_PUBLIC_BYTES_METHODDEF
    _SSL_CERTIFICATE_GET_INFO_METHODDEF
    {NULL, NULL}
};

static PyType_Slot PySSLCertificate_slots[] = {
    {Py_tp_dealloc, certificate_dealloc},
    {Py_tp_repr, certificate_repr},
    {Py_tp_hash, certificate_hash},
    {Py_tp_richcompare, certificate_richcompare},
    {Py_tp_methods, certificate_methods},
    {0, 0},
};

static PyType_Spec PySSLCertificate_spec = {
    "_ssl.Certificate",
    sizeof(PySSLCertificate),
    0,
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE,
    PySSLCertificate_slots,
};
