#include "pycurl.h"
#include "docstrings.h"


/*************************************************************************
// CurlSlistObject
**************************************************************************/

PYCURL_INTERNAL int
util_curlslist_update(CurlSlistObject **old, struct curl_slist *slist)
{
    CurlSlistObject *new_obj = PyObject_New(CurlSlistObject, p_CurlSlist_Type);
    if (new_obj == NULL) {
        return -1;
    }
    new_obj->slist = slist;
    Py_XDECREF(*old);
    *old = new_obj;
    return 0;
}

PYCURL_INTERNAL void
do_curlslist_dealloc(CurlSlistObject *self) {
    if (self->slist != NULL) {
        curl_slist_free_all(self->slist);
        self->slist = NULL;
    }
    CurlSlist_Type.tp_free(self);
}

PYCURL_INTERNAL PyTypeObject CurlSlist_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "pycurl.CurlSlist",         /* tp_name */
    sizeof(CurlSlistObject),    /* tp_basicsize */
    0,                          /* tp_itemsize */
    (destructor)do_curlslist_dealloc, /* tp_dealloc */
    0,                          /* tp_print / tp_vectorcall_offset */
    0,                          /* tp_getattr */
    0,                          /* tp_setattr */
    0,                          /* tp_reserved / tp_as_async */
    0,                          /* tp_repr */
    0,                          /* tp_as_number */
    0,                          /* tp_as_sequence */
    0,                          /* tp_as_mapping */
    0,                          /* tp_hash */
    0,                          /* tp_call */
    0,                          /* tp_str */
    0,                          /* tp_getattro */
    0,                          /* tp_setattro */
    0,                          /* tp_as_buffer */
    0,                          /* tp_flags */
    0,                          /* tp_doc */
    0,                          /* tp_traverse */
    0,                          /* tp_clear */
    0,                          /* tp_richcompare */
    0,                          /* tp_weaklistoffset */
    0,                          /* tp_iter */
    0,                          /* tp_iternext */
    0,                          /* tp_methods */
    0,                          /* tp_members */
    0,                          /* tp_getset */
    0,                          /* tp_base */
    0,                          /* tp_dict */
    0,                          /* tp_descr_get */
    0,                          /* tp_descr_set */
    0,                          /* tp_dictoffset */
    0,                          /* tp_init */
    0,                          /* tp_alloc */
    0,                          /* tp_new */
    0,                          /* tp_free */
    0,                          /* tp_is_gc */
    0,                          /* tp_bases */
    0,                          /* tp_mro */
    0,                          /* tp_cache */
    0,                          /* tp_subclasses */
    0,                          /* tp_weaklist */
    0,                          /* tp_del */
    0,                          /* tp_version_tag */
    0,                          /* tp_finalize */
    0,                          /* tp_vectorcall */
};


/*************************************************************************
// CurlHttppostObject
**************************************************************************/

PYCURL_INTERNAL int
util_curlhttppost_update(CurlObject *obj, struct curl_httppost *httppost, PyObject *reflist)
{
    CurlHttppostObject *new_obj = PyObject_New(CurlHttppostObject, p_CurlHttppost_Type);
    if (new_obj == NULL) {
        return -1;
    }
    new_obj->httppost = httppost;
    new_obj->reflist = reflist;
    Py_XDECREF(obj->httppost);
    obj->httppost = new_obj;
    return 0;
}

PYCURL_INTERNAL void
do_curlhttppost_dealloc(CurlHttppostObject *self) {
    if (self->httppost != NULL) {
        PYCURL_IGNORE_DEPRECATED_BEGIN
        curl_formfree(self->httppost);
        PYCURL_IGNORE_DEPRECATED_END
        self->httppost = NULL;
    }
    Py_CLEAR(self->reflist);
    CurlHttppost_Type.tp_free(self);
}

