diff -Nru pycurl-7.43.0.1/AUTHORS pycurl-7.43.0.2/AUTHORS --- pycurl-7.43.0.1/AUTHORS 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/AUTHORS 2018-06-02 04:59:02.000000000 +0000 @@ -1,6 +1,6 @@ Copyright (C) 2001-2008 by Kjetil Jacobsen Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer -Copyright (C) 2013-2017 by Oleg Pudeyev +Copyright (C) 2013-2018 by Oleg Pudeyev Please see README, COPYING-LGPL and COPYING-MIT for license information. @@ -15,6 +15,7 @@ Barry Warsaw Bastian Kleineidam Benjamin Peterson +Casey Miller Christopher Warner Clint Clayton Conrad Steenberg @@ -33,6 +34,7 @@ Iain R. Learmonth ideal Jakob Truelsen +Jakub Wilk Jan Kryl Jayne JiCiT @@ -66,6 +68,8 @@ Tino Lange Tom Pierce Victor Lascurain +Vitaly Murashev +Vitezslav Cizek Wei C Whitney Sorenson Wim Lewis diff -Nru pycurl-7.43.0.1/ChangeLog pycurl-7.43.0.2/ChangeLog --- pycurl-7.43.0.1/ChangeLog 2017-12-07 07:41:34.000000000 +0000 +++ pycurl-7.43.0.2/ChangeLog 2018-06-02 04:59:02.000000000 +0000 @@ -1,3 +1,67 @@ +Version 7.43.0.2 [requires libcurl-7.19.0 or better] - 2018-06-02 +----------------------------------------------------------------- + + * Official Windows builds now include HTTP 2 support via + libnghttp2 and international domain name support via WINIDN. + + * Added perform_rb and perform_rs methods to Curl objects to + return response body as byte string and string, respectively. + + * Added OPT_COOKIELIST constant for consistency with other + option constants. + + * PycURL is now able to report errors triggered by libcurl + via CURLOPT_FAILONERROR mechanism when the error messages are + not decodable in Python's default encoding (GitHub issue #259). + + * Added getinfo_raw method to Curl objects to return byte strings + as is from libcurl without attempting to decode them + (GitHub issue #493). + + * When adding a Curl easy object to CurlMulti via add_handle, + the easy objects now have their reference counts increased so that + the application is no longer required to keep references to them + to keep them from being garbage collected (GitHub issue #171). + + * PycURL easy, multi and share objects can now be weak referenced. + + * Python 3.2 and 3.3 support officially dropped as those versions + are end of lifed. + + * set_ca_certs now accepts byte strings as it should have been + all along. + + * PycURL now skips automatic SSL backend detection if curl-config + indicates that libcurl is not built with SSL support, and will warn + if an SSL backend is explicitly specified in this case. + + * PycURL now requires that SSL backend is determined by setup.py + to provide earlier failure compared to the existing warning + during compilation and failing during module import on mismatched + SSL backends. + + * Use OpenSSL 1.1 and 1.0 specific APIs for controlling thread locks + depending on OpenSSL version (patch by Vitaly Murashev). + + * Fixed a crash when closesocket callback failed (patch by + Gisle Vanem and toddrme2178). + + * Added CURLOPT_PROXY_SSLCERT, CURLOPT_PROXY_SSLCERTTYPE, + CURLOPT_PROXY_SSLKEY, CURLOPT_PROXY_SSLKEYTYPE, + CURLOPT_PROXY_SSL_VERIFYPEER (libcurl 7.52.0+, + patch by Casey Miller). + + * Added CURLOPT_PRE_PROXY (libcurl 7.52.0+, patch by ziggy). + + * Support for Python 2.6 officially dropped. + + * Added SOCKET_BAD constant and it is now recognized as a valid + return value from OPENSOCKET callback. + + * BoringSSL is now recognized as equivalent to OpenSSL backend + (patch by Gisle Vanem). + + Version 7.43.0.1 [requires libcurl-7.19.0 or better] - 2017-12-07 ----------------------------------------------------------------- diff -Nru pycurl-7.43.0.1/COPYING-MIT pycurl-7.43.0.2/COPYING-MIT --- pycurl-7.43.0.1/COPYING-MIT 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/COPYING-MIT 2018-06-02 04:59:02.000000000 +0000 @@ -2,7 +2,7 @@ Copyright (C) 2001-2008 by Kjetil Jacobsen Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer -Copyright (C) 2013-2017 by Oleg Pudeyev +Copyright (C) 2013-2018 by Oleg Pudeyev All rights reserved. diff -Nru pycurl-7.43.0.1/debian/changelog pycurl-7.43.0.2/debian/changelog --- pycurl-7.43.0.1/debian/changelog 2018-11-03 11:52:07.000000000 +0000 +++ pycurl-7.43.0.2/debian/changelog 2019-01-07 11:00:03.000000000 +0000 @@ -1,14 +1,20 @@ -pycurl (7.43.0.1-0.2build2) disco; urgency=medium +pycurl (7.43.0.2-0.1) unstable; urgency=medium - * No-change rebuild to build without python3.6 support. - - -- Matthias Klose Sat, 03 Nov 2018 11:52:07 +0000 - -pycurl (7.43.0.1-0.2build1) cosmic; urgency=medium + [ Iain R. Learmonth ] + * Non maintainer upload + * New upstream release - * No-change rebuild to build for python3.7. + [ Ondřej Nový ] + * d/control: Set Vcs-* to salsa.debian.org + * d/copyright: Use https protocol in Format field + * d/control: Deprecating priority extra as per policy 4.0.1 + * d/watch: Use https protocol + * d/changelog: Remove trailing whitespaces + * d/tests: Use AUTOPKGTEST_TMP instead of ADTTMP + * d/control: Remove ancient X-Python3-Version field + * Convert git repository from git-dpm to gbp layout - -- Matthias Klose Thu, 28 Jun 2018 06:54:14 +0000 + -- Iain R. Learmonth Mon, 07 Jan 2019 11:00:03 +0000 pycurl (7.43.0.1-0.2) unstable; urgency=medium @@ -115,7 +121,7 @@ [ Andreas Metzler ] * debian/control - Drop build-depency on librtmp-dev, as it is not in "curl-config - --static-libs", otoh add one for libgnutls28-dev, since pycurl.c + --static-libs", otoh add one for libgnutls28-dev, since pycurl.c #includes . Closes: #751059 (Applied from NMU diff by Barry Warsaw. Closes: #756199) @@ -148,7 +154,7 @@ pycurl (7.19.3.1-1) unstable; urgency=medium [ Bernd Zeimetz ] - * Removing myself from uploaders. + * Removing myself from uploaders. [ Sandro Tosi ] * New upstream release @@ -217,7 +223,7 @@ pycurl (7.19.0-3) unstable; urgency=low * Fix FTBFS on kfreebsd, thanks to KiBi (Closes: #540980). - * Bump Standards-version to 3.8.3, no changes needed. + * Bump Standards-version to 3.8.3, no changes needed. -- Bernd Zeimetz Sun, 27 Sep 2009 18:03:31 +0200 @@ -238,8 +244,8 @@ [ Bernd Zeimetz ] * debian/control: - Rising the priority to optional, as we have rdepends in optional. - * Switching from python-central to python-support. - * Support building for Python 2.6. + * Switching from python-central to python-support. + * Support building for Python 2.6. [ Sandro Tosi ] * New upstream release. diff -Nru pycurl-7.43.0.1/debian/control pycurl-7.43.0.2/debian/control --- pycurl-7.43.0.1/debian/control 2018-02-01 20:23:24.000000000 +0000 +++ pycurl-7.43.0.2/debian/control 2019-01-07 10:36:39.000000000 +0000 @@ -20,10 +20,9 @@ python3-flaky, Standards-Version: 3.9.8 X-Python-Version: all -X-Python3-Version: >= 3.3 Homepage: http://pycurl.sourceforge.net -Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/pycurl.git -Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/pycurl.git +Vcs-Git: https://salsa.debian.org/python-team/modules/pycurl.git +Vcs-Browser: https://salsa.debian.org/python-team/modules/pycurl Package: python-pycurl Architecture: any @@ -59,7 +58,6 @@ Package: python-pycurl-dbg Section: debug -Priority: extra Architecture: any Depends: python-dbg, python-pycurl (= ${binary:Version}), @@ -73,7 +71,6 @@ Package: python3-pycurl-dbg Section: debug -Priority: extra Architecture: any Depends: python3-dbg, python3-pycurl (= ${binary:Version}), diff -Nru pycurl-7.43.0.1/debian/copyright pycurl-7.43.0.2/debian/copyright --- pycurl-7.43.0.1/debian/copyright 2018-01-22 07:54:47.000000000 +0000 +++ pycurl-7.43.0.2/debian/copyright 2019-01-07 10:30:42.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pycurl Source: http://pycurl.sourceforge.net diff -Nru pycurl-7.43.0.1/debian/.git-dpm pycurl-7.43.0.2/debian/.git-dpm --- pycurl-7.43.0.1/debian/.git-dpm 2018-01-22 07:54:47.000000000 +0000 +++ pycurl-7.43.0.2/debian/.git-dpm 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# see git-dpm(1) from git-dpm package -04a5e8228fa57b23b53fb7bf3592728f171bb841 -04a5e8228fa57b23b53fb7bf3592728f171bb841 -81834bfdd6e021247c5ca0c6ec4abe55cc246ca5 -81834bfdd6e021247c5ca0c6ec4abe55cc246ca5 -pycurl_7.43.0.orig.tar.gz -e8e9c7e9ae91ae32096b8c86cfc7d49976a66d1b -182522 -debianTag="debian/%e%v" -patchedTag="patched/%e%v" -upstreamTag="upstream/%e%u" diff -Nru pycurl-7.43.0.1/debian/tests/livetest.sh pycurl-7.43.0.2/debian/tests/livetest.sh --- pycurl-7.43.0.1/debian/tests/livetest.sh 2018-01-22 07:54:47.000000000 +0000 +++ pycurl-7.43.0.2/debian/tests/livetest.sh 2019-01-07 10:30:42.000000000 +0000 @@ -1,6 +1,6 @@ #!/bin/sh -cat > $ADTTMP/livetest.py < $AUTOPKGTEST_TMP/livetest.py < ``perform()`` -> libcurl (C code) -> Python callback + +Because callbacks are invoked by libcurl, they should not raise exceptions +on failure but instead return appropriate values indicating failure. +The documentation for individual callbacks below specifies expected success and +failure return values. + +Unhandled exceptions propagated out of Python callbacks will be intercepted +by PycURL or the Python runtime. This will fail the callback with a +generic failure status, in turn failing the ``perform()`` operation. +A failing ``perform()`` will raise ``pycurl.error``, but the error code +used depends on the specific callback. + +Rich context information like exception objects can be stored in various ways, +for example the following example stores OPENSOCKET callback exception on the +Curl object:: + + import pycurl, random, socket + + class ConnectionRejected(Exception): + pass + + def opensocket(curl, purpose, curl_address): + # always fail + curl.exception = ConnectionRejected('Rejecting connection attempt in opensocket callback') + return pycurl.SOCKET_BAD + + # the callback must create a socket if it does not fail, + # see examples/opensocketexception.py + + c = pycurl.Curl() + c.setopt(c.URL, 'http://pycurl.io') + c.exception = None + c.setopt(c.OPENSOCKETFUNCTION, + lambda purpose, address: opensocket(c, purpose, address)) + + try: + c.perform() + except pycurl.error as e: + if e.args[0] == pycurl.E_COULDNT_CONNECT and c.exception: + print(c.exception) + else: + print(e) WRITEFUNCTION diff -Nru pycurl-7.43.0.1/doc/conf.py pycurl-7.43.0.2/doc/conf.py --- pycurl-7.43.0.1/doc/conf.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/doc/conf.py 2018-06-02 04:59:02.000000000 +0000 @@ -47,16 +47,16 @@ # General information about the project. project = u'PycURL' -copyright = u'2001-2017 Kjetil Jacobsen, Markus F.X.J. Oberhumer, Oleg Pudeyev' +copyright = u'2001-2018 Kjetil Jacobsen, Markus F.X.J. Oberhumer, Oleg Pudeyev' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '7.43.0.1' +version = '7.43.0.2' # The full version, including alpha/beta/rc tags. -release = '7.43.0.1' +release = '7.43.0.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -Nru pycurl-7.43.0.1/doc/curlobject.rst pycurl-7.43.0.2/doc/curlobject.rst --- pycurl-7.43.0.1/doc/curlobject.rst 2017-12-03 19:03:13.000000000 +0000 +++ pycurl-7.43.0.2/doc/curlobject.rst 2018-05-23 18:34:47.000000000 +0000 @@ -12,10 +12,21 @@ .. _setopt: .. automethod:: pycurl.Curl.setopt + .. _perform: .. automethod:: pycurl.Curl.perform + .. _perform_rb: + .. automethod:: pycurl.Curl.perform_rb + + .. _perform_rs: + .. automethod:: pycurl.Curl.perform_rs + + .. _getinfo: .. automethod:: pycurl.Curl.getinfo + .. _getinfo_raw: + .. automethod:: pycurl.Curl.getinfo_raw + .. automethod:: pycurl.Curl.reset .. _unsetopt: @@ -23,6 +34,10 @@ .. automethod:: pycurl.Curl.pause + .. _errstr: .. automethod:: pycurl.Curl.errstr + .. _errstr_raw: + .. automethod:: pycurl.Curl.errstr_raw + .. automethod:: pycurl.Curl.setopt_string diff -Nru pycurl-7.43.0.1/doc/docstrings/curl_errstr_raw.rst pycurl-7.43.0.2/doc/docstrings/curl_errstr_raw.rst --- pycurl-7.43.0.1/doc/docstrings/curl_errstr_raw.rst 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/curl_errstr_raw.rst 2018-05-23 04:18:06.000000000 +0000 @@ -0,0 +1,12 @@ +errstr_raw() -> byte string + +Return the internal libcurl error buffer of this handle as a byte string. + +Return value is a ``str`` instance on Python 2 and ``bytes`` instance +on Python 3. Unlike :ref:`errstr_raw `, ``errstr_raw`` +allows reading libcurl error buffer in Python 3 when its contents is not +valid in Python's default encoding. + +On Python 2, ``errstr`` and ``errstr_raw`` behave identically. + +*Added in version 7.43.0.2.* diff -Nru pycurl-7.43.0.1/doc/docstrings/curl_errstr.rst pycurl-7.43.0.2/doc/docstrings/curl_errstr.rst --- pycurl-7.43.0.1/doc/docstrings/curl_errstr.rst 2017-12-03 19:02:58.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/curl_errstr.rst 2018-05-23 04:18:06.000000000 +0000 @@ -3,3 +3,9 @@ Return the internal libcurl error buffer of this handle as a string. Return value is a ``str`` instance on all Python versions. +On Python 3, error buffer data is decoded using Python's default encoding +at the time of the call. If this decoding fails, ``UnicodeDecodeError`` is +raised. Use :ref:`errstr_raw ` to retrieve the error buffer +as a byte string in this case. + +On Python 2, ``errstr`` and ``errstr_raw`` behave identically. diff -Nru pycurl-7.43.0.1/doc/docstrings/curl_getinfo_raw.rst pycurl-7.43.0.2/doc/docstrings/curl_getinfo_raw.rst --- pycurl-7.43.0.1/doc/docstrings/curl_getinfo_raw.rst 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/curl_getinfo_raw.rst 2018-05-23 04:37:03.000000000 +0000 @@ -0,0 +1,70 @@ +getinfo_raw(option) -> Result + +Extract and return information from a curl session, +returning string data as byte strings. +Corresponds to `curl_easy_getinfo`_ in libcurl. +The ``getinfo_raw`` method should not be called unless +``perform`` has been called and finished. + +*option* is a constant corresponding to one of the +``CURLINFO_*`` constants in libcurl. Most option constant names match +the respective ``CURLINFO_*`` constant names with the ``CURLINFO_`` prefix +removed, for example ``CURLINFO_CONTENT_TYPE`` is accessible as +``pycurl.CONTENT_TYPE``. Exceptions to this rule are as follows: + +- ``CURLINFO_FILETIME`` is mapped as ``pycurl.INFO_FILETIME`` +- ``CURLINFO_COOKIELIST`` is mapped as ``pycurl.INFO_COOKIELIST`` +- ``CURLINFO_CERTINFO`` is mapped as ``pycurl.INFO_CERTINFO`` +- ``CURLINFO_RTSP_CLIENT_CSEQ`` is mapped as ``pycurl.INFO_RTSP_CLIENT_CSEQ`` +- ``CURLINFO_RTSP_CSEQ_RECV`` is mapped as ``pycurl.INFO_RTSP_CSEQ_RECV`` +- ``CURLINFO_RTSP_SERVER_CSEQ`` is mapped as ``pycurl.INFO_RTSP_SERVER_CSEQ`` +- ``CURLINFO_RTSP_SESSION_ID`` is mapped as ``pycurl.INFO_RTSP_SESSION_ID`` + +The type of return value depends on the option, as follows: + +- Options documented by libcurl to return an integer value return a + Python integer (``long`` on Python 2, ``int`` on Python 3). +- Options documented by libcurl to return a floating point value + return a Python ``float``. +- Options documented by libcurl to return a string value + return a Python byte string (``str`` on Python 2, ``bytes`` on Python 3). + The string contains whatever data libcurl returned. + Use :ref:`getinfo ` to retrieve this data as a Unicode string on Python 3. +- ``SSL_ENGINES`` and ``INFO_COOKIELIST`` return a list of byte strings. + The same encoding caveats apply; use :ref:`getinfo ` to retrieve the + data as a list of potentially Unicode strings. +- ``INFO_CERTINFO`` returns a list with one element + per certificate in the chain, starting with the leaf; each element is a + sequence of *(key, value)* tuples where both ``key`` and ``value`` are + byte strings. String encoding caveats apply; use :ref:`getinfo ` + to retrieve + certificate data as potentially Unicode strings. + +On Python 2, ``getinfo`` and ``getinfo_raw`` behave identically. + +Example usage:: + + import pycurl + c = pycurl.Curl() + c.setopt(pycurl.OPT_CERTINFO, 1) + c.setopt(pycurl.URL, "https://python.org") + c.setopt(pycurl.FOLLOWLOCATION, 1) + c.perform() + print(c.getinfo_raw(pycurl.HTTP_CODE)) + # --> 200 + print(c.getinfo_raw(pycurl.EFFECTIVE_URL)) + # --> b"https://www.python.org/" + certinfo = c.getinfo_raw(pycurl.INFO_CERTINFO) + print(certinfo) + # --> [((b'Subject', b'C = AU, ST = Some-State, O = PycURL test suite, + CN = localhost'), (b'Issuer', b'C = AU, ST = Some-State, + O = PycURL test suite, OU = localhost, CN = localhost'), + (b'Version', b'0'), ...)] + + +Raises pycurl.error exception upon failure. + +*Added in version 7.43.0.2.* + +.. _curl_easy_getinfo: + https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html diff -Nru pycurl-7.43.0.1/doc/docstrings/curl_getinfo.rst pycurl-7.43.0.2/doc/docstrings/curl_getinfo.rst --- pycurl-7.43.0.1/doc/docstrings/curl_getinfo.rst 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/curl_getinfo.rst 2018-05-23 04:37:03.000000000 +0000 @@ -1,33 +1,71 @@ -getinfo(info) -> Result +getinfo(option) -> Result -Extract and return information from a curl session. - -Corresponds to `curl_easy_getinfo`_ in libcurl, where *option* is -the same as the ``CURLINFO_*`` constants in libcurl, except that the -``CURLINFO_`` prefix has been removed. (See below for exceptions.) -*Result* contains an integer, float or string, depending on which -option is given. The ``getinfo`` method should not be called unless +Extract and return information from a curl session, +decoding string data in Python's default encoding at the time of the call. +Corresponds to `curl_easy_getinfo`_ in libcurl. +The ``getinfo`` method should not be called unless ``perform`` has been called and finished. -In order to distinguish between similarly-named CURLOPT and CURLINFO -constants, some have ``OPT_`` and ``INFO_`` prefixes. These are -``INFO_FILETIME``, ``OPT_FILETIME``, ``INFO_COOKIELIST`` (but ``setopt`` uses -``COOKIELIST``!), ``INFO_CERTINFO``, and ``OPT_CERTINFO``. - -The value returned by ``getinfo(INFO_CERTINFO)`` is a list with one element -per certificate in the chain, starting with the leaf; each element is a -sequence of *(key, value)* tuples. +*option* is a constant corresponding to one of the +``CURLINFO_*`` constants in libcurl. Most option constant names match +the respective ``CURLINFO_*`` constant names with the ``CURLINFO_`` prefix +removed, for example ``CURLINFO_CONTENT_TYPE`` is accessible as +``pycurl.CONTENT_TYPE``. Exceptions to this rule are as follows: + +- ``CURLINFO_FILETIME`` is mapped as ``pycurl.INFO_FILETIME`` +- ``CURLINFO_COOKIELIST`` is mapped as ``pycurl.INFO_COOKIELIST`` +- ``CURLINFO_CERTINFO`` is mapped as ``pycurl.INFO_CERTINFO`` +- ``CURLINFO_RTSP_CLIENT_CSEQ`` is mapped as ``pycurl.INFO_RTSP_CLIENT_CSEQ`` +- ``CURLINFO_RTSP_CSEQ_RECV`` is mapped as ``pycurl.INFO_RTSP_CSEQ_RECV`` +- ``CURLINFO_RTSP_SERVER_CSEQ`` is mapped as ``pycurl.INFO_RTSP_SERVER_CSEQ`` +- ``CURLINFO_RTSP_SESSION_ID`` is mapped as ``pycurl.INFO_RTSP_SESSION_ID`` + +The type of return value depends on the option, as follows: + +- Options documented by libcurl to return an integer value return a + Python integer (``long`` on Python 2, ``int`` on Python 3). +- Options documented by libcurl to return a floating point value + return a Python ``float``. +- Options documented by libcurl to return a string value + return a Python string (``str`` on Python 2 and Python 3). + On Python 2, the string contains whatever data libcurl returned. + On Python 3, the data returned by libcurl is decoded using the + default string encoding at the time of the call. + If the data cannot be decoded using the default encoding, ``UnicodeDecodeError`` + is raised. Use :ref:`getinfo_raw ` + to retrieve the data as ``bytes`` in these + cases. +- ``SSL_ENGINES`` and ``INFO_COOKIELIST`` return a list of strings. + The same encoding caveats apply; use :ref:`getinfo_raw ` + to retrieve the + data as a list of byte strings. +- ``INFO_CERTINFO`` returns a list with one element + per certificate in the chain, starting with the leaf; each element is a + sequence of *(key, value)* tuples where both ``key`` and ``value`` are + strings. String encoding caveats apply; use :ref:`getinfo_raw ` + to retrieve + certificate data as byte strings. + +On Python 2, ``getinfo`` and ``getinfo_raw`` behave identically. Example usage:: import pycurl c = pycurl.Curl() + c.setopt(pycurl.OPT_CERTINFO, 1) c.setopt(pycurl.URL, "https://python.org") c.setopt(pycurl.FOLLOWLOCATION, 1) c.perform() - print c.getinfo(pycurl.HTTP_CODE), c.getinfo(pycurl.EFFECTIVE_URL) - ... - --> 200 "https://www.python.org/" + print(c.getinfo(pycurl.HTTP_CODE)) + # --> 200 + print(c.getinfo(pycurl.EFFECTIVE_URL)) + # --> "https://www.python.org/" + certinfo = c.getinfo(pycurl.INFO_CERTINFO) + print(certinfo) + # --> [(('Subject', 'C = AU, ST = Some-State, O = PycURL test suite, + CN = localhost'), ('Issuer', 'C = AU, ST = Some-State, + O = PycURL test suite, OU = localhost, CN = localhost'), + ('Version', '0'), ...)] Raises pycurl.error exception upon failure. diff -Nru pycurl-7.43.0.1/doc/docstrings/curl_perform_rb.rst pycurl-7.43.0.2/doc/docstrings/curl_perform_rb.rst --- pycurl-7.43.0.1/doc/docstrings/curl_perform_rb.rst 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/curl_perform_rb.rst 2018-05-23 18:34:47.000000000 +0000 @@ -0,0 +1,17 @@ +perform_rb() -> response_body + +Perform a file transfer and return response body as a byte string. + +This method arranges for response body to be saved in a StringIO +(Python 2) or BytesIO (Python 3) instance, then invokes :ref:`perform ` +to perform the file transfer, then returns the value of the StringIO/BytesIO +instance which is a ``str`` instance on Python 2 and ``bytes`` instance +on Python 3. Errors during transfer raise ``pycurl.error`` exceptions +just like in :ref:`perform `. + +Use :ref:`perform_rs ` to retrieve response body as a string +(``str`` instance on both Python 2 and 3). + +Raises ``pycurl.error`` exception upon failure. + +*Added in version 7.43.0.2.* diff -Nru pycurl-7.43.0.1/doc/docstrings/curl_perform_rs.rst pycurl-7.43.0.2/doc/docstrings/curl_perform_rs.rst --- pycurl-7.43.0.1/doc/docstrings/curl_perform_rs.rst 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/curl_perform_rs.rst 2018-05-23 18:34:47.000000000 +0000 @@ -0,0 +1,25 @@ +perform_rs() -> response_body + +Perform a file transfer and return response body as a string. + +On Python 2, this method arranges for response body to be saved in a StringIO +instance, then invokes :ref:`perform ` +to perform the file transfer, then returns the value of the StringIO instance. +This behavior is identical to :ref:`perform_rb `. + +On Python 3, this method arranges for response body to be saved in a BytesIO +instance, then invokes :ref:`perform ` +to perform the file transfer, then decodes the response body in Python's +default encoding and returns the decoded body as a Unicode string +(``str`` instance). *Note:* decoding happens after the transfer finishes, +thus an encoding error implies the transfer/network operation succeeded. + +Any transfer errors raise ``pycurl.error`` exception, +just like in :ref:`perform `. + +Use :ref:`perform_rb ` to retrieve response body as a byte +string (``bytes`` instance on Python 3) without attempting to decode it. + +Raises ``pycurl.error`` exception upon failure. + +*Added in version 7.43.0.2.* diff -Nru pycurl-7.43.0.1/doc/docstrings/curl_setopt.rst pycurl-7.43.0.2/doc/docstrings/curl_setopt.rst --- pycurl-7.43.0.1/doc/docstrings/curl_setopt.rst 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/curl_setopt.rst 2018-05-23 04:37:03.000000000 +0000 @@ -5,7 +5,8 @@ *option* specifies which option to set. PycURL defines constants corresponding to ``CURLOPT_*`` constants in libcurl, except that the ``CURLOPT_`` prefix is removed. For example, ``CURLOPT_URL`` is -exposed in PycURL as ``pycurl.URL``. For convenience, ``CURLOPT_*`` +exposed in PycURL as ``pycurl.URL``, with some exceptions as detailed below. +For convenience, ``CURLOPT_*`` constants are also exposed on the Curl objects themselves:: import pycurl @@ -14,11 +15,18 @@ # Same as: c.setopt(c.URL, "http://www.python.org/") -In order to distinguish between similarly-named CURLOPT and CURLINFO -constants, some have CURLOPT constants have ``OPT_`` prefixes. -These are ``OPT_FILETIME`` and ``OPT_CERTINFO``. -As an exception to the exception, ``COOKIELIST`` does not have an ``OPT_`` -prefix but the corresponding CURLINFO option is ``INFO_COOKIELIST``. +The following are exceptions to option constant naming convention: + +- ``CURLOPT_FILETIME`` is mapped as ``pycurl.OPT_FILETIME`` +- ``CURLOPT_CERTINFO`` is mapped as ``pycurl.OPT_CERTINFO`` +- ``CURLOPT_COOKIELIST`` is mapped as ``pycurl.COOKIELIST`` + and, as of PycURL 7.43.0.2, also as ``pycurl.OPT_COOKIELIST`` +- ``CURLOPT_RTSP_CLIENT_CSEQ`` is mapped as ``pycurl.OPT_RTSP_CLIENT_CSEQ`` +- ``CURLOPT_RTSP_REQUEST`` is mapped as ``pycurl.OPT_RTSP_REQUEST`` +- ``CURLOPT_RTSP_SERVER_CSEQ`` is mapped as ``pycurl.OPT_RTSP_SERVER_CSEQ`` +- ``CURLOPT_RTSP_SESSION_ID`` is mapped as ``pycurl.OPT_RTSP_SESSION_ID`` +- ``CURLOPT_RTSP_STREAM_URI`` is mapped as ``pycurl.OPT_RTSP_STREAM_URI`` +- ``CURLOPT_RTSP_TRANSPORT`` is mapped as ``pycurl.OPT_RTSP_TRANSPORT`` *value* specifies the value to set the option to. Different options accept values of different types: diff -Nru pycurl-7.43.0.1/doc/docstrings/multi_add_handle.rst pycurl-7.43.0.2/doc/docstrings/multi_add_handle.rst --- pycurl-7.43.0.1/doc/docstrings/multi_add_handle.rst 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/doc/docstrings/multi_add_handle.rst 2018-05-22 03:44:40.000000000 +0000 @@ -3,9 +3,10 @@ Corresponds to `curl_multi_add_handle`_ in libcurl. This method adds an existing and valid Curl object to the CurlMulti object. -IMPORTANT NOTE: add_handle does not implicitly add a Python reference to the -Curl object (and thus does not increase the reference count on the Curl -object). +*Changed in version 7.43.0.2:* add_handle now ensures that the Curl object +is not garbage collected while it is being used by a CurlMulti object. +Previously application had to maintain an outstanding reference to the Curl +object to keep it from being garbage collected. .. _curl_multi_add_handle: https://curl.haxx.se/libcurl/c/curl_multi_add_handle.html diff -Nru pycurl-7.43.0.1/doc/index.rst pycurl-7.43.0.2/doc/index.rst --- pycurl-7.43.0.1/doc/index.rst 2017-12-07 08:20:49.000000000 +0000 +++ pycurl-7.43.0.2/doc/index.rst 2018-05-21 04:56:35.000000000 +0000 @@ -57,7 +57,7 @@ Requirements ------------ -- Python 2.6, 2.7 or 3.1 through 3.6. +- Python 2.7 or 3.4 through 3.6. - libcurl 7.19.0 or better. @@ -74,8 +74,7 @@ If this does not work, please see :ref:`install`. -On Windows, use pip to install a binary wheel for Python 2.6, 2.7 or -3.2 through 3.6:: +On Windows, use pip to install a binary wheel for Python 2.7, 3.5 or 3.6:: pip install pycurl diff -Nru pycurl-7.43.0.1/doc/quickstart.rst pycurl-7.43.0.2/doc/quickstart.rst --- pycurl-7.43.0.1/doc/quickstart.rst 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/doc/quickstart.rst 2018-05-19 00:47:45.000000000 +0000 @@ -74,6 +74,34 @@ example can be found in ``examples/quickstart/get.py``. +Working With HTTPS +------------------ + +Most web sites today use HTTPS which is HTTP over TLS/SSL. In order to +take advantage of security that HTTPS provides, PycURL needs to utilize +a *certificate bundle*. As certificates change over time PycURL does not +provide such a bundle; one may be supplied by your operating system, but +if not, consider using the `certifi`_ Python package:: + + import pycurl + import certifi + from StringIO import StringIO + + buffer = StringIO() + c = pycurl.Curl() + c.setopt(c.URL, 'https://python.org/') + c.setopt(c.WRITEDATA, buffer) + c.setopt(c.CAINFO, certifi.where()) + c.perform() + c.close() + + body = buffer.getvalue() + print(body) + +This code is available as ``examples/quickstart/get_python2_https.py`` and +``examples/quickstart/get_python3_https.py``. + + Troubleshooting --------------- @@ -314,11 +342,16 @@ c.setopt(c.CUSTOMREQUEST, 'PATCH') -File Upload ------------ +File Upload - Multipart POST +---------------------------- -To upload a file, use ``HTTPPOST`` option. To upload a physical file, -use ``FORM_FILE`` as follows:: +To replicate the behavior of file upload in an HTML form (specifically, +a multipart form), +use ``HTTPPOST`` option. Such an upload is performed with a ``POST`` request. +See the next example for how to upload a file with a ``PUT`` request. + +If the data to be uploaded is located in a physical file, +use ``FORM_FILE``:: import pycurl @@ -381,4 +414,51 @@ This code is available as ``examples/quickstart/file_upload_buffer.py``. + +File Upload - PUT +----------------- + +A file can also be uploaded in request body, via a ``PUT`` request. +Here is how this can be arranged with a physical file:: + + import pycurl + + c = pycurl.Curl() + c.setopt(c.URL, 'https://httpbin.org/put') + + c.setopt(c.UPLOAD, 1) + file = open('body.json') + c.setopt(c.READDATA, file) + + c.perform() + c.close() + # File must be kept open while Curl object is using it + file.close() + +This code is available as ``examples/quickstart/put_file.py``. + +And if the data is stored in a buffer:: + + import pycurl + try: + from io import BytesIO + except ImportError: + from StringIO import StringIO as BytesIO + + c = pycurl.Curl() + c.setopt(c.URL, 'https://httpbin.org/put') + + c.setopt(c.UPLOAD, 1) + data = '{"json":true}' + # READDATA requires an IO-like object; a string is not accepted + # encode() is necessary for Python 3 + buffer = BytesIO(data.encode('utf-8')) + c.setopt(c.READDATA, buffer) + + c.perform() + c.close() + +This code is available as ``examples/quickstart/put_buffer.py``. + .. _curl_formadd page: https://curl.haxx.se/libcurl/c/curl_formadd.html +.. _certifi: https://pypi.org/project/certifi/ diff -Nru pycurl-7.43.0.1/doc/release-process.rst pycurl-7.43.0.2/doc/release-process.rst --- pycurl-7.43.0.1/doc/release-process.rst 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/doc/release-process.rst 2018-06-02 04:59:02.000000000 +0000 @@ -2,30 +2,32 @@ =============== 1. Ensure changelog is up to date with commits in master. -2. Run ``git shortlog REL_...`` and add new contributors - to AUTHORS. -3. Run ``python setup.py manifest``, check that none of the listed files +2. Run ``python setup.py authors`` and review the updated AUTHORS file. +3. Run ``git shortlog REL_...`` and add new contributors + missed by the authors script to AUTHORS. +4. Run ``python setup.py manifest``, check that none of the listed files should be in MANIFEST.in. -4. Check ``get_data_files()`` in ``setup.py`` to see if any new files should +5. Check ``get_data_files()`` in ``setup.py`` to see if any new files should be included in binary distributions. -5. Make sure travis is green for master. -6. Update version numbers in: +6. Make sure Travis and AppVeyor are green for master. +7. Update version numbers in: - Changelog (also record release date) - doc/conf.py - setup.py - winbuild.py -7. Draft release notes, add to RELEASE-NOTES.rst. -8. ``make gen docs``. -9. ``python setup.py sdist``. -10. Manually test install the built package. -11. Build windows packages using winbuild.py. -12. Add sdist and windows packages to downloads repo on github. -13. Tag the new version. -14. Register new version with pypi - ``python setup.py register``. -15. Upload source distribution to pypi using twine. -16. Upload windows wheels to pypi using twine. -17. Upload windows exe installers to pypi using twine. -18. Upload release files to bintray. -19. Push tag to github pycurl repo. -20. Announce release on mailing list. -21. Link to announcement from website. +8. Update copyright years if necessary. +9. Draft release notes, add to RELEASE-NOTES.rst. +10. ``make gen docs``. +11. ``python setup.py sdist``. +12. Manually test install the built package. +13. Build windows packages using winbuild.py. +14. Add sdist and windows packages to downloads repo on github. +15. Tag the new version. +16. Register new version with pypi - ``python setup.py register``. +17. Upload source distribution to pypi using twine. +18. Upload windows wheels to pypi using twine. +19. Upload windows exe installers to pypi using twine. +20. Upload release files to bintray. +21. Push tag to github pycurl repo. +22. Announce release on mailing list. +23. Link to announcement from website. diff -Nru pycurl-7.43.0.1/doc/troubleshooting.rst pycurl-7.43.0.2/doc/troubleshooting.rst --- pycurl-7.43.0.1/doc/troubleshooting.rst 2017-12-07 08:20:49.000000000 +0000 +++ pycurl-7.43.0.2/doc/troubleshooting.rst 2018-02-25 19:03:59.000000000 +0000 @@ -7,6 +7,46 @@ network operations and transfer-related issues are generally the domain of libcurl. +``setopt``-Related Issues +------------------------- + +:ref:`setopt ` is one method that is used for setting most +of the libcurl options, as such calls to it can fail in a wide variety +of ways. + +``TypeError: invalid arguments to setopt`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This usually means the *type* of argument passed to ``setopt`` does not +match what the option expects. Recent versions of PycURL have improved +error reporting when this happens and they also accept more data types +(for example tuples in addition to lists). If you are using an old version of +PycURL, upgrading to the last version may help troubleshoot the situation. + +The next step is carefully reading libcurl documentation for the option +in question and verifying that the type, structure and format of data +you are passing matches what the option expects. + +``pycurl.error: (1, '')`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +An exception like this means PycURL accepted the structure and values +in the option parameter and sent them on to libcurl, and +libcurl rejected the attempt to set the option. + +Until PycURL implements an error code to symbol mapping, +you have to perform this mapping by hand. Error codes are +found in the file `curl.h`_ in libcurl source; look for ``CURLE_OK``. +For example, error code 1 means ``CURLE_UNSUPPORTED_PROTOCOL``. + +libcurl can reject a ``setopt`` call for a variety of reasons of its own, +including but not limited to the requested functionality +`not being compiled in`_ or being not supported with the SSL backend +being used. + +Transfer-Related Issues +----------------------- + If your issue is transfer-related (timeout, connection failure, transfer failure, ``perform`` hangs, etc.) the first step in troubleshooting is setting the ``VERBOSE`` flag for the operation. libcurl will then output @@ -34,7 +74,7 @@ * server certificate activation date OK * certificate public key: RSA * certificate version: #3 - * subject: + * subject: * start date: Sat, 17 Jun 2017 00:00:00 GMT * expire date: Thu, 27 Sep 2018 12:00:00 GMT * issuer: C=US,O=DigiCert Inc,OU=www.digicert.com,CN=DigiCert SHA2 Extended Validation Server CA @@ -65,10 +105,10 @@ < X-Timer: S1512631712.274059,VS0,VE0 < Vary: Cookie < Strict-Transport-Security: max-age=63072000; includeSubDomains - < + < * Curl_http_done: called premature == 0 * Connection #1 to host www.python.org left intact - >>> + >>> The verbose output in the above example includes: @@ -83,3 +123,6 @@ utility and verify that the behavior is PycURL-specific, as in most cases it is not. This is also a good time to check the behavior of the latest version of libcurl. + +.. _curl.h: https://github.com/curl/curl/blob/master/include/curl/curl.h#L456 +.. _not being compiled in: https://github.com/pycurl/pycurl/issues/477 diff -Nru pycurl-7.43.0.1/examples/opensocketexception.py pycurl-7.43.0.2/examples/opensocketexception.py --- pycurl-7.43.0.1/examples/opensocketexception.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/examples/opensocketexception.py 2018-05-18 19:53:25.000000000 +0000 @@ -0,0 +1,30 @@ +# Exposing rich exception information from callbacks example + +import pycurl, random, socket + +class ConnectionRejected(Exception): + pass + +def opensocket(curl, purpose, curl_address): + if random.random() < 0.5: + curl.exception = ConnectionRejected('Rejecting connection attempt in opensocket callback') + return pycurl.SOCKET_BAD + + family, socktype, protocol, address = curl_address + s = socket.socket(family, socktype, protocol) + s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + return s + +c = pycurl.Curl() +c.setopt(c.URL, 'http://pycurl.io') +c.exception = None +c.setopt(c.OPENSOCKETFUNCTION, + lambda purpose, address: opensocket(c, purpose, address)) + +try: + c.perform() +except pycurl.error as e: + if e.args[0] == pycurl.E_COULDNT_CONNECT and c.exception: + print(c.exception) + else: + print(e) diff -Nru pycurl-7.43.0.1/examples/quickstart/get_python2_https.py pycurl-7.43.0.2/examples/quickstart/get_python2_https.py --- pycurl-7.43.0.1/examples/quickstart/get_python2_https.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/examples/quickstart/get_python2_https.py 2018-05-19 00:47:45.000000000 +0000 @@ -0,0 +1,22 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vi:ts=4:et + +import pycurl +import certifi +from StringIO import StringIO + +buffer = StringIO() +c = pycurl.Curl() +c.setopt(c.URL, 'http://pycurl.io/') +c.setopt(c.WRITEDATA, buffer) +# For older PycURL versions: +#c.setopt(c.WRITEFUNCTION, buffer.write) +c.setopt(c.CAINFO, certifi.where()) +c.perform() +c.close() + +body = buffer.getvalue() +# Body is a string in some encoding. +# In Python 2, we can print it without knowing what the encoding is. +print(body) diff -Nru pycurl-7.43.0.1/examples/quickstart/get_python3_https.py pycurl-7.43.0.2/examples/quickstart/get_python3_https.py --- pycurl-7.43.0.1/examples/quickstart/get_python3_https.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/examples/quickstart/get_python3_https.py 2018-05-19 00:47:45.000000000 +0000 @@ -0,0 +1,21 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vi:ts=4:et + +import pycurl +import certifi +from io import BytesIO + +buffer = BytesIO() +c = pycurl.Curl() +c.setopt(c.URL, 'http://pycurl.io/') +c.setopt(c.WRITEDATA, buffer) +c.setopt(c.CAINFO, certifi.where()) +c.perform() +c.close() + +body = buffer.getvalue() +# Body is a byte string. +# We have to know the encoding in order to print it to a text file +# such as standard output. +print(body.decode('iso-8859-1')) diff -Nru pycurl-7.43.0.1/examples/quickstart/put_buffer.py pycurl-7.43.0.2/examples/quickstart/put_buffer.py --- pycurl-7.43.0.1/examples/quickstart/put_buffer.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/examples/quickstart/put_buffer.py 2018-05-19 00:47:45.000000000 +0000 @@ -0,0 +1,22 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vi:ts=4:et + +import pycurl +try: + from io import BytesIO +except ImportError: + from StringIO import StringIO as BytesIO + +c = pycurl.Curl() +c.setopt(c.URL, 'https://httpbin.org/put') + +c.setopt(c.UPLOAD, 1) +data = '{"json":true}' +# READDATA requires an IO-like object; a string is not accepted +# encode() is necessary for Python 3 +buffer = BytesIO(data.encode('utf-8')) +c.setopt(c.READDATA, buffer) + +c.perform() +c.close() diff -Nru pycurl-7.43.0.1/examples/quickstart/put_file.py pycurl-7.43.0.2/examples/quickstart/put_file.py --- pycurl-7.43.0.1/examples/quickstart/put_file.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/examples/quickstart/put_file.py 2018-05-19 00:47:45.000000000 +0000 @@ -0,0 +1,17 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vi:ts=4:et + +import pycurl + +c = pycurl.Curl() +c.setopt(c.URL, 'https://httpbin.org/put') + +c.setopt(c.UPLOAD, 1) +file = open(__file__) +c.setopt(c.READDATA, file) + +c.perform() +c.close() +# File must be kept open while Curl object is using it +file.close() diff -Nru pycurl-7.43.0.1/examples/smtp.py pycurl-7.43.0.2/examples/smtp.py --- pycurl-7.43.0.1/examples/smtp.py 2017-12-03 19:03:03.000000000 +0000 +++ pycurl-7.43.0.2/examples/smtp.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # https://github.com/bagder/curl/blob/master/docs/examples/smtp-mail.c # There are other SMTP examples in that directory that you may find helpful. +from . import localhost import pycurl try: from io import BytesIO @@ -11,7 +12,7 @@ PY3 = sys.version_info[0] > 2 -mail_server = 'smtp://localhost' +mail_server = 'smtp://%s' % localhost mail_from = 'sender@example.org' mail_to = 'addressee@example.net' diff -Nru pycurl-7.43.0.1/INSTALL.rst pycurl-7.43.0.2/INSTALL.rst --- pycurl-7.43.0.1/INSTALL.rst 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/INSTALL.rst 2018-05-18 19:53:25.000000000 +0000 @@ -149,7 +149,6 @@ Currently official PycURL packages are built against the following Python versions: -- 2.6.6 - 2.7.10 - 3.2.5 - 3.3.5 @@ -273,7 +272,7 @@ ``winbuild.py`` assumes all programs are installed in their default locations, if this is not the case edit it as needed. ``winbuild.py`` itself can be run -with any Python it supports - 2.6, 2.7 or 3.2 through 3.5. +with any Python it supports - 2.7 or 3.2 through 3.6. .. _`download area`: https://dl.bintray.com/pycurl/pycurl/ diff -Nru pycurl-7.43.0.1/Makefile pycurl-7.43.0.2/Makefile --- pycurl-7.43.0.1/Makefile 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/Makefile 2018-05-23 05:08:32.000000000 +0000 @@ -35,8 +35,9 @@ # src/module.c is first because it declares global variables # which other files reference; important for single source build -SOURCES = src/module.c src/easy.c src/multi.c src/oscompat.c src/pythoncompat.c \ - src/share.c src/stringcompat.c src/threadsupport.c +SOURCES = src/easy.c src/easycb.c src/easyinfo.c src/easyopt.c src/easyperform.c \ + src/module.c src/multi.c src/oscompat.c src/pythoncompat.c \ + src/share.c src/stringcompat.c src/threadsupport.c src/util.c GEN_SOURCES = src/docstrings.c src/docstrings.h @@ -48,7 +49,9 @@ doc/docstrings/curl.rst \ doc/docstrings/curl_close.rst \ doc/docstrings/curl_errstr.rst \ + doc/docstrings/curl_errstr_raw.rst \ doc/docstrings/curl_getinfo.rst \ + doc/docstrings/curl_getinfo_raw.rst \ doc/docstrings/curl_pause.rst \ doc/docstrings/curl_perform.rst \ doc/docstrings/curl_reset.rst \ diff -Nru pycurl-7.43.0.1/MANIFEST.in pycurl-7.43.0.2/MANIFEST.in --- pycurl-7.43.0.1/MANIFEST.in 2017-12-03 19:03:03.000000000 +0000 +++ pycurl-7.43.0.2/MANIFEST.in 2018-05-23 05:08:32.000000000 +0000 @@ -23,6 +23,10 @@ include src/docstrings.c include src/docstrings.h include src/easy.c +include src/easycb.c +include src/easyinfo.c +include src/easyopt.c +include src/easyperform.c include src/module.c include src/multi.c include src/oscompat.c @@ -31,6 +35,7 @@ include src/share.c include src/stringcompat.c include src/threadsupport.c +include src/util.c include python/curl/*.py include requirements*.txt include tests/*.py diff -Nru pycurl-7.43.0.1/PKG-INFO pycurl-7.43.0.2/PKG-INFO --- pycurl-7.43.0.1/PKG-INFO 2017-12-07 08:21:56.000000000 +0000 +++ pycurl-7.43.0.2/PKG-INFO 2018-06-02 04:59:34.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pycurl -Version: 7.43.0.1 +Version: 7.43.0.2 Summary: PycURL -- A Python Interface To The cURL library Home-page: http://pycurl.io/ Author: Oleg Pudeyev @@ -34,7 +34,7 @@ Requirements ------------ - - Python 2.6, 2.7 or 3.1 through 3.6. + - Python 2.7 or 3.4 through 3.6. - libcurl 7.19.0 or better. @@ -98,11 +98,8 @@ Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.2 -Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 diff -Nru pycurl-7.43.0.1/pycurl.egg-info/dependency_links.txt pycurl-7.43.0.2/pycurl.egg-info/dependency_links.txt --- pycurl-7.43.0.1/pycurl.egg-info/dependency_links.txt 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/pycurl.egg-info/dependency_links.txt 2018-06-02 04:59:34.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru pycurl-7.43.0.1/pycurl.egg-info/PKG-INFO pycurl-7.43.0.2/pycurl.egg-info/PKG-INFO --- pycurl-7.43.0.1/pycurl.egg-info/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/pycurl.egg-info/PKG-INFO 2018-06-02 04:59:34.000000000 +0000 @@ -0,0 +1,107 @@ +Metadata-Version: 1.1 +Name: pycurl +Version: 7.43.0.2 +Summary: PycURL -- A Python Interface To The cURL library +Home-page: http://pycurl.io/ +Author: Oleg Pudeyev +Author-email: oleg@bsdpower.com +License: LGPL/MIT +Description: PycURL -- A Python Interface To The cURL library + ================================================ + + PycURL is a Python interface to `libcurl`_, the multiprotocol file + transfer library. Similarly to the urllib_ Python module, + PycURL can be used to fetch objects identified by a URL from a Python program. + Beyond simple fetches however PycURL exposes most of the functionality of + libcurl, including: + + - Speed - libcurl is very fast and PycURL, being a thin wrapper above + libcurl, is very fast as well. PycURL `was benchmarked`_ to be several + times faster than requests_. + - Features including multiple protocol support, SSL, authentication and + proxy options. PycURL supports most of libcurl's callbacks. + - Multi_ and share_ interfaces. + - Sockets used for network operations, permitting integration of PycURL + into the application's I/O loop (e.g., using Tornado_). + + .. _was benchmarked: http://stackoverflow.com/questions/15461995/python-requests-vs-pycurl-performance + .. _requests: http://python-requests.org/ + .. _Multi: https://curl.haxx.se/libcurl/c/libcurl-multi.html + .. _share: https://curl.haxx.se/libcurl/c/libcurl-share.html + .. _Tornado: http://www.tornadoweb.org/ + + + Requirements + ------------ + + - Python 2.7 or 3.4 through 3.6. + - libcurl 7.19.0 or better. + + + Installation + ------------ + + Download source and binary distributions from `PyPI`_ or `Bintray`_. + Binary wheels are now available for 32 and 64 bit Windows versions. + + Please see `the installation documentation`_ for installation instructions. + + .. _PyPI: https://pypi.python.org/pypi/pycurl + .. _Bintray: https://dl.bintray.com/pycurl/pycurl/ + .. _the installation documentation: http://pycurl.io/docs/latest/install.html + + + Documentation + ------------- + + Documentation for the most recent PycURL release is available on + `PycURL website `_. + + + Support + ------- + + For support questions please use `curl-and-python mailing list`_. + `Mailing list archives`_ are available for your perusal as well. + + Although not an official support venue, `Stack Overflow`_ has been + popular with some PycURL users. + + Bugs can be reported `via GitHub`_. Please use GitHub only for bug + reports and direct questions to our mailing list instead. + + .. _curl-and-python mailing list: http://cool.haxx.se/mailman/listinfo/curl-and-python + .. _Stack Overflow: http://stackoverflow.com/questions/tagged/pycurl + .. _Mailing list archives: https://curl.haxx.se/mail/list.cgi?list=curl-and-python + .. _via GitHub: https://github.com/pycurl/pycurl/issues + + + License + ------- + + PycURL is dual licensed under the LGPL and an MIT/X derivative license + based on the libcurl license. The complete text of the licenses is available + in COPYING-LGPL_ and COPYING-MIT_ files in the source distribution. + + .. _libcurl: https://curl.haxx.se/libcurl/ + .. _urllib: http://docs.python.org/library/urllib.html + .. _COPYING-LGPL: https://raw.githubusercontent.com/pycurl/pycurl/master/COPYING-LGPL + .. _COPYING-MIT: https://raw.githubusercontent.com/pycurl/pycurl/master/COPYING-MIT + +Keywords: curl,libcurl,urllib,wget,download,file transfer,http,www +Platform: All +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Internet :: File Transfer Protocol (FTP) +Classifier: Topic :: Internet :: WWW/HTTP diff -Nru pycurl-7.43.0.1/pycurl.egg-info/SOURCES.txt pycurl-7.43.0.2/pycurl.egg-info/SOURCES.txt --- pycurl-7.43.0.1/pycurl.egg-info/SOURCES.txt 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/pycurl.egg-info/SOURCES.txt 2018-06-02 04:59:34.000000000 +0000 @@ -0,0 +1,215 @@ +AUTHORS +COPYING-LGPL +COPYING-MIT +ChangeLog +INSTALL.rst +MANIFEST.in +Makefile +README.rst +RELEASE-NOTES.rst +requirements-dev.txt +setup.py +winbuild.py +doc/callbacks.rst +doc/conf.py +doc/curl.rst +doc/curlmultiobject.rst +doc/curlobject.rst +doc/curlshareobject.rst +doc/files.rst +doc/index.rst +doc/install.rst +doc/internals.rst +doc/pycurl.rst +doc/quickstart.rst +doc/release-notes.rst +doc/release-process.rst +doc/thread-safety.rst +doc/troubleshooting.rst +doc/unicode.rst +doc/unimplemented.rst +doc/docstrings/curl.rst +doc/docstrings/curl_close.rst +doc/docstrings/curl_errstr.rst +doc/docstrings/curl_errstr_raw.rst +doc/docstrings/curl_getinfo.rst +doc/docstrings/curl_getinfo_raw.rst +doc/docstrings/curl_pause.rst +doc/docstrings/curl_perform.rst +doc/docstrings/curl_perform_rb.rst +doc/docstrings/curl_perform_rs.rst +doc/docstrings/curl_reset.rst +doc/docstrings/curl_set_ca_certs.rst +doc/docstrings/curl_setopt.rst +doc/docstrings/curl_setopt_string.rst +doc/docstrings/curl_unsetopt.rst +doc/docstrings/multi.rst +doc/docstrings/multi_add_handle.rst +doc/docstrings/multi_assign.rst +doc/docstrings/multi_close.rst +doc/docstrings/multi_fdset.rst +doc/docstrings/multi_info_read.rst +doc/docstrings/multi_perform.rst +doc/docstrings/multi_remove_handle.rst +doc/docstrings/multi_select.rst +doc/docstrings/multi_setopt.rst +doc/docstrings/multi_socket_action.rst +doc/docstrings/multi_socket_all.rst +doc/docstrings/multi_timeout.rst +doc/docstrings/pycurl_global_cleanup.rst +doc/docstrings/pycurl_global_init.rst +doc/docstrings/pycurl_module.rst +doc/docstrings/pycurl_version_info.rst +doc/docstrings/share.rst +doc/docstrings/share_close.rst +doc/docstrings/share_setopt.rst +doc/static/favicon.ico +examples/basicfirst.py +examples/file_upload.py +examples/linksys.py +examples/opensocketexception.py +examples/retriever-multi.py +examples/retriever.py +examples/sfquery.py +examples/smtp.py +examples/ssh_keyfunction.py +examples/xmlrpc_curl.py +examples/quickstart/file_upload_buffer.py +examples/quickstart/file_upload_real.py +examples/quickstart/file_upload_real_fancy.py +examples/quickstart/follow_redirect.py +examples/quickstart/form_post.py +examples/quickstart/get.py +examples/quickstart/get_python2.py +examples/quickstart/get_python2_https.py +examples/quickstart/get_python3.py +examples/quickstart/get_python3_https.py +examples/quickstart/put_buffer.py +examples/quickstart/put_file.py +examples/quickstart/response_headers.py +examples/quickstart/response_info.py +examples/quickstart/write_file.py +examples/tests/test_build_config.py +examples/tests/test_gtk.py +examples/tests/test_xmlrpc.py +pycurl.egg-info/PKG-INFO +pycurl.egg-info/SOURCES.txt +pycurl.egg-info/dependency_links.txt +pycurl.egg-info/top_level.txt +python/curl/__init__.py +src/docstrings.c +src/docstrings.h +src/easy.c +src/easycb.c +src/easyinfo.c +src/easyopt.c +src/easyperform.c +src/module.c +src/multi.c +src/oscompat.c +src/pycurl.h +src/pythoncompat.c +src/share.c +src/stringcompat.c +src/threadsupport.c +src/util.c +tests/__init__.py +tests/app.py +tests/appmanager.py +tests/cadata_test.py +tests/certinfo_test.py +tests/close_socket_cb_test.py +tests/curl_object_test.py +tests/debug_test.py +tests/default_write_cb_test.py +tests/error_constants_test.py +tests/error_test.py +tests/failonerror_test.py +tests/ftp_test.py +tests/getinfo_test.py +tests/global_init_test.py +tests/header_cb_test.py +tests/header_test.py +tests/high_level_curl_test.py +tests/info_constants_test.py +tests/internals_test.py +tests/matrix.py +tests/memory_mgmt_test.py +tests/multi_memory_mgmt_test.py +tests/multi_option_constants_test.py +tests/multi_socket_select_test.py +tests/multi_socket_test.py +tests/multi_test.py +tests/multi_timer_test.py +tests/open_socket_cb_test.py +tests/option_constants_test.py +tests/pause_test.py +tests/perform_test.py +tests/post_test.py +tests/procmgr.py +tests/protocol_constants_test.py +tests/read_cb_test.py +tests/readdata_test.py +tests/relative_url_test.py +tests/reload_test.py +tests/reset_test.py +tests/resolve_test.py +tests/run-quickstart.sh +tests/run.sh +tests/runwsgi.py +tests/seek_cb_constants_test.py +tests/seek_cb_test.py +tests/setopt_lifecycle_test.py +tests/setopt_string_test.py +tests/setopt_test.py +tests/setopt_unicode_test.py +tests/setup_test.py +tests/share_test.py +tests/sockopt_cb_test.py +tests/ssh_key_cb_test.py +tests/unset_range_test.py +tests/user_agent_string_test.py +tests/util.py +tests/version_comparison_test.py +tests/version_constants_test.py +tests/version_test.py +tests/vsftpd.conf +tests/weakref_test.py +tests/write_abort_test.py +tests/write_cb_bogus_test.py +tests/write_test.py +tests/write_to_stringio_test.py +tests/xferinfo_cb_test.py +tests/certs/ca.crt +tests/certs/ca.key +tests/certs/server.crt +tests/certs/server.key +tests/ext/test-lib.sh +tests/ext/test-suite.sh +tests/fake-curl/curl-config-empty +tests/fake-curl/curl-config-libs-and-static-libs +tests/fake-curl/curl-config-ssl-feature-only +tests/fake-curl/curl-config-ssl-in-libs +tests/fake-curl/curl-config-ssl-in-static-libs +tests/fake-curl/libcurl/Makefile +tests/fake-curl/libcurl/with_gnutls.c +tests/fake-curl/libcurl/with_gnutls.so +tests/fake-curl/libcurl/with_nss.c +tests/fake-curl/libcurl/with_nss.so +tests/fake-curl/libcurl/with_openssl.c +tests/fake-curl/libcurl/with_openssl.so +tests/fake-curl/libcurl/with_unknown_ssl.c +tests/fake-curl/libcurl/with_unknown_ssl.so +tests/fake-curl/libcurl/without_ssl.c +tests/fake-curl/libcurl/without_ssl.so +tests/fixtures/form_submission.txt +tests/matrix/curl-7.19.0-sslv2-2b0e09b0f98.patch +tests/matrix/curl-7.19.0-sslv2-c66b0b32fba-modified.patch +tests/matrix/openssl-1.0.1e-fix_pod_syntax-1.patch +winbuild/c-ares-vs2015.patch +winbuild/libcurl-fix-zlib-references.patch +winbuild/libssh2-vs2015.patch +winbuild/openssl-fix-crt-1.0.2.patch +winbuild/openssl-fix-crt-1.1.0.patch +winbuild/vcvars-vc14-32.sh +winbuild/vcvars-vc14-64.sh \ No newline at end of file diff -Nru pycurl-7.43.0.1/pycurl.egg-info/top_level.txt pycurl-7.43.0.2/pycurl.egg-info/top_level.txt --- pycurl-7.43.0.1/pycurl.egg-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/pycurl.egg-info/top_level.txt 2018-06-02 04:59:34.000000000 +0000 @@ -0,0 +1,2 @@ +curl +pycurl diff -Nru pycurl-7.43.0.1/README.rst pycurl-7.43.0.2/README.rst --- pycurl-7.43.0.1/README.rst 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/README.rst 2018-06-02 04:59:02.000000000 +0000 @@ -30,7 +30,7 @@ Requirements ------------ -- Python 2.6, 2.7 or 3.1 through 3.6. +- Python 2.7 or 3.4 through 3.6. - libcurl 7.19.0 or better. @@ -174,7 +174,7 @@ Copyright (C) 2001-2008 by Kjetil Jacobsen Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer - Copyright (C) 2013-2017 by Oleg Pudeyev + Copyright (C) 2013-2018 by Oleg Pudeyev All rights reserved. diff -Nru pycurl-7.43.0.1/RELEASE-NOTES.rst pycurl-7.43.0.2/RELEASE-NOTES.rst --- pycurl-7.43.0.1/RELEASE-NOTES.rst 2017-12-07 07:42:28.000000000 +0000 +++ pycurl-7.43.0.2/RELEASE-NOTES.rst 2018-06-02 04:59:02.000000000 +0000 @@ -1,6 +1,38 @@ Release Notes ============= +PycURL 7.43.0.2 - 2018-06-02 +---------------------------- + +Highlights of this release: + +1. Experimental perform_rs and perform_rb methods have been added to Curl + objects. They return response body as a string and a byte string, + respectively. The goal of these methods is to improve PycURL's usability + for typical use cases, specifically removing the need to set up + StringIO/BytesIO objects to store the response body. + +2. getinfo_raw and errstr_raw methods have been added to Curl objects to + return transfer information as byte strings, permitting applications to + retrieve transfer information that is not decodable using Python's + default encoding. + +3. errstr and "fail or error" exceptions now replace undecodable bytes + so as to provide usable strings; use errstr_raw to retrieve original + byte strings. + +4. There is no longer a need to keep references to Curl objects when they + are used in CurlMulti objects - PycURL now maintains such references + internally. + +5. Official Windows builds now include HTTP/2 and international domain + name support. + +6. PycURL now officially supports BoringSSL. + +7. A number of smaller improvements have been made and bugs fixed. + + PycURL 7.43.0.1 - 2017-12-07 ---------------------------- diff -Nru pycurl-7.43.0.1/requirements-dev-3.1.txt pycurl-7.43.0.2/requirements-dev-3.1.txt --- pycurl-7.43.0.1/requirements-dev-3.1.txt 2017-12-03 19:03:13.000000000 +0000 +++ pycurl-7.43.0.2/requirements-dev-3.1.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -bottle -# nose 1.3.1 is broken on python 3: -# https://github.com/nose-devs/nose/issues/780 -nose>=1.3.2 -# flaky 2.2.0 is not installable on python 3.1.5, -# install it manually -pyflakes -nose-show-skipped diff -Nru pycurl-7.43.0.1/requirements-dev.txt pycurl-7.43.0.2/requirements-dev.txt --- pycurl-7.43.0.1/requirements-dev.txt 2017-12-07 08:20:49.000000000 +0000 +++ pycurl-7.43.0.2/requirements-dev.txt 2018-05-18 19:53:25.000000000 +0000 @@ -5,6 +5,4 @@ flaky pyflakes nose-show-skipped -# for python 2.6 -unittest2 sphinx \ No newline at end of file diff -Nru pycurl-7.43.0.1/setup.cfg pycurl-7.43.0.2/setup.cfg --- pycurl-7.43.0.1/setup.cfg 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/setup.cfg 2018-06-02 04:59:34.000000000 +0000 @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 + diff -Nru pycurl-7.43.0.1/setup.py pycurl-7.43.0.2/setup.py --- pycurl-7.43.0.1/setup.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/setup.py 2018-06-02 04:59:02.000000000 +0000 @@ -6,11 +6,16 @@ PACKAGE = "pycurl" PY_PACKAGE = "curl" -VERSION = "7.43.0.1" +VERSION = "7.43.0.2" import glob, os, re, sys, subprocess import distutils -from distutils.core import setup +try: + import wheel + if wheel: + from setuptools import setup +except ImportError: + from distutils.core import setup from distutils.extension import Extension from distutils.util import split_quoted from distutils.version import LooseVersion @@ -54,6 +59,28 @@ return p +def scan_argvs(argv, s): + p = [] + i = 1 + while i < len(argv): + arg = argv[i] + if str.find(arg, s) == 0: + if s.endswith('='): + # --option=value + p.append(arg[len(s):]) + if s != '--openssl-lib-name=': + assert p[-1], arg + else: + # --option + # set value to True + raise Exception('specification must end with =') + del argv[i] + else: + i = i + 1 + ##print argv + return p + + class ExtensionConfiguration(object): def __init__(self, argv=[]): # we mutate argv, this is necessary because @@ -91,16 +118,108 @@ elif fatal: fail("FATAL: bad directory %s in environment variable %s" % (dir, envvar)) + def detect_features(self): + p = subprocess.Popen((self.curl_config(), '--features'), + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if p.wait() != 0: + msg = "Problem running `%s' --features" % self.curl_config() + if stderr: + msg += ":\n" + stderr.decode() + raise ConfigurationError(msg) + curl_has_ssl = False + for feature in split_quoted(stdout.decode()): + if feature == 'SSL': + # this means any ssl library, not just openssl. + # we set the ssl flag to check for ssl library mismatch + # at link time and run time + self.define_macros.append(('HAVE_CURL_SSL', 1)) + curl_has_ssl = True + self.curl_has_ssl = curl_has_ssl + + def ssl_options(self): + return { + '--with-openssl': self.using_openssl, + '--with-ssl': self.using_openssl, + '--with-gnutls': self.using_gnutls, + '--with-nss': self.using_nss, + } + + def detect_ssl_option(self): + for option in self.ssl_options(): + if scan_argv(self.argv, option) is not None: + for other_option in self.ssl_options(): + if option != other_option: + if scan_argv(self.argv, other_option) is not None: + raise ConfigurationError('Cannot give both %s and %s' % (option, other_option)) + + return option + + def detect_ssl_backend(self): + ssl_lib_detected = False + + if 'PYCURL_SSL_LIBRARY' in os.environ: + ssl_lib = os.environ['PYCURL_SSL_LIBRARY'] + if ssl_lib in ['openssl', 'gnutls', 'nss']: + ssl_lib_detected = True + getattr(self, 'using_%s' % ssl_lib)() + else: + raise ConfigurationError('Invalid value "%s" for PYCURL_SSL_LIBRARY' % ssl_lib) + + option = self.detect_ssl_option() + if option: + ssl_lib_detected = True + self.ssl_options()[option]() + + # ssl detection - ssl libraries are added + if not ssl_lib_detected: + libcurl_dll_path = scan_argv(self.argv, "--libcurl-dll=") + if libcurl_dll_path is not None: + if self.detect_ssl_lib_from_libcurl_dll(libcurl_dll_path): + ssl_lib_detected = True + + if not ssl_lib_detected: + # self.sslhintbuf is a hack + for arg in split_quoted(self.sslhintbuf): + if arg[:2] == "-l": + if arg[2:] == 'ssl': + self.using_openssl() + ssl_lib_detected = True + break + if arg[2:] == 'gnutls': + self.using_gnutls() + ssl_lib_detected = True + break + if arg[2:] == 'ssl3': + self.using_nss() + ssl_lib_detected = True + break + + if not ssl_lib_detected and len(self.argv) == len(self.original_argv) \ + and not os.environ.get('PYCURL_CURL_CONFIG') \ + and not os.environ.get('PYCURL_SSL_LIBRARY'): + # this path should only be taken when no options or + # configuration environment variables are given to setup.py + ssl_lib_detected = self.detect_ssl_lib_on_centos6() + + self.ssl_lib_detected = ssl_lib_detected + + def curl_config(self): + try: + return self._curl_config + except AttributeError: + curl_config = os.environ.get('PYCURL_CURL_CONFIG', "curl-config") + curl_config = scan_argv(self.argv, "--curl-config=", curl_config) + self._curl_config = curl_config + return curl_config def configure_unix(self): OPENSSL_DIR = scan_argv(self.argv, "--openssl-dir=") if OPENSSL_DIR is not None: self.include_dirs.append(os.path.join(OPENSSL_DIR, "include")) self.library_dirs.append(os.path.join(OPENSSL_DIR, "lib")) - CURL_CONFIG = os.environ.get('PYCURL_CURL_CONFIG', "curl-config") - CURL_CONFIG = scan_argv(self.argv, "--curl-config=", CURL_CONFIG) try: - p = subprocess.Popen((CURL_CONFIG, '--version'), + p = subprocess.Popen((self.curl_config(), '--version'), stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError: exc = sys.exc_info()[1] @@ -108,17 +227,17 @@ raise ConfigurationError(msg) stdout, stderr = p.communicate() if p.wait() != 0: - msg = "`%s' not found -- please install the libcurl development files or specify --curl-config=/path/to/curl-config" % CURL_CONFIG + msg = "`%s' not found -- please install the libcurl development files or specify --curl-config=/path/to/curl-config" % self.curl_config() if stderr: msg += ":\n" + stderr.decode() raise ConfigurationError(msg) libcurl_version = stdout.decode().strip() - print("Using %s (%s)" % (CURL_CONFIG, libcurl_version)) - p = subprocess.Popen((CURL_CONFIG, '--cflags'), + print("Using %s (%s)" % (self.curl_config(), libcurl_version)) + p = subprocess.Popen((self.curl_config(), '--cflags'), stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if p.wait() != 0: - msg = "Problem running `%s' --cflags" % CURL_CONFIG + msg = "Problem running `%s' --cflags" % self.curl_config() if stderr: msg += ":\n" + stderr.decode() raise ConfigurationError(msg) @@ -156,7 +275,7 @@ sslhintbuf = '' errtext = '' for option in ["--libs", "--static-libs"]: - p = subprocess.Popen((CURL_CONFIG, option), + p = subprocess.Popen((self.curl_config(), option), stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if p.wait() == 0: @@ -182,30 +301,22 @@ if errtext: msg += ":\n" + errtext raise ConfigurationError(msg) - - ssl_lib_detected = False - if 'PYCURL_SSL_LIBRARY' in os.environ: - ssl_lib = os.environ['PYCURL_SSL_LIBRARY'] - if ssl_lib in ['openssl', 'gnutls', 'nss']: - ssl_lib_detected = True - getattr(self, 'using_%s' % ssl_lib)() - else: - raise ConfigurationError('Invalid value "%s" for PYCURL_SSL_LIBRARY' % ssl_lib) - ssl_options = { - '--with-openssl': self.using_openssl, - '--with-ssl': self.using_openssl, - '--with-gnutls': self.using_gnutls, - '--with-nss': self.using_nss, - } - for option in ssl_options: - if scan_argv(self.argv, option) is not None: - for other_option in ssl_options: - if option != other_option: - if scan_argv(self.argv, other_option) is not None: - raise ConfigurationError('Cannot give both %s and %s' % (option, other_option)) - ssl_lib_detected = True - ssl_options[option]() - break + + # hack + self.sslhintbuf = sslhintbuf + + self.detect_features() + if self.curl_has_ssl: + self.detect_ssl_backend() + + if not self.ssl_lib_detected: + raise ConfigurationError('''\ +Curl is configured to use SSL, but we have not been able to determine \ +which SSL backend it is using. Please see PycURL documentation for how to \ +specify the SSL backend manually.''') + else: + if self.detect_ssl_option(): + sys.stderr.write("Warning: SSL backend specified manually but libcurl does not use SSL\n") # libraries and options - all libraries and options are forwarded # but if --libs succeeded, --static-libs output is ignored @@ -216,52 +327,7 @@ self.library_dirs.append(arg[2:]) else: self.extra_link_args.append(arg) - - # ssl detection - ssl libraries are added - if not ssl_lib_detected: - libcurl_dll_path = scan_argv(self.argv, "--libcurl-dll=") - if libcurl_dll_path is not None: - if self.detect_ssl_lib_from_libcurl_dll(libcurl_dll_path): - ssl_lib_detected = True - - if not ssl_lib_detected: - for arg in split_quoted(sslhintbuf): - if arg[:2] == "-l": - if arg[2:] == 'ssl': - self.using_openssl() - ssl_lib_detected = True - break - if arg[2:] == 'gnutls': - self.using_gnutls() - ssl_lib_detected = True - break - if arg[2:] == 'ssl3': - self.using_nss() - ssl_lib_detected = True - break - - if not ssl_lib_detected and len(self.argv) == len(self.original_argv) \ - and not os.environ.get('PYCURL_CURL_CONFIG') \ - and not os.environ.get('PYCURL_SSL_LIBRARY'): - # this path should only be taken when no options or - # configuration environment variables are given to setup.py - ssl_lib_detected = self.detect_ssl_lib_on_centos6() - - if not ssl_lib_detected: - p = subprocess.Popen((CURL_CONFIG, '--features'), - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = p.communicate() - if p.wait() != 0: - msg = "Problem running `%s' --features" % CURL_CONFIG - if stderr: - msg += ":\n" + stderr.decode() - raise ConfigurationError(msg) - for feature in split_quoted(stdout.decode()): - if feature == 'SSL': - # this means any ssl library, not just openssl. - # we set the ssl flag to check for ssl library mismatch - # at link time and run time - self.define_macros.append(('HAVE_CURL_SSL', 1)) + if not self.libraries: self.libraries.append("curl") @@ -329,6 +395,9 @@ # meaning the correct usage of this option is --openssl-lib-name="" self.openssl_lib_name = scan_argv(self.argv, '--openssl-lib-name=', 'libeay32.lib') + for lib in scan_argvs(self.argv, '--link-arg='): + self.extra_link_args.append(lib) + if scan_argv(self.argv, "--use-libcurl-dll") is not None: libcurl_lib_path = os.path.join(curl_dir, "lib", curl_lib_name) self.extra_link_args.extend(["ws2_32.lib"]) @@ -489,6 +558,10 @@ sources = [ os.path.join("src", "docstrings.c"), os.path.join("src", "easy.c"), + os.path.join("src", "easycb.c"), + os.path.join("src", "easyinfo.c"), + os.path.join("src", "easyopt.c"), + os.path.join("src", "easyperform.c"), os.path.join("src", "module.c"), os.path.join("src", "multi.c"), os.path.join("src", "oscompat.c"), @@ -496,6 +569,7 @@ os.path.join("src", "share.c"), os.path.join("src", "stringcompat.c"), os.path.join("src", "threadsupport.c"), + os.path.join("src", "util.c"), ] depends = [ os.path.join("src", "pycurl.h"), @@ -696,7 +770,7 @@ Requirements ------------ -- Python 2.6, 2.7 or 3.1 through 3.6. +- Python 2.7 or 3.4 through 3.6. - libcurl 7.19.0 or better. @@ -767,11 +841,8 @@ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', @@ -810,6 +881,7 @@ --libcurl-lib-name=libcurl_imp.lib override libcurl import library name --with-openssl libcurl is linked against OpenSSL --with-ssl legacy alias for --with-openssl + --link-arg=foo.lib also link against specified library ''' if __name__ == "__main__": diff -Nru pycurl-7.43.0.1/src/docstrings.c pycurl-7.43.0.2/src/docstrings.c --- pycurl-7.43.0.1/src/docstrings.c 2017-12-07 07:27:16.000000000 +0000 +++ pycurl-7.43.0.2/src/docstrings.c 2018-06-02 04:59:23.000000000 +0000 @@ -28,38 +28,95 @@ \n\ Return the internal libcurl error buffer of this handle as a string.\n\ \n\ -Return value is a ``str`` instance on all Python versions."; +Return value is a ``str`` instance on all Python versions.\n\ +On Python 3, error buffer data is decoded using Python's default encoding\n\ +at the time of the call. If this decoding fails, ``UnicodeDecodeError`` is\n\ +raised. Use :ref:`errstr_raw ` to retrieve the error buffer\n\ +as a byte string in this case.\n\ +\n\ +On Python 2, ``errstr`` and ``errstr_raw`` behave identically."; -PYCURL_INTERNAL const char curl_getinfo_doc[] = "getinfo(info) -> Result\n\ +PYCURL_INTERNAL const char curl_errstr_raw_doc[] = "errstr_raw() -> byte string\n\ +\n\ +Return the internal libcurl error buffer of this handle as a byte string.\n\ +\n\ +Return value is a ``str`` instance on Python 2 and ``bytes`` instance\n\ +on Python 3. Unlike :ref:`errstr_raw `, ``errstr_raw``\n\ +allows reading libcurl error buffer in Python 3 when its contents is not\n\ +valid in Python's default encoding.\n\ \n\ -Extract and return information from a curl session.\n\ +On Python 2, ``errstr`` and ``errstr_raw`` behave identically.\n\ \n\ -Corresponds to `curl_easy_getinfo`_ in libcurl, where *option* is\n\ -the same as the ``CURLINFO_*`` constants in libcurl, except that the\n\ -``CURLINFO_`` prefix has been removed. (See below for exceptions.)\n\ -*Result* contains an integer, float or string, depending on which\n\ -option is given. The ``getinfo`` method should not be called unless\n\ +*Added in version 7.43.0.2.*"; + +PYCURL_INTERNAL const char curl_getinfo_doc[] = "getinfo(option) -> Result\n\ +\n\ +Extract and return information from a curl session,\n\ +decoding string data in Python's default encoding at the time of the call.\n\ +Corresponds to `curl_easy_getinfo`_ in libcurl.\n\ +The ``getinfo`` method should not be called unless\n\ ``perform`` has been called and finished.\n\ \n\ -In order to distinguish between similarly-named CURLOPT and CURLINFO\n\ -constants, some have ``OPT_`` and ``INFO_`` prefixes. These are\n\ -``INFO_FILETIME``, ``OPT_FILETIME``, ``INFO_COOKIELIST`` (but ``setopt`` uses\n\ -``COOKIELIST``!), ``INFO_CERTINFO``, and ``OPT_CERTINFO``.\n\ -\n\ -The value returned by ``getinfo(INFO_CERTINFO)`` is a list with one element\n\ -per certificate in the chain, starting with the leaf; each element is a\n\ -sequence of *(key, value)* tuples.\n\ +*option* is a constant corresponding to one of the\n\ +``CURLINFO_*`` constants in libcurl. Most option constant names match\n\ +the respective ``CURLINFO_*`` constant names with the ``CURLINFO_`` prefix\n\ +removed, for example ``CURLINFO_CONTENT_TYPE`` is accessible as\n\ +``pycurl.CONTENT_TYPE``. Exceptions to this rule are as follows:\n\ +\n\ +- ``CURLINFO_FILETIME`` is mapped as ``pycurl.INFO_FILETIME``\n\ +- ``CURLINFO_COOKIELIST`` is mapped as ``pycurl.INFO_COOKIELIST``\n\ +- ``CURLINFO_CERTINFO`` is mapped as ``pycurl.INFO_CERTINFO``\n\ +- ``CURLINFO_RTSP_CLIENT_CSEQ`` is mapped as ``pycurl.INFO_RTSP_CLIENT_CSEQ``\n\ +- ``CURLINFO_RTSP_CSEQ_RECV`` is mapped as ``pycurl.INFO_RTSP_CSEQ_RECV``\n\ +- ``CURLINFO_RTSP_SERVER_CSEQ`` is mapped as ``pycurl.INFO_RTSP_SERVER_CSEQ``\n\ +- ``CURLINFO_RTSP_SESSION_ID`` is mapped as ``pycurl.INFO_RTSP_SESSION_ID``\n\ +\n\ +The type of return value depends on the option, as follows:\n\ +\n\ +- Options documented by libcurl to return an integer value return a\n\ + Python integer (``long`` on Python 2, ``int`` on Python 3).\n\ +- Options documented by libcurl to return a floating point value\n\ + return a Python ``float``.\n\ +- Options documented by libcurl to return a string value\n\ + return a Python string (``str`` on Python 2 and Python 3).\n\ + On Python 2, the string contains whatever data libcurl returned.\n\ + On Python 3, the data returned by libcurl is decoded using the\n\ + default string encoding at the time of the call.\n\ + If the data cannot be decoded using the default encoding, ``UnicodeDecodeError``\n\ + is raised. Use :ref:`getinfo_raw `\n\ + to retrieve the data as ``bytes`` in these\n\ + cases.\n\ +- ``SSL_ENGINES`` and ``INFO_COOKIELIST`` return a list of strings.\n\ + The same encoding caveats apply; use :ref:`getinfo_raw `\n\ + to retrieve the\n\ + data as a list of byte strings.\n\ +- ``INFO_CERTINFO`` returns a list with one element\n\ + per certificate in the chain, starting with the leaf; each element is a\n\ + sequence of *(key, value)* tuples where both ``key`` and ``value`` are\n\ + strings. String encoding caveats apply; use :ref:`getinfo_raw `\n\ + to retrieve\n\ + certificate data as byte strings.\n\ +\n\ +On Python 2, ``getinfo`` and ``getinfo_raw`` behave identically.\n\ \n\ Example usage::\n\ \n\ import pycurl\n\ c = pycurl.Curl()\n\ + c.setopt(pycurl.OPT_CERTINFO, 1)\n\ c.setopt(pycurl.URL, \"https://python.org\")\n\ c.setopt(pycurl.FOLLOWLOCATION, 1)\n\ c.perform()\n\ - print c.getinfo(pycurl.HTTP_CODE), c.getinfo(pycurl.EFFECTIVE_URL)\n\ - ...\n\ - --> 200 \"https://www.python.org/\"\n\ + print(c.getinfo(pycurl.HTTP_CODE))\n\ + # --> 200\n\ + print(c.getinfo(pycurl.EFFECTIVE_URL))\n\ + # --> \"https://www.python.org/\"\n\ + certinfo = c.getinfo(pycurl.INFO_CERTINFO)\n\ + print(certinfo)\n\ + # --> [(('Subject', 'C = AU, ST = Some-State, O = PycURL test suite,\n\ + CN = localhost'), ('Issuer', 'C = AU, ST = Some-State,\n\ + O = PycURL test suite, OU = localhost, CN = localhost'),\n\ + ('Version', '0'), ...)]\n\ \n\ \n\ Raises pycurl.error exception upon failure.\n\ @@ -67,6 +124,77 @@ .. _curl_easy_getinfo:\n\ https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html"; +PYCURL_INTERNAL const char curl_getinfo_raw_doc[] = "getinfo_raw(option) -> Result\n\ +\n\ +Extract and return information from a curl session,\n\ +returning string data as byte strings.\n\ +Corresponds to `curl_easy_getinfo`_ in libcurl.\n\ +The ``getinfo_raw`` method should not be called unless\n\ +``perform`` has been called and finished.\n\ +\n\ +*option* is a constant corresponding to one of the\n\ +``CURLINFO_*`` constants in libcurl. Most option constant names match\n\ +the respective ``CURLINFO_*`` constant names with the ``CURLINFO_`` prefix\n\ +removed, for example ``CURLINFO_CONTENT_TYPE`` is accessible as\n\ +``pycurl.CONTENT_TYPE``. Exceptions to this rule are as follows:\n\ +\n\ +- ``CURLINFO_FILETIME`` is mapped as ``pycurl.INFO_FILETIME``\n\ +- ``CURLINFO_COOKIELIST`` is mapped as ``pycurl.INFO_COOKIELIST``\n\ +- ``CURLINFO_CERTINFO`` is mapped as ``pycurl.INFO_CERTINFO``\n\ +- ``CURLINFO_RTSP_CLIENT_CSEQ`` is mapped as ``pycurl.INFO_RTSP_CLIENT_CSEQ``\n\ +- ``CURLINFO_RTSP_CSEQ_RECV`` is mapped as ``pycurl.INFO_RTSP_CSEQ_RECV``\n\ +- ``CURLINFO_RTSP_SERVER_CSEQ`` is mapped as ``pycurl.INFO_RTSP_SERVER_CSEQ``\n\ +- ``CURLINFO_RTSP_SESSION_ID`` is mapped as ``pycurl.INFO_RTSP_SESSION_ID``\n\ +\n\ +The type of return value depends on the option, as follows:\n\ +\n\ +- Options documented by libcurl to return an integer value return a\n\ + Python integer (``long`` on Python 2, ``int`` on Python 3).\n\ +- Options documented by libcurl to return a floating point value\n\ + return a Python ``float``.\n\ +- Options documented by libcurl to return a string value\n\ + return a Python byte string (``str`` on Python 2, ``bytes`` on Python 3).\n\ + The string contains whatever data libcurl returned.\n\ + Use :ref:`getinfo ` to retrieve this data as a Unicode string on Python 3.\n\ +- ``SSL_ENGINES`` and ``INFO_COOKIELIST`` return a list of byte strings.\n\ + The same encoding caveats apply; use :ref:`getinfo ` to retrieve the\n\ + data as a list of potentially Unicode strings.\n\ +- ``INFO_CERTINFO`` returns a list with one element\n\ + per certificate in the chain, starting with the leaf; each element is a\n\ + sequence of *(key, value)* tuples where both ``key`` and ``value`` are\n\ + byte strings. String encoding caveats apply; use :ref:`getinfo `\n\ + to retrieve\n\ + certificate data as potentially Unicode strings.\n\ +\n\ +On Python 2, ``getinfo`` and ``getinfo_raw`` behave identically.\n\ +\n\ +Example usage::\n\ +\n\ + import pycurl\n\ + c = pycurl.Curl()\n\ + c.setopt(pycurl.OPT_CERTINFO, 1)\n\ + c.setopt(pycurl.URL, \"https://python.org\")\n\ + c.setopt(pycurl.FOLLOWLOCATION, 1)\n\ + c.perform()\n\ + print(c.getinfo_raw(pycurl.HTTP_CODE))\n\ + # --> 200\n\ + print(c.getinfo_raw(pycurl.EFFECTIVE_URL))\n\ + # --> b\"https://www.python.org/\"\n\ + certinfo = c.getinfo_raw(pycurl.INFO_CERTINFO)\n\ + print(certinfo)\n\ + # --> [((b'Subject', b'C = AU, ST = Some-State, O = PycURL test suite,\n\ + CN = localhost'), (b'Issuer', b'C = AU, ST = Some-State,\n\ + O = PycURL test suite, OU = localhost, CN = localhost'),\n\ + (b'Version', b'0'), ...)]\n\ +\n\ +\n\ +Raises pycurl.error exception upon failure.\n\ +\n\ +*Added in version 7.43.0.2.*\n\ +\n\ +.. _curl_easy_getinfo:\n\ + https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html"; + PYCURL_INTERNAL const char curl_pause_doc[] = "pause(bitmask) -> None\n\ \n\ Pause or unpause a curl handle. Bitmask should be a value such as\n\ @@ -91,6 +219,50 @@ .. _curl_easy_perform:\n\ https://curl.haxx.se/libcurl/c/curl_easy_perform.html"; +PYCURL_INTERNAL const char curl_perform_rb_doc[] = "perform_rb() -> response_body\n\ +\n\ +Perform a file transfer and return response body as a byte string.\n\ +\n\ +This method arranges for response body to be saved in a StringIO\n\ +(Python 2) or BytesIO (Python 3) instance, then invokes :ref:`perform `\n\ +to perform the file transfer, then returns the value of the StringIO/BytesIO\n\ +instance which is a ``str`` instance on Python 2 and ``bytes`` instance\n\ +on Python 3. Errors during transfer raise ``pycurl.error`` exceptions\n\ +just like in :ref:`perform `.\n\ +\n\ +Use :ref:`perform_rs ` to retrieve response body as a string\n\ +(``str`` instance on both Python 2 and 3).\n\ +\n\ +Raises ``pycurl.error`` exception upon failure.\n\ +\n\ +*Added in version 7.43.0.2.*"; + +PYCURL_INTERNAL const char curl_perform_rs_doc[] = "perform_rs() -> response_body\n\ +\n\ +Perform a file transfer and return response body as a string.\n\ +\n\ +On Python 2, this method arranges for response body to be saved in a StringIO\n\ +instance, then invokes :ref:`perform `\n\ +to perform the file transfer, then returns the value of the StringIO instance.\n\ +This behavior is identical to :ref:`perform_rb `.\n\ +\n\ +On Python 3, this method arranges for response body to be saved in a BytesIO\n\ +instance, then invokes :ref:`perform `\n\ +to perform the file transfer, then decodes the response body in Python's\n\ +default encoding and returns the decoded body as a Unicode string\n\ +(``str`` instance). *Note:* decoding happens after the transfer finishes,\n\ +thus an encoding error implies the transfer/network operation succeeded.\n\ +\n\ +Any transfer errors raise ``pycurl.error`` exception,\n\ +just like in :ref:`perform `.\n\ +\n\ +Use :ref:`perform_rb ` to retrieve response body as a byte\n\ +string (``bytes`` instance on Python 3) without attempting to decode it.\n\ +\n\ +Raises ``pycurl.error`` exception upon failure.\n\ +\n\ +*Added in version 7.43.0.2.*"; + PYCURL_INTERNAL const char curl_reset_doc[] = "reset() -> None\n\ \n\ Reset all options set on curl handle to default values, but preserves\n\ @@ -113,7 +285,8 @@ *option* specifies which option to set. PycURL defines constants\n\ corresponding to ``CURLOPT_*`` constants in libcurl, except that\n\ the ``CURLOPT_`` prefix is removed. For example, ``CURLOPT_URL`` is\n\ -exposed in PycURL as ``pycurl.URL``. For convenience, ``CURLOPT_*``\n\ +exposed in PycURL as ``pycurl.URL``, with some exceptions as detailed below.\n\ +For convenience, ``CURLOPT_*``\n\ constants are also exposed on the Curl objects themselves::\n\ \n\ import pycurl\n\ @@ -122,11 +295,18 @@ # Same as:\n\ c.setopt(c.URL, \"http://www.python.org/\")\n\ \n\ -In order to distinguish between similarly-named CURLOPT and CURLINFO\n\ -constants, some have CURLOPT constants have ``OPT_`` prefixes.\n\ -These are ``OPT_FILETIME`` and ``OPT_CERTINFO``.\n\ -As an exception to the exception, ``COOKIELIST`` does not have an ``OPT_``\n\ -prefix but the corresponding CURLINFO option is ``INFO_COOKIELIST``.\n\ +The following are exceptions to option constant naming convention:\n\ +\n\ +- ``CURLOPT_FILETIME`` is mapped as ``pycurl.OPT_FILETIME``\n\ +- ``CURLOPT_CERTINFO`` is mapped as ``pycurl.OPT_CERTINFO``\n\ +- ``CURLOPT_COOKIELIST`` is mapped as ``pycurl.COOKIELIST``\n\ + and, as of PycURL 7.43.0.2, also as ``pycurl.OPT_COOKIELIST``\n\ +- ``CURLOPT_RTSP_CLIENT_CSEQ`` is mapped as ``pycurl.OPT_RTSP_CLIENT_CSEQ``\n\ +- ``CURLOPT_RTSP_REQUEST`` is mapped as ``pycurl.OPT_RTSP_REQUEST``\n\ +- ``CURLOPT_RTSP_SERVER_CSEQ`` is mapped as ``pycurl.OPT_RTSP_SERVER_CSEQ``\n\ +- ``CURLOPT_RTSP_SESSION_ID`` is mapped as ``pycurl.OPT_RTSP_SESSION_ID``\n\ +- ``CURLOPT_RTSP_STREAM_URI`` is mapped as ``pycurl.OPT_RTSP_STREAM_URI``\n\ +- ``CURLOPT_RTSP_TRANSPORT`` is mapped as ``pycurl.OPT_RTSP_TRANSPORT``\n\ \n\ *value* specifies the value to set the option to. Different options accept\n\ values of different types:\n\ @@ -267,9 +447,10 @@ Corresponds to `curl_multi_add_handle`_ in libcurl. This method adds an\n\ existing and valid Curl object to the CurlMulti object.\n\ \n\ -IMPORTANT NOTE: add_handle does not implicitly add a Python reference to the\n\ -Curl object (and thus does not increase the reference count on the Curl\n\ -object).\n\ +*Changed in version 7.43.0.2:* add_handle now ensures that the Curl object\n\ +is not garbage collected while it is being used by a CurlMulti object.\n\ +Previously application had to maintain an outstanding reference to the Curl\n\ +object to keep it from being garbage collected.\n\ \n\ .. _curl_multi_add_handle:\n\ https://curl.haxx.se/libcurl/c/curl_multi_add_handle.html"; diff -Nru pycurl-7.43.0.1/src/docstrings.h pycurl-7.43.0.2/src/docstrings.h --- pycurl-7.43.0.1/src/docstrings.h 2017-12-07 07:27:16.000000000 +0000 +++ pycurl-7.43.0.2/src/docstrings.h 2018-06-02 04:59:23.000000000 +0000 @@ -4,9 +4,13 @@ extern const char curl_doc[]; extern const char curl_close_doc[]; extern const char curl_errstr_doc[]; +extern const char curl_errstr_raw_doc[]; extern const char curl_getinfo_doc[]; +extern const char curl_getinfo_raw_doc[]; extern const char curl_pause_doc[]; extern const char curl_perform_doc[]; +extern const char curl_perform_rb_doc[]; +extern const char curl_perform_rs_doc[]; extern const char curl_reset_doc[]; extern const char curl_set_ca_certs_doc[]; extern const char curl_setopt_doc[]; diff -Nru pycurl-7.43.0.1/src/easy.c pycurl-7.43.0.2/src/easy.c --- pycurl-7.43.0.1/src/easy.c 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/src/easy.c 2018-05-23 18:34:47.000000000 +0000 @@ -6,147 +6,6 @@ **************************************************************************/ -/* Convert a curl slist (a list of strings) to a Python list. - * In case of error return NULL with an exception set. - */ -static PyObject *convert_slist(struct curl_slist *slist, int free_flags) -{ - PyObject *ret = NULL; - struct curl_slist *slist_start = slist; - - ret = PyList_New((Py_ssize_t)0); - if (ret == NULL) goto error; - - for ( ; slist != NULL; slist = slist->next) { - PyObject *v = NULL; - - if (slist->data == NULL) { - v = Py_None; Py_INCREF(v); - } else { - v = PyText_FromString(slist->data); - } - if (v == NULL || PyList_Append(ret, v) != 0) { - Py_XDECREF(v); - goto error; - } - Py_DECREF(v); - } - - if ((free_flags & 1) && slist_start) - curl_slist_free_all(slist_start); - return ret; - -error: - Py_XDECREF(ret); - if ((free_flags & 2) && slist_start) - curl_slist_free_all(slist_start); - return NULL; -} - - -static struct curl_slist * -pycurl_list_or_tuple_to_slist(int which, PyObject *obj, Py_ssize_t len) -{ - struct curl_slist *slist = NULL; - Py_ssize_t i; - - for (i = 0; i < len; i++) { - PyObject *listitem = PyListOrTuple_GetItem(obj, i, which); - struct curl_slist *nlist; - char *str; - PyObject *sencoded_obj; - - if (!PyText_Check(listitem)) { - curl_slist_free_all(slist); - PyErr_SetString(PyExc_TypeError, "list items must be byte strings or Unicode strings with ASCII code points only"); - return NULL; - } - /* INFO: curl_slist_append() internally does strdup() the data, so - * no embedded NUL characters allowed here. */ - str = PyText_AsString_NoNUL(listitem, &sencoded_obj); - if (str == NULL) { - curl_slist_free_all(slist); - return NULL; - } - nlist = curl_slist_append(slist, str); - PyText_EncodedDecref(sencoded_obj); - if (nlist == NULL || nlist->data == NULL) { - curl_slist_free_all(slist); - PyErr_NoMemory(); - return NULL; - } - slist = nlist; - } - return slist; -} - - -#ifdef HAVE_CURLOPT_CERTINFO -/* Convert a struct curl_certinfo into a Python data structure. - * In case of error return NULL with an exception set. - */ -static PyObject *convert_certinfo(struct curl_certinfo *cinfo) -{ - PyObject *certs; - int cert_index; - - if (!cinfo) - Py_RETURN_NONE; - - certs = PyList_New((Py_ssize_t)(cinfo->num_of_certs)); - if (!certs) - return NULL; - - for (cert_index = 0; cert_index < cinfo->num_of_certs; cert_index ++) { - struct curl_slist *fields = cinfo->certinfo[cert_index]; - struct curl_slist *field_cursor; - int field_count, field_index; - PyObject *cert; - - field_count = 0; - field_cursor = fields; - while (field_cursor != NULL) { - field_cursor = field_cursor->next; - field_count ++; - } - - - cert = PyTuple_New((Py_ssize_t)field_count); - if (!cert) - goto error; - PyList_SetItem(certs, cert_index, cert); /* Eats the ref from New() */ - - for(field_index = 0, field_cursor = fields; - field_cursor != NULL; - field_index ++, field_cursor = field_cursor->next) { - const char *field = field_cursor->data; - PyObject *field_tuple; - - if (!field) { - field_tuple = Py_None; Py_INCREF(field_tuple); - } else { - const char *sep = strchr(field, ':'); - if (!sep) { - field_tuple = PyText_FromString(field); - } else { - /* XXX check */ - field_tuple = Py_BuildValue("s#s", field, (int)(sep - field), sep+1); - } - if (!field_tuple) - goto error; - } - PyTuple_SET_ITEM(cert, field_index, field_tuple); /* Eats the ref */ - } - } - - return certs; - - error: - Py_DECREF(certs); - return NULL; -} -#endif - /* assert some CurlObject invariants */ PYCURL_INTERNAL void assert_curl_state(const CurlObject *self) @@ -160,7 +19,7 @@ /* check state for methods */ -static int +PYCURL_INTERNAL int check_curl_state(const CurlObject *self, int flags, const char *name) { assert_curl_state(self); @@ -178,78 +37,6 @@ } -#if defined(HAVE_CURL_OPENSSL) -/* internal helper that load certificates from buffer, returns -1 on error */ -static int -add_ca_certs(SSL_CTX *context, void *data, Py_ssize_t len) -{ - // this code was copied from _ssl module - BIO *biobuf = NULL; - X509_STORE *store; - int retval = 0, err, loaded = 0; - - if (len <= 0) { - PyErr_SetString(PyExc_ValueError, - "Empty certificate data"); - return -1; - } else if (len > INT_MAX) { - PyErr_SetString(PyExc_OverflowError, - "Certificate data is too long."); - return -1; - } - - biobuf = BIO_new_mem_buf(data, (int)len); - if (biobuf == NULL) { - PyErr_SetString(PyExc_MemoryError, "Can't allocate buffer"); - ERR_clear_error(); - return -1; - } - - store = SSL_CTX_get_cert_store(context); - assert(store != NULL); - - while (1) { - X509 *cert = NULL; - int r; - - cert = PEM_read_bio_X509(biobuf, NULL, 0, NULL); - if (cert == NULL) { - break; - } - r = X509_STORE_add_cert(store, cert); - X509_free(cert); - if (!r) { - err = ERR_peek_last_error(); - if ((ERR_GET_LIB(err) == ERR_LIB_X509) && - (ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) { - /* cert already in hash table, not an error */ - ERR_clear_error(); - } else { - break; - } - } - loaded++; - } - - err = ERR_peek_last_error(); - if ((loaded > 0) && - (ERR_GET_LIB(err) == ERR_LIB_PEM) && - (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) { - /* EOF PEM file, not an error */ - ERR_clear_error(); - retval = 0; - } else { - PyErr_SetString(ErrorObject, ERR_reason_error_string(err)); - ERR_clear_error(); - retval = -1; - } - - BIO_free(biobuf); - return retval; -} -#endif - - /************************************************************************* // CurlObject **************************************************************************/ @@ -344,7 +131,7 @@ /* util function shared by close() and clear() */ -static void +PYCURL_INTERNAL void util_curl_xdecref(CurlObject *self, int flags, CURL *handle) { if (flags & PYCURL_MEMGROUP_ATTRDICT) { @@ -358,6 +145,10 @@ CurlMultiObject *multi_stack = self->multi_stack; self->multi_stack = NULL; if (multi_stack->multi_handle != NULL && handle != NULL) { + /* TODO this is where we could remove the easy object + from the multi object's easy_object_dict, but this + requires us to have a reference to the multi object + which right now we don't. */ (void) curl_multi_remove_handle(multi_stack->multi_handle, handle); } Py_DECREF(multi_stack); @@ -459,6 +250,10 @@ /* Decref easy related objects */ util_curl_xdecref(self, PYCURL_MEMGROUP_EASY, handle); + 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) @@ -512,18 +307,6 @@ } -static PyObject * -do_curl_errstr(CurlObject *self) -{ - if (check_curl_state(self, 1 | 2, "errstr") != 0) { - return NULL; - } - self->error[sizeof(self->error) - 1] = 0; - - return PyText_FromString(self->error); -} - - /* --------------- GC support --------------- */ /* Drop references that may have created reference cycles. */ @@ -579,2234 +362,54 @@ } -/* --------------- perform --------------- */ +/* ------------------------ reset ------------------------ */ -static PyObject * -do_curl_perform(CurlObject *self) +static PyObject* +do_curl_reset(CurlObject *self) { int res; - if (check_curl_state(self, 1 | 2, "perform") != 0) { - return NULL; - } - - PYCURL_BEGIN_ALLOW_THREADS - res = curl_easy_perform(self->handle); - PYCURL_END_ALLOW_THREADS - - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - Py_RETURN_NONE; -} - - -/* --------------- callback handlers --------------- */ - -/* IMPORTANT NOTE: due to threading issues, we cannot call _any_ Python - * function without acquiring the thread state in the callback handlers. - */ - -static size_t -util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *stream) -{ - CurlObject *self; - PyObject *arglist; - PyObject *result = NULL; - size_t ret = 0; /* assume error */ - PyObject *cb; - int total_size; - PYCURL_DECLARE_THREAD_STATE; - - /* acquire thread */ - self = (CurlObject *)stream; - if (!PYCURL_ACQUIRE_THREAD()) - return ret; - - /* check args */ - cb = flags ? self->h_cb : self->w_cb; - if (cb == NULL) - goto silent_error; - if (size <= 0 || nmemb <= 0) - goto done; - total_size = (int)(size * nmemb); - if (total_size < 0 || (size_t)total_size / size != nmemb) { - PyErr_SetString(ErrorObject, "integer overflow in write callback"); - goto verbose_error; - } - - /* run callback */ -#if PY_MAJOR_VERSION >= 3 - arglist = Py_BuildValue("(y#)", ptr, total_size); -#else - arglist = Py_BuildValue("(s#)", ptr, total_size); -#endif - if (arglist == NULL) - goto verbose_error; - result = PyEval_CallObject(cb, arglist); - Py_DECREF(arglist); - if (result == NULL) - goto verbose_error; - - /* handle result */ - if (result == Py_None) { - ret = total_size; /* None means success */ - } - else if (PyInt_Check(result) || PyLong_Check(result)) { - /* if the cast to long fails, PyLong_AsLong() returns -1L */ - ret = (size_t) PyLong_AsLong(result); - } - else { - PyErr_SetString(ErrorObject, "write callback must return int or None"); - goto verbose_error; - } - -done: -silent_error: - Py_XDECREF(result); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} - - -static size_t -write_callback(char *ptr, size_t size, size_t nmemb, void *stream) -{ - return util_write_callback(0, ptr, size, nmemb, stream); -} - -static size_t -header_callback(char *ptr, size_t size, size_t nmemb, void *stream) -{ - return util_write_callback(1, ptr, size, nmemb, stream); -} - -/* convert protocol address from C to python, returns a tuple of protocol - specific values */ -static PyObject * -convert_protocol_address(struct sockaddr* saddr, unsigned int saddrlen) -{ - PyObject *res_obj = NULL; - - switch (saddr->sa_family) - { - case AF_INET: - { - struct sockaddr_in* sin = (struct sockaddr_in*)saddr; - char *addr_str = PyMem_New(char, INET_ADDRSTRLEN); - - if (addr_str == NULL) { - PyErr_NoMemory(); - goto error; - } - - if (inet_ntop(saddr->sa_family, &sin->sin_addr, addr_str, INET_ADDRSTRLEN) == NULL) { - PyErr_SetFromErrno(ErrorObject); - PyMem_Free(addr_str); - goto error; - } - res_obj = Py_BuildValue("(si)", addr_str, ntohs(sin->sin_port)); - PyMem_Free(addr_str); - } - break; - case AF_INET6: - { - struct sockaddr_in6* sin6 = (struct sockaddr_in6*)saddr; - char *addr_str = PyMem_New(char, INET6_ADDRSTRLEN); - - if (addr_str == NULL) { - PyErr_NoMemory(); - goto error; - } - - if (inet_ntop(saddr->sa_family, &sin6->sin6_addr, addr_str, INET6_ADDRSTRLEN) == NULL) { - PyErr_SetFromErrno(ErrorObject); - PyMem_Free(addr_str); - goto error; - } - res_obj = Py_BuildValue("(siii)", addr_str, (int) ntohs(sin6->sin6_port), - (int) ntohl(sin6->sin6_flowinfo), (int) ntohl(sin6->sin6_scope_id)); - PyMem_Free(addr_str); - } - break; -#if !defined(WIN32) - case AF_UNIX: - { - struct sockaddr_un* s_un = (struct sockaddr_un*)saddr; - -#if PY_MAJOR_VERSION >= 3 - res_obj = Py_BuildValue("y", s_un->sun_path); -#else - res_obj = Py_BuildValue("s", s_un->sun_path); -#endif - } - break; -#endif - default: - /* We (currently) only support IPv4/6 addresses. Can curl even be used - with anything else? */ - PyErr_SetString(ErrorObject, "Unsupported address family"); - } - -error: - return res_obj; -} - - -/* curl_socket_t is just an int on unix/windows (with limitations that - * are not important here) */ -static curl_socket_t -opensocket_callback(void *clientp, curlsocktype purpose, - struct curl_sockaddr *address) -{ - PyObject *arglist; - PyObject *result = NULL; - PyObject *fileno_result = NULL; - CurlObject *self; - int ret = CURL_SOCKET_BAD; - PyObject *converted_address; - PyObject *python_address; - PYCURL_DECLARE_THREAD_STATE; - - self = (CurlObject *)clientp; - PYCURL_ACQUIRE_THREAD(); - - converted_address = convert_protocol_address(&address->addr, address->addrlen); - if (converted_address == NULL) { - goto verbose_error; - } - - arglist = Py_BuildValue("(iiiN)", address->family, address->socktype, address->protocol, converted_address); - if (arglist == NULL) { - Py_DECREF(converted_address); - goto verbose_error; - } - python_address = PyEval_CallObject(curl_sockaddr_type, arglist); - Py_DECREF(arglist); - if (python_address == NULL) { - goto verbose_error; - } - - arglist = Py_BuildValue("(iN)", purpose, python_address); - if (arglist == NULL) { - Py_DECREF(python_address); - goto verbose_error; - } - result = PyEval_CallObject(self->opensocket_cb, arglist); - Py_DECREF(arglist); - if (result == NULL) { - goto verbose_error; - } + curl_easy_reset(self->handle); - if (PyObject_HasAttrString(result, "fileno")) { - fileno_result = PyObject_CallMethod(result, "fileno", NULL); + /* Decref easy interface related objects */ + util_curl_xdecref(self, PYCURL_MEMGROUP_EASY, self->handle); - if (fileno_result == NULL) { - ret = CURL_SOCKET_BAD; - goto verbose_error; - } - // normal operation: - if (PyInt_Check(fileno_result)) { - int sockfd = PyInt_AsLong(fileno_result); -#if defined(WIN32) - ret = dup_winsock(sockfd, address); -#else - ret = dup(sockfd); + /* 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 - goto done; - } else { - PyErr_SetString(ErrorObject, "Open socket callback returned an object whose fileno method did not return an integer"); - ret = CURL_SOCKET_BAD; - } - } else { - PyErr_SetString(ErrorObject, "Open socket callback's return value must be a socket"); - ret = CURL_SOCKET_BAD; - goto verbose_error; - } - -silent_error: -done: - Py_XDECREF(result); - Py_XDECREF(fileno_result); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} - - -static int -sockopt_cb(void *clientp, curl_socket_t curlfd, curlsocktype purpose) -{ - PyObject *arglist; - CurlObject *self; - int ret = -1; - PyObject *ret_obj = NULL; - PYCURL_DECLARE_THREAD_STATE; - - self = (CurlObject *)clientp; - PYCURL_ACQUIRE_THREAD(); - - arglist = Py_BuildValue("(ii)", (int) curlfd, (int) purpose); - if (arglist == NULL) - goto verbose_error; - - ret_obj = PyEval_CallObject(self->sockopt_cb, arglist); - Py_DECREF(arglist); - if (!PyInt_Check(ret_obj) && !PyLong_Check(ret_obj)) { - PyObject *ret_repr = PyObject_Repr(ret_obj); - if (ret_repr) { - PyObject *encoded_obj; - char *str = PyText_AsString_NoNUL(ret_repr, &encoded_obj); - fprintf(stderr, "sockopt callback returned %s which is not an integer\n", str); - /* PyErr_Format(PyExc_TypeError, "sockopt callback returned %s which is not an integer", str); */ - Py_XDECREF(encoded_obj); - Py_DECREF(ret_repr); - } - goto silent_error; - } - if (PyInt_Check(ret_obj)) { - /* long to int cast */ - ret = (int) PyInt_AsLong(ret_obj); - } else { - /* long to int cast */ - ret = (int) PyLong_AsLong(ret_obj); - } - goto done; - -silent_error: - ret = -1; -done: - Py_XDECREF(ret_obj); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} - - -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7) -static int -closesocket_callback(void *clientp, curl_socket_t curlfd) -{ - PyObject *arglist; - CurlObject *self; - int ret = -1; - PyObject *ret_obj = NULL; - PYCURL_DECLARE_THREAD_STATE; - - self = (CurlObject *)clientp; - PYCURL_ACQUIRE_THREAD(); - - arglist = Py_BuildValue("(i)", (int) curlfd); - if (arglist == NULL) - goto verbose_error; - - ret_obj = PyEval_CallObject(self->closesocket_cb, arglist); - Py_DECREF(arglist); - if (!PyInt_Check(ret_obj) && !PyLong_Check(ret_obj)) { - PyObject *ret_repr = PyObject_Repr(ret_obj); - if (ret_repr) { - PyObject *encoded_obj; - char *str = PyText_AsString_NoNUL(ret_repr, &encoded_obj); - fprintf(stderr, "closesocket callback returned %s which is not an integer\n", str); - /* PyErr_Format(PyExc_TypeError, "closesocket callback returned %s which is not an integer", str); */ - Py_XDECREF(encoded_obj); - Py_DECREF(ret_repr); - } - goto silent_error; - } - if (PyInt_Check(ret_obj)) { - /* long to int cast */ - ret = (int) PyInt_AsLong(ret_obj); - } else { - /* long to int cast */ - ret = (int) PyLong_AsLong(ret_obj); - } - goto done; - -silent_error: - ret = -1; -done: - Py_XDECREF(ret_obj); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} + 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_19_6_OPTS -static PyObject * -khkey_to_object(const struct curl_khkey *khkey) -{ - PyObject *arglist, *ret; - - if (khkey == NULL) { - Py_INCREF(Py_None); - return Py_None; - } - - if (khkey->len) { -#if PY_MAJOR_VERSION >= 3 - arglist = Py_BuildValue("(y#i)", khkey->key, khkey->len, khkey->keytype); -#else - arglist = Py_BuildValue("(s#i)", khkey->key, khkey->len, khkey->keytype); +#ifdef HAVE_CURL_7_20_0_OPTS + SFREE(self->mail_rcpt); #endif - } else { -#if PY_MAJOR_VERSION >= 3 - arglist = Py_BuildValue("(yi)", khkey->key, khkey->keytype); -#else - arglist = Py_BuildValue("(si)", khkey->key, khkey->keytype); +#ifdef HAVE_CURLOPT_CONNECT_TO + SFREE(self->connect_to); #endif - } - - if (arglist == NULL) { +#undef SFREE + res = util_curl_init(self); + if (res < 0) { + Py_DECREF(self); /* this also closes self->handle */ + PyErr_SetString(ErrorObject, "resetting curl failed"); return NULL; } - ret = PyObject_Call(khkey_type, arglist, NULL); - Py_DECREF(arglist); - return ret; -} - - -static int -ssh_key_cb(CURL *easy, const struct curl_khkey *knownkey, - const struct curl_khkey *foundkey, int khmatch, void *clientp) -{ - PyObject *arglist; - CurlObject *self; - int ret = -1; - PyObject *knownkey_obj = NULL; - PyObject *foundkey_obj = NULL; - PyObject *ret_obj = NULL; - PYCURL_DECLARE_THREAD_STATE; - - self = (CurlObject *)clientp; - PYCURL_ACQUIRE_THREAD(); - - knownkey_obj = khkey_to_object(knownkey); - if (knownkey_obj == NULL) { - goto silent_error; - } - foundkey_obj = khkey_to_object(foundkey); - if (foundkey_obj == NULL) { - goto silent_error; - } - - arglist = Py_BuildValue("(OOi)", knownkey_obj, foundkey_obj, khmatch); - if (arglist == NULL) - goto verbose_error; - - ret_obj = PyEval_CallObject(self->ssh_key_cb, arglist); - Py_DECREF(arglist); - if (!PyInt_Check(ret_obj) && !PyLong_Check(ret_obj)) { - PyObject *ret_repr = PyObject_Repr(ret_obj); - if (ret_repr) { - PyObject *encoded_obj; - char *str = PyText_AsString_NoNUL(ret_repr, &encoded_obj); - fprintf(stderr, "ssh key callback returned %s which is not an integer\n", str); - /* PyErr_Format(PyExc_TypeError, "ssh key callback returned %s which is not an integer", str); */ - Py_XDECREF(encoded_obj); - Py_DECREF(ret_repr); - } - goto silent_error; - } - if (PyInt_Check(ret_obj)) { - /* long to int cast */ - ret = (int) PyInt_AsLong(ret_obj); - } else { - /* long to int cast */ - ret = (int) PyLong_AsLong(ret_obj); - } - goto done; - -silent_error: - ret = -1; -done: - Py_XDECREF(knownkey_obj); - Py_XDECREF(foundkey_obj); - Py_XDECREF(ret_obj); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} -#endif - - -static int -seek_callback(void *stream, curl_off_t offset, int origin) -{ - CurlObject *self; - PyObject *arglist; - PyObject *result = NULL; - int ret = 2; /* assume error 2 (can't seek, libcurl free to work around). */ - PyObject *cb; - int source = 0; /* assume beginning */ - PYCURL_DECLARE_THREAD_STATE; - - /* acquire thread */ - self = (CurlObject *)stream; - if (!PYCURL_ACQUIRE_THREAD()) - return ret; - - /* check arguments */ - switch (origin) - { - case SEEK_SET: - source = 0; - break; - case SEEK_CUR: - source = 1; - break; - case SEEK_END: - source = 2; - break; - default: - source = origin; - break; - } - - /* run callback */ - cb = self->seek_cb; - if (cb == NULL) - goto silent_error; - arglist = Py_BuildValue("(i,i)", offset, source); - if (arglist == NULL) - goto verbose_error; - result = PyEval_CallObject(cb, arglist); - Py_DECREF(arglist); - if (result == NULL) - goto verbose_error; - - /* handle result */ - if (result == Py_None) { - ret = 0; /* None means success */ - } - else if (PyInt_Check(result)) { - int ret_code = PyInt_AsLong(result); - if (ret_code < 0 || ret_code > 2) { - PyErr_Format(ErrorObject, "invalid return value for seek callback %d not in (0, 1, 2)", ret_code); - goto verbose_error; - } - ret = ret_code; /* pass the return code from the callback */ - } - else { - PyErr_SetString(ErrorObject, "seek callback must return 0 (CURL_SEEKFUNC_OK), 1 (CURL_SEEKFUNC_FAIL), 2 (CURL_SEEKFUNC_CANTSEEK) or None"); - goto verbose_error; - } - -silent_error: - Py_XDECREF(result); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} - - - - -static size_t -read_callback(char *ptr, size_t size, size_t nmemb, void *stream) -{ - CurlObject *self; - PyObject *arglist; - PyObject *result = NULL; - - size_t ret = CURL_READFUNC_ABORT; /* assume error, this actually works */ - int total_size; - - PYCURL_DECLARE_THREAD_STATE; - - /* acquire thread */ - self = (CurlObject *)stream; - if (!PYCURL_ACQUIRE_THREAD()) - return ret; - - /* check args */ - if (self->r_cb == NULL) - goto silent_error; - if (size <= 0 || nmemb <= 0) - goto done; - total_size = (int)(size * nmemb); - if (total_size < 0 || (size_t)total_size / size != nmemb) { - PyErr_SetString(ErrorObject, "integer overflow in read callback"); - goto verbose_error; - } - - /* run callback */ - arglist = Py_BuildValue("(i)", total_size); - if (arglist == NULL) - goto verbose_error; - result = PyEval_CallObject(self->r_cb, arglist); - Py_DECREF(arglist); - if (result == NULL) - goto verbose_error; - - /* handle result */ - if (PyByteStr_Check(result)) { - char *buf = NULL; - Py_ssize_t obj_size = -1; - Py_ssize_t r; - r = PyByteStr_AsStringAndSize(result, &buf, &obj_size); - if (r != 0 || obj_size < 0 || obj_size > total_size) { - PyErr_Format(ErrorObject, "invalid return value for read callback (%ld bytes returned when at most %ld bytes were wanted)", (long)obj_size, (long)total_size); - goto verbose_error; - } - memcpy(ptr, buf, obj_size); - ret = obj_size; /* success */ - } - else if (PyUnicode_Check(result)) { - char *buf = NULL; - Py_ssize_t obj_size = -1; - Py_ssize_t r; - /* - Encode with ascii codec. - - HTTP requires sending content-length for request body to the server - before the request body is sent, therefore typically content length - is given via POSTFIELDSIZE before read function is invoked to - provide the data. - - However, if we encode the string using any encoding other than ascii, - the length of encoded string may not match the length of unicode - string we are encoding. Therefore, if client code does a simple - len(source_string) to determine the value to supply in content-length, - the length of bytes read may be different. - - To avoid this situation, we only accept ascii bytes in the string here. - - Encode data yourself to bytes when dealing with non-ascii data. - */ - PyObject *encoded = PyUnicode_AsEncodedString(result, "ascii", "strict"); - if (encoded == NULL) { - goto verbose_error; - } - r = PyByteStr_AsStringAndSize(encoded, &buf, &obj_size); - if (r != 0 || obj_size < 0 || obj_size > total_size) { - Py_DECREF(encoded); - PyErr_Format(ErrorObject, "invalid return value for read callback (%ld bytes returned after encoding to utf-8 when at most %ld bytes were wanted)", (long)obj_size, (long)total_size); - goto verbose_error; - } - memcpy(ptr, buf, obj_size); - Py_DECREF(encoded); - ret = obj_size; /* success */ - } -#if PY_MAJOR_VERSION < 3 - else if (PyInt_Check(result)) { - long r = PyInt_AsLong(result); - if (r != CURL_READFUNC_ABORT && r != CURL_READFUNC_PAUSE) - goto type_error; - ret = r; /* either CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE */ - } -#endif - else if (PyLong_Check(result)) { - long r = PyLong_AsLong(result); - if (r != CURL_READFUNC_ABORT && r != CURL_READFUNC_PAUSE) - goto type_error; - ret = r; /* either CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE */ - } - else { - type_error: - PyErr_SetString(ErrorObject, "read callback must return a byte string or Unicode string with ASCII code points only"); - goto verbose_error; - } - -done: -silent_error: - Py_XDECREF(result); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} - - -static int -progress_callback(void *stream, - double dltotal, double dlnow, double ultotal, double ulnow) -{ - CurlObject *self; - PyObject *arglist; - PyObject *result = NULL; - int ret = 1; /* assume error */ - PYCURL_DECLARE_THREAD_STATE; - - /* acquire thread */ - self = (CurlObject *)stream; - if (!PYCURL_ACQUIRE_THREAD()) - return ret; - - /* check args */ - if (self->pro_cb == NULL) - goto silent_error; - - /* run callback */ - arglist = Py_BuildValue("(dddd)", dltotal, dlnow, ultotal, ulnow); - if (arglist == NULL) - goto verbose_error; - result = PyEval_CallObject(self->pro_cb, arglist); - Py_DECREF(arglist); - if (result == NULL) - goto verbose_error; - - /* handle result */ - if (result == Py_None) { - ret = 0; /* None means success */ - } - else if (PyInt_Check(result)) { - ret = (int) PyInt_AsLong(result); - } - else { - ret = PyObject_IsTrue(result); /* FIXME ??? */ - } - -silent_error: - Py_XDECREF(result); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; + Py_RETURN_NONE; } -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0) -static int -xferinfo_callback(void *stream, - curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -{ - CurlObject *self; - PyObject *arglist; - PyObject *result = NULL; - int ret = 1; /* assume error */ - PYCURL_DECLARE_THREAD_STATE; - - /* acquire thread */ - self = (CurlObject *)stream; - if (!PYCURL_ACQUIRE_THREAD()) - return ret; - - /* check args */ - if (self->xferinfo_cb == NULL) - goto silent_error; - - /* run callback */ - arglist = Py_BuildValue("(LLLL)", - (PY_LONG_LONG) dltotal, (PY_LONG_LONG) dlnow, - (PY_LONG_LONG) ultotal, (PY_LONG_LONG) ulnow); - if (arglist == NULL) - goto verbose_error; - result = PyEval_CallObject(self->xferinfo_cb, arglist); - Py_DECREF(arglist); - if (result == NULL) - goto verbose_error; - - /* handle result */ - if (result == Py_None) { - ret = 0; /* None means success */ - } - else if (PyInt_Check(result)) { - ret = (int) PyInt_AsLong(result); - } - else { - ret = PyObject_IsTrue(result); /* FIXME ??? */ - } - -silent_error: - Py_XDECREF(result); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} -#endif - - -static int -debug_callback(CURL *curlobj, curl_infotype type, - char *buffer, size_t total_size, void *stream) -{ - CurlObject *self; - PyObject *arglist; - PyObject *result = NULL; - int ret = 0; /* always success */ - PYCURL_DECLARE_THREAD_STATE; - - UNUSED(curlobj); - - /* acquire thread */ - self = (CurlObject *)stream; - if (!PYCURL_ACQUIRE_THREAD()) - return ret; - - /* check args */ - if (self->debug_cb == NULL) - goto silent_error; - if ((int)total_size < 0 || (size_t)((int)total_size) != total_size) { - PyErr_SetString(ErrorObject, "integer overflow in debug callback"); - goto verbose_error; - } - - /* run callback */ -#if PY_MAJOR_VERSION >= 3 - arglist = Py_BuildValue("(iy#)", (int)type, buffer, (int)total_size); -#else - arglist = Py_BuildValue("(is#)", (int)type, buffer, (int)total_size); -#endif - if (arglist == NULL) - goto verbose_error; - result = PyEval_CallObject(self->debug_cb, arglist); - Py_DECREF(arglist); - if (result == NULL) - goto verbose_error; - - /* return values from debug callbacks should be ignored */ - -silent_error: - Py_XDECREF(result); - PYCURL_RELEASE_THREAD(); - return ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} - - -static curlioerr -ioctl_callback(CURL *curlobj, int cmd, void *stream) -{ - CurlObject *self; - PyObject *arglist; - PyObject *result = NULL; - int ret = CURLIOE_FAILRESTART; /* assume error */ - PYCURL_DECLARE_THREAD_STATE; - - UNUSED(curlobj); - - /* acquire thread */ - self = (CurlObject *)stream; - if (!PYCURL_ACQUIRE_THREAD()) - return (curlioerr) ret; - - /* check args */ - if (self->ioctl_cb == NULL) - goto silent_error; - - /* run callback */ - arglist = Py_BuildValue("(i)", cmd); - if (arglist == NULL) - goto verbose_error; - result = PyEval_CallObject(self->ioctl_cb, arglist); - Py_DECREF(arglist); - if (result == NULL) - goto verbose_error; - - /* handle result */ - if (result == Py_None) { - ret = CURLIOE_OK; /* None means success */ - } - else if (PyInt_Check(result)) { - ret = (int) PyInt_AsLong(result); - if (ret >= CURLIOE_LAST || ret < 0) { - PyErr_SetString(ErrorObject, "ioctl callback returned invalid value"); - goto verbose_error; - } - } - -silent_error: - Py_XDECREF(result); - PYCURL_RELEASE_THREAD(); - return (curlioerr) ret; -verbose_error: - PyErr_Print(); - goto silent_error; -} - - -#if defined(HAVE_CURL_OPENSSL) -static CURLcode -ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *ptr) -{ - CurlObject *self; - PYCURL_DECLARE_THREAD_STATE; - int r; - - UNUSED(curl); - - /* acquire thread */ - self = (CurlObject *)ptr; - if (!PYCURL_ACQUIRE_THREAD()) - return CURLE_FAILED_INIT; - - r = add_ca_certs((SSL_CTX*)ssl_ctx, - PyBytes_AS_STRING(self->ca_certs_obj), - PyBytes_GET_SIZE(self->ca_certs_obj)); - - if (r != 0) - PyErr_Print(); - - PYCURL_RELEASE_THREAD(); - return r == 0 ? CURLE_OK : CURLE_FAILED_INIT; -} -#endif - - -/* ------------------------ reset ------------------------ */ - -static PyObject* -do_curl_reset(CurlObject *self) -{ - int res; - - curl_easy_reset(self->handle); - - /* 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 */ - PyErr_SetString(ErrorObject, "resetting curl failed"); - return NULL; - } - - Py_RETURN_NONE; -} - -/* --------------- unsetopt/setopt/getinfo --------------- */ - -static PyObject * -util_curl_unsetopt(CurlObject *self, int option) -{ - int res; - -#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_CALLBACK(callback_option, data_option, callback_field) \ - case callback_option: \ - if ((res = curl_easy_setopt(self->handle, callback_option, NULL)) != CURLE_OK) \ - goto error; \ - if ((res = curl_easy_setopt(self->handle, data_option, NULL)) != CURLE_OK) \ - goto error; \ - Py_CLEAR(callback_field); \ - break - - /* FIXME: implement more options. Have to carefully check lib/url.c in the - * libcurl source code to see if it's actually safe to simply - * unset the option. */ - switch (option) - { - case CURLOPT_SHARE: - SETOPT((CURLSH *) NULL); - 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; - case CURLOPT_WRITEHEADER: - SETOPT((void *) 0); - Py_CLEAR(self->writeheader_fp); - break; - case CURLOPT_CAINFO: - case CURLOPT_CAPATH: - case CURLOPT_COOKIE: - case CURLOPT_COOKIEJAR: - case CURLOPT_CUSTOMREQUEST: - case CURLOPT_EGDSOCKET: - case CURLOPT_ENCODING: - case CURLOPT_FTPPORT: - case CURLOPT_PROXYUSERPWD: -#ifdef HAVE_CURLOPT_PROXYUSERNAME - case CURLOPT_PROXYUSERNAME: - case CURLOPT_PROXYPASSWORD: -#endif - case CURLOPT_RANDOM_FILE: - case CURLOPT_SSL_CIPHER_LIST: - case CURLOPT_USERPWD: -#ifdef HAVE_CURLOPT_USERNAME - case CURLOPT_USERNAME: - case CURLOPT_PASSWORD: -#endif - case CURLOPT_RANGE: -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 43, 0) - 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: -#endif - SETOPT((char *) NULL); - break; - -#ifdef HAVE_CURLOPT_CERTINFO - case CURLOPT_CERTINFO: - SETOPT((long) 0); - break; -#endif - - 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); -#endif - CLEAR_CALLBACK(CURLOPT_SOCKOPTFUNCTION, CURLOPT_SOCKOPTDATA, self->sockopt_cb); -#ifdef HAVE_CURL_7_19_6_OPTS - CLEAR_CALLBACK(CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA, self->ssh_key_cb); -#endif - - /* info: we explicitly list unsupported options here */ - case CURLOPT_COOKIEFILE: - default: - PyErr_SetString(PyExc_TypeError, "unsetopt() is not supported for this option"); - return NULL; - } - - Py_RETURN_NONE; - -error: - CURLERROR_RETVAL(); - -#undef SETOPT -#undef SETOPT2 -#undef CLEAR_CALLBACK -} - - -static PyObject * -do_curl_unsetopt(CurlObject *self, PyObject *args) -{ - int option; - - if (!PyArg_ParseTuple(args, "i:unsetopt", &option)) { - return NULL; - } - if (check_curl_state(self, 1 | 2, "unsetopt") != 0) { - return NULL; - } - - /* early checks of option value */ - if (option <= 0) - goto error; - if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE) - goto error; - if (option % 10000 >= OPTIONS_SIZE) - goto error; - - return util_curl_unsetopt(self, option); - -error: - PyErr_SetString(PyExc_TypeError, "invalid arguments to unsetopt"); - return NULL; -} - - -static PyObject * -do_curl_setopt_string_impl(CurlObject *self, int option, PyObject *obj) -{ - char *str = NULL; - Py_ssize_t len = -1; - PyObject *encoded_obj; - int res; - - /* Check that the option specified a string as well as the input */ - switch (option) { - case CURLOPT_CAINFO: - case CURLOPT_CAPATH: - case CURLOPT_COOKIE: - case CURLOPT_COOKIEFILE: - case CURLOPT_COOKIELIST: - case CURLOPT_COOKIEJAR: - case CURLOPT_CUSTOMREQUEST: - case CURLOPT_EGDSOCKET: - /* use CURLOPT_ENCODING instead of CURLOPT_ACCEPT_ENCODING - for compatibility with older libcurls */ - case CURLOPT_ENCODING: - case CURLOPT_FTPPORT: - case CURLOPT_INTERFACE: - case CURLOPT_KEYPASSWD: - case CURLOPT_NETRC_FILE: - case CURLOPT_PROXY: - case CURLOPT_PROXYUSERPWD: -#ifdef HAVE_CURLOPT_PROXYUSERNAME - case CURLOPT_PROXYUSERNAME: - case CURLOPT_PROXYPASSWORD: -#endif - case CURLOPT_RANDOM_FILE: - case CURLOPT_RANGE: - case CURLOPT_REFERER: - case CURLOPT_SSLCERT: - case CURLOPT_SSLCERTTYPE: - case CURLOPT_SSLENGINE: - case CURLOPT_SSLKEY: - case CURLOPT_SSLKEYTYPE: - case CURLOPT_SSL_CIPHER_LIST: - case CURLOPT_URL: - case CURLOPT_USERAGENT: - case CURLOPT_USERPWD: -#ifdef HAVE_CURLOPT_USERNAME - case CURLOPT_USERNAME: - case CURLOPT_PASSWORD: -#endif - case CURLOPT_FTP_ALTERNATIVE_TO_USER: - case CURLOPT_SSH_PUBLIC_KEYFILE: - case CURLOPT_SSH_PRIVATE_KEYFILE: - case CURLOPT_COPYPOSTFIELDS: - case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: - case CURLOPT_CRLFILE: - case CURLOPT_ISSUERCERT: -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) - case CURLOPT_RTSP_STREAM_URI: - case CURLOPT_RTSP_SESSION_ID: - case CURLOPT_RTSP_TRANSPORT: -#endif -#ifdef HAVE_CURLOPT_DNS_SERVERS - case CURLOPT_DNS_SERVERS: -#endif -#ifdef HAVE_CURLOPT_NOPROXY - case CURLOPT_NOPROXY: -#endif -#ifdef HAVE_CURL_7_19_4_OPTS - case CURLOPT_SOCKS5_GSSAPI_SERVICE: -#endif -#ifdef HAVE_CURL_7_19_6_OPTS - case CURLOPT_SSH_KNOWNHOSTS: -#endif -#ifdef HAVE_CURL_7_20_0_OPTS - case CURLOPT_MAIL_FROM: -#endif -#ifdef HAVE_CURL_7_25_0_OPTS - case CURLOPT_MAIL_AUTH: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 39, 0) - case CURLOPT_PINNEDPUBLICKEY: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 43, 0) - case CURLOPT_SERVICE_NAME: - case CURLOPT_PROXY_SERVICE_NAME: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 0) - case CURLOPT_WILDCARDMATCH: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 40, 0) - case CURLOPT_UNIX_SOCKET_PATH: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 4) - case CURLOPT_TLSAUTH_TYPE: - case CURLOPT_TLSAUTH_USERNAME: - case CURLOPT_TLSAUTH_PASSWORD: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 45, 0) - case CURLOPT_DEFAULT_PROTOCOL: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) - case CURLOPT_LOGIN_OPTIONS: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 33, 0) - case CURLOPT_XOAUTH2_BEARER: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 52, 0) - case CURLOPT_PROXY_CAPATH: - case CURLOPT_PROXY_CAINFO: -#endif - case CURLOPT_KRBLEVEL: - str = PyText_AsString_NoNUL(obj, &encoded_obj); - if (str == NULL) - return NULL; - break; - case CURLOPT_POSTFIELDS: - if (PyText_AsStringAndSize(obj, &str, &len, &encoded_obj) != 0) - return NULL; - /* automatically set POSTFIELDSIZE */ - if (len <= INT_MAX) { - res = curl_easy_setopt(self->handle, CURLOPT_POSTFIELDSIZE, (long)len); - } else { - res = curl_easy_setopt(self->handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)len); - } - if (res != CURLE_OK) { - PyText_EncodedDecref(encoded_obj); - CURLERROR_RETVAL(); - } - break; - default: - PyErr_SetString(PyExc_TypeError, "strings are not supported for this option"); - return NULL; - } - assert(str != NULL); - /* Call setopt */ - res = curl_easy_setopt(self->handle, (CURLoption)option, str); - /* Check for errors */ - if (res != CURLE_OK) { - PyText_EncodedDecref(encoded_obj); - CURLERROR_RETVAL(); - } - /* libcurl does not copy the value of CURLOPT_POSTFIELDS */ - if (option == CURLOPT_POSTFIELDS) { - PyObject *store_obj; - - /* if obj was bytes, it was not encoded, and we need to incref obj. - * if obj was unicode, it was encoded, and we need to incref - * encoded_obj - except we can simply transfer ownership. - */ - if (encoded_obj) { - store_obj = encoded_obj; - } else { - /* no encoding is performed, incref the original object. */ - store_obj = obj; - Py_INCREF(store_obj); - } - - util_curl_xdecref(self, PYCURL_MEMGROUP_POSTFIELDS, self->handle); - self->postfields_obj = store_obj; - } else { - PyText_EncodedDecref(encoded_obj); - } - Py_RETURN_NONE; -} - - -#define IS_LONG_OPTION(o) (o < CURLOPTTYPE_OBJECTPOINT) -#define IS_OFF_T_OPTION(o) (o >= CURLOPTTYPE_OFF_T) - - -static PyObject * -do_curl_setopt_int(CurlObject *self, int option, PyObject *obj) -{ - long d; - PY_LONG_LONG ld; - int res; - - if (IS_LONG_OPTION(option)) { - d = PyInt_AsLong(obj); - res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d); - } else if (IS_OFF_T_OPTION(option)) { - /* this path should only be taken in Python 3 */ - ld = PyLong_AsLongLong(obj); - res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)ld); - } else { - PyErr_SetString(PyExc_TypeError, "integers are not supported for this option"); - return NULL; - } - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - Py_RETURN_NONE; -} - - -static PyObject * -do_curl_setopt_long(CurlObject *self, int option, PyObject *obj) -{ - int res; - PY_LONG_LONG d = PyLong_AsLongLong(obj); - if (d == -1 && PyErr_Occurred()) - return NULL; - - if (IS_LONG_OPTION(option) && (long)d == d) - res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d); - else if (IS_OFF_T_OPTION(option) && (curl_off_t)d == d) - res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)d); - else { - PyErr_SetString(PyExc_TypeError, "longs are not supported for this option"); - return NULL; - } - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - Py_RETURN_NONE; -} - - -#undef IS_LONG_OPTION -#undef IS_OFF_T_OPTION - - -#if PY_MAJOR_VERSION < 3 && !defined(PYCURL_AVOID_STDIO) -static PyObject * -do_curl_setopt_file_passthrough(CurlObject *self, int option, PyObject *obj) -{ - FILE *fp; - int res; - - fp = PyFile_AsFile(obj); - if (fp == NULL) { - PyErr_SetString(PyExc_TypeError, "second argument must be open file"); - return NULL; - } - - switch (option) { - case CURLOPT_READDATA: - res = curl_easy_setopt(self->handle, CURLOPT_READFUNCTION, fread); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - break; - case CURLOPT_WRITEDATA: - res = curl_easy_setopt(self->handle, CURLOPT_WRITEFUNCTION, fwrite); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - break; - case CURLOPT_WRITEHEADER: - res = curl_easy_setopt(self->handle, CURLOPT_HEADERFUNCTION, fwrite); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - break; - default: - PyErr_SetString(PyExc_TypeError, "files are not supported for this option"); - return NULL; - } - - res = curl_easy_setopt(self->handle, (CURLoption)option, fp); - if (res != CURLE_OK) { - /* - If we get here fread/fwrite are set as callbacks but the file pointer - is not set, program will crash if it does not reset read/write - callback. Also, we won't do the memory management later in this - function. - */ - CURLERROR_RETVAL(); - } - Py_INCREF(obj); - - switch (option) { - case CURLOPT_READDATA: - Py_CLEAR(self->readdata_fp); - self->readdata_fp = obj; - break; - case CURLOPT_WRITEDATA: - Py_CLEAR(self->writedata_fp); - self->writedata_fp = obj; - break; - case CURLOPT_WRITEHEADER: - Py_CLEAR(self->writeheader_fp); - self->writeheader_fp = obj; - break; - default: - assert(0); - break; - } - /* Return success */ - Py_RETURN_NONE; -} -#endif - - -static PyObject * -do_curl_setopt_httppost(CurlObject *self, int option, int which, PyObject *obj) -{ - struct curl_httppost *post = NULL; - struct curl_httppost *last = NULL; - /* List of all references that have been INCed as a result of - * this operation */ - PyObject *ref_params = NULL; - PyObject *nencoded_obj, *cencoded_obj, *oencoded_obj; - int which_httppost_item, which_httppost_option; - PyObject *httppost_option; - Py_ssize_t i, len; - int res; - - len = PyListOrTuple_Size(obj, which); - if (len == 0) - Py_RETURN_NONE; - - for (i = 0; i < len; i++) { - char *nstr = NULL, *cstr = NULL; - Py_ssize_t nlen = -1, clen = -1; - PyObject *listitem = PyListOrTuple_GetItem(obj, i, which); - - which_httppost_item = PyListOrTuple_Check(listitem); - if (!which_httppost_item) { - PyErr_SetString(PyExc_TypeError, "list items must be list or tuple objects"); - goto error; - } - if (PyListOrTuple_Size(listitem, which_httppost_item) != 2) { - PyErr_SetString(PyExc_TypeError, "list or tuple must contain two elements (name, value)"); - goto error; - } - if (PyText_AsStringAndSize(PyListOrTuple_GetItem(listitem, 0, which_httppost_item), - &nstr, &nlen, &nencoded_obj) != 0) { - PyErr_SetString(PyExc_TypeError, "list or tuple must contain a byte string or Unicode string with ASCII code points only as first element"); - goto error; - } - httppost_option = PyListOrTuple_GetItem(listitem, 1, which_httppost_item); - if (PyText_Check(httppost_option)) { - /* Handle strings as second argument for backwards compatibility */ - - if (PyText_AsStringAndSize(httppost_option, &cstr, &clen, &cencoded_obj)) { - PyText_EncodedDecref(nencoded_obj); - CURLERROR_SET_RETVAL(); - goto error; - } - /* INFO: curl_formadd() internally does memdup() the data, so - * embedded NUL characters _are_ allowed here. */ - res = curl_formadd(&post, &last, - CURLFORM_COPYNAME, nstr, - CURLFORM_NAMELENGTH, (long) nlen, - CURLFORM_COPYCONTENTS, cstr, - CURLFORM_CONTENTSLENGTH, (long) clen, - CURLFORM_END); - PyText_EncodedDecref(cencoded_obj); - if (res != CURLE_OK) { - PyText_EncodedDecref(nencoded_obj); - CURLERROR_SET_RETVAL(); - goto error; - } - } - /* assignment is intended */ - else if ((which_httppost_option = PyListOrTuple_Check(httppost_option))) { - /* Supports content, file and content-type */ - Py_ssize_t tlen = PyListOrTuple_Size(httppost_option, which_httppost_option); - int j, k, l; - struct curl_forms *forms = NULL; - - /* Sanity check that there are at least two tuple items */ - if (tlen < 2) { - PyText_EncodedDecref(nencoded_obj); - PyErr_SetString(PyExc_TypeError, "list or tuple must contain at least one option and one value"); - goto error; - } - - if (tlen % 2 == 1) { - PyText_EncodedDecref(nencoded_obj); - PyErr_SetString(PyExc_TypeError, "list or tuple must contain an even number of items"); - goto error; - } - - /* Allocate enough space to accommodate length options for content or buffers, plus a terminator. */ - forms = PyMem_New(struct curl_forms, (tlen*2) + 1); - if (forms == NULL) { - PyText_EncodedDecref(nencoded_obj); - PyErr_NoMemory(); - goto error; - } - - /* Iterate all the tuple members pairwise */ - for (j = 0, k = 0, l = 0; j < tlen; j += 2, l++) { - char *ostr; - Py_ssize_t olen; - int val; - - if (j == (tlen-1)) { - PyErr_SetString(PyExc_TypeError, "expected value"); - PyMem_Free(forms); - PyText_EncodedDecref(nencoded_obj); - goto error; - } - if (!PyInt_Check(PyListOrTuple_GetItem(httppost_option, j, which_httppost_option))) { - PyErr_SetString(PyExc_TypeError, "option must be an integer"); - PyMem_Free(forms); - PyText_EncodedDecref(nencoded_obj); - goto error; - } - if (!PyText_Check(PyListOrTuple_GetItem(httppost_option, j+1, which_httppost_option))) { - PyErr_SetString(PyExc_TypeError, "value must be a byte string or a Unicode string with ASCII code points only"); - PyMem_Free(forms); - PyText_EncodedDecref(nencoded_obj); - goto error; - } - - val = PyLong_AsLong(PyListOrTuple_GetItem(httppost_option, j, which_httppost_option)); - if (val != CURLFORM_COPYCONTENTS && - val != CURLFORM_FILE && - val != CURLFORM_FILENAME && - val != CURLFORM_CONTENTTYPE && - val != CURLFORM_BUFFER && - val != CURLFORM_BUFFERPTR) - { - PyErr_SetString(PyExc_TypeError, "unsupported option"); - PyMem_Free(forms); - PyText_EncodedDecref(nencoded_obj); - goto error; - } - - if (PyText_AsStringAndSize(PyListOrTuple_GetItem(httppost_option, j+1, which_httppost_option), &ostr, &olen, &oencoded_obj)) { - /* exception should be already set */ - PyMem_Free(forms); - PyText_EncodedDecref(nencoded_obj); - goto error; - } - forms[k].option = val; - forms[k].value = ostr; - ++k; - - if (val == CURLFORM_COPYCONTENTS) { - /* Contents can contain \0 bytes so we specify the length */ - forms[k].option = CURLFORM_CONTENTSLENGTH; - forms[k].value = (const char *)olen; - ++k; - } else if (val == CURLFORM_BUFFERPTR) { - PyObject *obj = NULL; - - if (ref_params == NULL) { - ref_params = PyList_New((Py_ssize_t)0); - if (ref_params == NULL) { - PyText_EncodedDecref(oencoded_obj); - PyMem_Free(forms); - PyText_EncodedDecref(nencoded_obj); - goto error; - } - } - - /* Keep a reference to the object that holds the ostr buffer. */ - if (oencoded_obj == NULL) { - obj = PyListOrTuple_GetItem(httppost_option, j+1, which_httppost_option); - } - else { - obj = oencoded_obj; - } - - /* Ensure that the buffer remains alive until curl_easy_cleanup() */ - if (PyList_Append(ref_params, obj) != 0) { - PyText_EncodedDecref(oencoded_obj); - PyMem_Free(forms); - PyText_EncodedDecref(nencoded_obj); - goto error; - } - - /* As with CURLFORM_COPYCONTENTS, specify the length. */ - forms[k].option = CURLFORM_BUFFERLENGTH; - forms[k].value = (const char *)olen; - ++k; - } - } - forms[k].option = CURLFORM_END; - res = curl_formadd(&post, &last, - CURLFORM_COPYNAME, nstr, - CURLFORM_NAMELENGTH, (long) nlen, - CURLFORM_ARRAY, forms, - CURLFORM_END); - PyText_EncodedDecref(oencoded_obj); - PyMem_Free(forms); - if (res != CURLE_OK) { - PyText_EncodedDecref(nencoded_obj); - CURLERROR_SET_RETVAL(); - goto error; - } - } else { - /* Some other type was given, ignore */ - PyText_EncodedDecref(nencoded_obj); - PyErr_SetString(PyExc_TypeError, "unsupported second type in tuple"); - goto error; - } - PyText_EncodedDecref(nencoded_obj); - } - res = curl_easy_setopt(self->handle, CURLOPT_HTTPPOST, post); - /* Check for errors */ - if (res != CURLE_OK) { - 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; - - Py_RETURN_NONE; - -error: - curl_formfree(post); - Py_XDECREF(ref_params); - return NULL; -} - - -static PyObject * -do_curl_setopt_list(CurlObject *self, int option, int which, PyObject *obj) -{ - struct curl_slist **old_slist = NULL; - struct curl_slist *slist = NULL; - Py_ssize_t len; - int res; - - switch (option) { - case CURLOPT_HTTP200ALIASES: - old_slist = &self->http200aliases; - break; - case CURLOPT_HTTPHEADER: - old_slist = &self->httpheader; - break; -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0) - case CURLOPT_PROXYHEADER: - old_slist = &self->proxyheader; - break; -#endif - case CURLOPT_POSTQUOTE: - old_slist = &self->postquote; - break; - case CURLOPT_PREQUOTE: - old_slist = &self->prequote; - break; - case CURLOPT_QUOTE: - old_slist = &self->quote; - break; - case CURLOPT_TELNETOPTIONS: - old_slist = &self->telnetoptions; - break; -#ifdef HAVE_CURLOPT_RESOLVE - case CURLOPT_RESOLVE: - old_slist = &self->resolve; - break; -#endif -#ifdef HAVE_CURL_7_20_0_OPTS - case CURLOPT_MAIL_RCPT: - old_slist = &self->mail_rcpt; - break; -#endif -#ifdef HAVE_CURLOPT_CONNECT_TO - case CURLOPT_CONNECT_TO: - old_slist = &self->connect_to; - break; -#endif - default: - /* None of the list options were recognized, raise exception */ - PyErr_SetString(PyExc_TypeError, "lists are not supported for this option"); - return NULL; - } - - len = PyListOrTuple_Size(obj, which); - if (len == 0) - Py_RETURN_NONE; - - /* Just to be sure we do not bug off here */ - assert(old_slist != NULL && slist == NULL); - - /* Handle regular list operations on the other options */ - slist = pycurl_list_or_tuple_to_slist(which, obj, len); - if (slist == NULL) { - return NULL; - } - res = curl_easy_setopt(self->handle, (CURLoption)option, slist); - /* Check for errors */ - if (res != CURLE_OK) { - curl_slist_free_all(slist); - CURLERROR_RETVAL(); - } - /* Finally, free previously allocated list and update */ - curl_slist_free_all(*old_slist); - *old_slist = slist; - - Py_RETURN_NONE; -} - - -static PyObject * -do_curl_setopt_callable(CurlObject *self, int option, PyObject *obj) -{ - /* We use function types here to make sure that our callback - * definitions exactly match the interface. - */ - const curl_write_callback w_cb = write_callback; - const curl_write_callback h_cb = header_callback; - const curl_read_callback r_cb = read_callback; - const curl_progress_callback pro_cb = progress_callback; -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0) - const curl_xferinfo_callback xferinfo_cb = xferinfo_callback; -#endif - const curl_debug_callback debug_cb = debug_callback; - const curl_ioctl_callback ioctl_cb = ioctl_callback; - const curl_opensocket_callback opensocket_cb = opensocket_callback; -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7) - const curl_closesocket_callback closesocket_cb = closesocket_callback; -#endif - const curl_seek_callback seek_cb = seek_callback; - - switch(option) { - case CURLOPT_WRITEFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->writedata_fp); - Py_CLEAR(self->w_cb); - self->w_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_WRITEFUNCTION, w_cb); - curl_easy_setopt(self->handle, CURLOPT_WRITEDATA, self); - break; - case CURLOPT_HEADERFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->writeheader_fp); - Py_CLEAR(self->h_cb); - self->h_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_HEADERFUNCTION, h_cb); - curl_easy_setopt(self->handle, CURLOPT_WRITEHEADER, self); - break; - case CURLOPT_READFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->readdata_fp); - Py_CLEAR(self->r_cb); - self->r_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_READFUNCTION, r_cb); - curl_easy_setopt(self->handle, CURLOPT_READDATA, self); - break; - case CURLOPT_PROGRESSFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->pro_cb); - self->pro_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_PROGRESSFUNCTION, pro_cb); - curl_easy_setopt(self->handle, CURLOPT_PROGRESSDATA, self); - break; -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0) - case CURLOPT_XFERINFOFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->xferinfo_cb); - self->xferinfo_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_XFERINFOFUNCTION, xferinfo_cb); - curl_easy_setopt(self->handle, CURLOPT_XFERINFODATA, self); - break; -#endif - case CURLOPT_DEBUGFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->debug_cb); - self->debug_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_DEBUGFUNCTION, debug_cb); - curl_easy_setopt(self->handle, CURLOPT_DEBUGDATA, self); - break; - case CURLOPT_IOCTLFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->ioctl_cb); - self->ioctl_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_IOCTLFUNCTION, ioctl_cb); - curl_easy_setopt(self->handle, CURLOPT_IOCTLDATA, self); - break; - case CURLOPT_OPENSOCKETFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->opensocket_cb); - self->opensocket_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_OPENSOCKETFUNCTION, opensocket_cb); - curl_easy_setopt(self->handle, CURLOPT_OPENSOCKETDATA, self); - break; -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7) - case CURLOPT_CLOSESOCKETFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->closesocket_cb); - self->closesocket_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_CLOSESOCKETFUNCTION, closesocket_cb); - curl_easy_setopt(self->handle, CURLOPT_CLOSESOCKETDATA, self); - break; -#endif - case CURLOPT_SOCKOPTFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->sockopt_cb); - self->sockopt_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_SOCKOPTFUNCTION, sockopt_cb); - curl_easy_setopt(self->handle, CURLOPT_SOCKOPTDATA, self); - break; -#ifdef HAVE_CURL_7_19_6_OPTS - case CURLOPT_SSH_KEYFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->ssh_key_cb); - self->ssh_key_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_SSH_KEYFUNCTION, ssh_key_cb); - curl_easy_setopt(self->handle, CURLOPT_SSH_KEYDATA, self); - break; -#endif - case CURLOPT_SEEKFUNCTION: - Py_INCREF(obj); - Py_CLEAR(self->seek_cb); - self->seek_cb = obj; - curl_easy_setopt(self->handle, CURLOPT_SEEKFUNCTION, seek_cb); - curl_easy_setopt(self->handle, CURLOPT_SEEKDATA, self); - break; - - default: - /* None of the function options were recognized, raise exception */ - PyErr_SetString(PyExc_TypeError, "functions are not supported for this option"); - return NULL; - } - Py_RETURN_NONE; -} - - -static PyObject * -do_curl_setopt_share(CurlObject *self, PyObject *obj) -{ - CurlShareObject *share; - int res; - - if (self->share == NULL && (obj == NULL || obj == Py_None)) - Py_RETURN_NONE; - - if (self->share) { - if (obj != Py_None) { - PyErr_SetString(ErrorObject, "Curl object already sharing. Unshare first."); - return NULL; - } - else { - share = self->share; - res = curl_easy_setopt(self->handle, CURLOPT_SHARE, NULL); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - self->share = NULL; - Py_DECREF(share); - Py_RETURN_NONE; - } - } - if (Py_TYPE(obj) != p_CurlShare_Type) { - PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt"); - return NULL; - } - share = (CurlShareObject*)obj; - res = curl_easy_setopt(self->handle, CURLOPT_SHARE, share->share_handle); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - self->share = share; - Py_INCREF(share); - Py_RETURN_NONE; -} - - -/* prototype for do_curl_setopt_filelike */ -static PyObject * -do_curl_setopt(CurlObject *self, PyObject *args); - - -static PyObject * -do_curl_setopt_filelike(CurlObject *self, int option, PyObject *obj) -{ - const char *method_name; - PyObject *method; - - if (option == CURLOPT_READDATA) { - method_name = "read"; - } else { - method_name = "write"; - } - method = PyObject_GetAttrString(obj, method_name); - if (method) { - PyObject *arglist; - PyObject *rv; - - switch (option) { - case CURLOPT_READDATA: - option = CURLOPT_READFUNCTION; - break; - case CURLOPT_WRITEDATA: - option = CURLOPT_WRITEFUNCTION; - break; - case CURLOPT_WRITEHEADER: - option = CURLOPT_HEADERFUNCTION; - break; - default: - PyErr_SetString(PyExc_TypeError, "objects are not supported for this option"); - Py_DECREF(method); - return NULL; - } - - arglist = Py_BuildValue("(iO)", option, method); - /* reference is now in arglist */ - Py_DECREF(method); - if (arglist == NULL) { - return NULL; - } - rv = do_curl_setopt(self, arglist); - Py_DECREF(arglist); - return rv; - } else { - if (option == CURLOPT_READDATA) { - PyErr_SetString(PyExc_TypeError, "object given without a read method"); - } else { - PyErr_SetString(PyExc_TypeError, "object given without a write method"); - } - return NULL; - } -} - - -static PyObject * -do_curl_setopt(CurlObject *self, PyObject *args) -{ - int option; - PyObject *obj; - int which; - - if (!PyArg_ParseTuple(args, "iO:setopt", &option, &obj)) - return NULL; - if (check_curl_state(self, 1 | 2, "setopt") != 0) - return NULL; - - /* early checks of option value */ - if (option <= 0) - goto error; - if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE) - goto error; - if (option % 10000 >= OPTIONS_SIZE) - goto error; - - /* Handle the case of None as the call of unsetopt() */ - if (obj == Py_None) { - return util_curl_unsetopt(self, option); - } - - /* Handle the case of string arguments */ - if (PyText_Check(obj)) { - return do_curl_setopt_string_impl(self, option, obj); - } - - /* Handle the case of integer arguments */ - if (PyInt_Check(obj)) { - return do_curl_setopt_int(self, option, obj); - } - - /* Handle the case of long arguments (used by *_LARGE options) */ - if (PyLong_Check(obj)) { - return do_curl_setopt_long(self, option, obj); - } - -#if PY_MAJOR_VERSION < 3 && !defined(PYCURL_AVOID_STDIO) - /* Handle the case of file objects */ - if (PyFile_Check(obj)) { - return do_curl_setopt_file_passthrough(self, option, obj); - } -#endif - - /* Handle the case of list or tuple objects */ - which = PyListOrTuple_Check(obj); - if (which) { - if (option == CURLOPT_HTTPPOST) { - return do_curl_setopt_httppost(self, option, which, obj); - } else { - return do_curl_setopt_list(self, option, which, obj); - } - } - - /* Handle the case of function objects for callbacks */ - if (PyFunction_Check(obj) || PyCFunction_Check(obj) || - PyCallable_Check(obj) || PyMethod_Check(obj)) { - return do_curl_setopt_callable(self, option, obj); - } - /* handle the SHARE case */ - if (option == CURLOPT_SHARE) { - return do_curl_setopt_share(self, obj); - } - - /* - Handle the case of file-like objects. - - Given an object with a write method, we will call the write method - from the appropriate callback. - - Files in Python 3 are no longer FILE * instances and therefore cannot - be directly given to curl, therefore this method handles all I/O to - Python objects. - - In Python 2 true file objects are FILE * instances and will be handled - by stdio passthrough code invoked above, and file-like objects will - be handled by this method. - */ - if (option == CURLOPT_READDATA || - option == CURLOPT_WRITEDATA || - option == CURLOPT_WRITEHEADER) - { - return do_curl_setopt_filelike(self, option, obj); - } - - /* Failed to match any of the function signatures -- return error */ -error: - PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt"); - return NULL; -} - - -static PyObject * -do_curl_setopt_string(CurlObject *self, PyObject *args) -{ - int option; - PyObject *obj; - - if (!PyArg_ParseTuple(args, "iO:setopt", &option, &obj)) - return NULL; - if (check_curl_state(self, 1 | 2, "setopt") != 0) - return NULL; - - /* Handle the case of string arguments */ - if (PyText_Check(obj)) { - return do_curl_setopt_string_impl(self, option, obj); - } - - /* Failed to match any of the function signatures -- return error */ - PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt_string"); - return NULL; -} - - -static PyObject * -do_curl_getinfo(CurlObject *self, PyObject *args) -{ - int option; - int res; - - if (!PyArg_ParseTuple(args, "i:getinfo", &option)) { - return NULL; - } - if (check_curl_state(self, 1 | 2, "getinfo") != 0) { - return NULL; - } - - switch (option) { - case CURLINFO_FILETIME: - case CURLINFO_HEADER_SIZE: - case CURLINFO_RESPONSE_CODE: - case CURLINFO_REDIRECT_COUNT: - case CURLINFO_REQUEST_SIZE: - case CURLINFO_SSL_VERIFYRESULT: - case CURLINFO_HTTP_CONNECTCODE: - case CURLINFO_HTTPAUTH_AVAIL: - case CURLINFO_PROXYAUTH_AVAIL: - case CURLINFO_OS_ERRNO: - case CURLINFO_NUM_CONNECTS: - case CURLINFO_LASTSOCKET: -#ifdef HAVE_CURLINFO_LOCAL_PORT - case CURLINFO_LOCAL_PORT: -#endif -#ifdef HAVE_CURLINFO_PRIMARY_PORT - case CURLINFO_PRIMARY_PORT: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) - case CURLINFO_RTSP_CLIENT_CSEQ: - case CURLINFO_RTSP_SERVER_CSEQ: - case CURLINFO_RTSP_CSEQ_RECV: -#endif -#ifdef HAVE_CURLINFO_HTTP_VERSION - case CURLINFO_HTTP_VERSION: -#endif - - { - /* Return PyInt as result */ - long l_res = -1; - - res = curl_easy_getinfo(self->handle, (CURLINFO)option, &l_res); - /* Check for errors and return result */ - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - return PyInt_FromLong(l_res); - } - - case CURLINFO_CONTENT_TYPE: - case CURLINFO_EFFECTIVE_URL: - case CURLINFO_FTP_ENTRY_PATH: - case CURLINFO_REDIRECT_URL: - case CURLINFO_PRIMARY_IP: -#ifdef HAVE_CURLINFO_LOCAL_IP - case CURLINFO_LOCAL_IP: -#endif -#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) - case CURLINFO_RTSP_SESSION_ID: -#endif - { - /* Return PyString as result */ - char *s_res = NULL; - - res = curl_easy_getinfo(self->handle, (CURLINFO)option, &s_res); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - /* If the resulting string is NULL, return None */ - if (s_res == NULL) { - Py_RETURN_NONE; - } - return PyText_FromString(s_res); - - } - - case CURLINFO_CONNECT_TIME: - case CURLINFO_APPCONNECT_TIME: - case CURLINFO_CONTENT_LENGTH_DOWNLOAD: - case CURLINFO_CONTENT_LENGTH_UPLOAD: - case CURLINFO_NAMELOOKUP_TIME: - case CURLINFO_PRETRANSFER_TIME: - case CURLINFO_REDIRECT_TIME: - case CURLINFO_SIZE_DOWNLOAD: - case CURLINFO_SIZE_UPLOAD: - case CURLINFO_SPEED_DOWNLOAD: - case CURLINFO_SPEED_UPLOAD: - case CURLINFO_STARTTRANSFER_TIME: - case CURLINFO_TOTAL_TIME: - { - /* Return PyFloat as result */ - double d_res = 0.0; - - res = curl_easy_getinfo(self->handle, (CURLINFO)option, &d_res); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - return PyFloat_FromDouble(d_res); - } - - case CURLINFO_SSL_ENGINES: - case CURLINFO_COOKIELIST: - { - /* Return a list of strings */ - struct curl_slist *slist = NULL; - - res = curl_easy_getinfo(self->handle, (CURLINFO)option, &slist); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - return convert_slist(slist, 1 | 2); - } - -#ifdef HAVE_CURLOPT_CERTINFO - case CURLINFO_CERTINFO: - { - /* Return a list of lists of 2-tuples */ - struct curl_certinfo *clist = NULL; - res = curl_easy_getinfo(self->handle, CURLINFO_CERTINFO, &clist); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } else { - return convert_certinfo(clist); - } - } -#endif - } - - /* Got wrong option on the method call */ - PyErr_SetString(PyExc_ValueError, "invalid argument to getinfo"); - return NULL; -} - -/* curl_easy_pause() can be called from inside a callback or outside */ -static PyObject * -do_curl_pause(CurlObject *self, PyObject *args) -{ - int bitmask; - CURLcode res; -#ifdef WITH_THREAD - PyThreadState *saved_state; -#endif - - if (!PyArg_ParseTuple(args, "i:pause", &bitmask)) { - return NULL; - } - if (check_curl_state(self, 1, "pause") != 0) { - return NULL; - } - -#ifdef WITH_THREAD - /* Save handle to current thread (used as context for python callbacks) */ - saved_state = self->state; - PYCURL_BEGIN_ALLOW_THREADS - - /* We must allow threads here because unpausing a handle can cause - some of its callbacks to be invoked immediately, from inside - curl_easy_pause() */ -#endif - - res = curl_easy_pause(self->handle, bitmask); - -#ifdef WITH_THREAD - PYCURL_END_ALLOW_THREADS - - /* Restore the thread-state to whatever it was on entry */ - self->state = saved_state; -#endif - - if (res != CURLE_OK) { - CURLERROR_MSG("pause/unpause failed"); - } else { - Py_INCREF(Py_None); - return Py_None; - } -} - - -#if defined(HAVE_CURL_OPENSSL) -/* load ca certs from string */ -static PyObject * -do_curl_set_ca_certs(CurlObject *self, PyObject *args) -{ - PyObject *cadata, *cadata_ascii; - int res; - - if (!PyArg_ParseTuple(args, "O:cadata", &cadata)) - return NULL; - - cadata_ascii = PyUnicode_AsASCIIString(cadata); - if (cadata_ascii == NULL) { - PyErr_SetString(PyExc_TypeError, - "cadata should be an ASCII string or a " - "bytes-like object"); - return NULL; - } - - Py_CLEAR(self->ca_certs_obj); - self->ca_certs_obj = cadata_ascii; - - res = curl_easy_setopt(self->handle, CURLOPT_SSL_CTX_FUNCTION, (curl_ssl_ctx_callback) ssl_ctx_callback); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - - res = curl_easy_setopt(self->handle, CURLOPT_SSL_CTX_DATA, self); - if (res != CURLE_OK) { - CURLERROR_RETVAL(); - } - - Py_RETURN_NONE; -} -#endif - - static PyObject *do_curl_getstate(CurlObject *self) { PyErr_SetString(PyExc_TypeError, "Curl objects do not support serialization"); @@ -2830,9 +433,13 @@ PYCURL_INTERNAL PyMethodDef curlobject_methods[] = { {"close", (PyCFunction)do_curl_close, METH_NOARGS, curl_close_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}, {"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}, {"setopt", (PyCFunction)do_curl_setopt, METH_VARARGS, curl_setopt_doc}, {"setopt_string", (PyCFunction)do_curl_setopt_string, METH_VARARGS, curl_setopt_string_doc}, {"unsetopt", (PyCFunction)do_curl_unsetopt, METH_VARARGS, curl_unsetopt_doc}, @@ -2925,12 +532,12 @@ 0, /* tp_setattro */ #endif 0, /* tp_as_buffer */ - Py_TPFLAGS_HAVE_GC, /* tp_flags */ + PYCURL_TYPE_FLAGS, /* tp_flags */ curl_doc, /* tp_doc */ (traverseproc)do_curl_traverse, /* tp_traverse */ (inquiry)do_curl_clear, /* tp_clear */ 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ + offsetof(CurlObject, weakreflist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ curlobject_methods, /* tp_methods */ diff -Nru pycurl-7.43.0.1/src/easycb.c pycurl-7.43.0.2/src/easycb.c --- pycurl-7.43.0.1/src/easycb.c 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/src/easycb.c 2018-05-23 05:08:32.000000000 +0000 @@ -0,0 +1,941 @@ +#include "pycurl.h" + + +/* IMPORTANT NOTE: due to threading issues, we cannot call _any_ Python + * function without acquiring the thread state in the callback handlers. + */ + + +static size_t +util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *stream) +{ + CurlObject *self; + PyObject *arglist; + PyObject *result = NULL; + size_t ret = 0; /* assume error */ + PyObject *cb; + int total_size; + PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) + return ret; + + /* check args */ + cb = flags ? self->h_cb : self->w_cb; + if (cb == NULL) + goto silent_error; + if (size <= 0 || nmemb <= 0) + goto done; + total_size = (int)(size * nmemb); + if (total_size < 0 || (size_t)total_size / size != nmemb) { + PyErr_SetString(ErrorObject, "integer overflow in write callback"); + goto verbose_error; + } + + /* run callback */ +#if PY_MAJOR_VERSION >= 3 + arglist = Py_BuildValue("(y#)", ptr, total_size); +#else + arglist = Py_BuildValue("(s#)", ptr, total_size); +#endif + if (arglist == NULL) + goto verbose_error; + result = PyEval_CallObject(cb, arglist); + Py_DECREF(arglist); + if (result == NULL) + goto verbose_error; + + /* handle result */ + if (result == Py_None) { + ret = total_size; /* None means success */ + } + else if (PyInt_Check(result) || PyLong_Check(result)) { + /* if the cast to long fails, PyLong_AsLong() returns -1L */ + ret = (size_t) PyLong_AsLong(result); + } + else { + PyErr_SetString(ErrorObject, "write callback must return int or None"); + goto verbose_error; + } + +done: +silent_error: + Py_XDECREF(result); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + +PYCURL_INTERNAL size_t +write_callback(char *ptr, size_t size, size_t nmemb, void *stream) +{ + return util_write_callback(0, ptr, size, nmemb, stream); +} + +PYCURL_INTERNAL size_t +header_callback(char *ptr, size_t size, size_t nmemb, void *stream) +{ + return util_write_callback(1, ptr, size, nmemb, stream); +} + + +/* convert protocol address from C to python, returns a tuple of protocol + specific values */ +static PyObject * +convert_protocol_address(struct sockaddr* saddr, unsigned int saddrlen) +{ + PyObject *res_obj = NULL; + + switch (saddr->sa_family) + { + case AF_INET: + { + struct sockaddr_in* sin = (struct sockaddr_in*)saddr; + char *addr_str = PyMem_New(char, INET_ADDRSTRLEN); + + if (addr_str == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (inet_ntop(saddr->sa_family, &sin->sin_addr, addr_str, INET_ADDRSTRLEN) == NULL) { + PyErr_SetFromErrno(ErrorObject); + PyMem_Free(addr_str); + goto error; + } + res_obj = Py_BuildValue("(si)", addr_str, ntohs(sin->sin_port)); + PyMem_Free(addr_str); + } + break; + case AF_INET6: + { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)saddr; + char *addr_str = PyMem_New(char, INET6_ADDRSTRLEN); + + if (addr_str == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (inet_ntop(saddr->sa_family, &sin6->sin6_addr, addr_str, INET6_ADDRSTRLEN) == NULL) { + PyErr_SetFromErrno(ErrorObject); + PyMem_Free(addr_str); + goto error; + } + res_obj = Py_BuildValue("(siii)", addr_str, (int) ntohs(sin6->sin6_port), + (int) ntohl(sin6->sin6_flowinfo), (int) ntohl(sin6->sin6_scope_id)); + PyMem_Free(addr_str); + } + break; +#if !defined(WIN32) + case AF_UNIX: + { + struct sockaddr_un* s_un = (struct sockaddr_un*)saddr; + +#if PY_MAJOR_VERSION >= 3 + res_obj = Py_BuildValue("y", s_un->sun_path); +#else + res_obj = Py_BuildValue("s", s_un->sun_path); +#endif + } + break; +#endif + default: + /* We (currently) only support IPv4/6 addresses. Can curl even be used + with anything else? */ + PyErr_SetString(ErrorObject, "Unsupported address family"); + } + +error: + return res_obj; +} + + +/* curl_socket_t is just an int on unix/windows (with limitations that + * are not important here) */ +PYCURL_INTERNAL curl_socket_t +opensocket_callback(void *clientp, curlsocktype purpose, + struct curl_sockaddr *address) +{ + PyObject *arglist; + PyObject *result = NULL; + PyObject *fileno_result = NULL; + CurlObject *self; + int ret = CURL_SOCKET_BAD; + PyObject *converted_address; + PyObject *python_address; + PYCURL_DECLARE_THREAD_STATE; + + self = (CurlObject *)clientp; + PYCURL_ACQUIRE_THREAD(); + + converted_address = convert_protocol_address(&address->addr, address->addrlen); + if (converted_address == NULL) { + goto verbose_error; + } + + arglist = Py_BuildValue("(iiiN)", address->family, address->socktype, address->protocol, converted_address); + if (arglist == NULL) { + Py_DECREF(converted_address); + goto verbose_error; + } + python_address = PyEval_CallObject(curl_sockaddr_type, arglist); + Py_DECREF(arglist); + if (python_address == NULL) { + goto verbose_error; + } + + arglist = Py_BuildValue("(iN)", purpose, python_address); + if (arglist == NULL) { + Py_DECREF(python_address); + goto verbose_error; + } + result = PyEval_CallObject(self->opensocket_cb, arglist); + Py_DECREF(arglist); + if (result == NULL) { + goto verbose_error; + } + + if (PyInt_Check(result) && PyInt_AsLong(result) == CURL_SOCKET_BAD) { + ret = CURL_SOCKET_BAD; + } else if (PyObject_HasAttrString(result, "fileno")) { + fileno_result = PyObject_CallMethod(result, "fileno", NULL); + + if (fileno_result == NULL) { + ret = CURL_SOCKET_BAD; + goto verbose_error; + } + // normal operation: + if (PyInt_Check(fileno_result)) { + int sockfd = PyInt_AsLong(fileno_result); +#if defined(WIN32) + ret = dup_winsock(sockfd, address); +#else + ret = dup(sockfd); +#endif + goto done; + } else { + PyErr_SetString(ErrorObject, "Open socket callback returned an object whose fileno method did not return an integer"); + ret = CURL_SOCKET_BAD; + } + } else { + PyErr_SetString(ErrorObject, "Open socket callback's return value must be a socket"); + ret = CURL_SOCKET_BAD; + goto verbose_error; + } + +silent_error: +done: + Py_XDECREF(result); + Py_XDECREF(fileno_result); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + +PYCURL_INTERNAL int +sockopt_cb(void *clientp, curl_socket_t curlfd, curlsocktype purpose) +{ + PyObject *arglist; + CurlObject *self; + int ret = -1; + PyObject *ret_obj = NULL; + PYCURL_DECLARE_THREAD_STATE; + + self = (CurlObject *)clientp; + PYCURL_ACQUIRE_THREAD(); + + arglist = Py_BuildValue("(ii)", (int) curlfd, (int) purpose); + if (arglist == NULL) + goto verbose_error; + + ret_obj = PyEval_CallObject(self->sockopt_cb, arglist); + Py_DECREF(arglist); + if (!PyInt_Check(ret_obj) && !PyLong_Check(ret_obj)) { + PyObject *ret_repr = PyObject_Repr(ret_obj); + if (ret_repr) { + PyObject *encoded_obj; + char *str = PyText_AsString_NoNUL(ret_repr, &encoded_obj); + fprintf(stderr, "sockopt callback returned %s which is not an integer\n", str); + /* PyErr_Format(PyExc_TypeError, "sockopt callback returned %s which is not an integer", str); */ + Py_XDECREF(encoded_obj); + Py_DECREF(ret_repr); + } + goto silent_error; + } + if (PyInt_Check(ret_obj)) { + /* long to int cast */ + ret = (int) PyInt_AsLong(ret_obj); + } else { + /* long to int cast */ + ret = (int) PyLong_AsLong(ret_obj); + } + goto done; + +silent_error: + ret = -1; +done: + Py_XDECREF(ret_obj); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7) +PYCURL_INTERNAL int +closesocket_callback(void *clientp, curl_socket_t curlfd) +{ + PyObject *arglist; + CurlObject *self; + int ret = -1; + PyObject *ret_obj = NULL; + PYCURL_DECLARE_THREAD_STATE; + + self = (CurlObject *)clientp; + PYCURL_ACQUIRE_THREAD(); + + arglist = Py_BuildValue("(i)", (int) curlfd); + if (arglist == NULL) + goto verbose_error; + + ret_obj = PyEval_CallObject(self->closesocket_cb, arglist); + Py_DECREF(arglist); + if (!ret_obj) + goto silent_error; + if (!PyInt_Check(ret_obj) && !PyLong_Check(ret_obj)) { + PyObject *ret_repr = PyObject_Repr(ret_obj); + if (ret_repr) { + PyObject *encoded_obj; + char *str = PyText_AsString_NoNUL(ret_repr, &encoded_obj); + fprintf(stderr, "closesocket callback returned %s which is not an integer\n", str); + /* PyErr_Format(PyExc_TypeError, "closesocket callback returned %s which is not an integer", str); */ + Py_XDECREF(encoded_obj); + Py_DECREF(ret_repr); + } + goto silent_error; + } + if (PyInt_Check(ret_obj)) { + /* long to int cast */ + ret = (int) PyInt_AsLong(ret_obj); + } else { + /* long to int cast */ + ret = (int) PyLong_AsLong(ret_obj); + } + goto done; + +silent_error: + ret = -1; +done: + Py_XDECREF(ret_obj); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} +#endif + + +#ifdef HAVE_CURL_7_19_6_OPTS +static PyObject * +khkey_to_object(const struct curl_khkey *khkey) +{ + PyObject *arglist, *ret; + + if (khkey == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + + if (khkey->len) { +#if PY_MAJOR_VERSION >= 3 + arglist = Py_BuildValue("(y#i)", khkey->key, khkey->len, khkey->keytype); +#else + arglist = Py_BuildValue("(s#i)", khkey->key, khkey->len, khkey->keytype); +#endif + } else { +#if PY_MAJOR_VERSION >= 3 + arglist = Py_BuildValue("(yi)", khkey->key, khkey->keytype); +#else + arglist = Py_BuildValue("(si)", khkey->key, khkey->keytype); +#endif + } + + if (arglist == NULL) { + return NULL; + } + + ret = PyObject_Call(khkey_type, arglist, NULL); + Py_DECREF(arglist); + return ret; +} + + +PYCURL_INTERNAL int +ssh_key_cb(CURL *easy, const struct curl_khkey *knownkey, + const struct curl_khkey *foundkey, int khmatch, void *clientp) +{ + PyObject *arglist; + CurlObject *self; + int ret = -1; + PyObject *knownkey_obj = NULL; + PyObject *foundkey_obj = NULL; + PyObject *ret_obj = NULL; + PYCURL_DECLARE_THREAD_STATE; + + self = (CurlObject *)clientp; + PYCURL_ACQUIRE_THREAD(); + + knownkey_obj = khkey_to_object(knownkey); + if (knownkey_obj == NULL) { + goto silent_error; + } + foundkey_obj = khkey_to_object(foundkey); + if (foundkey_obj == NULL) { + goto silent_error; + } + + arglist = Py_BuildValue("(OOi)", knownkey_obj, foundkey_obj, khmatch); + if (arglist == NULL) + goto verbose_error; + + ret_obj = PyEval_CallObject(self->ssh_key_cb, arglist); + Py_DECREF(arglist); + if (!PyInt_Check(ret_obj) && !PyLong_Check(ret_obj)) { + PyObject *ret_repr = PyObject_Repr(ret_obj); + if (ret_repr) { + PyObject *encoded_obj; + char *str = PyText_AsString_NoNUL(ret_repr, &encoded_obj); + fprintf(stderr, "ssh key callback returned %s which is not an integer\n", str); + /* PyErr_Format(PyExc_TypeError, "ssh key callback returned %s which is not an integer", str); */ + Py_XDECREF(encoded_obj); + Py_DECREF(ret_repr); + } + goto silent_error; + } + if (PyInt_Check(ret_obj)) { + /* long to int cast */ + ret = (int) PyInt_AsLong(ret_obj); + } else { + /* long to int cast */ + ret = (int) PyLong_AsLong(ret_obj); + } + goto done; + +silent_error: + ret = -1; +done: + Py_XDECREF(knownkey_obj); + Py_XDECREF(foundkey_obj); + Py_XDECREF(ret_obj); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} +#endif + + +PYCURL_INTERNAL int +seek_callback(void *stream, curl_off_t offset, int origin) +{ + CurlObject *self; + PyObject *arglist; + PyObject *result = NULL; + int ret = 2; /* assume error 2 (can't seek, libcurl free to work around). */ + PyObject *cb; + int source = 0; /* assume beginning */ + PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) + return ret; + + /* check arguments */ + switch (origin) + { + case SEEK_SET: + source = 0; + break; + case SEEK_CUR: + source = 1; + break; + case SEEK_END: + source = 2; + break; + default: + source = origin; + break; + } + + /* run callback */ + cb = self->seek_cb; + if (cb == NULL) + goto silent_error; + arglist = Py_BuildValue("(i,i)", offset, source); + if (arglist == NULL) + goto verbose_error; + result = PyEval_CallObject(cb, arglist); + Py_DECREF(arglist); + if (result == NULL) + goto verbose_error; + + /* handle result */ + if (result == Py_None) { + ret = 0; /* None means success */ + } + else if (PyInt_Check(result)) { + int ret_code = PyInt_AsLong(result); + if (ret_code < 0 || ret_code > 2) { + PyErr_Format(ErrorObject, "invalid return value for seek callback %d not in (0, 1, 2)", ret_code); + goto verbose_error; + } + ret = ret_code; /* pass the return code from the callback */ + } + else { + PyErr_SetString(ErrorObject, "seek callback must return 0 (CURL_SEEKFUNC_OK), 1 (CURL_SEEKFUNC_FAIL), 2 (CURL_SEEKFUNC_CANTSEEK) or None"); + goto verbose_error; + } + +silent_error: + Py_XDECREF(result); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + + + +PYCURL_INTERNAL size_t +read_callback(char *ptr, size_t size, size_t nmemb, void *stream) +{ + CurlObject *self; + PyObject *arglist; + PyObject *result = NULL; + + size_t ret = CURL_READFUNC_ABORT; /* assume error, this actually works */ + int total_size; + + PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) + return ret; + + /* check args */ + if (self->r_cb == NULL) + goto silent_error; + if (size <= 0 || nmemb <= 0) + goto done; + total_size = (int)(size * nmemb); + if (total_size < 0 || (size_t)total_size / size != nmemb) { + PyErr_SetString(ErrorObject, "integer overflow in read callback"); + goto verbose_error; + } + + /* run callback */ + arglist = Py_BuildValue("(i)", total_size); + if (arglist == NULL) + goto verbose_error; + result = PyEval_CallObject(self->r_cb, arglist); + Py_DECREF(arglist); + if (result == NULL) + goto verbose_error; + + /* handle result */ + if (PyByteStr_Check(result)) { + char *buf = NULL; + Py_ssize_t obj_size = -1; + Py_ssize_t r; + r = PyByteStr_AsStringAndSize(result, &buf, &obj_size); + if (r != 0 || obj_size < 0 || obj_size > total_size) { + PyErr_Format(ErrorObject, "invalid return value for read callback (%ld bytes returned when at most %ld bytes were wanted)", (long)obj_size, (long)total_size); + goto verbose_error; + } + memcpy(ptr, buf, obj_size); + ret = obj_size; /* success */ + } + else if (PyUnicode_Check(result)) { + char *buf = NULL; + Py_ssize_t obj_size = -1; + Py_ssize_t r; + /* + Encode with ascii codec. + + HTTP requires sending content-length for request body to the server + before the request body is sent, therefore typically content length + is given via POSTFIELDSIZE before read function is invoked to + provide the data. + + However, if we encode the string using any encoding other than ascii, + the length of encoded string may not match the length of unicode + string we are encoding. Therefore, if client code does a simple + len(source_string) to determine the value to supply in content-length, + the length of bytes read may be different. + + To avoid this situation, we only accept ascii bytes in the string here. + + Encode data yourself to bytes when dealing with non-ascii data. + */ + PyObject *encoded = PyUnicode_AsEncodedString(result, "ascii", "strict"); + if (encoded == NULL) { + goto verbose_error; + } + r = PyByteStr_AsStringAndSize(encoded, &buf, &obj_size); + if (r != 0 || obj_size < 0 || obj_size > total_size) { + Py_DECREF(encoded); + PyErr_Format(ErrorObject, "invalid return value for read callback (%ld bytes returned after encoding to utf-8 when at most %ld bytes were wanted)", (long)obj_size, (long)total_size); + goto verbose_error; + } + memcpy(ptr, buf, obj_size); + Py_DECREF(encoded); + ret = obj_size; /* success */ + } +#if PY_MAJOR_VERSION < 3 + else if (PyInt_Check(result)) { + long r = PyInt_AsLong(result); + if (r != CURL_READFUNC_ABORT && r != CURL_READFUNC_PAUSE) + goto type_error; + ret = r; /* either CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE */ + } +#endif + else if (PyLong_Check(result)) { + long r = PyLong_AsLong(result); + if (r != CURL_READFUNC_ABORT && r != CURL_READFUNC_PAUSE) + goto type_error; + ret = r; /* either CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE */ + } + else { + type_error: + PyErr_SetString(ErrorObject, "read callback must return a byte string or Unicode string with ASCII code points only"); + goto verbose_error; + } + +done: +silent_error: + Py_XDECREF(result); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + +PYCURL_INTERNAL int +progress_callback(void *stream, + double dltotal, double dlnow, double ultotal, double ulnow) +{ + CurlObject *self; + PyObject *arglist; + PyObject *result = NULL; + int ret = 1; /* assume error */ + PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) + return ret; + + /* check args */ + if (self->pro_cb == NULL) + goto silent_error; + + /* run callback */ + arglist = Py_BuildValue("(dddd)", dltotal, dlnow, ultotal, ulnow); + if (arglist == NULL) + goto verbose_error; + result = PyEval_CallObject(self->pro_cb, arglist); + Py_DECREF(arglist); + if (result == NULL) + goto verbose_error; + + /* handle result */ + if (result == Py_None) { + ret = 0; /* None means success */ + } + else if (PyInt_Check(result)) { + ret = (int) PyInt_AsLong(result); + } + else { + ret = PyObject_IsTrue(result); /* FIXME ??? */ + } + +silent_error: + Py_XDECREF(result); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0) +PYCURL_INTERNAL int +xferinfo_callback(void *stream, + curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) +{ + CurlObject *self; + PyObject *arglist; + PyObject *result = NULL; + int ret = 1; /* assume error */ + PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) + return ret; + + /* check args */ + if (self->xferinfo_cb == NULL) + goto silent_error; + + /* run callback */ + arglist = Py_BuildValue("(LLLL)", + (PY_LONG_LONG) dltotal, (PY_LONG_LONG) dlnow, + (PY_LONG_LONG) ultotal, (PY_LONG_LONG) ulnow); + if (arglist == NULL) + goto verbose_error; + result = PyEval_CallObject(self->xferinfo_cb, arglist); + Py_DECREF(arglist); + if (result == NULL) + goto verbose_error; + + /* handle result */ + if (result == Py_None) { + ret = 0; /* None means success */ + } + else if (PyInt_Check(result)) { + ret = (int) PyInt_AsLong(result); + } + else { + ret = PyObject_IsTrue(result); /* FIXME ??? */ + } + +silent_error: + Py_XDECREF(result); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} +#endif + + +PYCURL_INTERNAL int +debug_callback(CURL *curlobj, curl_infotype type, + char *buffer, size_t total_size, void *stream) +{ + CurlObject *self; + PyObject *arglist; + PyObject *result = NULL; + int ret = 0; /* always success */ + PYCURL_DECLARE_THREAD_STATE; + + UNUSED(curlobj); + + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) + return ret; + + /* check args */ + if (self->debug_cb == NULL) + goto silent_error; + if ((int)total_size < 0 || (size_t)((int)total_size) != total_size) { + PyErr_SetString(ErrorObject, "integer overflow in debug callback"); + goto verbose_error; + } + + /* run callback */ +#if PY_MAJOR_VERSION >= 3 + arglist = Py_BuildValue("(iy#)", (int)type, buffer, (int)total_size); +#else + arglist = Py_BuildValue("(is#)", (int)type, buffer, (int)total_size); +#endif + if (arglist == NULL) + goto verbose_error; + result = PyEval_CallObject(self->debug_cb, arglist); + Py_DECREF(arglist); + if (result == NULL) + goto verbose_error; + + /* return values from debug callbacks should be ignored */ + +silent_error: + Py_XDECREF(result); + PYCURL_RELEASE_THREAD(); + return ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + +PYCURL_INTERNAL curlioerr +ioctl_callback(CURL *curlobj, int cmd, void *stream) +{ + CurlObject *self; + PyObject *arglist; + PyObject *result = NULL; + int ret = CURLIOE_FAILRESTART; /* assume error */ + PYCURL_DECLARE_THREAD_STATE; + + UNUSED(curlobj); + + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) + return (curlioerr) ret; + + /* check args */ + if (self->ioctl_cb == NULL) + goto silent_error; + + /* run callback */ + arglist = Py_BuildValue("(i)", cmd); + if (arglist == NULL) + goto verbose_error; + result = PyEval_CallObject(self->ioctl_cb, arglist); + Py_DECREF(arglist); + if (result == NULL) + goto verbose_error; + + /* handle result */ + if (result == Py_None) { + ret = CURLIOE_OK; /* None means success */ + } + else if (PyInt_Check(result)) { + ret = (int) PyInt_AsLong(result); + if (ret >= CURLIOE_LAST || ret < 0) { + PyErr_SetString(ErrorObject, "ioctl callback returned invalid value"); + goto verbose_error; + } + } + +silent_error: + Py_XDECREF(result); + PYCURL_RELEASE_THREAD(); + return (curlioerr) ret; +verbose_error: + PyErr_Print(); + goto silent_error; +} + + +#if defined(HAVE_CURL_OPENSSL) +/* internal helper that load certificates from buffer, returns -1 on error */ +static int +add_ca_certs(SSL_CTX *context, void *data, Py_ssize_t len) +{ + // this code was copied from _ssl module + BIO *biobuf = NULL; + X509_STORE *store; + int retval = 0, err, loaded = 0; + + if (len <= 0) { + PyErr_SetString(PyExc_ValueError, + "Empty certificate data"); + return -1; + } else if (len > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "Certificate data is too long."); + return -1; + } + + biobuf = BIO_new_mem_buf(data, (int)len); + if (biobuf == NULL) { + PyErr_SetString(PyExc_MemoryError, "Can't allocate buffer"); + ERR_clear_error(); + return -1; + } + + store = SSL_CTX_get_cert_store(context); + assert(store != NULL); + + while (1) { + X509 *cert = NULL; + int r; + + cert = PEM_read_bio_X509(biobuf, NULL, 0, NULL); + if (cert == NULL) { + break; + } + r = X509_STORE_add_cert(store, cert); + X509_free(cert); + if (!r) { + err = ERR_peek_last_error(); + if ((ERR_GET_LIB(err) == ERR_LIB_X509) && + (ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) { + /* cert already in hash table, not an error */ + ERR_clear_error(); + } else { + break; + } + } + loaded++; + } + + err = ERR_peek_last_error(); + if ((loaded > 0) && + (ERR_GET_LIB(err) == ERR_LIB_PEM) && + (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) { + /* EOF PEM file, not an error */ + ERR_clear_error(); + retval = 0; + } else { + PyErr_SetString(ErrorObject, ERR_reason_error_string(err)); + ERR_clear_error(); + retval = -1; + } + + BIO_free(biobuf); + return retval; +} + + +PYCURL_INTERNAL CURLcode +ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *ptr) +{ + CurlObject *self; + PYCURL_DECLARE_THREAD_STATE; + int r; + + UNUSED(curl); + + /* acquire thread */ + self = (CurlObject *)ptr; + if (!PYCURL_ACQUIRE_THREAD()) + return CURLE_FAILED_INIT; + + r = add_ca_certs((SSL_CTX*)ssl_ctx, + PyBytes_AS_STRING(self->ca_certs_obj), + PyBytes_GET_SIZE(self->ca_certs_obj)); + + if (r != 0) + PyErr_Print(); + + PYCURL_RELEASE_THREAD(); + return r == 0 ? CURLE_OK : CURLE_FAILED_INIT; +} +#endif diff -Nru pycurl-7.43.0.1/src/easyinfo.c pycurl-7.43.0.2/src/easyinfo.c --- pycurl-7.43.0.1/src/easyinfo.c 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/src/easyinfo.c 2018-05-23 05:08:32.000000000 +0000 @@ -0,0 +1,377 @@ +#include "pycurl.h" + + +/* Convert a curl slist (a list of strings) to a Python list. + * In case of error return NULL with an exception set. + */ +static PyObject *convert_slist(struct curl_slist *slist, int free_flags) +{ + PyObject *ret = NULL; + struct curl_slist *slist_start = slist; + + ret = PyList_New((Py_ssize_t)0); + if (ret == NULL) goto error; + + for ( ; slist != NULL; slist = slist->next) { + PyObject *v = NULL; + + if (slist->data == NULL) { + v = Py_None; Py_INCREF(v); + } else { + v = PyByteStr_FromString(slist->data); + } + if (v == NULL || PyList_Append(ret, v) != 0) { + Py_XDECREF(v); + goto error; + } + Py_DECREF(v); + } + + if ((free_flags & 1) && slist_start) + curl_slist_free_all(slist_start); + return ret; + +error: + Py_XDECREF(ret); + if ((free_flags & 2) && slist_start) + curl_slist_free_all(slist_start); + return NULL; +} + + +#ifdef HAVE_CURLOPT_CERTINFO +/* Convert a struct curl_certinfo into a Python data structure. + * In case of error return NULL with an exception set. + */ +static PyObject *convert_certinfo(struct curl_certinfo *cinfo, int decode) +{ + PyObject *certs; + int cert_index; + + if (!cinfo) + Py_RETURN_NONE; + + certs = PyList_New((Py_ssize_t)(cinfo->num_of_certs)); + if (!certs) + return NULL; + + for (cert_index = 0; cert_index < cinfo->num_of_certs; cert_index ++) { + struct curl_slist *fields = cinfo->certinfo[cert_index]; + struct curl_slist *field_cursor; + int field_count, field_index; + PyObject *cert; + + field_count = 0; + field_cursor = fields; + while (field_cursor != NULL) { + field_cursor = field_cursor->next; + field_count ++; + } + + + cert = PyTuple_New((Py_ssize_t)field_count); + if (!cert) + goto error; + PyList_SetItem(certs, cert_index, cert); /* Eats the ref from New() */ + + for(field_index = 0, field_cursor = fields; + field_cursor != NULL; + field_index ++, field_cursor = field_cursor->next) { + const char *field = field_cursor->data; + PyObject *field_tuple; + + if (!field) { + field_tuple = Py_None; Py_INCREF(field_tuple); + } else { + const char *sep = strchr(field, ':'); + if (!sep) { + if (decode) { + field_tuple = PyText_FromString(field); + } else { + field_tuple = PyByteStr_FromString(field); + } + } else { + /* XXX check */ + if (decode) { + field_tuple = Py_BuildValue("s#s", field, (int)(sep - field), sep+1); + } else { +#if PY_MAJOR_VERSION >= 3 + field_tuple = Py_BuildValue("y#y", field, (int)(sep - field), sep+1); +#else + field_tuple = Py_BuildValue("s#s", field, (int)(sep - field), sep+1); +#endif + } + } + if (!field_tuple) + goto error; + } + PyTuple_SET_ITEM(cert, field_index, field_tuple); /* Eats the ref */ + } + } + + return certs; + + error: + Py_DECREF(certs); + return NULL; +} +#endif + +PYCURL_INTERNAL PyObject * +do_curl_getinfo_raw(CurlObject *self, PyObject *args) +{ + int option; + int res; + + if (!PyArg_ParseTuple(args, "i:getinfo_raw", &option)) { + return NULL; + } + if (check_curl_state(self, 1 | 2, "getinfo") != 0) { + return NULL; + } + + switch (option) { + case CURLINFO_FILETIME: + case CURLINFO_HEADER_SIZE: + case CURLINFO_RESPONSE_CODE: + case CURLINFO_REDIRECT_COUNT: + case CURLINFO_REQUEST_SIZE: + case CURLINFO_SSL_VERIFYRESULT: + case CURLINFO_HTTP_CONNECTCODE: + case CURLINFO_HTTPAUTH_AVAIL: + case CURLINFO_PROXYAUTH_AVAIL: + case CURLINFO_OS_ERRNO: + case CURLINFO_NUM_CONNECTS: + case CURLINFO_LASTSOCKET: +#ifdef HAVE_CURLINFO_LOCAL_PORT + case CURLINFO_LOCAL_PORT: +#endif +#ifdef HAVE_CURLINFO_PRIMARY_PORT + case CURLINFO_PRIMARY_PORT: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) + case CURLINFO_RTSP_CLIENT_CSEQ: + case CURLINFO_RTSP_SERVER_CSEQ: + case CURLINFO_RTSP_CSEQ_RECV: +#endif +#ifdef HAVE_CURLINFO_HTTP_VERSION + case CURLINFO_HTTP_VERSION: +#endif + + { + /* Return PyInt as result */ + long l_res = -1; + + res = curl_easy_getinfo(self->handle, (CURLINFO)option, &l_res); + /* Check for errors and return result */ + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + return PyInt_FromLong(l_res); + } + + case CURLINFO_CONTENT_TYPE: + case CURLINFO_EFFECTIVE_URL: + case CURLINFO_FTP_ENTRY_PATH: + case CURLINFO_REDIRECT_URL: + case CURLINFO_PRIMARY_IP: +#ifdef HAVE_CURLINFO_LOCAL_IP + case CURLINFO_LOCAL_IP: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) + case CURLINFO_RTSP_SESSION_ID: +#endif + { + /* Return PyString as result */ + char *s_res = NULL; + + res = curl_easy_getinfo(self->handle, (CURLINFO)option, &s_res); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + /* If the resulting string is NULL, return None */ + if (s_res == NULL) { + Py_RETURN_NONE; + } + return PyByteStr_FromString(s_res); + + } + + case CURLINFO_CONNECT_TIME: + case CURLINFO_APPCONNECT_TIME: + case CURLINFO_CONTENT_LENGTH_DOWNLOAD: + case CURLINFO_CONTENT_LENGTH_UPLOAD: + case CURLINFO_NAMELOOKUP_TIME: + case CURLINFO_PRETRANSFER_TIME: + case CURLINFO_REDIRECT_TIME: + case CURLINFO_SIZE_DOWNLOAD: + case CURLINFO_SIZE_UPLOAD: + case CURLINFO_SPEED_DOWNLOAD: + case CURLINFO_SPEED_UPLOAD: + case CURLINFO_STARTTRANSFER_TIME: + case CURLINFO_TOTAL_TIME: + { + /* Return PyFloat as result */ + double d_res = 0.0; + + res = curl_easy_getinfo(self->handle, (CURLINFO)option, &d_res); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + return PyFloat_FromDouble(d_res); + } + + case CURLINFO_SSL_ENGINES: + case CURLINFO_COOKIELIST: + { + /* Return a list of strings */ + struct curl_slist *slist = NULL; + + res = curl_easy_getinfo(self->handle, (CURLINFO)option, &slist); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + return convert_slist(slist, 1 | 2); + } + +#ifdef HAVE_CURLOPT_CERTINFO + case CURLINFO_CERTINFO: + { + /* Return a list of lists of 2-tuples */ + struct curl_certinfo *clist = NULL; + res = curl_easy_getinfo(self->handle, CURLINFO_CERTINFO, &clist); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } else { + return convert_certinfo(clist, 0); + } + } +#endif + } + + /* Got wrong option on the method call */ + PyErr_SetString(PyExc_ValueError, "invalid argument to getinfo"); + return NULL; +} + + +#if PY_MAJOR_VERSION >= 3 +static PyObject * +decode_string_list(PyObject *list) +{ + PyObject *decoded_list = NULL; + Py_ssize_t size = PyList_Size(list); + int i; + + decoded_list = PyList_New(size); + if (decoded_list == NULL) { + return NULL; + } + + for (i = 0; i < size; ++i) { + PyObject *decoded_item = PyUnicode_FromEncodedObject( + PyList_GET_ITEM(list, i), + NULL, + NULL); + + if (decoded_item == NULL) { + goto err; + } + } + + return decoded_list; + +err: + Py_DECREF(decoded_list); + return NULL; +} + +PYCURL_INTERNAL PyObject * +do_curl_getinfo(CurlObject *self, PyObject *args) +{ + int option, res; + PyObject *rv; + + if (!PyArg_ParseTuple(args, "i:getinfo", &option)) { + return NULL; + } + +#ifdef HAVE_CURLOPT_CERTINFO + if (option == CURLINFO_CERTINFO) { + /* Return a list of lists of 2-tuples */ + struct curl_certinfo *clist = NULL; + res = curl_easy_getinfo(self->handle, CURLINFO_CERTINFO, &clist); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } else { + return convert_certinfo(clist, 1); + } + } +#endif + + rv = do_curl_getinfo_raw(self, args); + if (rv == NULL) { + return rv; + } + + switch (option) { + case CURLINFO_CONTENT_TYPE: + case CURLINFO_EFFECTIVE_URL: + case CURLINFO_FTP_ENTRY_PATH: + case CURLINFO_REDIRECT_URL: + case CURLINFO_PRIMARY_IP: +#ifdef HAVE_CURLINFO_LOCAL_IP + case CURLINFO_LOCAL_IP: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) + case CURLINFO_RTSP_SESSION_ID: +#endif + { + PyObject *decoded; + + // Decode bytes into a Unicode string using default encoding + decoded = PyUnicode_FromEncodedObject(rv, NULL, NULL); + // success and failure paths both need to free bytes object + Py_DECREF(rv); + return decoded; + } + + case CURLINFO_SSL_ENGINES: + case CURLINFO_COOKIELIST: + { + PyObject *decoded = decode_string_list(rv); + Py_DECREF(rv); + return decoded; + } + + default: + return rv; + } +} +#endif + + +PYCURL_INTERNAL PyObject * +do_curl_errstr(CurlObject *self) +{ + if (check_curl_state(self, 1 | 2, "errstr") != 0) { + return NULL; + } + self->error[sizeof(self->error) - 1] = 0; + + return PyText_FromString(self->error); +} + + +#if PY_MAJOR_VERSION >= 3 +PYCURL_INTERNAL PyObject * +do_curl_errstr_raw(CurlObject *self) +{ + if (check_curl_state(self, 1 | 2, "errstr") != 0) { + return NULL; + } + self->error[sizeof(self->error) - 1] = 0; + + return PyByteStr_FromString(self->error); +} +#endif diff -Nru pycurl-7.43.0.1/src/easyopt.c pycurl-7.43.0.2/src/easyopt.c --- pycurl-7.43.0.1/src/easyopt.c 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/src/easyopt.c 2018-05-23 18:34:47.000000000 +0000 @@ -0,0 +1,1168 @@ +#include "pycurl.h" + + +static struct curl_slist * +pycurl_list_or_tuple_to_slist(int which, PyObject *obj, Py_ssize_t len) +{ + struct curl_slist *slist = NULL; + Py_ssize_t i; + + for (i = 0; i < len; i++) { + PyObject *listitem = PyListOrTuple_GetItem(obj, i, which); + struct curl_slist *nlist; + char *str; + PyObject *sencoded_obj; + + if (!PyText_Check(listitem)) { + curl_slist_free_all(slist); + PyErr_SetString(PyExc_TypeError, "list items must be byte strings or Unicode strings with ASCII code points only"); + return NULL; + } + /* INFO: curl_slist_append() internally does strdup() the data, so + * no embedded NUL characters allowed here. */ + str = PyText_AsString_NoNUL(listitem, &sencoded_obj); + if (str == NULL) { + curl_slist_free_all(slist); + return NULL; + } + nlist = curl_slist_append(slist, str); + PyText_EncodedDecref(sencoded_obj); + if (nlist == NULL || nlist->data == NULL) { + curl_slist_free_all(slist); + PyErr_NoMemory(); + return NULL; + } + slist = nlist; + } + return slist; +} + + +static PyObject * +util_curl_unsetopt(CurlObject *self, int option) +{ + int res; + +#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_CALLBACK(callback_option, data_option, callback_field) \ + case callback_option: \ + if ((res = curl_easy_setopt(self->handle, callback_option, NULL)) != CURLE_OK) \ + goto error; \ + if ((res = curl_easy_setopt(self->handle, data_option, NULL)) != CURLE_OK) \ + goto error; \ + Py_CLEAR(callback_field); \ + break + + /* FIXME: implement more options. Have to carefully check lib/url.c in the + * libcurl source code to see if it's actually safe to simply + * unset the option. */ + switch (option) + { + case CURLOPT_SHARE: + SETOPT((CURLSH *) NULL); + 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; + case CURLOPT_WRITEHEADER: + SETOPT((void *) 0); + Py_CLEAR(self->writeheader_fp); + break; + case CURLOPT_CAINFO: + case CURLOPT_CAPATH: + case CURLOPT_COOKIE: + case CURLOPT_COOKIEJAR: + case CURLOPT_CUSTOMREQUEST: + case CURLOPT_EGDSOCKET: + case CURLOPT_ENCODING: + case CURLOPT_FTPPORT: + case CURLOPT_PROXYUSERPWD: +#ifdef HAVE_CURLOPT_PROXYUSERNAME + case CURLOPT_PROXYUSERNAME: + case CURLOPT_PROXYPASSWORD: +#endif + case CURLOPT_RANDOM_FILE: + case CURLOPT_SSL_CIPHER_LIST: + case CURLOPT_USERPWD: +#ifdef HAVE_CURLOPT_USERNAME + case CURLOPT_USERNAME: + case CURLOPT_PASSWORD: +#endif + case CURLOPT_RANGE: +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 43, 0) + 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: + case CURLOPT_PRE_PROXY: + case CURLOPT_PROXY_SSLCERT: + case CURLOPT_PROXY_SSLCERTTYPE: + case CURLOPT_PROXY_SSLKEY: + case CURLOPT_PROXY_SSLKEYTYPE: +#endif + SETOPT((char *) NULL); + break; + +#ifdef HAVE_CURLOPT_CERTINFO + case CURLOPT_CERTINFO: + SETOPT((long) 0); + break; +#endif + + 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); +#endif + CLEAR_CALLBACK(CURLOPT_SOCKOPTFUNCTION, CURLOPT_SOCKOPTDATA, self->sockopt_cb); +#ifdef HAVE_CURL_7_19_6_OPTS + CLEAR_CALLBACK(CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA, self->ssh_key_cb); +#endif + + /* info: we explicitly list unsupported options here */ + case CURLOPT_COOKIEFILE: + default: + PyErr_SetString(PyExc_TypeError, "unsetopt() is not supported for this option"); + return NULL; + } + + Py_RETURN_NONE; + +error: + CURLERROR_RETVAL(); + +#undef SETOPT +#undef SETOPT2 +#undef CLEAR_CALLBACK +} + + +PYCURL_INTERNAL PyObject * +do_curl_unsetopt(CurlObject *self, PyObject *args) +{ + int option; + + if (!PyArg_ParseTuple(args, "i:unsetopt", &option)) { + return NULL; + } + if (check_curl_state(self, 1 | 2, "unsetopt") != 0) { + return NULL; + } + + /* early checks of option value */ + if (option <= 0) + goto error; + if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE) + goto error; + if (option % 10000 >= OPTIONS_SIZE) + goto error; + + return util_curl_unsetopt(self, option); + +error: + PyErr_SetString(PyExc_TypeError, "invalid arguments to unsetopt"); + return NULL; +} + + +static PyObject * +do_curl_setopt_string_impl(CurlObject *self, int option, PyObject *obj) +{ + char *str = NULL; + Py_ssize_t len = -1; + PyObject *encoded_obj; + int res; + + /* Check that the option specified a string as well as the input */ + switch (option) { + case CURLOPT_CAINFO: + case CURLOPT_CAPATH: + case CURLOPT_COOKIE: + case CURLOPT_COOKIEFILE: + case CURLOPT_COOKIELIST: + case CURLOPT_COOKIEJAR: + case CURLOPT_CUSTOMREQUEST: + case CURLOPT_EGDSOCKET: + /* use CURLOPT_ENCODING instead of CURLOPT_ACCEPT_ENCODING + for compatibility with older libcurls */ + case CURLOPT_ENCODING: + case CURLOPT_FTPPORT: + case CURLOPT_INTERFACE: + case CURLOPT_KEYPASSWD: + case CURLOPT_NETRC_FILE: + case CURLOPT_PROXY: + case CURLOPT_PROXYUSERPWD: +#ifdef HAVE_CURLOPT_PROXYUSERNAME + case CURLOPT_PROXYUSERNAME: + case CURLOPT_PROXYPASSWORD: +#endif + case CURLOPT_RANDOM_FILE: + case CURLOPT_RANGE: + case CURLOPT_REFERER: + case CURLOPT_SSLCERT: + case CURLOPT_SSLCERTTYPE: + case CURLOPT_SSLENGINE: + case CURLOPT_SSLKEY: + case CURLOPT_SSLKEYTYPE: + case CURLOPT_SSL_CIPHER_LIST: + case CURLOPT_URL: + case CURLOPT_USERAGENT: + case CURLOPT_USERPWD: +#ifdef HAVE_CURLOPT_USERNAME + case CURLOPT_USERNAME: + case CURLOPT_PASSWORD: +#endif + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + case CURLOPT_SSH_PUBLIC_KEYFILE: + case CURLOPT_SSH_PRIVATE_KEYFILE: + case CURLOPT_COPYPOSTFIELDS: + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + case CURLOPT_CRLFILE: + case CURLOPT_ISSUERCERT: +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) + case CURLOPT_RTSP_STREAM_URI: + case CURLOPT_RTSP_SESSION_ID: + case CURLOPT_RTSP_TRANSPORT: +#endif +#ifdef HAVE_CURLOPT_DNS_SERVERS + case CURLOPT_DNS_SERVERS: +#endif +#ifdef HAVE_CURLOPT_NOPROXY + case CURLOPT_NOPROXY: +#endif +#ifdef HAVE_CURL_7_19_4_OPTS + case CURLOPT_SOCKS5_GSSAPI_SERVICE: +#endif +#ifdef HAVE_CURL_7_19_6_OPTS + case CURLOPT_SSH_KNOWNHOSTS: +#endif +#ifdef HAVE_CURL_7_20_0_OPTS + case CURLOPT_MAIL_FROM: +#endif +#ifdef HAVE_CURL_7_25_0_OPTS + case CURLOPT_MAIL_AUTH: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 39, 0) + case CURLOPT_PINNEDPUBLICKEY: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 43, 0) + case CURLOPT_SERVICE_NAME: + case CURLOPT_PROXY_SERVICE_NAME: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 0) + case CURLOPT_WILDCARDMATCH: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 40, 0) + case CURLOPT_UNIX_SOCKET_PATH: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 4) + case CURLOPT_TLSAUTH_TYPE: + case CURLOPT_TLSAUTH_USERNAME: + case CURLOPT_TLSAUTH_PASSWORD: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 45, 0) + case CURLOPT_DEFAULT_PROTOCOL: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) + case CURLOPT_LOGIN_OPTIONS: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 33, 0) + case CURLOPT_XOAUTH2_BEARER: +#endif +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 52, 0) + case CURLOPT_PROXY_CAPATH: + case CURLOPT_PROXY_CAINFO: + case CURLOPT_PRE_PROXY: + case CURLOPT_PROXY_SSLCERT: + case CURLOPT_PROXY_SSLCERTTYPE: + case CURLOPT_PROXY_SSLKEY: + case CURLOPT_PROXY_SSLKEYTYPE: +#endif + case CURLOPT_KRBLEVEL: + str = PyText_AsString_NoNUL(obj, &encoded_obj); + if (str == NULL) + return NULL; + break; + case CURLOPT_POSTFIELDS: + if (PyText_AsStringAndSize(obj, &str, &len, &encoded_obj) != 0) + return NULL; + /* automatically set POSTFIELDSIZE */ + if (len <= INT_MAX) { + res = curl_easy_setopt(self->handle, CURLOPT_POSTFIELDSIZE, (long)len); + } else { + res = curl_easy_setopt(self->handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)len); + } + if (res != CURLE_OK) { + PyText_EncodedDecref(encoded_obj); + CURLERROR_RETVAL(); + } + break; + default: + PyErr_SetString(PyExc_TypeError, "strings are not supported for this option"); + return NULL; + } + assert(str != NULL); + /* Call setopt */ + res = curl_easy_setopt(self->handle, (CURLoption)option, str); + /* Check for errors */ + if (res != CURLE_OK) { + PyText_EncodedDecref(encoded_obj); + CURLERROR_RETVAL(); + } + /* libcurl does not copy the value of CURLOPT_POSTFIELDS */ + if (option == CURLOPT_POSTFIELDS) { + PyObject *store_obj; + + /* if obj was bytes, it was not encoded, and we need to incref obj. + * if obj was unicode, it was encoded, and we need to incref + * encoded_obj - except we can simply transfer ownership. + */ + if (encoded_obj) { + store_obj = encoded_obj; + } else { + /* no encoding is performed, incref the original object. */ + store_obj = obj; + Py_INCREF(store_obj); + } + + util_curl_xdecref(self, PYCURL_MEMGROUP_POSTFIELDS, self->handle); + self->postfields_obj = store_obj; + } else { + PyText_EncodedDecref(encoded_obj); + } + Py_RETURN_NONE; +} + + +#define IS_LONG_OPTION(o) (o < CURLOPTTYPE_OBJECTPOINT) +#define IS_OFF_T_OPTION(o) (o >= CURLOPTTYPE_OFF_T) + + +static PyObject * +do_curl_setopt_int(CurlObject *self, int option, PyObject *obj) +{ + long d; + PY_LONG_LONG ld; + int res; + + if (IS_LONG_OPTION(option)) { + d = PyInt_AsLong(obj); + res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d); + } else if (IS_OFF_T_OPTION(option)) { + /* this path should only be taken in Python 3 */ + ld = PyLong_AsLongLong(obj); + res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)ld); + } else { + PyErr_SetString(PyExc_TypeError, "integers are not supported for this option"); + return NULL; + } + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + Py_RETURN_NONE; +} + + +static PyObject * +do_curl_setopt_long(CurlObject *self, int option, PyObject *obj) +{ + int res; + PY_LONG_LONG d = PyLong_AsLongLong(obj); + if (d == -1 && PyErr_Occurred()) + return NULL; + + if (IS_LONG_OPTION(option) && (long)d == d) + res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d); + else if (IS_OFF_T_OPTION(option) && (curl_off_t)d == d) + res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)d); + else { + PyErr_SetString(PyExc_TypeError, "longs are not supported for this option"); + return NULL; + } + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + Py_RETURN_NONE; +} + + +#undef IS_LONG_OPTION +#undef IS_OFF_T_OPTION + + +#if PY_MAJOR_VERSION < 3 && !defined(PYCURL_AVOID_STDIO) +static PyObject * +do_curl_setopt_file_passthrough(CurlObject *self, int option, PyObject *obj) +{ + FILE *fp; + int res; + + fp = PyFile_AsFile(obj); + if (fp == NULL) { + PyErr_SetString(PyExc_TypeError, "second argument must be open file"); + return NULL; + } + + switch (option) { + case CURLOPT_READDATA: + res = curl_easy_setopt(self->handle, CURLOPT_READFUNCTION, fread); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + break; + case CURLOPT_WRITEDATA: + res = curl_easy_setopt(self->handle, CURLOPT_WRITEFUNCTION, fwrite); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + break; + case CURLOPT_WRITEHEADER: + res = curl_easy_setopt(self->handle, CURLOPT_HEADERFUNCTION, fwrite); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + break; + default: + PyErr_SetString(PyExc_TypeError, "files are not supported for this option"); + return NULL; + } + + res = curl_easy_setopt(self->handle, (CURLoption)option, fp); + if (res != CURLE_OK) { + /* + If we get here fread/fwrite are set as callbacks but the file pointer + is not set, program will crash if it does not reset read/write + callback. Also, we won't do the memory management later in this + function. + */ + CURLERROR_RETVAL(); + } + Py_INCREF(obj); + + switch (option) { + case CURLOPT_READDATA: + Py_CLEAR(self->readdata_fp); + self->readdata_fp = obj; + break; + case CURLOPT_WRITEDATA: + Py_CLEAR(self->writedata_fp); + self->writedata_fp = obj; + break; + case CURLOPT_WRITEHEADER: + Py_CLEAR(self->writeheader_fp); + self->writeheader_fp = obj; + break; + default: + assert(0); + break; + } + /* Return success */ + Py_RETURN_NONE; +} +#endif + + +static PyObject * +do_curl_setopt_httppost(CurlObject *self, int option, int which, PyObject *obj) +{ + struct curl_httppost *post = NULL; + struct curl_httppost *last = NULL; + /* List of all references that have been INCed as a result of + * this operation */ + PyObject *ref_params = NULL; + PyObject *nencoded_obj, *cencoded_obj, *oencoded_obj; + int which_httppost_item, which_httppost_option; + PyObject *httppost_option; + Py_ssize_t i, len; + int res; + + len = PyListOrTuple_Size(obj, which); + if (len == 0) + Py_RETURN_NONE; + + for (i = 0; i < len; i++) { + char *nstr = NULL, *cstr = NULL; + Py_ssize_t nlen = -1, clen = -1; + PyObject *listitem = PyListOrTuple_GetItem(obj, i, which); + + which_httppost_item = PyListOrTuple_Check(listitem); + if (!which_httppost_item) { + PyErr_SetString(PyExc_TypeError, "list items must be list or tuple objects"); + goto error; + } + if (PyListOrTuple_Size(listitem, which_httppost_item) != 2) { + PyErr_SetString(PyExc_TypeError, "list or tuple must contain two elements (name, value)"); + goto error; + } + if (PyText_AsStringAndSize(PyListOrTuple_GetItem(listitem, 0, which_httppost_item), + &nstr, &nlen, &nencoded_obj) != 0) { + PyErr_SetString(PyExc_TypeError, "list or tuple must contain a byte string or Unicode string with ASCII code points only as first element"); + goto error; + } + httppost_option = PyListOrTuple_GetItem(listitem, 1, which_httppost_item); + if (PyText_Check(httppost_option)) { + /* Handle strings as second argument for backwards compatibility */ + + if (PyText_AsStringAndSize(httppost_option, &cstr, &clen, &cencoded_obj)) { + PyText_EncodedDecref(nencoded_obj); + CURLERROR_SET_RETVAL(); + goto error; + } + /* INFO: curl_formadd() internally does memdup() the data, so + * embedded NUL characters _are_ allowed here. */ + res = curl_formadd(&post, &last, + CURLFORM_COPYNAME, nstr, + CURLFORM_NAMELENGTH, (long) nlen, + CURLFORM_COPYCONTENTS, cstr, + CURLFORM_CONTENTSLENGTH, (long) clen, + CURLFORM_END); + PyText_EncodedDecref(cencoded_obj); + if (res != CURLE_OK) { + PyText_EncodedDecref(nencoded_obj); + CURLERROR_SET_RETVAL(); + goto error; + } + } + /* assignment is intended */ + else if ((which_httppost_option = PyListOrTuple_Check(httppost_option))) { + /* Supports content, file and content-type */ + Py_ssize_t tlen = PyListOrTuple_Size(httppost_option, which_httppost_option); + int j, k, l; + struct curl_forms *forms = NULL; + + /* Sanity check that there are at least two tuple items */ + if (tlen < 2) { + PyText_EncodedDecref(nencoded_obj); + PyErr_SetString(PyExc_TypeError, "list or tuple must contain at least one option and one value"); + goto error; + } + + if (tlen % 2 == 1) { + PyText_EncodedDecref(nencoded_obj); + PyErr_SetString(PyExc_TypeError, "list or tuple must contain an even number of items"); + goto error; + } + + /* Allocate enough space to accommodate length options for content or buffers, plus a terminator. */ + forms = PyMem_New(struct curl_forms, (tlen*2) + 1); + if (forms == NULL) { + PyText_EncodedDecref(nencoded_obj); + PyErr_NoMemory(); + goto error; + } + + /* Iterate all the tuple members pairwise */ + for (j = 0, k = 0, l = 0; j < tlen; j += 2, l++) { + char *ostr; + Py_ssize_t olen; + int val; + + if (j == (tlen-1)) { + PyErr_SetString(PyExc_TypeError, "expected value"); + PyMem_Free(forms); + PyText_EncodedDecref(nencoded_obj); + goto error; + } + if (!PyInt_Check(PyListOrTuple_GetItem(httppost_option, j, which_httppost_option))) { + PyErr_SetString(PyExc_TypeError, "option must be an integer"); + PyMem_Free(forms); + PyText_EncodedDecref(nencoded_obj); + goto error; + } + if (!PyText_Check(PyListOrTuple_GetItem(httppost_option, j+1, which_httppost_option))) { + PyErr_SetString(PyExc_TypeError, "value must be a byte string or a Unicode string with ASCII code points only"); + PyMem_Free(forms); + PyText_EncodedDecref(nencoded_obj); + goto error; + } + + val = PyLong_AsLong(PyListOrTuple_GetItem(httppost_option, j, which_httppost_option)); + if (val != CURLFORM_COPYCONTENTS && + val != CURLFORM_FILE && + val != CURLFORM_FILENAME && + val != CURLFORM_CONTENTTYPE && + val != CURLFORM_BUFFER && + val != CURLFORM_BUFFERPTR) + { + PyErr_SetString(PyExc_TypeError, "unsupported option"); + PyMem_Free(forms); + PyText_EncodedDecref(nencoded_obj); + goto error; + } + + if (PyText_AsStringAndSize(PyListOrTuple_GetItem(httppost_option, j+1, which_httppost_option), &ostr, &olen, &oencoded_obj)) { + /* exception should be already set */ + PyMem_Free(forms); + PyText_EncodedDecref(nencoded_obj); + goto error; + } + forms[k].option = val; + forms[k].value = ostr; + ++k; + + if (val == CURLFORM_COPYCONTENTS) { + /* Contents can contain \0 bytes so we specify the length */ + forms[k].option = CURLFORM_CONTENTSLENGTH; + forms[k].value = (const char *)olen; + ++k; + } else if (val == CURLFORM_BUFFERPTR) { + PyObject *obj = NULL; + + if (ref_params == NULL) { + ref_params = PyList_New((Py_ssize_t)0); + if (ref_params == NULL) { + PyText_EncodedDecref(oencoded_obj); + PyMem_Free(forms); + PyText_EncodedDecref(nencoded_obj); + goto error; + } + } + + /* Keep a reference to the object that holds the ostr buffer. */ + if (oencoded_obj == NULL) { + obj = PyListOrTuple_GetItem(httppost_option, j+1, which_httppost_option); + } + else { + obj = oencoded_obj; + } + + /* Ensure that the buffer remains alive until curl_easy_cleanup() */ + if (PyList_Append(ref_params, obj) != 0) { + PyText_EncodedDecref(oencoded_obj); + PyMem_Free(forms); + PyText_EncodedDecref(nencoded_obj); + goto error; + } + + /* As with CURLFORM_COPYCONTENTS, specify the length. */ + forms[k].option = CURLFORM_BUFFERLENGTH; + forms[k].value = (const char *)olen; + ++k; + } + } + forms[k].option = CURLFORM_END; + res = curl_formadd(&post, &last, + CURLFORM_COPYNAME, nstr, + CURLFORM_NAMELENGTH, (long) nlen, + CURLFORM_ARRAY, forms, + CURLFORM_END); + PyText_EncodedDecref(oencoded_obj); + PyMem_Free(forms); + if (res != CURLE_OK) { + PyText_EncodedDecref(nencoded_obj); + CURLERROR_SET_RETVAL(); + goto error; + } + } else { + /* Some other type was given, ignore */ + PyText_EncodedDecref(nencoded_obj); + PyErr_SetString(PyExc_TypeError, "unsupported second type in tuple"); + goto error; + } + PyText_EncodedDecref(nencoded_obj); + } + res = curl_easy_setopt(self->handle, CURLOPT_HTTPPOST, post); + /* Check for errors */ + if (res != CURLE_OK) { + 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; + + Py_RETURN_NONE; + +error: + curl_formfree(post); + Py_XDECREF(ref_params); + return NULL; +} + + +static PyObject * +do_curl_setopt_list(CurlObject *self, int option, int which, PyObject *obj) +{ + struct curl_slist **old_slist = NULL; + struct curl_slist *slist = NULL; + Py_ssize_t len; + int res; + + switch (option) { + case CURLOPT_HTTP200ALIASES: + old_slist = &self->http200aliases; + break; + case CURLOPT_HTTPHEADER: + old_slist = &self->httpheader; + break; +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 37, 0) + case CURLOPT_PROXYHEADER: + old_slist = &self->proxyheader; + break; +#endif + case CURLOPT_POSTQUOTE: + old_slist = &self->postquote; + break; + case CURLOPT_PREQUOTE: + old_slist = &self->prequote; + break; + case CURLOPT_QUOTE: + old_slist = &self->quote; + break; + case CURLOPT_TELNETOPTIONS: + old_slist = &self->telnetoptions; + break; +#ifdef HAVE_CURLOPT_RESOLVE + case CURLOPT_RESOLVE: + old_slist = &self->resolve; + break; +#endif +#ifdef HAVE_CURL_7_20_0_OPTS + case CURLOPT_MAIL_RCPT: + old_slist = &self->mail_rcpt; + break; +#endif +#ifdef HAVE_CURLOPT_CONNECT_TO + case CURLOPT_CONNECT_TO: + old_slist = &self->connect_to; + break; +#endif + default: + /* None of the list options were recognized, raise exception */ + PyErr_SetString(PyExc_TypeError, "lists are not supported for this option"); + return NULL; + } + + len = PyListOrTuple_Size(obj, which); + if (len == 0) + Py_RETURN_NONE; + + /* Just to be sure we do not bug off here */ + assert(old_slist != NULL && slist == NULL); + + /* Handle regular list operations on the other options */ + slist = pycurl_list_or_tuple_to_slist(which, obj, len); + if (slist == NULL) { + return NULL; + } + res = curl_easy_setopt(self->handle, (CURLoption)option, slist); + /* Check for errors */ + if (res != CURLE_OK) { + curl_slist_free_all(slist); + CURLERROR_RETVAL(); + } + /* Finally, free previously allocated list and update */ + curl_slist_free_all(*old_slist); + *old_slist = slist; + + Py_RETURN_NONE; +} + + +static PyObject * +do_curl_setopt_callable(CurlObject *self, int option, PyObject *obj) +{ + /* We use function types here to make sure that our callback + * definitions exactly match the interface. + */ + const curl_write_callback w_cb = write_callback; + const curl_write_callback h_cb = header_callback; + const curl_read_callback r_cb = read_callback; + const curl_progress_callback pro_cb = progress_callback; +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0) + const curl_xferinfo_callback xferinfo_cb = xferinfo_callback; +#endif + const curl_debug_callback debug_cb = debug_callback; + const curl_ioctl_callback ioctl_cb = ioctl_callback; + const curl_opensocket_callback opensocket_cb = opensocket_callback; +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7) + const curl_closesocket_callback closesocket_cb = closesocket_callback; +#endif + const curl_seek_callback seek_cb = seek_callback; + + switch(option) { + case CURLOPT_WRITEFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->writedata_fp); + Py_CLEAR(self->w_cb); + self->w_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_WRITEFUNCTION, w_cb); + curl_easy_setopt(self->handle, CURLOPT_WRITEDATA, self); + break; + case CURLOPT_HEADERFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->writeheader_fp); + Py_CLEAR(self->h_cb); + self->h_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_HEADERFUNCTION, h_cb); + curl_easy_setopt(self->handle, CURLOPT_WRITEHEADER, self); + break; + case CURLOPT_READFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->readdata_fp); + Py_CLEAR(self->r_cb); + self->r_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_READFUNCTION, r_cb); + curl_easy_setopt(self->handle, CURLOPT_READDATA, self); + break; + case CURLOPT_PROGRESSFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->pro_cb); + self->pro_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_PROGRESSFUNCTION, pro_cb); + curl_easy_setopt(self->handle, CURLOPT_PROGRESSDATA, self); + break; +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0) + case CURLOPT_XFERINFOFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->xferinfo_cb); + self->xferinfo_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_XFERINFOFUNCTION, xferinfo_cb); + curl_easy_setopt(self->handle, CURLOPT_XFERINFODATA, self); + break; +#endif + case CURLOPT_DEBUGFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->debug_cb); + self->debug_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_DEBUGFUNCTION, debug_cb); + curl_easy_setopt(self->handle, CURLOPT_DEBUGDATA, self); + break; + case CURLOPT_IOCTLFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->ioctl_cb); + self->ioctl_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_IOCTLFUNCTION, ioctl_cb); + curl_easy_setopt(self->handle, CURLOPT_IOCTLDATA, self); + break; + case CURLOPT_OPENSOCKETFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->opensocket_cb); + self->opensocket_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_OPENSOCKETFUNCTION, opensocket_cb); + curl_easy_setopt(self->handle, CURLOPT_OPENSOCKETDATA, self); + break; +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7) + case CURLOPT_CLOSESOCKETFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->closesocket_cb); + self->closesocket_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_CLOSESOCKETFUNCTION, closesocket_cb); + curl_easy_setopt(self->handle, CURLOPT_CLOSESOCKETDATA, self); + break; +#endif + case CURLOPT_SOCKOPTFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->sockopt_cb); + self->sockopt_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_SOCKOPTFUNCTION, sockopt_cb); + curl_easy_setopt(self->handle, CURLOPT_SOCKOPTDATA, self); + break; +#ifdef HAVE_CURL_7_19_6_OPTS + case CURLOPT_SSH_KEYFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->ssh_key_cb); + self->ssh_key_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_SSH_KEYFUNCTION, ssh_key_cb); + curl_easy_setopt(self->handle, CURLOPT_SSH_KEYDATA, self); + break; +#endif + case CURLOPT_SEEKFUNCTION: + Py_INCREF(obj); + Py_CLEAR(self->seek_cb); + self->seek_cb = obj; + curl_easy_setopt(self->handle, CURLOPT_SEEKFUNCTION, seek_cb); + curl_easy_setopt(self->handle, CURLOPT_SEEKDATA, self); + break; + + default: + /* None of the function options were recognized, raise exception */ + PyErr_SetString(PyExc_TypeError, "functions are not supported for this option"); + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject * +do_curl_setopt_share(CurlObject *self, PyObject *obj) +{ + CurlShareObject *share; + int res; + + if (self->share == NULL && (obj == NULL || obj == Py_None)) + Py_RETURN_NONE; + + if (self->share) { + if (obj != Py_None) { + PyErr_SetString(ErrorObject, "Curl object already sharing. Unshare first."); + return NULL; + } + else { + share = self->share; + res = curl_easy_setopt(self->handle, CURLOPT_SHARE, NULL); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + self->share = NULL; + Py_DECREF(share); + Py_RETURN_NONE; + } + } + if (Py_TYPE(obj) != p_CurlShare_Type) { + PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt"); + return NULL; + } + share = (CurlShareObject*)obj; + res = curl_easy_setopt(self->handle, CURLOPT_SHARE, share->share_handle); + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + self->share = share; + Py_INCREF(share); + Py_RETURN_NONE; +} + + +PYCURL_INTERNAL PyObject * +do_curl_setopt_filelike(CurlObject *self, int option, PyObject *obj) +{ + const char *method_name; + PyObject *method; + + if (option == CURLOPT_READDATA) { + method_name = "read"; + } else { + method_name = "write"; + } + method = PyObject_GetAttrString(obj, method_name); + if (method) { + PyObject *arglist; + PyObject *rv; + + switch (option) { + case CURLOPT_READDATA: + option = CURLOPT_READFUNCTION; + break; + case CURLOPT_WRITEDATA: + option = CURLOPT_WRITEFUNCTION; + break; + case CURLOPT_WRITEHEADER: + option = CURLOPT_HEADERFUNCTION; + break; + default: + PyErr_SetString(PyExc_TypeError, "objects are not supported for this option"); + Py_DECREF(method); + return NULL; + } + + arglist = Py_BuildValue("(iO)", option, method); + /* reference is now in arglist */ + Py_DECREF(method); + if (arglist == NULL) { + return NULL; + } + rv = do_curl_setopt(self, arglist); + Py_DECREF(arglist); + return rv; + } else { + if (option == CURLOPT_READDATA) { + PyErr_SetString(PyExc_TypeError, "object given without a read method"); + } else { + PyErr_SetString(PyExc_TypeError, "object given without a write method"); + } + return NULL; + } +} + + +PYCURL_INTERNAL PyObject * +do_curl_setopt(CurlObject *self, PyObject *args) +{ + int option; + PyObject *obj; + int which; + + if (!PyArg_ParseTuple(args, "iO:setopt", &option, &obj)) + return NULL; + if (check_curl_state(self, 1 | 2, "setopt") != 0) + return NULL; + + /* early checks of option value */ + if (option <= 0) + goto error; + if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE) + goto error; + if (option % 10000 >= OPTIONS_SIZE) + goto error; + + /* Handle the case of None as the call of unsetopt() */ + if (obj == Py_None) { + return util_curl_unsetopt(self, option); + } + + /* Handle the case of string arguments */ + if (PyText_Check(obj)) { + return do_curl_setopt_string_impl(self, option, obj); + } + + /* Handle the case of integer arguments */ + if (PyInt_Check(obj)) { + return do_curl_setopt_int(self, option, obj); + } + + /* Handle the case of long arguments (used by *_LARGE options) */ + if (PyLong_Check(obj)) { + return do_curl_setopt_long(self, option, obj); + } + +#if PY_MAJOR_VERSION < 3 && !defined(PYCURL_AVOID_STDIO) + /* Handle the case of file objects */ + if (PyFile_Check(obj)) { + return do_curl_setopt_file_passthrough(self, option, obj); + } +#endif + + /* Handle the case of list or tuple objects */ + which = PyListOrTuple_Check(obj); + if (which) { + if (option == CURLOPT_HTTPPOST) { + return do_curl_setopt_httppost(self, option, which, obj); + } else { + return do_curl_setopt_list(self, option, which, obj); + } + } + + /* Handle the case of function objects for callbacks */ + if (PyFunction_Check(obj) || PyCFunction_Check(obj) || + PyCallable_Check(obj) || PyMethod_Check(obj)) { + return do_curl_setopt_callable(self, option, obj); + } + /* handle the SHARE case */ + if (option == CURLOPT_SHARE) { + return do_curl_setopt_share(self, obj); + } + + /* + Handle the case of file-like objects. + + Given an object with a write method, we will call the write method + from the appropriate callback. + + Files in Python 3 are no longer FILE * instances and therefore cannot + be directly given to curl, therefore this method handles all I/O to + Python objects. + + In Python 2 true file objects are FILE * instances and will be handled + by stdio passthrough code invoked above, and file-like objects will + be handled by this method. + */ + if (option == CURLOPT_READDATA || + option == CURLOPT_WRITEDATA || + option == CURLOPT_WRITEHEADER) + { + return do_curl_setopt_filelike(self, option, obj); + } + + /* Failed to match any of the function signatures -- return error */ +error: + PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt"); + return NULL; +} + + +PYCURL_INTERNAL PyObject * +do_curl_setopt_string(CurlObject *self, PyObject *args) +{ + int option; + PyObject *obj; + + if (!PyArg_ParseTuple(args, "iO:setopt", &option, &obj)) + return NULL; + if (check_curl_state(self, 1 | 2, "setopt") != 0) + return NULL; + + /* Handle the case of string arguments */ + if (PyText_Check(obj)) { + return do_curl_setopt_string_impl(self, option, obj); + } + + /* Failed to match any of the function signatures -- return error */ + PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt_string"); + return NULL; +} + + +#if defined(HAVE_CURL_OPENSSL) +/* load ca certs from string */ +PYCURL_INTERNAL PyObject * +do_curl_set_ca_certs(CurlObject *self, PyObject *args) +{ + PyObject *cadata; + PyObject *encoded_obj; + char *buffer; + Py_ssize_t length; + int res; + + if (!PyArg_ParseTuple(args, "O:cadata", &cadata)) + return NULL; + + // This may result in cadata string being encoded twice, + // not going to worry about it for now + if (!PyText_Check(cadata)) { + PyErr_SetString(PyExc_TypeError, "set_ca_certs argument must be a byte string or a Unicode string with ASCII code points only"); + return NULL; + } + + res = PyText_AsStringAndSize(cadata, &buffer, &length, &encoded_obj); + if (res) { + PyErr_SetString(PyExc_TypeError, "set_ca_certs argument must be a byte string or a Unicode string with ASCII code points only"); + return NULL; + } + + Py_CLEAR(self->ca_certs_obj); + if (encoded_obj) { + self->ca_certs_obj = encoded_obj; + } else { + Py_INCREF(cadata); + self->ca_certs_obj = cadata; + } + + res = curl_easy_setopt(self->handle, CURLOPT_SSL_CTX_FUNCTION, (curl_ssl_ctx_callback) ssl_ctx_callback); + if (res != CURLE_OK) { + Py_CLEAR(self->ca_certs_obj); + CURLERROR_RETVAL(); + } + + res = curl_easy_setopt(self->handle, CURLOPT_SSL_CTX_DATA, self); + if (res != CURLE_OK) { + Py_CLEAR(self->ca_certs_obj); + CURLERROR_RETVAL(); + } + + Py_RETURN_NONE; +} +#endif diff -Nru pycurl-7.43.0.1/src/easyperform.c pycurl-7.43.0.2/src/easyperform.c --- pycurl-7.43.0.1/src/easyperform.c 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/src/easyperform.c 2018-05-23 18:34:47.000000000 +0000 @@ -0,0 +1,115 @@ +#include "pycurl.h" + + +/* --------------- perform --------------- */ + +PYCURL_INTERNAL PyObject * +do_curl_perform(CurlObject *self) +{ + int res; + + if (check_curl_state(self, 1 | 2, "perform") != 0) { + return NULL; + } + + PYCURL_BEGIN_ALLOW_THREADS + res = curl_easy_perform(self->handle); + PYCURL_END_ALLOW_THREADS + + if (res != CURLE_OK) { + CURLERROR_RETVAL(); + } + Py_RETURN_NONE; +} + + +PYCURL_INTERNAL PyObject * +do_curl_perform_rb(CurlObject *self) +{ + PyObject *v, *io; + + io = PyEval_CallObject(bytesio, NULL); + if (io == NULL) { + return NULL; + } + + v = do_curl_setopt_filelike(self, CURLOPT_WRITEDATA, io); + if (v == NULL) { + Py_DECREF(io); + return NULL; + } + + v = do_curl_perform(self); + if (v == NULL) { + return NULL; + } + + v = PyObject_CallMethod(io, "getvalue", NULL); + Py_DECREF(io); + return v; +} + +#if PY_MAJOR_VERSION >= 3 +PYCURL_INTERNAL PyObject * +do_curl_perform_rs(CurlObject *self) +{ + PyObject *v, *decoded; + + v = do_curl_perform_rb(self); + if (v == NULL) { + return NULL; + } + + decoded = PyUnicode_FromEncodedObject(v, NULL, NULL); + Py_DECREF(v); + return decoded; +} +#endif + + +/* --------------- pause --------------- */ + + +/* curl_easy_pause() can be called from inside a callback or outside */ +PYCURL_INTERNAL PyObject * +do_curl_pause(CurlObject *self, PyObject *args) +{ + int bitmask; + CURLcode res; +#ifdef WITH_THREAD + PyThreadState *saved_state; +#endif + + if (!PyArg_ParseTuple(args, "i:pause", &bitmask)) { + return NULL; + } + if (check_curl_state(self, 1, "pause") != 0) { + return NULL; + } + +#ifdef WITH_THREAD + /* Save handle to current thread (used as context for python callbacks) */ + saved_state = self->state; + PYCURL_BEGIN_ALLOW_THREADS + + /* We must allow threads here because unpausing a handle can cause + some of its callbacks to be invoked immediately, from inside + curl_easy_pause() */ +#endif + + res = curl_easy_pause(self->handle, bitmask); + +#ifdef WITH_THREAD + PYCURL_END_ALLOW_THREADS + + /* Restore the thread-state to whatever it was on entry */ + self->state = saved_state; +#endif + + if (res != CURLE_OK) { + CURLERROR_MSG("pause/unpause failed"); + } else { + Py_INCREF(Py_None); + return Py_None; + } +} diff -Nru pycurl-7.43.0.1/src/module.c pycurl-7.43.0.2/src/module.c --- pycurl-7.43.0.1/src/module.c 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/src/module.c 2018-05-23 18:34:47.000000000 +0000 @@ -13,6 +13,9 @@ PYCURL_INTERNAL char *empty_keywords[] = { NULL }; +PYCURL_INTERNAL PyObject *bytesio = NULL; +PYCURL_INTERNAL PyObject *stringio = NULL; + /* Initialized during module init */ PYCURL_INTERNAL char *g_pycurl_useragent = NULL; @@ -321,9 +324,14 @@ const curl_version_info_data *vi; const char *libcurl_version, *runtime_ssl_lib; size_t libcurl_version_len, pycurl_version_len; + PyObject *xio_module = NULL; PyObject *collections_module = NULL; PyObject *named_tuple = NULL; PyObject *arglist = NULL; + + assert(Curl_Type.tp_weaklistoffset > 0); + assert(CurlMulti_Type.tp_weaklistoffset > 0); + assert(CurlShare_Type.tp_weaklistoffset > 0); /* Check the version, as this has caused nasty problems in * some cases. */ @@ -340,7 +348,8 @@ /* Our compiled crypto locks should correspond to runtime ssl library. */ if (vi->ssl_version == NULL) { runtime_ssl_lib = "none/other"; - } else if (!strncmp(vi->ssl_version, "OpenSSL/", 8) || !strncmp(vi->ssl_version, "LibreSSL/", 9)) { + } else if (!strncmp(vi->ssl_version, "OpenSSL/", 8) || !strncmp(vi->ssl_version, "LibreSSL/", 9) || + !strncmp(vi->ssl_version, "BoringSSL", 9)) { runtime_ssl_lib = "openssl"; } else if (!strncmp(vi->ssl_version, "GnuTLS/", 7)) { runtime_ssl_lib = "gnutls"; @@ -452,6 +461,9 @@ /* constants for ioctl callback argument values */ insint_c(d, "IOCMD_NOP", CURLIOCMD_NOP); insint_c(d, "IOCMD_RESTARTREAD", CURLIOCMD_RESTARTREAD); + + /* opensocketfunction return value */ + insint_c(d, "SOCKET_BAD", CURL_SOCKET_BAD); /* curl_infotype: the kind of data that is passed to information_callback */ /* XXX do we actually need curl_infotype in pycurl ??? */ @@ -693,11 +705,11 @@ insint_c(d, "USERPWD", CURLOPT_USERPWD); insint_c(d, "WRITEFUNCTION", CURLOPT_WRITEFUNCTION); #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 20, 0) - insint_c(d, "OPT_RTSP_STREAM_URI", CURLOPT_RTSP_STREAM_URI); - insint_c(d, "OPT_RTSP_REQUEST", CURLOPT_RTSP_REQUEST); - insint_c(d, "OPT_RTSP_SESSION_ID", CURLOPT_RTSP_SESSION_ID); insint_c(d, "OPT_RTSP_CLIENT_CSEQ", CURLOPT_RTSP_CLIENT_CSEQ); + insint_c(d, "OPT_RTSP_REQUEST", CURLOPT_RTSP_REQUEST); insint_c(d, "OPT_RTSP_SERVER_CSEQ", CURLOPT_RTSP_SERVER_CSEQ); + insint_c(d, "OPT_RTSP_SESSION_ID", CURLOPT_RTSP_SESSION_ID); + insint_c(d, "OPT_RTSP_STREAM_URI", CURLOPT_RTSP_STREAM_URI); insint_c(d, "OPT_RTSP_TRANSPORT", CURLOPT_RTSP_TRANSPORT); #endif #ifdef HAVE_CURLOPT_USERNAME @@ -832,6 +844,7 @@ insint_c(d, "FTP_ACCOUNT", CURLOPT_FTP_ACCOUNT); insint_c(d, "IGNORE_CONTENT_LENGTH", CURLOPT_IGNORE_CONTENT_LENGTH); insint_c(d, "COOKIELIST", CURLOPT_COOKIELIST); + insint_c(d, "OPT_COOKIELIST", CURLOPT_COOKIELIST); insint_c(d, "FTP_SKIP_PASV_IP", CURLOPT_FTP_SKIP_PASV_IP); insint_c(d, "FTP_FILEMETHOD", CURLOPT_FTP_FILEMETHOD); insint_c(d, "CONNECT_ONLY", CURLOPT_CONNECT_ONLY); @@ -878,6 +891,12 @@ #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 52, 0) insint_c(d, "PROXY_CAPATH", CURLOPT_PROXY_CAPATH); insint_c(d, "PROXY_CAINFO", CURLOPT_PROXY_CAINFO); + insint_c(d, "PRE_PROXY", CURLOPT_PRE_PROXY); + insint_c(d, "PROXY_SSLCERT", CURLOPT_PROXY_SSLCERT); + insint_c(d, "PROXY_SSLCERTTYPE", CURLOPT_PROXY_SSLCERTTYPE); + insint_c(d, "PROXY_SSLKEY", CURLOPT_PROXY_SSLKEY); + insint_c(d, "PROXY_SSLKEYTYPE", CURLOPT_PROXY_SSLKEYTYPE); + insint_c(d, "PROXY_SSL_VERIFYPEER", CURLOPT_PROXY_SSL_VERIFYPEER); #endif insint_c(d, "COPYPOSTFIELDS", CURLOPT_COPYPOSTFIELDS); insint_c(d, "SSH_HOST_PUBLIC_KEY_MD5", CURLOPT_SSH_HOST_PUBLIC_KEY_MD5); @@ -1321,6 +1340,36 @@ } #endif +#if PY_MAJOR_VERSION >= 3 + xio_module = PyImport_ImportModule("io"); + if (xio_module == NULL) { + goto error; + } + bytesio = PyObject_GetAttrString(xio_module, "BytesIO"); + if (bytesio == NULL) { + goto error; + } + stringio = PyObject_GetAttrString(xio_module, "StringIO"); + if (stringio == NULL) { + goto error; + } +#else + xio_module = PyImport_ImportModule("cStringIO"); + if (xio_module == NULL) { + PyErr_Clear(); + xio_module = PyImport_ImportModule("StringIO"); + if (xio_module == NULL) { + goto error; + } + } + stringio = PyObject_GetAttrString(xio_module, "StringIO"); + if (stringio == NULL) { + goto error; + } + bytesio = stringio; + Py_INCREF(bytesio); +#endif + collections_module = PyImport_ImportModule("collections"); if (collections_module == NULL) { goto error; @@ -1371,6 +1420,9 @@ Py_XDECREF(ErrorObject); Py_XDECREF(collections_module); Py_XDECREF(named_tuple); + Py_XDECREF(xio_module); + Py_XDECREF(bytesio); + Py_XDECREF(stringio); Py_XDECREF(arglist); #ifdef HAVE_CURL_7_19_6_OPTS Py_XDECREF(khkey_type); diff -Nru pycurl-7.43.0.1/src/multi.c pycurl-7.43.0.2/src/multi.c --- pycurl-7.43.0.1/src/multi.c 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/src/multi.c 2018-05-22 03:44:40.000000000 +0000 @@ -67,6 +67,12 @@ ++ptr) assert(*ptr == 0); + self->easy_object_dict = PyDict_New(); + if (self->easy_object_dict == NULL) { + Py_DECREF(self); + return NULL; + } + /* Allocate libcurl multi handle */ self->multi_handle = curl_multi_init(); if (self->multi_handle == NULL) { @@ -81,9 +87,11 @@ util_multi_close(CurlMultiObject *self) { assert(self != NULL); + #ifdef WITH_THREAD self->state = NULL; #endif + if (self->multi_handle != NULL) { CURLM *multi_handle = self->multi_handle; self->multi_handle = NULL; @@ -95,6 +103,7 @@ static void util_multi_xdecref(CurlMultiObject *self) { + Py_CLEAR(self->easy_object_dict); Py_CLEAR(self->dict); Py_CLEAR(self->t_cb); Py_CLEAR(self->s_cb); @@ -110,6 +119,10 @@ util_multi_xdecref(self); util_multi_close(self); + if (self->weakreflist != NULL) { + PyObject_ClearWeakRefs((PyObject *) self); + } + CurlMulti_Type.tp_free(self); Py_TRASHCAN_SAFE_END(self); } @@ -144,6 +157,7 @@ #define VISIT(v) if ((v) != NULL && ((err = visit(v, arg)) != 0)) return err VISIT(self->dict); + VISIT(self->easy_object_dict); return 0; #undef VISIT @@ -607,13 +621,18 @@ PyErr_SetString(ErrorObject, "curl object already on this multi-stack"); return NULL; } + + PyDict_SetItem(self->easy_object_dict, (PyObject *) obj, Py_True); + assert(obj->multi_stack == NULL); res = curl_multi_add_handle(self->multi_handle, obj->handle); if (res != CURLM_OK) { CURLERROR_MSG("curl_multi_add_handle() failed due to internal errors"); + PyDict_DelItem(self->easy_object_dict, (PyObject *) obj); } obj->multi_stack = self; Py_INCREF(self); + Py_RETURN_NONE; } @@ -632,6 +651,9 @@ } if (obj->handle == NULL) { /* CurlObject handle already closed -- ignore */ + if (PyDict_GetItem(self->easy_object_dict, (PyObject *) obj)) { + PyDict_DelItem(self->easy_object_dict, (PyObject *) obj); + } goto done; } if (obj->multi_stack != self) { @@ -639,7 +661,13 @@ return NULL; } res = curl_multi_remove_handle(self->multi_handle, obj->handle); - if (res != CURLM_OK) { + if (res == CURLM_OK) { + PyDict_DelItem(self->easy_object_dict, (PyObject *) obj); + // if PyDict_DelItem fails, remove_handle call will also fail. + // but the dictionary should always have our object in it + // hence this failure shouldn't happen unless something unaccounted + // for went wrong + } else { CURLERROR_MSG("curl_multi_remove_handle() failed due to internal errors"); } assert(obj->multi_stack == self); @@ -963,12 +991,12 @@ 0, /* tp_setattro */ #endif 0, /* tp_as_buffer */ - Py_TPFLAGS_HAVE_GC, /* tp_flags */ + PYCURL_TYPE_FLAGS, /* tp_flags */ multi_doc, /* tp_doc */ (traverseproc)do_multi_traverse, /* tp_traverse */ (inquiry)do_multi_clear, /* tp_clear */ 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ + offsetof(CurlMultiObject, weakreflist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ curlmultiobject_methods, /* tp_methods */ diff -Nru pycurl-7.43.0.1/src/pycurl.h pycurl-7.43.0.2/src/pycurl.h --- pycurl-7.43.0.1/src/pycurl.h 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/src/pycurl.h 2018-05-23 18:34:47.000000000 +0000 @@ -249,11 +249,13 @@ #if PY_MAJOR_VERSION >= 3 # define PyText_FromFormat(format, str) PyUnicode_FromFormat((format), (str)) # define PyText_FromString(str) PyUnicode_FromString(str) +# define PyByteStr_FromString(str) PyBytes_FromString(str) # define PyByteStr_Check(obj) PyBytes_Check(obj) # define PyByteStr_AsStringAndSize(obj, buffer, length) PyBytes_AsStringAndSize((obj), (buffer), (length)) #else # define PyText_FromFormat(format, str) PyString_FromFormat((format), (str)) # define PyText_FromString(str) PyString_FromString(str) +# define PyByteStr_FromString(str) PyString_FromString(str) # define PyByteStr_Check(obj) PyString_Check(obj) # define PyByteStr_AsStringAndSize(obj, buffer, length) PyString_AsStringAndSize((obj), (buffer), (length)) #endif @@ -265,23 +267,23 @@ PyText_AsString_NoNUL(PyObject *obj, PyObject **encoded_obj); PYCURL_INTERNAL int PyText_Check(PyObject *o); +PYCURL_INTERNAL PyObject * +PyText_FromString_Ignore(const char *string); + +struct CurlObject; + +PYCURL_INTERNAL void +create_and_set_error_object(struct CurlObject *self, int code); /* Raise exception based on return value `res' and `self->error' */ #define CURLERROR_RETVAL() do {\ - PyObject *v; \ - self->error[sizeof(self->error) - 1] = 0; \ - v = Py_BuildValue("(is)", (int) (res), self->error); \ - if (v != NULL) { PyErr_SetObject(ErrorObject, v); Py_DECREF(v); } \ + create_and_set_error_object((self), (int) (res)); \ return NULL; \ } while (0) -#define CURLERROR_SET_RETVAL() do {\ - PyObject *v; \ - self->error[sizeof(self->error) - 1] = 0; \ - v = Py_BuildValue("(is)", (int) (res), self->error); \ - if (v != NULL) { PyErr_SetObject(ErrorObject, v); Py_DECREF(v); } \ -} while (0) +#define CURLERROR_SET_RETVAL() \ + create_and_set_error_object((self), (int) (res)); #define CURLERROR_RETVAL_MULTI_DONE() do {\ PyObject *v; \ @@ -291,6 +293,7 @@ } while (0) /* Raise exception based on return value `res' and custom message */ +/* msg should be ASCII */ #define CURLERROR_MSG(msg) do {\ PyObject *v; const char *m = (msg); \ v = Py_BuildValue("(is)", (int) (res), (m)); \ @@ -333,6 +336,8 @@ typedef struct CurlObject { PyObject_HEAD PyObject *dict; /* Python attributes dictionary */ + // https://docs.python.org/3/extending/newtypes.html + PyObject *weakreflist; CURL *handle; #ifdef WITH_THREAD PyThreadState *state; @@ -392,6 +397,8 @@ typedef struct CurlMultiObject { PyObject_HEAD PyObject *dict; /* Python attributes dictionary */ + // https://docs.python.org/3/extending/newtypes.html + PyObject *weakreflist; CURLM *multi_handle; #ifdef WITH_THREAD PyThreadState *state; @@ -402,6 +409,8 @@ /* callbacks */ PyObject *t_cb; PyObject *s_cb; + + PyObject *easy_object_dict; } CurlMultiObject; typedef struct { @@ -411,6 +420,8 @@ typedef struct CurlShareObject { PyObject_HEAD PyObject *dict; /* Python attributes dictionary */ + // https://docs.python.org/3/extending/newtypes.html + PyObject *weakreflist; CURLSH *share_handle; #ifdef WITH_THREAD ShareLock *lock; /* lock object to implement CURLSHOPT_LOCKFUNC */ @@ -468,6 +479,95 @@ PYCURL_INTERNAL PyObject * do_version_info(PyObject *dummy, PyObject *args); +PYCURL_INTERNAL PyObject * +do_curl_setopt(CurlObject *self, PyObject *args); +PYCURL_INTERNAL PyObject * +do_curl_setopt_string(CurlObject *self, PyObject *args); +PYCURL_INTERNAL PyObject * +do_curl_unsetopt(CurlObject *self, PyObject *args); +#if defined(HAVE_CURL_OPENSSL) +PYCURL_INTERNAL PyObject * +do_curl_set_ca_certs(CurlObject *self, PyObject *args); +#endif +PYCURL_INTERNAL PyObject * +do_curl_perform(CurlObject *self); +PYCURL_INTERNAL PyObject * +do_curl_perform_rb(CurlObject *self); +#if PY_MAJOR_VERSION >= 3 +PYCURL_INTERNAL PyObject * +do_curl_perform_rs(CurlObject *self); +#else +# define do_curl_perform_rs do_curl_perform_rb +#endif + +PYCURL_INTERNAL PyObject * +do_curl_pause(CurlObject *self, PyObject *args); + +PYCURL_INTERNAL int +check_curl_state(const CurlObject *self, int flags, const char *name); +PYCURL_INTERNAL void +util_curl_xdecref(CurlObject *self, int flags, CURL *handle); +PYCURL_INTERNAL PyObject * +do_curl_setopt_filelike(CurlObject *self, int option, PyObject *obj); + +PYCURL_INTERNAL PyObject * +do_curl_getinfo_raw(CurlObject *self, PyObject *args); +#if PY_MAJOR_VERSION >= 3 +PYCURL_INTERNAL PyObject * +do_curl_getinfo(CurlObject *self, PyObject *args); +#else +# define do_curl_getinfo do_curl_getinfo_raw +#endif +PYCURL_INTERNAL PyObject * +do_curl_errstr(CurlObject *self); +#if PY_MAJOR_VERSION >= 3 +PYCURL_INTERNAL PyObject * +do_curl_errstr_raw(CurlObject *self); +#else +# define do_curl_errstr_raw do_curl_errstr +#endif + +PYCURL_INTERNAL size_t +write_callback(char *ptr, size_t size, size_t nmemb, void *stream); +PYCURL_INTERNAL size_t +header_callback(char *ptr, size_t size, size_t nmemb, void *stream); +PYCURL_INTERNAL curl_socket_t +opensocket_callback(void *clientp, curlsocktype purpose, + struct curl_sockaddr *address); +PYCURL_INTERNAL int +sockopt_cb(void *clientp, curl_socket_t curlfd, curlsocktype purpose); +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 7) +PYCURL_INTERNAL int +closesocket_callback(void *clientp, curl_socket_t curlfd); +#endif +#ifdef HAVE_CURL_7_19_6_OPTS +PYCURL_INTERNAL int +ssh_key_cb(CURL *easy, const struct curl_khkey *knownkey, + const struct curl_khkey *foundkey, int khmatch, void *clientp); +#endif +PYCURL_INTERNAL int +seek_callback(void *stream, curl_off_t offset, int origin); +PYCURL_INTERNAL size_t +read_callback(char *ptr, size_t size, size_t nmemb, void *stream); +PYCURL_INTERNAL int +progress_callback(void *stream, + double dltotal, double dlnow, double ultotal, double ulnow); +#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 32, 0) +PYCURL_INTERNAL int +xferinfo_callback(void *stream, + curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow); +#endif +PYCURL_INTERNAL int +debug_callback(CURL *curlobj, curl_infotype type, + char *buffer, size_t total_size, void *stream); +PYCURL_INTERNAL curlioerr +ioctl_callback(CURL *curlobj, int cmd, void *stream); +#if defined(HAVE_CURL_OPENSSL) +PYCURL_INTERNAL CURLcode +ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *ptr); +#endif + #if !defined(PYCURL_SINGLE_FILE) /* Type objects */ extern PyTypeObject Curl_Type; @@ -488,6 +588,8 @@ extern char *g_pycurl_useragent; extern PYCURL_INTERNAL char *empty_keywords[]; +extern PYCURL_INTERNAL PyObject *bytesio; +extern PYCURL_INTERNAL PyObject *stringio; #if PY_MAJOR_VERSION >= 3 extern PyMethodDef curlobject_methods[]; @@ -496,5 +598,11 @@ #endif #endif /* !PYCURL_SINGLE_FILE */ +#if PY_MAJOR_VERSION >= 3 +# define PYCURL_TYPE_FLAGS Py_TPFLAGS_HAVE_GC +#else +# define PYCURL_TYPE_FLAGS Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_WEAKREFS +#endif + /* vi:ts=4:et:nowrap */ diff -Nru pycurl-7.43.0.1/src/share.c pycurl-7.43.0.2/src/share.c --- pycurl-7.43.0.1/src/share.c 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/src/share.c 2018-05-22 03:44:40.000000000 +0000 @@ -51,9 +51,10 @@ /* tp_alloc is expected to return zeroed memory */ for (ptr = (int *) &self->dict; ptr < (int *) (((char *) self) + sizeof(CurlShareObject)); - ++ptr) + ++ptr) { assert(*ptr == 0); - + } + #ifdef WITH_THREAD self->lock = share_lock_new(); assert(self->lock != NULL); @@ -127,6 +128,10 @@ share_lock_destroy(self->lock); #endif + if (self->weakreflist != NULL) { + PyObject_ClearWeakRefs((PyObject *) self); + } + CurlShare_Type.tp_free(self); Py_TRASHCAN_SAFE_END(self); } @@ -306,12 +311,12 @@ 0, /* tp_setattro */ #endif 0, /* tp_as_buffer */ - Py_TPFLAGS_HAVE_GC, /* tp_flags */ + PYCURL_TYPE_FLAGS, /* tp_flags */ share_doc, /* tp_doc */ (traverseproc)do_share_traverse, /* tp_traverse */ (inquiry)do_share_clear, /* tp_clear */ 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ + offsetof(CurlShareObject, weakreflist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ curlshareobject_methods, /* tp_methods */ diff -Nru pycurl-7.43.0.1/src/stringcompat.c pycurl-7.43.0.2/src/stringcompat.c --- pycurl-7.43.0.1/src/stringcompat.c 2017-12-03 19:02:58.000000000 +0000 +++ pycurl-7.43.0.2/src/stringcompat.c 2018-05-23 04:18:06.000000000 +0000 @@ -62,3 +62,25 @@ return PyUnicode_Check(o) || PyString_Check(o); } #endif + +PYCURL_INTERNAL PyObject * +PyText_FromString_Ignore(const char *string) +{ + PyObject *v; + +#if PY_MAJOR_VERSION >= 3 + PyObject *u; + + v = Py_BuildValue("y", string); + if (v == NULL) { + return NULL; + } + + u = PyUnicode_FromEncodedObject(v, NULL, "replace"); + Py_DECREF(v); + return u; +#else + v = Py_BuildValue("s", string); + return v; +#endif +} diff -Nru pycurl-7.43.0.1/src/threadsupport.c pycurl-7.43.0.2/src/threadsupport.c --- pycurl-7.43.0.1/src/threadsupport.c 2017-12-03 19:03:13.000000000 +0000 +++ pycurl-7.43.0.2/src/threadsupport.c 2018-05-19 00:47:45.000000000 +0000 @@ -88,6 +88,7 @@ #ifdef PYCURL_NEED_OPENSSL_TSL +#if OPENSSL_VERSION_NUMBER < 0x10100000 static PyThread_type_lock *pycurl_openssl_tsl = NULL; static void @@ -100,15 +101,27 @@ } } +#if OPENSSL_VERSION_NUMBER >= 0x10000000 +/* use new CRYPTO_THREADID API. */ +static void +pycurl_ssl_threadid_callback(CRYPTO_THREADID *id) +{ + CRYPTO_THREADID_set_numeric(id, (unsigned long)PyThread_get_thread_ident()); +} +#else +/* deprecated CRYPTO_set_id_callback() API. */ static unsigned long pycurl_ssl_id(void) { return (unsigned long) PyThread_get_thread_ident(); } +#endif +#endif PYCURL_INTERNAL int pycurl_ssl_init(void) { +#if OPENSSL_VERSION_NUMBER < 0x10100000 int i, c = CRYPTO_num_locks(); pycurl_openssl_tsl = PyMem_New(PyThread_type_lock, c); @@ -130,18 +143,28 @@ } } +#if OPENSSL_VERSION_NUMBER >= 0x10000000 + CRYPTO_THREADID_set_callback(pycurl_ssl_threadid_callback); +#else CRYPTO_set_id_callback(pycurl_ssl_id); +#endif CRYPTO_set_locking_callback(pycurl_ssl_lock); +#endif return 0; } PYCURL_INTERNAL void pycurl_ssl_cleanup(void) { +#if OPENSSL_VERSION_NUMBER < 0x10100000 if (pycurl_openssl_tsl) { int i, c = CRYPTO_num_locks(); +#if OPENSSL_VERSION_NUMBER >= 0x10000000 + CRYPTO_THREADID_set_callback(NULL); +#else CRYPTO_set_id_callback(NULL); +#endif CRYPTO_set_locking_callback(NULL); for (i = 0; i < c; ++i) { @@ -151,6 +174,7 @@ PyMem_Free(pycurl_openssl_tsl); pycurl_openssl_tsl = NULL; } +#endif } #endif diff -Nru pycurl-7.43.0.1/src/util.c pycurl-7.43.0.2/src/util.c --- pycurl-7.43.0.1/src/util.c 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/src/util.c 2018-05-23 04:18:06.000000000 +0000 @@ -0,0 +1,31 @@ +#include "pycurl.h" + +static PyObject * +create_error_object(CurlObject *self, int code) +{ + PyObject *s, *v; + + s = PyText_FromString_Ignore(self->error); + if (s == NULL) { + return NULL; + } + v = Py_BuildValue("(iO)", code, s); + if (v == NULL) { + Py_DECREF(s); + return NULL; + } + return v; +} + +PYCURL_INTERNAL void +create_and_set_error_object(CurlObject *self, int code) +{ + PyObject *e; + + self->error[sizeof(self->error) - 1] = 0; + e = create_error_object(self, code); + if (e != NULL) { + PyErr_SetObject(ErrorObject, e); + Py_DECREF(e); + } +} diff -Nru pycurl-7.43.0.1/tests/app.py pycurl-7.43.0.2/tests/app.py --- pycurl-7.43.0.1/tests/app.py 2017-12-03 19:03:13.000000000 +0000 +++ pycurl-7.43.0.2/tests/app.py 2018-05-23 18:34:47.000000000 +0000 @@ -123,3 +123,22 @@ def utf8_body(): # bottle encodes the body return 'Дружба народов' + +@app.route('/invalid_utf8_body') +def invalid_utf8_body(): + # bottle encodes the body + raise bottle.HTTPResponse(b'\xb3\xd2\xda\xcd\xd7', 200) + +@app.route('/set_cookie_invalid_utf8') +def set_cookie_invalid_utf8(): + bottle.response.set_header('Set-Cookie', '\xb3\xd2\xda\xcd\xd7=%96%A6g%9Ay%B0%A5g%A7tm%7C%95%9A') + return 'cookie set' + +@app.route('/content_type_invalid_utf8') +def content_type_invalid_utf8(): + bottle.response.set_header('Content-Type', '\xb3\xd2\xda\xcd\xd7') + return 'content type set' + +@app.route('/status_invalid_utf8') +def status_invalid_utf8(): + raise bottle.HTTPResponse('status set', '555 \xb3\xd2\xda\xcd\xd7') diff -Nru pycurl-7.43.0.1/tests/cadata_test.py pycurl-7.43.0.2/tests/cadata_test.py --- pycurl-7.43.0.1/tests/cadata_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/cadata_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -13,7 +13,7 @@ class CaCertsTest(unittest.TestCase): def setUp(self): - self.curl = util.DefaultCurl() + self.curl = util.DefaultCurlLocalhost(8384) def tearDown(self): self.curl.close() @@ -30,3 +30,14 @@ self.curl.setopt(pycurl.SSL_VERIFYPEER, 1) self.curl.perform() assert sio.getvalue().decode() == 'success' + + @util.only_ssl_backends('openssl') + def test_set_ca_certs_bytes(self): + self.curl.set_ca_certs(util.b('hello world\x02\xe0')) + + @util.only_ssl_backends('openssl') + def test_set_ca_certs_bogus_type(self): + try: + self.curl.set_ca_certs(42) + except TypeError as e: + self.assertEqual('set_ca_certs argument must be a byte string or a Unicode string with ASCII code points only', str(e)) diff -Nru pycurl-7.43.0.1/tests/certinfo_test.py pycurl-7.43.0.2/tests/certinfo_test.py --- pycurl-7.43.0.1/tests/certinfo_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/certinfo_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -13,7 +13,7 @@ class CertinfoTest(unittest.TestCase): def setUp(self): - self.curl = util.DefaultCurl() + self.curl = util.DefaultCurlLocalhost(8383) def tearDown(self): self.curl.close() @@ -63,5 +63,33 @@ certinfo_dict = {} for entry in certinfo: certinfo_dict[entry[0]] = entry[1] - assert 'Subject' in certinfo_dict - assert 'PycURL test suite' in certinfo_dict['Subject'] + assert util.u('Subject') in certinfo_dict + assert util.u('PycURL test suite') in certinfo_dict[util.u('Subject')] + + # CURLOPT_CERTINFO was introduced in libcurl-7.19.1 + @util.min_libcurl(7, 19, 1) + @util.only_ssl + def test_getinfo_raw_certinfo(self): + # CURLOPT_CERTINFO only works with OpenSSL + if 'openssl' not in pycurl.version.lower(): + raise nose.plugins.skip.SkipTest('libcurl does not use openssl') + + self.curl.setopt(pycurl.URL, 'https://localhost:8383/success') + sio = util.BytesIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.setopt(pycurl.OPT_CERTINFO, 1) + # self signed certificate + self.curl.setopt(pycurl.SSL_VERIFYPEER, 0) + self.curl.perform() + assert sio.getvalue().decode() == 'success' + + certinfo = self.curl.getinfo_raw(pycurl.INFO_CERTINFO) + # self signed certificate, one certificate in chain + assert len(certinfo) == 1 + certinfo = certinfo[0] + # convert to a dictionary + certinfo_dict = {} + for entry in certinfo: + certinfo_dict[entry[0]] = entry[1] + assert util.b('Subject') in certinfo_dict + assert util.b('PycURL test suite') in certinfo_dict[util.b('Subject')] diff -Nru pycurl-7.43.0.1/tests/close_socket_cb_test.py pycurl-7.43.0.2/tests/close_socket_cb_test.py --- pycurl-7.43.0.1/tests/close_socket_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/close_socket_cb_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,7 +2,8 @@ # -*- coding: utf-8 -*- # vi:ts=4:et -import os +from . import localhost +import socket import unittest import pycurl @@ -14,7 +15,7 @@ class CloseSocketCbTest(unittest.TestCase): def setUp(self): self.curl = util.DefaultCurl() - self.curl.setopt(self.curl.URL, 'http://localhost:8380/success') + self.curl.setopt(self.curl.URL, 'http://%s:8380/success' % localhost) self.curl.setopt(pycurl.FORBID_REUSE, True) def tearDown(self): @@ -26,7 +27,10 @@ def closesocketfunction(curlfd): called['called'] = True - os.close(curlfd) + # Unix only + #os.close(curlfd) + # Unix & Windows + socket.fromfd(curlfd, socket.AF_INET, socket.SOCK_STREAM).close() return 0 self.curl.setopt(pycurl.CLOSESOCKETFUNCTION, closesocketfunction) diff -Nru pycurl-7.43.0.1/tests/debug_test.py pycurl-7.43.0.2/tests/debug_test.py --- pycurl-7.43.0.1/tests/debug_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/debug_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest @@ -24,7 +25,7 @@ def test_perform_get_with_debug_function(self): self.curl.setopt(pycurl.VERBOSE, 1) self.curl.setopt(pycurl.DEBUGFUNCTION, self.debug_function) - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.perform() @@ -34,7 +35,7 @@ if util.pycurl_version_less_than(7, 24): self.check(0, util.b('connected')) else: - self.check(0, util.b('Connected to localhost')) + self.check(0, util.b('Connected to %s' % localhost)) self.check(0, util.b('port 8380')) # request self.check(2, util.b('GET /success HTTP/1.1')) @@ -48,7 +49,7 @@ def test_debug_unicode(self): self.curl.setopt(pycurl.VERBOSE, 1) self.curl.setopt(pycurl.DEBUGFUNCTION, self.debug_function) - self.curl.setopt(pycurl.URL, 'http://localhost:8380/utf8_body') + self.curl.setopt(pycurl.URL, 'http://%s:8380/utf8_body' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.perform() diff -Nru pycurl-7.43.0.1/tests/default_write_cb_test.py pycurl-7.43.0.2/tests/default_write_cb_test.py --- pycurl-7.43.0.1/tests/default_write_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/default_write_cb_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import unittest import pycurl import sys @@ -38,7 +39,7 @@ # test_perform_get_with_default_write_function is the test # which exercises default curl write handler. - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) self.curl.perform() # If this flush is not done, stdout output bleeds into the next test # that is executed (without nose output capture) @@ -47,7 +48,7 @@ # I have a really hard time getting this to work with nose output capture def skip_perform_get_with_default_write_function(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) f = tempfile.NamedTemporaryFile() try: #with open('w', 'w+') as f: diff -Nru pycurl-7.43.0.1/tests/failonerror_test.py pycurl-7.43.0.2/tests/failonerror_test.py --- pycurl-7.43.0.1/tests/failonerror_test.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/tests/failonerror_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -0,0 +1,83 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vi:ts=4:et + +from . import localhost +import pycurl +import unittest + +from . import appmanager +from . import util + +setup_module, teardown_module = appmanager.setup(('app', 8380)) + +class FailonerrorTest(unittest.TestCase): + def setUp(self): + self.curl = util.DefaultCurl() + + def tearDown(self): + self.curl.close() + + # not sure what the actual min is but 7.26 is too old + # and does not include status text, only the status code + @util.min_libcurl(7, 38, 0) + def test_failonerror(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/status/403' % localhost) + sio = util.BytesIO() + self.curl.setopt(pycurl.WRITEDATA, sio) + self.curl.setopt(pycurl.FAILONERROR, True) + #self.curl.setopt(pycurl.VERBOSE, True) + try: + self.curl.perform() + except pycurl.error as e: + self.assertEqual(pycurl.E_HTTP_RETURNED_ERROR, e.args[0]) + self.assertEqual('The requested URL returned error: 403 Forbidden', e.args[1]) + self.assertEqual(util.u('The requested URL returned error: 403 Forbidden'), self.curl.errstr()) + self.assertEqual(util.b('The requested URL returned error: 403 Forbidden'), self.curl.errstr_raw()) + else: + self.fail('Should have raised pycurl.error') + + @util.only_python2 + # not sure what the actual min is but 7.26 is too old + # and does not include status text, only the status code + @util.min_libcurl(7, 38, 0) + def test_failonerror_status_line_invalid_utf8_python2(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/status_invalid_utf8' % localhost) + sio = util.BytesIO() + self.curl.setopt(pycurl.WRITEDATA, sio) + self.curl.setopt(pycurl.FAILONERROR, True) + #self.curl.setopt(pycurl.VERBOSE, True) + try: + self.curl.perform() + except pycurl.error as e: + self.assertEqual(pycurl.E_HTTP_RETURNED_ERROR, e.args[0]) + self.assertEqual('The requested URL returned error: 555 \xb3\xd2\xda\xcd\xd7', e.args[1]) + self.assertEqual('The requested URL returned error: 555 \xb3\xd2\xda\xcd\xd7', self.curl.errstr()) + self.assertEqual('The requested URL returned error: 555 \xb3\xd2\xda\xcd\xd7', self.curl.errstr_raw()) + else: + self.fail('Should have raised pycurl.error') + + @util.only_python3 + # not sure what the actual min is but 7.26 is too old + # and does not include status text, only the status code + @util.min_libcurl(7, 38, 0) + def test_failonerror_status_line_invalid_utf8_python3(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/status_invalid_utf8' % localhost) + sio = util.BytesIO() + self.curl.setopt(pycurl.WRITEDATA, sio) + self.curl.setopt(pycurl.FAILONERROR, True) + #self.curl.setopt(pycurl.VERBOSE, True) + try: + self.curl.perform() + except pycurl.error as e: + self.assertEqual(pycurl.E_HTTP_RETURNED_ERROR, e.args[0]) + assert e.args[1].startswith('The requested URL returned error: 555 ') + try: + self.curl.errstr() + except UnicodeDecodeError: + pass + else: + self.fail('Should have raised') + self.assertEqual(util.b('The requested URL returned error: 555 \xb3\xd2\xda\xcd\xd7'), self.curl.errstr_raw()) + else: + self.fail('Should have raised pycurl.error') diff -Nru pycurl-7.43.0.1/tests/fake-curl/curl-config-ssl-in-libs pycurl-7.43.0.2/tests/fake-curl/curl-config-ssl-in-libs --- pycurl-7.43.0.1/tests/fake-curl/curl-config-ssl-in-libs 2017-12-03 19:02:43.000000000 +0000 +++ pycurl-7.43.0.2/tests/fake-curl/curl-config-ssl-in-libs 2018-05-19 05:12:07.000000000 +0000 @@ -9,6 +9,9 @@ --libs) echo '-lcurl -lssl' ;; + --features) + echo 'SSL' + ;; esac shift done diff -Nru pycurl-7.43.0.1/tests/fake-curl/curl-config-ssl-in-static-libs pycurl-7.43.0.2/tests/fake-curl/curl-config-ssl-in-static-libs --- pycurl-7.43.0.1/tests/fake-curl/curl-config-ssl-in-static-libs 2017-12-03 19:02:43.000000000 +0000 +++ pycurl-7.43.0.2/tests/fake-curl/curl-config-ssl-in-static-libs 2018-05-19 05:12:07.000000000 +0000 @@ -12,6 +12,9 @@ --static-libs) echo '-lssl' ;; + --features) + echo 'SSL' + ;; esac shift done diff -Nru pycurl-7.43.0.1/tests/functools_backport.py pycurl-7.43.0.2/tests/functools_backport.py --- pycurl-7.43.0.1/tests/functools_backport.py 2017-08-30 13:46:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/functools_backport.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -# partial implementation from -# http://stackoverflow.com/questions/12274814/functools-wraps-for-python-2-4 - -def partial(func, *args, **kwds): - """Emulate Python2.6's functools.partial""" - return lambda *fargs, **fkwds: func(*(args+fargs), **dict(kwds, **fkwds)) - -# functools from python 2.5 - -"""functools.py - Tools for working with functions and callable objects -""" -# Python module wrapper for _functools C module -# to allow utilities written in Python to be added -# to the functools module. -# Written by Nick Coghlan -# Copyright (C) 2006 Python Software Foundation. -# See C source code for _functools credits/copyright - -# update_wrapper() and wraps() are tools to help write -# wrapper functions that can handle naive introspection - -WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') -WRAPPER_UPDATES = ('__dict__',) -def update_wrapper(wrapper, - wrapped, - assigned = WRAPPER_ASSIGNMENTS, - updated = WRAPPER_UPDATES): - """Update a wrapper function to look like the wrapped function - - wrapper is the function to be updated - wrapped is the original function - assigned is a tuple naming the attributes assigned directly - from the wrapped function to the wrapper function (defaults to - functools.WRAPPER_ASSIGNMENTS) - updated is a tuple naming the attributes off the wrapper that - are updated with the corresponding attribute from the wrapped - function (defaults to functools.WRAPPER_UPDATES) - """ - for attr in assigned: - setattr(wrapper, attr, getattr(wrapped, attr)) - for attr in updated: - getattr(wrapper, attr).update(getattr(wrapped, attr, {})) - # Return the wrapper so this can be used as a decorator via partial() - return wrapper - -def wraps(wrapped, - assigned = WRAPPER_ASSIGNMENTS, - updated = WRAPPER_UPDATES): - """Decorator factory to apply update_wrapper() to a wrapper function - - Returns a decorator that invokes update_wrapper() with the decorated - function as the wrapper argument and the arguments to wraps() as the - remaining arguments. Default arguments are as for update_wrapper(). - This is a convenience function to simplify applying partial() to - update_wrapper(). - """ - return partial(update_wrapper, wrapped=wrapped, - assigned=assigned, updated=updated) diff -Nru pycurl-7.43.0.1/tests/getinfo_test.py pycurl-7.43.0.2/tests/getinfo_test.py --- pycurl-7.43.0.1/tests/getinfo_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/getinfo_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import flaky import pycurl import unittest @@ -25,12 +26,10 @@ self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) self.assertEqual(200, self.curl.getinfo(pycurl.RESPONSE_CODE)) assert type(self.curl.getinfo(pycurl.TOTAL_TIME)) is float - assert self.curl.getinfo(pycurl.TOTAL_TIME) > 0 - assert self.curl.getinfo(pycurl.TOTAL_TIME) < 1 assert type(self.curl.getinfo(pycurl.SPEED_DOWNLOAD)) is float assert self.curl.getinfo(pycurl.SPEED_DOWNLOAD) > 0 self.assertEqual(7, self.curl.getinfo(pycurl.SIZE_DOWNLOAD)) - self.assertEqual('http://localhost:8380/success', self.curl.getinfo(pycurl.EFFECTIVE_URL)) + self.assertEqual('http://%s:8380/success' % localhost, self.curl.getinfo(pycurl.EFFECTIVE_URL)) self.assertEqual('text/html; charset=utf-8', self.curl.getinfo(pycurl.CONTENT_TYPE).lower()) assert type(self.curl.getinfo(pycurl.NAMELOOKUP_TIME)) is float assert self.curl.getinfo(pycurl.NAMELOOKUP_TIME) > 0 @@ -40,6 +39,18 @@ # time not requested self.assertEqual(-1, self.curl.getinfo(pycurl.INFO_FILETIME)) + # It seems that times are 0 on appveyor + @util.only_unix + @flaky.flaky(max_runs=3) + def test_getinfo_times(self): + self.make_request() + + self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + self.assertEqual(200, self.curl.getinfo(pycurl.RESPONSE_CODE)) + assert type(self.curl.getinfo(pycurl.TOTAL_TIME)) is float + assert self.curl.getinfo(pycurl.TOTAL_TIME) > 0 + assert self.curl.getinfo(pycurl.TOTAL_TIME) < 1 + @util.min_libcurl(7, 21, 0) def test_primary_port_etc(self): self.make_request() @@ -47,9 +58,75 @@ assert type(self.curl.getinfo(pycurl.LOCAL_IP)) is str assert type(self.curl.getinfo(pycurl.LOCAL_PORT)) is int - def make_request(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + def make_request(self, path='/success', expected_body='success'): + self.curl.setopt(pycurl.URL, 'http://%s:8380' % localhost + path) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.perform() - self.assertEqual('success', sio.getvalue().decode()) + self.assertEqual(expected_body, sio.getvalue().decode()) + + @util.only_python2 + def test_getinfo_cookie_invalid_utf8_python2(self): + self.curl.setopt(self.curl.COOKIELIST, '') + self.make_request('/set_cookie_invalid_utf8', 'cookie set') + + self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + expected = "%s" % localhost + "\tFALSE\t/\tFALSE\t0\t\xb3\xd2\xda\xcd\xd7\t%96%A6g%9Ay%B0%A5g%A7tm%7C%95%9A" + self.assertEqual([expected], self.curl.getinfo(pycurl.INFO_COOKIELIST)) + + @util.only_python3 + def test_getinfo_cookie_invalid_utf8_python3(self): + self.curl.setopt(self.curl.COOKIELIST, '') + self.make_request('/set_cookie_invalid_utf8', 'cookie set') + + self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + try: + self.curl.getinfo(pycurl.INFO_COOKIELIST) + except UnicodeDecodeError: + pass + else: + self.fail('Should have raised') + + def test_getinfo_raw_cookie_invalid_utf8(self): + self.curl.setopt(self.curl.COOKIELIST, '') + self.make_request('/set_cookie_invalid_utf8', 'cookie set') + + self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + expected = util.b("%s" % localhost + "\tFALSE\t/\tFALSE\t0\t\xb3\xd2\xda\xcd\xd7\t%96%A6g%9Ay%B0%A5g%A7tm%7C%95%9A") + self.assertEqual([expected], self.curl.getinfo_raw(pycurl.INFO_COOKIELIST)) + + @util.only_python2 + def test_getinfo_content_type_invalid_utf8_python2(self): + self.make_request('/content_type_invalid_utf8', 'content type set') + + self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + expected = '\xb3\xd2\xda\xcd\xd7' + self.assertEqual(expected, self.curl.getinfo(pycurl.CONTENT_TYPE)) + + @util.only_python3 + def test_getinfo_content_type_invalid_utf8_python3(self): + self.make_request('/content_type_invalid_utf8', 'content type set') + + self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + + try: + self.curl.getinfo(pycurl.CONTENT_TYPE) + except UnicodeDecodeError: + pass + else: + self.fail('Should have raised') + + def test_getinfo_raw_content_type_invalid_utf8(self): + self.make_request('/content_type_invalid_utf8', 'content type set') + + self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + expected = util.b('\xb3\xd2\xda\xcd\xd7') + self.assertEqual(expected, self.curl.getinfo_raw(pycurl.CONTENT_TYPE)) + + def test_getinfo_number(self): + self.make_request() + self.assertEqual(7, self.curl.getinfo(pycurl.SIZE_DOWNLOAD)) + + def test_getinfo_raw_number(self): + self.make_request() + self.assertEqual(7, self.curl.getinfo_raw(pycurl.SIZE_DOWNLOAD)) diff -Nru pycurl-7.43.0.1/tests/header_cb_test.py pycurl-7.43.0.2/tests/header_cb_test.py --- pycurl-7.43.0.1/tests/header_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/header_cb_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import time as _time @@ -23,7 +24,7 @@ self.header_lines.append(line.decode()) def test_get(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.setopt(pycurl.HEADERFUNCTION, self.header_function) diff -Nru pycurl-7.43.0.1/tests/header_test.py pycurl-7.43.0.2/tests/header_test.py --- pycurl-7.43.0.1/tests/header_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/header_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import nose.tools @@ -46,7 +47,7 @@ self.do_check((send,), expected) def do_check(self, send, expected): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/header_utf8?h=x-test-header') + self.curl.setopt(pycurl.URL, 'http://%s:8380/header_utf8?h=x-test-header' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.setopt(pycurl.HTTPHEADER, send) diff -Nru pycurl-7.43.0.1/tests/high_level_curl_test.py pycurl-7.43.0.2/tests/high_level_curl_test.py --- pycurl-7.43.0.1/tests/high_level_curl_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/high_level_curl_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost # uses the high level interface import curl import unittest @@ -12,7 +13,7 @@ class RelativeUrlTest(unittest.TestCase): def setUp(self): - self.curl = curl.Curl('http://localhost:8380/') + self.curl = curl.Curl('http://%s:8380/' % localhost) def tearDown(self): self.curl.close() diff -Nru pycurl-7.43.0.1/tests/__init__.py pycurl-7.43.0.2/tests/__init__.py --- pycurl-7.43.0.1/tests/__init__.py 2017-12-03 19:03:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/__init__.py 2018-06-02 04:14:57.000000000 +0000 @@ -1,3 +1,6 @@ +# On recent windowses there is no localhost entry in hosts file, +# hence localhost resolves fail. https://github.com/c-ares/c-ares/issues/85 +# FTP tests also seem to want the numeric IP address rather than localhost. localhost = '127.0.0.1' def setup_package(): diff -Nru pycurl-7.43.0.1/tests/matrix/python25.patch pycurl-7.43.0.2/tests/matrix/python25.patch --- pycurl-7.43.0.1/tests/matrix/python25.patch 2017-08-30 13:46:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/matrix/python25.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ - -# HG changeset patch -# User Benjamin Peterson -# Date 1243106677 0 -# Node ID 150986ab3db284cfd38d67a1ace2f04c2780bae4 -# Parent 54bbfd41be668dfc23c083510211accda7e54133 -support building with subversion 1.7 #6094 - -diff --git a/Makefile.pre.in b/Makefile.pre.in ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -505,7 +505,7 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \ - $(SIGNAL_OBJS) \ - $(MODOBJS) \ - $(srcdir)/Modules/getbuildinfo.c -- $(CC) -c $(PY_CFLAGS) -DSVNVERSION=\"`LC_ALL=C $(SVNVERSION)`\" -o $@ $(srcdir)/Modules/getbuildinfo.c -+ $(CC) -c $(PY_CFLAGS) -DSVNVERSION="\"`LC_ALL=C $(SVNVERSION)`\"" -o $@ $(srcdir)/Modules/getbuildinfo.c - - Modules/getpath.o: $(srcdir)/Modules/getpath.c Makefile - $(CC) -c $(PY_CFLAGS) -DPYTHONPATH='"$(PYTHONPATH)"' \ -diff --git a/Modules/getbuildinfo.c b/Modules/getbuildinfo.c ---- a/Modules/getbuildinfo.c -+++ b/Modules/getbuildinfo.c -@@ -48,5 +48,5 @@ const char * - static const char svnversion[] = SVNVERSION; - if (svnversion[0] != '$') - return svnversion; /* it was interpolated, or passed on command line */ -- return "exported"; -+ return "Unversioned directory"; - } -diff --git a/Python/sysmodule.c b/Python/sysmodule.c ---- a/Python/sysmodule.c -+++ b/Python/sysmodule.c -@@ -1158,7 +1158,7 @@ svnversion_init(void) - - - svnversion = _Py_svnversion(); -- if (strcmp(svnversion, "exported") != 0) -+ if (strcmp(svnversion, "Unversioned directory") != 0 && strcmp(svnversion, "exported") != 0) - svn_revision = svnversion; - else if (istag) { - len = strlen(_patchlevel_revision); -diff --git a/configure b/configure ---- a/configure -+++ b/configure -@@ -4385,7 +4385,7 @@ if test $SVNVERSION = found - then - SVNVERSION="svnversion \$(srcdir)" - else -- SVNVERSION="echo exported" -+ SVNVERSION="echo Unversioned directory" - fi - - case $MACHDEP in -diff --git a/configure.in b/configure.in ---- a/configure.in -+++ b/configure.in -@@ -802,7 +802,7 @@ if test $SVNVERSION = found - then - SVNVERSION="svnversion \$(srcdir)" - else -- SVNVERSION="echo exported" -+ SVNVERSION="echo Unversioned directory" - fi - - case $MACHDEP in - diff -Nru pycurl-7.43.0.1/tests/matrix/python30.patch pycurl-7.43.0.2/tests/matrix/python30.patch --- pycurl-7.43.0.1/tests/matrix/python30.patch 2017-08-30 13:46:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/matrix/python30.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -Python 3.0's version.py uses cmp which is not defined by the interpreter. -The only difference between version.py in 3.0 and 3.1 is this fix. - ---- Python-3.0.1/Lib/distutils/version.py 2013-12-14 21:06:23.000000000 -0500 -+++ Python-3.1.5/Lib/distutils/version.py 2013-12-14 21:01:10.000000000 -0500 -@@ -338,7 +338,12 @@ - if isinstance(other, str): - other = LooseVersion(other) - -- return cmp(self.version, other.version) -+ if self.version == other.version: -+ return 0 -+ if self.version < other.version: -+ return -1 -+ if self.version > other.version: -+ return 1 - - - # end class LooseVersion diff -Nru pycurl-7.43.0.1/tests/memory_mgmt_test.py pycurl-7.43.0.2/tests/memory_mgmt_test.py --- pycurl-7.43.0.1/tests/memory_mgmt_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/memory_mgmt_test.py 2018-06-02 04:29:03.000000000 +0000 @@ -2,6 +2,8 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +import sys +import weakref import pycurl import unittest import gc @@ -10,6 +12,11 @@ debug = False +if sys.platform == 'win32': + devnull = 'NUL' +else: + devnull = '/dev/null' + @flaky.flaky(max_runs=3) class MemoryMgmtTest(unittest.TestCase): def maybe_enable_debug(self): @@ -214,14 +221,20 @@ #print("Tracked objects:", len(gc.get_objects())) def test_refcounting_bug_in_reset(self): + if sys.platform == 'win32': + iters = 10000 + else: + iters = 100000 + try: range_generator = xrange except NameError: range_generator = range # Ensure that the refcounting error in "reset" is fixed: - for i in range_generator(100000): + for i in range_generator(iters): c = util.DefaultCurl() c.reset() + c.close() def test_writefunction_collection(self): self.check_callback(pycurl.WRITEFUNCTION) @@ -251,6 +264,8 @@ def test_seekfunction_collection(self): self.check_callback(pycurl.SEEKFUNCTION) + # This is failing too much on appveyor + @util.only_unix def check_callback(self, callback): # Note: extracting a context manager seems to result in # everything being garbage collected even if the C code @@ -283,6 +298,7 @@ gc.collect() after_object_count = len(gc.get_objects()) self.assert_(after_object_count <= before_object_count + 1000, 'Grew from %d to %d objects' % (before_object_count, after_object_count)) + c.close() def test_form_bufferptr_memory_leak_gh267(self): c = util.DefaultCurl() @@ -302,3 +318,63 @@ gc.collect() after_object_count = len(gc.get_objects()) self.assert_(after_object_count <= before_object_count + 1000, 'Grew from %d to %d objects' % (before_object_count, after_object_count)) + c.close() + + def do_data_refcounting(self, option): + c = util.DefaultCurl() + f = open(devnull, 'a+') + c.setopt(option, f) + ref = weakref.ref(f) + del f + gc.collect() + assert ref() + + for i in range(100): + assert ref() + c.setopt(option, ref()) + gc.collect() + assert ref() + + c.close() + gc.collect() + assert ref() is None + + def test_readdata_refcounting(self): + self.do_data_refcounting(pycurl.READDATA) + + def test_writedata_refcounting(self): + self.do_data_refcounting(pycurl.WRITEDATA) + + def test_writeheader_refcounting(self): + self.do_data_refcounting(pycurl.WRITEHEADER) + + # Python < 3.5 cannot create weak references to functions + @util.min_python(3, 5) + def do_function_refcounting(self, option, method_name): + c = util.DefaultCurl() + f = open(devnull, 'a+') + fn = getattr(f, method_name) + c.setopt(option, fn) + ref = weakref.ref(fn) + del f, fn + gc.collect() + assert ref() + + for i in range(100): + assert ref() + c.setopt(option, ref()) + gc.collect() + assert ref() + + c.close() + gc.collect() + assert ref() is None + + def test_readfunction_refcounting(self): + self.do_function_refcounting(pycurl.READFUNCTION, 'read') + + def test_writefunction_refcounting(self): + self.do_function_refcounting(pycurl.WRITEFUNCTION, 'write') + + def test_headerfunction_refcounting(self): + self.do_function_refcounting(pycurl.HEADERFUNCTION, 'write') diff -Nru pycurl-7.43.0.1/tests/multi_memory_mgmt_test.py pycurl-7.43.0.2/tests/multi_memory_mgmt_test.py --- pycurl-7.43.0.1/tests/multi_memory_mgmt_test.py 2017-12-03 19:03:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/multi_memory_mgmt_test.py 2018-05-22 03:44:40.000000000 +0000 @@ -6,6 +6,9 @@ import unittest import gc import flaky +import weakref + +from . import util debug = False @@ -37,3 +40,19 @@ # it seems that GC sometimes collects something that existed # before this test ran, GH issues #273/#274 self.assertTrue(new_object_count in (object_count, object_count-1)) + + def test_curl_ref(self): + c = util.DefaultCurl() + m = pycurl.CurlMulti() + + ref = weakref.ref(c) + m.add_handle(c) + del c + + assert ref() + gc.collect() + assert ref() + + m.remove_handle(ref()) + gc.collect() + assert ref() is None diff -Nru pycurl-7.43.0.1/tests/multi_socket_select_test.py pycurl-7.43.0.2/tests/multi_socket_select_test.py --- pycurl-7.43.0.1/tests/multi_socket_select_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/multi_socket_select_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import select @@ -34,9 +35,9 @@ # we need libcurl to actually wait on the handles, # and initiate polling. # thus use urls that sleep for a bit. - 'http://localhost:8380/short_wait', - 'http://localhost:8381/short_wait', - 'http://localhost:8382/short_wait', + 'http://%s:8380/short_wait' % localhost, + 'http://%s:8381/short_wait' % localhost, + 'http://%s:8382/short_wait' % localhost, ] socket_events = [] diff -Nru pycurl-7.43.0.1/tests/multi_socket_test.py pycurl-7.43.0.2/tests/multi_socket_test.py --- pycurl-7.43.0.1/tests/multi_socket_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/multi_socket_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest @@ -28,9 +29,9 @@ # not sure why requesting /success produces no events. # see multi_socket_select_test.py for a longer explanation # why short wait is used there. - 'http://localhost:8380/short_wait', - 'http://localhost:8381/short_wait', - 'http://localhost:8382/short_wait', + 'http://%s:8380/short_wait' % localhost, + 'http://%s:8381/short_wait' % localhost, + 'http://%s:8382/short_wait' % localhost, ] socket_events = [] diff -Nru pycurl-7.43.0.1/tests/multi_test.py pycurl-7.43.0.2/tests/multi_test.py --- pycurl-7.43.0.1/tests/multi_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/multi_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import nose.tools @@ -32,9 +33,9 @@ handles = [] c1 = util.DefaultCurl() c2 = util.DefaultCurl() - c1.setopt(c1.URL, 'http://localhost:8380/success') + c1.setopt(c1.URL, 'http://%s:8380/success' % localhost) c1.setopt(c1.WRITEFUNCTION, io1.write) - c2.setopt(c2.URL, 'http://localhost:8381/success') + c2.setopt(c2.URL, 'http://%s:8381/success' % localhost) c2.setopt(c1.WRITEFUNCTION, io2.write) m.add_handle(c1) m.add_handle(c2) @@ -62,9 +63,9 @@ c1 = util.DefaultCurl() c2 = util.DefaultCurl() c3 = util.DefaultCurl() - c1.setopt(c1.URL, "http://localhost:8380/success") - c2.setopt(c2.URL, "http://localhost:8381/success") - c3.setopt(c3.URL, "http://localhost:8382/success") + c1.setopt(c1.URL, "http://%s:8380/success" % localhost) + c2.setopt(c2.URL, "http://%s:8381/success" % localhost) + c3.setopt(c3.URL, "http://%s:8382/success" % localhost) c1.body = util.BytesIO() c2.body = util.BytesIO() c3.body = util.BytesIO() @@ -112,9 +113,9 @@ m = pycurl.CurlMulti() m.handles = [] urls = [ - 'http://localhost:8380/success', - 'http://localhost:8381/status/403', - 'http://localhost:8382/status/404', + 'http://%s:8380/success' % localhost, + 'http://%s:8381/status/403' % localhost, + 'http://%s:8382/status/404' % localhost, ] for url in urls: c = util.DefaultCurl() @@ -163,9 +164,9 @@ m = pycurl.CurlMulti() m.handles = [] urls = [ - 'http://localhost:8380/success', - 'http://localhost:8381/status/403', - 'http://localhost:8382/status/404', + 'http://%s:8380/success' % localhost, + 'http://%s:8381/status/403' % localhost, + 'http://%s:8382/status/404' % localhost, ] for url in urls: c = util.DefaultCurl() @@ -246,9 +247,9 @@ c1 = util.DefaultCurl() c2 = util.DefaultCurl() c3 = util.DefaultCurl() - c1.setopt(c1.URL, "http://localhost:8380/success") - c2.setopt(c2.URL, "http://localhost:8381/success") - c3.setopt(c3.URL, "http://localhost:8382/success") + c1.setopt(c1.URL, "http://%s:8380/success" % localhost) + c2.setopt(c2.URL, "http://%s:8381/success" % localhost) + c3.setopt(c3.URL, "http://%s:8382/success" % localhost) c1.body = util.BytesIO() c2.body = util.BytesIO() c3.body = util.BytesIO() @@ -297,9 +298,9 @@ c1 = util.DefaultCurl() c2 = util.DefaultCurl() c3 = util.DefaultCurl() - c1.setopt(c1.URL, "http://localhost:8380/short_wait") - c2.setopt(c2.URL, "http://localhost:8381/short_wait") - c3.setopt(c3.URL, "http://localhost:8382/short_wait") + c1.setopt(c1.URL, "http://%s:8380/short_wait" % localhost) + c2.setopt(c2.URL, "http://%s:8381/short_wait" % localhost) + c3.setopt(c3.URL, "http://%s:8382/short_wait" % localhost) c1.body = util.BytesIO() c2.body = util.BytesIO() c3.body = util.BytesIO() diff -Nru pycurl-7.43.0.1/tests/multi_timer_test.py pycurl-7.43.0.2/tests/multi_timer_test.py --- pycurl-7.43.0.1/tests/multi_timer_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/multi_timer_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest @@ -25,9 +26,9 @@ class MultiSocketTest(unittest.TestCase): def test_multi_timer(self): urls = [ - 'http://localhost:8380/success', - 'http://localhost:8381/success', - 'http://localhost:8382/success', + 'http://%s:8380/success' % localhost, + 'http://%s:8381/success' % localhost, + 'http://%s:8382/success' % localhost, ] timers = [] diff -Nru pycurl-7.43.0.1/tests/open_socket_cb_test.py pycurl-7.43.0.2/tests/open_socket_cb_test.py --- pycurl-7.43.0.1/tests/open_socket_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/open_socket_cb_test.py 2018-06-02 04:29:03.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import socket import pycurl import unittest @@ -49,6 +50,9 @@ sockets[0].close() return sockets[1] +def socket_open_bad(purpose, curl_address): + return pycurl.SOCKET_BAD + class OpenSocketCbTest(unittest.TestCase): def setUp(self): self.curl = util.DefaultCurl() @@ -56,9 +60,11 @@ def tearDown(self): self.curl.close() + # This is failing too much on appveyor + @util.only_unix def test_socket_open(self): self.curl.setopt(pycurl.OPENSOCKETFUNCTION, socket_open_ipv4) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/success') + self.curl.setopt(self.curl.URL, 'http://%s:8380/success' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.perform() @@ -88,9 +94,10 @@ assert type(socket_open_address[3]) == int @util.min_libcurl(7, 40, 0) + @util.only_unix def test_socket_open_unix(self): self.curl.setopt(pycurl.OPENSOCKETFUNCTION, socket_open_unix) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/success') + self.curl.setopt(self.curl.URL, 'http://%s:8380/success' % localhost) self.curl.setopt(self.curl.UNIX_SOCKET_PATH, '/tmp/pycurl-test-path.sock') sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) @@ -114,3 +121,21 @@ def test_unset_socket_open(self): self.curl.unsetopt(pycurl.OPENSOCKETFUNCTION) + + def test_socket_bad(self): + self.assertEqual(-1, pycurl.SOCKET_BAD) + + def test_socket_open_bad(self): + self.curl.setopt(pycurl.OPENSOCKETFUNCTION, socket_open_bad) + self.curl.setopt(self.curl.URL, 'http://%s:8380/success' % localhost) + try: + self.curl.perform() + except pycurl.error as e: + # libcurl 7.38.0 for some reason fails with a timeout + # (and spends 5 minutes on this test) + if pycurl.version_info()[1].split('.') == ['7', '38', '0']: + self.assertEqual(pycurl.E_OPERATION_TIMEDOUT, e.args[0]) + else: + self.assertEqual(pycurl.E_COULDNT_CONNECT, e.args[0]) + else: + self.fail('Should have raised') diff -Nru pycurl-7.43.0.1/tests/option_constants_test.py pycurl-7.43.0.2/tests/option_constants_test.py --- pycurl-7.43.0.1/tests/option_constants_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/option_constants_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import nose.plugins.attrib @@ -73,7 +74,7 @@ @util.min_libcurl(7, 19, 4) def test_noproxy_setopt(self): curl = pycurl.Curl() - curl.setopt(curl.NOPROXY, 'localhost') + curl.setopt(curl.NOPROXY, localhost) curl.close() # CURLOPT_PROTOCOLS was introduced in libcurl-7.19.4 @@ -200,6 +201,7 @@ curl.close() @util.min_libcurl(7, 39, 0) + @util.only_ssl def test_pinnedpublickey(self): curl = pycurl.Curl() curl.setopt(curl.PINNEDPUBLICKEY, '/etc/publickey.der') @@ -211,6 +213,7 @@ curl.setopt(curl.WILDCARDMATCH, '*') curl.close() + @util.only_unix @util.min_libcurl(7, 40, 0) def test_unix_socket_path(self): curl = pycurl.Curl() @@ -266,6 +269,41 @@ curl.setopt(curl.PROXY_CAPATH, '/bogus-capath') curl.close() + @util.min_libcurl(7, 52, 0) + @util.only_ssl + def test_proxy_sslcert(self): + curl = pycurl.Curl() + curl.setopt(curl.PROXY_SSLCERT, '/bogus-sslcert') + curl.close() + + @util.min_libcurl(7, 52, 0) + @util.only_ssl + def test_proxy_sslcerttype(self): + curl = pycurl.Curl() + curl.setopt(curl.PROXY_SSLCERTTYPE, 'PEM') + curl.close() + + @util.min_libcurl(7, 52, 0) + @util.only_ssl + def test_proxy_sslkey(self): + curl = pycurl.Curl() + curl.setopt(curl.PROXY_SSLKEY, '/bogus-sslkey') + curl.close() + + @util.min_libcurl(7, 52, 0) + @util.only_ssl + def test_proxy_sslkeytype(self): + curl = pycurl.Curl() + curl.setopt(curl.PROXY_SSLKEYTYPE, 'PEM') + curl.close() + + @util.min_libcurl(7, 52, 0) + @util.only_ssl + def test_proxy_ssl_verifypeer(self): + curl = pycurl.Curl() + curl.setopt(curl.PROXY_SSL_VERIFYPEER, 1) + curl.close() + @util.only_ssl def test_crlfile(self): curl = pycurl.Curl() @@ -428,6 +466,8 @@ assert self.curl.PROTO_SMB is not None assert self.curl.PROTO_SMBS is not None + # Apparently TLSAUTH_TYPE=SRP is an unknown option on appveyor + @util.only_unix @util.min_libcurl(7, 21, 4) @util.only_ssl_backends('openssl', 'gnutls') def test_tlsauth(self): @@ -454,3 +494,6 @@ @util.min_libcurl(7, 33, 0) def test_xauth_bearer(self): self.curl.setopt(self.curl.XOAUTH2_BEARER, 'test') + + def test_cookielist_constants(self): + self.assertEqual(pycurl.OPT_COOKIELIST, pycurl.COOKIELIST) diff -Nru pycurl-7.43.0.1/tests/pause_test.py pycurl-7.43.0.2/tests/pause_test.py --- pycurl-7.43.0.1/tests/pause_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/pause_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import flaky import pycurl import unittest, signal @@ -26,9 +27,10 @@ def test_pause_via_return(self): self.check_pause(False) + @util.only_unix def check_pause(self, call): # the app sleeps for 0.5 seconds - self.curl.setopt(pycurl.URL, 'http://localhost:8380/pause') + self.curl.setopt(pycurl.URL, 'http://%s:8380/pause' % localhost) sio = util.BytesIO() state = dict(paused=False, resumed=False) if call: diff -Nru pycurl-7.43.0.1/tests/perform_test.py pycurl-7.43.0.2/tests/perform_test.py --- pycurl-7.43.0.1/tests/perform_test.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/tests/perform_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -0,0 +1,66 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vi:ts=4:et + +from . import localhost +try: + import unittest2 as unittest +except ImportError: + import unittest +import pycurl + +from . import appmanager +from . import util + +setup_module, teardown_module = appmanager.setup(('app', 8380)) + +class PerformTest(unittest.TestCase): + def setUp(self): + self.curl = util.DefaultCurl() + + def tearDown(self): + self.curl.close() + + def test_perform_rb(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) + body = self.curl.perform_rb() + self.assertEqual(util.b('success'), body) + + def test_perform_rs(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) + body = self.curl.perform_rs() + self.assertEqual(util.u('success'), body) + + def test_perform_rb_utf8(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/utf8_body' % localhost) + body = self.curl.perform_rb() + if util.py3: + self.assertEqual('Дружба народов'.encode('utf8'), body) + else: + self.assertEqual('Дружба народов', body) + + def test_perform_rs_utf8(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/utf8_body' % localhost) + body = self.curl.perform_rs() + self.assertEqual('Дружба народов', body) + + def test_perform_rb_invalid_utf8(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/invalid_utf8_body' % localhost) + body = self.curl.perform_rb() + self.assertEqual(util.b('\xb3\xd2\xda\xcd\xd7'), body) + + @util.only_python2 + def test_perform_rs_invalid_utf8_python2(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/invalid_utf8_body' % localhost) + body = self.curl.perform_rs() + self.assertEqual('\xb3\xd2\xda\xcd\xd7', body) + + @util.only_python3 + def test_perform_rs_invalid_utf8_python3(self): + self.curl.setopt(pycurl.URL, 'http://%s:8380/invalid_utf8_body' % localhost) + try: + self.curl.perform_rs() + except UnicodeDecodeError: + pass + else: + self.fail('Should have raised') diff -Nru pycurl-7.43.0.1/tests/post_test.py pycurl-7.43.0.2/tests/post_test.py --- pycurl-7.43.0.1/tests/post_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/post_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import flaky import os.path import pycurl @@ -42,7 +43,7 @@ self.urlencode_and_check(pf) def urlencode_and_check(self, pf): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(pycurl.URL, 'http://%s:8380/postfields' % localhost) postfields = urllib_parse.urlencode(pf) self.curl.setopt(pycurl.POSTFIELDS, postfields) @@ -81,7 +82,7 @@ expect = { 'field3': 'this is wei\000rd, but null-bytes are okay', } - self.check_post(send, expect, 'http://localhost:8380/postfields') + self.check_post(send, expect, 'http://%s:8380/postfields' % localhost) def test_post_file(self): path = os.path.join(os.path.dirname(__file__), '..', 'README.rst') @@ -99,7 +100,7 @@ 'filename': 'README.rst', 'data': contents, }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) def test_post_byte_buffer(self): contents = util.b('hello, world!') @@ -111,7 +112,7 @@ 'filename': 'uploaded.file', 'data': 'hello, world!', }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) def test_post_unicode_buffer(self): contents = util.u('hello, world!') @@ -123,7 +124,7 @@ 'filename': 'uploaded.file', 'data': 'hello, world!', }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) def test_post_tuple_of_tuples_of_tuples(self): contents = util.u('hello, world!') @@ -135,7 +136,7 @@ 'filename': 'uploaded.file', 'data': 'hello, world!', }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) def test_post_tuple_of_lists_of_tuples(self): contents = util.u('hello, world!') @@ -147,7 +148,7 @@ 'filename': 'uploaded.file', 'data': 'hello, world!', }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) def test_post_tuple_of_tuple_of_lists(self): contents = util.u('hello, world!') @@ -159,7 +160,7 @@ 'filename': 'uploaded.file', 'data': 'hello, world!', }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) def test_post_list_of_tuple_of_tuples(self): contents = util.u('hello, world!') @@ -171,7 +172,7 @@ 'filename': 'uploaded.file', 'data': 'hello, world!', }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) def test_post_list_of_list_of_lists(self): contents = util.u('hello, world!') @@ -183,7 +184,7 @@ 'filename': 'uploaded.file', 'data': 'hello, world!', }] - self.check_post(send, expect, 'http://localhost:8380/files') + self.check_post(send, expect, 'http://%s:8380/files' % localhost) # XXX this test takes about a second to run, check keep-alives? def check_post(self, send, expect, endpoint): diff -Nru pycurl-7.43.0.1/tests/read_cb_test.py pycurl-7.43.0.2/tests/read_cb_test.py --- pycurl-7.43.0.1/tests/read_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/read_cb_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import sys @@ -49,7 +50,7 @@ def test_post_with_read_callback(self): d = DataProvider(POSTSTRING) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, len(POSTSTRING)) self.curl.setopt(self.curl.READFUNCTION, d.read_cb) @@ -75,7 +76,7 @@ assert type(data) == util.binary_type d = DataProvider(data) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/raw_utf8') + self.curl.setopt(self.curl.URL, 'http://%s:8380/raw_utf8' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.HTTPHEADER, ['Content-Type: application/octet-stream']) # length of bytes @@ -111,7 +112,7 @@ assert type(poststring) == util.text_type d = DataProvider(poststring) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/raw_utf8') + self.curl.setopt(self.curl.URL, 'http://%s:8380/raw_utf8' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.HTTPHEADER, ['Content-Type: application/octet-stream']) self.curl.setopt(self.curl.POSTFIELDSIZE, len(poststring)) diff -Nru pycurl-7.43.0.1/tests/readdata_test.py pycurl-7.43.0.2/tests/readdata_test.py --- pycurl-7.43.0.1/tests/readdata_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/readdata_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl try: import unittest2 as unittest @@ -55,7 +56,7 @@ def test_readdata_object(self): d = DataProvider(POSTSTRING) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, len(POSTSTRING)) self.curl.setopt(self.curl.READDATA, d) @@ -80,7 +81,7 @@ assert type(data) == util.binary_type d = DataProvider(data) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/raw_utf8') + self.curl.setopt(self.curl.URL, 'http://%s:8380/raw_utf8' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.HTTPHEADER, ['Content-Type: application/octet-stream']) # length of bytes @@ -115,7 +116,7 @@ assert type(poststring) == util.text_type d = DataProvider(poststring) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/raw_utf8') + self.curl.setopt(self.curl.URL, 'http://%s:8380/raw_utf8' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.HTTPHEADER, ['Content-Type: application/octet-stream']) self.curl.setopt(self.curl.POSTFIELDSIZE, len(poststring)) @@ -132,7 +133,7 @@ # file opened in binary mode f = open(FORM_SUBMISSION_PATH, 'rb') try: - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, os.stat(FORM_SUBMISSION_PATH).st_size) self.curl.setopt(self.curl.READDATA, f) @@ -149,7 +150,7 @@ # file opened in text mode f = open(FORM_SUBMISSION_PATH, 'rt') try: - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, os.stat(FORM_SUBMISSION_PATH).st_size) self.curl.setopt(self.curl.READDATA, f) @@ -165,7 +166,7 @@ def test_readdata_file_like(self): data = 'hello=world' data_provider = DataProvider(data) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, len(data)) self.curl.setopt(self.curl.READDATA, data_provider) @@ -181,7 +182,7 @@ data_provider = DataProvider(data) # data must be the same length function_provider = DataProvider('aaaaa=bbbbb') - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, len(data)) self.curl.setopt(self.curl.READDATA, data_provider) @@ -198,7 +199,7 @@ data_provider = DataProvider(data) # data must be the same length function_provider = DataProvider('aaaaa=bbbbb') - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, len(data)) self.curl.setopt(self.curl.READFUNCTION, function_provider.read) @@ -214,7 +215,7 @@ # data must be the same length with open(FORM_SUBMISSION_PATH) as f: function_provider = DataProvider('aaa=bbb') - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, os.stat(FORM_SUBMISSION_PATH).st_size) self.curl.setopt(self.curl.READDATA, f) @@ -230,7 +231,7 @@ # data must be the same length with open(FORM_SUBMISSION_PATH) as f: function_provider = DataProvider('aaa=bbb') - self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(self.curl.URL, 'http://%s:8380/postfields' % localhost) self.curl.setopt(self.curl.POST, 1) self.curl.setopt(self.curl.POSTFIELDSIZE, os.stat(FORM_SUBMISSION_PATH).st_size) self.curl.setopt(self.curl.READFUNCTION, function_provider.read) diff -Nru pycurl-7.43.0.1/tests/relative_url_test.py pycurl-7.43.0.2/tests/relative_url_test.py --- pycurl-7.43.0.1/tests/relative_url_test.py 2017-08-30 13:46:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/relative_url_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost # uses the high level interface import curl import unittest @@ -12,7 +13,7 @@ class RelativeUrlTest(unittest.TestCase): def setUp(self): - self.curl = curl.Curl('http://localhost:8380/') + self.curl = curl.Curl('http://%s:8380/' % localhost) def tearDown(self): self.curl.close() diff -Nru pycurl-7.43.0.1/tests/reset_test.py pycurl-7.43.0.2/tests/reset_test.py --- pycurl-7.43.0.1/tests/reset_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/reset_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest @@ -14,7 +15,7 @@ def test_reset(self): c = util.DefaultCurl() c.setopt(pycurl.USERAGENT, 'Phony/42') - c.setopt(pycurl.URL, 'http://localhost:8380/header?h=user-agent') + c.setopt(pycurl.URL, 'http://%s:8380/header?h=user-agent' % localhost) sio = util.BytesIO() c.setopt(pycurl.WRITEFUNCTION, sio.write) c.perform() @@ -22,7 +23,7 @@ assert user_agent == 'Phony/42' c.reset() - c.setopt(pycurl.URL, 'http://localhost:8380/header?h=user-agent') + c.setopt(pycurl.URL, 'http://%s:8380/header?h=user-agent' % localhost) sio = util.BytesIO() c.setopt(pycurl.WRITEFUNCTION, sio.write) c.perform() @@ -40,7 +41,7 @@ for x in range(1, 20): eh.setopt(pycurl.WRITEFUNCTION, outf.write) - eh.setopt(pycurl.URL, 'http://localhost:8380/success') + eh.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) cm.add_handle(eh) while 1: diff -Nru pycurl-7.43.0.1/tests/setopt_lifecycle_test.py pycurl-7.43.0.2/tests/setopt_lifecycle_test.py --- pycurl-7.43.0.1/tests/setopt_lifecycle_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/setopt_lifecycle_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import gc import pycurl import unittest @@ -26,7 +27,7 @@ # garbage collected before perform call def do_setopt(self, curl, index): pf = TestString('&'.join(50*['field=value%d' % (index,)])) - curl.setopt(pycurl.URL, 'http://localhost:8380/postfields') + curl.setopt(pycurl.URL, 'http://%s:8380/postfields' % localhost) curl.setopt(pycurl.POSTFIELDS, pf) # This test takes 6+ seconds to run. diff -Nru pycurl-7.43.0.1/tests/setopt_string_test.py pycurl-7.43.0.2/tests/setopt_string_test.py --- pycurl-7.43.0.1/tests/setopt_string_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/setopt_string_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -3,6 +3,7 @@ # vi:ts=4:et import pycurl +from . import localhost import unittest import nose.tools @@ -19,7 +20,7 @@ self.curl.close() def test_setopt_string(self): - self.curl.setopt_string(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt_string(pycurl.URL, 'http://%s:8380/success' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.perform() diff -Nru pycurl-7.43.0.1/tests/setopt_test.py pycurl-7.43.0.2/tests/setopt_test.py --- pycurl-7.43.0.1/tests/setopt_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/setopt_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import nose.tools @@ -53,7 +54,7 @@ def test_unset_httpheader(self): self.curl.setopt(self.curl.HTTPHEADER, ('x-test: foo',)) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/header?h=x-test') + self.curl.setopt(self.curl.URL, 'http://%s:8380/header?h=x-test' % localhost) io = util.BytesIO() self.curl.setopt(self.curl.WRITEDATA, io) self.curl.perform() @@ -67,7 +68,7 @@ def test_set_httpheader_none(self): self.curl.setopt(self.curl.HTTPHEADER, ('x-test: foo',)) - self.curl.setopt(self.curl.URL, 'http://localhost:8380/header?h=x-test') + self.curl.setopt(self.curl.URL, 'http://%s:8380/header?h=x-test' % localhost) io = util.BytesIO() self.curl.setopt(self.curl.WRITEDATA, io) self.curl.perform() diff -Nru pycurl-7.43.0.1/tests/setopt_unicode_test.py pycurl-7.43.0.2/tests/setopt_unicode_test.py --- pycurl-7.43.0.1/tests/setopt_unicode_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/setopt_unicode_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import nose.tools @@ -29,7 +30,7 @@ self.check(util.u('p=Москва').encode('utf8'), util.u('Москва')) def check(self, send, expected): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/param_utf8_hack') + self.curl.setopt(pycurl.URL, 'http://%s:8380/param_utf8_hack' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.setopt(pycurl.POSTFIELDS, send) diff -Nru pycurl-7.43.0.1/tests/setup_test.py pycurl-7.43.0.2/tests/setup_test.py --- pycurl-7.43.0.1/tests/setup_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/setup_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,15 +2,18 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import util import setup as pycurl_setup import unittest import os, os.path, sys import nose.plugins.skip - +import functools try: - import functools + # Python 2 + from StringIO import StringIO except ImportError: - import functools_backport as functools + # Python 3 + from io import StringIO def set_env(key, new_value): old_value = os.environ.get(key) @@ -58,17 +61,21 @@ return decorator class SetupTest(unittest.TestCase): + + @util.only_unix def test_sanity_check(self): config = pycurl_setup.ExtensionConfiguration() # we should link against libcurl, one would expect assert 'curl' in config.libraries + @util.only_unix @using_curl_config('curl-config-empty') def test_no_ssl(self): config = pycurl_setup.ExtensionConfiguration() # do not expect anything to do with ssl assert 'crypto' not in config.libraries + @util.only_unix @using_curl_config('curl-config-libs-and-static-libs') def test_does_not_use_static_libs(self): config = pycurl_setup.ExtensionConfiguration() @@ -77,43 +84,63 @@ assert 'flurby' in config.libraries assert 'kzzert' not in config.libraries + @util.only_unix @using_curl_config('curl-config-ssl-in-libs') def test_ssl_in_libs(self): config = pycurl_setup.ExtensionConfiguration() # should link against openssl assert 'crypto' in config.libraries + @util.only_unix @using_curl_config('curl-config-ssl-in-static-libs') def test_ssl_in_static_libs(self): config = pycurl_setup.ExtensionConfiguration() # should link against openssl assert 'crypto' in config.libraries + @util.only_unix @using_curl_config('curl-config-empty') def test_no_ssl_define(self): config = pycurl_setup.ExtensionConfiguration() # ssl define should be off assert 'HAVE_CURL_SSL' not in config.define_symbols + @util.only_unix @using_curl_config('curl-config-ssl-in-libs') def test_ssl_in_libs_sets_ssl_define(self): config = pycurl_setup.ExtensionConfiguration() # ssl define should be on assert 'HAVE_CURL_SSL' in config.define_symbols + @util.only_unix @using_curl_config('curl-config-ssl-in-static-libs') def test_ssl_in_static_libs_sets_ssl_define(self): config = pycurl_setup.ExtensionConfiguration() # ssl define should be on assert 'HAVE_CURL_SSL' in config.define_symbols - @using_curl_config('curl-config-ssl-feature-only') + @util.only_unix + @using_curl_config('curl-config-ssl-in-libs') def test_ssl_feature_sets_ssl_define(self): config = pycurl_setup.ExtensionConfiguration() # ssl define should be on assert 'HAVE_CURL_SSL' in config.define_symbols - @using_curl_config('curl-config-empty') + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') + def test_ssl_feature_only(self): + try: + pycurl_setup.ExtensionConfiguration() + except pycurl_setup.ConfigurationError as e: + self.assertEqual('''\ +Curl is configured to use SSL, but we have not been able to determine \ +which SSL backend it is using. Please see PycURL documentation for how to \ +specify the SSL backend manually.''', str(e)) + else: + self.fail('Should have raised') + + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') def test_libcurl_ssl_openssl(self): config = pycurl_setup.ExtensionConfiguration(['', '--libcurl-dll=tests/fake-curl/libcurl/with_openssl.so']) @@ -125,7 +152,8 @@ assert 'HAVE_CURL_GNUTLS' not in config.define_symbols assert 'HAVE_CURL_NSS' not in config.define_symbols - @using_curl_config('curl-config-empty') + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') def test_libcurl_ssl_gnutls(self): config = pycurl_setup.ExtensionConfiguration(['', '--libcurl-dll=tests/fake-curl/libcurl/with_gnutls.so']) @@ -137,7 +165,8 @@ assert 'HAVE_CURL_OPENSSL' not in config.define_symbols assert 'HAVE_CURL_NSS' not in config.define_symbols - @using_curl_config('curl-config-empty') + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') def test_libcurl_ssl_nss(self): config = pycurl_setup.ExtensionConfiguration(['', '--libcurl-dll=tests/fake-curl/libcurl/with_nss.so']) @@ -149,6 +178,7 @@ assert 'HAVE_CURL_OPENSSL' not in config.define_symbols assert 'HAVE_CURL_GNUTLS' not in config.define_symbols + @util.only_unix @using_curl_config('curl-config-empty') def test_libcurl_ssl_unrecognized(self): config = pycurl_setup.ExtensionConfiguration(['', @@ -158,7 +188,8 @@ assert 'HAVE_CURL_GNUTLS' not in config.define_symbols assert 'HAVE_CURL_NSS' not in config.define_symbols - @using_curl_config('curl-config-empty') + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') def test_with_ssl_library(self): config = pycurl_setup.ExtensionConfiguration(['', '--with-ssl']) @@ -169,7 +200,8 @@ assert 'HAVE_CURL_GNUTLS' not in config.define_symbols assert 'HAVE_CURL_NSS' not in config.define_symbols - @using_curl_config('curl-config-empty') + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') def test_with_openssl_library(self): config = pycurl_setup.ExtensionConfiguration(['', '--with-openssl']) @@ -180,7 +212,8 @@ assert 'HAVE_CURL_GNUTLS' not in config.define_symbols assert 'HAVE_CURL_NSS' not in config.define_symbols - @using_curl_config('curl-config-empty') + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') def test_with_gnutls_library(self): config = pycurl_setup.ExtensionConfiguration(['', '--with-gnutls']) @@ -191,7 +224,8 @@ assert 'HAVE_CURL_OPENSSL' not in config.define_symbols assert 'HAVE_CURL_NSS' not in config.define_symbols - @using_curl_config('curl-config-empty') + @util.only_unix + @using_curl_config('curl-config-ssl-feature-only') def test_with_nss_library(self): config = pycurl_setup.ExtensionConfiguration(['', '--with-nss']) @@ -201,3 +235,32 @@ assert 'HAVE_CURL_OPENSSL' not in config.define_symbols assert 'HAVE_CURL_GNUTLS' not in config.define_symbols + + @util.only_unix + @using_curl_config('curl-config-empty') + def test_no_ssl_feature_with_libcurl_dll(self): + config = pycurl_setup.ExtensionConfiguration(['', + '--libcurl-dll=tests/fake-curl/libcurl/with_openssl.so']) + # openssl should not be detected + assert 'HAVE_CURL_SSL' not in config.define_symbols + assert 'HAVE_CURL_OPENSSL' not in config.define_symbols + assert 'crypto' not in config.libraries + + @util.only_unix + @using_curl_config('curl-config-empty') + def test_no_ssl_feature_with_ssl(self): + old_stderr = sys.stderr + sys.stderr = captured_stderr = StringIO() + + try: + config = pycurl_setup.ExtensionConfiguration(['', + '--with-ssl']) + # openssl should not be detected + assert 'HAVE_CURL_SSL' not in config.define_symbols + assert 'HAVE_CURL_OPENSSL' not in config.define_symbols + assert 'crypto' not in config.libraries + finally: + sys.stderr = old_stderr + + self.assertEqual("Warning: SSL backend specified manually but libcurl does not use SSL", + captured_stderr.getvalue().strip()) diff -Nru pycurl-7.43.0.1/tests/share_test.py pycurl-7.43.0.2/tests/share_test.py --- pycurl-7.43.0.1/tests/share_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/share_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import threading import pycurl import unittest @@ -17,7 +18,7 @@ def __init__(self, share): threading.Thread.__init__(self) self.curl = util.DefaultCurl() - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) self.curl.setopt(pycurl.SHARE, share) self.sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, self.sio.write) diff -Nru pycurl-7.43.0.1/tests/sockopt_cb_test.py pycurl-7.43.0.2/tests/sockopt_cb_test.py --- pycurl-7.43.0.1/tests/sockopt_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/sockopt_cb_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import unittest import pycurl @@ -13,7 +14,7 @@ class SockoptCbTest(unittest.TestCase): def setUp(self): self.curl = util.DefaultCurl() - self.curl.setopt(self.curl.URL, 'http://localhost:8380/success') + self.curl.setopt(self.curl.URL, 'http://%s:8380/success' % localhost) def tearDown(self): self.curl.close() diff -Nru pycurl-7.43.0.1/tests/ssh_key_cb_test.py pycurl-7.43.0.2/tests/ssh_key_cb_test.py --- pycurl-7.43.0.1/tests/ssh_key_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/ssh_key_cb_test.py 2018-02-25 05:45:37.000000000 +0000 @@ -24,6 +24,9 @@ self.curl.close() @util.min_libcurl(7, 19, 6) + # curl compiled with libssh doesn't support + # CURLOPT_SSH_KNOWNHOSTS and CURLOPT_SSH_KEYFUNCTION + @util.guard_unknown_libcurl_option def test_keyfunction(self): # with keyfunction returning ok @@ -54,6 +57,7 @@ self.assertEqual(pycurl.E_PEER_FAILED_VERIFICATION, e.args[0]) @util.min_libcurl(7, 19, 6) + @util.guard_unknown_libcurl_option def test_keyfunction_bogus_return(self): def keyfunction(known_key, found_key, match): return 'bogus' @@ -76,9 +80,11 @@ self.curl.setopt(pycurl.VERBOSE, True) @util.min_libcurl(7, 19, 6) + @util.guard_unknown_libcurl_option def test_keyfunction_none(self): self.curl.setopt(pycurl.SSH_KEYFUNCTION, None) @util.min_libcurl(7, 19, 6) + @util.guard_unknown_libcurl_option def test_keyfunction_unset(self): self.curl.unsetopt(pycurl.SSH_KEYFUNCTION) diff -Nru pycurl-7.43.0.1/tests/unset_range_test.py pycurl-7.43.0.2/tests/unset_range_test.py --- pycurl-7.43.0.1/tests/unset_range_test.py 2017-08-30 13:46:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/unset_range_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -4,7 +4,6 @@ import os.path import pycurl -import sys import unittest class UnsetRangeTest(unittest.TestCase): @@ -21,7 +20,7 @@ # download bytes 0-9 of the script itself through the file:// protocol self.read = 0 - self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) + self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(__file__).replace('\\', '/')) self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) self.curl.setopt(pycurl.RANGE, '0-9') self.curl.perform() diff -Nru pycurl-7.43.0.1/tests/user_agent_string_test.py pycurl-7.43.0.2/tests/user_agent_string_test.py --- pycurl-7.43.0.1/tests/user_agent_string_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/user_agent_string_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import unittest import pycurl @@ -18,7 +19,7 @@ self.curl.close() def test_pycurl_user_agent_string(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/header?h=user-agent') + self.curl.setopt(pycurl.URL, 'http://%s:8380/header?h=user-agent' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.perform() diff -Nru pycurl-7.43.0.1/tests/util.py pycurl-7.43.0.2/tests/util.py --- pycurl-7.43.0.1/tests/util.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/util.py 2018-06-02 04:14:57.000000000 +0000 @@ -4,10 +4,7 @@ import tempfile import os, sys, socket import time as _time -try: - import functools -except ImportError: - import functools_backport as functools +import functools py3 = sys.version_info[0] == 3 @@ -71,6 +68,18 @@ version = [int(part) for part in pycurl.version_info()[1].split('-')[0].split('.')] return version_less_than_spec(version, spec) +def only_python2(fn): + import nose.plugins.skip + + @functools.wraps(fn) + def decorated(*args, **kwargs): + if sys.version_info[0] >= 3: + raise nose.plugins.skip.SkipTest('python >= 3') + + return fn(*args, **kwargs) + + return decorated + def only_python3(fn): import nose.plugins.skip @@ -83,6 +92,21 @@ return decorated +def min_python(major, minor): + import nose.plugins.skip + + def decorator(fn): + @functools.wraps(fn) + def decorated(*args, **kwargs): + if sys.version_info[0:2] < (major, minor): + raise nose.plugins.skip.SkipTest('python < %d.%d' % (major, minor)) + + return fn(*args, **kwargs) + + return decorated + + return decorator + def min_libcurl(major, minor, patch): import nose.plugins.skip @@ -157,6 +181,18 @@ return decorated +def only_unix(fn): + import nose.plugins.skip + + @functools.wraps(fn) + def decorated(*args, **kwargs): + if sys.platform == 'win32': + raise nose.plugins.skip.SkipTest('Unix only') + + return fn(*args, **kwargs) + + return decorated + def guard_unknown_libcurl_option(fn): '''Converts curl error 48, CURLE_UNKNOWN_OPTION, into a SkipTest exception. This is meant to be used with tests exercising libcurl @@ -246,10 +282,21 @@ curl.setopt(curl.FORBID_REUSE, True) return curl +def DefaultCurlLocalhost(port): + '''This is a default curl with localhost -> 127.0.0.1 name mapping + on windows systems, because they don't have it in the hosts file. + ''' + + curl = DefaultCurl() + + if sys.platform == 'win32': + curl.setopt(curl.RESOLVE, ['localhost:%d:127.0.0.1' % port]) + + return curl + def with_real_write_file(fn): @functools.wraps(fn) def wrapper(*args): with tempfile.NamedTemporaryFile() as f: - with open(f.name, 'w+b') as real_f: - return fn(*(list(args) + [real_f])) + return fn(*(list(args) + [f.file])) return wrapper diff -Nru pycurl-7.43.0.1/tests/weakref_test.py pycurl-7.43.0.2/tests/weakref_test.py --- pycurl-7.43.0.1/tests/weakref_test.py 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/tests/weakref_test.py 2018-05-22 03:44:40.000000000 +0000 @@ -0,0 +1,23 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vi:ts=4:et + +import unittest +import weakref +import pycurl + +class WeakrefTest(unittest.TestCase): + def test_easy(self): + c = pycurl.Curl() + weakref.ref(c) + c.close() + + def test_multi(self): + m = pycurl.CurlMulti() + weakref.ref(m) + m.close() + + def test_share(self): + s = pycurl.CurlShare() + weakref.ref(s) + s.close() diff -Nru pycurl-7.43.0.1/tests/write_abort_test.py pycurl-7.43.0.2/tests/write_abort_test.py --- pycurl-7.43.0.1/tests/write_abort_test.py 2017-08-30 13:46:03.000000000 +0000 +++ pycurl-7.43.0.2/tests/write_abort_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -27,7 +27,7 @@ pass # download the script itself through the file:// protocol into write_cb - self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) + self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(__file__).replace('\\', '/')) self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) try: self.curl.perform() diff -Nru pycurl-7.43.0.1/tests/write_cb_bogus_test.py pycurl-7.43.0.2/tests/write_cb_bogus_test.py --- pycurl-7.43.0.1/tests/write_cb_bogus_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/write_cb_bogus_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -28,7 +28,7 @@ def check(self, write_cb): # download the script itself through the file:// protocol into write_cb - self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) + self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(__file__).replace('\\', '/')) self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) try: self.curl.perform() diff -Nru pycurl-7.43.0.1/tests/write_test.py pycurl-7.43.0.2/tests/write_test.py --- pycurl-7.43.0.1/tests/write_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/write_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost try: import unittest2 as unittest except ImportError: @@ -31,7 +32,7 @@ self.curl.close() def test_write_to_tempfile_via_function(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) f = tempfile.NamedTemporaryFile() try: self.curl.setopt(pycurl.WRITEFUNCTION, f.write) @@ -43,7 +44,7 @@ self.assertEqual('success', body.decode()) def test_write_to_tempfile_via_object(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) f = tempfile.NamedTemporaryFile() try: self.curl.setopt(pycurl.WRITEDATA, f) @@ -55,7 +56,7 @@ self.assertEqual('success', body.decode()) def test_write_to_file_via_function(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) dir = tempfile.mkdtemp() try: path = os.path.join(dir, 'pycurltest') @@ -72,7 +73,7 @@ self.assertEqual('success', body.decode()) def test_write_to_file_via_object(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) dir = tempfile.mkdtemp() try: path = os.path.join(dir, 'pycurltest') @@ -89,7 +90,7 @@ self.assertEqual('success', body.decode()) def test_write_to_file_like(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) acceptor = Acceptor() self.curl.setopt(pycurl.WRITEDATA, acceptor) self.curl.perform() @@ -97,7 +98,7 @@ @util.with_real_write_file def test_write_to_file_like_then_real_file(self, real_f): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) acceptor = Acceptor() self.curl.setopt(pycurl.WRITEDATA, acceptor) self.curl.perform() @@ -110,7 +111,7 @@ self.assertEqual('success', body.decode()) def test_headerfunction_and_writefunction(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) header_acceptor = Acceptor() body_acceptor = Acceptor() self.curl.setopt(pycurl.HEADERFUNCTION, header_acceptor.write) @@ -120,7 +121,7 @@ self.assertIn('content-type', header_acceptor.buffer.lower()) def test_writeheader_and_writedata_file_like(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) header_acceptor = Acceptor() body_acceptor = Acceptor() self.curl.setopt(pycurl.WRITEHEADER, header_acceptor) @@ -132,7 +133,7 @@ @util.with_real_write_file @util.with_real_write_file def test_writeheader_and_writedata_real_file(self, real_f_header, real_f_data): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) self.curl.setopt(pycurl.WRITEHEADER, real_f_header) self.curl.setopt(pycurl.WRITEDATA, real_f_data) self.curl.perform() @@ -142,7 +143,7 @@ self.assertIn('content-type', real_f_header.read().decode().lower()) def test_writedata_and_writefunction_file_like(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) data_acceptor = Acceptor() function_acceptor = Acceptor() self.curl.setopt(pycurl.WRITEDATA, data_acceptor) @@ -153,7 +154,7 @@ @util.with_real_write_file def test_writedata_and_writefunction_real_file(self, real_f): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) function_acceptor = Acceptor() self.curl.setopt(pycurl.WRITEDATA, real_f) self.curl.setopt(pycurl.WRITEFUNCTION, function_acceptor.write) @@ -163,7 +164,7 @@ self.assertEqual('success', function_acceptor.buffer) def test_writefunction_and_writedata_file_like(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) data_acceptor = Acceptor() function_acceptor = Acceptor() self.curl.setopt(pycurl.WRITEFUNCTION, function_acceptor.write) @@ -174,7 +175,7 @@ @util.with_real_write_file def test_writefunction_and_writedata_real_file(self, real_f): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) function_acceptor = Acceptor() self.curl.setopt(pycurl.WRITEFUNCTION, function_acceptor.write) self.curl.setopt(pycurl.WRITEDATA, real_f) @@ -184,7 +185,7 @@ self.assertEqual('', function_acceptor.buffer) def test_writeheader_and_headerfunction_file_like(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) data_acceptor = Acceptor() function_acceptor = Acceptor() body_acceptor = Acceptor() @@ -198,7 +199,7 @@ @util.with_real_write_file def test_writeheader_and_headerfunction_real_file(self, real_f): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) function_acceptor = Acceptor() body_acceptor = Acceptor() self.curl.setopt(pycurl.WRITEHEADER, real_f) @@ -211,7 +212,7 @@ self.assertIn('content-type', function_acceptor.buffer.lower()) def test_headerfunction_and_writeheader_file_like(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) data_acceptor = Acceptor() function_acceptor = Acceptor() body_acceptor = Acceptor() @@ -225,7 +226,7 @@ @util.with_real_write_file def test_headerfunction_and_writeheader_real_file(self, real_f): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) function_acceptor = Acceptor() body_acceptor = Acceptor() self.curl.setopt(pycurl.HEADERFUNCTION, function_acceptor.write) diff -Nru pycurl-7.43.0.1/tests/write_to_stringio_test.py pycurl-7.43.0.2/tests/write_to_stringio_test.py --- pycurl-7.43.0.1/tests/write_to_stringio_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/write_to_stringio_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import pycurl import unittest import sys @@ -19,7 +20,7 @@ self.curl.close() def test_write_to_bytesio(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) sio = util.BytesIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) self.curl.perform() @@ -27,7 +28,7 @@ @util.only_python3 def test_write_to_stringio(self): - self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) # stringio in python 3 sio = util.StringIO() self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) diff -Nru pycurl-7.43.0.1/tests/xferinfo_cb_test.py pycurl-7.43.0.2/tests/xferinfo_cb_test.py --- pycurl-7.43.0.1/tests/xferinfo_cb_test.py 2017-12-03 19:03:17.000000000 +0000 +++ pycurl-7.43.0.2/tests/xferinfo_cb_test.py 2018-06-02 04:14:57.000000000 +0000 @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # vi:ts=4:et +from . import localhost import unittest import pycurl @@ -13,7 +14,7 @@ class XferinfoCbTest(unittest.TestCase): def setUp(self): self.curl = util.DefaultCurl() - self.curl.setopt(self.curl.URL, 'http://localhost:8380/long_pause') + self.curl.setopt(self.curl.URL, 'http://%s:8380/long_pause' % localhost) def tearDown(self): self.curl.close() diff -Nru pycurl-7.43.0.1/winbuild/libcurl-fix-zlib-references.patch pycurl-7.43.0.2/winbuild/libcurl-fix-zlib-references.patch --- pycurl-7.43.0.1/winbuild/libcurl-fix-zlib-references.patch 2017-12-03 19:03:03.000000000 +0000 +++ pycurl-7.43.0.2/winbuild/libcurl-fix-zlib-references.patch 2018-05-29 01:36:18.000000000 +0000 @@ -1,19 +1,5 @@ --- winbuild/MakefileBuild.vc.orig 2015-11-27 07:00:14.000000000 -0800 +++ winbuild/MakefileBuild.vc 2016-01-01 21:33:44.263840800 -0800 -@@ -144,11 +144,11 @@ - !ENDIF - - !IF "$(WITH_ZLIB)"=="dll" --ZLIB_LIBS = zlib.lib -+ZLIB_LIBS = zdll.lib - USE_ZLIB = true - ZLIB = dll - !ELSEIF "$(WITH_ZLIB)"=="static" --ZLIB_LIBS = zlib_a.lib -+ZLIB_LIBS = zlib.lib - USE_ZLIB = true - ZLIB = static - !ENDIF @@ -238,7 +238,7 @@ # Runtime library configuration diff -Nru pycurl-7.43.0.1/winbuild/vcvars-vc14-32.sh pycurl-7.43.0.2/winbuild/vcvars-vc14-32.sh --- pycurl-7.43.0.1/winbuild/vcvars-vc14-32.sh 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/winbuild/vcvars-vc14-32.sh 2018-05-29 12:31:22.000000000 +0000 @@ -0,0 +1,25 @@ +# Courtesy of libiconv 1.15 + +# Set environment variables for using MSVC 14, +# for creating native 32-bit Windows executables. + +# Windows C library headers and libraries. +WindowsCrtIncludeDir='C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt' +WindowsCrtLibDir='C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\' +INCLUDE="${WindowsCrtIncludeDir};$INCLUDE" +LIB="${WindowsCrtLibDir}x86;$LIB" + +# Windows API headers and libraries. +WindowsSdkIncludeDir='C:\Program Files (x86)\Windows Kits\8.1\Include\' +WindowsSdkLibDir='C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\' +INCLUDE="${WindowsSdkIncludeDir}um;${WindowsSdkIncludeDir}shared;$INCLUDE" +LIB="${WindowsSdkLibDir}x86;$LIB" + +# Visual C++ tools, headers and libraries. +VSINSTALLDIR='C:\Program Files (x86)\Microsoft Visual Studio 14.0' +VCINSTALLDIR="${VSINSTALLDIR}"'\VC' +PATH=`cygpath -u "${VCINSTALLDIR}"`/bin:"$PATH" +INCLUDE="${VCINSTALLDIR}"'\include;'"${INCLUDE}" +LIB="${VCINSTALLDIR}"'\lib;'"${LIB}" + +export INCLUDE LIB diff -Nru pycurl-7.43.0.1/winbuild/vcvars-vc14-64.sh pycurl-7.43.0.2/winbuild/vcvars-vc14-64.sh --- pycurl-7.43.0.1/winbuild/vcvars-vc14-64.sh 1970-01-01 00:00:00.000000000 +0000 +++ pycurl-7.43.0.2/winbuild/vcvars-vc14-64.sh 2018-05-29 12:31:22.000000000 +0000 @@ -0,0 +1,25 @@ +# Courtesy of libiconv 1.15 + +# Set environment variables for using MSVC 14, +# for creating native 64-bit Windows executables. + +# Windows C library headers and libraries. +WindowsCrtIncludeDir='C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt' +WindowsCrtLibDir='C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\' +INCLUDE="${WindowsCrtIncludeDir};$INCLUDE" +LIB="${WindowsCrtLibDir}x64;$LIB" + +# Windows API headers and libraries. +WindowsSdkIncludeDir='C:\Program Files (x86)\Windows Kits\8.1\Include\' +WindowsSdkLibDir='C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\' +INCLUDE="${WindowsSdkIncludeDir}um;${WindowsSdkIncludeDir}shared;$INCLUDE" +LIB="${WindowsSdkLibDir}x64;$LIB" + +# Visual C++ tools, headers and libraries. +VSINSTALLDIR='C:\Program Files (x86)\Microsoft Visual Studio 14.0' +VCINSTALLDIR="${VSINSTALLDIR}"'\VC' +PATH=`cygpath -u "${VCINSTALLDIR}"`/bin/amd64:"$PATH" +INCLUDE="${VCINSTALLDIR}"'\include;'"${INCLUDE}" +LIB="${VCINSTALLDIR}"'\lib\amd64;'"${LIB}" + +export INCLUDE LIB \ No newline at end of file diff -Nru pycurl-7.43.0.1/winbuild.py pycurl-7.43.0.2/winbuild.py --- pycurl-7.43.0.1/winbuild.py 2017-12-07 07:18:12.000000000 +0000 +++ pycurl-7.43.0.2/winbuild.py 2018-06-02 04:59:02.000000000 +0000 @@ -27,79 +27,104 @@ # ActiveState Perl: # http://www.activestate.com/activeperl/downloads -# work directory for downloading dependencies and building everything -root = 'c:/dev/build-pycurl' -# where msysgit is installed -git_root = 'c:/program files/git' -# where NASM is installed, for building OpenSSL -nasm_path = ('c:/dev/nasm', 'c:/program files/nasm', 'c:/program files (x86)/nasm') -# where ActiveState Perl is installed, for building 64-bit OpenSSL -activestate_perl_path = ('c:/perl64', r'c:\dev\perl64') -# which versions of python to build against -python_versions = ['2.6.6', '2.7.10', '3.2.5', '3.3.5', '3.4.3', '3.5.4', '3.6.2'] -# where pythons are installed -python_path_template = 'c:/dev/%(bitness)s/python%(python_release)s/python' -vc_paths = { - # where msvc 9/vs 2008 is installed, for python 2.6 through 3.2 - 'vc9': None, - # where msvc 10/vs 2010 is installed, for python 3.3 through 3.4 - 'vc10': None, - # where msvc 14/vs 2015 is installed, for python 3.5 - 'vc14': None, -} -# whether to link libcurl against zlib -use_zlib = True -# which version of zlib to use, will be downloaded from internet -zlib_version = '1.2.11' -# whether to use openssl instead of winssl -use_openssl = True -# which version of openssl to use, will be downloaded from internet -openssl_version = '1.1.0g' -# whether to use c-ares -use_cares = True -cares_version = '1.13.0' -# whether to use libssh2 -use_libssh2 = True -libssh2_version = '1.8.0' -# which version of libcurl to use, will be downloaded from internet -libcurl_version = '7.57.0' -# virtualenv version -virtualenv_version = '15.1.0' -# whether to build binary wheels -build_wheels = True -# pycurl version to build, we should know this ourselves -pycurl_version = '7.43.0.1' - -default_vc_paths = { - # where msvc 9 is installed, for python 2.6-3.2 - 'vc9': [ - 'c:/program files (x86)/microsoft visual studio 9.0', - 'c:/program files/microsoft visual studio 9.0', - ], - # where msvc 10 is installed, for python 3.3-3.4 - 'vc10': [ - 'c:/program files (x86)/microsoft visual studio 10.0', - 'c:/program files/microsoft visual studio 10.0', - ], - # where msvc 14 is installed, for python 3.5-3.6 - 'vc14': [ - 'c:/program files (x86)/microsoft visual studio 14.0', - 'c:/program files/microsoft visual studio 14.0', - ], -} +class Config: + '''User-adjustable configuration. + + This class contains version numbers for dependencies, + which dependencies to use, + and where various binaries, headers and libraries are located in the filesystem. + ''' + + # work directory for downloading dependencies and building everything + root = 'c:/dev/build-pycurl' + # where msysgit is installed + git_root = 'c:/program files/git' + msysgit_bin_paths = [ + "c:\\Program Files\\Git\\bin", + "c:\\Program Files\\Git\\usr\\bin", + #"c:\\Program Files\\Git\\mingw64\\bin", + ] + # where NASM is installed, for building OpenSSL + nasm_path = ('c:/dev/nasm', 'c:/program files/nasm', 'c:/program files (x86)/nasm') + cmake_path = r"c:\Program Files\CMake\bin\cmake.exe" + gmake_path = r"c:\Program Files (x86)\GnuWin32\bin\make.exe" + # where ActiveState Perl is installed, for building 64-bit OpenSSL + activestate_perl_path = ('c:/perl64', r'c:\dev\perl64') + # which versions of python to build against + #python_versions = ['2.7.10', '3.2.5', '3.3.5', '3.4.3', '3.5.4', '3.6.2'] + # these require only vc9 and vc14 + python_versions = ['2.7.10', '3.5.4', '3.6.2'] + # where pythons are installed + python_path_template = 'c:/dev/%(bitness)s/python%(python_release)s/python' + # overrides only, defaults are given in default_vc_paths below + vc_paths = { + # where msvc 9/vs 2008 is installed, for python 2.6 through 3.2 + 'vc9': None, + # where msvc 10/vs 2010 is installed, for python 3.3 through 3.4 + 'vc10': None, + # where msvc 14/vs 2015 is installed, for python 3.5 through 3.6 + 'vc14': None, + } + # whether to link libcurl against zlib + use_zlib = True + # which version of zlib to use, will be downloaded from internet + zlib_version = '1.2.11' + # whether to use openssl instead of winssl + use_openssl = True + # which version of openssl to use, will be downloaded from internet + openssl_version = '1.1.0h' + # whether to use c-ares + use_cares = True + cares_version = '1.14.0' + # whether to use libssh2 + use_libssh2 = True + libssh2_version = '1.8.0' + use_nghttp2 = True + nghttp2_version = '1.32.0' + use_libidn = False + libiconv_version = '1.15' + libidn_version = '1.35' + # which version of libcurl to use, will be downloaded from internet + libcurl_version = '7.60.0' + # virtualenv version + virtualenv_version = '15.1.0' + # whether to build binary wheels + build_wheels = True + # pycurl version to build, we should know this ourselves + pycurl_version = '7.43.0.2' + + # sometimes vc14 does not include windows sdk path in vcvars which breaks stuff. + # another application for this is to supply normaliz.lib for vc9 + # which has an older version that doesn't have the symbols we need + windows_sdk_path = 'c:\\program files (x86)\\microsoft sdks\\windows\\v7.1a' + +# *** +# No user-serviceable parts beyond this point +# *** + +import os, os.path, sys, subprocess, shutil, contextlib, zipfile, re +try: + from urllib.request import urlopen +except ImportError: + from urllib import urlopen + +# https://stackoverflow.com/questions/35569042/python-3-ssl-certificate-verify-failed +import ssl +try: + ssl._create_default_https_context = ssl._create_unverified_context +except AttributeError: + pass def short_python_versions(python_versions): return ['.'.join(python_version.split('.')[:2]) for python_version in python_versions] def needed_vc_versions(python_versions): - return [vc_version for vc_version in vc_paths.keys() + return [vc_version for vc_version in config.vc_paths.keys() if vc_version in [ - python_vc_versions[short_python_version] + PYTHON_VC_VERSIONS[short_python_version] for short_python_version in short_python_versions(python_versions)]] -import os, os.path, sys, subprocess, shutil, contextlib, zipfile, re - def select_existing_path(paths): if isinstance(paths, list) or isinstance(paths, tuple): for path in paths: @@ -109,18 +134,134 @@ else: return paths -nasm_path = select_existing_path(nasm_path) -activestate_perl_path = select_existing_path(activestate_perl_path) +# This must be at top level as __file__ can be a relative path +# and changing current directory will break it +DIR_HERE = os.path.abspath(os.path.dirname(__file__)) + +def find_in_paths(binary, paths): + for path in paths: + if os.path.exists(os.path.join(path, binary)) or os.path.exists(os.path.join(path, binary + '.exe')): + return os.path.join(path, binary) + raise Exception('Could not find %s' % binary) + +def check_call(cmd): + try: + subprocess.check_call(cmd) + except Exception as e: + raise Exception('Failed to execute ' + str(cmd) + ': ' + str(type(e)) + ': ' +str(e)) + +class ExtendedConfig(Config): + '''Global configuration that specifies what the entire process will do. + + Unlike Config, this class contains also various derived properties + for convenience. + ''' + + def __init__(self, **kwargs): + for k in kwargs: + setattr(self, k, kwargs[k]) + + # These are defaults, overrides can be specified as vc_paths in Config above + default_vc_paths = { + # where msvc 9 is installed, for python 2.6-3.2 + 'vc9': [ + 'c:/program files (x86)/microsoft visual studio 9.0', + 'c:/program files/microsoft visual studio 9.0', + ], + # where msvc 10 is installed, for python 3.3-3.4 + 'vc10': [ + 'c:/program files (x86)/microsoft visual studio 10.0', + 'c:/program files/microsoft visual studio 10.0', + ], + # where msvc 14 is installed, for python 3.5-3.6 + 'vc14': [ + 'c:/program files (x86)/microsoft visual studio 14.0', + 'c:/program files/microsoft visual studio 14.0', + ], + } + + @property + def nasm_path(self): + return select_existing_path(Config.nasm_path) + + @property + def activestate_perl_path(self): + return select_existing_path(Config.activestate_perl_path) + + @property + def archives_path(self): + return os.path.join(self.root, 'archives') + + @property + def state_path(self): + return os.path.join(self.root, 'state') + + @property + def git_bin_path(self): + #git_bin_path = os.path.join(git_root, 'bin') + return '' + + @property + def git_path(self): + return os.path.join(self.git_bin_path, 'git') + + @property + def rm_path(self): + return find_in_paths('rm', self.msysgit_bin_paths) + + @property + def cp_path(self): + return find_in_paths('cp', self.msysgit_bin_paths) + + @property + def sed_path(self): + return find_in_paths('sed', self.msysgit_bin_paths) + + @property + def tar_path(self): + return find_in_paths('tar', self.msysgit_bin_paths) + + @property + def activestate_perl_bin_path(self): + return os.path.join(self.activestate_perl_path, 'bin') + + @property + def winbuild_patch_root(self): + return os.path.join(DIR_HERE, 'winbuild') + + @property + def openssl_version_tuple(self): + return tuple( + int(part) if part < 'a' else part + for part in re.sub(r'([a-z])', r'.\1', self.openssl_version).split('.') + ) + + @property + def libssh2_version_tuple(self): + return tuple(int(part) for part in self.libssh2_version.split('.')) + + @property + def cares_version_tuple(self): + return tuple(int(part) for part in self.cares_version.split('.')) + + @property + def libcurl_version_tuple(self): + return tuple(int(part) for part in self.libcurl_version.split('.')) + + @property + def python_releases(self): + return [PythonRelease('.'.join(version.split('.')[:2])) + for version in self.python_versions] -archives_path = os.path.join(root, 'archives') -state_path = os.path.join(root, 'state') -#git_bin_path = os.path.join(git_root, 'bin') -git_bin_path = '' -git_path = os.path.join(git_bin_path, 'git') -rm_path = os.path.join(git_bin_path, 'rm') -tar_path = os.path.join(git_bin_path, 'tar') -activestate_perl_bin_path = os.path.join(activestate_perl_path, 'bin') -python_vc_versions = { + def buildconfigs(self): + return [BuildConfig(bitness=bitness, vc_version=vc_version) + for bitness in self.bitnesses + for vc_version in needed_vc_versions(self.python_versions) + ] + +BITNESSES = (32, 64) + +PYTHON_VC_VERSIONS = { '2.6': 'vc9', '2.7': 'vc9', '3.2': 'vc9', @@ -129,23 +270,17 @@ '3.5': 'vc14', '3.6': 'vc14', } -vc_versions = vc_paths.keys() -dir_here = os.path.abspath(os.path.dirname(__file__)) - -openssl_version_tuple = tuple( - int(part) if part < 'a' else part - for part in re.sub(r'([a-z])', r'.\1', openssl_version).split('.') -) - -try: - from urllib.request import urlopen -except ImportError: - from urllib import urlopen def mkdir_p(path): if not os.path.exists(path): os.makedirs(path) +def rm_rf(path): + check_call([config.rm_path, '-rf', path]) + +def cp_r(src, dest): + check_call([config.cp_path, '-r', src, dest]) + def fetch(url, archive=None): if archive is None: archive = os.path.basename(url) @@ -164,8 +299,8 @@ os.rename(tmp_path, archive) def fetch_to_archives(url): - mkdir_p(archives_path) - path = os.path.join(archives_path, os.path.basename(url)) + mkdir_p(config.archives_path) + path = os.path.join(config.archives_path, os.path.basename(url)) fetch(url, path) @contextlib.contextmanager @@ -181,8 +316,8 @@ def step(step_fn, args, target_dir): #step = step_fn.__name__ state_tag = target_dir - mkdir_p(state_path) - state_file_path = os.path.join(state_path, state_tag) + mkdir_p(config.state_path) + state_file_path = os.path.join(config.state_path, state_tag) if not os.path.exists(state_file_path) or not os.path.exists(target_dir): step_fn(*args) with open(state_file_path, 'w'): @@ -191,14 +326,12 @@ def untar(basename): if os.path.exists(basename): shutil.rmtree(basename) - subprocess.check_call([tar_path, 'xf', '%s.tar.gz' % basename]) - -def rename_for_vc(basename, suffix): - suffixed_dir = '%s-%s' % (basename, suffix) - if os.path.exists(suffixed_dir): - shutil.rmtree(suffixed_dir) - os.rename(basename, suffixed_dir) - return suffixed_dir + check_call([config.tar_path, 'xf', '%s.tar.gz' % basename]) + +def require_file_exists(path): + if not os.path.exists(path): + raise Exception('Path %s does not exist!' % path) + return path class PythonRelease(str): @property @@ -210,10 +343,6 @@ def release(self): return PythonRelease('.'.join(self.split('.')[:2])) -def python_releases(): - return [PythonRelease('.'.join(version.split('.')[:2])) - for version in python_versions] - class PythonBinary(object): def __init__(self, python_release, bitness): self.python_release = python_release @@ -221,17 +350,37 @@ @property def executable_path(self): - return python_path_template % dict( + return config.python_path_template % dict( python_release=self.python_release.dotless, bitness=self.bitness) -class Builder(object): - def __init__(self, **kwargs): - bitness = kwargs.pop('bitness') - assert bitness in (32, 64) - self.bitness = bitness - self.vc_version = kwargs.pop('vc_version') - self.use_dlls = False +class Batch(object): + def __init__(self, bconf): + self.bconf = bconf + self.commands = [] + + self.add(self.vcvars_cmd) + self.add('echo on') + if self.bconf.vc_version == 'vc14': + # I don't know why vcvars doesn't configure this under vc14 + self.add('set include=%s\\include;%%include%%' % self.bconf.windows_sdk_path) + if self.bconf.bitness == 32: + self.add('set lib=%s\\lib;%%lib%%' % self.bconf.windows_sdk_path) + self.add('set path=%s\\bin;%%path%%' % self.bconf.windows_sdk_path) + else: + self.add('set lib=%s\\lib\\x64;%%lib%%' % self.bconf.windows_sdk_path) + self.add('set path=%s\\bin\\x64;%%path%%' % self.bconf.windows_sdk_path) + self.add(self.nasm_cmd) + + def add(self, cmd): + self.commands.append(cmd) + + # if patch fails to apply hunks, it exits with nonzero code. + # if patch doesn't find the patch file to apply, it exits with a zero code! + ERROR_CHECK = 'IF %ERRORLEVEL% NEQ 0 exit %errorlevel%' + + def batch_text(self): + return ("\n" + self.ERROR_CHECK + "\n").join(self.commands) @property def vcvars_bitness_parameter(self): @@ -239,7 +388,7 @@ 32: 'x86', 64: 'amd64', } - return params[self.bitness] + return params[self.bconf.bitness] @property def vcvars_relative_path(self): @@ -247,13 +396,13 @@ @property def vc_path(self): - if self.vc_version in vc_paths and vc_paths[self.vc_version]: - path = vc_paths[self.vc_version] + if self.bconf.vc_version in config.vc_paths and config.vc_paths[self.bconf.vc_version]: + path = config.vc_paths[self.bconf.vc_version] if not os.path.join(path, self.vcvars_relative_path): raise Exception('vcvars not found in specified path') return path else: - for path in default_vc_paths[self.vc_version]: + for path in config.default_vc_paths[self.bconf.vc_version]: if os.path.exists(os.path.join(path, self.vcvars_relative_path)): return path raise Exception('No usable vc path found') @@ -265,31 +414,45 @@ @property def vcvars_cmd(self): # https://msdn.microsoft.com/en-us/library/x4d2c09s.aspx - return "call \"%s\" %s\n" % ( + return "call \"%s\" %s" % ( self.vcvars_path, self.vcvars_bitness_parameter, ) @property def nasm_cmd(self): - return "set path=%s;%%path%%\n" % nasm_path + return "set path=%s;%%path%%\n" % config.nasm_path + +class BuildConfig(ExtendedConfig): + '''Parameters for a particular build configuration. + + Unlike ExtendedConfig, this class fixes bitness and Python version. + ''' + + def __init__(self, **kwargs): + ExtendedConfig.__init__(self, **kwargs) + for k in kwargs: + setattr(self, k, kwargs[k]) + + assert self.bitness + assert self.bitness in (32, 64) + assert self.vc_version + + @property + def vc_tag(self): + return '%s-%s' % (self.vc_version, self.bitness) + +class Builder(object): + def __init__(self, **kwargs): + self.bconf = kwargs.pop('bconf') + self.use_dlls = False @contextlib.contextmanager def execute_batch(self): + batch = Batch(self.bconf) + yield batch with open('doit.bat', 'w') as f: - f.write(self.vcvars_cmd) - if self.vc_version == 'vc14': - # I don't know why vcvars doesn't configure this under vc14 - windows_sdk_path = 'c:\\program files (x86)\\microsoft sdks\\windows\\v7.1a' - f.write('set include=%s\\include;%%include%%\n' % windows_sdk_path) - if self.bitness == 32: - f.write('set lib=%s\\lib;%%lib%%\n' % windows_sdk_path) - f.write('set path=%s\\bin;%%path%%\n' % windows_sdk_path) - else: - f.write('set lib=%s\\lib\\x64;%%lib%%\n' % windows_sdk_path) - f.write('set path=%s\\bin\\x64;%%path%%\n' % windows_sdk_path) - f.write(self.nasm_cmd) - yield f + f.write(batch.batch_text()) if False: print("Executing:") with open('doit.bat', 'r') as f: @@ -303,80 +466,97 @@ sys.stdout.flush() exit(3) +class StandardBuilder(Builder): @property - def vc_tag(self): - return '%s-%s' % (self.vc_version, self.bitness) - -class ZlibBuilder(Builder): - def __init__(self, **kwargs): - super(ZlibBuilder, self).__init__(**kwargs) - self.zlib_version = kwargs.pop('zlib_version') + def state_tag(self): + return self.output_dir_path @property - def state_tag(self): - return 'zlib-%s-%s' % (self.zlib_version, self.vc_tag) + def bin_path(self): + return os.path.join(config.archives_path, self.output_dir_path, 'dist', 'bin') - def build(self): - fetch('http://downloads.sourceforge.net/project/libpng/zlib/%s/zlib-%s.tar.gz' % (self.zlib_version, self.zlib_version)) - untar('zlib-%s' % self.zlib_version) - zlib_dir = rename_for_vc('zlib-%s' % self.zlib_version, self.vc_tag) - with in_dir(zlib_dir): - with self.execute_batch() as f: - f.write("nmake /f win32/Makefile.msc\n") + @property + def include_path(self): + return os.path.join(config.archives_path, self.output_dir_path, 'dist', 'include') @property - def output_dir_path(self): - return 'zlib-%s-%s' % (self.zlib_version, self.vc_tag) + def lib_path(self): + return os.path.join(config.archives_path, self.output_dir_path, 'dist', 'lib') @property def dll_paths(self): - return [ - os.path.join(self.output_dir_path, 'zlib1.dll'), - ] + raise NotImplementedError @property - def include_path(self): - return os.path.join(archives_path, self.output_dir_path) + def builder_name(self): + return self.__class__.__name__.replace('Builder', '').lower() + + @property + def my_version(self): + return getattr(self.bconf, '%s_version' % self.builder_name) @property - def lib_path(self): - return os.path.join(archives_path, self.output_dir_path) + def output_dir_path(self): + return '%s-%s-%s' % (self.builder_name, self.my_version, self.bconf.vc_tag) + + def standard_fetch_extract(self, url_template): + url = url_template % dict( + my_version=self.my_version, + ) + fetch(url) + archive_basename = os.path.basename(url) + archive_name = archive_basename.replace('.tar.gz', '') + untar(archive_name) + + suffixed_dir = self.output_dir_path + if os.path.exists(suffixed_dir): + shutil.rmtree(suffixed_dir) + os.rename(archive_name, suffixed_dir) + return suffixed_dir -class OpensslBuilder(Builder): - def __init__(self, **kwargs): - super(OpensslBuilder, self).__init__(**kwargs) - self.openssl_version = kwargs.pop('openssl_version') +class ZlibBuilder(StandardBuilder): + def build(self): + zlib_dir = self.standard_fetch_extract( + 'http://downloads.sourceforge.net/project/libpng/zlib/%(my_version)s/zlib-%(my_version)s.tar.gz') + with in_dir(zlib_dir): + with self.execute_batch() as b: + b.add("nmake /f win32/Makefile.msc") + # libcurl loves its _a suffixes on static library names + b.add("cp zlib.lib zlib_a.lib") + + # assemble dist + b.add('mkdir dist dist\\include dist\\lib dist\\bin') + b.add('cp *.lib *.exp dist/lib') + b.add('cp *.dll dist/bin') + b.add('cp *.h dist/include') @property - def state_tag(self): - return 'openssl-%s-%s' % (self.openssl_version, self.vc_tag) + def dll_paths(self): + return [ + os.path.join(self.bin_path, 'zlib1.dll'), + ] +class OpensslBuilder(StandardBuilder): def build(self): - fetch('https://www.openssl.org/source/openssl-%s.tar.gz' % self.openssl_version) - try: - untar('openssl-%s' % self.openssl_version) - except subprocess.CalledProcessError: - # openssl tarballs include symlinks which cannot be extracted on windows, - # and hence cause errors during extraction. - # apparently these symlinks will be regenerated at configure stage... - # makes one wonder why they are included in the first place. - pass # another openssl gem: # nasm output is redirected to NUL which ends up creating a file named NUL. # however being a reserved file name this file is not deletable by # ordinary tools. - nul_file = "openssl-%s-%s\\NUL" % (self.openssl_version, self.vc_tag) - subprocess.check_call(['rm', '-f', nul_file]) - openssl_dir = rename_for_vc('openssl-%s' % self.openssl_version, self.vc_tag) + nul_file = "openssl-%s-%s\\NUL" % (self.bconf.openssl_version, self.bconf.vc_tag) + check_call(['rm', '-f', nul_file]) + openssl_dir = self.standard_fetch_extract( + 'https://www.openssl.org/source/openssl-%(my_version)s.tar.gz') with in_dir(openssl_dir): - with self.execute_batch() as f: - if openssl_version_tuple < (1, 1): + with self.execute_batch() as b: + if self.bconf.openssl_version_tuple < (1, 1): # openssl 1.0.2 - f.write("patch -p0 < %s\n" % os.path.join(dir_here, 'winbuild', 'openssl-fix-crt-1.0.2.patch')) + b.add("patch -p0 < %s" % + require_file_exists(os.path.join(config.winbuild_patch_root, 'openssl-fix-crt-1.0.2.patch'))) else: # openssl 1.1.0 - f.write("patch -p0 < %s\n" % os.path.join(dir_here, 'winbuild', 'openssl-fix-crt-1.1.0.patch')) - if self.bitness == 64: + b.add("patch -p0 < %s" % + require_file_exists(os.path.join(config.winbuild_patch_root, 'openssl-fix-crt-1.1.0.patch'))) + if self.bconf.bitness == 64: target = 'VC-WIN64A' batch_file = 'do_win64a' else: @@ -387,18 +567,18 @@ # win64 assembly things in openssl 1.0.2 # and in x86 assembly as well in openssl 1.1.0; # use ActiveState Perl - if not os.path.exists(activestate_perl_bin_path): + if not os.path.exists(config.activestate_perl_bin_path): raise ValueError('activestate_perl_bin_path refers to a nonexisting path') - if not os.path.exists(os.path.join(activestate_perl_bin_path, 'perl.exe')): + if not os.path.exists(os.path.join(config.activestate_perl_bin_path, 'perl.exe')): raise ValueError('No perl binary in activestate_perl_bin_path') - f.write("set path=%s;%%path%%\n" % activestate_perl_bin_path) - f.write("perl -v\n") + b.add("set path=%s;%%path%%" % config.activestate_perl_bin_path) + b.add("perl -v") openssl_prefix = os.path.join(os.path.realpath('.'), 'build') # Do not want compression: # https://en.wikipedia.org/wiki/CRIME extras = ['no-comp'] - if openssl_version_tuple >= (1, 1): + if config.openssl_version_tuple >= (1, 1): # openssl 1.1.0 # in 1.1.0 the static/shared selection is handled by # invoking the right makefile @@ -409,96 +589,58 @@ # and like 1.0.2 does; provide a relative openssl dir # manually extras += ['--openssldir=ssl'] - f.write("perl Configure %s %s --prefix=%s\n" % (target, ' '.join(extras), openssl_prefix)) + b.add("perl Configure %s %s --prefix=%s" % (target, ' '.join(extras), openssl_prefix)) - if openssl_version_tuple < (1, 1): + if config.openssl_version_tuple < (1, 1): # openssl 1.0.2 - f.write("call ms\\%s\n" % batch_file) - f.write("nmake -f ms\\nt.mak\n") - f.write("nmake -f ms\\nt.mak install\n") + b.add("call ms\\%s" % batch_file) + b.add("nmake -f ms\\nt.mak") + b.add("nmake -f ms\\nt.mak install") else: # openssl 1.1.0 - f.write("nmake\n") - f.write("nmake install\n") - - @property - def output_dir_path(self): - return 'openssl-%s-%s/build' % (self.openssl_version, self.vc_tag) - - @property - def dll_paths(self): - raise NotImplemented - - @property - def include_path(self): - return os.path.join(archives_path, self.output_dir_path, 'include') - - @property - def lib_path(self): - return os.path.join(archives_path, self.output_dir_path, 'lib') - -class CaresBuilder(Builder): - def __init__(self, **kwargs): - super(CaresBuilder, self).__init__(**kwargs) - self.cares_version = kwargs.pop('cares_version') - - @property - def state_tag(self): - return 'c-ares-%s-%s' % (self.cares_version, self.vc_tag) + b.add("nmake") + b.add("nmake install") + + # assemble dist + b.add('mkdir dist') + b.add('cp -r build/include build/lib dist') +class CaresBuilder(StandardBuilder): def build(self): - fetch('http://c-ares.haxx.se/download/c-ares-%s.tar.gz' % (self.cares_version)) - untar('c-ares-%s' % self.cares_version) - if self.cares_version == '1.12.0': + cares_dir = self.standard_fetch_extract( + 'http://c-ares.haxx.se/download/c-ares-%(my_version)s.tar.gz') + if self.bconf.cares_version == '1.12.0': # msvc_ver.inc is missing in c-ares-1.12.0.tar.gz # https://github.com/c-ares/c-ares/issues/69 fetch('https://raw.githubusercontent.com/c-ares/c-ares/cares-1_12_0/msvc_ver.inc', - archive='c-ares-1.12.0/msvc_ver.inc') - cares_dir = rename_for_vc('c-ares-%s' % self.cares_version, self.vc_tag) + archive='cares-1.12.0/msvc_ver.inc') with in_dir(cares_dir): - with self.execute_batch() as f: - if self.cares_version == '1.10.0': - f.write("patch -p1 < %s\n" % os.path.join(dir_here, 'winbuild', 'c-ares-vs2015.patch')) - f.write("nmake -f Makefile.msvc\n") - - @property - def output_dir_path(self): - return 'c-ares-%s-%s' % (self.cares_version, self.vc_tag) - - @property - def dll_paths(self): - raise NotImplemented - - @property - def include_path(self): - return os.path.join(archives_path, self.output_dir_path) - - @property - def lib_path(self): - return os.path.join(archives_path, self.output_dir_path, - 'ms%s0' % self.vc_version, 'cares', 'lib-release') - -class Libssh2Builder(Builder): - def __init__(self, **kwargs): - super(Libssh2Builder, self).__init__(**kwargs) - self.libssh2_version = kwargs.pop('libssh2_version') - self.zlib_version = kwargs.pop('zlib_version') - self.openssl_version = kwargs.pop('openssl_version') - - @property - def state_tag(self): - return 'libssh2-%s-%s' % (self.libssh2_version, self.vc_tag) + with self.execute_batch() as b: + if self.bconf.cares_version == '1.10.0': + b.add("patch -p1 < %s" % + require_file_exists(os.path.join(config.winbuild_patch_root, 'c-ares-vs2015.patch'))) + b.add("nmake -f Makefile.msvc") + + # assemble dist + b.add('mkdir dist dist\\include dist\\lib') + if self.bconf.cares_version_tuple < (1, 14, 0): + subdir = 'ms%s0' % self.bconf.vc_version + else: + subdir = 'msvc' + b.add('cp %s/cares/lib-release/*.lib dist/lib' % subdir) + b.add('cp *.h dist/include') +class Libssh2Builder(StandardBuilder): def build(self): - fetch('http://www.libssh2.org/download/libssh2-%s.tar.gz' % (self.libssh2_version)) - untar('libssh2-%s' % self.libssh2_version) - libssh2_dir = rename_for_vc('libssh2-%s' % self.libssh2_version, self.vc_tag) + libssh2_dir = self.standard_fetch_extract( + 'http://www.libssh2.org/download/libssh2-%(my_version)s.tar.gz') with in_dir(libssh2_dir): - with self.execute_batch() as f: - if self.vc_version == 'vc14': - f.write("patch -p0 < %s\n" % os.path.join(dir_here, 'winbuild', 'libssh2-vs2015.patch')) - zlib_builder = ZlibBuilder(bitness=self.bitness, vc_version=self.vc_version, zlib_version=self.zlib_version) - openssl_builder = OpensslBuilder(bitness=self.bitness, vc_version=self.vc_version, openssl_version=self.openssl_version) + with self.execute_batch() as b: + if self.bconf.libssh2_version_tuple < (1, 8, 0) and self.bconf.vc_version == 'vc14': + b.add("patch -p0 < %s" % + require_file_exists(os.path.join(config.winbuild_patch_root, 'libssh2-vs2015.patch'))) + zlib_builder = ZlibBuilder(bconf=self.bconf) + openssl_builder = OpensslBuilder(bconf=self.bconf) vars = ''' OPENSSLINC=%(openssl_include_path)s OPENSSLLIB=%(openssl_lib_path)s @@ -517,186 +659,245 @@ cf.seek(0) cf.write(vars) cf.write(contents) - f.write("nmake -f NMakefile\n") + b.add("nmake -f NMakefile") # libcurl loves its _a suffixes on static library names - f.write("cp Release\\src\\libssh2.lib Release\\src\\libssh2_a.lib\n") - - @property - def output_dir_path(self): - return 'libssh2-%s-%s' % (self.libssh2_version, self.vc_tag) - - @property - def dll_paths(self): - raise NotImplemented - - @property - def include_path(self): - return os.path.join(archives_path, self.output_dir_path, 'include') - - @property - def lib_path(self): - return os.path.join(archives_path, self.output_dir_path, - 'Release', 'src') + b.add("cp Release\\src\\libssh2.lib Release\\src\\libssh2_a.lib") + + # assemble dist + b.add('mkdir dist dist\\include dist\\lib') + b.add('cp Release/src/*.lib dist/lib') + b.add('cp -r include dist') + +class Nghttp2Builder(StandardBuilder): + CMAKE_GENERATORS = { + # Thanks cmake for requiring both version number and year, + # necessitating this additional map + 'vc9': 'Visual Studio 9 2008', + 'vc14': 'Visual Studio 14 2015', + } + + def build(self): + nghttp2_dir = self.standard_fetch_extract( + 'https://github.com/nghttp2/nghttp2/releases/download/v%(my_version)s/nghttp2-%(my_version)s.tar.gz') + + # nghttp2 uses stdint.h which msvc9 does not ship. + # Amazingly, nghttp2 can seemingly build successfully without this + # file existing, but libcurl build subsequently fails + # when it tries to include stdint.h. + # Well, the reason why nghttp2 builds correctly is because it is built + # with the wrong compiler - msvc14 when 9 and 14 are both installed. + # nghttp2 build with msvc9 does fail without stdint.h existing. + if self.bconf.vc_version == 'vc9': + # https://stackoverflow.com/questions/126279/c99-stdint-h-header-and-ms-visual-studio + fetch('https://raw.githubusercontent.com/mattn/gntp-send/master/include/msinttypes/stdint.h') + with in_dir(nghttp2_dir): + shutil.copy('../stdint.h', 'lib/includes/stdint.h') + + with in_dir(nghttp2_dir): + generator = self.CMAKE_GENERATORS[self.bconf.vc_version] + # Cmake also couldn't care less about the bitness I have configured in the + # environment since it ignores the environment entirely. + # Educate it on the required bitness by hand. + # https://stackoverflow.com/questions/28350214/how-to-build-x86-and-or-x64-on-windows-from-command-line-with-cmake#28370892 + if self.bconf.bitness == 64: + generator += ' Win64' + with self.execute_batch() as b: + cmd = ' '.join([ + '"%s"' % config.cmake_path, + # I don't know if this does anything, build type/config + # must be specified with --build option below. + '-DCMAKE_BUILD_TYPE=Release', + # This configures libnghttp2 only which is what we want. + # However, configure step still complains about all of the + # missing dependencies for nghttp2 server. + # And there is no indication whatsoever from configure step + # that this option is enabled, or that the missing + # dependency complaints can be ignored. + '-DENABLE_LIB_ONLY=1', + # This is required to get a static library built. + # However, even with this turned on there is still a DLL + # built - without an import library for it. + '-DENABLE_STATIC_LIB=1', + # And cmake ignores all visual studio environment variables + # and uses the newest compiler by default, which is great + # if one doesn't care what compiler their code is compiled with. + # https://stackoverflow.com/questions/6430251/what-is-the-default-generator-for-cmake-in-windows + '-G', '"%s"' % generator, + ]) + b.add('%s .' % cmd) + b.add(' '.join([ + '"%s"' % config.cmake_path, + '--build', '.', + # this is what produces a release build + '--config', 'Release', + # this builds the static library. + # without this option cmake configures itself to be capable + # of building a static library but sometimes builds a DLL + # and sometimes builds a static library + # depending on compiler in use (vc9/vc14) or, possibly, + # phase of the moon. + '--target', 'nghttp2_static', + ])) + + # libcurl and its library name expectations + b.add('cp lib/Release/nghttp2.lib lib/Release/nghttp2_static.lib') + + # assemble dist + b.add('mkdir dist dist\\include dist\\include\\nghttp2 dist\\lib') + b.add('cp lib/Release/*.lib dist/lib') + b.add('cp lib/includes/nghttp2/*.h dist/include/nghttp2') + if self.bconf.vc_version == 'vc9': + # stdint.h + b.add('cp lib/includes/*.h dist/include') -class LibcurlBuilder(Builder): - def __init__(self, **kwargs): - super(LibcurlBuilder, self).__init__(**kwargs) - self.libcurl_version = kwargs.pop('libcurl_version') - self.use_zlib = kwargs.pop('use_zlib') - if self.use_zlib: - self.zlib_version = kwargs.pop('zlib_version') - self.use_openssl = kwargs.pop('use_openssl') - if self.use_openssl: - self.openssl_version = kwargs.pop('openssl_version') - self.use_cares = kwargs.pop('use_cares') - if self.use_cares: - self.cares_version = kwargs.pop('cares_version') - self.use_libssh2 = kwargs.pop('use_libssh2') - if self.use_libssh2: - self.libssh2_version = kwargs.pop('libssh2_version') +class LibiconvBuilder(StandardBuilder): + def build(self): + libiconv_dir = self.standard_fetch_extract( + 'https://ftp.gnu.org/pub/gnu/libiconv/libiconv-%(my_version)s.tar.gz') + with in_dir(libiconv_dir): + with self.execute_batch() as b: + b.add("env LD=link bash ./configure") + b.add(config.gmake_path) - @property - def state_tag(self): - return 'curl-%s-%s' % (self.libcurl_version, self.vc_tag) +class LibidnBuilder(StandardBuilder): + def build(self): + libidn_dir = self.standard_fetch_extract( + 'https://ftp.gnu.org/gnu/libidn/libidn-%(my_version)s.tar.gz') + with in_dir(libidn_dir): + with self.execute_batch() as b: + b.add("env LD=link bash ./configure") +class LibcurlBuilder(StandardBuilder): def build(self): - fetch('https://curl.haxx.se/download/curl-%s.tar.gz' % self.libcurl_version) - untar('curl-%s' % self.libcurl_version) - curl_dir = rename_for_vc('curl-%s' % self.libcurl_version, self.vc_tag) + curl_dir = self.standard_fetch_extract( + 'https://curl.haxx.se/download/curl-%(my_version)s.tar.gz') + with in_dir(os.path.join(curl_dir, 'winbuild')): - with self.execute_batch() as f: - f.write("patch -p1 < %s\n" % os.path.join(dir_here, 'winbuild', 'libcurl-fix-zlib-references.patch')) + if self.bconf.vc_version == 'vc9': + # normaliz.lib in vc9 does not have the symbols libcurl + # needs for winidn. + # Handily we have a working normaliz.lib in vc14. + # Let's take the working one and copy it locally. + os.mkdir('support') + if self.bconf.bitness == 32: + shutil.copy(os.path.join(self.bconf.windows_sdk_path, 'lib', 'normaliz.lib'), + os.path.join('support', 'normaliz.lib')) + else: + shutil.copy(os.path.join(self.bconf.windows_sdk_path, 'lib', 'x64', 'normaliz.lib'), + os.path.join('support', 'normaliz.lib')) + + with self.execute_batch() as b: + b.add("patch -p1 < %s" % + require_file_exists(os.path.join(config.winbuild_patch_root, 'libcurl-fix-zlib-references.patch'))) if self.use_dlls: dll_or_static = 'dll' else: dll_or_static = 'static' extra_options = ' mode=%s' % dll_or_static - if self.use_zlib: - zlib_builder = ZlibBuilder(bitness=self.bitness, vc_version=self.vc_version, zlib_version=self.zlib_version) - f.write("set include=%%include%%;%s\n" % zlib_builder.include_path) - f.write("set lib=%%lib%%;%s\n" % zlib_builder.lib_path) + if self.bconf.vc_version == 'vc9': + # use normaliz.lib from msvc14/more recent windows sdk + b.add("set lib=%s;%%lib%%" % os.path.abspath('support')) + if self.bconf.use_zlib: + zlib_builder = ZlibBuilder(bconf=self.bconf) + b.add("set include=%%include%%;%s" % zlib_builder.include_path) + b.add("set lib=%%lib%%;%s" % zlib_builder.lib_path) extra_options += ' WITH_ZLIB=%s' % dll_or_static - if self.use_openssl: - openssl_builder = OpensslBuilder(bitness=self.bitness, vc_version=self.vc_version, openssl_version=self.openssl_version) - f.write("set include=%%include%%;%s\n" % openssl_builder.include_path) - f.write("set lib=%%lib%%;%s\n" % openssl_builder.lib_path) + if self.bconf.use_openssl: + openssl_builder = OpensslBuilder(bconf=self.bconf) + b.add("set include=%%include%%;%s" % openssl_builder.include_path) + b.add("set lib=%%lib%%;%s" % openssl_builder.lib_path) extra_options += ' WITH_SSL=%s' % dll_or_static - if self.use_cares: - cares_builder = CaresBuilder(bitness=self.bitness, vc_version=self.vc_version, cares_version=self.cares_version) - f.write("set include=%%include%%;%s\n" % cares_builder.include_path) - f.write("set lib=%%lib%%;%s\n" % cares_builder.lib_path) + if self.bconf.use_cares: + cares_builder = CaresBuilder(bconf=self.bconf) + b.add("set include=%%include%%;%s" % cares_builder.include_path) + b.add("set lib=%%lib%%;%s" % cares_builder.lib_path) extra_options += ' WITH_CARES=%s' % dll_or_static - if self.use_libssh2: - libssh2_builder = Libssh2Builder(bitness=self.bitness, vc_version=self.vc_version, libssh2_version=self.libssh2_version, zlib_version=self.zlib_version, openssl_version=self.openssl_version) - f.write("set include=%%include%%;%s\n" % libssh2_builder.include_path) - f.write("set lib=%%lib%%;%s\n" % libssh2_builder.lib_path) + if self.bconf.use_libssh2: + libssh2_builder = Libssh2Builder(bconf=self.bconf) + b.add("set include=%%include%%;%s" % libssh2_builder.include_path) + b.add("set lib=%%lib%%;%s" % libssh2_builder.lib_path) extra_options += ' WITH_SSH2=%s' % dll_or_static - if openssl_version_tuple >= (1, 1): + if self.bconf.use_nghttp2: + nghttp2_builder = Nghttp2Builder(bconf=self.bconf) + b.add("set include=%%include%%;%s" % nghttp2_builder.include_path) + b.add("set lib=%%lib%%;%s" % nghttp2_builder.lib_path) + extra_options += ' WITH_NGHTTP2=%s NGHTTP2_STATICLIB=1' % dll_or_static + if self.bconf.use_libidn: + libidn_builder = LibidnBuilder(bconf=self.bconf) + b.add("set include=%%include%%;%s" % libidn_builder.include_path) + b.add("set lib=%%lib%%;%s" % libidn_builder.lib_path) + extra_options += ' WITH_LIBIDN=%s' % dll_or_static + if config.openssl_version_tuple >= (1, 1): # openssl 1.1.0 # https://curl.haxx.se/mail/lib-2016-08/0104.html # https://github.com/curl/curl/issues/984 # crypt32.lib: http://stackoverflow.com/questions/37522654/linking-with-openssl-lib-statically extra_options += ' MAKE="NMAKE /e" SSL_LIBS="libssl.lib libcrypto.lib crypt32.lib"' - f.write("nmake /f Makefile.vc ENABLE_IDN=no%s\n" % extra_options) - - @property - def output_dir_name(self): - if self.use_dlls: - dll_or_static = 'dll' - else: - dll_or_static = 'static' - if self.use_zlib: - zlib_part = '-zlib-%s' % dll_or_static - else: - zlib_part = '' - # don't know when spnego is on and when it is off yet - if False: - spnego_part = '-spnego' - else: - spnego_part = '' - bitness_indicators = {32: 'x86', 64: 'x64'} - bitness_indicator = bitness_indicators[self.bitness] - if self.use_openssl: - winssl_part = '' - openssl_part = '-ssl-%s' % dll_or_static - else: - winssl_part = '-winssl' - openssl_part = '' - if self.use_cares: - cares_part = '-cares-%s' % dll_or_static - else: - cares_part = '' - if self.use_libssh2: - libssh2_part = '-ssh2-%s' % dll_or_static - else: - libssh2_part = '' - output_dir_name = 'libcurl-vc-%s-release-%s%s%s%s%s-ipv6-sspi%s%s' % ( - bitness_indicator, dll_or_static, openssl_part, cares_part, zlib_part, libssh2_part, spnego_part, winssl_part) - return output_dir_name - - @property - def output_dir_path(self): - curl_dir = 'curl-%s-%s/builds/%s' % ( - self.libcurl_version, self.vc_tag, self.output_dir_name) - return curl_dir + # https://github.com/curl/curl/issues/1863 + extra_options += ' VC=%s' % self.bconf.vc_version[2:] + + # curl uses winidn APIs that do not exist in msvc9: + # https://github.com/curl/curl/issues/1863 + # We work around the msvc9 deficiency by using + # msvc14 normaliz.lib on vc9. + extra_options += ' ENABLE_IDN=yes' + + b.add("nmake /f Makefile.vc %s" % extra_options) + + # assemble dist - figure out where libcurl put its files + # and move them to a more reasonable location + with in_dir(curl_dir): + subdirs = sorted(os.listdir('builds')) + if len(subdirs) != 3: + raise Exception('Should be 3 directories here') + expected_dir = subdirs.pop(0) + for dir in subdirs: + if not dir.startswith(expected_dir): + raise Exception('%s does not start with %s' % (dir, expected_dir)) + + os.rename(os.path.join('builds', expected_dir), 'dist') + if self.bconf.vc_version == 'vc9': + # need this normaliz.lib to build pycurl later on + shutil.copy('winbuild/support/normaliz.lib', 'dist/lib/normaliz.lib') + + # need libcurl.lib to build pycurl with --curl-dir argument + shutil.copy('dist/lib/libcurl_a.lib', 'dist/lib/libcurl.lib') @property def dll_paths(self): return [ - os.path.join(self.output_dir_path, 'bin', 'libcurl.dll'), + os.path.join(self.bin_path, 'libcurl.dll'), ] class PycurlBuilder(Builder): def __init__(self, **kwargs): self.python_release = kwargs.pop('python_release') - kwargs['vc_version'] = python_vc_versions[self.python_release] super(PycurlBuilder, self).__init__(**kwargs) - self.pycurl_version = kwargs.pop('pycurl_version') - self.libcurl_version = kwargs.pop('libcurl_version') - self.zlib_version = kwargs.pop('zlib_version') - self.use_zlib = kwargs.pop('use_zlib') - self.openssl_version = kwargs.pop('openssl_version') - self.use_openssl = kwargs.pop('use_openssl') - self.cares_version = kwargs.pop('cares_version') - self.use_cares = kwargs.pop('use_cares') - self.libssh2_version = kwargs.pop('libssh2_version') - self.use_libssh2 = kwargs.pop('use_libssh2') + # vc_version is specified externally for bconf/BuildConfig + assert self.bconf.vc_version == PYTHON_VC_VERSIONS[self.python_release] @property def python_path(self): - if build_wheels: - python_path = os.path.join(archives_path, 'venv-%s-%s' % (self.python_release, self.bitness), 'scripts', 'python') + if config.build_wheels: + python_path = os.path.join(config.archives_path, 'venv-%s-%s' % (self.python_release, self.bconf.bitness), 'scripts', 'python') else: - python_path = PythonBinary(self.python_release, self.bitness).executable_path + python_path = PythonBinary(self.python_release, self.bconf.bitness).executable_path return python_path @property def platform_indicator(self): platform_indicators = {32: 'win32', 64: 'win-amd64'} - return platform_indicators[self.bitness] + return platform_indicators[self.bconf.bitness] def build(self, targets): - libcurl_builder = LibcurlBuilder(bitness=self.bitness, - vc_version=self.vc_version, - use_zlib=self.use_zlib, - zlib_version=self.zlib_version, - use_openssl=self.use_openssl, - openssl_version=self.openssl_version, - use_cares=self.use_cares, - cares_version=self.cares_version, - use_libssh2=self.use_libssh2, - libssh2_version=self.libssh2_version, - libcurl_version=self.libcurl_version) - libcurl_dir = os.path.abspath(libcurl_builder.output_dir_path) + libcurl_builder = LibcurlBuilder(bconf=self.bconf) + libcurl_dir = os.path.join(os.path.abspath(libcurl_builder.output_dir_path), 'dist') dll_paths = libcurl_builder.dll_paths - if self.use_zlib: - zlib_builder = ZlibBuilder(bitness=self.bitness, - vc_version=self.vc_version, - zlib_version=self.zlib_version, - ) + if self.bconf.use_zlib: + zlib_builder = ZlibBuilder(bconf=self.bconf) dll_paths += zlib_builder.dll_paths dll_paths = [os.path.abspath(dll_path) for dll_path in dll_paths] - with in_dir(os.path.join('pycurl-%s' % self.pycurl_version)): + with in_dir(os.path.join('pycurl-%s' % self.bconf.pycurl_version)): dest_lib_path = 'build/lib.%s-%s' % (self.platform_indicator, self.python_release) # exists for building additional targets for the same python version @@ -704,30 +905,71 @@ if self.use_dlls: for dll_path in dll_paths: shutil.copy(dll_path, dest_lib_path) - with self.execute_batch() as f: - f.write("%s setup.py docstrings\n" % (self.python_path,)) + with self.execute_batch() as b: + b.add("%s setup.py docstrings" % (self.python_path,)) if self.use_dlls: libcurl_arg = '--use-libcurl-dll' else: libcurl_arg = '--libcurl-lib-name=libcurl_a.lib' - if self.use_openssl: + if self.bconf.use_openssl: libcurl_arg += ' --with-openssl' - if openssl_version_tuple >= (1, 1): + if config.openssl_version_tuple >= (1, 1): libcurl_arg += ' --openssl-lib-name=""' - openssl_builder = OpensslBuilder(bitness=self.bitness, vc_version=self.vc_version, openssl_version=self.openssl_version) - f.write("set include=%%include%%;%s\n" % openssl_builder.include_path) - f.write("set lib=%%lib%%;%s\n" % openssl_builder.lib_path) + openssl_builder = OpensslBuilder(bconf=self.bconf) + b.add("set include=%%include%%;%s" % openssl_builder.include_path) + b.add("set lib=%%lib%%;%s" % openssl_builder.lib_path) #if build_wheels: - #f.write("call %s\n" % os.path.join('..', 'venv-%s-%s' % (self.python_release, self.bitness), 'Scripts', 'activate')) - if build_wheels: + #b.add("call %s" % os.path.join('..', 'venv-%s-%s' % (self.python_release, self.bconf.bitness), 'Scripts', 'activate')) + if config.build_wheels: targets = targets + ['bdist_wheel'] - f.write("%s setup.py %s --curl-dir=%s %s\n" % ( + if config.libcurl_version_tuple >= (7, 60, 0): + # As of 7.60.0 libcurl does not include its dependencies into + # its static libraries. + # libcurl_a.lib in 7.59.0 is 30 mb. + # libcurl_a.lib in 7.60.0 is 2 mb. + # https://github.com/curl/curl/pull/2474 is most likely culprit. + # As a result we need to specify all of the libraries that + # libcurl depends on here, plus the library paths, + # plus even windows standard libraries for good measure. + if self.bconf.use_zlib: + zlib_builder = ZlibBuilder(bconf=self.bconf) + libcurl_arg += ' --link-arg=/LIBPATH:%s' % zlib_builder.lib_path + libcurl_arg += ' --link-arg=zlib.lib' + if self.bconf.use_openssl: + openssl_builder = OpensslBuilder(bconf=self.bconf) + libcurl_arg += ' --link-arg=/LIBPATH:%s' % openssl_builder.lib_path + # openssl 1.1 + libcurl_arg += ' --link-arg=libcrypto.lib' + libcurl_arg += ' --link-arg=libssl.lib' + libcurl_arg += ' --link-arg=crypt32.lib' + libcurl_arg += ' --link-arg=advapi32.lib' + if self.bconf.use_cares: + cares_builder = CaresBuilder(bconf=self.bconf) + libcurl_arg += ' --link-arg=/LIBPATH:%s' % cares_builder.lib_path + libcurl_arg += ' --link-arg=libcares.lib' + if self.bconf.use_libssh2: + libssh2_builder = Libssh2Builder(bconf=self.bconf) + libcurl_arg += ' --link-arg=/LIBPATH:%s' % libssh2_builder.lib_path + libcurl_arg += ' --link-arg=libssh2.lib' + if self.bconf.use_nghttp2: + nghttp2_builder = Nghttp2Builder(bconf=self.bconf) + libcurl_arg += ' --link-arg=/LIBPATH:%s' % nghttp2_builder.lib_path + libcurl_arg += ' --link-arg=nghttp2.lib' + if self.bconf.vc_version == 'vc9': + # this is for normaliz.lib + libcurl_builder = LibcurlBuilder(bconf=self.bconf) + libcurl_arg += ' --link-arg=/LIBPATH:%s' % libcurl_builder.lib_path + # We always use normaliz.lib, but it may come from + # "standard" msvc location or from libcurl's lib dir for msvc9 + libcurl_arg += ' --link-arg=normaliz.lib' + libcurl_arg += ' --link-arg=user32.lib' + b.add("%s setup.py %s --curl-dir=%s %s" % ( self.python_path, ' '.join(targets), libcurl_dir, libcurl_arg)) if 'bdist' in targets: zip_basename_orig = 'pycurl-%s.%s.zip' % ( - self.pycurl_version, self.platform_indicator) + self.bconf.pycurl_version, self.platform_indicator) zip_basename_new = 'pycurl-%s.%s-py%s.zip' % ( - self.pycurl_version, self.platform_indicator, self.python_release) + self.bconf.pycurl_version, self.platform_indicator, self.python_release) with zipfile.ZipFile('dist/%s' % zip_basename_orig, 'r') as src_zip: with zipfile.ZipFile('dist/%s' % zip_basename_new, 'w') as dest_zip: for name in src_zip.namelist(): @@ -743,79 +985,73 @@ member = src_zip.open(name) dest_zip.writestr(new_name, member.read(), zipfile.ZIP_DEFLATED) -def build_dependencies(bitnesses=(32, 64)): - if use_libssh2: - if not use_zlib: +def dep_builders(bconf): + builders = [] + if config.use_zlib: + builders.append(ZlibBuilder) + if config.use_openssl: + builders.append(OpensslBuilder) + if config.use_cares: + builders.append(CaresBuilder) + if config.use_libssh2: + builders.append(Libssh2Builder) + if config.use_nghttp2: + builders.append(Nghttp2Builder) + if config.use_libidn: + builders.append(LibiconvBuilder) + builders.append(LibidnBuilder) + builders.append(LibcurlBuilder) + builders = [ + cls(bconf=bconf) + for cls in builders + ] + return builders + +def build_dependencies(config): + if config.use_libssh2: + if not config.use_zlib: # technically we can build libssh2 without zlib but I don't want to bother - raise ValueError('use_zlib must be True if use_libssh2 is True') - if not use_openssl: - raise ValueError('use_openssl must be True if use_libssh2 is True') - - if git_bin_path: - os.environ['PATH'] += ";%s" % git_bin_path - mkdir_p(archives_path) - with in_dir(archives_path): - for bitness in bitnesses: - for vc_version in needed_vc_versions(python_versions): + raise ValueError('use_zlib must be true if use_libssh2 is true') + if not config.use_openssl: + raise ValueError('use_openssl must be true if use_libssh2 is true') + + if config.git_bin_path: + os.environ['PATH'] += ";%s" % config.git_bin_path + mkdir_p(config.archives_path) + with in_dir(config.archives_path): + for bconf in config.buildconfigs(): if opts.verbose: - print('Builddep for %s, %s-bit' % (vc_version, bitness)) - if use_zlib: - zlib_builder = ZlibBuilder(bitness=bitness, vc_version=vc_version, zlib_version=zlib_version) - step(zlib_builder.build, (), zlib_builder.state_tag) - if use_openssl: - openssl_builder = OpensslBuilder(bitness=bitness, vc_version=vc_version, openssl_version=openssl_version) - step(openssl_builder.build, (), openssl_builder.state_tag) - if use_cares: - cares_builder = CaresBuilder(bitness=bitness, vc_version=vc_version, cares_version=cares_version) - step(cares_builder.build, (), cares_builder.state_tag) - if use_libssh2: - libssh2_builder = Libssh2Builder(bitness=bitness, vc_version=vc_version, libssh2_version=libssh2_version, zlib_version=zlib_version, openssl_version=openssl_version) - step(libssh2_builder.build, (), libssh2_builder.state_tag) - libcurl_builder = LibcurlBuilder(bitness=bitness, vc_version=vc_version, - use_zlib=use_zlib, zlib_version=zlib_version, - use_openssl=use_openssl, openssl_version=openssl_version, - use_cares=use_cares, cares_version=cares_version, - use_libssh2=use_libssh2, libssh2_version=libssh2_version, - libcurl_version=libcurl_version) - step(libcurl_builder.build, (), libcurl_builder.state_tag) + print('Builddep for %s, %s-bit' % (bconf.vc_version, bconf.bitness)) + for builder in dep_builders(bconf): + step(builder.build, (), builder.state_tag) -bitnesses = (32, 64) - -def build(): +def build(config): # note: adds git_bin_path to PATH if necessary, and creates archives_path - build_dependencies(bitnesses) - with in_dir(archives_path): + build_dependencies(config) + with in_dir(config.archives_path): def prepare_pycurl(): #fetch('https://dl.bintray.com/pycurl/pycurl/pycurl-%s.tar.gz' % pycurl_version) - if os.path.exists('pycurl-%s' % pycurl_version): - #shutil.rmtree('pycurl-%s' % pycurl_version) - subprocess.check_call([rm_path, '-rf', 'pycurl-%s' % pycurl_version]) - #subprocess.check_call([tar_path, 'xf', 'pycurl-%s.tar.gz' % pycurl_version]) - shutil.copytree('c:/dev/pycurl', 'pycurl-%s' % pycurl_version) - if build_wheels: - with in_dir('pycurl-%s' % pycurl_version): - subprocess.check_call(['sed', '-i', - 's/from distutils.core import setup/from setuptools import setup/', - 'setup.py']) + if os.path.exists('pycurl-%s' % config.pycurl_version): + # shutil.rmtree is incapable of removing .git directory because it contains + # files marked read-only (tested on python 2.7 and 3.6) + #shutil.rmtree('pycurl-%s' % config.pycurl_version) + rm_rf('pycurl-%s' % config.pycurl_version) + #check_call([tar_path, 'xf', 'pycurl-%s.tar.gz' % pycurl_version]) + shutil.copytree('c:/dev/pycurl', 'pycurl-%s' % config.pycurl_version) prepare_pycurl() - for bitness in bitnesses: - for python_release in python_releases(): + for bitness in config.bitnesses: + for python_release in config.python_releases: targets = ['bdist', 'bdist_wininst', 'bdist_msi'] - vc_version = python_vc_versions[python_release] - builder = PycurlBuilder(bitness=bitness, vc_version=vc_version, - python_release=python_release, pycurl_version=pycurl_version, - use_zlib=use_zlib, zlib_version=zlib_version, - use_openssl=use_openssl, openssl_version=openssl_version, - use_cares=use_cares, cares_version=cares_version, - use_libssh2=use_libssh2, libssh2_version=libssh2_version, - libcurl_version=libcurl_version) + vc_version = PYTHON_VC_VERSIONS[python_release] + bconf = BuildConfig(bitness=bitness, vc_version=vc_version) + builder = PycurlBuilder(bconf=bconf, python_release=python_release) builder.build(targets) def python_metas(): metas = [] - for version in python_versions: + for version in config.python_versions: parts = [int(part) for part in version.split('.')] if parts[0] >= 3 and parts[1] >= 5: ext = 'exe' @@ -834,23 +1070,23 @@ metas.append(meta) return metas -def download_pythons(): +def download_pythons(config): for meta in python_metas(): - for bitness in bitnesses: + for bitness in config.bitnesses: fetch_to_archives(meta['url_%d' % bitness]) -def install_pythons(): +def install_pythons(config): for meta in python_metas(): - for bitness in bitnesses: + for bitness in config.bitnesses: if not os.path.exists(meta['installed_path_%d' % bitness]): - install_python(meta, bitness) + install_python(config, meta, bitness) def fix_slashes(path): return path.replace('/', '\\') # http://eddiejackson.net/wp/?p=10276 -def install_python(meta, bitness): - archive_path = fix_slashes(os.path.join(archives_path, os.path.basename(meta['url_%d' % bitness]))) +def install_python(config, meta, bitness): + archive_path = fix_slashes(os.path.join(config.archives_path, os.path.basename(meta['url_%d' % bitness]))) if meta['ext'] == 'exe': cmd = [archive_path] else: @@ -863,37 +1099,66 @@ sys.stdout.write('Installing python %s (%d bit)\n' % (meta['version'], bitness)) print(' '.join(cmd)) sys.stdout.flush() - subprocess.check_call(cmd) + check_call(cmd) -def download_bootstrap_python(): - version = python_versions[-2] +def download_bootstrap_python(config): + version = config.python_versions[-2] url = 'https://www.python.org/ftp/python/%s/python-%s.msi' % (version, version) fetch(url) -def install_virtualenv(): - with in_dir(archives_path): +def install_virtualenv(config): + with in_dir(config.archives_path): #fetch('https://pypi.python.org/packages/source/v/virtualenv/virtualenv-%s.tar.gz' % virtualenv_version) fetch('https://pypi.python.org/packages/d4/0c/9840c08189e030873387a73b90ada981885010dd9aea134d6de30cd24cb8/virtualenv-15.1.0.tar.gz') - for bitness in bitnesses: - for python_release in python_releases(): - print('Installing virtualenv %s for Python %s (%s bit)' % (virtualenv_version, python_release, bitness)) + for bitness in config.bitnesses: + for python_release in config.python_releases: + print('Installing virtualenv %s for Python %s (%s bit)' % (config.virtualenv_version, python_release, bitness)) sys.stdout.flush() - untar('virtualenv-%s' % virtualenv_version) - with in_dir('virtualenv-%s' % virtualenv_version): + untar('virtualenv-%s' % config.virtualenv_version) + with in_dir('virtualenv-%s' % config.virtualenv_version): python_binary = PythonBinary(python_release, bitness) cmd = [python_binary.executable_path, 'setup.py', 'install'] - subprocess.check_call(cmd) + check_call(cmd) -def create_virtualenvs(): - for bitness in bitnesses: - for python_release in python_releases(): +def create_virtualenvs(config): + for bitness in config.bitnesses: + for python_release in config.python_releases: print('Creating a virtualenv for Python %s (%s bit)' % (python_release, bitness)) sys.stdout.flush() - with in_dir(archives_path): + with in_dir(config.archives_path): python_binary = PythonBinary(python_release, bitness) venv_basename = 'venv-%s-%s' % (python_release, bitness) cmd = [python_binary.executable_path, '-m', 'virtualenv', venv_basename] - subprocess.check_call(cmd) + check_call(cmd) + +def assemble_deps(config): + rm_rf('deps') + os.mkdir('deps') + for bconf in config.buildconfigs(): + print(bconf.vc_tag) + sys.stdout.flush() + dest = os.path.join('deps', bconf.vc_tag) + os.mkdir(dest) + for builder in dep_builders(bconf): + cp_r(builder.include_path, dest) + cp_r(builder.lib_path, dest) + with zipfile.ZipFile(os.path.join('deps', bconf.vc_tag + '.zip'), 'w', zipfile.ZIP_DEFLATED) as zip: + for root, dirs, files in os.walk(dest): + for file in files: + path = os.path.join(root, file) + zip_name = path[len(dest)+1:] + zip.write(path, zip_name) + +def get_deps(): + import struct + + python_release = sys.version_info[:2] + vc_version = PYTHON_VC_VERSIONS['.'.join(map(str, python_release))] + bitness = struct.calcsize('P') * 8 + vc_tag = '%s-%d' % (vc_version, bitness) + fetch('https://dl.bintray.com/pycurl/deps/%s.zip' % vc_tag) + check_call(['unzip', '-d', 'deps', vc_tag + '.zip']) + import optparse @@ -906,10 +1171,11 @@ if opts.bitness: chosen_bitnesses = [int(bitness) for bitness in opts.bitness.split(',')] for bitness in chosen_bitnesses: - if bitness not in bitnesses: + if bitness not in BITNESSES: print('Invalid bitness %d' % bitness) exit(2) - bitnesses = chosen_bitnesses +else: + chosen_bitnesses = BITNESSES if opts.python: chosen_pythons = opts.python.split(',') @@ -918,37 +1184,40 @@ python = python.replace('.', '') python = python[0] + '.' + python[1] + '.' ok = False - for python_version in python_versions: + for python_version in Config.python_versions: if python_version.startswith(python): chosen_python_versions.append(python_version) ok = True if not ok: print('Invalid python %s' % python) exit(2) - python_versions = chosen_python_versions +else: + chosen_python_versions = Config.python_versions -# https://stackoverflow.com/questions/35569042/python-3-ssl-certificate-verify-failed -import ssl -try: - ssl._create_default_https_context = ssl._create_unverified_context -except AttributeError: - pass +config = ExtendedConfig( + bitnesses=chosen_bitnesses, + python_versions=chosen_python_versions, +) if len(args) > 0: if args[0] == 'download': - download_pythons() + download_pythons(config) elif args[0] == 'bootstrap': - download_bootstrap_python() + download_bootstrap_python(config) elif args[0] == 'installpy': - install_pythons() + install_pythons(config) elif args[0] == 'builddeps': - build_dependencies(bitnesses) + build_dependencies(config) elif args[0] == 'installvirtualenv': - install_virtualenv() + install_virtualenv(config) elif args[0] == 'createvirtualenvs': - create_virtualenvs() + create_virtualenvs(config) + elif args[0] == 'assembledeps': + assemble_deps(config) + elif args[0] == 'getdeps': + get_deps() else: print('Unknown command: %s' % args[0]) exit(2) else: - build() + build(config)