[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