PYCURL_INTERNAL PyTypeObject CurlHttppost_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "pycurl.CurlHttppost",      /* tp_name */
    sizeof(CurlHttppostObject), /* tp_basicsize */
    0,                          /* tp_itemsize */
    (destructor)do_curlhttppost_dealloc, /* tp_dealloc */
    0,                          /* tp_print / tp_vectorcall_offset */
    0,                          /* tp_getattr */
    0,                          /* tp_setattr */
    0,                          /* tp_reserved / tp_as_async */
    0,                          /* tp_repr */
    0,                          /* tp_as_number */
    0,                          /* tp_as_sequence */
    0,                          /* tp_as_mapping */
    0,                          /* tp_hash */
    0,                          /* tp_call */
    0,                          /* tp_str */
    0,                          /* tp_getattro */
    0,                          /* tp_setattro */
    0,                          /* tp_as_buffer */
    0,                          /* tp_flags */
    0,                          /* tp_doc */
    0,                          /* tp_traverse */
    0,                          /* tp_clear */
    0,                          /* tp_richcompare */
    0,                          /* tp_weaklistoffset */
    0,                          /* tp_iter */
    0,                          /* tp_iternext */
    0,                          /* tp_methods */
    0,                          /* tp_members */
    0,                          /* tp_getset */
    0,                          /* tp_base */
    0,                          /* tp_dict */
    0,                          /* tp_descr_get */
    0,                          /* tp_descr_set */
    0,                          /* tp_dictoffset */
    0,                          /* tp_init */
    0,                          /* tp_alloc */
    0,                          /* tp_new */
    0,                          /* tp_free */
    0,                          /* tp_is_gc */
    0,                          /* tp_bases */
    0,                          /* tp_mro */
    0,                          /* tp_cache */
    0,                          /* tp_subclasses */
    0,                          /* tp_weaklist */
    0,                          /* tp_del */
    0,                          /* tp_version_tag */
    0,                          /* tp_finalize */
    0,                          /* tp_vectorcall */
};


/*************************************************************************
// static utility functions
**************************************************************************/


/* assert some CurlObject invariants */
PYCURL_INTERNAL void
assert_curl_state(const CurlObject *self)
{
    assert(self != NULL);
    assert(PyObject_IsInstance((PyObject *) self, (PyObject *) p_Curl_Type) == 1);
    (void) pycurl_get_thread_state(self);
}


/* check state for methods */
PYCURL_INTERNAL int
check_curl_state(const CurlObject *self, int flags, const char *name)
{
    assert_curl_state(self);
    if ((flags & 1) && self->handle == NULL) {
        PyErr_Format(ErrorObject, "cannot invoke %s() - no curl handle", name);
        return -1;
    }
    if ((flags & 2) && pycurl_get_thread_state(self) != NULL) {
        PyErr_Format(ErrorObject, "cannot invoke %s() - perform() is currently running", name);
        return -1;
    }
    return 0;
}


/*************************************************************************
// CurlObject
**************************************************************************/

/* --------------- construct/destruct (i.e. open/close) --------------- */

/* initializer - used to initialize curl easy handles for use with pycurl */
static int
util_curl_init(CurlObject *self)
{
    int res;

    /* Set curl error buffer and zero it */
    res = curl_easy_setopt(self->handle, CURLOPT_ERRORBUFFER, self->error);
    if (res != CURLE_OK) {
        return (-1);
    }
    memset(self->error, 0, sizeof(self->error));

    /* Set backreference */
    res = curl_easy_setopt(self->handle, CURLOPT_PRIVATE, (char *) self);
    if (res != CURLE_OK) {
        return (-1);
    }

    /* Enable NOPROGRESS by default, i.e. no progress output */
    res = curl_easy_setopt(self->handle, CURLOPT_NOPROGRESS, (long)1);
    if (res != CURLE_OK) {
        return (-1);
    }

    /* Disable VERBOSE by default, i.e. no verbose output */
    res = curl_easy_setopt(self->handle, CURLOPT_VERBOSE, (long)0);
    if (res != CURLE_OK) {
        return (-1);
    }

    /* Set default USERAGENT */
    assert(g_pycurl_useragent);
    res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, g_pycurl_useragent);
    if (res != CURLE_OK) {
        return (-1);
    }

    /* Set CAINFO/PROXY_CAINFO or CAPATH/PROXY_CAPATH if runtime autodetection enabled */
    #ifdef PYCURL_AUTODETECT_CA
    if (g_pycurl_autodetected_cainfo) {
        res = curl_easy_setopt(self->handle, CURLOPT_CAINFO, g_pycurl_autodetected_cainfo);
        if (res != CURLE_OK) {
            return (-1);
        }

        #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 52, 0)
        res = curl_easy_setopt(self->handle, CURLOPT_PROXY_CAINFO, g_pycurl_autodetected_cainfo);
        if (res != CURLE_OK) {
            return (-1);
        }
        #endif
    } else if (g_pycurl_autodetected_capath) {
        res = curl_easy_setopt(self->handle, CURLOPT_CAPATH, g_pycurl_autodetected_capath);
        if (res != CURLE_OK) {
            return (-1);
        }

        #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 52, 0)
        res = curl_easy_setopt(self->handle, CURLOPT_PROXY_CAPATH, g_pycurl_autodetected_capath);
        if (res != CURLE_OK) {
            return (-1);
        }
        #endif
    }
    #endif

    return (0);
}

