summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleg Pudeyev <p@users.noreply.github.com>2022-01-11 14:31:35 -0500
committerGitHub <noreply@github.com>2022-01-11 14:31:35 -0500
commit3e792014dcd932e763838ed4c84aa7737d30edcd (patch)
tree84fce20b3148511f085bd02a116726a3713dac3b
parent54ff45d0c4a254f5d2d4e392d40e864bb90e40d3 (diff)
parentad319e876faa2fc90a36392e75e0ff8f1e09aef1 (diff)
downloadpycurl-3e792014dcd932e763838ed4c84aa7737d30edcd.tar.gz
Merge pull request #714 from fsbs/add-easy-duphandle
easy.duphandle()
-rw-r--r--doc/docstrings/curl_duphandle.rst23
-rw-r--r--src/easy.c422
-rw-r--r--src/easyopt.c81
-rw-r--r--src/module.c13
-rw-r--r--src/pycurl.h53
-rw-r--r--tests/app.py3
-rw-r--r--tests/duphandle_test.py144
7 files changed, 635 insertions, 104 deletions
diff --git a/doc/docstrings/curl_duphandle.rst b/doc/docstrings/curl_duphandle.rst
new file mode 100644
index 0000000..ca6e7a8
--- /dev/null
+++ b/doc/docstrings/curl_duphandle.rst
@@ -0,0 +1,23 @@
+duphandle() -> Curl
+
+Clone a curl handle. This function will return a new curl handle,
+a duplicate, using all the options previously set in the input curl handle.
+Both handles can subsequently be used independently.
+
+The new handle will not inherit any state information, no connections,
+no SSL sessions and no cookies. It also will not inherit any share object
+states or options (it will be made as if SHARE was unset).
+
+Corresponds to `curl_easy_duphandle`_ in libcurl.
+
+Example usage::
+
+ import pycurl
+ curl = pycurl.Curl()
+ curl.setopt(pycurl.URL, "https://python.org")
+ dup = curl.duphandle()
+ curl.perform()
+ dup.perform()
+
+.. _curl_easy_duphandle:
+ https://curl.se/libcurl/c/curl_easy_duphandle.html
diff --git a/src/easy.c b/src/easy.c
index 893e56b..8bbace3 100644
--- a/src/easy.c
+++ b/src/easy.c
@@ -1,6 +1,179 @@
#include "pycurl.h"
#include "docstrings.h"
+
+/*************************************************************************
+// CurlSlistObject
+**************************************************************************/
+
+PYCURL_INTERNAL void
+util_curlslist_update(CurlSlistObject **old, struct curl_slist *slist)
+{
+ /* Decref previous object */
+ Py_XDECREF(*old);
+ /* Create a new object */
+ *old = PyObject_New(CurlSlistObject, p_CurlSlist_Type);
+ assert(*old != NULL);
+ /* Store curl_slist into the new object */
+ (*old)->slist = slist;
+}
+
+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 = {
+#if PY_MAJOR_VERSION >= 3
+ PyVarObject_HEAD_INIT(NULL, 0)
+#else
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+#endif
+ "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 */
+#if PY_MAJOR_VERSION >= 3
+ 0, /* tp_del */
+ 0, /* tp_version_tag */
+ 0, /* tp_finalize */
+ 0, /* tp_vectorcall */
+#endif
+};
+
+
+/*************************************************************************
+// CurlHttppostObject
+**************************************************************************/
+
+PYCURL_INTERNAL void
+util_curlhttppost_update(CurlObject *obj, struct curl_httppost *httppost, PyObject *reflist)
+{
+ /* Decref previous object */
+ Py_XDECREF(obj->httppost);
+ /* Create a new object */
+ obj->httppost = PyObject_New(CurlHttppostObject, p_CurlHttppost_Type);
+ assert(obj->httppost != NULL);
+ /* Store curl_httppost and reflist into the new object */
+ obj->httppost->httppost = httppost;
+ obj->httppost->reflist = reflist;
+}
+
+PYCURL_INTERNAL void
+do_curlhttppost_dealloc(CurlHttppostObject *self) {
+ if (self->httppost != NULL) {
+ curl_formfree(self->httppost);
+ self->httppost = NULL;
+ }
+ Py_CLEAR(self->reflist);
+ CurlHttppost_Type.tp_free(self);
+}
+
+PYCURL_INTERNAL PyTypeObject CurlHttppost_Type = {
+#if PY_MAJOR_VERSION >= 3
+ PyVarObject_HEAD_INIT(NULL, 0)
+#else
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+#endif
+ "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 */
+#if PY_MAJOR_VERSION >= 3
+ 0, /* tp_del */
+ 0, /* tp_version_tag */
+ 0, /* tp_finalize */
+ 0, /* tp_vectorcall */
+#endif
+};
+
+
/*************************************************************************
// static utility functions
**************************************************************************/
@@ -123,6 +296,156 @@ error:
return NULL;
}
+/* duphandle */
+PYCURL_INTERNAL CurlObject *
+do_curl_duphandle(CurlObject *self)
+{
+ 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 successfuly setopt'ed with self->handle. */
+
+ /* Assign and incref python callback and update data pointers */
+ if (self->w_cb != NULL) {
+ dup->w_cb = my_Py_NewRef(self->w_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_WRITEDATA, dup);
+ }
+ if (self->h_cb != NULL) {
+ dup->h_cb = my_Py_NewRef(self->h_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_WRITEHEADER, dup);
+ }
+ if (self->r_cb != NULL) {
+ dup->r_cb = my_Py_NewRef(self->r_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_READDATA, dup);
+ }
+ if (self->pro_cb != NULL) {
+ dup->pro_cb = my_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 = my_Py_NewRef(self->xferinfo_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_XFERINFODATA, dup);
+ }
+#endif
+ if (self->debug_cb != NULL) {
+ dup->debug_cb = my_Py_NewRef(self->debug_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_DEBUGDATA, dup);
+ }
+ if (self->ioctl_cb != NULL) {
+ dup->ioctl_cb = my_Py_NewRef(self->ioctl_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_IOCTLDATA, dup);
+ }
+ if (self->opensocket_cb != NULL) {
+ dup->opensocket_cb = my_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 = my_Py_NewRef(self->closesocket_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_CLOSESOCKETDATA, dup);
+ }
+#endif
+ if (self->sockopt_cb != NULL) {
+ dup->sockopt_cb = my_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 = my_Py_NewRef(self->ssh_key_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_SSH_KEYDATA, dup);
+ }
+#endif
+ if (self->seek_cb != NULL) {
+ dup->seek_cb = my_Py_NewRef(self->seek_cb);
+ curl_easy_setopt(dup->handle, CURLOPT_SEEKDATA, dup);
+ }
+
+ /* Assign and incref python file objects */
+ dup->readdata_fp = my_Py_XNewRef(self->readdata_fp);
+ dup->writedata_fp = my_Py_XNewRef(self->writedata_fp);
+ dup->writeheader_fp = my_Py_XNewRef(self->writeheader_fp);
+
+ /* Assign and incref postfields object */
+ dup->postfields_obj = my_Py_XNewRef(self->postfields_obj);
+
+ /* Assign and incref ca certs related references */
+ dup->ca_certs_obj = my_Py_XNewRef(self->ca_certs_obj);
+
+ /* Assign and incref every curl_slist allocated by setopt */
+ dup->httpheader = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->httpheader);
+#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
+ dup->proxyheader = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->proxyheader);
+#endif
+ dup->http200aliases = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->http200aliases);
+ dup->quote = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->quote);
+ dup->postquote = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->postquote);
+ dup->prequote = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->prequote);
+ dup->telnetoptions = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->telnetoptions);
+#ifdef HAVE_CURLOPT_RESOLVE
+ dup->resolve = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->resolve);
+#endif
+#ifdef HAVE_CURL_7_20_0_OPTS
+ dup->mail_rcpt = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->mail_rcpt);
+#endif
+#ifdef HAVE_CURLOPT_CONNECT_TO
+ dup->connect_to = (CurlSlistObject *)my_Py_XNewRef((PyObject *)self->connect_to);
+#endif
+
+ /* Assign and incref httppost */
+ dup->httppost = (CurlHttppostObject *)my_Py_XNewRef((PyObject *)self->httppost);
+
+ /* 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
@@ -197,14 +520,36 @@ util_curl_xdecref(CurlObject *self, int flags, CURL *handle)
}
if (flags & PYCURL_MEMGROUP_HTTPPOST) {
- /* Decrement refcounts for httppost related references. */
- Py_CLEAR(self->httppost_ref_list);
+ /* Decrement refcounts for httppost object. */
+ Py_CLEAR(self->httppost);
}
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
+ }
}
@@ -250,32 +595,6 @@ util_curl_close(CurlObject *self)
if (self->weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject *) self);
}
-
- /* Free all variables allocated by setopt */
-#undef SFREE
-#define SFREE(v) if ((v) != NULL) (curl_formfree(v), (v) = NULL)
- SFREE(self->httppost);
-#undef SFREE
-#define SFREE(v) if ((v) != NULL) (curl_slist_free_all(v), (v) = NULL)
- SFREE(self->httpheader);
-#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
- SFREE(self->proxyheader);
-#endif
- SFREE(self->http200aliases);
- SFREE(self->quote);
- SFREE(self->postquote);
- SFREE(self->prequote);
- SFREE(self->telnetoptions);
-#ifdef HAVE_CURLOPT_RESOLVE
- SFREE(self->resolve);
-#endif
-#ifdef HAVE_CURL_7_20_0_OPTS
- SFREE(self->mail_rcpt);
-#endif
-#ifdef HAVE_CURLOPT_CONNECT_TO
- SFREE(self->connect_to);
-#endif
-#undef SFREE
}
@@ -354,6 +673,27 @@ do_curl_traverse(CurlObject *self, visitproc visit, void *arg)
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
}
@@ -371,31 +711,6 @@ do_curl_reset(CurlObject *self)
/* Decref easy interface related objects */
util_curl_xdecref(self, PYCURL_MEMGROUP_EASY, self->handle);
- /* Free all variables allocated by setopt */
-#undef SFREE
-#define SFREE(v) if ((v) != NULL) (curl_formfree(v), (v) = NULL)
- SFREE(self->httppost);
-#undef SFREE
-#define SFREE(v) if ((v) != NULL) (curl_slist_free_all(v), (v) = NULL)
- SFREE(self->httpheader);
-#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
- SFREE(self->proxyheader);
-#endif
- SFREE(self->http200aliases);
- SFREE(self->quote);
- SFREE(self->postquote);
- SFREE(self->prequote);
- SFREE(self->telnetoptions);
-#ifdef HAVE_CURLOPT_RESOLVE
- SFREE(self->resolve);
-#endif
-#ifdef HAVE_CURL_7_20_0_OPTS
- SFREE(self->mail_rcpt);
-#endif
-#ifdef HAVE_CURLOPT_CONNECT_TO
- SFREE(self->connect_to);
-#endif
-#undef SFREE
res = util_curl_init(self);
if (res < 0) {
Py_DECREF(self); /* this also closes self->handle */
@@ -441,6 +756,7 @@ PYCURL_INTERNAL PyMethodDef curlobject_methods[] = {
{"setopt_string", (PyCFunction)do_curl_setopt_string, METH_VARARGS, curl_setopt_string_doc},
{"unsetopt", (PyCFunction)do_curl_unsetopt, METH_VARARGS, curl_unsetopt_doc},
{"reset", (PyCFunction)do_curl_reset, METH_NOARGS, curl_reset_doc},
+ {"duphandle", (PyCFunction)do_curl_duphandle, METH_NOARGS, curl_duphandle_doc},
#if defined(HAVE_CURL_OPENSSL)
{"set_ca_certs", (PyCFunction)do_curl_set_ca_certs, METH_VARARGS, curl_set_ca_certs_doc},
#endif
diff --git a/src/easyopt.c b/src/easyopt.c
index ba4108c..e3da86b 100644
--- a/src/easyopt.c
+++ b/src/easyopt.c
@@ -46,6 +46,12 @@ util_curl_unsetopt(CurlObject *self, int option)
#define SETOPT2(o,x) \
if ((res = curl_easy_setopt(self->handle, (o), (x))) != CURLE_OK) goto error
#define SETOPT(x) SETOPT2((CURLoption)option, (x))
+#define CLEAR_OBJECT(object_option, object_field) \
+ case object_option: \
+ if ((res = curl_easy_setopt(self->handle, object_option, NULL)) != CURLE_OK) \
+ goto error; \
+ Py_CLEAR(object_field); \
+ break
#define CLEAR_CALLBACK(callback_option, data_option, callback_field) \
case callback_option: \
if ((res = curl_easy_setopt(self->handle, callback_option, NULL)) != CURLE_OK) \
@@ -65,13 +71,6 @@ util_curl_unsetopt(CurlObject *self, int option)
Py_XDECREF(self->share);
self->share = NULL;
break;
- case CURLOPT_HTTPPOST:
- SETOPT((void *) 0);
- curl_formfree(self->httppost);
- util_curl_xdecref(self, PYCURL_MEMGROUP_HTTPPOST, self->handle);
- self->httppost = NULL;
- /* FIXME: what about data->set.httpreq ?? */
- break;
case CURLOPT_INFILESIZE:
SETOPT((long) -1);
break;
@@ -108,10 +107,6 @@ util_curl_unsetopt(CurlObject *self, int option)
case CURLOPT_SERVICE_NAME:
case CURLOPT_PROXY_SERVICE_NAME:
#endif
- case CURLOPT_HTTPHEADER:
-#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
- case CURLOPT_PROXYHEADER:
-#endif
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 52, 0)
case CURLOPT_PROXY_CAPATH:
case CURLOPT_PROXY_CAINFO:
@@ -130,6 +125,27 @@ util_curl_unsetopt(CurlObject *self, int option)
break;
#endif
+ CLEAR_OBJECT(CURLOPT_HTTPHEADER, self->httpheader);
+#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
+ CLEAR_OBJECT(CURLOPT_PROXYHEADER, self->proxyheader);
+#endif
+ CLEAR_OBJECT(CURLOPT_HTTP200ALIASES, self->http200aliases);
+ CLEAR_OBJECT(CURLOPT_QUOTE, self->quote);
+ CLEAR_OBJECT(CURLOPT_POSTQUOTE, self->postquote);
+ CLEAR_OBJECT(CURLOPT_PREQUOTE, self->prequote);
+ CLEAR_OBJECT(CURLOPT_TELNETOPTIONS, self->telnetoptions);
+#ifdef HAVE_CURLOPT_RESOLVE
+ CLEAR_OBJECT(CURLOPT_RESOLVE, self->resolve);
+#endif
+#ifdef HAVE_CURL_7_20_0_OPTS
+ CLEAR_OBJECT(CURLOPT_MAIL_RCPT, self->mail_rcpt);
+#endif
+#ifdef HAVE_CURLOPT_CONNECT_TO
+ CLEAR_OBJECT(CURLOPT_CONNECT_TO, self->connect_to);
+#endif
+ /* FIXME: what about data->set.httpreq ?? */
+ CLEAR_OBJECT(CURLOPT_HTTPPOST, self->httppost);
+
CLEAR_CALLBACK(CURLOPT_OPENSOCKETFUNCTION, CURLOPT_OPENSOCKETDATA, self->opensocket_cb);
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7)
CLEAR_CALLBACK(CURLOPT_CLOSESOCKETFUNCTION, CURLOPT_CLOSESOCKETDATA, self->closesocket_cb);
@@ -690,16 +706,9 @@ do_curl_setopt_httppost(CurlObject *self, int option, int which, PyObject *obj)
CURLERROR_SET_RETVAL();
goto error;
}
- /* Finally, free previously allocated httppost, ZAP any
- * buffer references, and update */
- curl_formfree(self->httppost);
- util_curl_xdecref(self, PYCURL_MEMGROUP_HTTPPOST, self->handle);
- self->httppost = post;
-
- /* The previous list of INCed references was ZAPed above; save
- * the new one so that we can clean it up on the next
- * self->httppost free. */
- self->httppost_ref_list = ref_params;
+ /* Finally, decref previous httppost object and replace it with a
+ * new one. */
+ util_curlhttppost_update(self, post, ref_params);
Py_RETURN_NONE;
@@ -713,48 +722,48 @@ error:
static PyObject *
do_curl_setopt_list(CurlObject *self, int option, int which, PyObject *obj)
{
- struct curl_slist **old_slist = NULL;
+ CurlSlistObject **old_slist_obj = NULL;
struct curl_slist *slist = NULL;
Py_ssize_t len;
int res;
switch (option) {
case CURLOPT_HTTP200ALIASES:
- old_slist = &self->http200aliases;
+ old_slist_obj = &self->http200aliases;
break;
case CURLOPT_HTTPHEADER:
- old_slist = &self->httpheader;
+ old_slist_obj = &self->httpheader;
break;
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
case CURLOPT_PROXYHEADER:
- old_slist = &self->proxyheader;
+ old_slist_obj = &self->proxyheader;
break;
#endif
case CURLOPT_POSTQUOTE:
- old_slist = &self->postquote;
+ old_slist_obj = &self->postquote;
break;
case CURLOPT_PREQUOTE:
- old_slist = &self->prequote;
+ old_slist_obj = &self->prequote;
break;
case CURLOPT_QUOTE:
- old_slist = &self->quote;
+ old_slist_obj = &self->quote;
break;
case CURLOPT_TELNETOPTIONS:
- old_slist = &self->telnetoptions;
+ old_slist_obj = &self->telnetoptions;
break;
#ifdef HAVE_CURLOPT_RESOLVE
case CURLOPT_RESOLVE:
- old_slist = &self->resolve;
+ old_slist_obj = &self->resolve;
break;
#endif
#ifdef HAVE_CURL_7_20_0_OPTS
case CURLOPT_MAIL_RCPT:
- old_slist = &self->mail_rcpt;
+ old_slist_obj = &self->mail_rcpt;
break;
#endif
#ifdef HAVE_CURLOPT_CONNECT_TO
case CURLOPT_CONNECT_TO:
- old_slist = &self->connect_to;
+ old_slist_obj = &self->connect_to;
break;
#endif
default:
@@ -768,7 +777,7 @@ do_curl_setopt_list(CurlObject *self, int option, int which, PyObject *obj)
Py_RETURN_NONE;
/* Just to be sure we do not bug off here */
- assert(old_slist != NULL && slist == NULL);
+ assert(old_slist_obj != NULL && slist == NULL);
/* Handle regular list operations on the other options */
slist = pycurl_list_or_tuple_to_slist(which, obj, len);
@@ -781,9 +790,9 @@ do_curl_setopt_list(CurlObject *self, int option, int which, PyObject *obj)
curl_slist_free_all(slist);
CURLERROR_RETVAL();
}
- /* Finally, free previously allocated list and update */
- curl_slist_free_all(*old_slist);
- *old_slist = slist;
+ /* Finally, decref previous slist object and replace it with a
+ * new one. */
+ util_curlslist_update(old_slist_obj, slist);
Py_RETURN_NONE;
}
diff --git a/src/module.c b/src/module.c
index b46a4fe..f75b84f 100644
--- a/src/module.c
+++ b/src/module.c
@@ -28,6 +28,8 @@ PYCURL_INTERNAL char *g_pycurl_useragent = NULL;
/* Type objects */
PYCURL_INTERNAL PyObject *ErrorObject = NULL;
PYCURL_INTERNAL PyTypeObject *p_Curl_Type = NULL;
+PYCURL_INTERNAL PyTypeObject *p_CurlSlist_Type = NULL;
+PYCURL_INTERNAL PyTypeObject *p_CurlHttppost_Type = NULL;
PYCURL_INTERNAL PyTypeObject *p_CurlMulti_Type = NULL;
PYCURL_INTERNAL PyTypeObject *p_CurlShare_Type = NULL;
#ifdef HAVE_CURL_7_19_6_OPTS
@@ -419,9 +421,13 @@ initpycurl(void)
/* Initialize the type of the new type objects here; doing it here
* is required for portability to Windows without requiring C++. */
p_Curl_Type = &Curl_Type;
+ p_CurlSlist_Type = &CurlSlist_Type;
+ p_CurlHttppost_Type = &CurlHttppost_Type;
p_CurlMulti_Type = &CurlMulti_Type;
p_CurlShare_Type = &CurlShare_Type;
Py_SET_TYPE(&Curl_Type, &PyType_Type);
+ Py_SET_TYPE(&CurlSlist_Type, &PyType_Type);
+ Py_SET_TYPE(&CurlHttppost_Type, &PyType_Type);
Py_SET_TYPE(&CurlMulti_Type, &PyType_Type);
Py_SET_TYPE(&CurlShare_Type, &PyType_Type);
@@ -429,12 +435,19 @@ initpycurl(void)
if (PyType_Ready(&Curl_Type) < 0)
goto error;
+ if (PyType_Ready(&CurlSlist_Type) < 0)
+ goto error;
+
+ if (PyType_Ready(&CurlHttppost_Type) < 0)
+ goto error;
+
if (PyType_Ready(&CurlMulti_Type) < 0)
goto error;
if (PyType_Ready(&CurlShare_Type) < 0)
goto error;
+
#if PY_MAJOR_VERSION >= 3
m = PyModule_Create(&curlmodule);
if (m == NULL)
diff --git a/src/pycurl.h b/src/pycurl.h
index ae649b8..6f70a9a 100644
--- a/src/pycurl.h
+++ b/src/pycurl.h
@@ -334,6 +334,10 @@ PyText_Check(PyObject *o);
PYCURL_INTERNAL PyObject *
PyText_FromString_Ignore(const char *string);
+/* Py_NewRef and Py_XNewRef - not part of Python's C API before 3.10 */
+static inline PyObject* my_Py_NewRef(PyObject *obj) { Py_INCREF(obj); return obj; }
+static inline PyObject* my_Py_XNewRef(PyObject *obj) { Py_XINCREF(obj); return obj; }
+
struct CurlObject;
PYCURL_INTERNAL void
@@ -387,16 +391,30 @@ create_and_set_error_object(struct CurlObject *self, int code);
#define PYCURL_MEMGROUP_POSTFIELDS 64
/* CA certs object */
#define PYCURL_MEMGROUP_CACERTS 128
+/* Curl slist objects */
+#define PYCURL_MEMGROUP_SLIST 256
#define PYCURL_MEMGROUP_EASY \
(PYCURL_MEMGROUP_CALLBACK | PYCURL_MEMGROUP_FILE | \
PYCURL_MEMGROUP_HTTPPOST | PYCURL_MEMGROUP_POSTFIELDS | \
- PYCURL_MEMGROUP_CACERTS)
+ PYCURL_MEMGROUP_CACERTS | PYCURL_MEMGROUP_SLIST)
#define PYCURL_MEMGROUP_ALL \
(PYCURL_MEMGROUP_ATTRDICT | PYCURL_MEMGROUP_EASY | \
PYCURL_MEMGROUP_MULTI | PYCURL_MEMGROUP_SHARE)
+typedef struct CurlSlistObject {
+ PyObject_HEAD
+ struct curl_slist *slist;
+} CurlSlistObject;
+
+typedef struct CurlHttppostObject {
+ PyObject_HEAD
+ struct curl_httppost *httppost;
+ /* List of INC'ed references associated with httppost. */
+ PyObject *reflist;
+} CurlHttppostObject;
+
typedef struct CurlObject {
PyObject_HEAD
PyObject *dict; /* Python attributes dictionary */
@@ -408,26 +426,24 @@ typedef struct CurlObject {
#endif
struct CurlMultiObject *multi_stack;
struct CurlShareObject *share;
- struct curl_httppost *httppost;
- /* List of INC'ed references associated with httppost. */
- PyObject *httppost_ref_list;
- struct curl_slist *httpheader;
+ struct CurlHttppostObject *httppost;
+ struct CurlSlistObject *httpheader;
#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0)
- struct curl_slist *proxyheader;
+ struct CurlSlistObject *proxyheader;
#endif
- struct curl_slist *http200aliases;
- struct curl_slist *quote;
- struct curl_slist *postquote;
- struct curl_slist *prequote;
- struct curl_slist *telnetoptions;
+ struct CurlSlistObject *http200aliases;
+ struct CurlSlistObject *quote;
+ struct CurlSlistObject *postquote;
+ struct CurlSlistObject *prequote;
+ struct CurlSlistObject *telnetoptions;
#ifdef HAVE_CURLOPT_RESOLVE
- struct curl_slist *resolve;
+ struct CurlSlistObject *resolve;
#endif
#ifdef HAVE_CURL_7_20_0_OPTS
- struct curl_slist *mail_rcpt;
+ struct CurlSlistObject *mail_rcpt;
#endif
#ifdef HAVE_CURLOPT_CONNECT_TO
- struct curl_slist *connect_to;
+ struct CurlSlistObject *connect_to;
#endif
/* callbacks */
PyObject *w_cb;
@@ -574,6 +590,11 @@ util_curl_xdecref(CurlObject *self, int flags, CURL *handle);
PYCURL_INTERNAL PyObject *
do_curl_setopt_filelike(CurlObject *self, int option, PyObject *obj);
+PYCURL_INTERNAL void
+util_curlslist_update(CurlSlistObject **old, struct curl_slist *slist);
+PYCURL_INTERNAL void
+util_curlhttppost_update(CurlObject *obj, struct curl_httppost *httppost, PyObject *reflist);
+
PYCURL_INTERNAL PyObject *
do_curl_getinfo_raw(CurlObject *self, PyObject *args);
#if PY_MAJOR_VERSION >= 3
@@ -635,11 +656,15 @@ ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *ptr);
#if !defined(PYCURL_SINGLE_FILE)
/* Type objects */
extern PyTypeObject Curl_Type;
+extern PyTypeObject CurlSlist_Type;
+extern PyTypeObject CurlHttppost_Type;
extern PyTypeObject CurlMulti_Type;
extern PyTypeObject CurlShare_Type;
extern PyObject *ErrorObject;
extern PyTypeObject *p_Curl_Type;
+extern PyTypeObject *p_CurlSlist_Type;
+extern PyTypeObject *p_CurlHttppost_Type;
extern PyTypeObject *p_CurlMulti_Type;
extern PyTypeObject *p_CurlShare_Type;
extern PyObject *khkey_type;
diff --git a/tests/app.py b/tests/app.py
index cf3fe75..62ff574 100644
--- a/tests/app.py
+++ b/tests/app.py
@@ -30,6 +30,7 @@ def forbidden():
def not_found():
return bottle.HTTPResponse('not found', 404)
+@app.route('/postfields', method='get')
@app.route('/postfields', method='post')
def postfields():
return json.dumps(dict(bottle.request.forms))
@@ -88,7 +89,7 @@ def header():
# Thanks to bdarnell for the idea: https://github.com/pycurl/pycurl/issues/124
@app.route('/header_utf8')
def header_utf8():
- header_value = bottle.request.headers[bottle.request.query['h']]
+ header_value = bottle.request.headers.get(bottle.request.query['h'], '' if py3 else b'')
if py3:
# header_value is a string, headers are decoded in latin1
header_value = header_value.encode('latin1').decode('utf8')
diff --git a/tests/duphandle_test.py b/tests/duphandle_test.py
new file mode 100644
index 0000000..fa30e80
--- /dev/null
+++ b/tests/duphandle_test.py
@@ -0,0 +1,144 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+from . import localhost
+import pycurl
+import unittest
+import gc
+import weakref
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+from . import appmanager
+from . import util
+
+setup_module, teardown_module = appmanager.setup(('app', 8380))
+
+class DuphandleTest(unittest.TestCase):
+ def setUp(self):
+ self.orig = util.DefaultCurl()
+
+ def test_duphandle_attribute_dict(self):
+ self.orig.orig_attr = 'orig-value'
+ # attribute dict should be copied - the *object*, not the reference
+ dup = self.orig.duphandle()
+ assert dup.orig_attr == 'orig-value'
+ # cloned dict should be a separate object
+ dup.dup_attr = 'dup-value'
+ try:
+ self.orig.dup_attr == 'does not exist'
+ except AttributeError as error:
+ assert 'trying to obtain a non-existing attribute: dup_attr' in str(error.args)
+ else:
+ self.fail('should have raised AttributeError')
+ # dealloc self.orig - original dict is freed from memory
+ self.orig.close()
+ del self.orig
+ # cloned dict should still exist
+ assert dup.orig_attr == 'orig-value'
+ assert dup.dup_attr == 'dup-value'
+ dup.close()
+
+ def slist_check(self, handle, value, persistance=True):
+ body = util.BytesIO()
+ handle.setopt(pycurl.WRITEFUNCTION, body.write)
+ handle.setopt(pycurl.URL, 'http://%s:8380/header_utf8?h=x-test-header' % localhost)
+ handle.perform()
+ result = body.getvalue().decode('utf-8')
+ assert (result == value) == persistance
+
+ def slist_test(self, clear_func, *args):
+ # new slist object is created with ref count = 1
+ self.orig.setopt(pycurl.HTTPHEADER, ['x-test-header: orig-slist'])
+ # ref is copied and object incref'ed
+ dup1 = self.orig.duphandle()
+ # slist object is decref'ed and ref set to null
+ clear_func(*args)
+ # null ref is copied - no effect
+ dup2 = self.orig.duphandle()
+ # check slist object persistance
+ self.slist_check(dup1, 'orig-slist', True)
+ self.slist_check(dup2, 'orig-slist', False)
+ # check overwriting - orig slist is decref'ed to 0 and finally deallocated
+ # util_curlslist_update() and util_curlslist_dealloc()
+ dup1.setopt(pycurl.HTTPHEADER, ['x-test-header: dup-slist'])
+ self.slist_check(dup1, 'dup-slist', True)
+ # cleanup
+ dup1.close()
+ dup2.close()
+ self.orig.close()
+
+ def test_duphandle_slist_xdecref(self):
+ # util_curl_xdecref()
+ self.slist_test(self.orig.reset)
+
+ def test_duphandle_slist_unsetopt(self):
+ # util_curl_unsetopt()
+ self.slist_test(self.orig.unsetopt, pycurl.HTTPHEADER)
+
+ def httppost_check(self, handle, value, persistance=True):
+ body = util.BytesIO()
+ handle.setopt(pycurl.WRITEFUNCTION, body.write)
+ handle.setopt(pycurl.URL, 'http://%s:8380/postfields' % localhost)
+ handle.perform()
+ result = json.loads(body.getvalue())
+ assert (result == value) == persistance
+
+ def httppost_test(self, clear_func, *args):
+ self.orig.setopt(pycurl.HTTPPOST, [
+ ('field', (pycurl.FORM_CONTENTS, 'orig-httppost')),
+ ])
+ dup1 = self.orig.duphandle()
+ clear_func(*args)
+ dup2 = self.orig.duphandle()
+ self.httppost_check(dup1, {'field': 'orig-httppost'}, True)
+ self.httppost_check(dup2, {'field': 'orig-httppost'}, False)
+ # util_curlhttppost_update() and util_curlhttppost_dealloc()
+ dup1.setopt(pycurl.HTTPPOST, [
+ ('field', (pycurl.FORM_CONTENTS, 'dup-httppost')),
+ ])
+ self.httppost_check(dup1, {'field': 'dup-httppost'}, True)
+ dup1.close()
+ dup2.close()
+ self.orig.close()
+
+ def test_duphandle_httppost_xdecref(self):
+ # util_curl_xdecref()
+ self.httppost_test(self.orig.reset)
+
+ def test_duphandle_httppost_unsetopt(self):
+ # util_curl_unsetopt()
+ self.httppost_test(self.orig.unsetopt, pycurl.HTTPPOST)
+
+ def test_duphandle_references(self):
+ body = util.BytesIO()
+ def callback(data):
+ body.write(data)
+ callback_ref = weakref.ref(callback)
+ # preliminary checks of gc and weakref working as expected
+ assert gc.get_referrers(callback) == []
+ assert callback_ref() is not None
+ # setopt - callback ref is copied and callback incref'ed
+ self.orig.setopt(pycurl.WRITEFUNCTION, callback)
+ assert gc.get_referrers(callback) == [self.orig]
+ # duphandle - callback ref is copied and callback incref'ed
+ dup = self.orig.duphandle()
+ assert set(gc.get_referrers(callback)) == {self.orig, dup}
+ # dealloc self.orig and decref callback
+ self.orig.close()
+ del self.orig
+ assert gc.get_referrers(callback) == [dup]
+ # decref callback again - back to ref count = 1
+ del callback
+ assert callback_ref() is not None
+ # check that callback object still exists and is invoked
+ dup.setopt(pycurl.URL, 'http://%s:8380/success' % localhost)
+ dup.perform()
+ result = body.getvalue().decode('utf-8')
+ assert result == 'success'
+ # final decref - callback is deallocated
+ dup.close()
+ assert callback_ref() is None