[Python-modules-commits] [pycurl] 118/140: Import pycurl_7.19.3.orig.tar.gz
Barry Warsaw
barry at moszumanska.debian.org
Wed Oct 1 21:45:15 UTC 2014
This is an automated email from the git hooks/post-receive script.
barry pushed a commit to branch master
in repository pycurl.
commit f8704167610d6109e193dcaaf6da64edca74921f
Author: Barry Warsaw <barry at python.org>
Date: Wed Oct 1 16:44:05 2014 -0400
Import pycurl_7.19.3.orig.tar.gz
---
COPYING => COPYING-LGPL | 0
COPYING2 => COPYING-MIT | 1 +
ChangeLog | 106 +-
INSTALL | 47 +-
MANIFEST.in | 11 +-
Makefile | 11 +-
PKG-INFO | 5 +-
README.rst | 117 +-
RELEASE-NOTES.rst | 32 +
TODO | 18 -
doc/callbacks.html | 1 -
doc/curlmultiobject.html | 1 -
doc/curlobject.html | 5 -
doc/curlshareobject.html | 1 -
doc/files.rst | 29 +
doc/internals.rst | 15 +
doc/pycurl.html | 12 +-
doc/release-process.rst | 42 +-
doc/unicode.rst | 100 ++
examples/basicfirst.py | 3 +-
examples/file_upload.py | 3 +-
examples/linksys.py | 2 +-
examples/retriever-multi.py | 3 +-
examples/retriever.py | 3 +-
examples/sfquery.py | 2 +-
examples/tests/test_gtk.py | 3 +-
examples/tests/test_xmlrpc.py | 3 +-
examples/xmlrpc_curl.py | 3 +-
python/curl/__init__.py | 31 +-
requirements-dev-2.4.txt | 2 +
requirements-dev-2.5.txt | 2 +
requirements-dev.txt | 3 +
setup.py | 421 +++++--
setup_win32_ssl.py | 36 -
src/pycurl.c | 1212 +++++++++++++++++---
tests/__init__.py | 8 +-
tests/app.py | 46 +-
tests/certinfo_test.py | 31 +-
tests/certs/server.crt | 24 +-
tests/curlopt_test.py | 20 -
tests/debug_test.py | 6 +-
tests/default_write_function_test.py | 2 +-
tests/easy_test.py | 16 +
tests/error_test.py | 82 ++
tests/ext/test-lib.sh | 69 ++
tests/ext/test-suite.sh | 25 +
tests/ftp_test.py | 14 +-
tests/functools_backport.py | 58 +
tests/getinfo_test.py | 22 +-
tests/global_init_test.py | 2 +-
tests/header_function_test.py | 8 +-
tests/header_test.py | 49 +
tests/internals_test.py | 77 +-
tests/matrix.py | 71 +-
.../curl-7.19.0-sslv2-c66b0b32fba-modified.patch | 18 +
tests/matrix/python30.patch | 19 +
tests/memleak_test.py | 55 -
tests/memory_mgmt_test.py | 221 ++++
tests/multi_socket_select_test.py | 16 +-
tests/multi_socket_test.py | 16 +-
tests/multi_test.py | 86 +-
tests/multi_timer_test.py | 7 +-
tests/option_constants_test.py | 76 ++
tests/pause_test.py | 6 +-
tests/post_test.py | 23 +-
tests/post_with_read_callback_test.py | 60 -
tests/pycurl_object_test.py | 125 ++
tests/read_callback_test.py | 126 ++
tests/relative_url_test.py | 4 +-
tests/reset_test.py | 7 +-
tests/resolve_test.py | 2 +-
tests/seek_function_test.py | 2 +-
tests/setopt_lifecycle_test.py | 6 +-
tests/setopt_unicode_test.py | 41 +
tests/share_test.py | 18 +-
tests/socket_open_test.py | 14 +-
tests/unset_range_test.py | 2 +-
tests/user_agent_string_test.py | 27 +
tests/util.py | 88 +-
tests/version_comparison_test.py | 2 +-
tests/version_test.py | 13 +
tests/vsftpd.conf | 13 +
tests/write_abort_test.py | 11 +-
tests/write_cb_bogus_test.py | 6 +-
tests/write_to_file_test.py | 70 +-
tests/write_to_stringio_test.py | 24 +-
winbuild.py | 167 +++
87 files changed, 3475 insertions(+), 812 deletions(-)
diff --git a/COPYING b/COPYING-LGPL
similarity index 100%
rename from COPYING
rename to COPYING-LGPL
diff --git a/COPYING2 b/COPYING-MIT
similarity index 94%
rename from COPYING2
rename to COPYING-MIT
index 3a19f22..bb06eea 100644
--- a/COPYING2
+++ b/COPYING-MIT
@@ -2,6 +2,7 @@ COPYRIGHT AND PERMISSION NOTICE
Copyright (C) 2001-2008 by Kjetil Jacobsen <kjetilja at gmail.com>
Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer <markus at oberhumer.com>
+Copyright (C) 2013-2014 by Oleg Pudeyev <oleg at bsdpower.com>
All rights reserved.
diff --git a/ChangeLog b/ChangeLog
index 89b97e1..3c90a29 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,90 @@
-Version 7.19.0.3 [requires libcurl-7.19.0 or better] - 2013-12-14
+Version 7.19.3 [requires libcurl-7.19.0 or better] - 2014-01-09
+---------------------------------------------------------------
+
+ * Added CURLOPT_NOPROXY.
+
+ * Added CURLINFO_LOCAL_PORT, CURLINFO_PRIMARY_PORT and
+ CURLINFO_LOCAL_IP (patch by Adam Jacob Muller).
+
+ * When running on Python 2.x, for compatibility with Python 3.x,
+ Unicode strings containing ASCII code points only are now accepted
+ in setopt() calls.
+
+ * PycURL now requires that compile time SSL backend used by libcurl
+ is the same as the one used at runtime. setup.py supports
+ --with-ssl, --with-gnutls and --with-nss options like libcurl does,
+ to specify which backend libcurl uses. On some systems PycURL can
+ automatically figure out libcurl's backend.
+ If the backend is not one for which PycURL provides crypto locks
+ (i.e., any of the other backends supported by libcurl),
+ no runtime SSL backend check is performed.
+
+ * Default PycURL user agent string is now built at runtime, and will
+ include the user agent string of libcurl loaded at runtime rather
+ than the one present at compile time.
+
+ * PycURL will now use WSAduplicateSocket rather than dup on Windows
+ to duplicate sockets obtained from OPENSOCKETFUNCTION.
+ Using dup may have caused crashes, OPENSOCKETFUNCTION should
+ now be usable on Windows.
+
+ * A new script, winbuild.py, was added to build PycURL on Windows
+ against Python 2.6, 2.7, 3.2 and 3.3.
+
+ * Added CURL_LOCK_DATA_SSL_SESSION (patch by Tom Pierce).
+
+ * Added E_OPERATION_TIMEDOUT (patch by Romuald Brunet).
+
+ * setup.py now handles --help argument and will print PycURL-specific
+ configuration options in addition to distutils help.
+
+ * Windows build configuration has been redone:
+ PYCURL_USE_LIBCURL_DLL #define is gone, use --use-libcurl-dll
+ argument to setup.py to build against a libcurl DLL.
+ CURL_STATICLIB is now #defined only when --use-libcurl-dll is not
+ given to setup.py, and PycURL is built against libcurl statically.
+ --libcurl-lib-name option can be used to override libcurl import
+ library name.
+
+ * Added CURLAUTH_DIGEST_IE as pycurl.HTTPAUTH_DIGEST_IE.
+
+ * Added CURLOPT_POSTREDIR option and CURL_REDIR_POST_301,
+ CURL_REDIR_POST_302, CURL_REDIR_POST_303 and CURL_REDIR_POST_ALL
+ constants. CURL_REDIR_POST_303 requires libcurl 7.26.0 or higher,
+ all others require libcurl 7.19.1 or higher.
+
+ * PycURL now supports Python 3.1 through 3.3. Python 3.0 might
+ work but it appears to ship with broken distutils, making virtualenv
+ not function on it.
+
+ * PycURL multi objects now have the multi constants defined on them.
+ Previously the constants were only available on pycurl module.
+ The new behavior matches that of curl and share objects.
+
+ * PycURL share objects can now be closed via the close() method.
+
+ * PycURL will no longer call `curl-config --static-libs` if
+ `curl-config --libs` succeeds and returns output.
+ Systems on which neither `curl-config --libs` nor
+ `curl-config --static-libs` do the right thing should provide
+ a `curl-config` wrapper that is sane.
+
+ * Added CURLFORM_BUFFER and CURLFORM_BUFFERPTR.
+
+ * pycurl.version and user agent string now include both
+ PycURL version and libcurl version as separate items.
+
+ * Added CURLOPT_DNS_SERVERS.
+
+ * PycURL can now be dynamically linked against libcurl on Windows
+ if PYCURL_USE_LIBCURL_DLL is #defined during compilation.
+
+ * Breaking change: opensocket callback now takes an additional
+ (address, port) tuple argument. Existing callbacks will need to
+ be modified to accept this new argument.
+ https://github.com/pycurl/pycurl/pull/18
+
+Version 7.19.0.3 [requires libcurl-7.19.0 or better] - 2013-12-24
-----------------------------------------------------------------
* Re-release of 7.19.0.2 with minor changes to build Windows packages
@@ -8,20 +94,20 @@ Version 7.19.0.3 [requires libcurl-7.19.0 or better] - 2013-12-14
Version 7.19.0.2 [requires libcurl-7.19.0 or better] - 2013-10-08
-----------------------------------------------------------------
- * Fixed a bug in a commit made in 2008 but not released until 7.19.0.1
- which caused CURLOPT_POSTFIELDS to not correctly increment reference
- count of the object being given as its argument, despite libcurl not
- copying the data provided by said object.
+ * Fixed a bug in a commit made in 2008 but not released until 7.19.0.1
+ which caused CURLOPT_POSTFIELDS to not correctly increment reference
+ count of the object being given as its argument, despite libcurl not
+ copying the data provided by said object.
- * Added support for libcurl pause/unpause functionality,
- via curl_easy_pause call and returning READFUNC_PAUSE from
- read callback function.
+ * Added support for libcurl pause/unpause functionality,
+ via curl_easy_pause call and returning READFUNC_PAUSE from
+ read callback function.
Version 7.19.0.1 [requires libcurl-7.19.0 or better] - 2013-09-23
-----------------------------------------------------------------
- * Test matrix tool added to test against all supported Python and
- libcurl versions.
+ * Test matrix tool added to test against all supported Python and
+ libcurl versions.
* Python 2.4 is now the minimum required version.
diff --git a/INSTALL b/INSTALL
index 17c7d06..4440e7a 100644
--- a/INSTALL
+++ b/INSTALL
@@ -30,6 +30,41 @@ applies only if there is more than one version of libcurl installed,
e.g. one in /usr/lib and one in /usr/local/lib.
+easy_install / pip
+----------------
+
+ easy_install pycurl
+ pip install pycurl
+
+If you need to specify an alternate curl-config, it can be done via an
+environment variable:
+
+ export PYCURL_CURL_CONFIG=/usr/local/bin/curl-config
+ easy_install pycurl
+
+The same applies to the SSL backend, if you need to specify it (see the SSL
+section below):
+
+ export PYCURL_SSL_LIBRARY=openssl
+ easy_install pycurl
+
+
+SSL
+---
+
+PycURL has locks around crypto functions. In order to compile correct locking
+code, it has to know which SSL library is going to be used by libcurl at
+runtime. setup.py will attempt to automatically detect the SSL library that
+libcurl uses, but this does not always work. In the cases when setup.py cannot
+figure out the SSL library, it must be provided via --with-ssl/--with-gnutls/
+--with-nss arguments, just like libcurl's configure script uses, or via
+PYCURL_SSL_LIBRARY=openssl|gnutls|nss environment variable.
+
+Please note the difference in spelling that concerns OpenSSL: the command-line
+argument is --with-ssl, to match libcurl, but the environment variable value is
+"openssl".
+
+
Windows
-------
@@ -40,10 +75,20 @@ For a minimum build you will just need libcurl source. Follow its Windows
build instructions to build either a static or a DLL version of the library,
then configure PycURL as follows to use it:
- python setup.py --curl-dir=c:\dev\curl-7.33.0\builds\libcurl-vc-x86-release-dll-ipv6-sspi-spnego-winssl
+ python setup.py --curl-dir=c:\dev\curl-7.33.0\builds\libcurl-vc-x86-release-dll-ipv6-sspi-spnego-winssl --use-libcurl-dll
Note that --curl-dir does not point to libcurl source but rather to headers
and compiled libraries.
+Additional Windows setup.py options:
+
+--use-libcurl-dll - build against libcurl DLL, if not given PycURL will be built
+against libcurl statically.
+
+--libcurl-lib-name=libcurl_imp.lib - specify a different name for libcurl
+import library. The default is libcurl.lib which is appropriate for static
+linking and is sometimes the correct choice for dynamic linking as well. The
+other possibility for dynamic linking is libcurl_imp.lib.
+
A good setup.py target to use is bdist_wininst which produces an executable
installer that you can run to install PycURL.
diff --git a/MANIFEST.in b/MANIFEST.in
index 2301703..25f8ffa 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,14 +3,14 @@
# Manifest template for creating the source distribution.
#
-include COPYING
-include COPYING2
+include COPYING-LGPL
+include COPYING-MIT
include ChangeLog
include INSTALL
include MANIFEST.in
include Makefile
include README.rst
-include TODO
+include RELEASE-NOTES.rst
include doc/*.html
include doc/*.rst
include examples/*.py
@@ -18,8 +18,11 @@ include examples/tests/*.py
include src/Makefile
include src/pycurl.c
include python/curl/*.py
+include requirements*.txt
include tests/*.py
include tests/certs/*.crt
include tests/certs/*.key
+include tests/ext/*.sh
include tests/matrix/*.patch
-include setup_win32_ssl.py
+include tests/vsftpd.conf
+include winbuild.py
diff --git a/Makefile b/Makefile
index cb12aaf..082af4f 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,6 @@
SHELL = /bin/sh
-PYTHON = python2.3
PYTHON = python
NOSETESTS = nosetests
@@ -17,8 +16,12 @@ build-7.10.8:
test: build
mkdir -p tests/tmp
- PYTHONPATH=$$(ls -d build/lib.*):$$PYTHONPATH \
+ PYTHONSUFFIX=$$(python -V 2>&1 |awk '{print $$2}' |awk -F. '{print $$1 "." $$2}') && \
+ PYTHONPATH=$$(ls -d build/lib.*$$PYTHONSUFFIX):$$PYTHONPATH \
+ $(PYTHON) -c 'import pycurl; print(pycurl.version)'
+ PYTHONPATH=$$(ls -d build/lib.*$$PYTHONSUFFIX):$$PYTHONPATH \
$(NOSETESTS)
+ ./tests/ext/test-suite.sh
# (needs GNU binutils)
strip: build
@@ -57,6 +60,10 @@ windist: distclean
python2.4 setup_win32_ssl.py bdist_wininst
rm -rf build
+docs:
+ cd doc && for file in *.rst; do rst2html "$$file" ../www/htdocs/doc/`echo "$$file" |sed -e 's/.rst$$/.html/'`; done
+ rst2html RELEASE-NOTES.rst www/htdocs/release-notes.html
+
.PHONY: all build test strip install install_lib clean distclean maintainer-clean dist sdist windist
diff --git a/PKG-INFO b/PKG-INFO
index 85082dc..86f09b9 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: pycurl
-Version: 7.19.0.3
+Version: 7.19.3
Summary: PycURL -- cURL library module for Python
Home-page: http://pycurl.sourceforge.net/
Author: Oleg Pudeyev
@@ -18,6 +18,7 @@ Classifier: License :: OSI Approved :: GNU Library or Lesser General Public Lice
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
-Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: File Transfer Protocol (FTP)
Classifier: Topic :: Internet :: WWW/HTTP
diff --git a/README.rst b/README.rst
index 5889043..2d8277d 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,9 @@
PycURL: Python interface to libcurl
====================================
+.. image:: https://api.travis-ci.org/pycurl/pycurl.png
+ :target: https://travis-ci.org/pycurl/pycurl
+
PycURL is a Python interface to `libcurl`_. PycURL can be used to fetch objects
identified by a URL from a Python program, similar to the `urllib`_ Python module.
PycURL is mature, very fast, and supports a lot of features.
@@ -34,6 +37,12 @@ Overview
.. _companies: http://curl.haxx.se/docs/companies.html
.. _applications: http://curl.haxx.se/libcurl/using/apps.html
+Requirements
+------------
+
+- Python 2.4 through 2.7 or 3.1 through 3.3.
+- libcurl 7.19.0 or better.
+
Installation
------------
@@ -45,10 +54,71 @@ or `pip`_::
pip install pycurl
+Installing from source is performed via ``setup.py``::
+
+ python setup.py install
+
+You will need libcurl headers and libraries installed to install PycURL
+from source. PycURL uses ``curl-config`` to determine correct flags/libraries
+to use during compilation; you can override the location of ``curl-config``
+if it is not in PATH or you want to use a custom libcurl installation::
+
+ python setup.py --curl-config=/path/to/curl-config install
+
+Sometimes it is more convenient to use an environment variable, if
+you are not directly invoking ``setup.py``::
+
+ PYCURL_CURL_CONFIG=/path/to/curl-config python setup.py install
+
+``curl-config`` is expected to support the following options:
+
+- ``--version``
+- ``--cflags``
+- ``--libs``
+- ``--static-libs`` (if ``--libs`` does not work)
+
+PycURL requires that the SSL library that it is built against is the same
+one libcurl, and therefore PycURL, uses at runtime. PycURL's ``setup.py``
+uses ``curl-config`` to attempt to figure out which SSL library libcurl
+was compiled against, however this does not always work. If PycURL is unable
+to determine the SSL library in use it will print a warning similar to
+the following::
+
+ src/pycurl.c:137:4: warning: #warning "libcurl was compiled with SSL support, but configure could not determine which " "library was used; thus no SSL crypto locking callbacks will be set, which may " "cause random crashes on SSL requests" [-Wcpp]
+
+It will then fail at runtime as follows::
+
+ ImportError: pycurl: libcurl link-time ssl backend (openssl) is different from compile-time ssl backend (none/other)
+
+To fix this, you need to tell ``setup.py`` what SSL backend is used::
+
+ python setup.py --with-[ssl|gnutls|nss] install
+
+Or use an environment variable::
+
+ PYCURL_SSL_LIBRARY=openssl|gnutls|nss python setup.py installl
+
+Note the difference between ``--with-ssl`` (for compatibility with libcurl) and
+``PYCURL_SSL_LIBRARY=openssl``.
.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
.. _pip: http://pypi.python.org/pypi/pip
+Support
+-------
+
+For support questions, please use `curl-and-python mailing list`_.
+`Mailing list archives`_ are available for your perusal as well.
+
+Bugs can be reported `via GitHub`_. Please only use GitHub issues when you are
+certain you have found a bug in PycURL. If you do not have a patch to fix
+the bug, or at least a specific code fragment in PycURL that you believe is
+the cause, you should instead post you inquiry to the mailing list.
+
+.. _curl-and-python mailing list: http://cool.haxx.se/mailman/listinfo/curl-and-python
+.. _Mailing list archives: http://curl.haxx.se/mail/list.cgi?list=curl-and-python
+.. _via GitHub: https://github.com/pycurl/pycurl/issues
+
Automated Tests
---------------
@@ -69,10 +139,44 @@ vsftpd tests you must explicitly set PYCURL_VSFTPD_PATH variable like so::
# specify full path to vsftpd
export PYCURL_VSFTPD_PATH=/usr/local/libexec/vsftpd
+These instructions work for Python 2.5 through 2.7 and 3.1 through 3.3.
+
.. _nose: https://nose.readthedocs.org/
.. _bottle: http://bottlepy.org/
.. _cherrypy: http://www.cherrypy.org/
+Test Matrix
+-----------
+
+The test matrix is a separate framework that runs tests on more esoteric
+configurations. It supports:
+
+- Testing against Python 2.4, which bottle does not support.
+- Testing against Python compiled without threads, which requires an out of
+ process test server.
+- Testing against locally compiled libcurl with arbitrary options.
+
+To use the test matrix, first you need to start the test server from
+Python 2.5+ by running:::
+
+ python -m tests.appmanager
+
+Then in a different shell, and preferably in a separate user account,
+run the test matrix:::
+
+ # run ftp tests, etc.
+ export PYCURL_VSFTPD_PATH=vsftpd
+ # create a new work directory, preferably not under pycurl tree
+ mkdir testmatrix
+ cd testmatrix
+ # run the matrix specifying absolute path
+ python /path/to/pycurl/tests/matrix.py
+
+The test matrix will download, build and install supported Python versions
+and supported libcurl versions, then run pycurl tests against each combination.
+To see what the combinations are, look in
+`tests/matrix.py <tests/matrix.py>`_.
+
Contribute
----------
@@ -84,6 +188,9 @@ For smaller changes:
#. Write a test which shows that the bug was fixed or that the feature
works as expected.
#. Send a pull request.
+#. Check back after 10-15 minutes to see if tests passed on Travis CI.
+ PycURL supports old Python and libcurl releases and their support is tested
+ on Travis.
For larger changes:
@@ -91,8 +198,8 @@ For larger changes:
#. Discuss your proposal on the mailing list.
#. When consensus is reached, implement it as described above.
-.. image:: https://api.travis-ci.org/pycurl/pycurl.png
- :target: https://travis-ci.org/pycurl/pycurl
+Please contribute binary distributions for your system to the
+`downloads repository`_.
License
-------
@@ -101,13 +208,14 @@ License
Copyright (C) 2001-2008 by Kjetil Jacobsen <kjetilja at gmail.com>
Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer <markus at oberhumer.com>
+ Copyright (C) 2013-2014 by Oleg Pudeyev <oleg at bsdpower.com>
All rights reserved.
PycURL is dual licensed under the LGPL and an MIT/X derivative license
based on the cURL license. A full copy of the LGPL license is included
- in the file COPYING. A full copy of the MIT/X derivative license is
- included in the file COPYING2. You can redistribute and/or modify PycURL
+ in the file COPYING-LGPL. A full copy of the MIT/X derivative license is
+ included in the file COPYING-MIT. You can redistribute and/or modify PycURL
according to the terms of either license.
.. _PycURL: http://pycurl.sourceforge.net/
@@ -115,3 +223,4 @@ License
.. _urllib: http://docs.python.org/library/urllib.html
.. _`the repository`: https://github.com/pycurl/pycurl
.. _`mailing list`: http://cool.haxx.se/mailman/listinfo/curl-and-python
+.. _`downloads repository`: https://github.com/pycurl/downloads
diff --git a/RELEASE-NOTES.rst b/RELEASE-NOTES.rst
new file mode 100644
index 0000000..c89466c
--- /dev/null
+++ b/RELEASE-NOTES.rst
@@ -0,0 +1,32 @@
+Release Notes
+=============
+
+PycURL 7.19.3 - 2014-01-09
+--------------------------
+
+This release brings official Python 3 support to PycURL.
+Several GNU/Linux distributions provided Python 3 packages of PycURL
+previously; these packages were based on patches that were incomplete and
+in some places incorrect. Behavior of PycURL 7.19.3 and later may therefore
+differ from behavior of unofficial Python 3 packages of previous PycURL
+versions.
+
+To summarize the behavior under Python 3, PycURL will accept ``bytes`` where
+it accepted strings under Python 2, and will also accept Unicode strings
+containing ASCII codepoints only for convenience. Please refer to
+`Unicode`_ and `file`_ documentation for further details.
+
+In the interests of compatibility, PycURL will also accept Unicode data on
+Python 2 given the same constraints as under Python 3.
+
+While Unicode and file handling rules are expected to be sensible for
+all use cases, and retain backwards compatibility with previous PycURL
+versions, please treat behavior of this versions under Python 3 as experimental
+and subject to change.
+
+Another potentially disruptive change in PycURL is the requirement for
+compile time and runtime SSL backends to match. Please see the readme for
+how to indicate the SSL backend to setup.py.
+
+.. _Unicode: doc/unicode.html
+.. _file: doc/files.html
diff --git a/TODO b/TODO
deleted file mode 100644
index 54d1da7..0000000
--- a/TODO
+++ /dev/null
@@ -1,18 +0,0 @@
-If you want to hack on pycurl, here's our list of unresolved issues:
-
-
-NEW FEATURES/IMPROVEMENTS:
-
- * Callback handling for stream seek.
-
- * Add docs to the high-level interface.
-
-
-DEFICIENICES:
-
- * Using certain invalid options, it may be possible to cause a crash.
- This is un-Pythonic behaviour, but you somewhere have to draw a
- line between efficiency (and feature completeness) and safety.
- There _are_ quite a number of internal error checks, but tracking
- and catching all possible (deliberate) misuses is not a goal
- (and probably impossible anyway, due to the complexity of libcurl).
diff --git a/doc/callbacks.html b/doc/callbacks.html
index 19d287b..83b1445 100644
--- a/doc/callbacks.html
+++ b/doc/callbacks.html
@@ -141,7 +141,6 @@ and 'tests/test_getinfo.py' shows PROGRESSFUNCTION.</p>
<a href="http://validator.w3.org/check/referer"><img align="right"
src="http://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
- $Id$
</p>
</body>
diff --git a/doc/curlmultiobject.html b/doc/curlmultiobject.html
index 66191d3..f14827c 100644
--- a/doc/curlmultiobject.html
+++ b/doc/curlmultiobject.html
@@ -129,7 +129,6 @@ returned.</p>
<a href="http://validator.w3.org/check/referer"><img align="right"
src="http://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
- $Id$
</p>
</body>
diff --git a/doc/curlobject.html b/doc/curlobject.html
index f4bd5bc..0de0730 100644
--- a/doc/curlobject.html
+++ b/doc/curlobject.html
@@ -102,10 +102,6 @@ in libcurl. The argument should be derived from
the <code>PAUSE_RECV</code>, <code>PAUSE_SEND</code>, <code>PAUSE_ALL</code>
and <code>PAUSE_CONT</code> constants.</p>
-<p>The <code>pause()</code> method (and its associated constants) is only
-available if the version of libcurl with which pycurl was built is at
-least version 7.18.0.</p>
-
<dt><code>errstr()</code> -> <em>String</em></dt>
<dd>
<p>Returns the internal libcurl error buffer of this handle as a string.</p>
@@ -130,7 +126,6 @@ tuples.</p>
<a href="http://validator.w3.org/check/referer"><img align="right"
src="http://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
- $Id$
</p>
</body>
diff --git a/doc/curlshareobject.html b/doc/curlshareobject.html
index 2043e48..a624f48 100644
--- a/doc/curlshareobject.html
+++ b/doc/curlshareobject.html
@@ -47,7 +47,6 @@ curl.close()
<a href="http://validator.w3.org/check/referer"><img align="right"
src="http://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
- $Id$
</p>
</body>
diff --git a/doc/files.rst b/doc/files.rst
new file mode 100644
index 0000000..614a466
--- /dev/null
+++ b/doc/files.rst
@@ -0,0 +1,29 @@
+Files
+=====
+
+In PycURL 7.19.0.3 and below, CURLOPT_READDATA, CURLOPT_WRITEDATA and
+CURLOPT_WRITEHEADER options accepted file objects and directly passed
+the underlying C library FILE pointers to libcurl.
+
+Python 3 no longer implements files as C library FILE objects.
+In PycURL 7.19.3 and above, when running on Python 3, these options
+are implemented as calls to CURLOPT_READFUNCTION, CURLOPT_WRITEFUNCTION
+and CURLOPT_HEADERFUNCTION, respectively, with the write method of the
+Python file object as the parameter. As a result, any Python file-like
+object implementing a write method can be passed to CURLOPT_READDATA,
+CURLOPT_WRITEDATA or CURLOPT_WRITEHEADER options.
+
+When running PycURL 7.19.3 and above on Python 2, the old behavior of
+passing FILE pointers to libcurl remains when a true file object is given
+to CURLOPT_READDATA, CURLOPT_WRITEDATA and CURLOPT_WRITEHEADER options.
+For consistency with Python 3 behavior these options also accept file-like
+objects implementing a write method as arguments, in which case the
+Python 3 code path is used converting these options to CURLOPT_*FUNCTION
+option calls.
+
+Files given to PycURL as arguments to CURLOPT_READDATA, CURLOPT_WRITEDATA or
+CURLOPT_WRITEHEADER must be opened for writing in binary mode. Files opened
+in text mode (without "b" flag to open()) expect string objects and writing
+to them from PycURL will fail. Similarly when passing f.write method of
+an open file to CURLOPT_WRITEFUNCTION or CURLOPT_HEADERFUNCTION, or f.read
+to CURLOPT_READFUNCTION, the file must have been be opened in binary mode.
diff --git a/doc/internals.rst b/doc/internals.rst
new file mode 100644
index 0000000..9729a2b
--- /dev/null
+++ b/doc/internals.rst
@@ -0,0 +1,15 @@
+Internals
+=========
+
+Cleanup sequence:
+
+x=curl/multi/share
+
+x.close() -> do_x_close -> util_x_close
+del x -> do_x_dealloc -> util_x_close
+
+do_* functions are directly invoked by user code.
+They check pycurl object state.
+
+util_* functions are only invoked by other pycurl C functions.
+They do not check pycurl object state.
diff --git a/doc/pycurl.html b/doc/pycurl.html
index 567231f..bde7753 100644
--- a/doc/pycurl.html
+++ b/doc/pycurl.html
@@ -10,7 +10,7 @@
</head>
<body>
-<h1><tt>pycurl</tt> — A Python interface to the cURL library</h1>
+<h1><tt>PycURL</tt> — A Python Interface To The cURL library</h1>
<p>The pycurl package is a Python interface to libcurl (<a
href="http://curl.haxx.se/libcurl/">http://curl.haxx.se/libcurl/</a>). pycurl
@@ -116,6 +116,15 @@ pass as an argument to the SHARE option on Curl objects.</p>
<li><a href="curlmultiobject.html">CurlMulti objects</a></li>
<li><a href="curlshareobject.html">CurlShare objects</a></li>
<li><a href="callbacks.html">Callbacks</a></li>
+ <li><a href="unicode.html">Unicode handling</a></li>
+ <li><a href="files.html">File handling</a></li>
+</ul>
+
+<h1>Documentation For Developers</h1>
+
+<ul>
+ <li><a href="internals.html">Notes on PycURL internals</a></li>
+ <li><a href="release-process.html">Release process</a></li>
</ul>
<hr />
@@ -123,7 +132,6 @@ pass as an argument to the SHARE option on Curl objects.</p>
<a href="http://validator.w3.org/check/referer"><img align="right"
src="http://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
- $Id$
</p>
</body>
diff --git a/doc/release-process.rst b/doc/release-process.rst
index 71acb4b..813cd51 100644
--- a/doc/release-process.rst
+++ b/doc/release-process.rst
@@ -2,24 +2,28 @@ Release Process
===============
1. Ensure changelog is up to date with commits in master.
-2. Check via tests/matrix.py that test suite passes on the following
- configurations:
- - Python 2.4, 2.5, 2.6, 2.7.
- - Minimum supported libcurl (currently 7.19.0).
- - Most recent available libcurl (currently 7.32.0).
+2. Run `python setup.py manifest`, check that none of the listed files
+ should be in MANIFEST.in.
+3. Make sure travis is green for master.
4. Update version numbers in:
- - Changelog
- - setup.py
- - www/htdocs/index.php
-5. TODO: update setup_win32_ssl.py.
-6. Copy Changelog to www/htdocs.
-7. Rsync doc directory to www/htdocs.
-8. python setup.py sdist
+ - Changelog
+ - setup.py
+ - winbuild.py
+ - www/htdocs/index.php
+5. Copy Changelog to www/htdocs.
+6. Draft release notes.
+7. `make docs`.
+8. `python setup.py sdist`.
9. Manually test install the built package.
-10. TODO: build windows packages.
-11. Tag the new version.
-12. Copy built source distribution to downloads repo on github.
-13. Rsync downloads repo to sourceforge.
-14. Rsync www/htdocs to sourceforge.
-15. Upload source distribution to pypi.
-16. Announce release on mailing list.
+10. Build windows packages using winbuild.py.
+11. Add windows packages to downloads repo on github.
+12. Tag the new version.
+13. Register new version with pypi - `python setup.py register`.
+14. Upload source distribution to pypi - `python setup.py sdist upload`.
+ This recreates the source distribution.
+15. Add the source distribution to downloads repo on github.
+16. Rsync downloads repo to sourceforge.
+17. Rsync www/htdocs to sourceforge.
+18. Push tag to github pycurl repo.
+19. Announce release on mailing list.
+20. Link to announcement from website.
diff --git a/doc/unicode.rst b/doc/unicode.rst
new file mode 100644
index 0000000..5ff56c3
--- /dev/null
+++ b/doc/unicode.rst
@@ -0,0 +1,100 @@
+Unicode
+=======
+
+Python 2.x
+----------
+
+Under Python 2, the string type can hold arbitrary encoded byte strings.
+PycURL will pass whatever byte strings it is given verbatim to libcurl.
+
+If your application works with encoded byte strings, you should be able to
+pass them to PycURL. If your application works with Unicode data, you need to
+encode the data to byte strings yourself. Which encoding to use depends on
+the protocol you are working with - HTTP headers should be encoded in latin1,
+HTTP request bodies are commonly encoded in utf-8 and their encoding is
+specified in the Content-Type header value.
+
+Prior to PycURL 7.19.3, PycURL did not accept Unicode data under Python 2.
+Even Unicode strings containing only ASCII code points had to be encoded to
+byte strings.
+
+As of PycURL 7.19.3, for compatibility with Python 3, PycURL will accept
+Unicode strings under Python 2 provided they contain ASCII code points only.
+In other words, PycURL will encode Unicode into ASCII for you. If you supply
+a Unicode string containing characters that are outside of ASCII, the call will
+fail with a UnicodeEncodeError.
+
+PycURL will return data from libcurl, like request bodies and header values,
+as byte strings. If the data is ASCII, you can treat it as string data.
+Otherwise you will need to decode the byte strings usisng the correct encoding.
+What encoding is correct depends on the protocol and potentially returned
+data itself - HTTP response headers are supposed to be latin1 encoded but
+encoding of response body is specified in the Content-Type header.
+
+Python 3.x (from PycURL 7.19.3 onward)
+--------------------------------------
+
+Under Python 3, the rules are as follows:
+
+PycURL will accept bytes for any string data passed to libcurl (e.g.
+arguments to curl_easy_setopt).
+
+PycURL will accept (Unicode) strings for string arguments to curl_easy_setopt.
+libcurl generally expects the options to be already appropriately encoded
+and escaped (e.g. for CURLOPT_URL). Also, Python 2 code dealing with
+Unicode values for string options will typically manually encode such values.
+Therefore PycURL will attempt to encode Unicode strings with the ascii codec
+only, allowing the application to pass ASCII data in a straightforward manner
+but requiring Unicode data to be appropriately encoded.
+
+It may be helpful to remember that libcurl operates on byte arrays.
+It is a C library and does not do any Unicode encoding or decoding, offloading
+that task on the application using it. PycURL, being a thin wrapper around
+libcurl, passes the Unicode encoding and decoding responsibilities to you
+except for the trivial case of encoding Unicode data containing only ASCII
+characters into ASCII.
+
+Caution: when using CURLOPT_READFUNCTION in tandem with CURLOPT_POSTFIELDSIZE,
+as would be done for HTTP for example, take care to pass the length of
+encoded data to CURLOPT_POSTFIELDSIZE if you are doing the encoding from
+Unicode strings. If you pass the number of Unicode characters rather than
+encoded bytes to libcurl, the server will receive wrong Content-Length.
+Alternatively you can return Unicode strings from a CURLOPT_READFUNCTION
+function, if you are certain they will only contain ASCII code points.
+
+If encoding to ASCII fails, PycURL will return an error to libcurl, and
+libcurl in turn will fail the request with an exception like
+"read function error/data error". You may examine sys.last_value for
+information on exception that occurred during encoding in this case.
+
+PycURL will return all data read from the network as bytes. In particular,
+this means that BytesIO should be used rather than StringIO for writing the
+response to memory. Header function will also receive bytes.
+
+Because PycURL does not perform encoding or decoding, other than to ASCII,
+any file objects that PycURL is meant to interact with via CURLOPT_READDATA,
+CURLOPT_WRITEDATA, CURLOPT_WRITEHEADER, CURLOPT_READFUNCTION,
+CURLOPT_WRITEFUNCTION or CURLOPT_HEADERFUNCTION must be opened in binary
+mode ("b" flag to open() call).
+
+Python 3.x before PycURL 7.19.3
+-------------------------------
+
+PycURL did not have official Python 3 support prior to PycURL 7.19.3.
+There were two patches on SourceForge (original_, revised_)
+adding Python 3 support, but they did not handle Unicode strings correctly.
+Instead of using Python encoding functionality, these patches used
+C standard library unicode to multibyte conversion functions, and thus
+they can have the same behavior as Python encoding code or behave
+entirely differently.
+
+Python 3 support as implemented in PycURL 7.19.3 and documented here
+does not, as mentioned, actually perform any encoding other than to convert
+from Unicode strings containing ASCII-only bytes to ASCII byte strings.
+
+Linux distributions that offered Python 3 packages of PycURL prior to 7.19.3
+used SourceForge patches and may behave in ways contradictory to what is
+described in this document.
+
+.. _original: http://sourceforge.net/p/pycurl/patches/5/
+.. _revised: http://sourceforge.net/p/pycurl/patches/12/
diff --git a/examples/basicfirst.py b/examples/basicfirst.py
index 44060af..6eacd45 100644
--- a/examples/basicfirst.py
+++ b/examples/basicfirst.py
@@ -1,7 +1,6 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
import sys
import pycurl
diff --git a/examples/file_upload.py b/examples/file_upload.py
index 7750865..a3b769a 100644
--- a/examples/file_upload.py
+++ b/examples/file_upload.py
@@ -1,7 +1,6 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
import os, sys
import pycurl
diff --git a/examples/linksys.py b/examples/linksys.py
index f66ff0d..3c6f837 100755
--- a/examples/linksys.py
+++ b/examples/linksys.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
#
# linksys.py -- program settings on a Linkys router
diff --git a/examples/retriever-multi.py b/examples/retriever-multi.py
index ad4cebd..2daff11 100644
--- a/examples/retriever-multi.py
+++ b/examples/retriever-multi.py
@@ -1,7 +1,6 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
#
# Usage: python retriever-multi.py <file with URLs to fetch> [<# of
diff --git a/examples/retriever.py b/examples/retriever.py
index be1b6ea..7cabea3 100644
--- a/examples/retriever.py
+++ b/examples/retriever.py
@@ -1,7 +1,6 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
#
# Usage: python retriever.py <file with URLs to fetch> [<# of
diff --git a/examples/sfquery.py b/examples/sfquery.py
index 16aa9d4..b0836d0 100644
--- a/examples/sfquery.py
+++ b/examples/sfquery.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
#
# sfquery -- Source Forge query script using the ClientCGI high-level interface
diff --git a/examples/tests/test_gtk.py b/examples/tests/test_gtk.py
index da8c22a..3aeebd6 100644
--- a/examples/tests/test_gtk.py
+++ b/examples/tests/test_gtk.py
@@ -1,7 +1,6 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
import sys, threading
import pycurl
diff --git a/examples/tests/test_xmlrpc.py b/examples/tests/test_xmlrpc.py
index d64794e..9fb0f94 100644
--- a/examples/tests/test_xmlrpc.py
+++ b/examples/tests/test_xmlrpc.py
@@ -1,7 +1,6 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
## XML-RPC lib included in python2.2
try:
diff --git a/examples/xmlrpc_curl.py b/examples/xmlrpc_curl.py
index 21418b5..3cdb2d3 100644
--- a/examples/xmlrpc_curl.py
+++ b/examples/xmlrpc_curl.py
@@ -1,7 +1,6 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
# the libcurl tutorial for more info.
diff --git a/python/curl/__init__.py b/python/curl/__init__.py
index 7f307b1..eb6c06a 100644
--- a/python/curl/__init__.py
+++ b/python/curl/__init__.py
@@ -6,20 +6,22 @@
#
# By Eric S. Raymond, April 2003.
-import sys, exceptions, mimetools, pycurl
-try:
+import sys, pycurl
+
+py3 = sys.version_info[0] == 3
+
+# python 2/3 compatibility
+if py3:
import urllib.parse as urllib_parse
from urllib.parse import urljoin
-except ImportError:
+ from io import BytesIO
+else:
import urllib as urllib_parse
from urlparse import urljoin
-try:
- from cStringIO import StringIO
-except ImportError:
try:
- from StringIO import StringIO
+ from cStringIO import StringIO as BytesIO
except ImportError:
- from io import StringIO
+ from StringIO import StringIO as BytesIO
try:
import signal
@@ -38,7 +40,8 @@ class Curl:
self.verbosity = 0
self.fakeheaders = fakeheaders
# Nothing past here should be modified by the caller.
- self.payload = ""
+ self.payload = None
+ self.payload_io = BytesIO()
self.hrd = ""
# Verify that we've got the right site; harmless on a non-SSL connect.
self.set_option(pycurl.SSL_VERIFYHOST, 2)
@@ -53,12 +56,9 @@ class Curl:
self.set_timeout(30)
# Use password identification from .netrc automatically
self.set_option(pycurl.NETRC, 1)
- # Set up a callback to capture the payload
- def payload_callback(x):
- self.payload += x
- self.set_option(pycurl.WRITEFUNCTION, payload_callback)
+ self.set_option(pycurl.WRITEFUNCTION, self.payload_io.write)
def header_callback(x):
- self.hdr += x
+ self.hdr += x.decode('ascii')
self.set_option(pycurl.HEADERFUNCTION, header_callback)
def set_timeout(self, timeout):
@@ -84,9 +84,10 @@ class Curl:
self.set_option(pycurl.HTTPHEADER, self.fakeheaders)
if relative_url:
self.set_option(pycurl.URL, urljoin(self.base_url, relative_url))
- self.payload = ""
+ self.payload = None
self.hdr = ""
self.handle.perform()
+ self.payload = self.payload_io.getvalue()
return self.payload
def get(self, url="", params=None):
diff --git a/requirements-dev-2.4.txt b/requirements-dev-2.4.txt
new file mode 100644
index 0000000..76411a0
--- /dev/null
+++ b/requirements-dev-2.4.txt
@@ -0,0 +1,2 @@
+nose
+simplejson==2.1.0
diff --git a/requirements-dev-2.5.txt b/requirements-dev-2.5.txt
new file mode 100644
index 0000000..52e3460
--- /dev/null
+++ b/requirements-dev-2.5.txt
@@ -0,0 +1,2 @@
+-r requirements-dev.txt
+simplejson
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..ea11ac9
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,3 @@
+bottle
+nose
+cherrypy
diff --git a/setup.py b/setup.py
index f770b30..40603ff 100644
--- a/setup.py
+++ b/setup.py
@@ -1,13 +1,12 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
"""Setup script for the PycURL module distribution."""
PACKAGE = "pycurl"
PY_PACKAGE = "curl"
-VERSION = "7.19.0.3"
+VERSION = "7.19.3"
import glob, os, re, sys, string, subprocess
import distutils
@@ -16,8 +15,17 @@ from distutils.extension import Extension
from distutils.util import split_quoted
from distutils.version import LooseVersion
+try:
+ # python 2
+ exception_base = StandardError
+except NameError:
+ # python 3
+ exception_base = Exception
+class ConfigurationError(exception_base):
+ pass
+
include_dirs = []
-define_macros = []
+define_macros = [("PYCURL_VERSION", '"%s"' % VERSION)]
library_dirs = []
libraries = []
runtime_library_dirs = []
@@ -26,14 +34,25 @@ extra_compile_args = []
extra_link_args = []
-def scan_argv(s, default):
+def fail(msg):
+ sys.stderr.write(msg + "\n")
+ exit(10)
+
+
+def scan_argv(s, default=None):
p = default
i = 1
while i < len(sys.argv):
arg = sys.argv[i]
if str.find(arg, s) == 0:
- p = arg[len(s):]
- assert p, arg
+ if s.endswith('='):
+ # --option=value
+ p = arg[len(s):]
+ assert p, arg
+ else:
+ # --option
+ # set value to True
+ p = True
del sys.argv[i]
else:
i = i + 1
@@ -42,7 +61,7 @@ def scan_argv(s, default):
# append contents of an environment variable to library_dirs[]
-def add_libdirs(envvar, sep, fatal=0):
+def add_libdirs(envvar, sep, fatal=False):
v = os.environ.get(envvar)
if not v:
return
@@ -55,15 +74,13 @@ def add_libdirs(envvar, sep, fatal=0):
if not dir in library_dirs:
library_dirs.append(dir)
elif fatal:
- print("FATAL: bad directory %s in environment variable %s" % (dir, envvar))
- sys.exit(1)
+ fail("FATAL: bad directory %s in environment variable %s" % (dir, envvar))
-if sys.platform == "win32":
- # Windows users have to configure the curl_dir path parameter to match
- # their cURL source installation. The path set here is just an example
- # and thus unlikely to match your installation.
- curl_dir = scan_argv("--curl-dir=", None)
+def configure_windows():
+ # Windows users have to pass --curl-dir parameter to specify path
+ # to libcurl, because there is no curl-config on windows at all.
+ curl_dir = scan_argv("--curl-dir=")
if curl_dir is None:
fail("Please specify --curl-dir=/path/to/built/libcurl")
if not os.path.exists(curl_dir):
@@ -72,105 +89,200 @@ if sys.platform == "win32":
fail("Curl directory is not a directory: %s" % curl_dir)
print("Using curl directory: %s" % curl_dir)
include_dirs.append(os.path.join(curl_dir, "include"))
- libcurl_lib_path = os.path.join(curl_dir, "lib", "libcurl.lib")
+
+ # libcurl windows documentation states that for linking against libcurl
+ # dll, the import library name is libcurl_imp.lib.
+ # in practice, the library name sometimes is libcurl.lib.
+ # override with: --libcurl-lib-name=libcurl_imp.lib
+ curl_lib_name = scan_argv('--libcurl-lib-name=', 'libcurl.lib')
+
+ if scan_argv("--use-libcurl-dll") is not None:
+ libcurl_lib_path = os.path.join(curl_dir, "lib", curl_lib_name)
+ extra_link_args.extend(["ws2_32.lib"])
+ if str.find(sys.version, "MSC") >= 0:
+ # build a dll
+ extra_compile_args.append("-MD")
+ else:
+ extra_compile_args.append("-DCURL_STATICLIB")
+ libcurl_lib_path = os.path.join(curl_dir, "lib", curl_lib_name)
+ extra_link_args.extend(["gdi32.lib", "wldap32.lib", "winmm.lib", "ws2_32.lib",])
+
if not os.path.exists(libcurl_lib_path):
fail("libcurl.lib does not exist at %s.\nCurl directory must point to compiled libcurl (bin/include/lib subdirectories): %s" %(libcurl_lib_path, curl_dir))
extra_objects.append(libcurl_lib_path)
- extra_link_args.extend(["gdi32.lib", "wldap32.lib", "winmm.lib", "ws2_32.lib",])
- add_libdirs("LIB", ";")
+
+ # make pycurl binary work on windows xp.
+ # we use inet_ntop which was added in vista and implement a fallback.
+ # our implementation will not be compiled with _WIN32_WINNT targeting
+ # vista or above, thus said binary won't work on xp.
+ # http://curl.haxx.se/mail/curlpython-2013-12/0007.html
+ extra_compile_args.append("-D_WIN32_WINNT=0x0501")
+
if str.find(sys.version, "MSC") >= 0:
extra_compile_args.append("-O2")
extra_compile_args.append("-GF") # enable read-only string pooling
extra_compile_args.append("-WX") # treat warnings as errors
p = subprocess.Popen(['cl.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
- match = re.search(r'Version (\d+)', err.split("\n")[0])
+ match = re.search(r'Version (\d+)', err.decode().split("\n")[0])
if match and int(match.group(1)) < 16:
# option removed in vs 2010:
# connect.microsoft.com/VisualStudio/feedback/details/475896/link-fatal-error-lnk1117-syntax-error-in-option-opt-nowin98/
extra_link_args.append("/opt:nowin98") # use small section alignment
- # workaround for distutils/msi version requirement per
- # epydoc.sourceforge.net/stdlib/distutils.version.StrictVersion-class.html -
- # only x.y.z version numbers are supported, whereas our versions might be x.y.z.p.
- # bugs.python.org/issue6040#msg133094
- from distutils.command.bdist_msi import bdist_msi
- import inspect
- import types
- import re
-
- class bdist_msi_version_hack(bdist_msi):
- """ MSI builder requires version to be in the x.x.x format """
- def run(self):
- def monkey_get_version(self):
- """ monkey patch replacement for metadata.get_version() that
- returns MSI compatible version string for bdist_msi
- """
- # get filename of the calling function
- if inspect.stack()[1][1].endswith('bdist_msi.py'):
- # strip revision from version (if any), e.g. 11.0.0-r31546
- match = re.match(r'(\d+\.\d+\.\d+)', self.version)
- assert match
- return match.group(1)
- else:
- return self.version
-
- # monkeypatching get_version() call for DistributionMetadata
- self.distribution.metadata.get_version = \
- types.MethodType(monkey_get_version, self.distribution.metadata)
- bdist_msi.run(self)
-else:
+def get_bdist_msi_version_hack():
+ # workaround for distutils/msi version requirement per
+ # epydoc.sourceforge.net/stdlib/distutils.version.StrictVersion-class.html -
+ # only x.y.z version numbers are supported, whereas our versions might be x.y.z.p.
+ # bugs.python.org/issue6040#msg133094
+ from distutils.command.bdist_msi import bdist_msi
+ import inspect
+ import types
+ import re
+
+ class bdist_msi_version_hack(bdist_msi):
+ """ MSI builder requires version to be in the x.x.x format """
+ def run(self):
+ def monkey_get_version(self):
+ """ monkey patch replacement for metadata.get_version() that
+ returns MSI compatible version string for bdist_msi
+ """
+ # get filename of the calling function
+ if inspect.stack()[1][1].endswith('bdist_msi.py'):
+ # strip revision from version (if any), e.g. 11.0.0-r31546
+ match = re.match(r'(\d+\.\d+\.\d+)', self.version)
+ assert match
+ return match.group(1)
+ else:
+ return self.version
+
+ # monkeypatching get_version() call for DistributionMetadata
+ self.distribution.metadata.get_version = \
+ types.MethodType(monkey_get_version, self.distribution.metadata)
+ bdist_msi.run(self)
+
+ return bdist_msi_version_hack
+
+
+def configure_unix():
# Find out the rest the hard way
- OPENSSL_DIR = scan_argv("--openssl-dir=", "")
- if OPENSSL_DIR != "":
+ OPENSSL_DIR = scan_argv("--openssl-dir=")
+ if OPENSSL_DIR is not None:
include_dirs.append(os.path.join(OPENSSL_DIR, "include"))
CURL_CONFIG = os.environ.get('PYCURL_CURL_CONFIG', "curl-config")
CURL_CONFIG = scan_argv("--curl-config=", CURL_CONFIG)
- d = os.popen("'%s' --version" % CURL_CONFIG).read()
- if d:
- d = str.strip(d)
- if not d:
- raise Exception("`%s' not found -- please install the libcurl development files or specify --curl-config=/path/to/curl-config" % CURL_CONFIG)
- print("Using %s (%s)" % (CURL_CONFIG, d))
- for e in split_quoted(os.popen("'%s' --cflags" % CURL_CONFIG).read()):
- if e[:2] == "-I":
+ try:
+ p = subprocess.Popen((CURL_CONFIG, '--version'),
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except OSError:
+ exc = sys.exc_info()[1]
+ msg = 'Could not run curl-config: %s' % str(exc)
+ 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
+ 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'),
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if p.wait() != 0:
+ msg = "Problem running `%s' --cflags" % CURL_CONFIG
+ if stderr:
+ msg += ":\n" + stderr.decode()
+ raise ConfigurationError(msg)
+ for arg in split_quoted(stdout.decode()):
+ if arg[:2] == "-I":
# do not add /usr/include
- if not re.search(r"^\/+usr\/+include\/*$", e[2:]):
- include_dirs.append(e[2:])
+ if not re.search(r"^\/+usr\/+include\/*$", arg[2:]):
+ include_dirs.append(arg[2:])
else:
- extra_compile_args.append(e)
+ extra_compile_args.append(arg)
- # Run curl-config --libs and --static-libs. Some platforms may not
- # support one or the other of these curl-config options, so gracefully
- # tolerate failure of either, but not both.
+ # Obtain linker flags/libraries to link against.
+ # In theory, all we should need is `curl-config --libs`.
+ # Apparently on some platforms --libs fails and --static-libs works,
+ # so try that.
+ # If --libs succeeds do not try --static libs; see
+ # https://github.com/pycurl/pycurl/issues/52 for more details.
+ # If neither --libs nor --static-libs work, fail.
optbuf = ""
+ errtext = ''
for option in ["--libs", "--static-libs"]:
- p = subprocess.Popen("'%s' %s" % (CURL_CONFIG, option), shell=True,
- stdout=subprocess.PIPE)
- (stdout, stderr) = p.communicate()
+ p = subprocess.Popen((CURL_CONFIG, option),
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
if p.wait() == 0:
- optbuf += stdout.decode()
+ optbuf = stdout.decode()
+ break
+ else:
+ errtext += stderr.decode()
if optbuf == "":
- raise Exception("Neither curl-config --libs nor curl-config --static-libs" +
- " produced output")
+ msg = "Neither curl-config --libs nor curl-config --static-libs" +\
+ " succeeded and produced output"
+ if errtext:
+ msg += ":\n" + errtext
+ raise ConfigurationError(msg)
libs = split_quoted(optbuf)
+
+ 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
+ define_macros.append(('HAVE_CURL_%s' % ssl_lib.upper(), 1))
+ else:
+ raise ConfigurationError('Invalid value "%s" for PYCURL_SSL_LIBRARY' % ssl_lib)
+ ssl_options = {
+ '--with-ssl': 'HAVE_CURL_OPENSSL',
+ '--with-gnutls': 'HAVE_CURL_GNUTLS',
+ '--with-nss': 'HAVE_CURL_NSS',
+ }
+ for option in ssl_options:
+ if scan_argv(option) is not None:
+ for other_option in ssl_options:
+ if option != other_option:
+ if scan_argv(other_option) is not None:
+ raise ConfigurationError('Cannot give both %s and %s' % (option, other_option))
+ ssl_lib_detected = True
+ define_macros.append((ssl_options[option], 1))
- for e in libs:
- if e[:2] == "-l":
- libraries.append(e[2:])
- if e[2:] == 'ssl':
+ for arg in libs:
+ if arg[:2] == "-l":
+ libraries.append(arg[2:])
+ if not ssl_lib_detected and arg[2:] == 'ssl':
define_macros.append(('HAVE_CURL_OPENSSL', 1))
- if e[2:] == 'gnutls':
+ ssl_lib_detected = True
+ if not ssl_lib_detected and arg[2:] == 'gnutls':
define_macros.append(('HAVE_CURL_GNUTLS', 1))
- if e[2:] == 'ssl3':
+ ssl_lib_detected = True
+ if not ssl_lib_detected and arg[2:] == 'ssl3':
define_macros.append(('HAVE_CURL_NSS', 1))
- elif e[:2] == "-L":
- library_dirs.append(e[2:])
+ ssl_lib_detected = True
+ elif arg[:2] == "-L":
+ library_dirs.append(arg[2:])
else:
- extra_link_args.append(e)
- for e in split_quoted(os.popen("'%s' --features" % CURL_CONFIG).read()):
- if e == 'SSL':
- define_macros.append(('HAVE_CURL_SSL', 1))
+ extra_link_args.append(arg)
+ 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
+ define_macros.append(('HAVE_CURL_SSL', 1))
+ else:
+ # if we are configuring for a particular ssl library,
+ # we can assume that ssl is being used
+ define_macros.append(('HAVE_CURL_SSL', 1))
if not libraries:
libraries.append("curl")
# Add extra compile flag for MacOS X
@@ -178,23 +290,42 @@ else:
extra_link_args.append("-flat_namespace")
+def configure():
+ if sys.platform == "win32":
+ configure_windows()
+ else:
+ configure_unix()
+
+
+
+def strip_pycurl_options():
+ if sys.platform == 'win32':
+ options = ['--curl-dir=', '--curl-lib-name=', '--use-libcurl-dll']
+ else:
+ options = ['--openssl-dir', '--curl-config']
+ for option in options:
+ scan_argv(option)
+
+
###############################################################################
-ext = Extension(
- name=PACKAGE,
- sources=[
- os.path.join("src", "pycurl.c"),
- ],
- include_dirs=include_dirs,
- define_macros=define_macros,
- library_dirs=library_dirs,
- libraries=libraries,
- runtime_library_dirs=runtime_library_dirs,
- extra_objects=extra_objects,
- extra_compile_args=extra_compile_args,
- extra_link_args=extra_link_args,
-)
-##print ext.__dict__; sys.exit(1)
+def get_extension():
+ ext = Extension(
+ name=PACKAGE,
+ sources=[
+ os.path.join("src", "pycurl.c"),
+ ],
+ include_dirs=include_dirs,
+ define_macros=define_macros,
+ library_dirs=library_dirs,
+ libraries=libraries,
+ runtime_library_dirs=runtime_library_dirs,
+ extra_objects=extra_objects,
+ extra_compile_args=extra_compile_args,
+ extra_link_args=extra_link_args,
+ )
+ ##print(ext.__dict__); sys.exit(1)
+ return ext
###############################################################################
@@ -209,7 +340,7 @@ def get_data_files():
else:
datadir = os.path.join("share", "doc", PACKAGE)
#
- files = ["ChangeLog", "COPYING", "COPYING2", "INSTALL", "README.rst", "TODO",]
+ files = ["ChangeLog", "COPYING-LGPL", "COPYING-MIT", "INSTALL", "README.rst"]
if files:
data_files.append((os.path.join(datadir), files))
files = glob.glob(os.path.join("doc", "*.html"))
@@ -234,12 +365,50 @@ def get_data_files():
###############################################################################
+def check_manifest():
+ import fnmatch
+
+ f = open('MANIFEST.in')
+ globs = []
+ try:
+ for line in f.readlines():
+ stripped = line.strip()
+ if stripped == '' or stripped.startswith('#'):
+ continue
+ assert stripped.startswith('include ')
+ glob = stripped[8:]
+ globs.append(glob)
+ finally:
+ f.close()
+
+ paths = []
+ start = os.path.abspath(os.path.dirname(__file__))
+ for root, dirs, files in os.walk(start):
+ if '.git' in dirs:
+ dirs.remove('.git')
+ for file in files:
+ if file.endswith('.pyc'):
+ continue
+ rel = os.path.join(root, file)[len(start)+1:]
+ paths.append(rel)
+
+ for path in paths:
+ included = False
+ for glob in globs:
+ if fnmatch.fnmatch(path, glob):
+ included = True
+ break
+ if not included:
+ print(path)
+
+###############################################################################
+
setup_args = dict(
name=PACKAGE,
version=VERSION,
description="PycURL -- cURL library module for Python",
- author="Kjetil Jacobsen, Markus F.X.J. Oberhumer",
- author_email="kjetilja at gmail.com, markus at oberhumer.com",
+ author="Kjetil Jacobsen, Markus F.X.J. Oberhumer, Oleg Pudeyev",
+ author_email="kjetilja at gmail.com, markus at oberhumer.com, oleg at bsdpower.com",
maintainer="Oleg Pudeyev",
maintainer_email="oleg at bsdpower.com",
url="http://pycurl.sourceforge.net/",
@@ -255,12 +424,11 @@ setup_args = dict(
'License :: OSI Approved :: MIT License',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
- 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 3',
'Topic :: Internet :: File Transfer Protocol (FTP)',
'Topic :: Internet :: WWW/HTTP',
],
- data_files=get_data_files(),
- ext_modules=[ext],
packages=[PY_PACKAGE],
package_dir={ PY_PACKAGE: os.path.join('python', 'curl') },
long_description="""
@@ -268,7 +436,7 @@ This module provides Python bindings for the cURL library.""",
)
if sys.platform == "win32":
- setup_args['cmdclass'] = {'bdist_msi': bdist_msi_version_hack}
+ setup_args['cmdclass'] = {'bdist_msi': get_bdist_msi_version_hack()}
##print distutils.__version__
if LooseVersion(distutils.__version__) > LooseVersion("1.0.1"):
@@ -276,7 +444,44 @@ if LooseVersion(distutils.__version__) > LooseVersion("1.0.1"):
if LooseVersion(distutils.__version__) < LooseVersion("1.0.3"):
setup_args["licence"] = setup_args["license"]
+unix_help = '''\
+PycURL Unix options:
+ --curl-config=/path/to/curl-config use specified curl-config binary
+ --openssl-dir=/path/to/openssl/dir path to OpenSSL headers and libraries
+ --with-ssl libcurl is linked against OpenSSL
+ --with-gnutls libcurl is linked against GnuTLS
+ --with-nss libcurl is linked against NSS
+'''
+
+windows_help = '''\
+PycURL Windows options:
+ --curl-dir=/path/to/compiled/libcurl path to libcurl headers and libraries
+ --use-libcurl-dll link against libcurl DLL, if not given
+ link against libcurl statically
+ --libcurl-lib-name=libcurl_imp.lib override libcurl import library name
+'''
+
if __name__ == "__main__":
- for o in ext.extra_objects:
- assert os.path.isfile(o), o
- setup(**setup_args)
+ if '--help' in sys.argv:
+ # unfortunately this help precedes distutils help
+ if sys.platform == "win32":
+ print(windows_help)
+ else:
+ print(unix_help)
+ # invoke setup without configuring pycurl because
+ # configuration might fail, and we want to display help anyway.
+ # we need to remove our options because distutils complains about them
+ strip_pycurl_options()
+ setup(**setup_args)
+ elif len(sys.argv) > 1 and sys.argv[1] == 'manifest':
+ check_manifest()
+ else:
+ configure()
+
+ setup_args['data_files'] = get_data_files()
+ ext = get_extension()
+ setup_args['ext_modules'] = [ext]
+
+ for o in ext.extra_objects:
+ assert os.path.isfile(o), o
+ setup(**setup_args)
diff --git a/setup_win32_ssl.py b/setup_win32_ssl.py
deleted file mode 100644
index 0ecc399..0000000
--- a/setup_win32_ssl.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
-# vi:ts=4:et
-# $Id$
-
-import os, sys, string
-assert sys.platform == "win32", "Only for building on Win32 with SSL and zlib"
-
-
-CURL_DIR = r"c:\src\build\pycurl\curl-7.19.0-ssl"
-OPENSSL_DIR = r"c:\src\build\pycurl\openssl-0.9.7g"
-sys.argv.insert(1, "--curl-dir=" + CURL_DIR)
-
-from setup import *
-
-setup_args["name"] = "pycurl-ssl"
-
-for l in ("libeay32.lib", "ssleay32.lib",):
- ext.extra_objects.append(os.path.join(OPENSSL_DIR, "out32", l))
-
-define_macros.append(('HAVE_CURL_SSL', 1))
-define_macros.append(('HAVE_CURL_OPENSSL', 1))
-
-pool = "\\" + r"pool\win32\vc6" + "\\"
-if string.find(sys.version, "MSC v.1310") >= 0:
- pool = "\\" + r"pool\win32\vc71" + "\\"
-ext.extra_objects.append(r"c:\src\pool\zlib-1.2.2" + pool + "zlib.lib")
-ext.extra_objects.append(r"c:\src\pool\c-ares-20050411" + pool + "ares.lib")
-ext.extra_objects.append(r"c:\src\pool\libidn-0.5.15" + pool + "idn.lib")
-
-
-if __name__ == "__main__":
- for o in ext.extra_objects:
- assert os.path.isfile(o), o
- setup(**setup_args)
-
diff --git a/src/pycurl.c b/src/pycurl.c
index 72bd00f..0a9f7b8 100644
--- a/src/pycurl.c
+++ b/src/pycurl.c
@@ -1,10 +1,9 @@
-/* $Id$ */
-
/* PycURL -- cURL Python module
*
* Authors:
* Copyright (C) 2001-2008 by Kjetil Jacobsen <kjetilja at gmail.com>
* Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer <markus at oberhumer.com>
+ * Copyright (C) 2013-2014 by Oleg Pudeyev <oleg at bsdpower.com>
*
* All rights reserved.
*
@@ -37,23 +36,46 @@
#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
# define WIN32 1
#endif
-#if defined(WIN32)
-# define CURL_STATICLIB 1
-#endif
#include <Python.h>
#include <pythread.h>
-#include <sys/types.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
+#include <sys/types.h>
+
+#if !defined(WIN32)
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
#include <curl/curl.h>
#include <curl/easy.h>
#include <curl/multi.h>
#undef NDEBUG
#include <assert.h>
+#if defined(WIN32)
+/* supposedly not present in errno.h provided with VC */
+# if !defined(EAFNOSUPPORT)
+# define EAFNOSUPPORT 97
+# endif
+#endif
+
+/* The inet_ntop() was added in ws2_32.dll on Windows Vista [1]. Hence the
+ * Windows SDK targeting lesser OS'es doesn't provide that prototype.
+ * Maybe we should use the local hidden inet_ntop() for all OS'es thus
+ * making a pycurl.pyd work across OS'es w/o rebuilding?
+ *
+ * [1] http://msdn.microsoft.com/en-us/library/windows/desktop/cc805843(v=vs.85).aspx
+ */
+#if defined(WIN32) && ((_WIN32_WINNT < 0x0600) || (NTDDI_VERSION < NTDDI_VISTA))
+ static const char * pycurl_inet_ntop (int family, void *addr, char *string, size_t string_size);
+ #define inet_ntop(fam,addr,string,size) pycurl_inet_ntop(fam,addr,string,size)
+#endif
+
/* Ensure we have updated versions */
#if !defined(PY_VERSION_HEX) || (PY_VERSION_HEX < 0x02040000)
# error "Need Python version 2.4 or greater to compile pycurl."
@@ -62,27 +84,46 @@
# error "Need libcurl version 7.19.0 or greater to compile pycurl."
#endif
-#if LIBCURL_VERSION_MAJOR >= 8 || \
- LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 20 || \
- LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR == 19 && LIBCURL_VERSION_PATCH >= 1
+#if LIBCURL_VERSION_NUM >= 0x071301 /* check for 7.19.1 or greater */
#define HAVE_CURLOPT_USERNAME
#define HAVE_CURLOPT_PROXYUSERNAME
#define HAVE_CURLOPT_CERTINFO
+#define HAVE_CURLOPT_POSTREDIR
+#endif
+
+#if LIBCURL_VERSION_NUM >= 0x071303 /* check for 7.19.3 or greater */
+#define HAVE_CURLAUTH_DIGEST_IE
+#endif
+
+#if LIBCURL_VERSION_NUM >= 0x071500 /* check for 7.21.0 or greater */
+#define HAVE_CURLINFO_LOCAL_PORT
+#define HAVE_CURLINFO_PRIMARY_PORT
+#define HAVE_CURLINFO_LOCAL_IP
+#endif
+
+#if LIBCURL_VERSION_NUM >= 0x071304 /* check for 7.19.4 or greater */
+#define HAVE_CURLOPT_NOPROXY
#endif
#if LIBCURL_VERSION_NUM >= 0x071503 /* check for 7.21.3 or greater */
#define HAVE_CURLOPT_RESOLVE
#endif
+#if LIBCURL_VERSION_NUM >= 0x071800 /* check for 7.24.0 or greater */
+#define HAVE_CURLOPT_DNS_SERVERS
+#endif
+
+#if LIBCURL_VERSION_NUM >= 0x071A00 /* check for 7.26.0 or greater */
+#define HAVE_CURL_REDIR_POST_303
+#endif
+
/* Python < 2.5 compat for Py_ssize_t */
-#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+#if PY_VERSION_HEX < 0x02050000
typedef int Py_ssize_t;
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
#endif
/* Py_TYPE is defined by Python 2.6+ */
-#if !defined(Py_TYPE)
+#if PY_VERSION_HEX < 0x02060000 && !defined(Py_TYPE)
# define Py_TYPE(x) (x)->ob_type
#endif
@@ -95,6 +136,7 @@ typedef int Py_ssize_t;
# define PYCURL_NEED_SSL_TSL
# define PYCURL_NEED_OPENSSL_TSL
# include <openssl/crypto.h>
+# define COMPILE_SSL_LIB "openssl"
# elif defined(HAVE_CURL_GNUTLS)
# include <gnutls/gnutls.h>
# if GNUTLS_VERSION_NUMBER <= 0x020b00
@@ -102,12 +144,20 @@ typedef int Py_ssize_t;
# define PYCURL_NEED_GNUTLS_TSL
# include <gcrypt.h>
# endif
-# elif !defined(HAVE_CURL_NSS)
+# define COMPILE_SSL_LIB "gnutls"
+# elif defined(HAVE_CURL_NSS)
+# define COMPILE_SSL_LIB "nss"
+# else
# warning \
"libcurl was compiled with SSL support, but configure could not determine which " \
"library was used; thus no SSL crypto locking callbacks will be set, which may " \
"cause random crashes on SSL requests"
+ /* since we have no crypto callbacks for other ssl backends,
+ * no reason to require users match those */
+# define COMPILE_SSL_LIB "none/other"
# endif /* HAVE_CURL_OPENSSL || HAVE_CURL_GNUTLS || HAVE_CURL_NSS */
+#else
+# define COMPILE_SSL_LIB "none/other"
#endif /* HAVE_CURL_SSL */
#if defined(PYCURL_NEED_SSL_TSL)
@@ -139,6 +189,13 @@ static void pycurl_ssl_cleanup(void);
# define PYCURL_END_ALLOW_THREADS
#endif
+#if PY_MAJOR_VERSION >= 3
+ #define PyInt_Type PyLong_Type
+ #define PyInt_Check(op) PyLong_Check(op)
+ #define PyInt_FromLong PyLong_FromLong
+ #define PyInt_AsLong PyLong_AsLong
+#endif
+
/* Calculate the number of OBJECTPOINT options we need to store */
#define OPTIONS_SIZE ((int)CURLOPT_LASTENTRY % 10000)
#define MOPTIONS_SIZE ((int)CURLMOPT_LASTENTRY % 10000)
@@ -154,19 +211,31 @@ static void pycurl_ssl_cleanup(void);
#define PYCURL_MEMGROUP_FILE 8
/* Share objects */
#define PYCURL_MEMGROUP_SHARE 16
+/* httppost buffer references */
+#define PYCURL_MEMGROUP_HTTPPOST 32
/* Postfields object */
#define PYCURL_MEMGROUP_POSTFIELDS 64
#define PYCURL_MEMGROUP_EASY \
- (PYCURL_MEMGROUP_CALLBACK | \
- PYCURL_MEMGROUP_FILE | PYCURL_MEMGROUP_POSTFIELDS)
+ (PYCURL_MEMGROUP_CALLBACK | PYCURL_MEMGROUP_FILE | \
+ PYCURL_MEMGROUP_HTTPPOST | PYCURL_MEMGROUP_POSTFIELDS)
#define PYCURL_MEMGROUP_ALL \
(PYCURL_MEMGROUP_ATTRDICT | PYCURL_MEMGROUP_EASY | \
PYCURL_MEMGROUP_MULTI | PYCURL_MEMGROUP_SHARE)
-/* Keep some default variables around */
-static char *g_pycurl_useragent = "PycURL/" LIBCURL_VERSION;
+#if defined(WIN32)
+# define PYCURL_STRINGIZE_IMP(x) #x
+# define PYCURL_STRINGIZE(x) PYCURL_STRINGIZE_IMP(x)
+# define PYCURL_VERSION_STRING PYCURL_STRINGIZE(PYCURL_VERSION)
+#else
+# define PYCURL_VERSION_STRING PYCURL_VERSION
+#endif
+
+#define PYCURL_VERSION_PREFIX "PycURL/" PYCURL_VERSION_STRING
+
+/* Initialized during module init */
+static char *g_pycurl_useragent = NULL;
/* Type objects */
static PyObject *ErrorObject = NULL;
@@ -213,6 +282,8 @@ typedef struct {
CurlMultiObject *multi_stack;
CurlShareObject *share;
struct curl_httppost *httppost;
+ /* List of INC'ed references associated with httppost. */
+ PyObject *httppost_ref_list;
struct curl_slist *httpheader;
struct curl_slist *http200aliases;
struct curl_slist *quote;
@@ -258,32 +329,60 @@ typedef struct {
} while (0)
-/* Safe XDECREF for object states that handles nested deallocations */
-#define ZAP(v) do {\
- PyObject *tmp = (PyObject *)(v); \
- (v) = NULL; \
- Py_XDECREF(tmp); \
-} while (0)
+/*************************************************************************
+// python 2/3 compatibility
+**************************************************************************/
+
+#if PY_MAJOR_VERSION >= 3
+# define PyText_FromFormat(format, str) PyUnicode_FromFormat((format), (str))
+# define PyText_FromString(str) PyUnicode_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_Check(obj) PyString_Check(obj)
+# define PyByteStr_AsStringAndSize(obj, buffer, length) PyString_AsStringAndSize((obj), (buffer), (length))
+#endif
+#define PyText_EncodedDecref(encoded) Py_XDECREF(encoded)
/*************************************************************************
// python utility functions
**************************************************************************/
-#if (PY_VERSION_HEX < 0x02030000) && !defined(PY_LONG_LONG)
-# define PY_LONG_LONG LONG_LONG
-#endif
+int PyText_AsStringAndSize(PyObject *obj, char **buffer, Py_ssize_t *length, PyObject **encoded_obj)
+{
+ if (PyByteStr_Check(obj)) {
+ *encoded_obj = NULL;
+ return PyByteStr_AsStringAndSize(obj, buffer, length);
+ } else {
+ int rv;
+ assert(PyUnicode_Check(obj));
+ *encoded_obj = PyUnicode_AsEncodedString(obj, "ascii", "strict");
+ if (*encoded_obj == NULL) {
+ return -1;
+ }
+ rv = PyByteStr_AsStringAndSize(*encoded_obj, buffer, length);
+ if (rv != 0) {
+ /* If we free the object, pointer must be reset to NULL */
+ Py_CLEAR(*encoded_obj);
+ }
+ return rv;
+ }
+}
+
/* Like PyString_AsString(), but set an exception if the string contains
* embedded NULs. Actually PyString_AsStringAndSize() already does that for
* us if the `len' parameter is NULL - see Objects/stringobject.c.
*/
-static char *PyString_AsString_NoNUL(PyObject *obj)
+static char *PyText_AsString_NoNUL(PyObject *obj, PyObject **encoded_obj)
{
char *s = NULL;
Py_ssize_t r;
- r = PyString_AsStringAndSize(obj, &s, NULL);
+ r = PyText_AsStringAndSize(obj, &s, NULL, encoded_obj);
if (r != 0)
return NULL; /* exception already set */
assert(s != NULL);
@@ -291,6 +390,20 @@ static char *PyString_AsString_NoNUL(PyObject *obj)
}
+/* Returns true if the object is of a type that can be given to
+ * curl_easy_setopt and such - either a byte string or a Unicode string
+ * with ASCII code points only.
+ */
+#if PY_MAJOR_VERSION >= 3
+static int PyText_Check(PyObject *o) {
+ return PyUnicode_Check(o) || PyBytes_Check(o);
+}
+#else
+static int PyText_Check(PyObject *o) {
+ return PyUnicode_Check(o) || PyString_Check(o);
+}
+#endif
+
/* Convert a curl slist (a list of strings) to a Python list.
* In case of error return NULL with an exception set.
*/
@@ -308,7 +421,7 @@ static PyObject *convert_slist(struct curl_slist *slist, int free_flags)
if (slist->data == NULL) {
v = Py_None; Py_INCREF(v);
} else {
- v = PyString_FromString(slist->data);
+ v = PyText_FromString(slist->data);
}
if (v == NULL || PyList_Append(ret, v) != 0) {
Py_XDECREF(v);
@@ -374,8 +487,9 @@ static PyObject *convert_certinfo(struct curl_certinfo *cinfo)
} else {
const char *sep = strchr(field, ':');
if (!sep) {
- field_tuple = PyString_FromString(field);
+ field_tuple = PyText_FromString(field);
} else {
+ /* XXX check */
field_tuple = Py_BuildValue("s#s", field, (int)(sep - field), sep+1);
}
if (!field_tuple)
@@ -716,7 +830,7 @@ share_lock_destroy(ShareLock *lock)
}
-void
+static void
share_lock_callback(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr)
{
CurlShareObject *share = (CurlShareObject*)userptr;
@@ -724,7 +838,7 @@ share_lock_callback(CURL *handle, curl_lock_data data, curl_lock_access locktype
}
-void
+static void
share_unlock_callback(CURL *handle, curl_lock_data data, void *userptr)
{
CurlShareObject *share = (CurlShareObject*)userptr;
@@ -733,6 +847,7 @@ share_unlock_callback(CURL *handle, curl_lock_data data, void *userptr)
#else /* WITH_THREAD */
+#if defined(PYCURL_NEED_SSL_TSL)
static void pycurl_ssl_init(void)
{
return;
@@ -742,6 +857,7 @@ static void pycurl_ssl_cleanup(void)
{
return;
}
+#endif
#endif /* WITH_THREAD */
@@ -814,17 +930,18 @@ do_share_traverse(CurlShareObject *self, visitproc visit, void *arg)
static int
do_share_clear(CurlShareObject *self)
{
- ZAP(self->dict);
+ Py_CLEAR(self->dict);
return 0;
}
static void
util_share_close(CurlShareObject *self){
- curl_share_cleanup(self->share_handle);
-#ifdef WITH_THREAD
- share_lock_destroy(self->lock);
-#endif
+ if (self->share_handle != NULL) {
+ CURLSH *share_handle = self->share_handle;
+ self->share_handle = NULL;
+ curl_share_cleanup(share_handle);
+ }
}
@@ -833,14 +950,29 @@ do_share_dealloc(CurlShareObject *self){
PyObject_GC_UnTrack(self);
Py_TRASHCAN_SAFE_BEGIN(self);
- ZAP(self->dict);
+ Py_CLEAR(self->dict);
util_share_close(self);
+#ifdef WITH_THREAD
+ share_lock_destroy(self->lock);
+#endif
+
PyObject_GC_Del(self);
Py_TRASHCAN_SAFE_END(self)
}
+static PyObject *
+do_share_close(CurlShareObject *self)
+{
+ if (check_share_state(self, 2, "close") != 0) {
+ return NULL;
+ }
+ util_share_close(self);
+ Py_RETURN_NONE;
+}
+
+
/* setopt, unsetopt*/
/* --------------- unsetopt/setopt/getinfo --------------- */
@@ -873,7 +1005,7 @@ do_curlshare_setopt(CurlShareObject *self, PyObject *args)
/* Handle the case of integer arguments */
if (PyInt_Check(obj)) {
long d = PyInt_AsLong(obj);
- if (d != CURL_LOCK_DATA_COOKIE && d != CURL_LOCK_DATA_DNS) {
+ if (d != CURL_LOCK_DATA_COOKIE && d != CURL_LOCK_DATA_DNS && d != CURL_LOCK_DATA_SSL_SESSION) {
goto error;
}
switch(option) {
@@ -920,6 +1052,7 @@ util_curl_new(void)
self->share = NULL;
self->multi_stack = NULL;
self->httppost = NULL;
+ self->httppost_ref_list = NULL;
self->httpheader = NULL;
self->http200aliases = NULL;
self->quote = NULL;
@@ -991,7 +1124,8 @@ util_curl_init(CurlObject *self)
}
/* Set default USERAGENT */
- res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) g_pycurl_useragent);
+ assert(g_pycurl_useragent);
+ res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, g_pycurl_useragent);
if (res != CURLE_OK) {
return (-1);
}
@@ -1036,7 +1170,7 @@ util_curl_xdecref(CurlObject *self, int flags, CURL *handle)
{
if (flags & PYCURL_MEMGROUP_ATTRDICT) {
/* Decrement refcount for attributes dictionary. */
- ZAP(self->dict);
+ Py_CLEAR(self->dict);
}
if (flags & PYCURL_MEMGROUP_MULTI) {
@@ -1053,24 +1187,24 @@ util_curl_xdecref(CurlObject *self, int flags, CURL *handle)
if (flags & PYCURL_MEMGROUP_CALLBACK) {
/* Decrement refcount for python callbacks. */
- ZAP(self->w_cb);
- ZAP(self->h_cb);
- ZAP(self->r_cb);
- ZAP(self->pro_cb);
- ZAP(self->debug_cb);
- ZAP(self->ioctl_cb);
+ Py_CLEAR(self->w_cb);
+ Py_CLEAR(self->h_cb);
+ Py_CLEAR(self->r_cb);
+ Py_CLEAR(self->pro_cb);
+ Py_CLEAR(self->debug_cb);
+ Py_CLEAR(self->ioctl_cb);
}
if (flags & PYCURL_MEMGROUP_FILE) {
/* Decrement refcount for python file objects. */
- ZAP(self->readdata_fp);
- ZAP(self->writedata_fp);
- ZAP(self->writeheader_fp);
+ Py_CLEAR(self->readdata_fp);
+ Py_CLEAR(self->writedata_fp);
+ Py_CLEAR(self->writeheader_fp);
}
if (flags & PYCURL_MEMGROUP_POSTFIELDS) {
/* Decrement refcount for postfields object */
- ZAP(self->postfields_obj);
+ Py_CLEAR(self->postfields_obj);
}
if (flags & PYCURL_MEMGROUP_SHARE) {
@@ -1084,6 +1218,11 @@ util_curl_xdecref(CurlObject *self, int flags, CURL *handle)
Py_DECREF(share);
}
}
+
+ if (flags & PYCURL_MEMGROUP_HTTPPOST) {
+ /* Decrement refcounts for httppost related references. */
+ Py_CLEAR(self->httppost_ref_list);
+ }
}
@@ -1150,7 +1289,7 @@ do_curl_dealloc(CurlObject *self)
PyObject_GC_UnTrack(self);
Py_TRASHCAN_SAFE_BEGIN(self)
- ZAP(self->dict);
+ Py_CLEAR(self->dict);
util_curl_close(self);
PyObject_GC_Del(self);
@@ -1176,7 +1315,8 @@ do_curl_errstr(CurlObject *self)
return NULL;
}
self->error[sizeof(self->error) - 1] = 0;
- return PyString_FromString(self->error);
+
+ return PyText_FromString(self->error);
}
@@ -1203,6 +1343,7 @@ do_curl_traverse(CurlObject *self, visitproc visit, void *arg)
VISIT(self->dict);
VISIT((PyObject *) self->multi_stack);
+ VISIT((PyObject *) self->share);
VISIT(self->w_cb);
VISIT(self->h_cb);
@@ -1210,10 +1351,14 @@ do_curl_traverse(CurlObject *self, visitproc visit, void *arg)
VISIT(self->pro_cb);
VISIT(self->debug_cb);
VISIT(self->ioctl_cb);
+ VISIT(self->opensocket_cb);
+ VISIT(self->seek_cb);
VISIT(self->readdata_fp);
VISIT(self->writedata_fp);
VISIT(self->writeheader_fp);
+
+ VISIT(self->postfields_obj);
return 0;
#undef VISIT
@@ -1277,7 +1422,11 @@ util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *strea
}
/* 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);
@@ -1321,11 +1470,85 @@ 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 = (char *)PyMem_Malloc(INET_ADDRSTRLEN);
+
+ if (addr_str == NULL) {
+ PyErr_SetString(ErrorObject, "Out of memory");
+ 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 = (char *)PyMem_Malloc(INET6_ADDRSTRLEN);
+
+ if (addr_str == NULL) {
+ PyErr_SetString(ErrorObject, "Out of memory");
+ 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("(si)", addr_str, ntohs(sin6->sin6_port));
+ PyMem_Free(addr_str);
+ }
+ break;
+ 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;
+}
+
+#if defined(WIN32)
+static SOCKET
+dup_winsock(SOCKET sock, const struct curl_sockaddr *address)
+{
+ int rv;
+ WSAPROTOCOL_INFO pi;
+
+ rv = WSADuplicateSocket(sock, GetCurrentProcessId(), &pi);
+ if (rv) {
+ return CURL_SOCKET_BAD;
+ }
+
+ /* not sure if WSA_FLAG_OVERLAPPED is needed, but it does not seem to hurt */
+ return WSASocket(address->family, address->socktype, address->protocol, &pi, 0, WSA_FLAG_OVERLAPPED);
+}
+#endif
+
/* 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)
+ struct curl_sockaddr *address)
{
PyObject *arglist;
PyObject *result = NULL;
@@ -1337,7 +1560,7 @@ opensocket_callback(void *clientp, curlsocktype purpose,
self = (CurlObject *)clientp;
PYCURL_ACQUIRE_THREAD();
- arglist = Py_BuildValue("(iii)", address->family, address->socktype, address->protocol);
+ arglist = Py_BuildValue("(iiiN)", address->family, address->socktype, address->protocol, convert_protocol_address(&address->addr, address->addrlen));
if (arglist == NULL)
goto verbose_error;
@@ -1349,21 +1572,26 @@ opensocket_callback(void *clientp, curlsocktype purpose,
}
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)) {
- ret = dup(PyInt_AsLong(fileno_result));
- goto done;
- }
+ 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, "Return value must be a socket.");
- ret = CURL_SOCKET_BAD;
- goto verbose_error;
+ PyErr_SetString(ErrorObject, "Return value must be a socket.");
+ ret = CURL_SOCKET_BAD;
+ goto verbose_error;
}
silent_error:
@@ -1489,43 +1717,71 @@ read_callback(char *ptr, size_t size, size_t nmemb, void *stream)
goto verbose_error;
/* handle result */
- if (PyString_Check(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;
- r = PyString_AsStringAndSize(result, &buf, &obj_size);
+ /*
+ 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) {
- PyErr_Format(ErrorObject, "invalid return value for read callback %ld %ld", (long)obj_size, (long)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
-#if LIBCURL_VERSION_NUM >= 0x071200 /* CURL_READFUNC_PAUSE appeared in libcurl 7.18.0 */
- && r != CURL_READFUNC_PAUSE
-#endif
- ) {
+ 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
-#if LIBCURL_VERSION_NUM >= 0x071200 /* CURL_READFUNC_PAUSE appeared in libcurl 7.18.0 */
- && r != CURL_READFUNC_PAUSE
-#endif
- ) {
+ 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 string");
+ PyErr_SetString(ErrorObject, "read callback must return a byte string or Unicode string with ASCII code points only");
goto verbose_error;
}
@@ -1747,6 +2003,7 @@ util_curl_unsetopt(CurlObject *self, int option)
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;
@@ -1755,7 +2012,7 @@ util_curl_unsetopt(CurlObject *self, int option)
break;
case CURLOPT_WRITEHEADER:
SETOPT((void *) 0);
- ZAP(self->writeheader_fp);
+ Py_CLEAR(self->writeheader_fp);
break;
case CURLOPT_CAINFO:
case CURLOPT_CAPATH:
@@ -1837,6 +2094,7 @@ do_curl_setopt(CurlObject *self, PyObject *args)
int option;
PyObject *obj;
int res;
+ PyObject *encoded_obj;
if (!PyArg_ParseTuple(args, "iO:setopt", &option, &obj))
return NULL;
@@ -1857,7 +2115,8 @@ do_curl_setopt(CurlObject *self, PyObject *args)
}
/* Handle the case of string arguments */
- if (PyString_Check(obj)) {
+
+ if (PyText_Check(obj)) {
char *str = NULL;
Py_ssize_t len = -1;
@@ -1906,13 +2165,19 @@ do_curl_setopt(CurlObject *self, PyObject *args)
case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5:
case CURLOPT_CRLFILE:
case CURLOPT_ISSUERCERT:
+#ifdef HAVE_CURLOPT_DNS_SERVERS
+ case CURLOPT_DNS_SERVERS:
+#endif
+#ifdef HAVE_CURLOPT_NOPROXY
+ case CURLOPT_NOPROXY:
+#endif
/* FIXME: check if more of these options allow binary data */
- str = PyString_AsString_NoNUL(obj);
+ str = PyText_AsString_NoNUL(obj, &encoded_obj);
if (str == NULL)
return NULL;
break;
case CURLOPT_POSTFIELDS:
- if (PyString_AsStringAndSize(obj, &str, &len) != 0)
+ if (PyText_AsStringAndSize(obj, &str, &len, &encoded_obj) != 0)
return NULL;
/* automatically set POSTFIELDSIZE */
if (len <= INT_MAX) {
@@ -1933,13 +2198,32 @@ do_curl_setopt(CurlObject *self, PyObject *args)
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) {
- Py_INCREF(obj);
+ PyObject *store_obj;
+#if PY_MAJOR_VERSION >= 3
+ /* 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 {
+ store_obj = obj;
+ Py_INCREF(store_obj);
+ }
+#else
+ /* no encoding is performed, incref the original object. */
+ store_obj = obj;
+ Py_INCREF(store_obj);
+#endif
util_curl_xdecref(self, PYCURL_MEMGROUP_POSTFIELDS, self->handle);
- self->postfields_obj = obj;
+ self->postfields_obj = store_obj;
+ } else {
+ PyText_EncodedDecref(encoded_obj);
}
Py_RETURN_NONE;
}
@@ -1988,6 +2272,7 @@ do_curl_setopt(CurlObject *self, PyObject *args)
#undef IS_LONG_OPTION
#undef IS_OFF_T_OPTION
+#if PY_MAJOR_VERSION < 3
/* Handle the case of file objects */
if (PyFile_Check(obj)) {
FILE *fp;
@@ -2021,15 +2306,15 @@ do_curl_setopt(CurlObject *self, PyObject *args)
switch (option) {
case CURLOPT_READDATA:
- ZAP(self->readdata_fp);
+ Py_CLEAR(self->readdata_fp);
self->readdata_fp = obj;
break;
case CURLOPT_WRITEDATA:
- ZAP(self->writedata_fp);
+ Py_CLEAR(self->writedata_fp);
self->writedata_fp = obj;
break;
case CURLOPT_WRITEHEADER:
- ZAP(self->writeheader_fp);
+ Py_CLEAR(self->writeheader_fp);
self->writeheader_fp = obj;
break;
default:
@@ -2039,6 +2324,7 @@ do_curl_setopt(CurlObject *self, PyObject *args)
/* Return success */
Py_RETURN_NONE;
}
+#endif
/* Handle the case of list objects */
if (PyList_Check(obj)) {
@@ -2083,6 +2369,10 @@ do_curl_setopt(CurlObject *self, PyObject *args)
if (option == CURLOPT_HTTPPOST) {
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;
for (i = 0; i < len; i++) {
char *nstr = NULL, *cstr = NULL;
@@ -2091,22 +2381,30 @@ do_curl_setopt(CurlObject *self, PyObject *args)
if (!PyTuple_Check(listitem)) {
curl_formfree(post);
+ Py_XDECREF(ref_params);
PyErr_SetString(PyExc_TypeError, "list items must be tuple objects");
return NULL;
}
if (PyTuple_GET_SIZE(listitem) != 2) {
curl_formfree(post);
+ Py_XDECREF(ref_params);
PyErr_SetString(PyExc_TypeError, "tuple must contain two elements (name, value)");
return NULL;
}
- if (PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 0), &nstr, &nlen) != 0) {
+ if (PyText_AsStringAndSize(PyTuple_GET_ITEM(listitem, 0), &nstr, &nlen, &nencoded_obj) != 0) {
curl_formfree(post);
- PyErr_SetString(PyExc_TypeError, "tuple must contain string as first element");
+ Py_XDECREF(ref_params);
+ PyErr_SetString(PyExc_TypeError, "tuple must contain a byte string or Unicode string with ASCII code points only as first element");
return NULL;
}
- if (PyString_Check(PyTuple_GET_ITEM(listitem, 1))) {
+ if (PyText_Check(PyTuple_GET_ITEM(listitem, 1))) {
/* Handle strings as second argument for backwards compatibility */
- PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 1), &cstr, &clen);
+
+ if (PyText_AsStringAndSize(PyTuple_GET_ITEM(listitem, 1), &cstr, &clen, &cencoded_obj)) {
+ curl_formfree(post);
+ Py_XDECREF(ref_params);
+ CURLERROR_RETVAL();
+ }
/* INFO: curl_formadd() internally does memdup() the data, so
* embedded NUL characters _are_ allowed here. */
res = curl_formadd(&post, &last,
@@ -2115,8 +2413,10 @@ do_curl_setopt(CurlObject *self, PyObject *args)
CURLFORM_COPYCONTENTS, cstr,
CURLFORM_CONTENTSLENGTH, (long) clen,
CURLFORM_END);
+ PyText_EncodedDecref(cencoded_obj);
if (res != CURLE_OK) {
curl_formfree(post);
+ Py_XDECREF(ref_params);
CURLERROR_RETVAL();
}
}
@@ -2130,14 +2430,16 @@ do_curl_setopt(CurlObject *self, PyObject *args)
/* Sanity check that there are at least two tuple items */
if (tlen < 2) {
curl_formfree(post);
+ Py_XDECREF(ref_params);
PyErr_SetString(PyExc_TypeError, "tuple must contain at least one option and one value");
return NULL;
}
- /* Allocate enough space to accommodate length options for content */
+ /* Allocate enough space to accommodate length options for content or buffers, plus a terminator. */
forms = PyMem_Malloc(sizeof(struct curl_forms) * ((tlen*2) + 1));
if (forms == NULL) {
curl_formfree(post);
+ Py_XDECREF(ref_params);
PyErr_NoMemory();
return NULL;
}
@@ -2152,18 +2454,21 @@ do_curl_setopt(CurlObject *self, PyObject *args)
PyErr_SetString(PyExc_TypeError, "expected value");
PyMem_Free(forms);
curl_formfree(post);
+ Py_XDECREF(ref_params);
return NULL;
}
if (!PyInt_Check(PyTuple_GET_ITEM(t, j))) {
PyErr_SetString(PyExc_TypeError, "option must be long");
PyMem_Free(forms);
curl_formfree(post);
+ Py_XDECREF(ref_params);
return NULL;
}
- if (!PyString_Check(PyTuple_GET_ITEM(t, j+1))) {
- PyErr_SetString(PyExc_TypeError, "value must be string");
+ if (!PyText_Check(PyTuple_GET_ITEM(t, j+1))) {
+ PyErr_SetString(PyExc_TypeError, "value must be a byte string or a Unicode string with ASCII code points only");
PyMem_Free(forms);
curl_formfree(post);
+ Py_XDECREF(ref_params);
return NULL;
}
@@ -2171,14 +2476,23 @@ do_curl_setopt(CurlObject *self, PyObject *args)
if (val != CURLFORM_COPYCONTENTS &&
val != CURLFORM_FILE &&
val != CURLFORM_FILENAME &&
- val != CURLFORM_CONTENTTYPE)
+ val != CURLFORM_CONTENTTYPE &&
+ val != CURLFORM_BUFFER &&
+ val != CURLFORM_BUFFERPTR)
{
PyErr_SetString(PyExc_TypeError, "unsupported option");
PyMem_Free(forms);
curl_formfree(post);
+ Py_XDECREF(ref_params);
+ return NULL;
+ }
+ if (PyText_AsStringAndSize(PyTuple_GET_ITEM(t, j+1), &ostr, &olen, &oencoded_obj)) {
+ /* exception should be already set */
+ PyMem_Free(forms);
+ curl_formfree(post);
+ Py_XDECREF(ref_params);
return NULL;
}
- PyString_AsStringAndSize(PyTuple_GET_ITEM(t, j+1), &ostr, &olen);
forms[k].option = val;
forms[k].value = ostr;
++k;
@@ -2188,6 +2502,31 @@ do_curl_setopt(CurlObject *self, PyObject *args)
forms[k].value = (const char *)olen;
++k;
}
+ else if (val == CURLFORM_BUFFERPTR) {
+ PyObject *obj = PyTuple_GET_ITEM(t, j+1);
+
+ ref_params = PyList_New((Py_ssize_t)0);
+ if (ref_params == NULL) {
+ PyText_EncodedDecref(oencoded_obj);
+ PyMem_Free(forms);
+ curl_formfree(post);
+ return NULL;
+ }
+
+ /* Ensure that the buffer remains alive until curl_easy_cleanup() */
+ if (PyList_Append(ref_params, obj) != 0) {
+ PyText_EncodedDecref(oencoded_obj);
+ PyMem_Free(forms);
+ curl_formfree(post);
+ Py_DECREF(ref_params);
+ return NULL;
+ }
+
+ /* 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,
@@ -2195,28 +2534,41 @@ do_curl_setopt(CurlObject *self, PyObject *args)
CURLFORM_NAMELENGTH, (long) nlen,
CURLFORM_ARRAY, forms,
CURLFORM_END);
+ PyText_EncodedDecref(oencoded_obj);
PyMem_Free(forms);
if (res != CURLE_OK) {
curl_formfree(post);
+ Py_XDECREF(ref_params);
CURLERROR_RETVAL();
}
} else {
/* Some other type was given, ignore */
+ PyText_EncodedDecref(nencoded_obj);
curl_formfree(post);
+ Py_XDECREF(ref_params);
PyErr_SetString(PyExc_TypeError, "unsupported second type in tuple");
return NULL;
}
+ PyText_EncodedDecref(nencoded_obj);
}
res = curl_easy_setopt(self->handle, CURLOPT_HTTPPOST, post);
/* Check for errors */
if (res != CURLE_OK) {
curl_formfree(post);
+ Py_XDECREF(ref_params);
CURLERROR_RETVAL();
}
- /* Finally, free previously allocated httppost and update */
+ /* 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;
}
@@ -2228,20 +2580,22 @@ do_curl_setopt(CurlObject *self, PyObject *args)
PyObject *listitem = PyList_GetItem(obj, i);
struct curl_slist *nlist;
char *str;
+ PyObject *sencoded_obj;
- if (!PyString_Check(listitem)) {
+ if (!PyText_Check(listitem)) {
curl_slist_free_all(slist);
- PyErr_SetString(PyExc_TypeError, "list items must be string objects");
+ 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 = PyString_AsString_NoNUL(listitem);
+ 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);
return PyErr_NoMemory();
@@ -2283,58 +2637,58 @@ do_curl_setopt(CurlObject *self, PyObject *args)
return NULL;
}
Py_INCREF(obj);
- ZAP(self->writedata_fp);
- ZAP(self->w_cb);
+ 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);
- ZAP(self->h_cb);
+ 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);
- ZAP(self->readdata_fp);
- ZAP(self->r_cb);
+ 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);
- ZAP(self->pro_cb);
+ 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;
case CURLOPT_DEBUGFUNCTION:
Py_INCREF(obj);
- ZAP(self->debug_cb);
+ 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);
- ZAP(self->ioctl_cb);
+ 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);
- ZAP(self->opensocket_cb);
+ 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;
case CURLOPT_SEEKFUNCTION:
Py_INCREF(obj);
- ZAP(self->seek_cb);
+ 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);
@@ -2384,6 +2738,63 @@ do_curl_setopt(CurlObject *self, PyObject *args)
Py_RETURN_NONE;
}
+ /*
+ Handle the case of file-like objects for Python 3.
+
+ 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.
+
+ For consistency, ability to use any file-like object is also available
+ on Python 2.
+ */
+ if (option == CURLOPT_READDATA ||
+ option == CURLOPT_WRITEDATA ||
+ option == CURLOPT_WRITEHEADER)
+ {
+ PyObject *write_method = PyObject_GetAttrString(obj, "write");
+ if (write_method) {
+ PyObject *arglist;
+ PyObject *rv;
+
+ switch (option) {
+ case CURLOPT_READDATA:
+ option = CURLOPT_READFUNCTION;
+ break;
+ case CURLOPT_WRITEDATA:
+ option = CURLOPT_WRITEFUNCTION;
+ break;
+ case CURLOPT_WRITEHEADER:
+ if (self->w_cb != NULL) {
+ PyErr_SetString(ErrorObject, "cannot combine WRITEHEADER with WRITEFUNCTION.");
+ Py_DECREF(write_method);
+ return NULL;
+ }
+ option = CURLOPT_HEADERFUNCTION;
+ break;
+ default:
+ PyErr_SetString(PyExc_TypeError, "objects are not supported for this option");
+ Py_DECREF(write_method);
+ return NULL;
+ }
+
+ arglist = Py_BuildValue("(iO)", option, write_method);
+ /* reference is now in arglist */
+ Py_DECREF(write_method);
+ if (arglist == NULL) {
+ return NULL;
+ }
+ rv = do_curl_setopt(self, arglist);
+ Py_DECREF(arglist);
+ return rv;
+ } else {
+ PyErr_SetString(ErrorObject, "object given without a write method");
+ return NULL;
+ }
+ }
+
/* Failed to match any of the function signatures -- return error */
error:
PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt");
@@ -2417,6 +2828,13 @@ do_curl_getinfo(CurlObject *self, PyObject *args)
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
+
{
/* Return PyInt as result */
long l_res = -1;
@@ -2434,6 +2852,9 @@ do_curl_getinfo(CurlObject *self, PyObject *args)
case CURLINFO_FTP_ENTRY_PATH:
case CURLINFO_REDIRECT_URL:
case CURLINFO_PRIMARY_IP:
+#ifdef HAVE_CURLINFO_LOCAL_IP
+ case CURLINFO_LOCAL_IP:
+#endif
{
/* Return PyString as result */
char *s_res = NULL;
@@ -2443,9 +2864,11 @@ do_curl_getinfo(CurlObject *self, PyObject *args)
CURLERROR_RETVAL();
}
/* If the resulting string is NULL, return None */
- if (s_res == NULL)
+ if (s_res == NULL) {
Py_RETURN_NONE;
- return PyString_FromString(s_res);
+ }
+ return PyText_FromString(s_res);
+
}
case CURLINFO_CONNECT_TIME:
@@ -2505,8 +2928,6 @@ do_curl_getinfo(CurlObject *self, PyObject *args)
return NULL;
}
-#if LIBCURL_VERSION_NUM >= 0x071200 /* curl_easy_pause() appeared in libcurl 7.18.0 */
-
/* curl_easy_pause() can be called from inside a callback or outside */
static PyObject *
do_curl_pause(CurlObject *self, PyObject *args)
@@ -2551,12 +2972,11 @@ do_curl_pause(CurlObject *self, PyObject *args)
}
}
-static char co_pause_doc [] = "pause(bitmask) -> None. "
+static const char co_pause_doc [] =
+ "pause(bitmask) -> None. "
"Pauses or unpauses a curl handle. Bitmask should be a value such as PAUSE_RECV or PAUSE_CONT. "
"Raises pycurl.error exception upon failure.\n";
-#endif
-
/*************************************************************************
// CurlMultiObject
**************************************************************************/
@@ -2619,7 +3039,7 @@ do_multi_dealloc(CurlMultiObject *self)
PyObject_GC_UnTrack(self);
Py_TRASHCAN_SAFE_BEGIN(self)
- ZAP(self->dict);
+ Py_CLEAR(self->dict);
util_multi_close(self);
PyObject_GC_Del(self);
@@ -2644,7 +3064,7 @@ do_multi_close(CurlMultiObject *self)
static int
do_multi_clear(CurlMultiObject *self)
{
- ZAP(self->dict);
+ Py_CLEAR(self->dict);
return 0;
}
@@ -2664,21 +3084,20 @@ do_multi_traverse(CurlMultiObject *self, visitproc visit, void *arg)
/* --------------- setopt --------------- */
-int multi_socket_callback(CURL *easy,
- curl_socket_t s,
- int what,
- void *userp,
- void *socketp)
+static int
+multi_socket_callback(CURL *easy,
+ curl_socket_t s,
+ int what,
+ void *userp,
+ void *socketp)
{
CurlMultiObject *self;
- CurlObject *easy_self;
PyObject *arglist;
PyObject *result = NULL;
PYCURL_DECLARE_THREAD_STATE;
/* acquire thread */
self = (CurlMultiObject *)userp;
- curl_easy_getinfo(easy, CURLINFO_PRIVATE, &easy_self);
if (!PYCURL_ACQUIRE_THREAD_MULTI())
return 0;
@@ -2713,9 +3132,10 @@ verbose_error:
}
-int multi_timer_callback(CURLM *multi,
- long timeout_ms,
- void *userp)
+static int
+multi_timer_callback(CURLM *multi,
+ long timeout_ms,
+ void *userp)
{
CurlMultiObject *self;
PyObject *arglist;
@@ -3247,22 +3667,53 @@ do_multi_select(CurlMultiObject *self, PyObject *args)
/* --------------- methods --------------- */
-static char cso_setopt_doc [] = "setopt(option, parameter) -> None. Set curl share option. Raises pycurl.error exception upon failure.\n";
-static char co_close_doc [] = "close() -> None. Close handle and end curl session.\n";
-static char co_errstr_doc [] = "errstr() -> String. Return the internal libcurl error buffer string.\n";
-static char co_getinfo_doc [] = "getinfo(info) -> Res. Extract and return information from a curl session. Raises pycurl.error exception upon failure.\n";
-static char co_perform_doc [] = "perform() -> None. Perform a file transfer. Raises pycurl.error exception upon failure.\n";
-static char co_setopt_doc [] = "setopt(option, parameter) -> None. Set curl session option. Raises pycurl.error exception upon failure.\n";
-static char co_unsetopt_doc [] = "unsetopt(option) -> None. Reset curl session option to default value. Raises pycurl.error exception upon failure.\n";
-static char co_reset_doc [] = "reset() -> None. Reset all options set on curl handle to default values, but preserves live connections, session ID cache, DNS cache, cookies, and shares.\n";
-
-static char co_multi_fdset_doc [] = "fdset() -> Tuple. Returns a tuple of three lists that can be passed to the select.select() method .\n";
-static char co_multi_info_read_doc [] = "info_read([max_objects]) -> Tuple. Returns a tuple (number of queued handles, [curl objects]).\n";
-static char co_multi_select_doc [] = "select([timeout]) -> Int. Returns result from doing a select() on the curl multi file descriptor with the given timeout.\n";
-static char co_multi_socket_action_doc [] = "socket_action(sockfd, ev_bitmask) -> Tuple. Returns result from doing a socket_action() on the curl multi file descriptor with the given timeout.\n";
-static char co_multi_socket_all_doc [] = "socket_all() -> Tuple. Returns result from doing a socket_all() on the curl multi file descriptor with the given timeout.\n";
+static const char cso_close_doc [] =
+ "close() -> None. "
+ "Close shared handle.\n";
+static const char cso_setopt_doc [] =
+ "setopt(option, parameter) -> None. "
+ "Set curl share option. Raises pycurl.error exception upon failure.\n";
+
+static const char co_close_doc [] =
+ "close() -> None. "
+ "Close handle and end curl session.\n";
+static const char co_errstr_doc [] =
+ "errstr() -> String. "
+ "Return the internal libcurl error buffer string.\n";
+static const char co_getinfo_doc [] =
+ "getinfo(info) -> Res. "
+ "Extract and return information from a curl session. Raises pycurl.error exception upon failure.\n";
+static const char co_perform_doc [] =
+ "perform() -> None. "
+ "Perform a file transfer. Raises pycurl.error exception upon failure.\n";
+static const char co_setopt_doc [] =
+ "setopt(option, parameter) -> None. "
+ "Set curl session option. Raises pycurl.error exception upon failure.\n";
+static const char co_unsetopt_doc [] =
+ "unsetopt(option) -> None. "
+ "Reset curl session option to default value. Raises pycurl.error exception upon failure.\n";
+static const char co_reset_doc [] =
+ "reset() -> None. "
+ "Reset all options set on curl handle to default values, but preserves live connections, session ID cache, DNS cache, cookies, and shares.\n";
+
+static const char co_multi_fdset_doc [] =
+ "fdset() -> Tuple. "
+ "Returns a tuple of three lists that can be passed to the select.select() method .\n";
+static const char co_multi_info_read_doc [] =
+ "info_read([max_objects]) -> Tuple. "
+ "Returns a tuple (number of queued handles, [curl objects]).\n";
+static const char co_multi_select_doc [] =
+ "select([timeout]) -> Int. "
+ "Returns result from doing a select() on the curl multi file descriptor with the given timeout.\n";
+static const char co_multi_socket_action_doc [] =
+ "socket_action(sockfd, ev_bitmask) -> Tuple. "
+ "Returns result from doing a socket_action() on the curl multi file descriptor with the given timeout.\n";
+static const char co_multi_socket_all_doc [] =
+ "socket_all() -> Tuple. "
+ "Returns result from doing a socket_all() on the curl multi file descriptor with the given timeout.\n";
static PyMethodDef curlshareobject_methods[] = {
+ {"close", (PyCFunction)do_share_close, METH_NOARGS, cso_close_doc},
{"setopt", (PyCFunction)do_curlshare_setopt, METH_VARARGS, cso_setopt_doc},
{NULL, NULL, 0, 0}
};
@@ -3271,9 +3722,7 @@ static PyMethodDef curlobject_methods[] = {
{"close", (PyCFunction)do_curl_close, METH_NOARGS, co_close_doc},
{"errstr", (PyCFunction)do_curl_errstr, METH_NOARGS, co_errstr_doc},
{"getinfo", (PyCFunction)do_curl_getinfo, METH_VARARGS, co_getinfo_doc},
-#if LIBCURL_VERSION_NUM >= 0x071200 /* curl_easy_pause() appeared in libcurl 7.18.0 */
{"pause", (PyCFunction)do_curl_pause, METH_VARARGS, co_pause_doc},
-#endif
{"perform", (PyCFunction)do_curl_perform, METH_NOARGS, co_perform_doc},
{"setopt", (PyCFunction)do_curl_setopt, METH_VARARGS, co_setopt_doc},
{"unsetopt", (PyCFunction)do_curl_unsetopt, METH_VARARGS, co_unsetopt_doc},
@@ -3304,6 +3753,112 @@ static PyObject *curlobject_constants = NULL;
static PyObject *curlmultiobject_constants = NULL;
static PyObject *curlshareobject_constants = NULL;
+
+#if PY_MAJOR_VERSION >= 3
+static PyObject *
+my_getattro(PyObject *co, PyObject *name, PyObject *dict1, PyObject *dict2, PyMethodDef *m)
+{
+ PyObject *v = NULL;
+ if( dict1 != NULL )
+ v = PyDict_GetItem(dict1, name);
+ if( v == NULL && dict2 != NULL )
+ v = PyDict_GetItem(dict2, name);
+ if( v != NULL )
+ {
+ Py_INCREF(v);
+ return v;
+ }
+ PyErr_SetString(PyExc_AttributeError, "trying to obtain a non-existing attribute");
+ return NULL;
+}
+
+static int
+my_setattro(PyObject **dict, PyObject *name, PyObject *v)
+{
+ if( *dict == NULL )
+ {
+ *dict = PyDict_New();
+ if( *dict == NULL )
+ return -1;
+ }
+ if (v != NULL)
+ return PyDict_SetItem(*dict, name, v);
+ else {
+ int v = PyDict_DelItem(*dict, name);
+ if (v != 0) {
+ /* need to convert KeyError to AttributeError */
+ if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+ PyErr_SetString(PyExc_AttributeError, "trying to delete a non-existing attribute");
+ }
+ }
+ return v;
+ }
+}
+
+PyObject *do_curl_getattro(PyObject *o, PyObject *n)
+{
+ PyObject *v = PyObject_GenericGetAttr(o, n);
+ if( !v && PyErr_ExceptionMatches(PyExc_AttributeError) )
+ {
+ PyErr_Clear();
+ v = my_getattro(o, n, ((CurlObject *)o)->dict,
+ curlobject_constants, curlobject_methods);
+ }
+ return v;
+}
+
+static int
+do_curl_setattro(PyObject *o, PyObject *name, PyObject *v)
+{
+ assert_curl_state((CurlObject *)o);
+ return my_setattro(&((CurlObject *)o)->dict, name, v);
+}
+
+static PyObject *
+do_multi_getattro(PyObject *o, PyObject *n)
+{
+ PyObject *v;
+ assert_multi_state((CurlMultiObject *)o);
+ v = PyObject_GenericGetAttr(o, n);
+ if( !v && PyErr_ExceptionMatches(PyExc_AttributeError) )
+ {
+ PyErr_Clear();
+ v = my_getattro(o, n, ((CurlMultiObject *)o)->dict,
+ curlmultiobject_constants, curlmultiobject_methods);
+ }
+ return v;
+}
+
+static int
+do_multi_setattro(PyObject *o, PyObject *n, PyObject *v)
+{
+ assert_multi_state((CurlMultiObject *)o);
+ return my_setattro(&((CurlMultiObject *)o)->dict, n, v);
+}
+
+static PyObject *
+do_share_getattro(PyObject *o, PyObject *n)
+{
+ PyObject *v;
+ assert_share_state((CurlShareObject *)o);
+ v = PyObject_GenericGetAttr(o, n);
+ if( !v && PyErr_ExceptionMatches(PyExc_AttributeError) )
+ {
+ PyErr_Clear();
+ v = my_getattro(o, n, ((CurlShareObject *)o)->dict,
+ curlshareobject_constants, curlshareobject_methods);
+ }
+ return v;
+}
+
+static int
+do_share_setattro(PyObject *o, PyObject *n, PyObject *v)
+{
+ assert_share_state((CurlShareObject *)o);
+ return my_setattro(&((CurlShareObject *)o)->dict, n, v);
+}
+
+#else
static int
my_setattr(PyObject **dict, char *name, PyObject *v)
{
@@ -3382,10 +3937,53 @@ do_multi_getattr(CurlMultiObject *co, char *name)
return my_getattr((PyObject *)co, name, co->dict,
curlmultiobject_constants, curlmultiobject_methods);
}
+#endif
/* --------------- actual type definitions --------------- */
+#if PY_MAJOR_VERSION >= 3
+static PyTypeObject CurlShare_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pycurl.CurlShare", /* tp_name */
+ sizeof(CurlShareObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)do_share_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_reserved */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ (getattrofunc)do_share_getattro, /* tp_getattro */
+ (setattrofunc)do_share_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ 0, /* tp_doc */
+ (traverseproc)do_share_traverse, /* tp_traverse */
+ (inquiry)do_share_clear, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ curlshareobject_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+#else
static PyTypeObject CurlShare_Type = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
@@ -3416,7 +4014,50 @@ static PyTypeObject CurlShare_Type = {
* safely ignore any compiler warnings about missing initializers.
*/
};
+#endif
+#if PY_MAJOR_VERSION >= 3
+static PyTypeObject Curl_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pycurl.Curl", /* tp_name */
+ sizeof(CurlObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)do_curl_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_reserved */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ (getattrofunc)do_curl_getattro, /* tp_getattro */
+ (setattrofunc)do_curl_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ 0, /* tp_doc */
+ (traverseproc)do_curl_traverse, /* tp_traverse */
+ (inquiry)do_curl_clear, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ curlobject_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+#else
static PyTypeObject Curl_Type = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
@@ -3447,7 +4088,50 @@ static PyTypeObject Curl_Type = {
* safely ignore any compiler warnings about missing initializers.
*/
};
+#endif
+#if PY_MAJOR_VERSION >= 3
+static PyTypeObject CurlMulti_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pycurl.CurlMulti", /* tp_name */
+ sizeof(CurlMultiObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)do_multi_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, // (getattrfunc)do_curl_getattr, /* tp_getattr */
+ 0, //(setattrfunc)do_curl_setattr, /* tp_setattr */
+ 0, /* tp_reserved */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ (getattrofunc)do_multi_getattro, //0, /* tp_getattro */
+ (setattrofunc)do_multi_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ 0, /* tp_doc */
+ (traverseproc)do_multi_traverse, /* tp_traverse */
+ (inquiry)do_multi_clear, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ curlmultiobject_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+#else
static PyTypeObject CurlMulti_Type = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
@@ -3478,6 +4162,7 @@ static PyTypeObject CurlMulti_Type = {
* safely ignore any compiler warnings about missing initializers.
*/
};
+#endif
static int
are_global_init_flags_valid(int flags)
@@ -3539,7 +4224,7 @@ static PyObject *vi_str(const char *s)
Py_RETURN_NONE;
while (*s == ' ' || *s == '\t')
s++;
- return PyString_FromString(s);
+ return PyText_FromString(s);
}
static PyObject *
@@ -3605,23 +4290,27 @@ error:
/* Per function docstrings */
-static char pycurl_global_init_doc [] =
-"global_init(option) -> None. Initialize curl environment.\n";
+static const char pycurl_global_init_doc[] =
+ "global_init(option) -> None. "
+ "Initialize curl environment.\n";
-static char pycurl_global_cleanup_doc [] =
-"global_cleanup() -> None. Cleanup curl environment.\n";
+static const char pycurl_global_cleanup_doc[] =
+ "global_cleanup() -> None. "
+ "Cleanup curl environment.\n";
-static char pycurl_version_info_doc [] =
-"version_info() -> tuple. Returns a 12-tuple with the version info.\n";
+static const char pycurl_version_info_doc[] =
+ "version_info() -> tuple. "
+ "Returns a 12-tuple with the version info.\n";
-static char pycurl_share_new_doc [] =
-"CurlShare() -> New CurlShare object.";
+static const char pycurl_share_new_doc[] =
+ "CurlShare() -> New CurlShare object.";
-static char pycurl_curl_new_doc [] =
-"Curl() -> New curl object. Implicitly calls global_init() if not called.\n";
+static const char pycurl_curl_new_doc[] =
+ "Curl() -> New curl object. "
+ "Implicitly calls global_init() if not called.\n";
-static char pycurl_multi_new_doc [] =
-"CurlMulti() -> New curl multi-object.\n";
+static const char pycurl_multi_new_doc[] =
+ "CurlMulti() -> New curl multi-object.\n";
/* List of functions defined in this module */
@@ -3637,7 +4326,7 @@ static PyMethodDef curl_methods[] = {
/* Module docstring */
-static char module_doc [] =
+static const char module_doc [] =
"This module implements an interface to the cURL library.\n"
"\n"
"Types:\n"
@@ -3666,7 +4355,9 @@ insobj2(PyObject *dict1, PyObject *dict2, char *name, PyObject *value)
goto error;
if (value == NULL)
goto error;
- key = PyString_FromString(name);
+
+ key = PyText_FromString(name);
+
if (key == NULL)
goto error;
#if 0
@@ -3693,7 +4384,7 @@ error:
static void
insstr(PyObject *d, char *name, char *value)
{
- PyObject *v = PyString_FromString(value);
+ PyObject *v = PyText_FromString(value);
insobj2(d, NULL, name, v);
}
@@ -3726,6 +4417,35 @@ insint_m(PyObject *d, char *name, long value)
}
+/* Used in Python 3 only, and even then this function seems to never get
+ * called. Python 2 has no module cleanup:
+ * http://stackoverflow.com/questions/20741856/run-a-function-when-a-c-extension-module-is-freed-on-python-2
+ */
+void do_curlmod_free(void *unused) {
+ PyMem_Free(g_pycurl_useragent);
+ g_pycurl_useragent = NULL;
+}
+
+#if PY_MAJOR_VERSION >= 3
+static PyModuleDef curlmodule = {
+ PyModuleDef_HEAD_INIT,
+ "pycurl", /* m_name */
+ module_doc, /* m_doc */
+ -1, /* m_size */
+ curl_methods, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ do_curlmod_free /* m_free */
+};
+#endif
+
+
+#if PY_MAJOR_VERSION >= 3
+#define PYCURL_MODINIT_RETURN_NULL return NULL
+PyMODINIT_FUNC PyInit_pycurl(void)
+#else
+#define PYCURL_MODINIT_RETURN_NULL return
/* Initialization function for the module */
#if defined(PyMODINIT_FUNC)
PyMODINIT_FUNC
@@ -3736,9 +4456,41 @@ extern "C"
DL_EXPORT(void)
#endif
initpycurl(void)
+#endif
{
PyObject *m, *d;
const curl_version_info_data *vi;
+ const char *libcurl_version, *runtime_ssl_lib;
+ int libcurl_version_len, pycurl_version_len;
+
+ /* Check the version, as this has caused nasty problems in
+ * some cases. */
+ vi = curl_version_info(CURLVERSION_NOW);
+ if (vi == NULL) {
+ PyErr_SetString(PyExc_ImportError, "pycurl: curl_version_info() failed");
+ PYCURL_MODINIT_RETURN_NULL;
+ }
+ if (vi->version_num < LIBCURL_VERSION_NUM) {
+ PyErr_Format(PyExc_ImportError, "pycurl: libcurl link-time version (%s) is older than compile-time version (%s)", vi->version, LIBCURL_VERSION);
+ PYCURL_MODINIT_RETURN_NULL;
+ }
+
+ /* 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)) {
+ runtime_ssl_lib = "openssl";
+ } else if (!strncmp(vi->ssl_version, "GnuTLS/", 7)) {
+ runtime_ssl_lib = "gnutls";
+ } else if (!strncmp(vi->ssl_version, "NSS/", 4)) {
+ runtime_ssl_lib = "nss";
+ } else {
+ runtime_ssl_lib = "none/other";
+ }
+ if (strcmp(runtime_ssl_lib, COMPILE_SSL_LIB)) {
+ PyErr_Format(PyExc_ImportError, "pycurl: libcurl link-time ssl backend (%s) is different from compile-time ssl backend (%s)", runtime_ssl_lib, COMPILE_SSL_LIB);
+ PYCURL_MODINIT_RETURN_NULL;
+ }
/* Initialize the type of the new type objects here; doing it here
* is required for portability to Windows without requiring C++. */
@@ -3750,8 +4502,24 @@ initpycurl(void)
Py_TYPE(&CurlShare_Type) = &PyType_Type;
/* Create the module and add the functions */
+#if PY_MAJOR_VERSION >= 3
+ if (PyType_Ready(&Curl_Type) < 0)
+ return NULL;
+
+ if (PyType_Ready(&CurlMulti_Type) < 0)
+ return NULL;
+
+
+ m = PyModule_Create(&curlmodule);
+ if (m == NULL)
+ return NULL;
+
+ Py_INCREF(&Curl_Type);
+#else
+
m = Py_InitModule3("pycurl", curl_methods, module_doc);
assert(m != NULL && PyModule_Check(m));
+#endif
/* Add error object to the module */
d = PyModule_GetDict(m);
@@ -3764,7 +4532,23 @@ initpycurl(void)
assert(curlobject_constants != NULL);
/* Add version strings to the module */
- insstr(d, "version", curl_version());
+ libcurl_version = curl_version();
+ libcurl_version_len = strlen(libcurl_version);
+#define PYCURL_VERSION_PREFIX_SIZE sizeof(PYCURL_VERSION_PREFIX)
+ /* PYCURL_VERSION_PREFIX_SIZE includes terminating null which will be
+ * replaced with the space; libcurl_version_len does not include
+ * terminating null. */
+ pycurl_version_len = PYCURL_VERSION_PREFIX_SIZE + libcurl_version_len + 1;
+ g_pycurl_useragent = PyMem_Malloc(pycurl_version_len);
+ assert(g_pycurl_useragent != NULL);
+ memcpy(g_pycurl_useragent, PYCURL_VERSION_PREFIX, PYCURL_VERSION_PREFIX_SIZE);
+ g_pycurl_useragent[PYCURL_VERSION_PREFIX_SIZE-1] = ' ';
+ memcpy(g_pycurl_useragent + PYCURL_VERSION_PREFIX_SIZE,
+ libcurl_version, libcurl_version_len);
+ g_pycurl_useragent[pycurl_version_len - 1] = 0;
+#undef PYCURL_VERSION_PREFIX_SIZE
+
+ insobj2(d, NULL, "version", PyText_FromString(g_pycurl_useragent));
insstr(d, "COMPILE_DATE", __DATE__ " " __TIME__);
insint(d, "COMPILE_PY_VERSION_HEX", PY_VERSION_HEX);
insint(d, "COMPILE_LIBCURL_VERSION_NUM", LIBCURL_VERSION_NUM);
@@ -3775,9 +4559,7 @@ initpycurl(void)
/* Abort curl_read_callback(). */
insint_c(d, "READFUNC_ABORT", CURL_READFUNC_ABORT);
-#if LIBCURL_VERSION_NUM >= 0x071200 /* CURL_READFUNC_PAUSE appeared in libcurl 7.18.0 */
insint_c(d, "READFUNC_PAUSE", CURL_READFUNC_PAUSE);
-#endif
/* Pause curl_write_callback(). */
insint_c(d, "WRITEFUNC_PAUSE", CURL_WRITEFUNC_PAUSE);
@@ -3828,6 +4610,7 @@ initpycurl(void)
insint_c(d, "E_READ_ERROR", CURLE_READ_ERROR);
insint_c(d, "E_OUT_OF_MEMORY", CURLE_OUT_OF_MEMORY);
insint_c(d, "E_OPERATION_TIMEOUTED", CURLE_OPERATION_TIMEOUTED);
+ insint_c(d, "E_OPERATION_TIMEDOUT", CURLE_OPERATION_TIMEDOUT);
insint_c(d, "E_FTP_COULDNT_SET_ASCII", CURLE_FTP_COULDNT_SET_ASCII);
insint_c(d, "E_FTP_PORT_FAILED", CURLE_FTP_PORT_FAILED);
insint_c(d, "E_FTP_COULDNT_USE_REST", CURLE_FTP_COULDNT_USE_REST);
@@ -3887,6 +4670,9 @@ initpycurl(void)
insint_c(d, "HTTPAUTH_NONE", CURLAUTH_NONE);
insint_c(d, "HTTPAUTH_BASIC", CURLAUTH_BASIC);
insint_c(d, "HTTPAUTH_DIGEST", CURLAUTH_DIGEST);
+#ifdef HAVE_CURLAUTH_DIGEST_IE
+ insint_c(d, "HTTPAUTH_DIGEST_IE", CURLAUTH_DIGEST_IE);
+#endif
insint_c(d, "HTTPAUTH_GSSNEGOTIATE", CURLAUTH_GSSNEGOTIATE);
insint_c(d, "HTTPAUTH_NTLM", CURLAUTH_NTLM);
insint_c(d, "HTTPAUTH_ANY", CURLAUTH_ANY);
@@ -3904,6 +4690,8 @@ initpycurl(void)
insint_c(d, "FTPAUTH_TLS", CURLFTPAUTH_TLS);
/* curl_ftpauth: constants for setopt(FTPSSLAUTH, x) */
+ insint_c(d, "FORM_BUFFER", CURLFORM_BUFFER);
+ insint_c(d, "FORM_BUFFERPTR", CURLFORM_BUFFERPTR);
insint_c(d, "FORM_CONTENTS", CURLFORM_COPYCONTENTS);
insint_c(d, "FORM_FILE", CURLFORM_FILE);
insint_c(d, "FORM_CONTENTTYPE", CURLFORM_CONTENTTYPE);
@@ -4071,6 +4859,12 @@ initpycurl(void)
#ifdef HAVE_CURLOPT_CERTINFO
insint_c(d, "OPT_CERTINFO", CURLOPT_CERTINFO);
#endif
+#ifdef HAVE_CURLOPT_POSTREDIR
+ insint_c(d, "POSTREDIR", CURLOPT_POSTREDIR);
+#endif
+#ifdef HAVE_CURLOPT_NOPROXY
+ insint_c(d, "NOPROXY", CURLOPT_NOPROXY);
+#endif
insint_c(d, "M_TIMERFUNCTION", CURLMOPT_TIMERFUNCTION);
insint_c(d, "M_SOCKETFUNCTION", CURLMOPT_SOCKETFUNCTION);
@@ -4139,6 +4933,15 @@ initpycurl(void)
insint_c(d, "REDIRECT_COUNT", CURLINFO_REDIRECT_COUNT);
insint_c(d, "REDIRECT_URL", CURLINFO_REDIRECT_URL);
insint_c(d, "PRIMARY_IP", CURLINFO_PRIMARY_IP);
+#ifdef HAVE_CURLINFO_PRIMARY_PORT
+ insint_c(d, "PRIMARY_PORT", CURLINFO_PRIMARY_PORT);
+#endif
+#ifdef HAVE_CURLINFO_LOCAL_IP
+ insint_c(d, "LOCAL_IP", CURLINFO_LOCAL_IP);
+#endif
+#ifdef HAVE_CURLINFO_LOCAL_PORT
+ insint_c(d, "LOCAL_PORT", CURLINFO_LOCAL_PORT);
+#endif
insint_c(d, "HTTP_CONNECTCODE", CURLINFO_HTTP_CONNECTCODE);
insint_c(d, "HTTPAUTH_AVAIL", CURLINFO_HTTPAUTH_AVAIL);
insint_c(d, "PROXYAUTH_AVAIL", CURLINFO_PROXYAUTH_AVAIL);
@@ -4152,12 +4955,23 @@ initpycurl(void)
insint_c(d, "INFO_CERTINFO", CURLINFO_CERTINFO);
#endif
-#if LIBCURL_VERSION_NUM >= 0x071200 /* curl_easy_pause() appeared in libcurl 7.18.0 */
/* CURLPAUSE: symbolic constants for pause(bitmask) */
insint_c(d, "PAUSE_RECV", CURLPAUSE_RECV);
insint_c(d, "PAUSE_SEND", CURLPAUSE_SEND);
insint_c(d, "PAUSE_ALL", CURLPAUSE_ALL);
insint_c(d, "PAUSE_CONT", CURLPAUSE_CONT);
+
+#ifdef HAVE_CURLOPT_DNS_SERVERS
+ insint_c(d, "DNS_SERVERS", CURLOPT_DNS_SERVERS);
+#endif
+
+#ifdef HAVE_CURLOPT_POSTREDIR
+ insint_c(d, "REDIR_POST_301", CURL_REDIR_POST_301);
+ insint_c(d, "REDIR_POST_302", CURL_REDIR_POST_302);
+# ifdef HAVE_CURL_REDIR_POST_303
+ insint_c(d, "REDIR_POST_303", CURL_REDIR_POST_303);
+# endif
+ insint_c(d, "REDIR_POST_ALL", CURL_REDIR_POST_ALL);
#endif
/* options for global_init() */
@@ -4219,6 +5033,8 @@ initpycurl(void)
**/
/* CURLMcode: multi error codes */
+ curlmultiobject_constants = PyDict_New();
+ assert(curlmultiobject_constants != NULL);
insint_m(d, "E_CALL_MULTI_PERFORM", CURLM_CALL_MULTI_PERFORM);
insint_m(d, "E_MULTI_OK", CURLM_OK);
insint_m(d, "E_MULTI_BAD_HANDLE", CURLM_BAD_HANDLE);
@@ -4231,20 +5047,10 @@ initpycurl(void)
assert(curlshareobject_constants != NULL);
insint_s(d, "SH_SHARE", CURLSHOPT_SHARE);
insint_s(d, "SH_UNSHARE", CURLSHOPT_UNSHARE);
+
insint_s(d, "LOCK_DATA_COOKIE", CURL_LOCK_DATA_COOKIE);
insint_s(d, "LOCK_DATA_DNS", CURL_LOCK_DATA_DNS);
-
- /* Check the version, as this has caused nasty problems in
- * some cases. */
- vi = curl_version_info(CURLVERSION_NOW);
- if (vi == NULL) {
- Py_FatalError("pycurl: curl_version_info() failed");
- assert(0);
- }
- if (vi->version_num < LIBCURL_VERSION_NUM) {
- Py_FatalError("pycurl: libcurl link-time version is older than compile-time version");
- assert(0);
- }
+ insint_s(d, "LOCK_DATA_SSL_SESSION", CURL_LOCK_DATA_SSL_SESSION);
/* Initialize callback locks if ssl is enabled */
#if defined(PYCURL_NEED_SSL_TSL)
@@ -4256,7 +5062,43 @@ initpycurl(void)
PyEval_InitThreads();
#endif
+#if PY_MAJOR_VERSION >= 3
+ return m;
+#endif
}
+#if defined(WIN32) && ((_WIN32_WINNT < 0x0600) || (NTDDI_VERSION < NTDDI_VISTA))
+/*
+ * Only Winsock on Vista+ has inet_ntop().
+ */
+static const char * pycurl_inet_ntop (int family, void *addr, char *string, size_t string_size)
+{
+ SOCKADDR *sa;
+ int sa_len;
+
+ if (family == AF_INET6) {
+ struct sockaddr_in6 sa6;
+ memset(&sa6, 0, sizeof(sa6));
+ sa6.sin6_family = AF_INET6;
+ memcpy(&sa6.sin6_addr, addr, sizeof(sa6.sin6_addr));
+ sa = (SOCKADDR*) &sa6;
+ sa_len = sizeof(sa6);
+ } else if (family == AF_INET) {
+ struct sockaddr_in sa4;
+ memset(&sa4, 0, sizeof(sa4));
+ sa4.sin_family = AF_INET;
+ memcpy(&sa4.sin_addr, addr, sizeof(sa4.sin_addr));
+ sa = (SOCKADDR*) &sa4;
+ sa_len = sizeof(sa4);
+ } else {
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+ if (WSAAddressToString(sa, sa_len, NULL, string, &string_size))
+ return NULL;
+ return string;
+}
+#endif
+
/* vi:ts=4:et:nowrap
*/
diff --git a/tests/__init__.py b/tests/__init__.py
index c1ff976..840f94c 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,4 +1,8 @@
-import pycurl
-
def setup_package():
+ # import here, not globally, so that running
+ # python -m tests.appmanager
+ # to launch the app manager is possible without having pycurl installed
+ # (as the test app does not depend on pycurl)
+ import pycurl
+
print('Testing %s' % pycurl.version)
diff --git a/tests/app.py b/tests/app.py
index b44e066..78a2064 100644
--- a/tests/app.py
+++ b/tests/app.py
@@ -1,10 +1,12 @@
-import time as _time
+import time as _time, sys
import bottle
try:
import json
except ImportError:
import simplejson as json
+py3 = sys.version_info[0] == 3
+
app = bottle.Bottle()
app.debug = True
@@ -12,6 +14,11 @@ app.debug = True
def ok():
return 'success'
+ at app.route('/short_wait')
+def ok():
+ _time.sleep(0.1)
+ return 'success'
+
@app.route('/status/403')
def forbidden():
return bottle.HTTPResponse('forbidden', 403)
@@ -24,6 +31,11 @@ def not_found():
def postfields():
return json.dumps(dict(bottle.request.forms))
+ at app.route('/raw_utf8', method='post')
+def raw_utf8():
+ data = bottle.request.body.getvalue().decode('utf8')
+ return json.dumps(data)
+
# XXX file is not a bottle FileUpload instance, but FieldStorage?
def convert_file(key, file):
return {
@@ -40,7 +52,7 @@ def convert_file(key, file):
return {
'name': file.name,
'filename': file.filename,
- 'data': file.file.read(),
+ 'data': file.file.read().decode(),
}
@app.route('/files', method='post')
@@ -48,6 +60,36 @@ def files():
files = [convert_file(key, bottle.request.files[key]) for key in bottle.request.files]
return json.dumps(files)
+ at app.route('/header')
+def header():
+ return bottle.request.headers[bottle.request.query['h']]
+
+# This is a hacky endpoint to test non-ascii text being given to libcurl
+# via headers.
+# HTTP RFC requires headers to be latin1-encoded.
+# Any string can be decoded as latin1; here we encode the header value
+# back into latin1 to obtain original bytestring, then decode it in utf-8.
+# Thanks to bdarnell for the idea: https://github.com/pycurl/pycurl/issues/124
+ at app.route('/header_utf8')
+def header():
+ header_value = bottle.request.headers[bottle.request.query['h']]
+ if py3:
+ # header_value is a string, headers are decoded in latin1
+ header_value = header_value.encode('latin1').decode('utf8')
+ else:
+ # header_value is a binary string, decode in utf-8 directly
+ header_value = header_value.decode('utf8')
+ return header_value
+
+ at app.route('/param_utf8_hack', method='post')
+def param_utf8_hack():
+ param = bottle.request.forms['p']
+ if py3:
+ # python 3 decodes bytes as latin1 perhaps?
+ # apply the latin1-utf8 hack
+ param = param.encode('latin').decode('utf8')
+ return param
+
def pause_writer():
yield 'part1'
_time.sleep(0.5)
diff --git a/tests/certinfo_test.py b/tests/certinfo_test.py
index 73a238a..26d669d 100644
--- a/tests/certinfo_test.py
+++ b/tests/certinfo_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -18,45 +18,42 @@ class CertinfoTest(unittest.TestCase):
def tearDown(self):
self.curl.close()
+ # CURLOPT_CERTINFO was introduced in libcurl-7.19.1
+ @util.min_libcurl(7, 19, 1)
def test_certinfo_option(self):
- # CURLOPT_CERTINFO was introduced in libcurl-7.19.1
- if util.pycurl_version_less_than(7, 19, 1):
- raise nose.plugins.skip.SkipTest('libcurl < 7.19.1')
-
assert hasattr(pycurl, 'OPT_CERTINFO')
+ # CURLOPT_CERTINFO was introduced in libcurl-7.19.1
+ @util.min_libcurl(7, 19, 1)
+ @util.only_ssl
def test_request_without_certinfo(self):
- # CURLOPT_CERTINFO was introduced in libcurl-7.19.1
- if util.pycurl_version_less_than(7, 19, 1):
- raise nose.plugins.skip.SkipTest('libcurl < 7.19.1')
-
self.curl.setopt(pycurl.URL, 'https://localhost:8383/success')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
# self signed certificate
self.curl.setopt(pycurl.SSL_VERIFYPEER, 0)
self.curl.perform()
- assert sio.getvalue() == 'success'
+ assert sio.getvalue().decode() == 'success'
certinfo = self.curl.getinfo(pycurl.INFO_CERTINFO)
self.assertEqual([], certinfo)
+ # CURLOPT_CERTINFO was introduced in libcurl-7.19.1
+ @util.min_libcurl(7, 19, 1)
+ @util.only_ssl
def test_request_with_certinfo(self):
- # CURLOPT_CERTINFO was introduced in libcurl-7.19.1
- if util.pycurl_version_less_than(7, 19, 1):
- raise nose.plugins.skip.SkipTest('libcurl < 7.19.1')
# 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.StringIO()
+ 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() == 'success'
+ assert sio.getvalue().decode() == 'success'
certinfo = self.curl.getinfo(pycurl.INFO_CERTINFO)
# self signed certificate, one certificate in chain
@@ -67,4 +64,4 @@ class CertinfoTest(unittest.TestCase):
for entry in certinfo:
certinfo_dict[entry[0]] = entry[1]
assert 'Subject' in certinfo_dict
- assert 'pycurl test suite' in certinfo_dict['Subject']
+ assert 'PycURL test suite' in certinfo_dict['Subject']
diff --git a/tests/certs/server.crt b/tests/certs/server.crt
index 4a8decc..f904889 100644
--- a/tests/certs/server.crt
+++ b/tests/certs/server.crt
@@ -1,14 +1,14 @@
-----BEGIN CERTIFICATE-----
-MIICJTCCAY4CCQDfQAHGuFkN2zANBgkqhkiG9w0BAQUFADBXMQswCQYDVQQGEwJB
-VTETMBEGA1UECBMKU29tZS1TdGF0ZTEaMBgGA1UEChMRcHljdXJsIHRlc3Qgc3Vp
-dGUxFzAVBgNVBAMTDmxvY2FsaG9zdDo4MzgzMB4XDTEzMDcyNDAwMDgxNVoXDTE0
-MDcyNDAwMDgxNVowVzELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
-GjAYBgNVBAoTEXB5Y3VybCB0ZXN0IHN1aXRlMRcwFQYDVQQDEw5sb2NhbGhvc3Q6
-ODM4MzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxE0+59Kf2z9ccZyUAuKG
-QpkQaXtEJC13exY4SWIfr78FfCStdqpZdfmm66djFENhmaAZYGsPHGXrEIHQqja2
-7KYkHo4cXLxksR4Db01yPMtMU9xHzg37OTIS2lGRmMxLduKc5XKsxA98PV/D1k4k
-sqLcLDH//YdLR0iYUYLOIgMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAppFdMNMHe
-68uQA1y2xAYW7faUH8/g+XAuH9WjLL2QfWGXgWey/pwofsrTO2Hl+D9y8Rey4eJ/
-BDv3OV2cBWBYBOxZv/kqyDHQc38tho9gdaPQnD4ttFk2TSgaOs1W39pGY1On0Ejd
-O6CXEGV7p8C613AgEkbdudnn+ChvyH/Shw==
+MIICGzCCAYQCCQCdeJzNRLLLvDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEaMBgGA1UEChMRUHljVVJMIHRlc3Qgc3Vp
+dGUxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMzEyMjIxMzA0MTVaFw0xNzA5MTcx
+MzA0MTVaMFIxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRowGAYD
+VQQKExFQeWNVUkwgdGVzdCBzdWl0ZTESMBAGA1UEAxMJbG9jYWxob3N0MIGfMA0G
+CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDETT7n0p/bP1xxnJQC4oZCmRBpe0QkLXd7
+FjhJYh+vvwV8JK12qll1+abrp2MUQ2GZoBlgaw8cZesQgdCqNrbspiQejhxcvGSx
+HgNvTXI8y0xT3EfODfs5MhLaUZGYzEt24pzlcqzED3w9X8PWTiSyotwsMf/9h0tH
+SJhRgs4iAwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAGhX0iIMCrRcI6T6S4YydkXf
+7LrXZpjSJwVGSCd5ehx3Qvc1LQNJjpB68F0MhVTqbDP1o3CAHaTa2s/8NC9j3tV7
+bUynoKJT3srpHisfdd/SV538mWvFDtGRctbmmqp8qT4On+kr76dKj+/d3HyfOKIK
+Aasa7ODxFKbbY542yYHu
-----END CERTIFICATE-----
diff --git a/tests/curlopt_test.py b/tests/curlopt_test.py
deleted file mode 100644
index fd30394..0000000
--- a/tests/curlopt_test.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
-# vi:ts=4:et
-
-import pycurl
-import unittest
-import nose.plugins.skip
-
-from . import util
-
-class CurloptTest(unittest.TestCase):
- def test_username(self):
- # CURLOPT_USERNAME was introduced in libcurl-7.19.1
- if util.pycurl_version_less_than(7, 19, 1):
- raise nose.plugins.skip.SkipTest('libcurl < 7.19.1')
-
- assert hasattr(pycurl, 'USERNAME')
- assert hasattr(pycurl, 'PASSWORD')
- assert hasattr(pycurl, 'PROXYUSERNAME')
- assert hasattr(pycurl, 'PROXYPASSWORD')
diff --git a/tests/debug_test.py b/tests/debug_test.py
index e6303c2..5c273ce 100644
--- a/tests/debug_test.py
+++ b/tests/debug_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -25,12 +25,12 @@ class DebugTest(unittest.TestCase):
self.curl.setopt(pycurl.VERBOSE, 1)
self.curl.setopt(pycurl.DEBUGFUNCTION, self.debug_function)
self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.perform()
# Some checks with no particular intent
- self.check(0, 'About to connect')
+ self.check(0, 'Trying')
if util.pycurl_version_less_than(7, 24):
self.check(0, 'connected')
else:
diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py
index cdf848b..c552568 100644
--- a/tests/default_write_function_test.py
+++ b/tests/default_write_function_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import unittest
diff --git a/tests/easy_test.py b/tests/easy_test.py
new file mode 100644
index 0000000..a3d4539
--- /dev/null
+++ b/tests/easy_test.py
@@ -0,0 +1,16 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import unittest
+
+class EasyTest(unittest.TestCase):
+ def test_easy_close(self):
+ c = pycurl.Curl()
+ c.close()
+
+ def test_easy_close_twice(self):
+ c = pycurl.Curl()
+ c.close()
+ c.close()
diff --git a/tests/error_test.py b/tests/error_test.py
new file mode 100644
index 0000000..32a5add
--- /dev/null
+++ b/tests/error_test.py
@@ -0,0 +1,82 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import sys
+import unittest
+
+class ErrorTest(unittest.TestCase):
+ def setUp(self):
+ self.curl = pycurl.Curl()
+
+ def tearDown(self):
+ self.curl.close()
+
+ # error originating in libcurl
+ def test_pycurl_error_libcurl(self):
+ try:
+ # perform without a url
+ self.curl.perform()
+ except pycurl.error:
+ exc_type, exc = sys.exc_info()[:2]
+ assert exc_type == pycurl.error
+ # pycurl.error's arguments are libcurl errno and message
+ self.assertEqual(2, len(exc.args))
+ self.assertEqual(int, type(exc.args[0]))
+ self.assertEqual(str, type(exc.args[1]))
+ # unpack
+ err, msg = exc.args
+ self.assertEqual(pycurl.E_URL_MALFORMAT, err)
+ # possibly fragile
+ self.assertEqual('No URL set!', msg)
+ else:
+ self.fail('Expected pycurl.error to be raised')
+
+ def test_pycurl_errstr_initially_empty(self):
+ self.assertEqual('', self.curl.errstr())
+
+ def test_pycurl_errstr_type(self):
+ self.assertEqual('', self.curl.errstr())
+ try:
+ # perform without a url
+ self.curl.perform()
+ except pycurl.error:
+ # might be fragile
+ self.assertEqual('No URL set!', self.curl.errstr())
+ # repeated checks do not clear value
+ self.assertEqual('No URL set!', self.curl.errstr())
+ # check the type - on all python versions
+ self.assertEqual(str, type(self.curl.errstr()))
+ else:
+ self.fail('no exception')
+
+ # pycurl raises standard library exceptions in some cases
+ def test_pycurl_error_stdlib(self):
+ try:
+ # set an option of the wrong type
+ self.curl.setopt(pycurl.WRITEFUNCTION, True)
+ except TypeError:
+ exc_type, exc = sys.exc_info()[:2]
+ else:
+ self.fail('Expected TypeError to be raised')
+
+ # error originating in pycurl
+ def test_pycurl_error_pycurl(self):
+ try:
+ # invalid option combination
+ self.curl.setopt(pycurl.WRITEFUNCTION, lambda x: x)
+ f = open(__file__)
+ try:
+ self.curl.setopt(pycurl.WRITEHEADER, f)
+ finally:
+ f.close()
+ except pycurl.error:
+ exc_type, exc = sys.exc_info()[:2]
+ assert exc_type == pycurl.error
+ # for non-libcurl errors, arguments are just the error string
+ self.assertEqual(1, len(exc.args))
+ self.assertEqual(str, type(exc.args[0]))
+ self.assertEqual('cannot combine WRITEHEADER with WRITEFUNCTION.', exc.args[0])
+ else:
+ self.fail('Expected pycurl.error to be raised')
diff --git a/tests/ext/test-lib.sh b/tests/ext/test-lib.sh
new file mode 100644
index 0000000..0cb9489
--- /dev/null
+++ b/tests/ext/test-lib.sh
@@ -0,0 +1,69 @@
+# shell test framework based on test framework in rpg:
+# https://github.com/rtomayko/rpg
+#
+# Copyright (c) 2010 Ryan Tomayko <http://tomayko.com/about>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+: ${VERBOSE:=false}
+
+unset CDPATH
+
+#cd "$(dirname $0)"
+if test -z "$TESTDIR"; then
+ TESTDIR=$(realpath $(pwd))
+fi
+
+test_count=0
+successes=0
+failures=0
+
+output="$TESTDIR/$(basename "$0" .sh).out"
+trap "rm -f $output" 0
+
+succeeds () {
+ test_count=$(( test_count + 1 ))
+ echo "\$ ${2:-$1}" > "$output"
+ eval "( ${2:-$1} )" 1>>"$output" 2>&1
+ ec=$?
+ if test $ec -eq 0
+ then successes=$(( successes + 1 ))
+ printf 'ok %d - %s\n' $test_count "$1"
+ else failures=$(( failures + 1 ))
+ printf 'not ok %d - %s [%d]\n' $test_count "$1" "$ec"
+ fi
+
+ $VERBOSE && dcat $output
+ return 0
+}
+
+fails () {
+ if test $# -eq 1
+ then succeeds "! $1"
+ else succeeds "$1" "! $2"
+ fi
+}
+
+diag () { echo "$@" | sed 's/^/# /'; }
+dcat () { cat "$@" | sed 's/^/# /'; }
+desc () { diag "$@"; }
+
+setup () {
+ rm -rf "$TESTDIR/trash"
+ return 0
+}
diff --git a/tests/ext/test-suite.sh b/tests/ext/test-suite.sh
new file mode 100755
index 0000000..27a868b
--- /dev/null
+++ b/tests/ext/test-suite.sh
@@ -0,0 +1,25 @@
+#
+
+dir=$(dirname "$0")
+
+. "$dir"/test-lib.sh
+
+setup
+
+desc 'setup.py without arguments'
+fails 'python setup.py'
+succeeds 'python setup.py 2>&1 |grep "usage: setup.py"'
+
+desc 'setup.py --help'
+succeeds 'python setup.py --help'
+# .* = Unix|Windows
+succeeds 'python setup.py --help |grep "PycURL .* options:"'
+# distutils help
+succeeds 'python setup.py --help |grep "Common commands:"'
+
+desc 'setup.py --help with bogus --curl-config'
+succeeds 'python setup.py --help --curl-config=/dev/null'
+succeeds 'python setup.py --help --curl-config=/dev/null |grep "PycURL .* options:"'
+# this checks that --curl-config is consumed prior to
+# distutils processing --help
+fails 'python setup.py --help --curl-config=/dev/null 2>&1 |grep "option .* not recognized"'
diff --git a/tests/ftp_test.py b/tests/ftp_test.py
index 5ee380c..a88fc44 100644
--- a/tests/ftp_test.py
+++ b/tests/ftp_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
# Note: this test is meant to be run from pycurl project root.
@@ -21,33 +21,33 @@ class FtpTest(unittest.TestCase):
def test_get_ftp(self):
self.curl.setopt(pycurl.URL, 'ftp://localhost:8321')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.perform()
- result = sio.getvalue()
+ result = sio.getvalue().decode()
assert 'README.rst' in result
assert 'INSTALL' in result
# XXX this test needs to be fixed
def test_quote(self):
self.curl.setopt(pycurl.URL, 'ftp://localhost:8321')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.setopt(pycurl.QUOTE, ['CWD tests'])
self.curl.perform()
- result = sio.getvalue()
+ result = sio.getvalue().decode()
assert 'README.rst' not in result
assert 'ftp_test.py' in result
def test_epsv(self):
self.curl.setopt(pycurl.URL, 'ftp://localhost:8321')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.setopt(pycurl.FTP_USE_EPSV, 1)
self.curl.perform()
- result = sio.getvalue()
+ result = sio.getvalue().decode()
assert 'README.rst' in result
assert 'INSTALL' in result
diff --git a/tests/functools_backport.py b/tests/functools_backport.py
new file mode 100644
index 0000000..8e63500
--- /dev/null
+++ b/tests/functools_backport.py
@@ -0,0 +1,58 @@
+# 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 <ncoghlan at gmail.com>
+# 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 --git a/tests/getinfo_test.py b/tests/getinfo_test.py
index 7df1287..8632172 100644
--- a/tests/getinfo_test.py
+++ b/tests/getinfo_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -18,11 +18,7 @@ class GetinfoTest(unittest.TestCase):
self.curl.close()
def test_getinfo(self):
- self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
- sio = util.StringIO()
- self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
- self.curl.perform()
- self.assertEqual('success', sio.getvalue())
+ self.make_request()
self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE))
assert type(self.curl.getinfo(pycurl.TOTAL_TIME)) is float
@@ -40,3 +36,17 @@ class GetinfoTest(unittest.TestCase):
self.assertEqual(0, self.curl.getinfo(pycurl.REDIRECT_COUNT))
# time not requested
self.assertEqual(-1, self.curl.getinfo(pycurl.INFO_FILETIME))
+
+ @util.min_libcurl(7, 21, 0)
+ def test_primary_port_etc(self):
+ self.make_request()
+ assert type(self.curl.getinfo(pycurl.PRIMARY_PORT)) is int
+ 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')
+ sio = util.BytesIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ self.curl.perform()
+ self.assertEqual('success', sio.getvalue().decode())
diff --git a/tests/global_init_test.py b/tests/global_init_test.py
index b0d1986..27dab76 100644
--- a/tests/global_init_test.py
+++ b/tests/global_init_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
diff --git a/tests/header_function_test.py b/tests/header_function_test.py
index ae1062f..0eac72c 100644
--- a/tests/header_function_test.py
+++ b/tests/header_function_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -20,15 +20,15 @@ class HeaderFunctionTest(unittest.TestCase):
self.curl.close()
def header_function(self, line):
- self.header_lines.append(line)
+ self.header_lines.append(line.decode())
def test_get(self):
self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.setopt(pycurl.HEADERFUNCTION, self.header_function)
self.curl.perform()
- self.assertEqual('success', sio.getvalue())
+ self.assertEqual('success', sio.getvalue().decode())
assert len(self.header_lines) > 0
self.assertEqual("HTTP/1.0 200 OK\r\n", self.header_lines[0])
diff --git a/tests/header_test.py b/tests/header_test.py
new file mode 100644
index 0000000..d0cc870
--- /dev/null
+++ b/tests/header_test.py
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import unittest
+import nose.tools
+
+from . import appmanager
+from . import util
+
+setup_module, teardown_module = appmanager.setup(('app', 8380))
+
+# NB: HTTP RFC requires headers to be latin1 encoded, which we violate.
+# See the comments under /header_utf8 route in app.py.
+
+class HeaderTest(unittest.TestCase):
+ def setUp(self):
+ self.curl = pycurl.Curl()
+
+ def tearDown(self):
+ self.curl.close()
+
+ def test_ascii_string_header(self):
+ self.check('x-test-header: ascii', 'ascii')
+
+ def test_ascii_unicode_header(self):
+ self.check(util.u('x-test-header: ascii'), 'ascii')
+
+ # on python 2 unicode is accepted in strings because strings are byte strings
+ @util.only_python3
+ @nose.tools.raises(UnicodeEncodeError)
+ def test_unicode_string_header(self):
+ self.check('x-test-header: Москва', 'Москва')
+
+ @nose.tools.raises(UnicodeEncodeError)
+ def test_unicode_unicode_header(self):
+ self.check(util.u('x-test-header: Москва'), util.u('Москва'))
+
+ def test_encoded_unicode_header(self):
+ self.check(util.u('x-test-header: Москва').encode('utf-8'), util.u('Москва'))
+
+ def check(self, send, expected):
+ self.curl.setopt(pycurl.URL, 'http://localhost:8380/header_utf8?h=x-test-header')
+ sio = util.BytesIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ self.curl.setopt(pycurl.HTTPHEADER, [send])
+ self.curl.perform()
+ self.assertEqual(expected, sio.getvalue().decode('utf-8'))
diff --git a/tests/internals_test.py b/tests/internals_test.py
index 0133da0..7e26ae0 100644
--- a/tests/internals_test.py
+++ b/tests/internals_test.py
@@ -1,18 +1,18 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
import unittest
-from .util import StringIO
try:
import cPickle
except ImportError:
cPickle = None
import pickle
-import gc
import copy
+from . import util
+
class InternalsTest(unittest.TestCase):
def setUp(self):
self.curl = pycurl.Curl()
@@ -117,7 +117,7 @@ class InternalsTest(unittest.TestCase):
assert False, "No exception when trying to copy a CurlMulti handle"
def test_pickle_curl(self):
- fp = StringIO()
+ fp = util.StringIO()
p = pickle.Pickler(fp, 1)
try:
p.dump(self.curl)
@@ -130,7 +130,7 @@ class InternalsTest(unittest.TestCase):
def test_pickle_multi(self):
m = pycurl.CurlMulti()
- fp = StringIO()
+ fp = util.StringIO()
p = pickle.Pickler(fp, 1)
try:
p.dump(m)
@@ -142,7 +142,7 @@ class InternalsTest(unittest.TestCase):
if cPickle is not None:
def test_cpickle_curl(self):
- fp = StringIO()
+ fp = util.StringIO()
p = cPickle.Pickler(fp, 1)
try:
p.dump(self.curl)
@@ -154,7 +154,7 @@ class InternalsTest(unittest.TestCase):
def test_cpickle_multi(self):
m = pycurl.CurlMulti()
- fp = StringIO()
+ fp = util.StringIO()
p = cPickle.Pickler(fp, 1)
try:
p.dump(m)
@@ -163,66 +163,3 @@ class InternalsTest(unittest.TestCase):
else:
assert 0, "No exception when trying to pickle a CurlMulti handle via cPickle"
del m, fp, p
-
- # /***********************************************************************
- # // test refcounts
- # ************************************************************************/
-
- # basic check of reference counting (use a memory checker like valgrind)
- def test_reference_counting(self):
- c = pycurl.Curl()
- m = pycurl.CurlMulti()
- m.add_handle(c)
- del m
- m = pycurl.CurlMulti()
- c.close()
- del m, c
-
- def test_cyclic_gc(self):
- gc.collect()
- c = pycurl.Curl()
- c.m = pycurl.CurlMulti()
- c.m.add_handle(c)
- # create some nasty cyclic references
- c.c = c
- c.c.c1 = c
- c.c.c2 = c
- c.c.c3 = c.c
- c.c.c4 = c.m
- c.m.c = c
- c.m.m = c.m
- c.m.c = c
- # delete
- gc.collect()
- flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE
- # python 3 has no DEBUG_OBJECTS
- #if hasattr(gc, 'DEBUG_OBJECTS'):
- #flags |= gc.DEBUG_OBJECTS
- #if opts.verbose >= 1:
- #flags = flags | gc.DEBUG_STATS
- gc.set_debug(flags)
- gc.collect()
- ##print gc.get_referrers(c)
- ##print gc.get_objects()
- #if opts.verbose >= 1:
- #print("Tracked objects:", len(gc.get_objects()))
- c_id = id(c)
- # The `del' below should delete these 4 objects:
- # Curl + internal dict, CurlMulti + internal dict
- del c
- gc.collect()
- objects = gc.get_objects()
- for object in objects:
- assert id(object) != c_id
- #if opts.verbose >= 1:
- #print("Tracked objects:", len(gc.get_objects()))
-
- def test_refcounting_bug_in_reset(self):
- try:
- range_generator = xrange
- except NameError:
- range_generator = range
- # Ensure that the refcounting error in "reset" is fixed:
- for i in range_generator(100000):
- c = pycurl.Curl()
- c.reset()
diff --git a/tests/matrix.py b/tests/matrix.py
index 4bb613b..afc5b23 100644
--- a/tests/matrix.py
+++ b/tests/matrix.py
@@ -1,12 +1,20 @@
-import os, os.path, urllib, subprocess, shutil, re
+import os, os.path, subprocess, shutil, re
-python_versions = ['2.4.6', '2.5.6', '2.6.8', '2.7.5']
-libcurl_versions = ['7.19.0', '7.32.0']
+try:
+ from urllib.request import urlopen
+except ImportError:
+ from urllib import urlopen
+
+python_versions = ['2.4.6', '2.5.6', '2.6.8', '2.7.5', '3.0.1', '3.1.5', '3.2.5', '3.3.3']
+libcurl_versions = ['7.19.0', '7.33.0']
python_meta = {
'2.5.6': {
'patches': ['python25.patch'],
},
+ '3.0.1': {
+ 'patches': ['python25.patch', 'python30.patch'],
+ },
}
root = os.path.abspath(os.path.dirname(__file__))
@@ -22,11 +30,13 @@ class in_dir:
def __exit__(self, type, value, traceback):
os.chdir(self.oldwd)
-def fetch(url, archive):
+def fetch(url, archive=None):
+ if archive is None:
+ archive = os.path.basename(url)
if not os.path.exists(archive):
- print "Fetching %s" % url
- io = urllib.urlopen(url)
- with open('.tmp.%s' % archive, 'w') as f:
+ sys.stdout.write("Fetching %s\n" % url)
+ io = urlopen(url)
+ with open('.tmp.%s' % archive, 'wb') as f:
while True:
chunk = io.read(65536)
if len(chunk) == 0:
@@ -36,7 +46,7 @@ def fetch(url, archive):
def build(archive, dir, prefix, meta=None):
if not os.path.exists(dir):
- print "Building %s" % archive
+ sys.stdout.write("Building %s\n" % archive)
subprocess.check_call(['tar', 'xf', archive])
with in_dir(dir):
if meta and 'patches' in meta:
@@ -54,14 +64,14 @@ def patch_pycurl_for_24():
for file in files:
if file.endswith('.py'):
path = os.path.join(root, file)
- with open(path, 'rb') as f:
+ with open(path, 'r') as f:
contents = f.read()
contents = re.compile(r'^(\s*)from \. import', re.M).sub(r'\1import', contents)
contents = re.compile(r'^(\s*)from \.(\w+) import', re.M).sub(r'\1from \2 import', contents)
- with open(path, 'wb') as f:
+ with open(path, 'w') as f:
f.write(contents)
-def run_matrix():
+def run_matrix(python_versions, libcurl_versions):
for python_version in python_versions:
url = 'http://www.python.org/ftp/python/%s/Python-%s.tgz' % (python_version, python_version)
archive = os.path.basename(url)
@@ -81,12 +91,13 @@ def run_matrix():
build(archive, dir, prefix)
fetch('https://raw.github.com/pypa/virtualenv/1.7/virtualenv.py', 'virtualenv-1.7.py')
+ fetch('https://raw.github.com/pypa/virtualenv/1.9.1/virtualenv.py', 'virtualenv-1.9.1.py')
if not os.path.exists('venv'):
os.mkdir('venv')
for python_version in python_versions:
- python_version_pieces = map(int, python_version.split('.')[:2])
+ python_version_pieces = [int(piece) for piece in python_version.split('.')[:2]]
for libcurl_version in libcurl_versions:
python_prefix = os.path.abspath('i/Python-%s' % python_version)
libcurl_prefix = os.path.abspath('i/curl-%s' % libcurl_version)
@@ -94,9 +105,22 @@ def run_matrix():
if os.path.exists(venv):
shutil.rmtree(venv)
if python_version_pieces >= [2, 5]:
- subprocess.check_call(['virtualenv', venv, '-p', '%s/bin/python' % python_prefix, '--no-site-packages'])
+ fetch('https://pypi.python.org/packages/2.5/s/setuptools/setuptools-0.6c11-py2.5.egg')
+ fetch('https://pypi.python.org/packages/2.6/s/setuptools/setuptools-0.6c11-py2.6.egg')
+ fetch('https://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg')
+ # I had virtualenv 1.8.2 installed systemwide which
+ # did not work with python 3.0:
+ # http://stackoverflow.com/questions/14224361/why-am-i-getting-this-error-related-to-pip-and-easy-install-when-trying-to-set
+ # so, use known versions everywhere
+ # md5=89e68df89faf1966bcbd99a0033fbf8e
+ fetch('https://pypi.python.org/packages/source/d/distribute/distribute-0.6.49.tar.gz')
+ subprocess.check_call(['python', 'virtualenv-1.9.1.py', venv, '-p', '%s/bin/python%d.%d' % (python_prefix, python_version_pieces[0], python_version_pieces[1]), '--no-site-packages', '--never-download'])
else:
- subprocess.check_call(['python', 'virtualenv-1.7.py', venv, '-p', '%s/bin/python' % python_prefix, '--no-site-packages'])
+ # md5=bd639f9b0eac4c42497034dec2ec0c2b
+ fetch('https://pypi.python.org/packages/2.4/s/setuptools/setuptools-0.6c11-py2.4.egg')
+ # md5=6afbb46aeb48abac658d4df742bff714
+ fetch('https://pypi.python.org/packages/source/p/pip/pip-1.4.1.tar.gz')
+ subprocess.check_call(['python', 'virtualenv-1.7.py', venv, '-p', '%s/bin/python' % python_prefix, '--no-site-packages', '--never-download'])
curl_config_path = os.path.join(libcurl_prefix, 'bin/curl-config')
curl_lib_path = os.path.join(libcurl_prefix, 'lib')
with in_dir('pycurl'):
@@ -133,7 +157,24 @@ def run_matrix():
if __name__ == '__main__':
import sys
+ def main():
+ import optparse
+
+ parser = optparse.OptionParser()
+ parser.add_option('-p', '--python', help='Specify python version to test against')
+ parser.add_option('-c', '--curl', help='Specify libcurl version to test against')
+ options, args = parser.parse_args()
+ if options.python:
+ chosen_python_versions = [options.python]
+ else:
+ chosen_python_versions = python_versions
+ if options.curl:
+ chosen_libcurl_versions = [options.curl]
+ else:
+ chosen_libcurl_versions = libcurl_versions
+ run_matrix(chosen_python_versions, chosen_libcurl_versions)
+
if len(sys.argv) > 1 and sys.argv[1] == 'patch-24':
patch_pycurl_for_24()
else:
- run_matrix()
+ main()
diff --git a/tests/matrix/curl-7.19.0-sslv2-c66b0b32fba-modified.patch b/tests/matrix/curl-7.19.0-sslv2-c66b0b32fba-modified.patch
new file mode 100644
index 0000000..7a15292
--- /dev/null
+++ b/tests/matrix/curl-7.19.0-sslv2-c66b0b32fba-modified.patch
@@ -0,0 +1,18 @@
+diff --git a/lib/ssluse.c b/lib/ssluse.c
+index cee78bb..967fc57 100644
+--- a/lib/ssluse.c
++++ b/lib/ssluse.c
+@@ -1327,8 +1327,13 @@ ossl_connect_step1(struct connectdata *conn,
+ req_method = TLSv1_client_method();
+ break;
+ case CURL_SSLVERSION_SSLv2:
++#ifdef OPENSSL_NO_SSL2
++ failf(data, "OpenSSL was built without SSLv2 support");
++ return CURLE_UNSUPPORTED_PROTOCOL /* CURLE_NOT_BUILT_IN not defined in 7.19.0 */;
++#else
+ req_method = SSLv2_client_method();
+ break;
++#endif
+ case CURL_SSLVERSION_SSLv3:
+ req_method = SSLv3_client_method();
+ break;
diff --git a/tests/matrix/python30.patch b/tests/matrix/python30.patch
new file mode 100644
index 0000000..8b067a7
--- /dev/null
+++ b/tests/matrix/python30.patch
@@ -0,0 +1,19 @@
+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 --git a/tests/memleak_test.py b/tests/memleak_test.py
deleted file mode 100644
index 1b1bbd5..0000000
--- a/tests/memleak_test.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
-# vi:ts=4:et
-
-import pycurl
-import unittest
-import gc
-
-class MemleakTest(unittest.TestCase):
- def test_collection(self):
- gc.collect()
- flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE
- # python 3 has no DEBUG_OBJECTS
- #if hasattr(gc, 'DEBUG_OBJECTS'):
- #flags |= gc.DEBUG_OBJECTS
- #if 1:
- #flags = flags | gc.DEBUG_STATS
- #gc.set_debug(flags)
- gc.collect()
-
- #print("Tracked objects:", len(gc.get_objects()))
-
- multi = pycurl.CurlMulti()
- t = []
- searches = []
- for a in range(100):
- curl = pycurl.Curl()
- multi.add_handle(curl)
- t.append(curl)
-
- c_id = id(curl)
- searches.append(c_id)
- m_id = id(multi)
- searches.append(m_id)
-
- #print("Tracked objects:", len(gc.get_objects()))
-
- for curl in t:
- curl.close()
- multi.remove_handle(curl)
-
- #print("Tracked objects:", len(gc.get_objects()))
-
- del curl
- del t
- del multi
-
- #print("Tracked objects:", len(gc.get_objects()))
- gc.collect()
- #print("Tracked objects:", len(gc.get_objects()))
-
- objects = gc.get_objects()
- for search in searches:
- for object in objects:
- assert search != id(object)
diff --git a/tests/memory_mgmt_test.py b/tests/memory_mgmt_test.py
new file mode 100644
index 0000000..7281923
--- /dev/null
+++ b/tests/memory_mgmt_test.py
@@ -0,0 +1,221 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import unittest
+import gc
+
+debug = False
+
+class MemoryMgmtTest(unittest.TestCase):
+ def maybe_enable_debug(self):
+ if debug:
+ flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE
+ # python 3 has no DEBUG_OBJECTS
+ if hasattr(gc, 'DEBUG_OBJECTS'):
+ flags |= gc.DEBUG_OBJECTS
+ flags |= gc.DEBUG_STATS
+ gc.set_debug(flags)
+ gc.collect()
+
+ print("Tracked objects:", len(gc.get_objects()))
+
+ def maybe_print_objects(self):
+ if debug:
+ print("Tracked objects:", len(gc.get_objects()))
+
+ def tearDown(self):
+ gc.set_debug(0)
+
+ def test_multi_collection(self):
+ gc.collect()
+ self.maybe_enable_debug()
+
+ multi = pycurl.CurlMulti()
+ t = []
+ searches = []
+ for a in range(100):
+ curl = pycurl.Curl()
+ multi.add_handle(curl)
+ t.append(curl)
+
+ c_id = id(curl)
+ searches.append(c_id)
+ m_id = id(multi)
+ searches.append(m_id)
+
+ self.maybe_print_objects()
+
+ for curl in t:
+ curl.close()
+ multi.remove_handle(curl)
+
+ self.maybe_print_objects()
+
+ del curl
+ del t
+ del multi
+
+ self.maybe_print_objects()
+ gc.collect()
+ self.maybe_print_objects()
+
+ objects = gc.get_objects()
+ for search in searches:
+ for object in objects:
+ assert search != id(object)
+
+ def test_multi_cycle(self):
+ gc.collect()
+ self.maybe_enable_debug()
+
+ multi = pycurl.CurlMulti()
+ t = []
+ searches = []
+ for a in range(100):
+ curl = pycurl.Curl()
+ multi.add_handle(curl)
+ t.append(curl)
+
+ c_id = id(curl)
+ searches.append(c_id)
+ m_id = id(multi)
+ searches.append(m_id)
+
+ self.maybe_print_objects()
+
+ del curl
+ del t
+ del multi
+
+ self.maybe_print_objects()
+ gc.collect()
+ self.maybe_print_objects()
+
+ objects = gc.get_objects()
+ for search in searches:
+ for object in objects:
+ assert search != id(object)
+
+ def test_share_collection(self):
+ gc.collect()
+ self.maybe_enable_debug()
+
+ share = pycurl.CurlShare()
+ t = []
+ searches = []
+ for a in range(100):
+ curl = pycurl.Curl()
+ curl.setopt(curl.SHARE, share)
+ t.append(curl)
+
+ c_id = id(curl)
+ searches.append(c_id)
+ m_id = id(share)
+ searches.append(m_id)
+
+ self.maybe_print_objects()
+
+ for curl in t:
+ curl.unsetopt(curl.SHARE)
+ curl.close()
+
+ self.maybe_print_objects()
+
+ del curl
+ del t
+ del share
+
+ self.maybe_print_objects()
+ gc.collect()
+ self.maybe_print_objects()
+
+ objects = gc.get_objects()
+ for search in searches:
+ for object in objects:
+ assert search != id(object)
+
+ def test_share_cycle(self):
+ gc.collect()
+ self.maybe_enable_debug()
+
+ share = pycurl.CurlShare()
+ t = []
+ searches = []
+ for a in range(100):
+ curl = pycurl.Curl()
+ curl.setopt(curl.SHARE, share)
+ t.append(curl)
+
+ c_id = id(curl)
+ searches.append(c_id)
+ m_id = id(share)
+ searches.append(m_id)
+
+ self.maybe_print_objects()
+
+ del curl
+ del t
+ del share
+
+ self.maybe_print_objects()
+ gc.collect()
+ self.maybe_print_objects()
+
+ objects = gc.get_objects()
+ for search in searches:
+ for object in objects:
+ assert search != id(object)
+
+ # basic check of reference counting (use a memory checker like valgrind)
+ def test_reference_counting(self):
+ c = pycurl.Curl()
+ m = pycurl.CurlMulti()
+ m.add_handle(c)
+ del m
+ m = pycurl.CurlMulti()
+ c.close()
+ del m, c
+
+ def test_cyclic_gc(self):
+ gc.collect()
+ c = pycurl.Curl()
+ c.m = pycurl.CurlMulti()
+ c.m.add_handle(c)
+ # create some nasty cyclic references
+ c.c = c
+ c.c.c1 = c
+ c.c.c2 = c
+ c.c.c3 = c.c
+ c.c.c4 = c.m
+ c.m.c = c
+ c.m.m = c.m
+ c.m.c = c
+ # delete
+ gc.collect()
+ self.maybe_enable_debug()
+ ##print gc.get_referrers(c)
+ ##print gc.get_objects()
+ #if opts.verbose >= 1:
+ #print("Tracked objects:", len(gc.get_objects()))
+ c_id = id(c)
+ # The `del' below should delete these 4 objects:
+ # Curl + internal dict, CurlMulti + internal dict
+ del c
+ gc.collect()
+ objects = gc.get_objects()
+ for object in objects:
+ assert id(object) != c_id
+ #if opts.verbose >= 1:
+ #print("Tracked objects:", len(gc.get_objects()))
+
+ def test_refcounting_bug_in_reset(self):
+ try:
+ range_generator = xrange
+ except NameError:
+ range_generator = range
+ # Ensure that the refcounting error in "reset" is fixed:
+ for i in range_generator(100000):
+ c = pycurl.Curl()
+ c.reset()
diff --git a/tests/multi_socket_select_test.py b/tests/multi_socket_select_test.py
index 7011bb0..79f4e87 100644
--- a/tests/multi_socket_select_test.py
+++ b/tests/multi_socket_select_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -29,9 +29,12 @@ class MultiSocketSelectTest(unittest.TestCase):
timeout = 0
urls = [
- 'http://localhost:8380/success',
- 'http://localhost:8381/success',
- 'http://localhost:8382/success',
+ # 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',
]
socket_events = []
@@ -49,14 +52,13 @@ class MultiSocketSelectTest(unittest.TestCase):
# init
m = pycurl.CurlMulti()
- m.setopt(pycurl.M_PIPELINING, 1)
m.setopt(pycurl.M_SOCKETFUNCTION, socket)
m.handles = []
for url in urls:
c = pycurl.Curl()
# save info in standard Python attributes
c.url = url
- c.body = util.StringIO()
+ c.body = util.BytesIO()
c.http_code = -1
m.handles.append(c)
# pycurl API calls
@@ -97,7 +99,7 @@ class MultiSocketSelectTest(unittest.TestCase):
# print result
for c in m.handles:
- self.assertEqual('success', c.body.getvalue())
+ self.assertEqual('success', c.body.getvalue().decode())
self.assertEqual(200, c.http_code)
# multi, not curl handle
diff --git a/tests/multi_socket_test.py b/tests/multi_socket_test.py
index 106132e..d5ea30e 100644
--- a/tests/multi_socket_test.py
+++ b/tests/multi_socket_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -25,9 +25,12 @@ def teardown_module(mod):
class MultiSocketTest(unittest.TestCase):
def test_multi_socket(self):
urls = [
- 'http://localhost:8380/success',
- 'http://localhost:8381/success',
- 'http://localhost:8382/success',
+ # 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',
]
socket_events = []
@@ -39,14 +42,13 @@ class MultiSocketTest(unittest.TestCase):
# init
m = pycurl.CurlMulti()
- m.setopt(pycurl.M_PIPELINING, 1)
m.setopt(pycurl.M_SOCKETFUNCTION, socket)
m.handles = []
for url in urls:
c = pycurl.Curl()
# save info in standard Python attributes
c.url = url
- c.body = util.StringIO()
+ c.body = util.BytesIO()
c.http_code = -1
m.handles.append(c)
# pycurl API calls
@@ -74,7 +76,7 @@ class MultiSocketTest(unittest.TestCase):
# print result
for c in m.handles:
- self.assertEqual('success', c.body.getvalue())
+ self.assertEqual('success', c.body.getvalue().decode())
self.assertEqual(200, c.http_code)
# multi, not curl handle
diff --git a/tests/multi_test.py b/tests/multi_test.py
index 974c364..1afa674 100644
--- a/tests/multi_test.py
+++ b/tests/multi_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -25,10 +25,10 @@ def teardown_module(mod):
class MultiTest(unittest.TestCase):
def test_multi(self):
- io1 = util.StringIO()
- io2 = util.StringIO()
+ io1 = util.BytesIO()
+ io2 = util.BytesIO()
m = pycurl.CurlMulti()
- m.handles = []
+ handles = []
c1 = pycurl.Curl()
c2 = pycurl.Curl()
c1.setopt(c1.URL, 'http://localhost:8380/success')
@@ -37,10 +37,10 @@ class MultiTest(unittest.TestCase):
c2.setopt(c1.WRITEFUNCTION, io2.write)
m.add_handle(c1)
m.add_handle(c2)
- m.handles.append(c1)
- m.handles.append(c2)
+ handles.append(c1)
+ handles.append(c2)
- num_handles = len(m.handles)
+ num_handles = len(handles)
while num_handles:
while 1:
ret, num_handles = m.perform()
@@ -50,13 +50,12 @@ class MultiTest(unittest.TestCase):
m.remove_handle(c2)
m.remove_handle(c1)
- del m.handles
m.close()
c1.close()
c2.close()
- self.assertEqual('success', io1.getvalue())
- self.assertEqual('success', io2.getvalue())
+ self.assertEqual('success', io1.getvalue().decode())
+ self.assertEqual('success', io2.getvalue().decode())
def test_multi_select_fdset(self):
c1 = pycurl.Curl()
@@ -65,9 +64,9 @@ class MultiTest(unittest.TestCase):
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.body = util.StringIO()
- c2.body = util.StringIO()
- c3.body = util.StringIO()
+ c1.body = util.BytesIO()
+ c2.body = util.BytesIO()
+ c3.body = util.BytesIO()
c1.setopt(c1.WRITEFUNCTION, c1.body.write)
c2.setopt(c2.WRITEFUNCTION, c2.body.write)
c3.setopt(c3.WRITEFUNCTION, c3.body.write)
@@ -103,9 +102,9 @@ class MultiTest(unittest.TestCase):
c2.close()
c3.close()
- self.assertEqual('success', c1.body.getvalue())
- self.assertEqual('success', c2.body.getvalue())
- self.assertEqual('success', c3.body.getvalue())
+ self.assertEqual('success', c1.body.getvalue().decode())
+ self.assertEqual('success', c2.body.getvalue().decode())
+ self.assertEqual('success', c3.body.getvalue().decode())
def test_multi_status_codes(self):
# init
@@ -120,7 +119,7 @@ class MultiTest(unittest.TestCase):
c = pycurl.Curl()
# save info in standard Python attributes
c.url = url.rstrip()
- c.body = util.StringIO()
+ c.body = util.BytesIO()
c.http_code = -1
m.handles.append(c)
# pycurl API calls
@@ -149,13 +148,13 @@ class MultiTest(unittest.TestCase):
m.close()
# check result
- self.assertEqual('success', m.handles[0].body.getvalue())
+ self.assertEqual('success', m.handles[0].body.getvalue().decode())
self.assertEqual(200, m.handles[0].http_code)
# bottle generated response body
- self.assertEqual('forbidden', m.handles[1].body.getvalue())
+ self.assertEqual('forbidden', m.handles[1].body.getvalue().decode())
self.assertEqual(403, m.handles[1].http_code)
# bottle generated response body
- self.assertEqual('not found', m.handles[2].body.getvalue())
+ self.assertEqual('not found', m.handles[2].body.getvalue().decode())
self.assertEqual(404, m.handles[2].http_code)
def check_adding_closed_handle(self, close_fn):
@@ -171,7 +170,7 @@ class MultiTest(unittest.TestCase):
c = pycurl.Curl()
# save info in standard Python attributes
c.url = url
- c.body = util.StringIO()
+ c.body = util.BytesIO()
c.http_code = -1
c.debug = 0
m.handles.append(c)
@@ -210,13 +209,13 @@ class MultiTest(unittest.TestCase):
m.close()
# check result
- self.assertEqual('success', m.handles[0].body.getvalue())
+ self.assertEqual('success', m.handles[0].body.getvalue().decode())
self.assertEqual(200, m.handles[0].http_code)
# bottle generated response body
- self.assertEqual('forbidden', m.handles[1].body.getvalue())
+ self.assertEqual('forbidden', m.handles[1].body.getvalue().decode())
self.assertEqual(403, m.handles[1].http_code)
# bottle generated response body
- self.assertEqual('', m.handles[2].body.getvalue())
+ self.assertEqual('', m.handles[2].body.getvalue().decode())
self.assertEqual(-1, m.handles[2].http_code)
def _remove_then_close(self, m, c):
@@ -249,9 +248,9 @@ class MultiTest(unittest.TestCase):
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.body = util.StringIO()
- c2.body = util.StringIO()
- c3.body = util.StringIO()
+ c1.body = util.BytesIO()
+ c2.body = util.BytesIO()
+ c3.body = util.BytesIO()
c1.setopt(c1.WRITEFUNCTION, c1.body.write)
c2.setopt(c2.WRITEFUNCTION, c2.body.write)
c3.setopt(c3.WRITEFUNCTION, c3.body.write)
@@ -289,20 +288,20 @@ class MultiTest(unittest.TestCase):
c2.close()
c3.close()
- self.assertEqual('success', c1.body.getvalue())
- self.assertEqual('success', c2.body.getvalue())
- self.assertEqual('success', c3.body.getvalue())
+ self.assertEqual('success', c1.body.getvalue().decode())
+ self.assertEqual('success', c2.body.getvalue().decode())
+ self.assertEqual('success', c3.body.getvalue().decode())
def test_multi_info_read(self):
c1 = pycurl.Curl()
c2 = pycurl.Curl()
c3 = pycurl.Curl()
- 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.body = util.StringIO()
- c2.body = util.StringIO()
- c3.body = util.StringIO()
+ 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.body = util.BytesIO()
+ c2.body = util.BytesIO()
+ c3.body = util.BytesIO()
c1.setopt(c1.WRITEFUNCTION, c1.body.write)
c2.setopt(c2.WRITEFUNCTION, c2.body.write)
c3.setopt(c3.WRITEFUNCTION, c3.body.write)
@@ -355,6 +354,15 @@ class MultiTest(unittest.TestCase):
c2.close()
c3.close()
- self.assertEqual('success', c1.body.getvalue())
- self.assertEqual('success', c2.body.getvalue())
- self.assertEqual('success', c3.body.getvalue())
+ self.assertEqual('success', c1.body.getvalue().decode())
+ self.assertEqual('success', c2.body.getvalue().decode())
+ self.assertEqual('success', c3.body.getvalue().decode())
+
+ def test_multi_close(self):
+ m = pycurl.CurlMulti()
+ m.close()
+
+ def test_multi_close_twice(self):
+ m = pycurl.CurlMulti()
+ m.close()
+ m.close()
diff --git a/tests/multi_timer_test.py b/tests/multi_timer_test.py
index 64dc497..a11054c 100644
--- a/tests/multi_timer_test.py
+++ b/tests/multi_timer_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -39,14 +39,13 @@ class MultiSocketTest(unittest.TestCase):
# init
m = pycurl.CurlMulti()
- m.setopt(pycurl.M_PIPELINING, 1)
m.setopt(pycurl.M_TIMERFUNCTION, timer)
m.handles = []
for url in urls:
c = pycurl.Curl()
# save info in standard Python attributes
c.url = url
- c.body = util.StringIO()
+ c.body = util.BytesIO()
c.http_code = -1
m.handles.append(c)
# pycurl API calls
@@ -71,7 +70,7 @@ class MultiSocketTest(unittest.TestCase):
# print result
for c in m.handles:
- self.assertEqual('success', c.body.getvalue())
+ self.assertEqual('success', c.body.getvalue().decode())
self.assertEqual(200, c.http_code)
assert len(timers) > 0
diff --git a/tests/option_constants_test.py b/tests/option_constants_test.py
new file mode 100644
index 0000000..878d1b3
--- /dev/null
+++ b/tests/option_constants_test.py
@@ -0,0 +1,76 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import unittest
+import nose.plugins.skip
+
+from . import util
+
+class OptionConstantsTest(unittest.TestCase):
+ # CURLOPT_USERNAME was introduced in libcurl-7.19.1
+ @util.min_libcurl(7, 19, 1)
+ def test_username(self):
+ assert hasattr(pycurl, 'USERNAME')
+ assert hasattr(pycurl, 'PASSWORD')
+ assert hasattr(pycurl, 'PROXYUSERNAME')
+ assert hasattr(pycurl, 'PROXYPASSWORD')
+
+ # CURLOPT_DNS_SERVERS was introduced in libcurl-7.24.0
+ @util.min_libcurl(7, 24, 0)
+ def test_dns_servers(self):
+ assert hasattr(pycurl, 'DNS_SERVERS')
+
+ # Does not work unless libcurl was built against c-ares
+ #c = pycurl.Curl()
+ #c.setopt(c.DNS_SERVERS, '1.2.3.4')
+ #c.close()
+
+ # CURLOPT_POSTREDIR was introduced in libcurl-7.19.1
+ @util.min_libcurl(7, 19, 1)
+ def test_postredir(self):
+ assert hasattr(pycurl, 'POSTREDIR')
+ assert hasattr(pycurl, 'REDIR_POST_301')
+ assert hasattr(pycurl, 'REDIR_POST_302')
+ assert hasattr(pycurl, 'REDIR_POST_ALL')
+
+ # CURLOPT_POSTREDIR was introduced in libcurl-7.19.1
+ @util.min_libcurl(7, 19, 1)
+ def test_postredir_setopt(self):
+ curl = pycurl.Curl()
+ curl.setopt(curl.POSTREDIR, curl.REDIR_POST_301)
+ curl.close()
+
+ # CURL_REDIR_POST_303 was introduced in libcurl-7.26.0
+ @util.min_libcurl(7, 26, 0)
+ def test_redir_post_303(self):
+ assert hasattr(pycurl, 'REDIR_POST_303')
+
+ # CURLOPT_POSTREDIR was introduced in libcurl-7.19.1
+ @util.min_libcurl(7, 19, 1)
+ def test_postredir_flags(self):
+ self.assertEqual(pycurl.REDIR_POST_301, pycurl.REDIR_POST_ALL & pycurl.REDIR_POST_301)
+ self.assertEqual(pycurl.REDIR_POST_302, pycurl.REDIR_POST_ALL & pycurl.REDIR_POST_302)
+
+ # CURL_REDIR_POST_303 was introduced in libcurl-7.26.0
+ @util.min_libcurl(7, 26, 0)
+ def test_postredir_flags(self):
+ self.assertEqual(pycurl.REDIR_POST_303, pycurl.REDIR_POST_ALL & pycurl.REDIR_POST_303)
+
+ # HTTPAUTH_DIGEST_IE was introduced in libcurl-7.19.3
+ @util.min_libcurl(7, 19, 3)
+ def test_httpauth_digest_ie(self):
+ assert hasattr(pycurl, 'HTTPAUTH_DIGEST_IE')
+
+ # CURLE_OPERATION_TIMEDOUT was introduced in libcurl-7.10.2
+ # to replace CURLE_OPERATION_TIMEOUTED
+ def test_operation_timedout_constant(self):
+ self.assertEqual(pycurl.E_OPERATION_TIMEDOUT, pycurl.E_OPERATION_TIMEOUTED)
+
+ # CURLOPT_NOPROXY was introduced in libcurl-7.19.4
+ @util.min_libcurl(7, 19, 4)
+ def test_noproxy_setopt(self):
+ curl = pycurl.Curl()
+ curl.setopt(curl.NOPROXY, 'localhost')
+ curl.close()
diff --git a/tests/pause_test.py b/tests/pause_test.py
index 1567718..8f993de 100644
--- a/tests/pause_test.py
+++ b/tests/pause_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -27,7 +27,7 @@ class PauseTest(unittest.TestCase):
def check_pause(self, call):
# the app sleeps for 0.5 seconds
self.curl.setopt(pycurl.URL, 'http://localhost:8380/pause')
- sio = util.StringIO()
+ sio = util.BytesIO()
state = dict(paused=False, resumed=False)
if call:
def writefunc(data):
@@ -85,7 +85,7 @@ class PauseTest(unittest.TestCase):
m.remove_handle(self.curl)
m.close()
- self.assertEqual('part1part2', sio.getvalue())
+ self.assertEqual('part1part2', sio.getvalue().decode())
end = _time.time()
# check that client side waited
self.assertTrue(end-start > 1)
diff --git a/tests/post_test.py b/tests/post_test.py
index d6f9678..bd83944 100644
--- a/tests/post_test.py
+++ b/tests/post_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import os.path
@@ -64,11 +64,11 @@ class PostTest(unittest.TestCase):
# UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 4: invalid start byte
#self.curl.setopt(pycurl.VERBOSE, 1)
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.perform()
self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE))
- body = sio.getvalue()
+ body = sio.getvalue().decode()
returned_fields = json.loads(body)
self.assertEqual(pf, returned_fields)
@@ -99,14 +99,27 @@ class PostTest(unittest.TestCase):
}]
self.check_post(send, expect, 'http://localhost:8380/files')
+ def test_post_buffer(self):
+ contents = 'hello, world!'
+ send = [
+ ('field2', (pycurl.FORM_BUFFER, 'uploaded.file', pycurl.FORM_BUFFERPTR, contents)),
+ ]
+ expect = [{
+ 'name': 'field2',
+ 'filename': 'uploaded.file',
+ 'data': contents,
+ }]
+ self.check_post(send, expect, 'http://localhost:8380/files')
+
# XXX this test takes about a second to run, check keep-alives?
def check_post(self, send, expect, endpoint):
self.curl.setopt(pycurl.URL, endpoint)
self.curl.setopt(pycurl.HTTPPOST, send)
#self.curl.setopt(pycurl.VERBOSE, 1)
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.perform()
- body = sio.getvalue()
+ self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE))
+ body = sio.getvalue().decode()
returned_fields = json.loads(body)
self.assertEqual(expect, returned_fields)
diff --git a/tests/post_with_read_callback_test.py b/tests/post_with_read_callback_test.py
deleted file mode 100644
index aba4e16..0000000
--- a/tests/post_with_read_callback_test.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
-# vi:ts=4:et
-
-import pycurl
-import unittest
-try:
- import json
-except ImportError:
- import simplejson as json
-try:
- import urllib.parse as urllib_parse
-except ImportError:
- import urllib as urllib_parse
-
-from . import appmanager
-from . import util
-
-setup_module, teardown_module = appmanager.setup(('app', 8380))
-
-POSTFIELDS = {
- 'field1':'value1',
- 'field2':'value2 with blanks',
- 'field3':'value3',
-}
-POSTSTRING = urllib_parse.urlencode(POSTFIELDS)
-
-class DataProvider(object):
- def __init__(self):
- self.finished = False
-
- def read_cb(self, size):
- assert len(POSTSTRING) <= size
- if not self.finished:
- self.finished = True
- return POSTSTRING
- else:
- # Nothing more to read
- return ""
-
-class PostWithReadCallbackTest(unittest.TestCase):
- def setUp(self):
- self.curl = pycurl.Curl()
-
- def tearDown(self):
- self.curl.close()
-
- def test_post_with_read_callback(self):
- d = DataProvider()
- self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields')
- self.curl.setopt(self.curl.POST, 1)
- self.curl.setopt(self.curl.POSTFIELDSIZE, len(POSTSTRING))
- self.curl.setopt(self.curl.READFUNCTION, d.read_cb)
- #self.curl.setopt(self.curl.VERBOSE, 1)
- sio = util.StringIO()
- self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
- self.curl.perform()
-
- actual = json.loads(sio.getvalue())
- self.assertEqual(POSTFIELDS, actual)
diff --git a/tests/pycurl_object_test.py b/tests/pycurl_object_test.py
new file mode 100644
index 0000000..7e5dcf3
--- /dev/null
+++ b/tests/pycurl_object_test.py
@@ -0,0 +1,125 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import unittest
+import sys
+
+class PycurlObjectTest(unittest.TestCase):
+ def setUp(self):
+ self.curl = pycurl.Curl()
+
+ def tearDown(self):
+ self.curl.close()
+
+ def test_set_attribute_curl(self):
+ self.instantiate_and_check(self.check_set_attribute, 'Curl')
+
+ def test_get_attribute_curl(self):
+ self.instantiate_and_check(self.check_get_attribute, 'Curl')
+
+ def test_get_missing_attribute_curl(self):
+ self.instantiate_and_check(self.check_get_missing_attribute, 'Curl')
+
+ def test_delete_attribute_curl(self):
+ self.instantiate_and_check(self.check_delete_attribute, 'Curl')
+
+ def test_delete_missing_attribute_curl(self):
+ self.instantiate_and_check(self.check_delete_missing_attribute, 'Curl')
+
+ def test_set_attribute_multi(self):
+ self.instantiate_and_check(self.check_set_attribute, 'CurlMulti')
+
+ def test_get_attribute_multi(self):
+ self.instantiate_and_check(self.check_get_attribute, 'CurlMulti')
+
+ def test_get_missing_attribute_curl(self):
+ self.instantiate_and_check(self.check_get_missing_attribute, 'CurlMulti')
+
+ def test_delete_attribute_multi(self):
+ self.instantiate_and_check(self.check_delete_attribute, 'CurlMulti')
+
+ def test_delete_missing_attribute_curl(self):
+ self.instantiate_and_check(self.check_delete_missing_attribute, 'CurlMulti')
+
+ def test_set_attribute_share(self):
+ self.instantiate_and_check(self.check_set_attribute, 'CurlShare')
+
+ def test_get_attribute_share(self):
+ self.instantiate_and_check(self.check_get_attribute, 'CurlShare')
+
+ def test_get_missing_attribute_curl(self):
+ self.instantiate_and_check(self.check_get_missing_attribute, 'CurlShare')
+
+ def test_delete_attribute_share(self):
+ self.instantiate_and_check(self.check_delete_attribute, 'CurlShare')
+
+ def test_delete_missing_attribute_curl(self):
+ self.instantiate_and_check(self.check_delete_missing_attribute, 'CurlShare')
+
+ def instantiate_and_check(self, fn, cls_name):
+ cls = getattr(pycurl, cls_name)
+ instance = cls()
+ try:
+ fn(instance)
+ finally:
+ instance.close()
+
+ def check_set_attribute(self, pycurl_obj):
+ assert not hasattr(pycurl_obj, 'attr')
+ pycurl_obj.attr = 1
+ assert hasattr(pycurl_obj, 'attr')
+
+ def check_get_attribute(self, pycurl_obj):
+ assert not hasattr(pycurl_obj, 'attr')
+ pycurl_obj.attr = 1
+ self.assertEqual(1, pycurl_obj.attr)
+
+ def check_get_missing_attribute(self, pycurl_obj):
+ try:
+ getattr(pycurl_obj, 'doesnotexist')
+ self.fail('Expected an AttributeError exception to be raised')
+ except AttributeError:
+ pass
+
+ def check_delete_attribute(self, pycurl_obj):
+ assert not hasattr(pycurl_obj, 'attr')
+ pycurl_obj.attr = 1
+ self.assertEqual(1, pycurl_obj.attr)
+ assert hasattr(pycurl_obj, 'attr')
+ del pycurl_obj.attr
+ assert not hasattr(pycurl_obj, 'attr')
+
+ def check_delete_missing_attribute(self, pycurl_obj):
+ try:
+ del pycurl_obj.doesnotexist
+ self.fail('Expected an AttributeError exception to be raised')
+ except AttributeError:
+ pass
+
+ def test_modify_attribute_curl(self):
+ self.check_modify_attribute(pycurl.Curl, 'READFUNC_PAUSE')
+
+ def test_modify_attribute_multi(self):
+ self.check_modify_attribute(pycurl.CurlMulti, 'E_MULTI_OK')
+
+ def test_modify_attribute_share(self):
+ self.check_modify_attribute(pycurl.CurlShare, 'SH_SHARE')
+
+ def check_modify_attribute(self, cls, name):
+ obj1 = cls()
+ obj2 = cls()
+ old_value = getattr(obj1, name)
+ self.assertNotEqual('helloworld', old_value)
+ # value should be identical to pycurl global
+ self.assertEqual(old_value, getattr(pycurl, name))
+ setattr(obj1, name, 'helloworld')
+ self.assertEqual('helloworld', getattr(obj1, name))
+
+ # change does not affect other existing objects
+ self.assertEqual(old_value, getattr(obj2, name))
+
+ # change does not affect objects created later
+ obj3 = cls()
+ self.assertEqual(old_value, getattr(obj3, name))
diff --git a/tests/read_callback_test.py b/tests/read_callback_test.py
new file mode 100644
index 0000000..e3aacb1
--- /dev/null
+++ b/tests/read_callback_test.py
@@ -0,0 +1,126 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import unittest
+import sys
+try:
+ import json
+except ImportError:
+ import simplejson as json
+try:
+ import urllib.parse as urllib_parse
+except ImportError:
+ import urllib as urllib_parse
+
+from . import appmanager
+from . import util
+
+setup_module, teardown_module = appmanager.setup(('app', 8380))
+
+POSTFIELDS = {
+ 'field1':'value1',
+ 'field2':'value2 with blanks',
+ 'field3':'value3',
+}
+POSTSTRING = urllib_parse.urlencode(POSTFIELDS)
+
+class DataProvider(object):
+ def __init__(self, data):
+ self.data = data
+ self.finished = False
+
+ def read_cb(self, size):
+ assert len(self.data) <= size
+ if not self.finished:
+ self.finished = True
+ return self.data
+ else:
+ # Nothing more to read
+ return ""
+
+class ReadCallbackTest(unittest.TestCase):
+ def setUp(self):
+ self.curl = pycurl.Curl()
+
+ def tearDown(self):
+ self.curl.close()
+
+ 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.POST, 1)
+ self.curl.setopt(self.curl.POSTFIELDSIZE, len(POSTSTRING))
+ self.curl.setopt(self.curl.READFUNCTION, d.read_cb)
+ #self.curl.setopt(self.curl.VERBOSE, 1)
+ sio = util.BytesIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ self.curl.perform()
+
+ actual = json.loads(sio.getvalue().decode())
+ self.assertEqual(POSTFIELDS, actual)
+
+ def test_post_with_read_callback_returning_bytes(self):
+ self.check_bytes('world')
+
+ def test_post_with_read_callback_returning_bytes_with_nulls(self):
+ self.check_bytes("wor\0ld")
+
+ def test_post_with_read_callback_returning_bytes_with_multibyte(self):
+ self.check_bytes(util.u("Пушкин"))
+
+ def check_bytes(self, poststring):
+ data = poststring.encode('utf8')
+ 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.POST, 1)
+ self.curl.setopt(self.curl.HTTPHEADER, ['Content-Type: application/octet-stream'])
+ # length of bytes
+ self.curl.setopt(self.curl.POSTFIELDSIZE, len(data))
+ self.curl.setopt(self.curl.READFUNCTION, d.read_cb)
+ #self.curl.setopt(self.curl.VERBOSE, 1)
+ sio = util.BytesIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ self.curl.perform()
+
+ # json should be ascii
+ actual = json.loads(sio.getvalue().decode('ascii'))
+ self.assertEqual(poststring, actual)
+
+ def test_post_with_read_callback_returning_unicode(self):
+ self.check_unicode(util.u('world'))
+
+ def test_post_with_read_callback_returning_unicode_with_nulls(self):
+ self.check_unicode(util.u("wor\0ld"))
+
+ def test_post_with_read_callback_returning_unicode_with_multibyte(self):
+ try:
+ self.check_unicode(util.u("Пушкин"))
+ # prints:
+ # UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-11: ordinal not in range(128)
+ except pycurl.error:
+ err, msg = sys.exc_info()[1].args
+ # we expect pycurl.E_WRITE_ERROR as the response
+ self.assertEqual(pycurl.E_ABORTED_BY_CALLBACK, err)
+ self.assertEqual('operation aborted by callback', msg)
+
+ def check_unicode(self, poststring):
+ 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.POST, 1)
+ self.curl.setopt(self.curl.HTTPHEADER, ['Content-Type: application/octet-stream'])
+ self.curl.setopt(self.curl.POSTFIELDSIZE, len(poststring))
+ self.curl.setopt(self.curl.READFUNCTION, d.read_cb)
+ #self.curl.setopt(self.curl.VERBOSE, 1)
+ sio = util.BytesIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ self.curl.perform()
+
+ # json should be ascii
+ actual = json.loads(sio.getvalue().decode('ascii'))
+ self.assertEqual(poststring, actual)
diff --git a/tests/relative_url_test.py b/tests/relative_url_test.py
index ef8b132..42052ec 100644
--- a/tests/relative_url_test.py
+++ b/tests/relative_url_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
# uses the high level interface
@@ -19,4 +19,4 @@ class RelativeUrlTest(unittest.TestCase):
def test_get_relative(self):
self.curl.get('/success')
- self.assertEqual('success', self.curl.body())
+ self.assertEqual('success', self.curl.body().decode())
diff --git a/tests/reset_test.py b/tests/reset_test.py
index 8482e2f..f3c5fd3 100644
--- a/tests/reset_test.py
+++ b/tests/reset_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
@@ -17,12 +17,9 @@ setup_module, teardown_module = appmanager.setup(('app', 8380))
class ResetTest(unittest.TestCase):
# XXX this test was broken when it was test_reset.py
def skip_reset(self):
- outf = util.StringIO()
+ outf = util.BytesIO()
cm = pycurl.CurlMulti()
- # Set multi handle's options
- cm.setopt(pycurl.M_PIPELINING, 1)
-
eh = pycurl.Curl()
for x in range(1, 20):
diff --git a/tests/resolve_test.py b/tests/resolve_test.py
index eb00bdc..7539d23 100644
--- a/tests/resolve_test.py
+++ b/tests/resolve_test.py
@@ -1,4 +1,4 @@
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
import pycurl
import unittest
diff --git a/tests/seek_function_test.py b/tests/seek_function_test.py
index 33a1334..48b9171 100644
--- a/tests/seek_function_test.py
+++ b/tests/seek_function_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
# Note: this test is meant to be run from pycurl project root.
diff --git a/tests/setopt_lifecycle_test.py b/tests/setopt_lifecycle_test.py
index 0bb9530..0c00bd0 100644
--- a/tests/setopt_lifecycle_test.py
+++ b/tests/setopt_lifecycle_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import gc
@@ -45,11 +45,11 @@ class SetoptLifecycleTest(unittest.TestCase):
for i in range(100):
curl = requests[i]
#self.curl.setopt(pycurl.VERBOSE, 1)
- sio = util.StringIO()
+ sio = util.BytesIO()
curl.setopt(pycurl.WRITEFUNCTION, sio.write)
curl.perform()
self.assertEqual(200, curl.getinfo(pycurl.HTTP_CODE))
- body = sio.getvalue()
+ body = sio.getvalue().decode()
returned_fields = json.loads(body)
self.assertEqual(dict(field='value%d' % i), returned_fields)
diff --git a/tests/setopt_unicode_test.py b/tests/setopt_unicode_test.py
new file mode 100644
index 0000000..cb083e0
--- /dev/null
+++ b/tests/setopt_unicode_test.py
@@ -0,0 +1,41 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import pycurl
+import unittest
+import nose.tools
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+from . import appmanager
+from . import util
+
+setup_module, teardown_module = appmanager.setup(('app', 8380))
+
+class SetoptUnicodeTest(unittest.TestCase):
+ def setUp(self):
+ self.curl = pycurl.Curl()
+
+ def tearDown(self):
+ self.curl.close()
+
+ def test_ascii_string(self):
+ self.check('p=test', 'test')
+
+ @nose.tools.raises(UnicodeEncodeError)
+ def test_unicode_string(self):
+ self.check(util.u('p=Москва'), util.u('Москва'))
+
+ def test_unicode_encoded(self):
+ 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')
+ sio = util.BytesIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ self.curl.setopt(pycurl.POSTFIELDS, send)
+ self.curl.perform()
+ self.assertEqual(expected, sio.getvalue().decode('utf-8'))
diff --git a/tests/share_test.py b/tests/share_test.py
index a7cc7f0..cb008a6 100644
--- a/tests/share_test.py
+++ b/tests/share_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import threading
@@ -22,7 +22,7 @@ class WorkerThread(threading.Thread):
self.curl = pycurl.Curl()
self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
self.curl.setopt(pycurl.SHARE, share)
- self.sio = util.StringIO()
+ self.sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, self.sio.write)
def run(self):
@@ -34,6 +34,7 @@ class ShareTest(unittest.TestCase):
s = pycurl.CurlShare()
s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_COOKIE)
s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_DNS)
+ s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_SSL_SESSION)
t1 = WorkerThread(s)
t2 = WorkerThread(s)
@@ -46,5 +47,14 @@ class ShareTest(unittest.TestCase):
del s
- self.assertEqual('success', t1.sio.getvalue())
- self.assertEqual('success', t2.sio.getvalue())
+ self.assertEqual('success', t1.sio.getvalue().decode())
+ self.assertEqual('success', t2.sio.getvalue().decode())
+
+ def test_share_close(self):
+ s = pycurl.CurlShare()
+ s.close()
+
+ def test_share_close_twice(self):
+ s = pycurl.CurlShare()
+ s.close()
+ s.close()
diff --git a/tests/socket_open_test.py b/tests/socket_open_test.py
index 3b76318..78a214c 100644
--- a/tests/socket_open_test.py
+++ b/tests/socket_open_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import socket
@@ -16,12 +16,15 @@ from . import util
setup_module, teardown_module = appmanager.setup(('app', 8380))
socket_open_called = False
+socket_open_address = None
-def socket_open(family, socktype, protocol):
+def socket_open(family, socktype, protocol, address):
global socket_open_called
+ global socket_open_address
socket_open_called = True
+ socket_open_address = address
- #print(family, socktype, protocol)
+ #print(family, socktype, protocol, address)
s = socket.socket(family, socktype, protocol)
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
return s
@@ -36,9 +39,10 @@ class SocketOpenTest(unittest.TestCase):
def test_socket_open(self):
self.curl.setopt(pycurl.OPENSOCKETFUNCTION, socket_open)
self.curl.setopt(self.curl.URL, 'http://localhost:8380/success')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.perform()
assert socket_open_called
- self.assertEqual('success', sio.getvalue())
+ self.assertEqual(("127.0.0.1", 8380), socket_open_address)
+ self.assertEqual('success', sio.getvalue().decode())
diff --git a/tests/unset_range_test.py b/tests/unset_range_test.py
index 10ee801..f64e43a 100644
--- a/tests/unset_range_test.py
+++ b/tests/unset_range_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import os.path
diff --git a/tests/user_agent_string_test.py b/tests/user_agent_string_test.py
new file mode 100644
index 0000000..e391f1b
--- /dev/null
+++ b/tests/user_agent_string_test.py
@@ -0,0 +1,27 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import unittest
+import pycurl
+
+from . import appmanager
+from . import util
+
+setup_module, teardown_module = appmanager.setup(('app', 8380))
+
+class UserAgentStringTest(unittest.TestCase):
+ def setUp(self):
+ self.curl = pycurl.Curl()
+
+ def tearDown(self):
+ self.curl.close()
+
+ def test_pycurl_user_agent_string(self):
+ self.curl.setopt(pycurl.URL, 'http://localhost:8380/header?h=user-agent')
+ sio = util.BytesIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ self.curl.perform()
+ user_agent = sio.getvalue().decode()
+ assert user_agent.startswith('PycURL/')
+ assert 'libcurl/' in user_agent, 'User agent did not include libcurl/: %s' % user_agent
diff --git a/tests/util.py b/tests/util.py
index eef636f..9eca60f 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -1,18 +1,45 @@
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
-# $Id$
import os, sys, socket
import time as _time
-import pycurl
-
try:
- from cStringIO import StringIO
+ import functools
except ImportError:
+ import functools_backport as functools
+
+py3 = sys.version_info[0] == 3
+
+# python 2/3 compatibility
+if py3:
+ from io import StringIO, BytesIO
+
+ # borrowed from six
+ def b(s):
+ '''Byte literal'''
+ return s.encode("latin-1")
+ def u(s):
+ '''Text literal'''
+ return s
+ text_type = str
+ binary_type = bytes
+else:
try:
- from StringIO import StringIO
+ from cStringIO import StringIO
except ImportError:
- from io import StringIO
+ from StringIO import StringIO
+ BytesIO = StringIO
+
+ # borrowed from six
+ def b(s):
+ '''Byte literal'''
+ return s
+ # Workaround for standalone backslash
+ def u(s):
+ '''Text literal'''
+ return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+ text_type = unicode
+ binary_type = str
def version_less_than_spec(version_tuple, spec_tuple):
# spec_tuple may have 2 elements, expect version_tuple to have 3 elements
@@ -25,9 +52,54 @@ def version_less_than_spec(version_tuple, spec_tuple):
return False
def pycurl_version_less_than(*spec):
+ import pycurl
+
version = [int(part) for part in pycurl.version_info()[1].split('.')]
return version_less_than_spec(version, spec)
+def only_python3(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 min_libcurl(major, minor, patch):
+ import nose.plugins.skip
+
+ def decorator(fn):
+ @functools.wraps(fn)
+ def decorated(*args, **kwargs):
+ if pycurl_version_less_than(major, minor, patch):
+ raise nose.plugins.skip.SkipTest('libcurl < %d.%d.%d' % (major, minor, patch))
+
+ return fn(*args, **kwargs)
+
+ return decorated
+
+ return decorator
+
+def only_ssl(fn):
+ import nose.plugins.skip
+ import pycurl
+
+ @functools.wraps(fn)
+ def decorated(*args, **kwargs):
+ # easier to check that pycurl supports https, although
+ # theoretically it is not the same test.
+ # pycurl.version_info()[8] is a tuple of protocols supported by libcurl
+ if 'https' not in pycurl.version_info()[8]:
+ raise nose.plugins.skip.SkipTest('libcurl does not support ssl')
+
+ return fn(*args, **kwargs)
+
+ return decorated
+
try:
create_connection = socket.create_connection
except AttributeError:
@@ -46,7 +118,7 @@ def wait_for_network_service(netloc, check_interval, num_attempts):
try:
conn = create_connection(netloc, check_interval)
except socket.error:
- e = sys.exc_info()[1]
+ #e = sys.exc_info()[1]
_time.sleep(check_interval)
else:
conn.close()
diff --git a/tests/version_comparison_test.py b/tests/version_comparison_test.py
index 80e780c..f13a53c 100644
--- a/tests/version_comparison_test.py
+++ b/tests/version_comparison_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import unittest
diff --git a/tests/version_test.py b/tests/version_test.py
new file mode 100644
index 0000000..a021a49
--- /dev/null
+++ b/tests/version_test.py
@@ -0,0 +1,13 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+# vi:ts=4:et
+
+import unittest
+import pycurl
+
+class VersionTest(unittest.TestCase):
+ def test_pycurl_presence_and_case(self):
+ assert pycurl.version.startswith('PycURL/')
+
+ def test_libcurl_presence(self):
+ assert 'libcurl/' in pycurl.version
diff --git a/tests/vsftpd.conf b/tests/vsftpd.conf
new file mode 100644
index 0000000..787da0e
--- /dev/null
+++ b/tests/vsftpd.conf
@@ -0,0 +1,13 @@
+anon_world_readable_only=yes
+anonymous_enable=yes
+background=no
+# currently we only list files
+download_enable=no
+listen=yes
+run_as_launching_user=yes
+write_enable=yes
+anon_upload_enable=yes
+anon_other_write_enable=yes
+listen_port=8321
+# should be supplied on command line
+anon_root=/var/empty
diff --git a/tests/write_abort_test.py b/tests/write_abort_test.py
index 957fe78..4867ade 100644
--- a/tests/write_abort_test.py
+++ b/tests/write_abort_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import os.path
@@ -19,13 +19,20 @@ class WriteAbortTest(unittest.TestCase):
# this should cause pycurl.WRITEFUNCTION (without any range errors)
return -1
+ try:
+ # set when running full test suite if any earlier tests
+ # failed in Python code called from C
+ del sys.last_value
+ except AttributeError:
+ 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.WRITEFUNCTION, write_cb)
try:
self.curl.perform()
except pycurl.error:
- err, msg = sys.exc_info()[1]
+ err, msg = sys.exc_info()[1].args
# we expect pycurl.E_WRITE_ERROR as the response
assert pycurl.E_WRITE_ERROR == err
diff --git a/tests/write_cb_bogus_test.py b/tests/write_cb_bogus_test.py
index ef709db..6278ffd 100644
--- a/tests/write_cb_bogus_test.py
+++ b/tests/write_cb_bogus_test.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import os.path
@@ -33,8 +33,10 @@ class WriteAbortTest(unittest.TestCase):
self.curl.setopt(pycurl.WRITEFUNCTION, write_cb)
try:
self.curl.perform()
+
+ self.fail('Should not get here')
except pycurl.error:
- err, msg = sys.exc_info()[1]
+ err, msg = sys.exc_info()[1].args
# we expect pycurl.E_WRITE_ERROR as the response
assert pycurl.E_WRITE_ERROR == err
diff --git a/tests/write_to_file_test.py b/tests/write_to_file_test.py
index a4b3070..09b1abc 100644
--- a/tests/write_to_file_test.py
+++ b/tests/write_to_file_test.py
@@ -1,15 +1,25 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import unittest
import pycurl
import tempfile
+import shutil
+import os.path
from . import appmanager
+from . import util
setup_module, teardown_module = appmanager.setup(('app', 8380))
+class Acceptor(object):
+ def __init__(self):
+ self.buffer = ''
+
+ def write(self, chunk):
+ self.buffer += chunk.decode()
+
class WriteToFileTest(unittest.TestCase):
def setUp(self):
self.curl = pycurl.Curl()
@@ -17,7 +27,7 @@ class WriteToFileTest(unittest.TestCase):
def tearDown(self):
self.curl.close()
- def test_get_to_file(self):
+ def test_write_to_tempfile_via_function(self):
self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
f = tempfile.NamedTemporaryFile()
try:
@@ -27,4 +37,58 @@ class WriteToFileTest(unittest.TestCase):
body = f.read()
finally:
f.close()
- self.assertEqual('success', body)
+ self.assertEqual('success', body.decode())
+
+ @util.only_python3
+ def test_write_to_tempfile_via_object(self):
+ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
+ f = tempfile.NamedTemporaryFile()
+ try:
+ self.curl.setopt(pycurl.WRITEDATA, f)
+ self.curl.perform()
+ f.seek(0)
+ body = f.read()
+ finally:
+ f.close()
+ self.assertEqual('success', body.decode())
+
+ def test_write_to_file_via_function(self):
+ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
+ dir = tempfile.mkdtemp()
+ try:
+ path = os.path.join(dir, 'pycurltest')
+ f = open(path, 'wb+')
+ try:
+ self.curl.setopt(pycurl.WRITEFUNCTION, f.write)
+ self.curl.perform()
+ f.seek(0)
+ body = f.read()
+ finally:
+ f.close()
+ finally:
+ shutil.rmtree(dir)
+ self.assertEqual('success', body.decode())
+
+ def test_write_to_file_via_object(self):
+ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
+ dir = tempfile.mkdtemp()
+ try:
+ path = os.path.join(dir, 'pycurltest')
+ f = open(path, 'wb+')
+ try:
+ self.curl.setopt(pycurl.WRITEDATA, f)
+ self.curl.perform()
+ f.seek(0)
+ body = f.read()
+ finally:
+ f.close()
+ finally:
+ shutil.rmtree(dir)
+ self.assertEqual('success', body.decode())
+
+ def test_write_to_file_like(self):
+ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
+ acceptor = Acceptor()
+ self.curl.setopt(pycurl.WRITEDATA, acceptor)
+ self.curl.perform()
+ self.assertEqual('success', acceptor.buffer)
diff --git a/tests/write_to_stringio_test.py b/tests/write_to_stringio_test.py
index 418a794..a27251b 100644
--- a/tests/write_to_stringio_test.py
+++ b/tests/write_to_stringio_test.py
@@ -1,9 +1,10 @@
#! /usr/bin/env python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
# vi:ts=4:et
import pycurl
import unittest
+import sys
from . import appmanager
from . import util
@@ -17,9 +18,24 @@ class WriteToStringioTest(unittest.TestCase):
def tearDown(self):
self.curl.close()
- def test_get(self):
+ def test_write_to_bytesio(self):
self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
- sio = util.StringIO()
+ sio = util.BytesIO()
self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
self.curl.perform()
- self.assertEqual('success', sio.getvalue())
+ self.assertEqual('success', sio.getvalue().decode())
+
+ @util.only_python3
+ def test_write_to_stringio(self):
+ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success')
+ # stringio in python 3
+ sio = util.StringIO()
+ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write)
+ try:
+ self.curl.perform()
+
+ self.fail('Should have received a write error')
+ except pycurl.error:
+ err, msg = sys.exc_info()[1].args
+ # we expect pycurl.E_WRITE_ERROR as the response
+ assert pycurl.E_WRITE_ERROR == err
diff --git a/winbuild.py b/winbuild.py
new file mode 100644
index 0000000..2ba10a4
--- /dev/null
+++ b/winbuild.py
@@ -0,0 +1,167 @@
+# work directory for downloading dependencies and building everything
+root = 'c:/dev/build-pycurl'
+# where msysgit is installed
+git_root = 'c:/program files/git'
+# which versions of python to build against
+python_versions = ['2.6', '2.7', '3.2', '3.3']
+# where pythons are installed
+python_path_template = 'c:/python%s/python'
+vc_paths = {
+ # where msvc 9 is installed, for python 2.6 through 3.2
+ 'vc9': 'c:/program files/microsoft visual studio 9.0',
+ # where msvc 10 is installed, for python 3.3
+ 'vc10': 'c:/program files/microsoft visual studio 10.0',
+}
+# whether to link libcurl against zlib
+use_zlib = False
+# which version of zlib to use, will be downloaded from internet
+zlib_version = '1.2.8'
+# which version of libcurl to use, will be downloaded from the internet
+libcurl_version = '7.34.0'
+# pycurl version to build, we should know this ourselves
+pycurl_version = '7.19.3'
+
+import os, os.path, sys, subprocess, shutil, contextlib
+
+archives_path = os.path.join(root, 'archives')
+state_path = os.path.join(root, 'state')
+git_bin_path = os.path.join(git_root, 'bin')
+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')
+for key in vc_paths:
+ vc_paths[key] = {
+ 'root': vc_paths[key],
+ 'vsvars': os.path.join(vc_paths[key], 'common7/tools/vsvars32.bat'),
+ }
+python_vc_versions = {
+ '2.6': 'vc9',
+ '2.7': 'vc9',
+ '3.2': 'vc9',
+ '3.3': 'vc10',
+}
+vc_versions = vc_paths.keys()
+
+try:
+ from urllib.request import urlopen
+except ImportError:
+ from urllib import urlopen
+
+def fetch(url, archive=None):
+ if archive is None:
+ archive = os.path.basename(url)
+ if not os.path.exists(archive):
+ sys.stdout.write("Fetching %s\n" % url)
+ io = urlopen(url)
+ with open('.tmp.%s' % archive, 'wb') as f:
+ while True:
+ chunk = io.read(65536)
+ if len(chunk) == 0:
+ break
+ f.write(chunk)
+ os.rename('.tmp.%s' % archive, archive)
+
+ at contextlib.contextmanager
+def in_dir(dir):
+ old_cwd = os.getcwd()
+ try:
+ os.chdir(dir)
+ yield
+ finally:
+ os.chdir(old_cwd)
+
+ at contextlib.contextmanager
+def step(step_fn, *args):
+ step = step_fn.__name__
+ if args:
+ step += '-' + '-'.join(args)
+ if not os.path.exists(state_path):
+ os.makedirs(state_path)
+ state_file_path = os.path.join(state_path, step)
+ if not os.path.exists(state_file_path):
+ step_fn(*args)
+ with open(state_file_path, 'w') as f:
+ pass
+
+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, vc_version):
+ suffixed_dir = '%s-%s' % (basename, vc_version)
+ if os.path.exists(suffixed_dir):
+ shutil.rmtree(suffixed_dir)
+ os.rename(basename, suffixed_dir)
+ return suffixed_dir
+
+def work():
+ os.environ['PATH'] += ";%s" % git_bin_path
+ if not os.path.exists(archives_path):
+ os.makedirs(archives_path)
+ with in_dir(archives_path):
+ def build_zlib(vc_version):
+ fetch('http://downloads.sourceforge.net/project/libpng/zlib/%s/zlib-%s.tar.gz' % (zlib_version, zlib_version))
+ untar('zlib-%s' % zlib_version)
+ zlib_dir = rename_for_vc('zlib-%s' % zlib_version, vc_version)
+ with in_dir(zlib_dir):
+ with open('doit.bat', 'w') as f:
+ f.write("call \"%s\"\n" % vc_paths[vc_version]['vsvars'])
+ f.write("nmake /f win32/Makefile.msc\n")
+ subprocess.check_call(['doit.bat'])
+
+ def build_curl(vc_version):
+ fetch('http://curl.haxx.se/download/curl-%s.tar.gz' % libcurl_version)
+ untar('curl-%s' % libcurl_version)
+ curl_dir = rename_for_vc('curl-%s' % libcurl_version, vc_version)
+ with in_dir(os.path.join(curl_dir, 'winbuild')):
+ with open('doit.bat', 'w') as f:
+ f.write("call \"%s\"\n" % vc_paths[vc_version]['vsvars'])
+ f.write("set include=%%include%%;%s\n" % os.path.join(archives_path, 'zlib-%s-%s' % (zlib_version, vc_version)))
+ f.write("set lib=%%lib%%;%s\n" % os.path.join(archives_path, 'zlib-%s-%s' % (zlib_version, vc_version)))
+ if use_zlib:
+ extra_options = ' WITH_ZLIB=dll'
+ else:
+ extra_options = ''
+ f.write("nmake /f Makefile.vc mode=dll ENABLE_IDN=no\n")
+ subprocess.check_call(['doit.bat'])
+ for vc_version in vc_versions:
+ if use_zlib:
+ step(build_zlib, vc_version)
+ step(build_curl, vc_version)
+
+ def prepare_pycurl():
+ #fetch('http://pycurl.sourceforge.net/download/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)
+
+ def build_pycurl(python_version, target):
+ python_path = python_path_template % python_version.replace('.', '')
+ vc_version = python_vc_versions[python_version]
+
+ with in_dir(os.path.join('pycurl-%s' % pycurl_version)):
+ if use_zlib:
+ libcurl_build_name = 'libcurl-vc-x86-release-dll-zlib-dll-ipv6-sspi-spnego-winssl'
+ else:
+ libcurl_build_name = 'libcurl-vc-x86-release-dll-ipv6-sspi-spnego-winssl'
+ curl_dir = '../curl-%s-%s/builds/%s' % (libcurl_version, vc_version, libcurl_build_name)
+ if not os.path.exists('build/lib.win32-%s' % python_version):
+ # exists for building additional targets for the same python version
+ os.makedirs('build/lib.win32-%s' % python_version)
+ shutil.copy(os.path.join(curl_dir, 'bin', 'libcurl.dll'), 'build/lib.win32-%s' % python_version)
+ with open('doit.bat', 'w') as f:
+ f.write("call \"%s\"\n" % vc_paths[vc_version]['vsvars'])
+ f.write("%s setup.py %s --curl-dir=%s --use-libcurl-dll\n" % (python_path, target, curl_dir))
+ subprocess.check_call(['doit.bat'])
+ if target == 'bdist':
+ os.rename('dist/pycurl-%s.win32.zip' % pycurl_version, 'dist/pycurl-%s.win32-py%s.zip' % (pycurl_version, python_version))
+
+ prepare_pycurl()
+ for python_version in python_versions:
+ for target in ['bdist', 'bdist_wininst', 'bdist_msi']:
+ build_pycurl(python_version, target)
+
+work()
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/pycurl.git
More information about the Python-modules-commits
mailing list