/* constructor */
PYCURL_INTERNAL CurlObject *
do_curl_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
{
    CurlObject *self;
    int res;
    int *ptr;

    if (subtype == p_Curl_Type && !PyArg_ParseTupleAndKeywords(args, kwds, "", empty_keywords)) {
        return NULL;
    }

    /* Allocate python curl object */
    self = (CurlObject *) subtype->tp_alloc(subtype, 0);
    if (self == NULL)
        return NULL;

    /* tp_alloc is expected to return zeroed memory */
    for (ptr = (int *) &self->dict;
        ptr < (int *) (((char *) self) + sizeof(CurlObject));
        ++ptr)
            assert(*ptr == 0);

    /* Initialize curl handle */
    self->handle = curl_easy_init();
    if (self->handle == NULL)
        goto error;

    res = util_curl_init(self);
    if (res < 0)
            goto error;
    /* Success - return new object */
    return self;

error:
    Py_DECREF(self);    /* this also closes self->handle */
    PyErr_SetString(ErrorObject, "initializing curl failed");
    return NULL;
}

/* duphandle */
PYCURL_INTERNAL CurlObject *
do_curl_duphandle(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    PyTypeObject *subtype;
    CurlObject *dup;
    int res;
    int *ptr;

    /* Allocate python curl object */
    subtype = Py_TYPE(self);
    dup = (CurlObject *) subtype->tp_alloc(subtype, 0);
    if (dup == NULL)
        return NULL;

    /* tp_alloc is expected to return zeroed memory */
    for (ptr = (int *) &dup->dict;
        ptr < (int *) (((char *) dup) + sizeof(CurlObject));
        ++ptr)
            assert(*ptr == 0);

    /* Clone the curl handle */
    dup->handle = curl_easy_duphandle(self->handle);
    if (dup->handle == NULL)
        goto error;

    /* Set curl error buffer and zero it */
    res = curl_easy_setopt(dup->handle, CURLOPT_ERRORBUFFER, dup->error);
    if (res != CURLE_OK) {
        goto error;
    }
    memset(dup->error, 0, sizeof(dup->error));

    /* Set backreference */
    res = curl_easy_setopt(dup->handle, CURLOPT_PRIVATE, (char *) dup);
    if (res != CURLE_OK) {
        goto error;
    }

    /* Copy attribute dictionary */
    if (self->dict != NULL) {
        dup->dict = PyDict_Copy(self->dict);
        if (dup->dict == NULL) {
            goto error;
        }
    }

    /* Checking for CURLE_OK is not required here.
     * All values have already been successfully setopt'ed with self->handle. */

    /* Assign and incref python callback and update data pointers */
    if (self->w_cb != NULL) {
        dup->w_cb = Py_NewRef(self->w_cb);
        curl_easy_setopt(dup->handle, CURLOPT_WRITEDATA, dup);
    }
    if (self->h_cb != NULL) {
        dup->h_cb = Py_NewRef(self->h_cb);
        curl_easy_setopt(dup->handle, CURLOPT_WRITEHEADER, dup);
    }
    if (self->r_cb != NULL) {
        dup->r_cb = Py_NewRef(self->r_cb);
        curl_easy_setopt(dup->handle, CURLOPT_READDATA, dup);
    }
    if (self->pro_cb != NULL) {
        dup->pro_cb = Py_NewRef(self->pro_cb);
        curl_easy_setopt(dup->handle, CURLOPT_PROGRESSDATA, dup);
    }
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0)
    if (self->xferinfo_cb != NULL) {
        dup->xferinfo_cb = Py_NewRef(self->xferinfo_cb);
        curl_easy_setopt(dup->handle, CURLOPT_XFERINFODATA, dup);
    }
#endif
    if (self->debug_cb != NULL) {
        dup->debug_cb = Py_NewRef(self->debug_cb);
        curl_easy_setopt(dup->handle, CURLOPT_DEBUGDATA, dup);
    }
    if (self->ioctl_cb != NULL) {
        dup->ioctl_cb = Py_NewRef(self->ioctl_cb);
        PYCURL_IGNORE_DEPRECATED_BEGIN
        curl_easy_setopt(dup->handle, CURLOPT_IOCTLDATA, dup);
        PYCURL_IGNORE_DEPRECATED_END
    }
    if (self->opensocket_cb != NULL) {
        dup->opensocket_cb = Py_NewRef(self->opensocket_cb);
        curl_easy_setopt(dup->handle, CURLOPT_OPENSOCKETDATA, dup);
    }
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7)
    if (self->closesocket_cb != NULL) {
        dup->closesocket_cb = Py_NewRef(self->closesocket_cb);
        curl_easy_setopt(dup->handle, CURLOPT_CLOSESOCKETDATA, dup);
    }
#endif
    if (self->sockopt_cb != NULL) {
        dup->sockopt_cb = Py_NewRef(self->sockopt_cb);
        curl_easy_setopt(dup->handle, CURLOPT_SOCKOPTDATA, dup);
    }
#ifdef HAVE_CURL_7_19_6_OPTS
    if (self->ssh_key_cb != NULL) {
        dup->ssh_key_cb = Py_NewRef(self->ssh_key_cb);
        curl_easy_setopt(dup->handle, CURLOPT_SSH_KEYDATA, dup);
    }
#endif
    if (self->seek_cb != NULL) {
        dup->seek_cb = Py_NewRef(self->seek_cb);
        curl_easy_setopt(dup->handle, CURLOPT_SEEKDATA, dup);
    }
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 80, 0)
    if (self->prereq_cb != NULL) {
        dup->prereq_cb = Py_NewRef(self->prereq_cb);
        curl_easy_setopt(dup->handle, CURLOPT_PREREQDATA, dup);
    }
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 0)
    if (self->fnmatch_cb != NULL) {
        dup->fnmatch_cb = Py_NewRef(self->fnmatch_cb);
        curl_easy_setopt(dup->handle, CURLOPT_FNMATCH_DATA, dup);
    }
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 59, 0)
    if (self->resolver_start_cb != NULL) {
        dup->resolver_start_cb = Py_NewRef(self->resolver_start_cb);
        curl_easy_setopt(dup->handle, CURLOPT_RESOLVER_START_DATA, dup);
    }
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 64, 0)
    if (self->trailer_cb != NULL) {
        dup->trailer_cb = Py_NewRef(self->trailer_cb);
        curl_easy_setopt(dup->handle, CURLOPT_TRAILERDATA, dup);
    }
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 74, 0)
    if (self->hstsread_cb != NULL) {
        dup->hstsread_cb = Py_NewRef(self->hstsread_cb);
        curl_easy_setopt(dup->handle, CURLOPT_HSTSREADDATA, dup);
    }
    if (self->hstswrite_cb != NULL) {
        dup->hstswrite_cb = Py_NewRef(self->hstswrite_cb);
        curl_easy_setopt(dup->handle, CURLOPT_HSTSWRITEDATA, dup);
    }
#endif

    /* Assign and incref python file objects */
    dup->readdata_fp = Py_XNewRef(self->readdata_fp);
    dup->writedata_fp = Py_XNewRef(self->writedata_fp);
    dup->writeheader_fp = Py_XNewRef(self->writeheader_fp);

    /* Assign and incref postfields object */
    dup->postfields_obj = Py_XNewRef(self->postfields_obj);

    /* Assign and incref ca certs related references */
    dup->ca_certs_obj = Py_XNewRef(self->ca_certs_obj);

    /* Assign and incref every curl_slist allocated by setopt */
    dup->httpheader = (CurlSlistObject *)Py_XNewRef((PyObject *)self->httpheader);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
    dup->proxyheader = (CurlSlistObject *)Py_XNewRef((PyObject *)self->proxyheader);
#endif
    dup->http200aliases = (CurlSlistObject *)Py_XNewRef((PyObject *)self->http200aliases);
    dup->quote = (CurlSlistObject *)Py_XNewRef((PyObject *)self->quote);
    dup->postquote = (CurlSlistObject *)Py_XNewRef((PyObject *)self->postquote);
    dup->prequote = (CurlSlistObject *)Py_XNewRef((PyObject *)self->prequote);
    dup->telnetoptions = (CurlSlistObject *)Py_XNewRef((PyObject *)self->telnetoptions);
#ifdef HAVE_CURLOPT_RESOLVE
    dup->resolve = (CurlSlistObject *)Py_XNewRef((PyObject *)self->resolve);
#endif
#ifdef HAVE_CURL_7_20_0_OPTS
    dup->mail_rcpt = (CurlSlistObject *)Py_XNewRef((PyObject *)self->mail_rcpt);
#endif
#ifdef HAVE_CURLOPT_CONNECT_TO
    dup->connect_to = (CurlSlistObject *)Py_XNewRef((PyObject *)self->connect_to);
#endif

    /* Assign and incref httppost */
    dup->httppost = (CurlHttppostObject *)Py_XNewRef((PyObject *)self->httppost);
#ifdef HAVE_CURL_MIME
    dup->mimepost_obj = Py_XNewRef(self->mimepost_obj);

    if (self->mimepost_obj != NULL) {
        /*
         * libcurl duplicates curl_mime_data_cb userdata pointers as-is.
         * Add one logical owner reference for every duplicated easy handle so
         * free callbacks run exactly once on the final release path.
         */
        curlmime_duphandle_incref_data_cb_owners(self->mimepost_obj);
    }
#endif

    /* Success - return cloned object */
    return dup;

error:
    Py_CLEAR(dup->dict);
    Py_DECREF(dup);    /* this also closes dup->handle */
    PyErr_SetString(ErrorObject, "cloning curl failed");
    return NULL;
}


/* util function shared by close() and clear() */
PYCURL_INTERNAL void
util_curl_xdecref(CurlObject *self, int flags, CURL *handle)
{
    if (flags & PYCURL_MEMGROUP_ATTRDICT) {
        /* Decrement refcount for attributes dictionary. */
        Py_CLEAR(self->dict);
    }

    if (flags & PYCURL_MEMGROUP_MULTI) {
        /* multi_stack is borrowed; multi_weakref is owned */
        self->multi_stack = NULL;
        Py_CLEAR(self->multi_weakref);
    }

    if (flags & PYCURL_MEMGROUP_CALLBACK) {
        /* Decrement refcount for python callbacks. */
        Py_CLEAR(self->w_cb);
        Py_CLEAR(self->h_cb);
        Py_CLEAR(self->r_cb);
        Py_CLEAR(self->pro_cb);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0)
        Py_CLEAR(self->xferinfo_cb);
#endif
        Py_CLEAR(self->debug_cb);
        Py_CLEAR(self->ioctl_cb);
        Py_CLEAR(self->seek_cb);
        Py_CLEAR(self->opensocket_cb);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7)
        Py_CLEAR(self->closesocket_cb);
#endif
        Py_CLEAR(self->sockopt_cb);
        Py_CLEAR(self->ssh_key_cb);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 80, 0)
        Py_CLEAR(self->prereq_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 0)
        Py_CLEAR(self->fnmatch_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 59, 0)
        Py_CLEAR(self->resolver_start_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 64, 0)
        Py_CLEAR(self->trailer_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 74, 0)
        Py_CLEAR(self->hstsread_cb);
        Py_CLEAR(self->hstswrite_cb);
#endif
    }

    if (flags & PYCURL_MEMGROUP_FILE) {
        /* Decrement refcount for python file objects. */
        Py_CLEAR(self->readdata_fp);
        Py_CLEAR(self->writedata_fp);
        Py_CLEAR(self->writeheader_fp);
    }

    if (flags & PYCURL_MEMGROUP_POSTFIELDS) {
        /* Decrement refcount for postfields object */
        Py_CLEAR(self->postfields_obj);
    }

    if (flags & PYCURL_MEMGROUP_SHARE) {
        /* Decrement refcount for share objects. */
        if (self->share != NULL) {
            CurlShareObject *share = self->share;
            share_unregister_easy(share, self);
            self->share = NULL;
            if (share->share_handle != NULL && handle != NULL) {
                curl_easy_setopt(handle, CURLOPT_SHARE, NULL);
            }
            Py_DECREF(share);
        }
    }

    if (flags & PYCURL_MEMGROUP_HTTPPOST) {
        /* Decrement refcounts for httppost object. */
        Py_CLEAR(self->httppost);
    }

#ifdef HAVE_CURL_MIME
    if (flags & PYCURL_MEMGROUP_MIMEPOST) {
        if (self->mimepost_obj != NULL && handle != NULL) {
            (void)curl_easy_setopt(handle, CURLOPT_MIMEPOST, NULL);
        }
        /* Decrement refcounts for mimepost object. */
        Py_CLEAR(self->mimepost_obj);
    }
#endif

    if (flags & PYCURL_MEMGROUP_CACERTS) {
        /* Decrement refcounts for ca certs related references. */
        Py_CLEAR(self->ca_certs_obj);
    }

    if (flags & PYCURL_MEMGROUP_SLIST) {
        /* Decrement refcounts for slist objects. */
        Py_CLEAR(self->httpheader);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
        Py_CLEAR(self->proxyheader);
#endif
        Py_CLEAR(self->http200aliases);
        Py_CLEAR(self->quote);
        Py_CLEAR(self->postquote);
        Py_CLEAR(self->prequote);
        Py_CLEAR(self->telnetoptions);
#ifdef HAVE_CURLOPT_RESOLVE
        Py_CLEAR(self->resolve);
#endif
#ifdef HAVE_CURL_7_20_0_OPTS
        Py_CLEAR(self->mail_rcpt);
#endif
#ifdef HAVE_CURLOPT_CONNECT_TO
        Py_CLEAR(self->connect_to);
#endif
    }
}


static void
util_easy_detach_from_multi(CurlObject *self, CURL *easy_handle)
{
    CurlMultiObject *multi;

    assert(self != NULL);

    multi = self->multi_stack;
    if (multi == NULL || multi->multi_handle == NULL || easy_handle == NULL) {
        return;
    }

    /* Allow threads because callbacks can be invoked */
    PYCURL_BEGIN_ALLOW_THREADS_EASY
    (void) curl_multi_remove_handle(multi->multi_handle, easy_handle);
    PYCURL_END_ALLOW_THREADS_EASY

    if (multi->easy_object_dict != NULL && PyDict_DelItem(multi->easy_object_dict, (PyObject *)self) < 0) {
        PyErr_Clear();
    }
}


static void
util_curl_close(CurlObject *self)
{
    CURL *handle;

    /* Zero handle and thread-state to disallow any operations to be run
     * from now on */
    assert(self != NULL);
    assert(PyObject_IsInstance((PyObject *) self, (PyObject *) p_Curl_Type) == 1);
    handle = self->handle;
    if (handle == NULL) {
        /* Some paranoia assertions just to make sure the object
         * deallocation problem is finally really fixed... */
        assert(self->state == NULL);
        assert(self->multi_stack == NULL);
        assert(self->multi_weakref == NULL);
        assert(self->share == NULL);
        return;             /* already closed */
    }
    self->state = NULL;

    /* Decref multi stuff which uses this handle */
    if (self->multi_stack != NULL) {
        util_easy_detach_from_multi(self, handle);
    }
    util_curl_xdecref(self, PYCURL_MEMGROUP_MULTI, handle);
    /* Decref share which uses this handle */
    util_curl_xdecref(self, PYCURL_MEMGROUP_SHARE, handle);

    /* Cleanup curl handle - must be done without the gil */
    PYCURL_BEGIN_ALLOW_THREADS
    curl_easy_cleanup(handle);
    PYCURL_END_ALLOW_THREADS
    self->handle = NULL;
    handle = NULL;

    /* Decref easy related objects */
    util_curl_xdecref(self, PYCURL_MEMGROUP_EASY, handle);
}


PYCURL_INTERNAL void
do_curl_dealloc(CurlObject *self)
{
    PyObject_GC_UnTrack(self);
    Py_TRASHCAN_BEGIN(self, do_curl_dealloc);

    Py_CLEAR(self->dict);
    util_curl_close(self);

    if (self->weakreflist != NULL) {
        PyObject_ClearWeakRefs((PyObject *) self);
    }

    Curl_Type.tp_free(self);
    Py_TRASHCAN_END;
}


static PyObject *
do_curl_close(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    if (check_curl_state(self, 2, "close") != 0) {
        return NULL;
    }
    util_curl_close(self);
    Py_RETURN_NONE;
}


static PyObject *do_curl_closed(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    if (self->handle == NULL) {
        Py_RETURN_TRUE;
    } else {
        Py_RETURN_FALSE;
    }
}


/* --------------- GC support --------------- */

/* Drop references that may have created reference cycles. */
PYCURL_INTERNAL int
do_curl_clear(CurlObject *self)
{
    assert(pycurl_get_thread_state(self) == NULL);
    util_curl_xdecref(self, PYCURL_MEMGROUP_ALL, self->handle);
    return 0;
}

/* Traverse all refcounted objects. */
PYCURL_INTERNAL int
do_curl_traverse(CurlObject *self, visitproc visit, void *arg)
{
    int err;
#undef VISIT
#define VISIT(v)    if ((v) != NULL && ((err = visit(v, arg)) != 0)) return err

    VISIT(self->dict);
    VISIT(self->multi_weakref);
    VISIT((PyObject *) self->share);

    VISIT(self->w_cb);
    VISIT(self->h_cb);
    VISIT(self->r_cb);
    VISIT(self->pro_cb);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0)
    VISIT(self->xferinfo_cb);
#endif
    VISIT(self->debug_cb);
    VISIT(self->ioctl_cb);
    VISIT(self->seek_cb);
    VISIT(self->opensocket_cb);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7)
    VISIT(self->closesocket_cb);
#endif
    VISIT(self->sockopt_cb);
    VISIT(self->ssh_key_cb);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 80, 0)
    VISIT(self->prereq_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 0)
    VISIT(self->fnmatch_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 59, 0)
    VISIT(self->resolver_start_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 64, 0)
    VISIT(self->trailer_cb);
#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 74, 0)
    VISIT(self->hstsread_cb);
    VISIT(self->hstswrite_cb);
#endif

    VISIT(self->readdata_fp);
    VISIT(self->writedata_fp);
    VISIT(self->writeheader_fp);

    VISIT(self->postfields_obj);

#ifdef HAVE_CURL_MIME
    VISIT(self->mimepost_obj);
#endif

    VISIT(self->ca_certs_obj);

    VISIT((PyObject *) self->httpheader);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
    VISIT((PyObject *) self->proxyheader);
#endif
    VISIT((PyObject *) self->http200aliases);
    VISIT((PyObject *) self->quote);
    VISIT((PyObject *) self->postquote);
    VISIT((PyObject *) self->prequote);
    VISIT((PyObject *) self->telnetoptions);
#ifdef HAVE_CURLOPT_RESOLVE
    VISIT((PyObject *) self->resolve);
#endif
#ifdef HAVE_CURL_7_20_0_OPTS
    VISIT((PyObject *) self->mail_rcpt);
#endif
#ifdef HAVE_CURLOPT_CONNECT_TO
    VISIT((PyObject *) self->connect_to);
#endif

    VISIT((PyObject *) self->httppost);

    return 0;
#undef VISIT
}


/* ------------------------ reset ------------------------ */

static PyObject*
do_curl_reset(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    int res;

    if (check_curl_state(self, 1 | 2, "reset") != 0) {
        return NULL;
    }

    curl_easy_reset(self->handle);

    /* Decref easy interface related objects */
    util_curl_xdecref(self, PYCURL_MEMGROUP_EASY, self->handle);

    res = util_curl_init(self);
    if (res < 0) {
        /* util_curl_init failed to re-set the default options; the libcurl
         * handle is in an inconsistent state. Close it so subsequent calls
         * fail predictably instead of crashing on NULL derefs inside libcurl. */
        util_curl_close(self);
        PyErr_SetString(ErrorObject, "resetting curl failed");
        return NULL;
    }

    Py_RETURN_NONE;
}


static PyObject *do_curl_getstate(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    PyErr_SetString(PyExc_TypeError, "Curl objects do not support serialization");
    return NULL;
}


static PyObject *do_curl_setstate(CurlObject *self, PyObject *args)
{
    PyErr_SetString(PyExc_TypeError, "Curl objects do not support deserialization");
    return NULL;
}


static PyObject *do_curl_enter(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    Py_INCREF(self);
    return (PyObject *)self;
}


static PyObject *
do_curl_multi(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    PyObject *obj = NULL;;

    assert_curl_state(self);

    if (self->multi_weakref == NULL) {
        Py_RETURN_NONE;
    }

#if PY_VERSION_HEX >= 0x030D0000  /* Python 3.13+ */
    {
        int rc = PyWeakref_GetRef(self->multi_weakref, &obj);
        if (rc < 0) {
            return NULL;
        }
        if (rc == 0 || obj == NULL) {
            Py_RETURN_NONE;
        }
    }
#else
    obj = PyWeakref_GetObject(self->multi_weakref);
    if (obj == Py_None) {
        Py_RETURN_NONE;
    }
    Py_INCREF(obj);
#endif

    if (!PyObject_IsInstance(obj, (PyObject *)p_CurlMulti_Type)) {
        Py_DECREF(obj);
        PyErr_SetString(PyExc_TypeError, "multi object is not a CurlMulti");
        return NULL;
    }

    return obj;
}


static PyObject *
do_curl_share(CurlObject *self, PyObject *Py_UNUSED(ignored))
{
    assert_curl_state(self);

    if (self->share == NULL) {
        Py_RETURN_NONE;
    }

    if (!PyObject_IsInstance((PyObject *)self->share, (PyObject *)p_CurlShare_Type)) {
        PyErr_SetString(PyExc_TypeError, "share object is not a CurlShare");
        return NULL;
    }

    Py_INCREF(self->share);
    return (PyObject *)self->share;
}


/*************************************************************************
// type definitions
**************************************************************************/

/* --------------- methods --------------- */

PYCURL_INTERNAL PyMethodDef curlobject_methods[] = {
    {"close", (PyCFunction)do_curl_close, METH_NOARGS, curl_close_doc},
    {"closed", (PyCFunction)do_curl_closed, METH_NOARGS, curl_closed_doc},
    {"duphandle", (PyCFunction)do_curl_duphandle, METH_NOARGS, curl_duphandle_doc},
    {"errstr", (PyCFunction)do_curl_errstr, METH_NOARGS, curl_errstr_doc},
    {"errstr_raw", (PyCFunction)do_curl_errstr_raw, METH_NOARGS, curl_errstr_raw_doc},
    {"getinfo", (PyCFunction)do_curl_getinfo, METH_VARARGS, curl_getinfo_doc},
    {"getinfo_raw", (PyCFunction)do_curl_getinfo_raw, METH_VARARGS, curl_getinfo_raw_doc},
    {"multi", (PyCFunction)do_curl_multi, METH_NOARGS, curl_multi_doc},
    {"pause", (PyCFunction)do_curl_pause, METH_VARARGS, curl_pause_doc},
    {"perform", (PyCFunction)do_curl_perform, METH_NOARGS, curl_perform_doc},
    {"perform_rb", (PyCFunction)do_curl_perform_rb, METH_NOARGS, curl_perform_rb_doc},
    {"perform_rs", (PyCFunction)do_curl_perform_rs, METH_NOARGS, curl_perform_rs_doc},
    {"recv", (PyCFunction)do_curl_recv, METH_VARARGS, curl_recv_doc},
    {"recv_into", (PyCFunction)do_curl_recv_into, METH_VARARGS | METH_KEYWORDS, curl_recv_into_doc},
    {"reset", (PyCFunction)do_curl_reset, METH_NOARGS, curl_reset_doc},
    {"send", (PyCFunction)do_curl_send, METH_VARARGS, curl_send_doc},
    {"setopt", (PyCFunction)do_curl_setopt, METH_VARARGS, curl_setopt_doc},
    {"setopt_string", (PyCFunction)do_curl_setopt_string, METH_VARARGS, curl_setopt_string_doc},
    {"share", (PyCFunction)do_curl_share, METH_NOARGS, curl_share_doc},
    {"unpause", (PyCFunction)do_curl_unpause, METH_NOARGS, curl_unpause_doc},
    {"unsetopt", (PyCFunction)do_curl_unsetopt, METH_VARARGS, curl_unsetopt_doc},
#if defined(HAVE_CURL_OPENSSL)
    {"set_ca_certs", (PyCFunction)do_curl_set_ca_certs, METH_VARARGS, curl_set_ca_certs_doc},
#endif
#ifdef HAVE_CURL_WEBSOCKETS
    {"ws_send", (PyCFunction)do_curl_ws_send, METH_VARARGS | METH_KEYWORDS, curl_ws_send_doc},
    {"ws_recv", (PyCFunction)do_curl_ws_recv, METH_VARARGS, curl_ws_recv_doc},
    {"ws_recv_into", (PyCFunction)do_curl_ws_recv_into, METH_VARARGS | METH_KEYWORDS, curl_ws_recv_into_doc},
    {"ws_meta", (PyCFunction)do_curl_ws_meta, METH_NOARGS, curl_ws_meta_doc},
    {"ws_close", (PyCFunction)do_curl_ws_close, METH_VARARGS | METH_KEYWORDS, curl_ws_close_doc},
#endif
    {"__getstate__", (PyCFunction)do_curl_getstate, METH_NOARGS, NULL},
    {"__setstate__", (PyCFunction)do_curl_setstate, METH_VARARGS, NULL},
    {"__enter__", (PyCFunction)do_curl_enter, METH_NOARGS, NULL},
    {"__exit__", (PyCFunction)do_curl_close, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}
};


/* --------------- setattr/getattr --------------- */


PYCURL_INTERNAL PyObject *
do_curl_getattro(PyObject *o, PyObject *n)
{
    PyObject *v = PyObject_GenericGetAttr(o, n);
    if( !v && PyErr_ExceptionMatches(PyExc_AttributeError) )
    {
        PyErr_Clear();
        v = my_getattro(o, n, ((CurlObject *)o)->dict,
                        curlobject_constants, curlobject_methods);
    }
    return v;
}

PYCURL_INTERNAL int
do_curl_setattro(PyObject *o, PyObject *name, PyObject *v)
{
    assert_curl_state((CurlObject *)o);
    return my_setattro(&((CurlObject *)o)->dict, name, v);
}

PYCURL_INTERNAL PyTypeObject Curl_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "pycurl.Curl",              /* tp_name */
    sizeof(CurlObject),         /* tp_basicsize */
    0,                          /* tp_itemsize */
    (destructor)do_curl_dealloc, /* tp_dealloc */
    0,                          /* tp_print */
    0,                          /* tp_getattr */
    0,                          /* tp_setattr */
    0,                          /* tp_reserved */
    0,                          /* tp_repr */
    0,                          /* tp_as_number */
    0,                          /* tp_as_sequence */
    0,                          /* tp_as_mapping */
    0,                          /* tp_hash  */
    0,                          /* tp_call */
    0,                          /* tp_str */
    (getattrofunc)do_curl_getattro, /* tp_getattro */
    (setattrofunc)do_curl_setattro, /* tp_setattro */
    0,                          /* tp_as_buffer */
    PYCURL_TYPE_FLAGS,          /* tp_flags */
    curl_doc,                   /* tp_doc */
    (traverseproc)do_curl_traverse, /* tp_traverse */
    (inquiry)do_curl_clear,     /* tp_clear */
    0,                          /* tp_richcompare */
    offsetof(CurlObject, weakreflist), /* tp_weaklistoffset */
    0,                          /* tp_iter */
    0,                          /* tp_iternext */
    curlobject_methods,         /* tp_methods */
    0,                          /* tp_members */
    0,                          /* tp_getset */
    0,                          /* tp_base */
    0,                          /* tp_dict */
    0,                          /* tp_descr_get */
    0,                          /* tp_descr_set */
    0,                          /* tp_dictoffset */
    0,                          /* tp_init */
    PyType_GenericAlloc,        /* tp_alloc */
    (newfunc)do_curl_new,       /* tp_new */
    PyObject_GC_Del,            /* tp_free */
};

/* vi:ts=4:et:nowrap
 */
