[Python-modules-commits] [pycurl] 01/140: Import pycurl_7.13.0.orig.tar.gz

Barry Warsaw barry at moszumanska.debian.org
Wed Oct 1 21:44:59 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 5cdfa85e9eb0e7c8d73ce4e8a9a1ae8839a39b66
Author: Barry Warsaw <barry at python.org>
Date:   Wed Oct 1 16:43:19 2014 -0400

    Import pycurl_7.13.0.orig.tar.gz
---
 COPYING                       |  504 ++++++++
 ChangeLog                     |  731 +++++++++++
 INSTALL                       |   44 +
 MANIFEST.in                   |   22 +
 Makefile                      |   60 +
 PKG-INFO                      |   11 +
 README                        |   12 +
 TODO                          |   32 +
 doc/callbacks.html            |  140 +++
 doc/curlmultiobject.html      |  136 ++
 doc/curlobject.html           |  102 ++
 doc/pycurl.html               |  119 ++
 examples/basicfirst.py        |   27 +
 examples/file_upload.py       |   54 +
 examples/linksys.py           |  563 +++++++++
 examples/retriever-multi.py   |  125 ++
 examples/retriever.py         |   79 ++
 examples/sfquery.py           |   64 +
 examples/xmlrpc_curl.py       |   61 +
 python/curl/__init__.py       |  146 +++
 setup.py                      |  199 +++
 setup_win32_ssl.py            |   34 +
 src/Makefile                  |   19 +
 src/pycurl.c                  | 2732 +++++++++++++++++++++++++++++++++++++++++
 tests/test.py                 |   74 ++
 tests/test_cb.py              |   28 +
 tests/test_debug.py           |   16 +
 tests/test_getinfo.py         |   49 +
 tests/test_gtk.py             |   93 ++
 tests/test_internals.py       |  253 ++++
 tests/test_memleak.py         |   53 +
 tests/test_multi.py           |   33 +
 tests/test_multi2.py          |   72 ++
 tests/test_multi3.py          |   87 ++
 tests/test_multi4.py          |   57 +
 tests/test_multi5.py          |   60 +
 tests/test_multi6.py          |   62 +
 tests/test_multi_vs_thread.py |  262 ++++
 tests/test_post.py            |   24 +
 tests/test_post2.py           |   16 +
 tests/test_post3.py           |   32 +
 tests/test_stringio.py        |   25 +
 tests/test_xmlrpc.py          |   29 +
 tests/util.py                 |   38 +
 44 files changed, 7379 insertions(+)

diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..99dce33
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,504 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+

+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+

+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+

+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+

+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+

+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+

+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+

+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+

+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..f02bd3c
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,731 @@
+Version 7.13.0 [requires libcurl-7.13.0 or better]
+--------------
+
+2005-02-10  Kjetil Jacobsen  <kjetilja>
+
+        * Added file_upload.py to examples, shows how to upload
+          a file.
+
+        * Added CURLOPT_IOCTLFUNCTION/DATA.
+
+        * Added options from libcurl 7.13.0: FTP_ACCOUNT, SOURCE_URL,
+          SOURCE_QUOTE.
+
+        * Obsoleted options: SOURCE_HOST, SOURCE_PATH, SOURCE_PORT,
+          PASV_HOST.
+
+
+Version 7.12.3
+--------------
+
+2004-12-22  Markus F.X.J. Oberhumer <mfx>
+
+        * Added CURLINFO_NUM_CONNECTS and CURLINFO_SSL_ENGINES.
+
+        * Added some other missing constants.
+
+        * Updated pycurl.version_info() to return a 12-tuple
+          instead of a 9-tuple.
+
+
+Version 7.12.2
+--------------
+
+2004-10-15  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_FTPSSLAUTH (and CURLFTPAUTH_*).
+
+        * Added CURLINFO_OS_ERRNO.
+
+2004-08-17 Kjetil Jacobsen <kjetilja>
+
+        * Use LONG_LONG instead of PY_LONG_LONG to make pycurl compile
+          on Python versions < 2.3 (fix from Domenico Andreoli
+          <cavok at libero.it>).
+
+
+Version 7.12.1
+--------------
+
+2004-08-02  Kjetil Jacobsen  <kjetilja>
+
+        * Added INFOTYPE_SSL_DATA_IN/OUT.
+
+2004-07-16  Markus F.X.J. Oberhumer <mfx>
+
+        * WARNING: removed deprecated PROXY_, TIMECOND_ and non-prefixed
+          INFOTYPE constant names. See ChangeLog entry 2003-06-10.
+
+2004-06-21  Kjetil Jacobsen  <kjetilja>
+
+        * Added test program for HTTP post using the read callback (see
+          tests/test_post3.py for details).
+
+        * Use the new CURL_READFUNC_ABORT return code where appropriate
+          to avoid hanging in perform() when read callbacks are used.
+
+        * Added support for libcurl 7.12.1 CURLOPT features:
+          SOURCE_HOST, SOURCE_USERPWD, SOURCE_PATH, SOURCE_PORT,
+          PASV_HOST, SOURCE_PREQUOTE, SOURCE_POSTQUOTE.
+
+2004-06-08  Markus F.X.J. Oberhumer <mfx>
+
+        * Setting CURLOPT_POSTFIELDS now allows binary data and
+          automatically sets CURLOPT_POSTFIELDSIZE for you. If you really
+          want a different size you have to manually set POSTFIELDSIZE
+          after setting POSTFIELDS.
+          (Based on a patch by Martin Muenstermann).
+
+2004-06-05  Markus F.X.J. Oberhumer <mfx>
+
+        * Added stricter checks within the callback handlers.
+
+        * Unify the behaviour of int and long parameters where appropriate.
+
+
+Version 7.12
+------------
+
+2004-05-18  Kjetil Jacobsen  <kjetilja>
+
+        * WARNING: To simplify code maintenance pycurl now requires
+          libcurl 7.11.2 and Python 2.2 or newer to work.
+
+        * GC support is now always enabled.
+
+
+Version 7.11.3
+--------------
+
+2004-04-30  Kjetil Jacobsen  <kjetilja>
+
+        * Do not use the deprecated curl_formparse function.
+          API CHANGE: HTTPPOST now takes a list of tuples where each
+          tuple contains a form name and a form value, both strings
+          (see test/test_post2.py for example usage).
+
+        * Found a possible reference count bug in the multithreading
+          code which may have contributed to the long-standing GC
+          segfault which has haunted pycurl.  Fingers crossed.
+
+
+Version 7.11.2
+--------------
+
+2004-04-21  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for libcurl 7.11.2 CURLOPT features:
+          CURLOPT_TCP_NODELAY.
+
+2004-03-25 Kjetil Jacobsen   <kjetilja>
+
+        * Store Python longs in off_t with PyLong_AsLongLong instead
+          of PyLong_AsLong.  Should make the options which deal
+          with large files behave a little better.  Note that this
+          requires the long long support in Python 2.2 or newer to
+          work properly.
+
+
+Version 7.11.1
+--------------
+
+2004-03-16  Kjetil Jacobsen  <kjetilja>
+
+        * WARNING: Removed support for the PASSWDFUNCTION callback, which
+          is no longer supported by libcurl.
+
+2004-03-15  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for libcurl 7.11.1 CURLOPT features:
+          CURLOPT_POSTFIELDSIZE_LARGE.
+
+
+Version 7.11.0
+--------------
+
+2004-02-11  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for libcurl 7.11.0 CURLOPT features:
+          INFILESIZE_LARGE, RESUME_FROM_LARGE, MAXFILESIZE_LARGE
+          and FTP_SSL.
+
+        * Circular garbage collection support can now be enabled or
+          disabled by passing the '--use-gc=[1|0]' parameter to setup.py
+          when building pycurl.
+
+        * HTTP_VERSION options are known as CURL_HTTP_VERSION_NONE,
+          CURL_HTTP_VERSION_1_0, CURL_HTTP_VERSION_1_1 and
+          CURL_HTTP_VERSION_LAST.
+
+2003-11-16  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for these new libcurl 7.11.0 features:
+          CURLOPT_NETRC_FILE.
+
+
+Version 7.10.8
+--------------
+
+2003-11-04  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for these new libcurl 7.10.8 features:
+          CURLOPT_FTP_RESPONSE_TIMEOUT, CURLOPT_IPRESOLVE,
+          CURLOPT_MAXFILESIZE,
+          CURLINFO_HTTPAUTH_AVAIL, CURLINFO_PROXYAUTH_AVAIL,
+          CURL_IPRESOLVE_* constants.
+
+        * Added support for these new libcurl 7.10.7 features:
+          CURLOPT_FTP_CREATE_MISSING_DIRS, CURLOPT_PROXYAUTH,
+          CURLINFO_HTTP_CONNECTCODE.
+
+
+2003-10-28  Kjetil Jacobsen  <kjetilja>
+
+        * Added missing CURLOPT_ENCODING option (patch by Martijn
+          Boerwinkel <xim at xs4all.nl>)
+
+
+Version 7.10.6
+--------------
+
+2003-07-29  Markus F.X.J. Oberhumer <mfx>
+
+        * Started working on support for CURLOPT_SSL_CTX_FUNCTION and
+          CURLOPT_SSL_CTX_DATA (libcurl-7.10.6) - not yet finished.
+
+2003-06-10  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for CURLOPT_HTTPAUTH (libcurl-7.10.6), including
+          the new HTTPAUTH_BASIC, HTTPAUTH_DIGEST, HTTPAUTH_GSSNEGOTIATE
+          and HTTPAUTH_NTML constants.
+
+        * Some constants were renamed for consistency:
+
+          All curl_infotype constants are now prefixed with "INFOTYPE_",
+          all curl_proxytype constants are prefixed with "PROXYTYPE_" instead
+          of "PROXY_", and all curl_TimeCond constants are now prefixed
+          with "TIMECONDITION_" instead of "TIMECOND_".
+
+          (The old names are still available but will get removed
+          in a future release.)
+
+        * WARNING: Removed the deprecated pycurl.init() and pycurl.multi_init()
+          names - use pycurl.Curl() and pycurl.CurlMulti() instead.
+
+        * WARNING: Removed the deprecated Curl.cleanup() and
+          CurlMulti.cleanup() methods - use Curl.close() and
+          CurlMulti.close() instead.
+
+
+Version 7.10.5
+--------------
+
+2003-05-15  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for CURLOPT_FTP_USE_EPRT (libcurl-7.10.5).
+
+        * Documentation updates.
+
+2003-05-07  Eric S. Raymond  <esr at snark.thyrsus.com>
+
+        * Lifted all HTML docs to clean XHTML, verified by tidy.
+
+2003-05-02  Markus F.X.J. Oberhumer <mfx>
+
+        * Fixed some `int' vs. `long' mismatches that affected 64-bit systems.
+
+        * Fixed wrong pycurl.CAPATH constant.
+
+2003-05-01  Markus F.X.J. Oberhumer <mfx>
+
+        * Added new method Curl.errstr() which returns the internal
+        libcurl error buffer string of the handle.
+
+
+Version 7.10.4.2
+----------------
+
+2003-04-15  Markus F.X.J. Oberhumer <mfx>
+
+        * Allow compilation against the libcurl-7.10.3 release - some
+        recent Linux distributions (e.g. Mandrake 9.1) ship with 7.10.3,
+        and apart from the new CURLOPT_UNRESTRICTED_AUTH option there is
+        no need that we require libcurl-7.10.4.
+
+
+Version 7.10.4
+--------------
+
+2003-04-01  Kjetil Jacobsen  <kjetilja>
+
+        * Markus added CURLOPT_UNRESTRICTED_AUTH (libcurl-7.10.4).
+
+2003-02-25  Kjetil Jacobsen  <kjetilja>
+
+        * Fixed some broken test code and removed the fileupload test
+        since it didn't work properly.
+
+2003-01-28  Kjetil Jacobsen  <kjetilja>
+
+        * Some documentation updates by Markus and me.
+
+2003-01-22  Kjetil Jacobsen  <kjetilja>
+
+        * API CHANGE: the CurlMulti.info_read() method now returns
+        a separate array with handles that failed.  Each entry in this array
+        is a tuple with (curl object, error number, error message).
+        This addition makes it simpler to do error checking of individual
+        curl objects when using the multi interface.
+
+
+Version 7.10.3
+--------------
+
+2003-01-13  Kjetil Jacobsen  <kjetilja>
+
+        * PycURL memory usage has been reduced.
+
+2003-01-10  Kjetil Jacobsen  <kjetilja>
+
+        * Added 'examples/retriever-multi.py' which shows how to retrieve
+        a set of URLs concurrently using the multi interface.
+
+2003-01-09  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for CURLOPT_HTTP200ALIASES.
+
+2002-11-22  Kjetil Jacobsen  <kjetilja>
+
+        * Updated pycurl documentation in the 'doc' directory.
+
+2002-11-21  Kjetil Jacobsen  <kjetilja>
+
+        * Updated and improved 'examples/curl.py'.
+
+        * Added 'tests/test_multi6.py' which shows how to use the
+        info_read method with CurlMulti.
+
+2002-11-19  Kjetil Jacobsen  <kjetilja>
+
+        * Added new method CurlMulti.info_read().
+
+
+Version 7.10.2
+--------------
+
+2002-11-14  Kjetil Jacobsen <kjetilja>
+
+        * Free options set with setopt after cleanup is called, as cleanup
+        assumes that options are still valid when invoked.  This fixes the
+        bug with COOKIEJAR reported by Bastiaan Naber
+        <bastiaan at ricardis.tudelft.nl>.
+
+2002-11-06  Markus F.X.J. Oberhumer <mfx>
+
+        * Install documentation under /usr/share/doc instead of /usr/doc.
+        Also, start shipping the (unfinished) HTML docs and some
+        basic test scripts.
+
+2002-10-30  Markus F.X.J. Oberhumer <mfx>
+
+        * API CHANGE: For integral values, Curl.getinfo() now returns a
+        Python-int instead of a Python-long.
+
+
+Version 7.10.1
+--------------
+
+2002-10-03  Markus F.X.J. Oberhumer <mfx>
+
+        * Added new module-level function version_info() from
+        libcurl-7.10.
+
+
+Version 7.10
+------------
+
+2002-09-13  Kjetil Jacobsen  <kjetilja>
+
+        * Added commandline options to setup.py for specifying the path to
+        'curl-config' (non-windows) and the curl installation directory
+        (windows).  See the 'INSTALL' file for details.
+
+        * Added CURLOPT_ENCODING, CURLOPT_NOSIGNAL and CURLOPT_BUFFERSIZE
+        from libcurl-7.10 (by Markus Oberhumer).
+
+
+Version 7.9.8.4
+---------------
+
+2002-08-28  Kjetil Jacobsen  <kjetilja>
+
+        * Added a simple web-browser example based on gtkhtml and pycurl.
+        See the file 'examples/gtkhtml_demo.py' for details.  The example
+        requires a working installation of gnome-python with gtkhtml
+        bindings enabled (pass --with-gtkhtml to gnome-python configure).
+
+2002-08-14  Kjetil Jacobsen  <kjetilja>
+
+        * Added new method 'select' on CurlMulti objects.  Example usage
+        in 'tests/test_multi5.py'.  This method is just an optimization of
+        the combined use of fdset and select.
+
+2002-08-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for curl_multi_fdset.  See the file
+        'tests/test_multi4.py' for example usage.  Contributed by Conrad
+        Steenberg <conrad at hep.caltech.edu>.
+
+        * perform() on multi objects now returns a tuple (result, number
+        of handles) like the libcurl interface does.
+
+2002-08-08  Kjetil Jacobsen  <kjetilja>
+
+        * Added the 'sfquery' script which retrieves a SourceForge XML
+        export object for a given project.  See the file 'examples/sfquery.py'
+        for details and usage.  'sfquery' was contributed by Eric
+        S. Raymond <esr at thyrsus.com>.
+
+2002-07-20  Markus F.X.J. Oberhumer <mfx>
+
+        * API enhancements: added Curl() and CurlMulti() as aliases for
+        init() and multi_init(), and added close() methods as aliases
+        for the cleanup() methods. The new names much better match
+        the actual intended use of the objects, and they also nicely
+        correspond to Python's file object.
+
+        * Also, all constants for Curl.setopt() and Curl.getinfo() are now
+        visible from within Curl objects.
+
+        All changes are fully backward-compatible.
+
+
+Version 7.9.8.3
+---------------
+
+2002-07-16  Markus F.X.J. Oberhumer <mfx>
+
+        * Under Python 2.2 or better, Curl and CurlMulti objects now
+        automatically participate in cyclic garbarge collection
+        (using the gc module).
+
+
+Version 7.9.8.2
+---------------
+
+2002-07-05  Markus F.X.J. Oberhumer <mfx>
+
+        * Curl and CurlMulti objects now support standard Python attributes.
+        See tests/test_multi2.py for an example.
+
+2002-07-02  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for the multi-interface.
+
+
+Version 7.9.8.1
+---------------
+
+2002-06-25  Markus F.X.J. Oberhumer <mfx>
+
+        * Fixed a couple of `int' vs. `size_t' mismatches in callbacks
+        and Py_BuildValue() calls.
+
+2002-06-25  Kjetil Jacobsen  <kjetilja>
+
+        * Use 'double' type instead of 'size_t' for progress callbacks
+        (by Conrad Steenberg <conrad at hep.caltech.edu>).  Also cleaned up
+        some other type mismatches in the callback interfaces.
+
+2002-06-24  Kjetil Jacobsen  <kjetilja>
+
+        * Added example code on how to upload a file using HTTPPOST in
+        pycurl (code by Amit Mongia <amit_mongia at hotmail.com>).  See the
+        file 'test_fileupload.py' for details.
+
+
+Version 7.9.8
+-------------
+
+2002-06-24  Kjetil Jacobsen  <kjetilja>
+
+        * Resolved some build problems on Windows (by Markus Oberhumer).
+
+2002-06-19  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_CAPATH.
+
+        * Added option constants for CURLOPT_NETRC: CURL_NETRC_OPTIONAL,
+        CURL_NETRC_IGNORED and CURL_NETRC_REQUIRED.
+
+        * Added option constants for CURLOPT_TIMECONDITION:
+        TIMECOND_IFMODSINCE and TIMECOND_IFUNMODSINCE.
+
+        * Added an simple example crawler, which downloads documents
+        listed in a file with a configurable number of worker threads.
+        See the file 'crawler.py' in the 'tests' directory for details.
+
+        * Removed the redundant 'test_xmlrpc2.py' test script.
+
+        * Disallow recursive callback invocations (by Markus Oberhumer).
+
+2002-06-18  Kjetil Jacobsen  <kjetilja>
+
+        * Made some changes to setup.py which should fix the build
+        problems on RedHat 7.3 (suggested by Benji <benji at kioza.net>).
+
+        * Use CURLOPT_READDATA instead of CURLOPT_INFILE, and
+        CURLOPT_WRITEDATA instead of CURLOPT_FILE.  Also fixed some
+        reference counting bugs with file objects.
+
+        * CURLOPT_FILETIME and CURLINFO_FILETIME had a namespace clash
+        which caused them not to work.  Use OPT_FILETIME for setopt() and
+        INFO_FILETIME for getinfo().  See example usage in
+        'test_getinfo.py' for details.
+
+
+Version 7.9.7
+-------------
+
+2002-05-20  Kjetil Jacobsen  <kjetilja>
+
+        * New versioning scheme.  Pycurl now has the same version number
+        as the libcurl version it was built with.  The pycurl version
+        number thus indicates which version of libcurl is required to run.
+
+2002-05-17  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLINFO_REDIRECT_TIME and CURLINFO_REDIRECT_COUNT.
+
+2002-04-27  Kjetil Jacobsen  <kjetilja>
+
+        * Fixed potential memory leak and thread race (by Markus
+        Oberhumer).
+
+
+Version 0.4.9
+-------------
+
+2002-04-15  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_DEBUGFUNCTION to allow debug callbacks to be
+        specified (see the file 'test_debug.py' for details on how to use
+        debug callbacks).
+
+        * Added CURLOPT_DNS_USE_GLOBAL_CACHE and
+        CURLOPT_DNS_CACHE_TIMEOUT.
+
+        * Fixed a segfault when finalizing curl objects in Python 1.5.2.
+
+        * Now requires libcurl 7.9.6 or greater.
+
+2002-04-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added 'test_post2.py' file which is another example on how to
+        issue POST requests.
+
+2002-04-11  Markus F.X.J. Oberhumer <mfx>
+
+        * Added the 'test_post.py' file which demonstrates the use of
+        POST requests.
+
+
+Version 0.4.8
+-------------
+
+2002-03-07  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_PREQUOTE.
+
+        * Now requires libcurl 7.9.5 or greater.
+
+        * Other minor code cleanups and bugfixes.
+
+2002-03-05  Kjetil Jacobsen  <kjetilja>
+
+        * Do not allow WRITEFUNCTION and WRITEHEADER on the same handle.
+
+
+Version 0.4.7
+-------------
+
+2002-02-27  Kjetil Jacobsen  <kjetilja>
+
+        * Abort callback if the thread state of the calling thread cannot
+        be determined.
+
+        * Check that the installed version of libcurl matches the
+        requirements of pycurl.
+
+2002-02-26  Kjetil Jacobsen  <kjetilja>
+
+        * Clarence Garnder <clarence at silcom.com> found a bug where string
+        arguments to setopt sometimes were prematurely deallocated, this
+        should now be fixed.
+
+2002-02-21  Kjetil Jacobsen  <kjetilja>
+
+        * Added the 'xmlrpc_curl.py' file which implements a transport
+        for xmlrpclib (xmlrpclib is part of Python 2.2).
+
+        * Added CURLINFO_CONTENT_TYPE.
+
+        * Added CURLOPT_SSLCERTTYPE, CURLOPT_SSLKEY, CURLOPT_SSLKEYTYPE,
+        CURLOPT_SSLKEYPASSWD, CURLOPT_SSLENGINE and
+        CURLOPT_SSLENGINE_DEFAULT.
+
+        * When thrown, the pycurl.error exception is now a tuple consisting
+        of the curl error code and the error message.
+
+        * Now requires libcurl 7.9.4 or greater.
+
+2002-02-19  Kjetil Jacobsen  <kjetilja>
+
+        * Fixed docstring for getopt() function.
+
+2001-12-18  Kjetil Jacobsen  <kjetilja>
+
+        * Updated the INSTALL information for Win32.
+
+2001-12-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added missing link flag to make pycurl build on MacOS X (by Matt
+        King <matt at gnik.com>).
+
+2001-12-06  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLINFO_STARTTRANSFER_TIME and CURLOPT_FTP_USE_EPSV from
+        libcurl 7.9.2.
+
+2001-12-01  Markus F.X.J. Oberhumer <mfx>
+
+        * Added the 'test_stringio.py' file which demonstrates the use of
+        StringIO objects as callback.
+
+2001-12-01  Markus F.X.J. Oberhumer <mfx>
+
+        * setup.py: Do not remove entries from a list while iterating
+        over it.
+
+2001-11-29  Kjetil Jacobsen  <kjetilja>
+
+        * Added code in setup.py to install on Windows.  Requires some
+        manual configuration (by Tino Lange <Tino.Lange at gmx.de>).
+
+2001-11-27  Kjetil Jacobsen  <kjetilja>
+
+        * Improved detection of where libcurl is installed in setup.py.
+        Should make it easier to install pycurl when libcurl is not
+        located in regular lib/include paths.
+
+2001-11-05  Kjetil Jacobsen  <kjetilja>
+
+        * Some of the newer options to setopt were missing, this should
+        now be fixed.
+
+2001-11-04  Kjetil Jacobsen  <kjetilja>
+
+        * Exception handling has been improved and should no longer throw
+        spurious exceptions (by Markus F.X.J. Oberhumer
+        <markus at oberhumer.com>).
+
+2001-10-15  Kjetil Jacobsen  <kjetilja>
+
+        * Refactored the test_gtk.py script to avoid global variables.
+
+2001-10-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added module docstrings, terse perhaps, but better than nothing.
+
+        * Added the 'basicfirst.py' file which is a Python version of the
+        corresponding Perl script by Daniel.
+
+        * PycURL now works properly under Python 1.5 and 1.6 (by Markus
+        F.X.J. Oberhumer <markus at oberhumer.com>).
+
+        * Allow C-functions and Python methods as callbacks (by Markus
+        F.X.J. Oberhumer <markus at oberhumer.com>).
+
+        * Allow None as success result of write, header and progress
+        callback invocations (by Markus F.X.J. Oberhumer
+        <markus at oberhumer.com>).
+
+        * Added the 'basicfirst2.py' file which demonstrates the use of a
+        class method as callback instead of just a function.
+
+2001-08-21  Kjetil Jacobsen  <kjetilja>
+
+        * Cleaned up the script with GNOME/PycURL integration.
+
+2001-08-20  Kjetil Jacobsen  <kjetilja>
+
+        * Added another test script for shipping XML-RPC requests which
+        uses py-xmlrpc to encode the arguments (tests/test_xmlrpc2.py).
+
+2001-08-20  Kjetil Jacobsen  <kjetilja>
+
+        * Added test script for using PycURL and GNOME (tests/test_gtk.py).
+
+2001-08-20  Kjetil Jacobsen  <kjetilja>
+
+        * Added test script for using XML-RPC (tests/test_xmlrpc.py).
+
+        * Added more comments to the test sources.
+
+2001-08-06  Kjetil Jacobsen  <kjetilja>
+
+        * Renamed module namespace to pycurl instead of curl.
+
+2001-08-06  Kjetil Jacobsen  <kjetilja>
+
+        * Set CURLOPT_VERBOSE to 0 by default.
+
+2001-06-29  Kjetil Jacobsen  <kjetilja>
+
+        * Updated INSTALL, curl version 7.8 or greater is now mandatory to
+        use pycurl.
+
+2001-06-13  Kjetil Jacobsen  <kjetilja>
+
+        * Set NOPROGRESS to 1 by default.
+
+2001-06-07  Kjetil Jacobsen  <kjetilja>
+
+        * Added global_init/cleanup.
+
+2001-06-06  Kjetil Jacobsen  <kjetilja>
+
+        * Added HEADER/PROGRESSFUNCTION callbacks (see files in tests/).
+
+        * Added PASSWDFUNCTION callback (untested).
+
+        * Added READFUNCTION callback (untested).
+
+2001-06-05  Kjetil Jacobsen  <kjetilja>
+
+        * WRITEFUNCTION callbacks now work (see tests/test_cb.py for details).
+
+        * Preliminary distutils installation.
+
+        * Added CLOSEPOLICY constants to module namespace.
+
+2001-06-04  Kjetil Jacobsen  <kjetilja>
+
+        * Return -1 on error from Python callback in WRITEFUNCTION callback.
+
+2001-06-01  Kjetil Jacobsen  <kjetilja>
+
+        * Moved source to src and tests to tests directory.
+
+2001-05-31  Kjetil Jacobsen  <kjetilja>
+
+        * Added better type checking for setopt.
+
+2001-05-30  Kjetil Jacobsen  <kjetilja>
+
+        * Moved code to sourceforge.
+
+        * Added getinfo support.
+
+
+# vi:ts=8:et
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..ef42cf6
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,44 @@
+NOTE: You need Python and libcurl installed on your system to use or
+build pycurl.  Some RPM distributions of curl/libcurl do not include
+everything necessary to build pycurl, in which case you need to
+install the developer specific RPM which is usually called curl-dev.
+
+
+Distutils
+---------
+
+Assuming that distutils is installed (which it is by default on Python
+versions greater than 1.5.2) build and install pycurl with the
+following commands:
+
+    (if necessary, become root)
+    tar -zxvf pycurl-$VER.tar.gz
+    cd pycurl-$VER
+    python setup.py install
+
+$VER should be substituted with the version number, e.g. 7.10.5.
+
+Note that the installation script assumes that 'curl-config' can be
+located in your path setting.  If curl-config is installed outside
+your path or you want to force installation to use a particular
+version of curl-config, use the '--curl-config' commandline option to
+specify the location of curl-config.  Example:
+
+    python setup.py install --curl-config=/usr/local/bin/curl-config
+
+If libcurl is linked dynamically with pycurl, you may have to alter the
+LD_LIBRARY_PATH environment variable accordingly.  This normally
+applies only if there is more than one version of libcurl installed,
+e.g. one in /usr/lib and one in /usr/local/lib.
+
+
+Windows
+-------
+
+When installing on Windows, you need to manually configure the path to
+the curl source tree, specified with the CURL_DIR variable in the file
+'setup.py'.  The CURL_DIR variable can also be set using the
+commandline option '--curl-dir' when invoking setup.py:
+
+    python setup.py install --curl-dir=c:\curl-7.10.5
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..f4e3837
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,22 @@
+#
+# MANIFEST.in
+# Manifest template for creating the source distribution.
+#
+
+include ChangeLog
+include COPYING
+include INSTALL
+include Makefile
+include README
+include TODO
+include MANIFEST.in
+include src/Makefile
+include src/pycurl.c
+include python/curl/*.py
+include examples/*.py
+include tests/*.py
+include doc/*.html
+include setup_win32_ssl.py
+
+# exclude unfinished test scripts
+#exclude tests/test_multi_vs_thread.py
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9b2369d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,60 @@
+#
+# to use a specific python version call
+#   `make PYTHON=python2.2'
+#
+
+SHELL = /bin/sh
+
+PYTHON = python2.3
+PYTHON = python
+
+all build:
+	$(PYTHON) setup.py build
+
+build-7.10.8:
+	$(PYTHON) setup.py build --curl-config=/home/hosts/localhost/packages/curl-7.10.8/bin/curl-config
+
+test: build
+	$(PYTHON) tests/test_internals.py -q
+
+# (needs GNU binutils)
+strip: build
+	strip -p --strip-unneeded build/lib*/*.so
+	chmod -x build/lib*/*.so
+
+install install_lib:
+	$(PYTHON) setup.py $@
+
+clean:
+	-rm -rf build dist
+	-rm -f *.pyc *.pyo */*.pyc */*.pyo */*/*.pyc */*/*.pyo
+	-rm -f MANIFEST
+	cd src && $(MAKE) clean
+
+distclean: clean
+
+maintainer-clean: distclean
+
+dist sdist: distclean
+	$(PYTHON) setup.py sdist
+
+# target for maintainer
+windist: distclean
+	rm -rf build
+	python2.2 setup.py bdist_wininst
+	rm -rf build
+	python2.3 setup.py bdist_wininst
+	rm -rf build
+	python2.4 setup.py bdist_wininst
+	rm -rf build
+	python2.2 setup_win32_ssl.py bdist_wininst
+	rm -rf build
+	python2.3 setup_win32_ssl.py bdist_wininst
+	rm -rf build
+	python2.4 setup_win32_ssl.py bdist_wininst
+	rm -rf build
+
+
+.PHONY: all build test strip install install_lib clean distclean maintainer-clean dist sdist windist
+
+.NOEXPORT:
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..7012c4e
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,11 @@
+Metadata-Version: 1.0
+Name: pycurl
+Version: 7.13.0
+Summary: PycURL -- cURL library module for Python
+Home-page: http://pycurl.sourceforge.net/
+Author: Kjetil Jacobsen, Markus F.X.J. Oberhumer
+Author-email: kjetilja at cs.uit.no, markus at oberhumer.com
+License: GNU Lesser General Public License (LGPL)
+Description: 
+        This module provides Python bindings for the cURL library.
+Platform: All
diff --git a/README b/README
new file mode 100644
index 0000000..bd04ab6
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+LICENSE
+-------
+
+Copyright (C) 2001-2005 by Kjetil Jacobsen <kjetilja at cs.uit.no>
+Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer <markus at oberhumer.com>
+
+PycURL is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+A full copy of the LGPL license is included in the file COPYING.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..8696e58
--- /dev/null
+++ b/TODO
@@ -0,0 +1,32 @@
+# $Id: TODO,v 1.92 2005/02/08 11:46:05 kjetilja Exp $
+# vi:ts=4:et
+
+If you want to hack on pycurl, here's our list of unresolved issues:
+
+
+NEW FEATURES/IMPROVEMENTS:
+
+    * Support more of the CURLFORM_* API in HTTPPOST?
+
+    * Add docs to the high-level interface.
+
+    * Add more options to the undocumented and currently mostly useless
+      Curl.unsetopt() method. Have to carefully check the libcurl source
+      code for each option we want to support.
+
+    * curl_easy_reset() should probably be supported.  But we have to be
+      careful since curl_easy_reset() e.g. modifies callbacks and other
+      pointers which could leave pycurl and libcurl out of sync.
+
+    * Use METH_O and METH_NOARGS where appropriate instead of METH_VARAGS.
+
+
+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
new file mode 100644
index 0000000..e300c4d
--- /dev/null
+++ b/doc/callbacks.html
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PyCurl: Callbacks</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<h1>Callbacks</h1>
+
+<p>For more fine-grained control, libcurl allows a
+number of callbacks to be associated with each connection. In
+pycurl, callbacks are defined using the <code>setopt()</code> method for
+Curl objects with options WRITEFUNCTION, READFUNCTION, HEADERFUNCTION,
+PROGRESSFUNCTION, IOCTLFUNCTION, or DEBUGFUNCTION. These options
+correspond to the libcurl options with CURLOPT_* prefix removed.  A
+callback in pycurl must be either a regular Python function, a class
+method or an extension type function.</p>
+
+<p>There are some limitations to some of the options which can be used
+concurrently with the pycurl callbacks compared to the libcurl callbacks.
+This is to allow different callback functions to be associated with
+different Curl objects.  More specifically, WRITEDATA cannot
+be used with WRITEFUNCTION, READDATA cannot be used with READFUNCTION,
+WRITEHEADER cannot be used with HEADERFUNCTION, PROGRESSDATA cannot be
+used with PROGRESSFUNCTION, IOCTLDATA cannot be used with IOCTLFUNCTION,
+and DEBUGDATA cannot be used with DEBUGFUNCTION.
+In practice, these limitations can be overcome by having a callback
+function be a class instance method and rather use the class instance
+attributes to store per object data such as files used in the callbacks.
+</p>
+
+The signature of each callback used in pycurl is as follows:<br/>
+<br/>
+<code>WRITEFUNCTION(</code><em>string</em><code>) </code><em>-> number of characters written<br/>
+</em>
+<br/>
+<code>READFUNCTION(</code><em>number of characters to read</em><code>)</code><em>->
+string</em><br/>
+<br/>
+<code>HEADERFUNCTION(</code><em>string</em><code>)</code><em> -> number of characters written<br/>
+</em><br/>
+<code>PROGRESSFUNCTION(</code><em>download total, downloaded, upload total, uploaded</em><code>) </code><em>-> status</em><br/>
+<br/>
+<code>DEBUGFUNCTION(</code><em>debug message type, debug message string</em><code>)</code>
+<em>-> None<br/></em>
+<br/>
+<code>IOCTLFUNCTION(</code><em>ioctl cmd</em><code>)</code>
+<em>-> status<br/></em>
+<br/>
+<hr/>
+
+<h2>Example: Callbacks for document header and body</h2>
+
+<p>This example prints the header data to stderr and the body data to
+stdout.  Also note that neither callback returns the number of bytes
+written.  For WRITEFUNCTION and HEADERFUNCTION callbacks, returning
+None implies that all bytes where written.</p>
+
+<pre>
+    ## Callback function invoked when body data is ready
+    def body(buf):
+        # Print body data to stdout
+        import sys
+        sys.stdout.write(buf)
+        # Returning None implies that all bytes were written
+
+    ## Callback function invoked when header data is ready
+    def header(buf):
+        # Print header data to stderr
+        import sys
+        sys.stderr.write(buf)
+        # Returning None implies that all bytes were written
+
+    c = pycurl.Curl()
+    c.setopt(pycurl.URL, "http://www.python.org/")
+    c.setopt(pycurl.WRITEFUNCTION, body)
+    c.setopt(pycurl.HEADERFUNCTION, header)
+    c.perform()
+</pre>
+
+<h2>Example: Download/upload progress callback</h2>
+
+<p>This example shows how to use the progress callback.  When downloading
+a document, the arguments related to uploads are zero, and vice versa.</p>
+
+<pre>
+    ## Callback function invoked when download/upload has progress
+    def progress(download_t, download_d, upload_t, upload_d):
+        print "Total to download", download_t
+        print "Total downloaded", download_d
+        print "Total to upload", upload_t
+        print "Total uploaded", upload_d
+
+    c.setopt(c.URL, "http://slashdot.org/")
+    c.setopt(c.NOPROGRESS, 0)
+    c.setopt(c.PROGRESSFUNCTION, progress)
+    c.perform()
+</pre>
+
+<h2>Example: Debug callbacks</h2>
+
+<p>This example shows how to use the debug callback.  The debug message
+type is an integer indicating the type of debug message.  The
+VERBOSE option must be enabled for this callback to be invoked.</p>
+
+<pre>
+    def test(debug_type, debug_msg):
+        print "debug(%d): %s" % (debug_type, debug_msg)
+
+    c = pycurl.Curl()
+    c.setopt(pycurl.URL, "http://curl.haxx.se/")
+    c.setopt(pycurl.VERBOSE, 1)
+    c.setopt(pycurl.DEBUGFUNCTION, test)
+    c.perform()
+</pre>
+
+<h2>Other examples</h2>
+The pycurl distribution also contains a number of test scripts and
+examples which show how to use the various callbacks in libcurl.
+For instance, the file 'examples/file_upload.py' in the distribution contains
+example code for using READFUNCTION, 'tests/test_cb.py' shows
+WRITEFUNCTION and HEADERFUNCTION, 'tests/test_debug.py' shows DEBUGFUNCTION,
+and 'tests/test_getinfo.py' shows PROGRESSFUNCTION.</p>
+
+
+<hr />
+<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: callbacks.html,v 1.15 2005/02/10 11:35:23 kjetilja Exp $
+</p>
+
+</body>
+</html>
diff --git a/doc/curlmultiobject.html b/doc/curlmultiobject.html
new file mode 100644
index 0000000..812111d
--- /dev/null
+++ b/doc/curlmultiobject.html
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PycURL: CurlMulti Objects</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<h1>CurlMulti Object</h1>
+
+<p>CurlMulti objects have the following methods: </p>
+
+<dl>
+<dt><code>close()</code> -> <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_cleanup.html"><code>curl_multi_cleanup()</code></a> in libcurl.
+This method is automatically called by pycurl when a CurlMulti object no
+longer has any references to it, but can also be called
+explicitly.</p>
+</dd>
+
+<dt><code>perform()</code> -> <em>tuple of status and the number of active Curl objects</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_perform.html"><code>curl_multi_perform()</code></a> in libcurl.</p>
+</dd>
+
+<dt><code> add_handle(</code><em>Curl object</em><code>) </code>-> <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_add_handle.html"><code>curl_multi_add_handle()</code></a> in libcurl.
+This method  adds an existing and valid Curl object to the CurlMulti
+object.</p>
+
+<p>IMPORTANT NOTE: add_handle does not implicitly add a Python reference
+to the Curl object (and thus does not increase the reference count on the Curl
+object).</p>
+</dd>
+
+<dt><code>remove_handle(</code><em>Curl object</em><code>)</code> -> <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_remove_handle.html"><code>curl_multi_remove_handle()</code></a> in libcurl.
+This method removes an existing and valid Curl object from the CurlMulti
+object.</p>
+
+<p>IMPORTANT NOTE: remove_handle does not implicitly remove a Python reference
+from the Curl object (and thus does not decrease the reference count on the Curl
+object).</p>
+</dd>
+
+<dt><code>fdset()</code> ->
+<em>triple of lists with active file descriptors,
+readable,  writeable, exceptions.</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_fdset.html"><code>curl_multi_fdset()</code></a> in libcurl.
+This method extracts  the file descriptor information from a CurlMulti object.
+The returned  lists can be used with the <code>select</code> module to
+poll for events.</p>
+
+<p>Example usage:</p>
+
+<pre>
+import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://curl.haxx.se")
+m = pycurl.CurlMulti()
+m.add_handle(c)
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM: break
+while num_handles:
+    apply(select.select, m.fdset() + (1,))
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM: break
+</pre>
+</dd>
+
+<dt><code>select(</code><em>[timeout]</em><code>)</code> ->
+<em>number of ready file descriptors or -1 on timeout</em></dt>
+<dd>
+<p>This is a convenience function which simplifies the combined
+use of <code>fdset()</code> and the <code>select</code> module.</p>
+
+<p>Example usage:</p>
+
+<pre>import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://curl.haxx.se")
+m = pycurl.CurlMulti()
+m.add_handle(c)
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM: break
+while num_handles:
+    ret = m.select()
+    if ret == -1:  continue
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM: break
+</pre>
+</dd>
+
+<dt><code>info_read(</code><em>[max]</em><code>)</code> ->
+<em>numberof queued messages, a list of successful objects, a list of
+failed objects</em></dt>
+<dd>
+<p>Corresponds to the
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_info_read.html"><code>curl_multi_info_read()</code></a> function in libcurl.
+This method extracts at most <em>max</em> messages
+from the multi stack and returns them in two lists. The first
+list contains the handles which completed successfully and the second
+list contains a tuple <em><curl object, curl error number, curl
+error message></em> for each failed curl object. The number
+of queued messages after this method has been called is also
+returned.</p>
+</dd>
+</dl>
+
+<hr />
+<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: curlmultiobject.html,v 1.4 2004/06/05 17:59:01 mfx Exp $
+</p>
+
+</body>
+</html>
diff --git a/doc/curlobject.html b/doc/curlobject.html
new file mode 100644
index 0000000..281ba2e
--- /dev/null
+++ b/doc/curlobject.html
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PycURL: Curl Objects</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<h1>Curl Object</h1>
+
+<p>Curl objects have the following methods:</p>
+
+<dl>
+<dt><code>close()</code> -> <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_cleanup.html"><code>curl_easy_cleanup</code></a> in libcurl.
+This method is automatically called by pycurl when a Curl object no longer has
+any references to it, but can also be called explicitly.</p>
+</dd>
+
+<dt><code>perform()</code> -> <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_perform.html"><code>curl_easy_perform</code></a> in libcurl.</p>
+</dd>
+
+<dt><code>setopt(</code><em>option, value</em><code>)</code> -> <em>None</em></dt>
+<dd>
+
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_setopt.html"><code>curl_easy_setopt</code></a> in libcurl, where
+<em>option</em> is specified with the CURLOPT_* constants in libcurl,
+except that the CURLOPT_ prefix has been removed. The type for
+<em>value</em> depends on the option, and can be either a string,
+integer, long integer, file objects, lists, or functions.</p>
+
+<p>Example usage:</p>
+
+<pre>
+import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://www.python.org/")
+c.setopt(pycurl.HTTPHEADER, ["User-Agent: PycURL test", "Accept:"])
+import StringIO
+b = StringIO.StringIO()
+c.setopt(pycurl.WRITEFUNCTION, b.write)
+c.setopt(pycurl.FOLLOWLOCATION, 1)
+c.setopt(pycurl.MAXREDIRS, 5)
+c.perform()
+print b.getvalue()
+...
+</pre>
+</dd>
+
+<dt><code>getinfo(</code><em>option</em><code>) </code>-> <em>Result</em></dt>
+<dd>
+
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html"><code>curl_easy_getinfo</code></a> in libcurl, where
+<em>option</em> is the same as the CURLINFO_* constants in libcurl,
+except that the CURLINFO_ prefix has been removed.
+<em>Result</em> contains an integer, float or string, depending on
+which option is given. The <code>getinfo</code> method should
+not be called unless <code>perform</code> has been called and
+finished.</p>
+
+<p>Example usage:</p>
+
+<pre>
+import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://sf.net")
+c.setopt(pycurl.FOLLOWLOCATION, 1)
+c.perform()
+print c.getinfo(pycurl.HTTP_CODE), c.getinfo(pycurl.EFFECTIVE_URL)
+...
+--> 200 "http://sourceforge.net/"
+</pre>
+</dd>
+
+<dt><code>errstr()</code> -> <em>String</em></dt>
+<dd>
+<p>Returns the internal libcurl error buffer of this handle as a string.</p>
+</dd>
+</dl>
+
+
+<hr />
+<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: curlobject.html,v 1.13 2004/03/15 10:57:04 kjetilja Exp $
+</p>
+
+</body>
+</html>
diff --git a/doc/pycurl.html b/doc/pycurl.html
new file mode 100644
index 0000000..bdbcc10
--- /dev/null
+++ b/doc/pycurl.html
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PycURL Documentation</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<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
+has been successfully built and tested with Python versions from
+2.2 to the current 2.4.x releases.</p>
+
+<p>libcurl is a client-side URL transfer library supporting FTP, FTPS,
+HTTP, HTTPS, GOPHER, TELNET, DICT, FILE and LDAP.  libcurl
+also supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploads, proxies,
+cookies, basic authentication, file transfer resume of FTP sessions, HTTP
+proxy tunneling and more.</p>
+
+<p>All the functionality provided by libcurl can used through the
+pycurl interface. The following subsections describe how to use the
+pycurl interface, and assume familiarity with how libcurl works.  For
+information on how libcurl works, please consult the curl library web pages
+(<a href="http://curl.haxx.se/libcurl/c/">http://curl.haxx.se/libcurl/c/</a>).</p>
+
+<hr/>
+
+<h1>Module Functionality</h1>
+
+<dl>
+<dt><code>pycurl.global_init(</code><em>option</em><code>)</code> -><em>None</em></dt>
+
+<dd><p><em>option</em> is one of the constants
+pycurl.GLOBAL_SSL, pycurl.GLOBAL_WIN32, pycurl.GLOBAL_ALL,
+pycurl.GLOBAL_NOTHING, pycurl.GLOBAL_DEFAULT.  Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_global_init.html"><code>curl_global_init()</code></a> in libcurl.</p>
+</dd>
+
+<dt><code>pycurl.global_cleanup()</code> -> <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_global_cleanup.html"><code>curl_global_cleanup()</code></a> in libcurl.</p>
+</dd>
+
+<dt><code>pycurl.version</code></dt>
+
+<dd><p>This is a string with version information on libcurl,
+corresponding to
+<a href="http://curl.haxx.se/libcurl/c/curl_version.html"><code>curl_version()</code></a> in libcurl.</p>
+
+<p>Example usage:</p>
+<pre>
+>>> import pycurl
+>>> pycurl.version
+'libcurl/7.12.3 OpenSSL/0.9.7e zlib/1.2.2.1 libidn/0.5.12'
+</pre>
+</dd>
+
+<dt><code>pycurl.version_info()</code> -> <em>Tuple</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_version_info.html"><code>curl_version_info()</code></a> in libcurl.
+Returns a tuple of information which is similar to the
+<code>curl_version_info_data</code> struct returned by
+<code>curl_version_info()</code> in libcurl.</p>
+
+<p>Example usage:</p>
+<pre>
+>>> import pycurl
+>>> pycurl.version_info()
+(2, '7.12.3', 461827, 'i586-pc-linux-gnu', 1565, 'OpenSSL/0.9.7e', 9465951,
+'1.2.2.1', ('ftp', 'gopher', 'telnet', 'dict', 'ldap', 'http', 'file',
+'https', 'ftps'), None, 0, '0.5.12')
+</pre>
+</dd>
+
+<dt><code>pycurl.Curl()</code> -> <em>Curl object</em></dt>
+<dd>
+<p>This function creates a new
+<a href="curlobject.html">Curl object</a> which corresponds to a
+<code>CURL</code> handle in libcurl. Curl objects automatically
+set CURLOPT_VERBOSE to 0, CURLOPT_NOPROGRESS to 1 and
+CURLOPT_ERRORBUFFER to point to a private error buffer.</p>
+</dd>
+
+<dt><code>pycurl.CurlMulti()</code> -> <em>CurlMulti object</em></dt>
+<dd>
+<p>This function creates a new
+<a href="curlmultiobject.html">CurlMulti object</a> which corresponds to
+a <code>CURLM</code> handle in libcurl.</p>
+</dd>
+</dl>
+
+<hr/>
+
+<h1>Subsections</h1>
+
+<ul>
+  <li><a href="curlobject.html">Curl objects</a></li>
+  <li><a href="curlmultiobject.html">CurlMulti objects</a></li>
+  <li><a href="callbacks.html">Callbacks</a></li>
+</ul>
+
+<hr />
+<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: pycurl.html,v 1.27 2004/12/22 14:27:16 mfx Exp $
+</p>
+
+</body>
+</html>
diff --git a/examples/basicfirst.py b/examples/basicfirst.py
new file mode 100644
index 0000000..a63c4be
--- /dev/null
+++ b/examples/basicfirst.py
@@ -0,0 +1,27 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: basicfirst.py,v 1.4 2003/05/07 17:46:58 esr Exp $
+
+import sys
+import pycurl
+
+class Test:
+    def __init__(self):
+        self.contents = ''
+
+    def body_callback(self, buf):
+        self.contents = self.contents + buf
+
+print >>sys.stderr, 'Testing', pycurl.version
+
+t = Test()
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://curl.haxx.se/dev/')
+c.setopt(c.WRITEFUNCTION, t.body_callback)
+c.setopt(c.HTTPHEADER, ["I-am-a-silly-programmer: yes indeed you are",
+                        "User-Agent: Python interface for libcURL"])
+c.perform()
+c.close()
+
+print t.contents
diff --git a/examples/file_upload.py b/examples/file_upload.py
new file mode 100644
index 0000000..ad690cb
--- /dev/null
+++ b/examples/file_upload.py
@@ -0,0 +1,54 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: file_upload.py,v 1.3 2005/02/10 11:26:23 kjetilja Exp $
+
+import pycurl
+import sys
+import os.path
+
+# Class which holds a file reference and the read callback
+class filereader:
+
+    def __init__(self, f):
+        self.f = f
+
+    def read_callback(self, size):
+        return self.f.read(size)
+
+# Check commandline arguments
+if len(sys.argv) < 3:
+    print "Usage: %s <url> <file to upload>" % sys.argv[0]
+    raise SystemExit
+else:
+    url = sys.argv[1]
+    filename = sys.argv[2]
+
+if not os.path.exists(filename):
+    print "Error: the file '%s' does not exist" % filename
+    raise SystemExit
+
+# Initialize pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, url)
+c.setopt(pycurl.UPLOAD, 1)
+
+# Two versions with the same semantics here, but the filereader version
+# is useful when you have to process the data which is read before returning
+if 1:
+    c.setopt(pycurl.READFUNCTION, filereader(open(filename, 'rb')).read_callback)
+else:
+    c.setopt(pycurl.READFUNCTION, open(filename, 'rb').read)
+
+# Set size of file to be uploaded, use LARGE option if file size is
+# greater than 2GB
+filesize = os.path.getsize(filename)
+if filesize > 2**31:
+    c.setopt(pycurl.INFILESIZE_LARGE, filesize)
+else:
+    c.setopt(pycurl.INFILESIZE, filesize)
+
+# Start transfer
+print 'Uploading file %s to url %s' % (filename, url)
+c.perform()
+c.close()
diff --git a/examples/linksys.py b/examples/linksys.py
new file mode 100755
index 0000000..a60eba1
--- /dev/null
+++ b/examples/linksys.py
@@ -0,0 +1,563 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+#
+# linksys.py -- program settings on a Linkys router
+#
+# This tool is designed to help you recover from the occasional episodes
+# of catatonia that afflict Linksys boxes. It allows you to batch-program
+# them rather than manually entering values to the Web interface.  Commands
+# are taken from the command line first, then standard input.
+#
+# The somewhat spotty coverage of status queries is because I only did the
+# ones that were either (a) easy, or (b) necessary.  If you want to know the
+# status of the box, look at the web interface.
+#
+# This code has been tested against the following hardware:
+#
+#   Hardware    Firmware
+#   ----------  ---------------------
+#   BEFW11S4v2  1.44.2.1, Dec 20 2002
+#
+# The code is, of course, sensitive to changes in the names of CGI pages
+# and field names.
+#
+# Note: to make the no-arguments form work, you'll need to have the following
+# entry in your ~/.netrc file.  If you have changed the router IP address or
+# name/password, modify accordingly.
+#
+# machine 192.168.1.1
+#   login ""
+#   password admin
+#
+# By Eric S. Raymond, August April 2003.  All rites reversed.
+
+import sys, re, copy, curl, exceptions
+
+class LinksysError(exceptions.Exception):
+    def __init__(self, *args):
+        self.args = args
+
+class LinksysSession:
+    months = 'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec'
+
+    WAN_CONNECT_AUTO = '1'
+    WAN_CONNECT_STATIC = '2'
+    WAN_CONNECT_PPOE = '3'
+    WAN_CONNECT_RAS = '4'
+    WAN_CONNECT_PPTP = '5'
+    WAN_CONNECT_HEARTBEAT = '6'
+
+    # Substrings to check for on each page load.
+    # This may enable us to detect when a firmware change has hosed us.
+    check_strings = {
+        "":           "basic setup functions",
+        "Passwd.htm": "For security reasons,",
+        "DHCP.html":  "You can configure the router to act as a DHCP",
+        "Log.html":   "There are some log settings and lists in this page.",
+        "Forward.htm":"Port forwarding can be used to set up public services",
+        }
+
+    def __init__(self):
+        self.actions = []
+        self.host = "http://192.168.1.1"
+        self.verbosity = False
+        self.pagecache = {}
+
+    def set_verbosity(self, flag):
+        self.verbosity = flag
+
+    # This is not a performance hack -- we need the page cache to do
+    # sanity checks at configure time.
+    def cache_load(self, page):
+        if page not in self.pagecache:
+            fetch = curl.Curl(self.host)
+            fetch.set_verbosity(self.verbosity)
+            fetch.get(page)
+            self.pagecache[page] = fetch.body()
+            if fetch.answered("401"):
+                raise LinksysError("authorization failure.", True)
+            elif not fetch.answered(LinksysSession.check_strings[page]):
+                del self.pagecache[page]
+                raise LinksysError("check string for page %s missing!" % os.path.join(self.host, page), False)
+            fetch.close()
+    def cache_flush(self):
+        self.pagecache = {}
+
+    # Primitives
+    def screen_scrape(self, page, template):
+        self.cache_load(page)
+        match = re.compile(template).search(self.pagecache[page])
+        if match:
+            result = match.group(1)
+        else:
+            result = None
+        return result
+    def get_MAC_address(self, page, prefix):
+        return self.screen_scrape("", prefix+r":[^M]*\(MAC Address: *([^)]*)")
+    def set_flag(page, flag, value):
+        if value:
+            self.actions.append(page, flag, "1")
+        else:
+            self.actions.append(page, flag, "0")
+    def set_IP_address(self, page, cgi, role, ip):
+        ind = 0
+        for octet in ip.split("."):
+            self.actions.append(("", "F1", role + `ind+1`, octet))
+            ind += 1
+
+    # Scrape configuration data off the main page
+    def get_firmware_version(self):
+        # This is fragile.  There is no distinguishing tag before the firmware
+        # version, so we have to key off the pattern of the version number.
+        # Our model is ">1.44.2.1, Dec 20 2002<"
+        return self.screen_scrape("", ">([0-9.v]*, (" + \
+                                  LinksysSession.months + ")[^<]*)<", )
+    def get_LAN_MAC(self):
+        return self.get_MAC_address("", r"LAN IP Address")
+    def get_Wireless_MAC(self):
+        return self.get_MAC_address("", r"Wireless")
+    def get_WAN_MAC(self):
+        return self.get_MAC_address("", r"WAN Connection Type")
+
+    # Set configuration data on the main page
+    def set_host_name(self, name):
+        self.actions.append(("", "hostName", name))
+    def set_domain_name(self, name):
+        self.actions.append(("", "DomainName", name))
+    def set_LAN_IP(self, ip):
+        self.set_IP_address("", "ipAddr", ip)
+    def set_LAN_netmask(self, ip):
+        if not ip.startswith("255.255.255."):
+            raise ValueError
+        lastquad = ip.split(".")[-1]
+        if lastquad not in ("0", "128", "192", "240", "252"):
+            raise ValueError
+        self.actions.append("", "netMask", lastquad)
+    def set_wireless(self, flag):
+        self.set_flag("", "wirelessStatus")
+    def set_SSID(self, ssid):
+        self.actions.append(("", "wirelessESSID", ssid))
+    def set_SSID_broadcast(self, flag):
+        self.set_flag("", "broadcastSSID")
+    def set_channel(self, channel):
+        self.actions.append(("", "wirelessChannel", channel))
+    def set_WEP(self, flag):
+        self.set_flag("", "WepType")
+    # FIXME: Add support for setting WEP keys
+    def set_connection_type(self, type):
+        self.actions.append(("", "WANConnectionType", type))
+    def set_WAN_IP(self, ip):
+        self.set_IP_address("", "aliasIP", ip)
+    def set_WAN_netmask(self, ip):
+        self.set_IP_address("", "aliasMaskIP", ip)
+    def set_WAN_gateway_address(self, ip):
+        self.set_IP_address("", "routerIP", ip)
+    def set_DNS_server(self, index, ip):
+        self.set_IP_address("", "dns" + "ABC"[index], ip)
+
+    # Set configuration data on the password page
+    def set_password(self, str):
+        self.actions.append("Passwd.htm","sysPasswd", str)
+        self.actions.append("Passwd.htm","sysPasswdConfirm", str)
+    def set_UPnP(self, flag):
+        self.set_flag("Passwd.htm", "UPnP_Work")
+    def reset(self):
+        self.actions.append("Passwd.htm", "FactoryDefaults")
+
+    # DHCP features
+    def set_DHCP(self, flag):
+        if flag:
+            self.actions.append("DHCP.htm","dhcpStatus","Enable")
+        else:
+            self.actions.append("DHCP.htm","dhcpStatus","Disable")
+    def set_DHCP_starting_IP(self, val):
+        self.actions.append("DHCP.htm","dhcpS4", str(val))
+    def set_DHCP_users(self, val):
+        self.actions.append("DHCP.htm","dhcpLen", str(val))
+    def set_DHCP_lease_time(self, val):
+        self.actions.append("DHCP.htm","leaseTime", str(val))
+    def set_DHCP_DNS_server(self, index, ip):
+        self.set_IP_address("DHCP.htm", "dns" + "ABC"[index], ip)
+    # FIXME: add support for setting WINS key
+
+    # Logging features
+    def set_logging(self, flag):
+        if flag:
+            self.actions.append("Log.htm", "rLog", "Enable")
+        else:
+            self.actions.append("Log.htm", "rLog", "Disable")
+    def set_log_address(self, val):
+        self.actions.append("DHCP.htm","trapAddr3", str(val))
+
+    # The AOL parental control flag is not supported by design.
+
+    # FIXME: add Filters and other advanced features
+
+    def configure(self):
+        "Write configuration changes to the Linksys."
+        if self.actions:
+            fields = []
+            self.cache_flush()
+            for (page, field, value) in self.actions:
+                self.cache_load(page)
+                if self.pagecache[page].find(field) == -1:
+                    print >>sys.stderr, "linksys: field %s not found where expected in page %s!" % (field, os.path.join(self.host, page))
+                    continue
+                else:
+                    fields.append((field, value))
+            # Clearing the action list before fieldsping is deliberate.
+            # Otherwise we could get permanently wedged by a 401.
+            self.actions = []
+            transaction = curl.Curl(self.host)
+            transaction.set_verbosity(self.verbosity)
+            transaction.get("Gozila.cgi", tuple(fields))
+            transaction.close()
+
+if __name__ == "__main__":
+    import os, cmd
+
+    class LinksysInterpreter(cmd.Cmd):
+        """Interpret commands to perform LinkSys programming actions."""
+        def __init__(self):
+            self.session = LinksysSession()
+            if os.isatty(0):
+                import readline
+                print "Type ? or `help' for help."
+                self.prompt = self.session.host + ": "
+            else:
+                self.prompt = ""
+                print "Bar1"
+
+        def flag_command(self, func):
+            if line.strip() in ("on", "enable", "yes"):
+                func(True)
+            elif line.strip() in ("off", "disable", "no"):
+                func(False)
+            else:
+                print >>sys.stderr, "linksys: unknown switch value"
+            return 0
+
+        def do_connect(self, line):
+            newhost = line.strip()
+            if newhost:
+                self.session.host = newhost
+                self.session.cache_flush()
+                self.prompt = self.session.host + ": "
+            else:
+                print self.session.host
+            return 0
+        def help_connect(self):
+            print "Usage: connect [<hostname-or-IP>]"
+            print "Connect to a Linksys by name or IP address."
+            print "If no argument is given, print the current host."
+
+        def do_status(self, line):
+            self.session.cache_load("")
+            if "" in self.session.pagecache:
+                print "Firmware:", self.session.get_firmware_version()
+                print "LAN MAC:", self.session.get_LAN_MAC()
+                print "Wireless MAC:", self.session.get_Wireless_MAC()
+                print "WAN MAC:", self.session.get_WAN_MAC()
+                print "."
+            return 0
+        def help_status(self):
+            print "Usage: status"
+            print "The status command shows the status of the Linksys."
+            print "It is mainly useful as a sanity check to make sure"
+            print "the box is responding correctly."
+
+        def do_verbose(self, line):
+            self.flag_command(self.session.set_verbosity)
+        def help_verbose(self):
+            print "Usage: verbose {on|off|enable|disable|yes|no}"
+            print "Enables display of HTTP requests."
+
+        def do_host(self, line):
+            self.session.set_host_name(line)
+            return 0
+        def help_host(self):
+            print "Usage: host <hostname>"
+            print "Sets the Host field to be queried by the ISP."
+
+        def do_domain(self, line):
+            print "Usage: host <domainname>"
+            self.session.set_domain_name(line)
+            return 0
+        def help_domain(self):
+            print "Sets the Domain field to be queried by the ISP."
+
+        def do_lan_address(self, line):
+            self.session.set_LAN_IP(line)
+            return 0
+        def help_lan_address(self):
+            print "Usage: lan_address <ip-address>"
+            print "Sets the LAN IP address."
+
+        def do_lan_netmask(self, line):
+            self.session.set_LAN_netmask(line)
+            return 0
+        def help_lan_netmask(self):
+            print "Usage: lan_netmask <ip-mask>"
+            print "Sets the LAN subnetwork mask."
+
+        def do_wireless(self, line):
+            self.flag_command(self.session.set_wireless)
+            return 0
+        def help_wireless(self):
+            print "Usage: wireless {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable wireless features."
+
+        def do_ssid(self, line):
+            self.session.set_SSID(line)
+            return 0
+        def help_ssid(self):
+            print "Usage: ssid <string>"
+            print "Sets the SSID used to control wireless access."
+
+        def do_ssid_broadcast(self, line):
+            self.flag_command(self.session.set_SSID_broadcast)
+            return 0
+        def help_ssid_broadcast(self):
+            print "Usage: ssid_broadcast {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable SSID broadcast."
+
+        def do_channel(self, line):
+            self.session.set_channel(line)
+            return 0
+        def help_channel(self):
+            print "Usage: channel <number>"
+            print "Sets the wireless channel."
+
+        def do_wep(self, line):
+            self.flag_command(self.session.set_WEP)
+            return 0
+        def help_wep(self):
+            print "Usage: wep {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable WEP security."
+
+        def do_wan_type(self, line):
+            try:
+                type=eval("LinksysSession.WAN_CONNECT_"+line.strip().upper())
+                self.session.set_connection_type(type)
+            except ValueError:
+                print >>sys.stderr, "linksys: unknown connection type."
+            return 0
+        def help_wan_type(self):
+            print "Usage: wan_type {auto|static|ppoe|ras|pptp|heartbeat}"
+            print "Set the WAN connection type."
+
+        def do_wan_address(self, line):
+            self.session.set_WAN_IP(line)
+            return 0
+        def help_wan_address(self):
+            print "Usage: wan_address <ip-address>"
+            print "Sets the WAN IP address."
+
+        def do_wan_netmask(self, line):
+            self.session.set_WAN_netmask(line)
+            return 0
+        def help_wan_netmask(self):
+            print "Usage: wan_netmask <ip-mask>"
+            print "Sets the WAN subnetwork mask."
+
+        def do_wan_gateway(self, line):
+            self.session.set_WAN_gateway(line)
+            return 0
+        def help_wan_gateway(self):
+            print "Usage: wan_gateway <ip-address>"
+            print "Sets the LAN subnetwork mask."
+
+        def do_dns(self, line):
+            (index, address) = line.split()
+            if index in ("1", "2", "3"):
+                self.session.set_DNS_server(eval(index), address)
+            else:
+                print >>sys.stderr, "linksys: server index out of bounds."
+            return 0
+        def help_dns(self):
+            print "Usage: dns {1|2|3} <ip-mask>"
+            print "Sets a primary, secondary, or tertiary DNS server address."
+
+        def do_password(self, line):
+            self.session.set_password(line)
+            return 0
+        def help_password(self):
+            print "Usage: password <string>"
+            print "Sets the router password."
+
+        def do_upnp(self, line):
+            self.flag_command(self.session.set_UPnP)
+            return 0
+        def help_upnp(self):
+            print "Usage: upnp {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable Universal Plug and Play."
+
+        def do_reset(self, line):
+            self.session.reset()
+        def help_reset(self):
+            print "Usage: reset"
+            print "Reset Linksys settings to factory defaults."
+
+        def do_dhcp(self, line):
+            self.flag_command(self.session.set_DHCP)
+        def help_dhcp(self):
+            print "Usage: dhcp {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable DHCP features."
+
+        def do_dhcp_start(self, line):
+            self.session.set_DHCP_starting_IP(line)
+        def help_dhcp_start(self):
+            print "Usage: dhcp_start <number>"
+            print "Set the start address of the DHCP pool."
+
+        def do_dhcp_users(self, line):
+            self.session.set_DHCP_users(line)
+        def help_dhcp_users(self):
+            print "Usage: dhcp_users <number>"
+            print "Set number of address slots to allocate in the DHCP pool."
+
+        def do_dhcp_lease(self, line):
+            self.session.set_DHCP_lease(line)
+        def help_dhcp_lease(self):
+            print "Usage: dhcp_lease <number>"
+            print "Set number of address slots to allocate in the DHCP pool."
+
+        def do_dhcp_dns(self, line):
+            (index, address) = line.split()
+            if index in ("1", "2", "3"):
+                self.session.set_DHCP_DNS_server(eval(index), address)
+            else:
+                print >>sys.stderr, "linksys: server index out of bounds."
+            return 0
+        def help_dhcp_dns(self):
+            print "Usage: dhcp_dns {1|2|3} <ip-mask>"
+            print "Sets primary, secondary, or tertiary DNS server address."
+
+        def do_logging(self, line):
+            self.flag_command(self.session.set_logging)
+        def help_logging(self):
+            print "Usage: logging {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable session logging."
+
+        def do_log_address(self, line):
+            self.session.set_Log_address(line)
+        def help_log_address(self):
+            print "Usage: log_address <number>"
+            print "Set the last quad of the address to which to log."
+
+        def do_configure(self, line):
+            self.session.configure()
+            return 0
+        def help_configure(self):
+            print "Usage: configure"
+            print "Writes the configuration to the Linksys."
+
+        def do_cache(self, line):
+            print self.session.pagecache
+        def help_cache(self):
+            print "Usage: cache"
+            print "Display the page cache."
+
+        def do_quit(self, line):
+            return 1
+        def help_quit(self, line):
+            print "The quit command ends your linksys session without"
+            print "writing configuration changes to the Linksys."
+        def do_EOF(self, line):
+            print ""
+            self.session.configure()
+            return 1
+        def help_EOF(self):
+            print "The EOF command writes the configuration to the linksys"
+            print "and ends your session."
+
+        def default(self, line):
+            """Pass the command through to be executed by the shell."""
+            os.system(line)
+            return 0
+
+        def help_help(self):
+            print "On-line help is available through this command."
+            print "? is a convenience alias for help."
+
+        def help_introduction(self):
+            print """\
+
+This program supports changing the settings on Linksys blue-box routers.  This
+capability may come in handy when they freeze up and have to be reset.  Though
+it can be used interactively (and will command-prompt when standard input is a
+terminal) it is really designed to be used in batch mode. Commands are taken
+from the command line first, then standard input.
+
+By default, it is assumed that the Linksys is at http://192.168.1.1, the
+default LAN address.  You can connect to a different address or IP with the
+'connect' command.  Note that your .netrc must contain correct user/password
+credentials for the router.  The entry corresponding to the defaults is:
+
+machine 192.168.1.1
+    login ""
+    password admin
+
+Most commands queue up changes but don't actually send them to the Linksys.
+You can force pending changes to be written with 'configure'.  Otherwise, they
+will be shipped to the Linksys at the end of session (e.g.  when the program
+running in batch mode encounters end-of-file or you type a control-D).  If you
+end the session with `quit', pending changes will be discarded.
+
+For more help, read the topics 'wan', 'lan', and 'wireless'."""
+
+        def help_lan(self):
+            print """\
+The `lan_address' and `lan_netmask' commands let you set the IP location of
+the Linksys on your LAN, or inside.  Normally you'll want to leave these
+untouched."""
+
+        def help_wan(self):
+            print """\
+The WAN commands become significant if you are using the BEFSR41 or any of
+the other Linksys boxes designed as DSL or cable-modem gateways.  You will
+need to use `wan_type' to declare how you expect to get your address.
+
+If your ISP has issued you a static address, you'll need to use the
+`wan_address', `wan_netmask', and `wan_gateway' commands to set the address
+of the router as seen from the WAN, the outside. In this case you will also
+need to use the `dns' command to declare which remote servers your DNS
+requests should be forwarded to.
+
+Some ISPs may require you to set host and domain for use with dynamic-address
+allocation."""
+
+        def help_wireless(self):
+            print """\
+The channel, ssid, ssid_broadcast, wep, and wireless commands control
+wireless routing."""
+
+        def help_switches(self):
+            print "Switches may be turned on with 'on', 'enable', or 'yes'."
+            print "Switches may be turned off with 'off', 'disable', or 'no'."
+            print "Switch commands include: wireless, ssid_broadcast."
+
+        def help_addresses(self):
+            print "An address argument must be a valid IP address;"
+            print "four decimal numbers separated by dots, each "
+            print "between 0 and 255."
+
+        def emptyline(self):
+            pass
+
+    interpreter = LinksysInterpreter()
+    for arg in sys.argv[1:]:
+        interpreter.onecmd(arg)
+    fatal = False
+    while not fatal:
+        try:
+            interpreter.cmdloop()
+            fatal = True
+        except LinksysError, (message, fatal):
+            print "linksys:", message
+
+# The following sets edit modes for GNU EMACS
+# Local Variables:
+# mode:python
+# End:
diff --git a/examples/retriever-multi.py b/examples/retriever-multi.py
new file mode 100644
index 0000000..549810e
--- /dev/null
+++ b/examples/retriever-multi.py
@@ -0,0 +1,125 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: retriever-multi.py,v 1.23 2005/01/08 19:15:42 mfx Exp $
+
+#
+# Usage: python retriever-multi.py <file with URLs to fetch> [<# of
+#          concurrent connections>]
+#
+
+import string, sys
+import pycurl
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+# Get args
+num_conn = 10
+try:
+    urls = open(sys.argv[1]).readlines()
+    if len(sys.argv) >= 3:
+        num_conn = int(sys.argv[2])
+except:
+    print "Usage: %s <file with URLs to fetch> [<# of concurrent connections>]" % sys.argv[0]
+    raise SystemExit
+
+
+# Make a queue with (url, filename) tuples
+queue = []
+fileno = 1
+for url in urls:
+    url = string.strip(url)
+    if not url or url[0] == "#":
+        continue
+    filename = "doc_%d" % (fileno)
+    queue.append((url, filename))
+    fileno = fileno + 1
+del fileno, url, urls
+
+
+# Check args
+assert queue, "no URLs given"
+num_urls = len(queue)
+num_conn = min(num_conn, num_urls)
+assert 1 <= num_conn <= 10000, "invalid number of concurrent connections"
+print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
+print "----- Getting", num_urls, "URLs using", num_conn, "connections -----"
+
+
+# Preallocate a list of curl objects
+m = pycurl.CurlMulti()
+m.handles = []
+for i in range(num_conn):
+    c = pycurl.Curl()
+    c.fp = None
+    c.setopt(pycurl.HTTPHEADER, ["User-Agent: PycURL"])
+    c.setopt(pycurl.FOLLOWLOCATION, 1)
+    c.setopt(pycurl.MAXREDIRS, 5)
+    c.setopt(pycurl.CONNECTTIMEOUT, 30)
+    c.setopt(pycurl.TIMEOUT, 300)
+    c.setopt(pycurl.NOSIGNAL, 1)
+    m.handles.append(c)
+
+
+# Main loop
+freelist = m.handles[:]
+num_processed = 0
+while num_processed < num_urls:
+    # If there is an url to process and a free curl object, add to multi stack
+    while queue and freelist:
+        url, filename = queue.pop(0)
+        c = freelist.pop()
+        c.fp = open(filename, "wb")
+        c.setopt(pycurl.URL, url)
+        c.setopt(pycurl.WRITEDATA, c.fp)
+        m.add_handle(c)
+        # store some info
+        c.filename = filename
+        c.url = url
+    # Run the internal curl state machine for the multi stack
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+    # Check for curl objects which have terminated, and add them to the freelist
+    while 1:
+        num_q, ok_list, err_list = m.info_read()
+        for c in ok_list:
+            c.fp.close()
+            c.fp = None
+            m.remove_handle(c)
+            print "Success:", c.filename, c.url, c.getinfo(pycurl.EFFECTIVE_URL)
+            freelist.append(c)
+        for c, errno, errmsg in err_list:
+            c.fp.close()
+            c.fp = None
+            m.remove_handle(c)
+            print "Failed: ", c.filename, c.url, errno, errmsg
+            freelist.append(c)
+        num_processed = num_processed + len(ok_list) + len(err_list)
+        if num_q == 0:
+            break
+    # Currently no more I/O is pending, could do something in the meantime
+    # (display a progress bar, etc.).
+    # We just call select() to sleep until some more data is available.
+    m.select()
+
+
+# Cleanup
+for c in m.handles:
+    if c.fp is not None:
+        c.fp.close()
+        c.fp = None
+    c.close()
+m.close()
+
+# Delete objects (just for testing the refcounts)
+del c, m, freelist, queue
+
diff --git a/examples/retriever.py b/examples/retriever.py
new file mode 100644
index 0000000..3d6990a
--- /dev/null
+++ b/examples/retriever.py
@@ -0,0 +1,79 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: retriever.py,v 1.15 2004/12/26 17:31:53 mfx Exp $
+
+import sys, threading, Queue
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+class WorkerThread(threading.Thread):
+    def __init__(self, queue):
+        threading.Thread.__init__(self)
+        self.queue = queue
+
+    def run(self):
+        while 1:
+            try:
+                url, filename = self.queue.get_nowait()
+            except Queue.Empty:
+                raise SystemExit
+            f = open(filename, "wb")
+            curl = pycurl.Curl()
+            curl.setopt(pycurl.HTTPHEADER, ["User-Agent: PycURL"])
+            curl.setopt(pycurl.FOLLOWLOCATION, 1)
+            curl.setopt(pycurl.MAXREDIRS, 5)
+            curl.setopt(pycurl.URL, url)
+            curl.setopt(pycurl.WRITEDATA, f)
+            curl.setopt(pycurl.NOSIGNAL, 1)
+            curl.setopt(pycurl.CONNECTTIMEOUT, 30)
+            curl.setopt(pycurl.TIMEOUT, 300)
+            try:
+                curl.perform()
+            except:
+                import traceback
+                traceback.print_exc(file=sys.stderr)
+                sys.stderr.flush()
+            curl.close()
+            f.close()
+            sys.stdout.write(".")
+            sys.stdout.flush()
+
+# Read list of URLs from file specified on commandline
+try:
+    urls = open(sys.argv[1]).readlines()
+    num_workers = int(sys.argv[2])
+except:
+    # File or number of workers was not specified, show usage string
+    print "Usage: %s <file with URLs to fetch> <number of worker threads>" % sys.argv[0]
+    raise SystemExit
+
+# Initialize thread array and the file number used to store documents
+threads = []
+fileno = 0
+queue = Queue.Queue()
+
+# Fill the work input queue with URLs
+for url in urls:
+    fileno = fileno + 1
+    filename = "doc_%d" % (fileno,)
+    queue.put((url, filename))
+
+# Start a bunch of threads
+for num_threads in range(num_workers):
+    t = WorkerThread(queue)
+    t.start()
+    threads.append(t)
+
+# Wait for all threads to finish
+for thread in threads:
+    thread.join()
diff --git a/examples/sfquery.py b/examples/sfquery.py
new file mode 100644
index 0000000..0c63f61
--- /dev/null
+++ b/examples/sfquery.py
@@ -0,0 +1,64 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+#
+# sfquery -- Source Forge query script using the ClientCGI high-level interface
+#
+# Retrieves a SourceForge XML export object for a given project.
+# Specify the *numeric* project ID. the user name, and the password,
+# as arguments. If you have a valid ~/.netrc entry for sourceforge.net,
+# you can just give the project ID.
+#
+# By Eric S. Raymond, August 2002.  All rites reversed.
+
+import os, sys, netrc
+import curl
+
+assert sys.version[:3] >= "2.2", "requires Python 2.2 or better"
+
+class SourceForgeUserSession(curl.Curl):
+    # SourceForge-specific methods.  Sensitive to changes in site design.
+    def login(self, name, password):
+        "Establish a login session."
+        self.post("account/login.php", (("form_loginname", name),
+                                        ("form_pw", password),
+                                        ("return_to", ""),
+                                        ("stay_in_ssl", "1"),
+                                        ("login", "Login With SSL")))
+    def logout(self):
+        "Log out of SourceForge."
+        self.get("account/logout.php")
+    def fetch_xml(self, numid):
+        self.get("export/xml_export.php?group_id=%s" % numid)
+
+if __name__ == "__main__":
+    if len(sys.argv) == 1:
+        project_id = '28236'    # PyCurl project ID
+    else:
+        project_id = sys.argv[1]
+    # Try to grab authenticators out of your .netrc
+    try:
+        auth = netrc.netrc().authenticators("sourceforge.net")
+        name, account, password = auth
+    except:
+        name = sys.argv[2]
+        password = sys.argv[3]
+    session = SourceForgeUserSession("https://sourceforge.net/")
+    session.set_verbosity(0)
+    session.login(name, password)
+    # Login could fail.
+    if session.answered("Invalid Password or User Name"):
+        sys.stderr.write("Login/password not accepted (%d bytes)\n" % len(session.body()))
+        sys.exit(1)
+    # We'll see this if we get the right thing.
+    elif session.answered("Personal Page For: " + name):
+        session.fetch_xml(project_id)
+        sys.stdout.write(session.body())
+        session.logout()
+        sys.exit(0)
+    # Or maybe SourceForge has changed its site design so our check strings
+    # are no longer valid.
+    else:
+        sys.stderr.write("Unexpected page (%d bytes)\n"%len(session.body()))
+        sys.exit(1)
+
diff --git a/examples/xmlrpc_curl.py b/examples/xmlrpc_curl.py
new file mode 100644
index 0000000..640db0e
--- /dev/null
+++ b/examples/xmlrpc_curl.py
@@ -0,0 +1,61 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: xmlrpc_curl.py,v 1.9 2004/12/26 17:31:53 mfx Exp $
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import xmlrpclib, pycurl
+
+
+class CURLTransport(xmlrpclib.Transport):
+    """Handles a cURL HTTP transaction to an XML-RPC server."""
+
+    xmlrpc_h = [ "User-Agent: PycURL XML-RPC", "Content-Type: text/xml" ]
+
+    def __init__(self, username=None, password=None):
+        self.c = pycurl.Curl()
+        self.c.setopt(pycurl.POST, 1)
+        self.c.setopt(pycurl.NOSIGNAL, 1)
+        self.c.setopt(pycurl.CONNECTTIMEOUT, 30)
+        self.c.setopt(pycurl.HTTPHEADER, self.xmlrpc_h)
+        if username != None and password != None:
+            self.c.setopt(pycurl.USERPWD, '%s:%s' % (username, password))
+
+    def request(self, host, handler, request_body, verbose=0):
+        b = StringIO()
+        self.c.setopt(pycurl.URL, 'http://%s%s' % (host, handler))
+        self.c.setopt(pycurl.POSTFIELDS, request_body)
+        self.c.setopt(pycurl.WRITEFUNCTION, b.write)
+        self.c.setopt(pycurl.VERBOSE, verbose)
+        self.verbose = verbose
+        try:
+           self.c.perform()
+        except pycurl.error, v:
+            raise xmlrpclib.ProtocolError(
+                host + handler,
+                v[0], v[1], None
+                )
+        b.seek(0)
+        return self.parse_response(b)
+
+
+if __name__ == "__main__":
+    ## Test
+    server = xmlrpclib.ServerProxy("http://betty.userland.com",
+                                   transport=CURLTransport())
+    print server
+    try:
+        print server.examples.getStateName(41)
+    except xmlrpclib.Error, v:
+        print "ERROR", v
diff --git a/python/curl/__init__.py b/python/curl/__init__.py
new file mode 100644
index 0000000..8fecb4d
--- /dev/null
+++ b/python/curl/__init__.py
@@ -0,0 +1,146 @@
+# A high-level interface to the pycurl extension
+#
+# ** mfx NOTE: the CGI class uses "black magic" using COOKIEFILE in
+#    combination with a non-existant file name. See the libcurl docs
+#    for more info.
+#
+# If you want thread-safe operation, you'll have to set the NOSIGNAL option
+# yourself.
+#
+# By Eric S. Raymond, April 2003.
+
+import os, sys, urllib, exceptions, mimetools, pycurl
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+class Curl:
+    "High-level interface to cURL functions."
+    def __init__(self, base_url="", fakeheaders=[]):
+        self.handle = pycurl.Curl()
+        # These members might be set.
+        self.set_url(base_url)
+        self.verbosity = 0
+        self.fakeheaders = fakeheaders
+        # Nothing past here should be modified by the caller.
+        self.payload = ""
+        self.header = StringIO()
+        # Verify that we've got the right site; harmless on a non-SSL connect.
+        self.set_option(pycurl.SSL_VERIFYHOST, 2)
+        # Follow redirects in case it wants to take us to a CGI...
+        self.set_option(pycurl.FOLLOWLOCATION, 1)
+        self.set_option(pycurl.MAXREDIRS, 5)
+        # Setting this option with even a nonexistent file makes libcurl
+        # handle cookie capture and playback automatically.
+        self.set_option(pycurl.COOKIEFILE, "/dev/null")
+        # Set timeouts to avoid hanging too long
+        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)
+        def header_callback(x):
+            self.header.write(x)
+        self.set_option(pycurl.HEADERFUNCTION, header_callback)
+
+    def set_timeout(self, timeout):
+        "Set timeout for connect and object retrieval (applies for both)"
+        self.set_option(pycurl.CONNECTTIMEOUT, timeout)
+        self.set_option(pycurl.TIMEOUT, timeout)
+
+    def set_url(self, url):
+        "Set the base URL to be retrieved."
+        self.base_url = url
+        self.set_option(pycurl.URL, self.base_url)
+
+    def set_option(self, *args):
+        "Set an option on the retrieval,"
+        apply(self.handle.setopt, args)
+
+    def set_verbosity(self, level):
+        "Set verbosity to 1 to see transactions."
+        self.set_option(pycurl.VERBOSE, level)
+
+    def __request(self, relative_url=None):
+        "Perform the pending request."
+        if self.fakeheaders:
+            self.set_option(pycurl.HTTPHEADER, self.fakeheaders)
+        if relative_url:
+            self.set_option(pycurl.URL,os.path.join(self.base_url,relative_url))
+        self.header.seek(0,0)
+        self.payload = ""
+        self.handle.perform()
+        return self.payload
+
+    def get(self, url="", params=None):
+        "Ship a GET request for a specified URL, capture the response."
+        if params:
+            url += "?" + urllib.urlencode(params)
+        self.set_option(pycurl.HTTPGET, 1)
+        return self.__request(url)
+
+    def post(self, cgi, params):
+        "Ship a POST request to a specified CGI, capture the response."
+        self.set_option(pycurl.POST, 1)
+        self.set_option(pycurl.POSTFIELDS, urllib.urlencode(params))
+        return self.__request(cgi)
+
+    def body(self):
+        "Return the body from the last response."
+        return self.payload
+
+    def info(self):
+        "Return an RFC822 object with info on the page."
+        self.header.seek(0,0)
+        url = self.handle.getinfo(pycurl.EFFECTIVE_URL)
+        if url[:5] == 'http:':
+            self.header.readline()
+            m = mimetools.Message(self.header)
+        else:
+            m = mimetools.Message(StringIO())
+        m['effective-url'] = url
+        m['http-code'] = str(self.handle.getinfo(pycurl.HTTP_CODE))
+        m['total-time'] = str(self.handle.getinfo(pycurl.TOTAL_TIME))
+        m['namelookup-time'] = str(self.handle.getinfo(pycurl.NAMELOOKUP_TIME))
+        m['connect-time'] = str(self.handle.getinfo(pycurl.CONNECT_TIME))
+        m['pretransfer-time'] = str(self.handle.getinfo(pycurl.PRETRANSFER_TIME))
+        m['redirect-time'] = str(self.handle.getinfo(pycurl.REDIRECT_TIME))
+        m['redirect-count'] = str(self.handle.getinfo(pycurl.REDIRECT_COUNT))
+        m['size-upload'] = str(self.handle.getinfo(pycurl.SIZE_UPLOAD))
+        m['size-download'] = str(self.handle.getinfo(pycurl.SIZE_DOWNLOAD))
+        m['speed-upload'] = str(self.handle.getinfo(pycurl.SPEED_UPLOAD))
+        m['header-size'] = str(self.handle.getinfo(pycurl.HEADER_SIZE))
+        m['request-size'] = str(self.handle.getinfo(pycurl.REQUEST_SIZE))
+        m['content-length-download'] = str(self.handle.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD))
+        m['content-length-upload'] = str(self.handle.getinfo(pycurl.CONTENT_LENGTH_UPLOAD))
+        m['content-type'] = (self.handle.getinfo(pycurl.CONTENT_TYPE) or '').strip(';')
+        return m
+
+    def answered(self, check):
+        "Did a given check string occur in the last payload?"
+        return self.payload.find(check) >= 0
+
+    def close(self):
+        "Close a session, freeing resources."
+        self.handle.close()
+        self.header.close()
+
+    def __del__(self):
+        self.close()
+
+
+if __name__ == "__main__":
+    if len(sys.argv) < 2:
+        url = 'http://curl.haxx.se'
+    else:
+        url = sys.argv[1]
+    c = Curl()
+    c.get(url)
+    print c.body()
+    print '='*74 + '\n'
+    print c.info()
+    c.close()
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..9fe4054
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,199 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: setup.py,v 1.121 2005/02/04 01:38:20 mfx Exp $
+
+"""Setup script for the PycURL module distribution."""
+
+PACKAGE = "pycurl"
+PY_PACKAGE = "curl"
+VERSION = "7.13.0"
+
+import glob, os, re, sys, string
+import distutils
+from distutils.core import setup
+from distutils.extension import Extension
+from distutils.util import split_quoted
+from distutils.version import LooseVersion
+
+include_dirs = []
+define_macros = []
+library_dirs = []
+libraries = []
+runtime_library_dirs = []
+extra_objects = []
+extra_compile_args = []
+extra_link_args = []
+
+
+def scan_argv(s, default):
+    p = default
+    i = 1
+    while i < len(sys.argv):
+        arg = sys.argv[i]
+        if string.find(arg, s) == 0:
+            p = arg[len(s):]
+            assert p, arg
+            del sys.argv[i]
+        else:
+            i = i + 1
+    ##print sys.argv
+    return p
+
+
+# append contents of an environment variable to library_dirs[]
+def add_libdirs(envvar, sep, fatal=0):
+    v = os.environ.get(envvar)
+    if not v:
+        return
+    for dir in string.split(v, sep):
+        dir = string.strip(dir)
+        if not dir:
+            continue
+        dir = os.path.normpath(dir)
+        if os.path.isdir(dir):
+            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)
+
+
+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 = r"c:\src\build\pycurl\curl-7.12.3"
+    CURL_DIR = scan_argv("--curl-dir=", CURL_DIR)
+    print "Using curl directory:", CURL_DIR
+    assert os.path.isdir(CURL_DIR), "please check CURL_DIR in setup.py"
+    include_dirs.append(os.path.join(CURL_DIR, "include"))
+    extra_objects.append(os.path.join(CURL_DIR, "lib", "libcurl.lib"))
+    extra_link_args.extend(["gdi32.lib", "winmm.lib", "ws2_32.lib",])
+    add_libdirs("LIB", ";")
+    if string.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
+        extra_link_args.append("/opt:nowin98")  # use small section alignment
+else:
+    # Find out the rest the hard way
+    CURL_CONFIG = "curl-config"
+    CURL_CONFIG = scan_argv("--curl-config=", CURL_CONFIG)
+    d = os.popen("'%s' --version" % CURL_CONFIG).read()
+    if d:
+        d = string.strip(d)
+    if not d:
+        raise Exception, ("`%s' not found -- please install the libcurl development files" % 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":
+            # do not add /usr/include
+            if not re.search(r"^\/+usr\/+include\/*$", e[2:]):
+                include_dirs.append(e[2:])
+        else:
+            extra_compile_args.append(e)
+    for e in split_quoted(os.popen("'%s' --libs" % CURL_CONFIG).read()):
+        if e[:2] == "-l":
+            libraries.append(e[2:])
+        elif e[:2] == "-L":
+            library_dirs.append(e[2:])
+        else:
+            extra_link_args.append(e)
+    if not libraries:
+        libraries.append("curl")
+    # Add extra compile flag for MacOS X
+    if sys.platform[:-1] == "darwin":
+        extra_link_args.append("-flat_namespace")
+
+
+###############################################################################
+
+def get_kw(**kw): return kw
+
+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)
+
+
+###############################################################################
+
+# prepare data_files
+
+def get_data_files():
+    # a list of tuples with (path to install to, a list of local files)
+    data_files = []
+    if sys.platform == "win32":
+        datadir = os.path.join("doc", PACKAGE)
+    else:
+        datadir = os.path.join("share", "doc", PACKAGE)
+    #
+    files = ["ChangeLog", "COPYING", "INSTALL", "README", "TODO",]
+    if files:
+        data_files.append((os.path.join(datadir), files))
+    files = glob.glob(os.path.join("doc", "*.html"))
+    if files:
+        data_files.append((os.path.join(datadir, "html"), files))
+    files = glob.glob(os.path.join("examples", "*.py"))
+    if files:
+        data_files.append((os.path.join(datadir, "examples"), files))
+    files = glob.glob(os.path.join("tests", "*.py"))
+    if files:
+        data_files.append((os.path.join(datadir, "tests"), files))
+    #
+    assert data_files
+    for install_dir, files in data_files:
+        assert files
+        for f in files:
+            assert os.path.isfile(f), (f, install_dir)
+    return data_files
+
+##print get_data_files(); sys.exit(1)
+
+
+###############################################################################
+
+setup_args = get_kw(
+    name=PACKAGE,
+    version=VERSION,
+    description="PycURL -- cURL library module for Python",
+    author="Kjetil Jacobsen, Markus F.X.J. Oberhumer",
+    author_email="kjetilja at cs.uit.no, markus at oberhumer.com",
+    maintainer="Kjetil Jacobsen, Markus F.X.J. Oberhumer",
+    maintainer_email="kjetilja at cs.uit.no, markus at oberhumer.com",
+    url="http://pycurl.sourceforge.net/",
+    license="GNU Lesser General Public License (LGPL)",
+    data_files=get_data_files(),
+    ext_modules=[ext],
+    long_description="""
+This module provides Python bindings for the cURL library.""",
+)
+
+if sys.version >= "2.2":
+    setup_args["packages"] = [PY_PACKAGE]
+    setup_args["package_dir"] = { PY_PACKAGE: os.path.join('python', 'curl') }
+
+
+##print distutils.__version__
+if LooseVersion(distutils.__version__) > LooseVersion("1.0.1"):
+    setup_args["platforms"] = "All"
+if LooseVersion(distutils.__version__) < LooseVersion("1.0.3"):
+    setup_args["licence"] = setup_args["license"]
+
+if __name__ == "__main__":
+    for o in ext.extra_objects:
+        assert os.path.isfile(o), o
+    # We can live with the deprecationwarning for a while
+    apply(setup, (), setup_args)
diff --git a/setup_win32_ssl.py b/setup_win32_ssl.py
new file mode 100644
index 0000000..18e7e3a
--- /dev/null
+++ b/setup_win32_ssl.py
@@ -0,0 +1,34 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: setup_win32_ssl.py,v 1.26 2005/02/04 01:38:20 mfx Exp $
+
+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.13.0-ssl"
+OPENSSL_DIR = r"c:\src\build\pycurl\openssl-0.9.7e"
+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))
+
+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-20041212" + pool + "ares.lib")
+ext.extra_objects.append(r"c:\src\pool\libidn-0.5.13" + pool + "idn.lib")
+
+
+if __name__ == "__main__":
+    for o in ext.extra_objects:
+        assert os.path.isfile(o), o
+    apply(setup, (), setup_args)
+
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..a4828d3
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,19 @@
+CC=gcc
+RM=rm
+CP=cp
+PYINCLUDE=/usr/include/python2.2
+CURLINCLUDE=/usr/include/curl
+INCLUDE=-I$(PYINCLUDE) -I$(CURLINCLUDE)
+LIBS=-L/usr/lib -lcurl
+LDOPTS=-shared
+CCOPTS=-g -O2 -Wall -Wstrict-prototypes -fPIC
+
+all:
+	$(CC) $(INCLUDE) $(CCOPTS) -c pycurl.c -o pycurl.o
+	$(CC) $(LIBS) $(LDOPTS) -lcurl pycurl.o -o pycurl.so
+
+install: all
+	$(CP) pycurl.so /usr/lib/python2.2/site-packages
+
+clean:
+	$(RM) -f *~ *.o *obj *.so
diff --git a/src/pycurl.c b/src/pycurl.c
new file mode 100644
index 0000000..504e0d0
--- /dev/null
+++ b/src/pycurl.c
@@ -0,0 +1,2732 @@
+/* $Id: pycurl.c,v 1.74 2005/02/10 10:17:15 kjetilja Exp $ */
+
+/* PycURL -- cURL Python module
+ *
+ * Authors:
+ *  Copyright (C) 2001-2005 by Kjetil Jacobsen <kjetilja at cs.uit.no>
+ *  Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer <markus at oberhumer.com>
+ *
+ * Contributions:
+ *  Tino Lange <Tino.Lange at gmx.de>
+ *  Matt King <matt at gnik.com>
+ *  Conrad Steenberg <conrad at hep.caltech.edu>
+ *  Amit Mongia <amit_mongia at hotmail.com>
+ *  Eric S. Raymond <esr at thyrsus.com>
+ *  Martin Muenstermann <mamuema at sourceforge.net>
+ *  Domenico Andreoli <cavok at libero.it>
+ *
+ * See file COPYING for license information.
+ *
+ * Some quick info on Python's refcount:
+ *   Py_BuildValue          does incref the item(s)
+ *   PyArg_ParseTuple       does NOT incref the item
+ *   PyList_Append          does incref the item
+ *   PyTuple_SET_ITEM       does NOT incref the item
+ *   PyTuple_SetItem        does NOT incref the item
+ *   PyXXX_GetItem          returns a borrowed reference
+ */
+
+#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
+#  define WIN32 1
+#endif
+#if defined(WIN32)
+#  define CURL_STATICLIB 1
+#endif
+#include <Python.h>
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <curl/curl.h>
+#include <curl/multi.h>
+#undef NDEBUG
+#include <assert.h>
+
+/* Ensure we have updated versions */
+#if !defined(PY_VERSION_HEX) || (PY_VERSION_HEX < 0x02020000)
+#  error "Need Python version 2.2 or greater to compile pycurl."
+#endif
+#if !defined(LIBCURL_VERSION_NUM) || (LIBCURL_VERSION_NUM < 0x070d00)
+#  error "Need libcurl version 7.13.0 or greater to compile pycurl."
+#endif
+
+#undef UNUSED
+#define UNUSED(var)     ((void)&var)
+
+#undef COMPILE_TIME_ASSERT
+#define COMPILE_TIME_ASSERT(expr) \
+     { typedef int compile_time_assert_fail__[1 - 2 * !(expr)]; }
+
+
+/* Calculate the number of OBJECTPOINT options we need to store */
+#define OPTIONS_SIZE    ((int)CURLOPT_LASTENTRY % 10000)
+static int OPT_INDEX(int o)
+{
+    assert(o >= CURLOPTTYPE_OBJECTPOINT);
+    assert(o < CURLOPTTYPE_OBJECTPOINT + OPTIONS_SIZE);
+    return o - CURLOPTTYPE_OBJECTPOINT;
+}
+
+
+static PyObject *ErrorObject = NULL;
+static PyTypeObject *p_Curl_Type = NULL;
+static PyTypeObject *p_CurlMulti_Type = NULL;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *dict;                 /* Python attributes dictionary */
+    CURLM *multi_handle;
+    PyThreadState *state;
+    fd_set read_fd_set;
+    fd_set write_fd_set;
+    fd_set exc_fd_set;
+} CurlMultiObject;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *dict;                 /* Python attributes dictionary */
+    CURL *handle;
+    PyThreadState *state;
+    CurlMultiObject *multi_stack;
+    struct curl_httppost *httppost;
+    struct curl_slist *httpheader;
+    struct curl_slist *http200aliases;
+    struct curl_slist *quote;
+    struct curl_slist *postquote;
+    struct curl_slist *prequote;
+    struct curl_slist *source_prequote;
+    struct curl_slist *source_postquote;
+    /* callbacks */
+    PyObject *w_cb;
+    PyObject *h_cb;
+    PyObject *r_cb;
+    PyObject *pro_cb;
+    PyObject *debug_cb;
+    PyObject *ioctl_cb;
+    /* file objects */
+    PyObject *readdata_fp;
+    PyObject *writedata_fp;
+    PyObject *writeheader_fp;
+    /* misc */
+    void *options[OPTIONS_SIZE];    /* for OBJECTPOINT options */
+    char error[CURL_ERROR_SIZE+1];
+} CurlObject;
+
+/* Throw exception based on return value `res' and `self->error' */
+#define CURLERROR_RETVAL() do {\
+    PyObject *v; \
+    self->error[sizeof(self->error) - 1] = 0; \
+    v = Py_BuildValue("(is)", (int) (res), self->error); \
+    if (v != NULL) { PyErr_SetObject(ErrorObject, v); Py_DECREF(v); } \
+    return NULL; \
+} while (0)
+
+/* Throw exception based on return value `res' and custom message */
+#define CURLERROR_MSG(msg) do {\
+    PyObject *v; const char *m = (msg); \
+    v = Py_BuildValue("(is)", (int) (res), (m)); \
+    if (v != NULL) { PyErr_SetObject(ErrorObject, v); Py_DECREF(v); } \
+    return NULL; \
+} 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 utility functions
+**************************************************************************/
+
+#if (PY_VERSION_HEX < 0x02030000) && !defined(PY_LONG_LONG)
+#  define PY_LONG_LONG LONG_LONG
+#endif
+
+/* 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)
+{
+    char *s = NULL;
+    int r;
+    r = PyString_AsStringAndSize(obj, &s, NULL);
+    if (r != 0)
+        return NULL;    /* exception already set */
+    assert(s != NULL);
+    return s;
+}
+
+
+/* Convert a curl slist (a list of strings) to a Python list.
+ * In case of error return NULL with an exception set.
+ */
+static PyObject *convert_slist(struct curl_slist *slist, int free_flags)
+{
+    PyObject *ret = NULL;
+
+    ret = PyList_New(0);
+    if (ret == NULL) goto error;
+
+    for ( ; slist != NULL; slist = slist->next) {
+        PyObject *v = NULL;
+
+        if (slist->data != NULL) {
+            v = PyString_FromString(slist->data);
+            if (v == NULL || PyList_Append(ret, v) != 0) {
+                Py_XDECREF(v);
+                goto error;
+            }
+            Py_DECREF(v);
+        }
+    }
+
+    if ((free_flags & 1) && slist)
+        curl_slist_free_all(slist);
+    return ret;
+
+error:
+    Py_XDECREF(ret);
+    if ((free_flags & 2) && slist)
+        curl_slist_free_all(slist);
+    return NULL;
+}
+
+
+/*************************************************************************
+// static utility functions
+**************************************************************************/
+
+static PyThreadState *
+get_thread_state(const CurlObject *self)
+{
+    /* Get the thread state for callbacks to run in.
+     * This is either `self->state' when running inside perform() or
+     * `self->multi_stack->state' when running inside multi_perform().
+     * When the result is != NULL we also implicitly assert
+     * a valid `self->handle'.
+     */
+    if (self == NULL)
+        return NULL;
+    assert(self->ob_type == p_Curl_Type);
+    if (self->state != NULL)
+    {
+        /* inside perform() */
+        assert(self->handle != NULL);
+        if (self->multi_stack != NULL) {
+            assert(self->multi_stack->state == NULL);
+        }
+        return self->state;
+    }
+    if (self->multi_stack != NULL && self->multi_stack->state != NULL)
+    {
+        /* inside multi_perform() */
+        assert(self->handle != NULL);
+        assert(self->multi_stack->multi_handle != NULL);
+        assert(self->state == NULL);
+        return self->multi_stack->state;
+    }
+    return NULL;
+}
+
+
+/* assert some CurlObject invariants */
+static void
+assert_curl_state(const CurlObject *self)
+{
+    assert(self != NULL);
+    assert(self->ob_type == p_Curl_Type);
+    (void) get_thread_state(self);
+}
+
+
+/* assert some CurlMultiObject invariants */
+static void
+assert_multi_state(const CurlMultiObject *self)
+{
+    assert(self != NULL);
+    assert(self->ob_type == p_CurlMulti_Type);
+    if (self->state != NULL) {
+        assert(self->multi_handle != NULL);
+    }
+}
+
+
+/* check state for methods */
+static int
+check_curl_state(const CurlObject *self, int flags, const char *name)
+{
+    assert_curl_state(self);
+    if ((flags & 1) && self->handle == NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - no curl handle", name);
+        return -1;
+    }
+    if ((flags & 2) && get_thread_state(self) != NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - perform() is currently running", name);
+        return -1;
+    }
+    return 0;
+}
+
+static int
+check_multi_state(const CurlMultiObject *self, int flags, const char *name)
+{
+    assert_multi_state(self);
+    if ((flags & 1) && self->multi_handle == NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - no multi handle", name);
+        return -1;
+    }
+    if ((flags & 2) && self->state != NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - multi_perform() is currently running", name);
+        return -1;
+    }
+    return 0;
+}
+
+
+/*************************************************************************
+// CurlObject
+**************************************************************************/
+
+/* --------------- construct/destruct (i.e. open/close) --------------- */
+
+/* Allocate a new python curl object */
+static CurlObject *
+util_curl_new(void)
+{
+    CurlObject *self;
+
+    self = (CurlObject *) PyObject_GC_New(CurlObject, p_Curl_Type);
+    if (self == NULL)
+        return NULL;
+    PyObject_GC_Track(self);
+
+    /* Set python curl object initial values */
+    self->dict = NULL;
+    self->handle = NULL;
+    self->state = NULL;
+    self->multi_stack = NULL;
+    self->httppost = NULL;
+    self->httpheader = NULL;
+    self->http200aliases = NULL;
+    self->quote = NULL;
+    self->postquote = NULL;
+    self->prequote = NULL;
+    self->source_postquote = NULL;
+    self->source_prequote = NULL;
+
+    /* Set callback pointers to NULL by default */
+    self->w_cb = NULL;
+    self->h_cb = NULL;
+    self->r_cb = NULL;
+    self->pro_cb = NULL;
+    self->debug_cb = NULL;
+    self->ioctl_cb = NULL;
+
+    /* Set file object pointers to NULL by default */
+    self->readdata_fp = NULL;
+    self->writedata_fp = NULL;
+    self->writeheader_fp = NULL;
+
+    /* Zero string pointer memory buffer used by setopt */
+    memset(self->options, 0, sizeof(self->options));
+    memset(self->error, 0, sizeof(self->error));
+
+    return self;
+}
+
+
+/* constructor - this is a module-level function returning a new instance */
+static CurlObject *
+do_curl_new(PyObject *dummy, PyObject *args)
+{
+    CurlObject *self;
+    int res;
+
+    UNUSED(dummy);
+    if (!PyArg_ParseTuple(args, ":Curl")) {
+        return NULL;
+    }
+
+    /* Allocate python curl object */
+    self = util_curl_new();
+    if (self == NULL)
+        return NULL;
+
+    /* Initialize curl handle */
+    self->handle = curl_easy_init();
+    if (self->handle == NULL)
+        goto error;
+
+    /* Set curl error buffer and zero it */
+    res = curl_easy_setopt(self->handle, CURLOPT_ERRORBUFFER, self->error);
+    if (res != CURLE_OK)
+        goto error;
+    memset(self->error, 0, sizeof(self->error));
+
+    /* Enable NOPROGRESS by default, i.e. no progress output */
+    res = curl_easy_setopt(self->handle, CURLOPT_NOPROGRESS, (long)1);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Disable VERBOSE by default, i.e. no verbose output */
+    res = curl_easy_setopt(self->handle, CURLOPT_VERBOSE, (long)0);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Set backreference */
+    res = curl_easy_setopt(self->handle, CURLOPT_PRIVATE, (char *) self);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Set FTP_ACCOUNT to NULL by default */
+    res = curl_easy_setopt(self->handle, CURLOPT_FTP_ACCOUNT, NULL);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Success - return new object */
+    return self;
+
+error:
+    Py_DECREF(self);    /* this also closes self->handle */
+    PyErr_SetString(ErrorObject, "initializing curl failed");
+    return NULL;
+}
+
+
+/* util function shared by close() and clear() */
+static void
+util_curl_xdecref(CurlObject *self, int flags, CURL *handle)
+{
+    if (flags & 1) {
+        /* Decrement refcount for attributes dictionary. */
+        ZAP(self->dict);
+    }
+
+    if (flags & 2) {
+        /* Decrement refcount for multi_stack. */
+        if (self->multi_stack != NULL) {
+            CurlMultiObject *multi_stack = self->multi_stack;
+            self->multi_stack = NULL;
+            if (multi_stack->multi_handle != NULL && handle != NULL) {
+                (void) curl_multi_remove_handle(multi_stack->multi_handle, handle);
+            }
+            Py_DECREF(multi_stack);
+        }
+    }
+
+    if (flags & 4) {
+        /* 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);
+    }
+
+    if (flags & 8) {
+        /* Decrement refcount for python file objects. */
+        ZAP(self->readdata_fp);
+        ZAP(self->writedata_fp);
+        ZAP(self->writeheader_fp);
+    }
+}
+
+
+static void
+util_curl_close(CurlObject *self)
+{
+    CURL *handle;
+    int i;
+
+    /* Zero handle and thread-state to disallow any operations to be run
+     * from now on */
+    assert(self != NULL);
+    assert(self->ob_type == p_Curl_Type);
+    handle = self->handle;
+    self->handle = NULL;
+    if (handle == NULL) {
+        /* Some paranoia assertions just to make sure the object
+         * deallocation problem is finally really fixed... */
+        assert(self->state == NULL);
+        assert(self->multi_stack == NULL);
+        return;             /* already closed */
+    }
+    self->state = NULL;
+
+    /* Decref multi stuff which uses this handle */
+    util_curl_xdecref(self, 2, handle);
+
+    /* Cleanup curl handle - must be done without the gil */
+    Py_BEGIN_ALLOW_THREADS
+    curl_easy_cleanup(handle);
+    Py_END_ALLOW_THREADS
+    handle = NULL;
+
+    /* Decref callbacks and file handles */
+    util_curl_xdecref(self, 4 | 8, handle);
+
+    /* Free all variables allocated by setopt */
+#undef SFREE
+#define SFREE(v)   if ((v) != NULL) (curl_formfree(v), (v) = NULL)
+    SFREE(self->httppost);
+#undef SFREE
+#define SFREE(v)   if ((v) != NULL) (curl_slist_free_all(v), (v) = NULL)
+    SFREE(self->httpheader);
+    SFREE(self->http200aliases);
+    SFREE(self->quote);
+    SFREE(self->postquote);
+    SFREE(self->prequote);
+    SFREE(self->source_postquote);
+    SFREE(self->source_prequote);
+#undef SFREE
+
+    /* Last, free the options.  This must be done after the curl handle
+     * is closed since libcurl assumes that some options are valid when
+     * invoking curl_easy_cleanup(). */
+    for (i = 0; i < OPTIONS_SIZE; i++) {
+        if (self->options[i] != NULL) {
+            free(self->options[i]);
+            self->options[i] = NULL;
+        }
+    }
+}
+
+
+static void
+do_curl_dealloc(CurlObject *self)
+{
+    PyObject_GC_UnTrack(self);
+    Py_TRASHCAN_SAFE_BEGIN(self)
+
+    ZAP(self->dict);
+    util_curl_close(self);
+
+    PyObject_GC_Del(self);
+    Py_TRASHCAN_SAFE_END(self)
+}
+
+
+static PyObject *
+do_curl_close(CurlObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":close")) {
+        return NULL;
+    }
+    if (check_curl_state(self, 2, "close") != 0) {
+        return NULL;
+    }
+    util_curl_close(self);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+static PyObject *
+do_curl_errstr(CurlObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":errstr")) {
+        return NULL;
+    }
+    if (check_curl_state(self, 1 | 2, "errstr") != 0) {
+        return NULL;
+    }
+    self->error[sizeof(self->error) - 1] = 0;
+    return PyString_FromString(self->error);
+}
+
+
+/* --------------- GC support --------------- */
+
+/* Drop references that may have created reference cycles. */
+static int
+do_curl_clear(CurlObject *self)
+{
+    assert(get_thread_state(self) == NULL);
+    util_curl_xdecref(self, 1 | 2 | 4 | 8, self->handle);
+    return 0;
+}
+
+/* Traverse all refcounted objects. */
+static int
+do_curl_traverse(CurlObject *self, visitproc visit, void *arg)
+{
+    int err;
+#undef VISIT
+#define VISIT(v)    if ((v) != NULL && ((err = visit(v, arg)) != 0)) return err
+
+    VISIT(self->dict);
+    VISIT((PyObject *) self->multi_stack);
+
+    VISIT(self->w_cb);
+    VISIT(self->h_cb);
+    VISIT(self->r_cb);
+    VISIT(self->pro_cb);
+    VISIT(self->debug_cb);
+    VISIT(self->ioctl_cb);
+
+    VISIT(self->readdata_fp);
+    VISIT(self->writedata_fp);
+    VISIT(self->writeheader_fp);
+
+    return 0;
+#undef VISIT
+}
+
+
+/* --------------- perform --------------- */
+
+static PyObject *
+do_curl_perform(CurlObject *self, PyObject *args)
+{
+    int res;
+
+    if (!PyArg_ParseTuple(args, ":perform")) {
+        return NULL;
+    }
+    if (check_curl_state(self, 1 | 2, "perform") != 0) {
+        return NULL;
+    }
+
+    /* Save handle to current thread (used as context for python callbacks) */
+    self->state = PyThreadState_Get();
+    assert(self->state != NULL);
+
+    /* Release global lock and start */
+    Py_BEGIN_ALLOW_THREADS
+    res = curl_easy_perform(self->handle);
+    Py_END_ALLOW_THREADS
+
+    /* Zero thread-state to disallow callbacks to be run from now on */
+    self->state = NULL;
+
+    if (res != CURLE_OK) {
+        CURLERROR_RETVAL();
+    }
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* --------------- callback handlers --------------- */
+
+/* IMPORTANT NOTE: due to threading issues, we cannot call _any_ Python
+ * function without acquiring the thread state in the callback handlers.
+ */
+
+static size_t
+util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    size_t ret = 0;     /* assume error */
+    PyObject *cb;
+    int total_size;
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    cb = flags ? self->h_cb : self->w_cb;
+    if (cb == NULL)
+        goto silent_error;
+    if (size <= 0 || nmemb <= 0)
+        goto done;
+    total_size = (int)(size * nmemb);
+    if (total_size < 0 || (size_t)total_size / size != nmemb) {
+        PyErr_SetString(ErrorObject, "integer overflow in write callback");
+        goto verbose_error;
+    }
+
+    /* run callback */
+    arglist = Py_BuildValue("(s#)", ptr, total_size);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (result == Py_None) {
+        ret = total_size;           /* None means success */
+    }
+    else if (PyInt_Check(result)) {
+        long obj_size = PyInt_AsLong(result);
+        if (obj_size < 0 || obj_size > total_size) {
+            PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size);
+            goto verbose_error;
+        }
+        ret = (size_t) obj_size;    /* success */
+    }
+    else if (PyLong_Check(result)) {
+        long obj_size = PyLong_AsLong(result);
+        if (obj_size < 0 || obj_size > total_size) {
+            PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size);
+            goto verbose_error;
+        }
+        ret = (size_t) obj_size;    /* success */
+    }
+    else {
+        PyErr_SetString(ErrorObject, "write callback must return int or None");
+        goto verbose_error;
+    }
+
+done:
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static size_t
+write_callback(char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    return util_write_callback(0, ptr, size, nmemb, stream);
+}
+
+static size_t
+header_callback(char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    return util_write_callback(1, ptr, size, nmemb, stream);
+}
+
+
+static size_t
+read_callback(char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+
+    size_t ret = CURL_READFUNC_ABORT;     /* assume error, this actually works */
+    int total_size;
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->r_cb == NULL)
+        goto silent_error;
+    if (size <= 0 || nmemb <= 0)
+        goto done;
+    total_size = (int)(size * nmemb);
+    if (total_size < 0 || (size_t)total_size / size != nmemb) {
+        PyErr_SetString(ErrorObject, "integer overflow in read callback");
+        goto verbose_error;
+    }
+
+    /* run callback */
+    arglist = Py_BuildValue("(i)", total_size);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->r_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (PyString_Check(result)) {
+        char *buf = NULL;
+        int obj_size = -1;
+        int r;
+        r = PyString_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 %ld", (long)obj_size, (long)total_size);
+            goto verbose_error;
+        }
+        memcpy(ptr, buf, obj_size);
+        ret = obj_size;             /* success */
+    }
+    else {
+        PyErr_SetString(ErrorObject, "read callback must return string");
+        goto verbose_error;
+    }
+
+done:
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static int
+progress_callback(void *stream,
+                  double dltotal, double dlnow, double ultotal, double ulnow)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    int ret = 1;       /* assume error */
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->pro_cb == NULL)
+        goto silent_error;
+
+    /* run callback */
+    arglist = Py_BuildValue("(dddd)", dltotal, dlnow, ultotal, ulnow);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->pro_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (result == Py_None) {
+        ret = 0;        /* None means success */
+    }
+    else if (PyInt_Check(result)) {
+        ret = (int) PyInt_AsLong(result);
+    }
+    else {
+        ret = PyObject_IsTrue(result);  /* FIXME ??? */
+    }
+
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static int
+debug_callback(CURL *curlobj, curl_infotype type,
+               char *buffer, size_t total_size, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    int ret = 0;       /* always success */
+
+    UNUSED(curlobj);
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->debug_cb == NULL)
+        goto silent_error;
+    if ((int)total_size < 0 || (size_t)((int)total_size) != total_size) {
+        PyErr_SetString(ErrorObject, "integer overflow in debug callback");
+        goto verbose_error;
+    }
+
+    /* run callback */
+    arglist = Py_BuildValue("(is#)", (int)type, buffer, (int)total_size);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->debug_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* return values from debug callbacks should be ignored */
+
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static curlioerr
+ioctl_callback(CURL *curlobj, int cmd, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    int ret = CURLIOE_FAILRESTART;       /* assume error */
+
+    UNUSED(curlobj);
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->ioctl_cb == NULL)
+        goto silent_error;
+
+    /* run callback */
+    arglist = Py_BuildValue("(i)", (int)cmd);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->ioctl_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (result == Py_None) {
+        ret = CURLIOE_OK;        /* None means success */
+    }
+    else if (PyInt_Check(result)) {
+        ret = (int) PyInt_AsLong(result);
+        if (ret >= CURLIOE_LAST || ret < 0) {
+            PyErr_SetString(ErrorObject, "ioctl callback returned invalid value");
+            goto verbose_error;
+        }
+    }
+
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+/* --------------- unsetopt/setopt/getinfo --------------- */
+
+static PyObject *
+util_curl_unsetopt(CurlObject *self, int option)
+{
+    int res;
+    int opt_index = -1;
+
+#define SETOPT2(o,x) \
+    if ((res = curl_easy_setopt(self->handle, (o), (x))) != CURLE_OK) goto error
+#define SETOPT(x)   SETOPT2((CURLoption)option, (x))
+
+    /* FIXME: implement more options. Have to carefully check lib/url.c in the
+     *   libcurl source code to see if it's actually safe to simply
+     *   unset the option. */
+    switch (option)
+    {
+    case CURLOPT_HTTPPOST:
+        SETOPT((void *) 0);
+        curl_formfree(self->httppost);
+        self->httppost = NULL;
+        /* FIXME: what about data->set.httpreq ?? */
+        break;
+    case CURLOPT_INFILESIZE:
+        SETOPT((long) -1);
+        break;
+    case CURLOPT_WRITEHEADER:
+        SETOPT((void *) 0);
+        ZAP(self->writeheader_fp);
+        break;
+    case CURLOPT_CAINFO:
+    case CURLOPT_CAPATH:
+    case CURLOPT_COOKIE:
+    case CURLOPT_COOKIEJAR:
+    case CURLOPT_CUSTOMREQUEST:
+    case CURLOPT_EGDSOCKET:
+    case CURLOPT_FTPPORT:
+    case CURLOPT_PROXYUSERPWD:
+    case CURLOPT_RANDOM_FILE:
+    case CURLOPT_SSL_CIPHER_LIST:
+    case CURLOPT_USERPWD:
+        SETOPT((char *) 0);
+        opt_index = OPT_INDEX(option);
+        break;
+
+    /* info: we explicitly list unsupported options here */
+    case CURLOPT_COOKIEFILE:
+    default:
+        PyErr_SetString(PyExc_TypeError, "unsetopt() is not supported for this option");
+        return NULL;
+    }
+
+    if (opt_index >= 0 && self->options[opt_index] != NULL) {
+        free(self->options[opt_index]);
+        self->options[opt_index] = NULL;
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+
+error:
+    CURLERROR_RETVAL();
+
+#undef SETOPT
+#undef SETOPT2
+}
+
+
+static PyObject *
+do_curl_unsetopt(CurlObject *self, PyObject *args)
+{
+    int option;
+
+    if (!PyArg_ParseTuple(args, "i:unsetopt", &option)) {
+        return NULL;
+    }
+    if (check_curl_state(self, 1 | 2, "unsetopt") != 0) {
+        return NULL;
+    }
+
+    /* early checks of option value */
+    if (option <= 0)
+        goto error;
+    if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE)
+        goto error;
+    if (option % 10000 >= OPTIONS_SIZE)
+        goto error;
+
+    return util_curl_unsetopt(self, option);
+
+error:
+    PyErr_SetString(PyExc_TypeError, "invalid arguments to unsetopt");
+    return NULL;
+}
+
+
+static PyObject *
+do_curl_setopt(CurlObject *self, PyObject *args)
+{
+    int option;
+    PyObject *obj;
+    int res;
+
+    if (!PyArg_ParseTuple(args, "iO:setopt", &option, &obj))
+        return NULL;
+    if (check_curl_state(self, 1 | 2, "setopt") != 0)
+        return NULL;
+
+    /* early checks of option value */
+    if (option <= 0)
+        goto error;
+    if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE)
+        goto error;
+    if (option % 10000 >= OPTIONS_SIZE)
+        goto error;
+
+#if 0 /* XXX - should we ??? */
+    /* Handle the case of None */
+    if (obj == Py_None) {
+        return util_curl_unsetopt(self, option);
+    }
+#endif
+
+    /* Handle the case of string arguments */
+    if (PyString_Check(obj)) {
+        char *str = NULL;
+        int len = -1;
+        char *buf;
+        int opt_index;
+
+        /* Check that the option specified a string as well as the input */
+        switch (option) {
+        case CURLOPT_CAINFO:
+        case CURLOPT_CAPATH:
+        case CURLOPT_COOKIE:
+        case CURLOPT_COOKIEFILE:
+        case CURLOPT_COOKIEJAR:
+        case CURLOPT_CUSTOMREQUEST:
+        case CURLOPT_EGDSOCKET:
+        case CURLOPT_ENCODING:
+        case CURLOPT_FTPPORT:
+        case CURLOPT_INTERFACE:
+        case CURLOPT_KRB4LEVEL:
+        case CURLOPT_NETRC_FILE:
+        case CURLOPT_PROXY:
+        case CURLOPT_PROXYUSERPWD:
+        case CURLOPT_RANDOM_FILE:
+        case CURLOPT_RANGE:
+        case CURLOPT_REFERER:
+        case CURLOPT_SSLCERT:
+        case CURLOPT_SSLCERTTYPE:
+        case CURLOPT_SSLENGINE:
+        case CURLOPT_SSLKEY:
+        case CURLOPT_SSLKEYPASSWD:
+        case CURLOPT_SSLKEYTYPE:
+        case CURLOPT_SSL_CIPHER_LIST:
+        case CURLOPT_URL:
+        case CURLOPT_USERAGENT:
+        case CURLOPT_USERPWD:
+        case CURLOPT_SOURCE_HOST:
+        case CURLOPT_SOURCE_USERPWD:
+        case CURLOPT_SOURCE_PATH:
+/* FIXME: check if more of these options allow binary data */
+            str = PyString_AsString_NoNUL(obj);
+            if (str == NULL)
+                return NULL;
+            break;
+        case CURLOPT_POSTFIELDS:
+            if (PyString_AsStringAndSize(obj, &str, &len) != 0)
+                return NULL;
+            /* automatically set POSTFIELDSIZE */
+            res = curl_easy_setopt(self->handle, CURLOPT_POSTFIELDSIZE, (long)len);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            break;
+        default:
+            PyErr_SetString(PyExc_TypeError, "strings are not supported for this option");
+            return NULL;
+        }
+        /* Allocate memory to hold the string */
+        assert(str != NULL);
+        if (len <= 0)
+            buf = strdup(str);
+        else {
+            buf = (char *) malloc(len);
+            if (buf) memcpy(buf, str, len);
+        }
+        if (buf == NULL)
+            return PyErr_NoMemory();
+        /* Call setopt */
+        res = curl_easy_setopt(self->handle, (CURLoption)option, buf);
+        /* Check for errors */
+        if (res != CURLE_OK) {
+            free(buf);
+            CURLERROR_RETVAL();
+        }
+        /* Save allocated option buffer */
+        opt_index = OPT_INDEX(option);
+        if (self->options[opt_index] != NULL) {
+            free(self->options[opt_index]);
+            self->options[opt_index] = NULL;
+        }
+        self->options[opt_index] = buf;
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+#define IS_LONG_OPTION(o)   (o < CURLOPTTYPE_OBJECTPOINT)
+#define IS_OFF_T_OPTION(o)  (o >= CURLOPTTYPE_OFF_T)
+
+    /* Handle the case of integer arguments */
+    if (PyInt_Check(obj)) {
+        long d = PyInt_AsLong(obj);
+
+        if (IS_LONG_OPTION(option))
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d);
+        else if (IS_OFF_T_OPTION(option))
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)d);
+        else {
+            PyErr_SetString(PyExc_TypeError, "integers are not supported for this option");
+            return NULL;
+        }
+        if (res != CURLE_OK) {
+            CURLERROR_RETVAL();
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Handle the case of long arguments (used by *LARGE options) */
+    if (PyLong_Check(obj)) {
+        PY_LONG_LONG d = PyLong_AsLongLong(obj);
+        if (d == -1 && PyErr_Occurred())
+            return NULL;
+
+        if (IS_LONG_OPTION(option) && (long)d == d)
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d);
+        else if (IS_OFF_T_OPTION(option) && (curl_off_t)d == d)
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)d);
+        else {
+            PyErr_SetString(PyExc_TypeError, "longs are not supported for this option");
+            return NULL;
+        }
+        if (res != CURLE_OK) {
+            CURLERROR_RETVAL();
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+#undef IS_LONG_OPTION
+#undef IS_OFF_T_OPTION
+
+    /* Handle the case of file objects */
+    if (PyFile_Check(obj)) {
+        FILE *fp;
+
+        /* Ensure the option specified a file as well as the input */
+        switch (option) {
+        case CURLOPT_READDATA:
+        case CURLOPT_WRITEDATA:
+            break;
+        case CURLOPT_WRITEHEADER:
+            if (self->w_cb != NULL) {
+                PyErr_SetString(ErrorObject, "cannot combine WRITEHEADER with WRITEFUNCTION.");
+                return NULL;
+            }
+            break;
+        default:
+            PyErr_SetString(PyExc_TypeError, "files are not supported for this option");
+            return NULL;
+        }
+
+        fp = PyFile_AsFile(obj);
+        if (fp == NULL) {
+            PyErr_SetString(PyExc_TypeError, "second argument must be open file");
+            return NULL;
+        }
+        res = curl_easy_setopt(self->handle, (CURLoption)option, fp);
+        if (res != CURLE_OK) {
+            CURLERROR_RETVAL();
+        }
+        Py_INCREF(obj);
+
+        switch (option) {
+        case CURLOPT_READDATA:
+            ZAP(self->readdata_fp);
+            self->readdata_fp = obj;
+            break;
+        case CURLOPT_WRITEDATA:
+            ZAP(self->writedata_fp);
+            self->writedata_fp = obj;
+            break;
+        case CURLOPT_WRITEHEADER:
+            ZAP(self->writeheader_fp);
+            self->writeheader_fp = obj;
+            break;
+        default:
+            assert(0);
+            break;
+        }
+        /* Return success */
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Handle the case of list objects */
+    if (PyList_Check(obj)) {
+        struct curl_slist **old_slist = NULL;
+        struct curl_slist *slist = NULL;
+        int i, len;
+
+        switch (option) {
+        case CURLOPT_HTTP200ALIASES:
+            old_slist = &self->http200aliases;
+            break;
+        case CURLOPT_HTTPHEADER:
+            old_slist = &self->httpheader;
+            break;
+        case CURLOPT_QUOTE:
+            old_slist = &self->quote;
+            break;
+        case CURLOPT_POSTQUOTE:
+            old_slist = &self->postquote;
+            break;
+        case CURLOPT_PREQUOTE:
+            old_slist = &self->prequote;
+            break;
+        case CURLOPT_SOURCE_PREQUOTE:
+            old_slist = &self->source_prequote;
+            break;
+        case CURLOPT_SOURCE_POSTQUOTE:
+            old_slist = &self->source_postquote;
+            break;
+        case CURLOPT_HTTPPOST:
+            break;
+        default:
+            /* None of the list options were recognized, throw exception */
+            PyErr_SetString(PyExc_TypeError, "lists are not supported for this option");
+            return NULL;
+        }
+
+        len = PyList_Size(obj);
+        if (len == 0) {
+            /* Empty list - do nothing */
+            Py_INCREF(Py_None);
+            return Py_None;
+        }
+
+        /* Handle HTTPPOST different since we construct a HttpPost form struct */
+        if (option == CURLOPT_HTTPPOST) {
+            struct curl_httppost *post = NULL;
+            struct curl_httppost *last = NULL;
+
+            for (i = 0; i < len; i++) {
+                char *nstr = NULL, *cstr = NULL;
+                int nlen = -1, clen = -1;
+                PyObject *listitem = PyList_GetItem(obj, i);
+
+                if (!PyTuple_Check(listitem)) {
+                    curl_formfree(post);
+                    PyErr_SetString(PyExc_TypeError, "list items must be tuple objects");
+                    return NULL;
+                }
+                if (PyTuple_GET_SIZE(listitem) != 2) {
+                    curl_formfree(post);
+                    PyErr_SetString(PyExc_TypeError, "tuple must contain two items (name and value)");
+                    return NULL;
+                }
+                /* FIXME: Only support strings as names and values for now */
+                if (PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 0), &nstr, &nlen) != 0 ||
+                    PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 1), &cstr, &clen) != 0) {
+                    curl_formfree(post);
+                    PyErr_SetString(PyExc_TypeError, "tuple items must be strings");
+                    return NULL;
+                }
+                /* INFO: curl_formadd() internally does memdup() the data, so
+                 * embedded NUL characters _are_ allowed here. */
+                res = curl_formadd(&post, &last,
+                                   CURLFORM_COPYNAME, nstr,
+                                   CURLFORM_NAMELENGTH, (long) nlen,
+                                   CURLFORM_COPYCONTENTS, cstr,
+                                   CURLFORM_CONTENTSLENGTH, (long) clen,
+                                   CURLFORM_END);
+                if (res != CURLE_OK) {
+                    curl_formfree(post);
+                    CURLERROR_RETVAL();
+                }
+            }
+            res = curl_easy_setopt(self->handle, CURLOPT_HTTPPOST, post);
+            /* Check for errors */
+            if (res != CURLE_OK) {
+                curl_formfree(post);
+                CURLERROR_RETVAL();
+            }
+            /* Finally, free previously allocated httppost and update */
+            curl_formfree(self->httppost);
+            self->httppost = post;
+
+            Py_INCREF(Py_None);
+            return Py_None;
+        }
+
+        /* Just to be sure we do not bug off here */
+        assert(old_slist != NULL && slist == NULL);
+
+        /* Handle regular list operations on the other options */
+        for (i = 0; i < len; i++) {
+            PyObject *listitem = PyList_GetItem(obj, i);
+            struct curl_slist *nlist;
+            char *str;
+
+            if (!PyString_Check(listitem)) {
+                curl_slist_free_all(slist);
+                PyErr_SetString(PyExc_TypeError, "list items must be string objects");
+                return NULL;
+            }
+            /* INFO: curl_slist_append() internally does strdup() the data, so
+             * no embedded NUL characters allowed here. */
+            str = PyString_AsString_NoNUL(listitem);
+            if (str == NULL) {
+                curl_slist_free_all(slist);
+                return NULL;
+            }
+            nlist = curl_slist_append(slist, str);
+            if (nlist == NULL || nlist->data == NULL) {
+                curl_slist_free_all(slist);
+                return PyErr_NoMemory();
+            }
+            slist = nlist;
+        }
+        res = curl_easy_setopt(self->handle, (CURLoption)option, slist);
+        /* Check for errors */
+        if (res != CURLE_OK) {
+            curl_slist_free_all(slist);
+            CURLERROR_RETVAL();
+        }
+        /* Finally, free previously allocated list and update */
+        curl_slist_free_all(*old_slist);
+        *old_slist = slist;
+
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Handle the case of function objects for callbacks */
+    if (PyFunction_Check(obj) || PyCFunction_Check(obj) || PyMethod_Check(obj)) {
+        /* We use function types here to make sure that our callback
+         * definitions exactly match the <curl/curl.h> interface.
+         */
+        const curl_write_callback w_cb = write_callback;
+        const curl_write_callback h_cb = header_callback;
+        const curl_read_callback r_cb = read_callback;
+        const curl_progress_callback pro_cb = progress_callback;
+        const curl_debug_callback debug_cb = debug_callback;
+        const curl_ioctl_callback ioctl_cb = ioctl_callback;
+
+        switch(option) {
+        case CURLOPT_WRITEFUNCTION:
+            if (self->writeheader_fp != NULL) {
+                PyErr_SetString(ErrorObject, "cannot combine WRITEFUNCTION with WRITEHEADER option.");
+                return NULL;
+            }
+            Py_INCREF(obj);
+            ZAP(self->writedata_fp);
+            ZAP(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);
+            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);
+            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);
+            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);
+            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);
+            self->ioctl_cb = obj;
+            curl_easy_setopt(self->handle, CURLOPT_IOCTLFUNCTION, ioctl_cb);
+            curl_easy_setopt(self->handle, CURLOPT_IOCTLDATA, self);
+            break;
+
+        default:
+            /* None of the function options were recognized, throw exception */
+            PyErr_SetString(PyExc_TypeError, "functions are not supported for this option");
+            return NULL;
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Failed to match any of the function signatures -- return error */
+error:
+    PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt");
+    return NULL;
+}
+
+
+static PyObject *
+do_curl_getinfo(CurlObject *self, PyObject *args)
+{
+    int option;
+    int res;
+
+    if (!PyArg_ParseTuple(args, "i:getinfo", &option)) {
+        return NULL;
+    }
+    if (check_curl_state(self, 1 | 2, "getinfo") != 0) {
+        return NULL;
+    }
+
+    switch (option) {
+    case CURLINFO_FILETIME:
+    case CURLINFO_HEADER_SIZE:
+    case CURLINFO_HTTP_CODE:
+    case CURLINFO_REDIRECT_COUNT:
+    case CURLINFO_REQUEST_SIZE:
+    case CURLINFO_SSL_VERIFYRESULT:
+    case CURLINFO_HTTP_CONNECTCODE:
+    case CURLINFO_HTTPAUTH_AVAIL:
+    case CURLINFO_PROXYAUTH_AVAIL:
+    case CURLINFO_OS_ERRNO:
+    case CURLINFO_NUM_CONNECTS:
+        {
+            /* Return PyInt as result */
+            long l_res = -1;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &l_res);
+            /* Check for errors and return result */
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            return PyInt_FromLong(l_res);
+        }
+
+    case CURLINFO_CONTENT_TYPE:
+    case CURLINFO_EFFECTIVE_URL:
+        {
+            /* Return PyString as result */
+            char *s_res = NULL;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &s_res);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            /* If the resulting string is NULL, return None */
+            if (s_res == NULL) {
+                Py_INCREF(Py_None);
+                return Py_None;
+            }
+            return PyString_FromString(s_res);
+        }
+
+    case CURLINFO_CONNECT_TIME:
+    case CURLINFO_CONTENT_LENGTH_DOWNLOAD:
+    case CURLINFO_CONTENT_LENGTH_UPLOAD:
+    case CURLINFO_NAMELOOKUP_TIME:
+    case CURLINFO_PRETRANSFER_TIME:
+    case CURLINFO_REDIRECT_TIME:
+    case CURLINFO_SIZE_DOWNLOAD:
+    case CURLINFO_SIZE_UPLOAD:
+    case CURLINFO_SPEED_DOWNLOAD:
+    case CURLINFO_SPEED_UPLOAD:
+    case CURLINFO_STARTTRANSFER_TIME:
+    case CURLINFO_TOTAL_TIME:
+        {
+            /* Return PyFloat as result */
+            double d_res = 0.0;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &d_res);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            return PyFloat_FromDouble(d_res);
+        }
+
+    case CURLINFO_SSL_ENGINES:
+        {
+            /* Return a list of strings */
+            struct curl_slist *slist = NULL;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &slist);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            return convert_slist(slist, 1 | 2);
+        }
+    }
+
+    /* Got wrong option on the method call */
+    PyErr_SetString(PyExc_ValueError, "invalid argument to getinfo");
+    return NULL;
+}
+
+
+/*************************************************************************
+// CurlMultiObject
+**************************************************************************/
+
+/* --------------- construct/destruct (i.e. open/close) --------------- */
+
+/* constructor - this is a module-level function returning a new instance */
+static CurlMultiObject *
+do_multi_new(PyObject *dummy, PyObject *args)
+{
+    CurlMultiObject *self;
+
+    UNUSED(dummy);
+    if (!PyArg_ParseTuple(args, ":CurlMulti")) {
+        return NULL;
+    }
+
+    /* Allocate python curl-multi object */
+    self = (CurlMultiObject *) PyObject_GC_New(CurlMultiObject, p_CurlMulti_Type);
+    if (self) {
+        PyObject_GC_Track(self);
+    }
+    else {
+        return NULL;
+    }
+
+    /* Initialize object attributes */
+    self->dict = NULL;
+    self->state = NULL;
+
+    /* Allocate libcurl multi handle */
+    self->multi_handle = curl_multi_init();
+    if (self->multi_handle == NULL) {
+        Py_DECREF(self);
+        PyErr_SetString(ErrorObject, "initializing curl-multi failed");
+        return NULL;
+    }
+    return self;
+}
+
+
+static void
+util_multi_close(CurlMultiObject *self)
+{
+    assert(self != NULL);
+    self->state = NULL;
+    if (self->multi_handle != NULL) {
+        CURLM *multi_handle = self->multi_handle;
+        self->multi_handle = NULL;
+        curl_multi_cleanup(multi_handle);
+    }
+}
+
+
+static void
+do_multi_dealloc(CurlMultiObject *self)
+{
+    PyObject_GC_UnTrack(self);
+    Py_TRASHCAN_SAFE_BEGIN(self)
+
+    ZAP(self->dict);
+    util_multi_close(self);
+
+    PyObject_GC_Del(self);
+    Py_TRASHCAN_SAFE_END(self)
+}
+
+
+static PyObject *
+do_multi_close(CurlMultiObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":close")) {
+        return NULL;
+    }
+    if (check_multi_state(self, 2, "close") != 0) {
+        return NULL;
+    }
+    util_multi_close(self);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* --------------- GC support --------------- */
+
+/* Drop references that may have created reference cycles. */
+static int
+do_multi_clear(CurlMultiObject *self)
+{
+    ZAP(self->dict);
+    return 0;
+}
+
+static int
+do_multi_traverse(CurlMultiObject *self, visitproc visit, void *arg)
+{
+    int err;
+#undef VISIT
+#define VISIT(v)    if ((v) != NULL && ((err = visit(v, arg)) != 0)) return err
+
+    VISIT(self->dict);
+
+    return 0;
+#undef VISIT
+}
+
+/* --------------- perform --------------- */
+
+
+static PyObject *
+do_multi_perform(CurlMultiObject *self, PyObject *args)
+{
+    CURLMcode res;
+    int running = -1;
+
+    if (!PyArg_ParseTuple(args, ":perform")) {
+        return NULL;
+    }
+    if (check_multi_state(self, 1 | 2, "perform") != 0) {
+        return NULL;
+    }
+
+    /* Release global lock and start */
+    self->state = PyThreadState_Get();
+    assert(self->state != NULL);
+    Py_BEGIN_ALLOW_THREADS
+    res = curl_multi_perform(self->multi_handle, &running);
+    Py_END_ALLOW_THREADS
+    self->state = NULL;
+
+    /* We assume these errors are ok, otherwise throw exception */
+    if (res != CURLM_OK && res != CURLM_CALL_MULTI_PERFORM) {
+        CURLERROR_MSG("perform failed");
+    }
+
+    /* Return a tuple with the result and the number of running handles */
+    return Py_BuildValue("(ii)", (int)res, running);
+}
+
+
+/* --------------- add_handle/remove_handle --------------- */
+
+/* static utility function */
+static int
+check_multi_add_remove(const CurlMultiObject *self, const CurlObject *obj)
+{
+    /* check CurlMultiObject status */
+    assert_multi_state(self);
+    if (self->multi_handle == NULL) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - multi-stack is closed");
+        return -1;
+    }
+    if (self->state != NULL) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - multi_perform() already running");
+        return -1;
+    }
+    /* check CurlObject status */
+    assert_curl_state(obj);
+    if (obj->state != NULL) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - perform() of curl object already running");
+        return -1;
+    }
+    if (obj->multi_stack != NULL && obj->multi_stack != self) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - curl object already on another multi-stack");
+        return -1;
+    }
+    return 0;
+}
+
+
+static PyObject *
+do_multi_add_handle(CurlMultiObject *self, PyObject *args)
+{
+    CurlObject *obj;
+    CURLMcode res;
+
+    if (!PyArg_ParseTuple(args, "O!:add_handle", p_Curl_Type, &obj)) {
+        return NULL;
+    }
+    if (check_multi_add_remove(self, obj) != 0) {
+        return NULL;
+    }
+    if (obj->handle == NULL) {
+        PyErr_SetString(ErrorObject, "curl object already closed");
+        return NULL;
+    }
+    if (obj->multi_stack == self) {
+        PyErr_SetString(ErrorObject, "curl object already on this multi-stack");
+        return NULL;
+    }
+    assert(obj->multi_stack == NULL);
+    res = curl_multi_add_handle(self->multi_handle, obj->handle);
+    if (res != CURLM_OK) {
+        CURLERROR_MSG("curl_multi_add_handle() failed due to internal errors");
+    }
+    obj->multi_stack = self;
+    Py_INCREF(self);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+static PyObject *
+do_multi_remove_handle(CurlMultiObject *self, PyObject *args)
+{
+    CurlObject *obj;
+    CURLMcode res;
+
+    if (!PyArg_ParseTuple(args, "O!:remove_handle", p_Curl_Type, &obj)) {
+        return NULL;
+    }
+    if (check_multi_add_remove(self, obj) != 0) {
+        return NULL;
+    }
+    if (obj->handle == NULL) {
+        /* CurlObject handle already closed -- ignore */
+        goto done;
+    }
+    if (obj->multi_stack != self) {
+        PyErr_SetString(ErrorObject, "curl object not on this multi-stack");
+        return NULL;
+    }
+    res = curl_multi_remove_handle(self->multi_handle, obj->handle);
+    if (res != CURLM_OK) {
+        CURLERROR_MSG("curl_multi_remove_handle() failed due to internal errors");
+    }
+    assert(obj->multi_stack == self);
+    obj->multi_stack = NULL;
+    Py_DECREF(self);
+done:
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* --------------- fdset ---------------------- */
+
+static PyObject *
+do_multi_fdset(CurlMultiObject *self, PyObject *args)
+{
+    CURLMcode res;
+    int max_fd = -1, fd;
+    PyObject *ret = NULL;
+    PyObject *read_list = NULL, *write_list = NULL, *except_list = NULL;
+    PyObject *py_fd = NULL;
+
+    if (!PyArg_ParseTuple(args, ":fdset")) {
+        return NULL;
+    }
+    if (check_multi_state(self, 1 | 2, "fdset") != 0) {
+        return NULL;
+    }
+
+    /* Clear file descriptor sets */
+    FD_ZERO(&self->read_fd_set);
+    FD_ZERO(&self->write_fd_set);
+    FD_ZERO(&self->exc_fd_set);
+
+    /* Don't bother releasing the gil as this is just a data structure operation */
+    res = curl_multi_fdset(self->multi_handle, &self->read_fd_set,
+                           &self->write_fd_set, &self->exc_fd_set, &max_fd);
+    if (res != CURLM_OK || max_fd < 0) {
+        CURLERROR_MSG("curl_multi_fdset() failed due to internal errors");
+    }
+
+    /* Allocate lists. */
+    if ((read_list = PyList_New(0)) == NULL) goto error;
+    if ((write_list = PyList_New(0)) == NULL) goto error;
+    if ((except_list = PyList_New(0)) == NULL) goto error;
+
+    /* Populate lists */
+    for (fd = 0; fd < max_fd + 1; fd++) {
+        if (FD_ISSET(fd, &self->read_fd_set)) {
+            if ((py_fd = PyInt_FromLong((long)fd)) == NULL) goto error;
+            if (PyList_Append(read_list, py_fd) != 0) goto error;
+            Py_DECREF(py_fd);
+            py_fd = NULL;
+        }
+        if (FD_ISSET(fd, &self->write_fd_set)) {
+            if ((py_fd = PyInt_FromLong((long)fd)) == NULL) goto error;
+            if (PyList_Append(write_list, py_fd) != 0) goto error;
+            Py_DECREF(py_fd);
+            py_fd = NULL;
+        }
+        if (FD_ISSET(fd, &self->exc_fd_set)) {
+            if ((py_fd = PyInt_FromLong((long)fd)) == NULL) goto error;
+            if (PyList_Append(except_list, py_fd) != 0) goto error;
+            Py_DECREF(py_fd);
+            py_fd = NULL;
+        }
+    }
+
+    /* Return a tuple with the 3 lists */
+    ret = Py_BuildValue("(OOO)", read_list, write_list, except_list);
+error:
+    Py_XDECREF(py_fd);
+    Py_XDECREF(except_list);
+    Py_XDECREF(write_list);
+    Py_XDECREF(read_list);
+    return ret;
+}
+
+
+/* --------------- info_read --------------- */
+
+static PyObject *
+do_multi_info_read(CurlMultiObject *self, PyObject *args)
+{
+    PyObject *ret = NULL;
+    PyObject *ok_list = NULL, *err_list = NULL;
+    CURLMsg *msg;
+    int in_queue = 0, num_results = INT_MAX;
+
+    /* Sanity checks */
+    if (!PyArg_ParseTuple(args, "|i:info_read", &num_results)) {
+        return NULL;
+    }
+    if (num_results <= 0) {
+        PyErr_SetString(ErrorObject, "argument to info_read must be greater than zero");
+        return NULL;
+    }
+    if (check_multi_state(self, 1 | 2, "info_read") != 0) {
+        return NULL;
+    }
+
+    if ((ok_list = PyList_New(0)) == NULL) goto error;
+    if ((err_list = PyList_New(0)) == NULL) goto error;
+
+    /* Loop through all messages */
+    while ((msg = curl_multi_info_read(self->multi_handle, &in_queue)) != NULL) {
+        CURLcode res;
+        CurlObject *co = NULL;
+
+        /* Check for termination as specified by the user */
+        if (num_results-- <= 0) {
+            break;
+        }
+
+        /* Fetch the curl object that corresponds to the curl handle in the message */
+        res = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &co);
+        if (res != CURLE_OK || co == NULL) {
+            Py_DECREF(err_list);
+            Py_DECREF(ok_list);
+            CURLERROR_MSG("Unable to fetch curl handle from curl object");
+        }
+        assert(co->ob_type == p_Curl_Type);
+        if (msg->msg != CURLMSG_DONE) {
+            /* FIXME: what does this mean ??? */
+        }
+        if (msg->data.result == CURLE_OK) {
+            /* Append curl object to list of objects which succeeded */
+            if (PyList_Append(ok_list, (PyObject *)co) != 0) {
+                goto error;
+            }
+        }
+        else {
+            /* Create a result tuple that will get added to err_list. */
+            PyObject *v = Py_BuildValue("(Ois)", (PyObject *)co, (int)msg->data.result, co->error);
+            /* Append curl object to list of objects which failed */
+            if (v == NULL || PyList_Append(err_list, v) != 0) {
+                Py_XDECREF(v);
+                goto error;
+            }
+            Py_DECREF(v);
+        }
+    }
+    /* Return (number of queued messages, [ok_objects], [error_objects]) */
+    ret = Py_BuildValue("(iOO)", in_queue, ok_list, err_list);
+error:
+    Py_XDECREF(err_list);
+    Py_XDECREF(ok_list);
+    return ret;
+}
+
+
+/* --------------- select --------------- */
+
+static PyObject *
+do_multi_select(CurlMultiObject *self, PyObject *args)
+{
+    int max_fd = -1, n;
+    double timeout = -1.0;
+    struct timeval tv, *tvp;
+    CURLMcode res;
+
+    if (!PyArg_ParseTuple(args, "|d:select", &timeout)) {
+        return NULL;
+    }
+    if (check_multi_state(self, 1 | 2, "select") != 0) {
+        return NULL;
+    }
+
+   if (timeout == -1.0) {
+        /* no timeout given - wait forever */
+        tvp = NULL;
+   } else if (timeout < 0 || timeout >= 365 * 24 * 60 * 60) {
+        PyErr_SetString(PyExc_OverflowError, "invalid timeout period");
+        return NULL;
+   } else {
+        long seconds = (long)timeout;
+        timeout = timeout - (double)seconds;
+        assert(timeout >= 0.0); assert(timeout < 1.0);
+        tv.tv_sec = seconds;
+        tv.tv_usec = (long)(timeout*1000000.0);
+        tvp = &tv;
+    }
+
+    FD_ZERO(&self->read_fd_set);
+    FD_ZERO(&self->write_fd_set);
+    FD_ZERO(&self->exc_fd_set);
+
+    res = curl_multi_fdset(self->multi_handle, &self->read_fd_set,
+                           &self->write_fd_set, &self->exc_fd_set, &max_fd);
+    if (res != CURLM_OK) {
+        CURLERROR_MSG("multi_fdset failed");
+    }
+
+    if (max_fd < 0) {
+        n = 0;
+    }
+    else {
+        Py_BEGIN_ALLOW_THREADS
+        n = select(max_fd + 1, &self->read_fd_set, &self->write_fd_set, &self->exc_fd_set, tvp);
+        Py_END_ALLOW_THREADS
+        /* info: like Python's socketmodule.c we do not raise an exception
+         *       if select() fails - we'll leave it to the actual libcurl
+         *       socket code to report any errors.
+         */
+    }
+
+    return PyInt_FromLong(n);
+}
+
+
+/*************************************************************************
+// type definitions
+**************************************************************************/
+
+/* --------------- methods --------------- */
+
+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.  Throws pycurl.error exception upon failure.\n";
+static char co_perform_doc [] = "perform() -> None.  Perform a file transfer.  Throws pycurl.error exception upon failure.\n";
+static char co_setopt_doc [] = "setopt(option, parameter) -> None.  Set curl session option.  Throws pycurl.error exception upon failure.\n";
+static char co_unsetopt_doc [] = "unsetopt(option) -> None.  Reset curl session option to default value.  Throws pycurl.error exception upon failure.\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 PyMethodDef curlobject_methods[] = {
+    {"close", (PyCFunction)do_curl_close, METH_VARARGS, co_close_doc},
+    {"errstr", (PyCFunction)do_curl_errstr, METH_VARARGS, co_errstr_doc},
+    {"getinfo", (PyCFunction)do_curl_getinfo, METH_VARARGS, co_getinfo_doc},
+    {"perform", (PyCFunction)do_curl_perform, METH_VARARGS, co_perform_doc},
+    {"setopt", (PyCFunction)do_curl_setopt, METH_VARARGS, co_setopt_doc},
+    {"unsetopt", (PyCFunction)do_curl_unsetopt, METH_VARARGS, co_unsetopt_doc},
+    {NULL, NULL, 0, NULL}
+};
+
+static PyMethodDef curlmultiobject_methods[] = {
+    {"add_handle", (PyCFunction)do_multi_add_handle, METH_VARARGS, NULL},
+    {"close", (PyCFunction)do_multi_close, METH_VARARGS, NULL},
+    {"fdset", (PyCFunction)do_multi_fdset, METH_VARARGS, co_multi_fdset_doc},
+    {"info_read", (PyCFunction)do_multi_info_read, METH_VARARGS, co_multi_info_read_doc},
+    {"perform", (PyCFunction)do_multi_perform, METH_VARARGS, NULL},
+    {"remove_handle", (PyCFunction)do_multi_remove_handle, METH_VARARGS, NULL},
+    {"select", (PyCFunction)do_multi_select, METH_VARARGS, co_multi_select_doc},
+    {NULL, NULL, 0, NULL}
+};
+
+
+/* --------------- setattr/getattr --------------- */
+
+static PyObject *curlobject_constants = NULL;
+static PyObject *curlmultiobject_constants = NULL;
+
+static int
+my_setattr(PyObject **dict, char *name, PyObject *v)
+{
+    if (v == NULL) {
+        int rv = -1;
+        if (*dict != NULL)
+            rv = PyDict_DelItemString(*dict, name);
+        if (rv < 0)
+            PyErr_SetString(PyExc_AttributeError, "delete non-existing attribute");
+        return rv;
+    }
+    if (*dict == NULL) {
+        *dict = PyDict_New();
+        if (*dict == NULL)
+            return -1;
+    }
+    return PyDict_SetItemString(*dict, name, v);
+}
+
+static PyObject *
+my_getattr(PyObject *co, char *name, PyObject *dict1, PyObject *dict2, PyMethodDef *m)
+{
+    PyObject *v = NULL;
+    if (v == NULL && dict1 != NULL)
+        v = PyDict_GetItemString(dict1, name);
+    if (v == NULL && dict2 != NULL)
+        v = PyDict_GetItemString(dict2, name);
+    if (v != NULL) {
+        Py_INCREF(v);
+        return v;
+    }
+    return Py_FindMethod(m, co, name);
+}
+
+static int
+do_curl_setattr(CurlObject *co, char *name, PyObject *v)
+{
+    assert_curl_state(co);
+    return my_setattr(&co->dict, name, v);
+}
+
+static int
+do_multi_setattr(CurlMultiObject *co, char *name, PyObject *v)
+{
+    assert_multi_state(co);
+    return my_setattr(&co->dict, name, v);
+}
+
+static PyObject *
+do_curl_getattr(CurlObject *co, char *name)
+{
+    assert_curl_state(co);
+    return my_getattr((PyObject *)co, name, co->dict,
+                      curlobject_constants, curlobject_methods);
+}
+
+static PyObject *
+do_multi_getattr(CurlMultiObject *co, char *name)
+{
+    assert_multi_state(co);
+    return my_getattr((PyObject *)co, name, co->dict,
+                      curlmultiobject_constants, curlmultiobject_methods);
+}
+
+
+/* --------------- actual type definitions --------------- */
+
+static PyTypeObject Curl_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                          /* ob_size */
+    "pycurl.Curl",              /* tp_name */
+    sizeof(CurlObject),         /* tp_basicsize */
+    0,                          /* tp_itemsize */
+    /* Methods */
+    (destructor)do_curl_dealloc,   /* tp_dealloc */
+    0,                          /* tp_print */
+    (getattrfunc)do_curl_getattr,  /* tp_getattr */
+    (setattrfunc)do_curl_setattr,  /* tp_setattr */
+    0,                          /* tp_compare */
+    0,                          /* tp_repr */
+    0,                          /* tp_as_number */
+    0,                          /* tp_as_sequence */
+    0,                          /* tp_as_mapping */
+    0,                          /* tp_hash */
+    0,                          /* tp_call */
+    0,                          /* tp_str */
+    0,                          /* tp_getattro */
+    0,                          /* tp_setattro */
+    0,                          /* tp_as_buffer */
+    Py_TPFLAGS_HAVE_GC,         /* tp_flags */
+    0,                          /* tp_doc */
+    (traverseproc)do_curl_traverse, /* tp_traverse */
+    (inquiry)do_curl_clear      /* tp_clear */
+    /* More fields follow here, depending on your Python version. You can
+     * safely ignore any compiler warnings about missing initializers.
+     */
+};
+
+static PyTypeObject CurlMulti_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                          /* ob_size */
+    "pycurl.CurlMulti",         /* tp_name */
+    sizeof(CurlMultiObject),    /* tp_basicsize */
+    0,                          /* tp_itemsize */
+    /* Methods */
+    (destructor)do_multi_dealloc,   /* tp_dealloc */
+    0,                          /* tp_print */
+    (getattrfunc)do_multi_getattr,  /* tp_getattr */
+    (setattrfunc)do_multi_setattr,  /* tp_setattr */
+    0,                          /* tp_compare */
+    0,                          /* tp_repr */
+    0,                          /* tp_as_number */
+    0,                          /* tp_as_sequence */
+    0,                          /* tp_as_mapping */
+    0,                          /* tp_hash */
+    0,                          /* tp_call */
+    0,                          /* tp_str */
+    0,                          /* tp_getattro */
+    0,                          /* tp_setattro */
+    0,                          /* tp_as_buffer */
+    Py_TPFLAGS_HAVE_GC,         /* tp_flags */
+    0,                          /* tp_doc */
+    (traverseproc)do_multi_traverse, /* tp_traverse */
+    (inquiry)do_multi_clear     /* tp_clear */
+    /* More fields follow here, depending on your Python version. You can
+     * safely ignore any compiler warnings about missing initializers.
+     */
+};
+
+
+/*************************************************************************
+// module level
+// Note that the object constructors (do_curl_new, do_multi_new)
+// are module-level functions as well.
+**************************************************************************/
+
+static PyObject *
+do_global_init(PyObject *dummy, PyObject *args)
+{
+    int res, option;
+
+    UNUSED(dummy);
+    if (!PyArg_ParseTuple(args, "i:global_init", &option)) {
+        return NULL;
+    }
+
+    if (!(option == CURL_GLOBAL_SSL ||
+          option == CURL_GLOBAL_WIN32 ||
+          option == CURL_GLOBAL_ALL ||
+          option == CURL_GLOBAL_NOTHING)) {
+        PyErr_SetString(PyExc_ValueError, "invalid option to global_init");
+        return NULL;
+    }
+
+    res = curl_global_init(option);
+    if (res != CURLE_OK) {
+        PyErr_SetString(ErrorObject, "unable to set global option");
+        return NULL;
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+static PyObject *
+do_global_cleanup(PyObject *dummy, PyObject *args)
+{
+    UNUSED(dummy);
+    if (!PyArg_ParseTuple(args, ":global_cleanup")) {
+        return NULL;
+    }
+
+    curl_global_cleanup();
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+
+static PyObject *vi_str(const char *s)
+{
+    if (s == NULL) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+    while (*s == ' ' || *s == '\t')
+        s++;
+    return PyString_FromString(s);
+}
+
+static PyObject *
+do_version_info(PyObject *dummy, PyObject *args)
+{
+    const curl_version_info_data *vi;
+    PyObject *ret = NULL;
+    PyObject *protocols = NULL;
+    PyObject *tmp;
+    int i;
+    int stamp = CURLVERSION_NOW;
+
+    UNUSED(dummy);
+    if (!PyArg_ParseTuple(args, "|i:version_info", &stamp)) {
+        return NULL;
+    }
+    vi = curl_version_info((CURLversion) stamp);
+    if (vi == NULL) {
+        PyErr_SetString(ErrorObject, "unable to get version info");
+        return NULL;
+    }
+
+    /* Note: actually libcurl in lib/version.c does ignore
+     * the "stamp" parm, and so do we */
+
+    for (i = 0; vi->protocols[i] != NULL; )
+        i++;
+    protocols = PyTuple_New(i);
+    if (protocols == NULL)
+        goto error;
+    for (i = 0; vi->protocols[i] != NULL; i++) {
+        tmp = vi_str(vi->protocols[i]);
+        if (tmp == NULL)
+            goto error;
+        PyTuple_SET_ITEM(protocols, i, tmp);
+    }
+    ret = PyTuple_New(12);
+    if (ret == NULL)
+        goto error;
+
+#define SET(i, v) \
+        tmp = (v); if (tmp == NULL) goto error; PyTuple_SET_ITEM(ret, i, tmp)
+    SET(0, PyInt_FromLong((long) vi->age));
+    SET(1, vi_str(vi->version));
+    SET(2, PyInt_FromLong(vi->version_num));
+    SET(3, vi_str(vi->host));
+    SET(4, PyInt_FromLong(vi->features));
+    SET(5, vi_str(vi->ssl_version));
+    SET(6, PyInt_FromLong(vi->ssl_version_num));
+    SET(7, vi_str(vi->libz_version));
+    SET(8, protocols);
+    SET(9, vi_str(vi->ares));
+    SET(10, PyInt_FromLong(vi->ares_num));
+    SET(11, vi_str(vi->libidn));
+#undef SET
+    return ret;
+
+error:
+    Py_XDECREF(ret);
+    Py_XDECREF(protocols);
+    return NULL;
+}
+
+
+/* Per function docstrings */
+static 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 char pycurl_version_info_doc [] =
+"version_info() -> tuple.  Returns a 12-tuple with the version info.\n";
+
+static 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";
+
+
+/* List of functions defined in this module */
+static PyMethodDef curl_methods[] = {
+    {"global_init", (PyCFunction)do_global_init, METH_VARARGS, pycurl_global_init_doc},
+    {"global_cleanup", (PyCFunction)do_global_cleanup, METH_VARARGS, pycurl_global_cleanup_doc},
+    {"version_info", (PyCFunction)do_version_info, METH_VARARGS, pycurl_version_info_doc},
+    {"Curl", (PyCFunction)do_curl_new, METH_VARARGS, pycurl_curl_new_doc},
+    {"CurlMulti", (PyCFunction)do_multi_new, METH_VARARGS, pycurl_multi_new_doc},
+    {NULL, NULL, 0, NULL}
+};
+
+
+/* Module docstring */
+static char module_doc [] =
+"This module implements an interface to the cURL library.\n"
+"\n"
+"Types:\n"
+"\n"
+"Curl() -> New object.  Create a new curl object.\n"
+"CurlMulti() -> New object.  Create a new curl multi-object.\n"
+"\n"
+"Functions:\n"
+"\n"
+"global_init(option) -> None.  Initialize curl environment.\n"
+"global_cleanup() -> None.  Cleanup curl environment.\n"
+"version_info() -> tuple.  Return version information.\n"
+;
+
+
+/* Helper functions for inserting constants into the module namespace */
+
+static void
+insobj2(PyObject *dict1, PyObject *dict2, char *name, PyObject *value)
+{
+    /* Insert an object into one or two dicts. Eats a reference to value.
+     * See also the implementation of PyDict_SetItemString(). */
+    PyObject *key = NULL;
+
+    if (dict1 == NULL && dict2 == NULL)
+        goto error;
+    if (value == NULL)
+        goto error;
+    key = PyString_FromString(name);
+    if (key == NULL)
+        goto error;
+#if 0
+    PyString_InternInPlace(&key);   /* XXX Should we really? */
+#endif
+    if (dict1 != NULL) {
+        assert(PyDict_GetItem(dict1, key) == NULL);
+        if (PyDict_SetItem(dict1, key, value) != 0)
+            goto error;
+    }
+    if (dict2 != NULL && dict2 != dict1) {
+        assert(PyDict_GetItem(dict2, key) == NULL);
+        if (PyDict_SetItem(dict2, key, value) != 0)
+            goto error;
+    }
+    Py_DECREF(key);
+    Py_DECREF(value);
+    return;
+error:
+    Py_FatalError("pycurl: FATAL: insobj2() failed");
+    assert(0);
+}
+
+static void
+insstr(PyObject *d, char *name, char *value)
+{
+    PyObject *v = PyString_FromString(value);
+    insobj2(d, NULL, name, v);
+}
+
+static void
+insint(PyObject *d, char *name, long value)
+{
+    PyObject *v = PyInt_FromLong(value);
+    insobj2(d, NULL, name, v);
+}
+
+static void
+insint_c(PyObject *d, char *name, long value)
+{
+    PyObject *v = PyInt_FromLong(value);
+    insobj2(d, curlobject_constants, name, v);
+}
+
+static void
+insint_m(PyObject *d, char *name, long value)
+{
+    PyObject *v = PyInt_FromLong(value);
+    insobj2(d, curlmultiobject_constants, name, v);
+}
+
+
+/* Initialization function for the module */
+#if defined(PyMODINIT_FUNC)
+PyMODINIT_FUNC
+#else
+#if defined(__cplusplus)
+extern "C"
+#endif
+DL_EXPORT(void)
+#endif
+initpycurl(void)
+{
+    PyObject *m, *d;
+    const curl_version_info_data *vi;
+
+    /* Initialize the type of the new type objects here; doing it here
+     * is required for portability to Windows without requiring C++. */
+    p_Curl_Type = &Curl_Type;
+    p_CurlMulti_Type = &CurlMulti_Type;
+    Curl_Type.ob_type = &PyType_Type;
+    CurlMulti_Type.ob_type = &PyType_Type;
+
+    /* Create the module and add the functions */
+    m = Py_InitModule3("pycurl", curl_methods, module_doc);
+    assert(m != NULL && PyModule_Check(m));
+
+    /* Add error object to the module */
+    d = PyModule_GetDict(m);
+    assert(d != NULL);
+    ErrorObject = PyErr_NewException("pycurl.error", NULL, NULL);
+    assert(ErrorObject != NULL);
+    PyDict_SetItemString(d, "error", ErrorObject);
+
+    curlobject_constants = PyDict_New();
+    assert(curlobject_constants != NULL);
+
+    /* Add version strings to the module */
+    insstr(d, "version", curl_version());
+    insstr(d, "COMPILE_DATE", __DATE__ " " __TIME__);
+    insint(d, "COMPILE_PY_VERSION_HEX", PY_VERSION_HEX);
+    insint(d, "COMPILE_LIBCURL_VERSION_NUM", LIBCURL_VERSION_NUM);
+
+    /**
+     ** the order of these constants mostly follows <curl/curl.h>
+     **/
+
+    /* curl_infotype: the kind of data that is passed to information_callback */
+/* XXX do we actually need curl_infotype in pycurl ??? */
+    insint_c(d, "INFOTYPE_TEXT", CURLINFO_TEXT);
+    insint_c(d, "INFOTYPE_HEADER_IN", CURLINFO_HEADER_IN);
+    insint_c(d, "INFOTYPE_HEADER_OUT", CURLINFO_HEADER_OUT);
+    insint_c(d, "INFOTYPE_DATA_IN", CURLINFO_DATA_IN);
+    insint_c(d, "INFOTYPE_DATA_OUT", CURLINFO_DATA_OUT);
+    insint_c(d, "INFOTYPE_SSL_DATA_IN", CURLINFO_SSL_DATA_IN);
+    insint_c(d, "INFOTYPE_SSL_DATA_OUT", CURLINFO_SSL_DATA_OUT);
+
+    /* CURLcode: error codes */
+/* FIXME: lots of error codes are missing */
+    insint_c(d, "E_OK", CURLE_OK);
+    insint_c(d, "E_UNSUPPORTED_PROTOCOL", CURLE_UNSUPPORTED_PROTOCOL);
+
+    /* curl_proxytype: constants for setopt(PROXYTYPE, x) */
+    insint_c(d, "PROXYTYPE_HTTP", CURLPROXY_HTTP);
+    insint_c(d, "PROXYTYPE_SOCKS4", CURLPROXY_SOCKS4);
+    insint_c(d, "PROXYTYPE_SOCKS5", CURLPROXY_SOCKS5);
+
+    /* curl_httpauth: constants for setopt(HTTPAUTH, x) */
+    insint_c(d, "HTTPAUTH_NONE", CURLAUTH_NONE);
+    insint_c(d, "HTTPAUTH_BASIC", CURLAUTH_BASIC);
+    insint_c(d, "HTTPAUTH_DIGEST", CURLAUTH_DIGEST);
+    insint_c(d, "HTTPAUTH_GSSNEGOTIATE", CURLAUTH_GSSNEGOTIATE);
+    insint_c(d, "HTTPAUTH_NTLM", CURLAUTH_NTLM);
+    insint_c(d, "HTTPAUTH_ANY", CURLAUTH_ANY);
+    insint_c(d, "HTTPAUTH_ANYSAFE", CURLAUTH_ANYSAFE);
+
+    /* curl_ftpssl: constants for setopt(FTP_SSL, x) */
+    insint_c(d, "FTPSSL_NONE", CURLFTPSSL_NONE);
+    insint_c(d, "FTPSSL_TRY", CURLFTPSSL_TRY);
+    insint_c(d, "FTPSSL_CONTROL", CURLFTPSSL_CONTROL);
+    insint_c(d, "FTPSSL_ALL", CURLFTPSSL_ALL);
+
+    /* curl_ftpauth: constants for setopt(FTPSSLAUTH, x) */
+    insint_c(d, "FTPAUTH_DEFAULT", CURLFTPAUTH_DEFAULT);
+    insint_c(d, "FTPAUTH_SSL", CURLFTPAUTH_SSL);
+    insint_c(d, "FTPAUTH_TLS", CURLFTPAUTH_TLS);
+
+    /* CURLoption: symbolic constants for setopt() */
+/* FIXME: reorder these to match <curl/curl.h> */
+    insint_c(d, "FILE", CURLOPT_WRITEDATA);
+    insint_c(d, "INFILE", CURLOPT_READDATA);
+    insint_c(d, "WRITEDATA", CURLOPT_WRITEDATA);
+    insint_c(d, "WRITEFUNCTION", CURLOPT_WRITEFUNCTION);
+    insint_c(d, "READDATA", CURLOPT_READDATA);
+    insint_c(d, "READFUNCTION", CURLOPT_READFUNCTION);
+    insint_c(d, "INFILESIZE", CURLOPT_INFILESIZE);
+    insint_c(d, "URL", CURLOPT_URL);
+    insint_c(d, "PROXY", CURLOPT_PROXY);
+    insint_c(d, "PROXYPORT", CURLOPT_PROXYPORT);
+    insint_c(d, "HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL);
+    insint_c(d, "VERBOSE", CURLOPT_VERBOSE);
+    insint_c(d, "HEADER", CURLOPT_HEADER);
+    insint_c(d, "NOPROGRESS", CURLOPT_NOPROGRESS);
+    insint_c(d, "NOBODY", CURLOPT_NOBODY);
+    insint_c(d, "FAILONERROR", CURLOPT_FAILONERROR);
+    insint_c(d, "UPLOAD", CURLOPT_UPLOAD);
+    insint_c(d, "POST", CURLOPT_POST);
+    insint_c(d, "FTPLISTONLY", CURLOPT_FTPLISTONLY);
+    insint_c(d, "FTPAPPEND", CURLOPT_FTPAPPEND);
+    insint_c(d, "NETRC", CURLOPT_NETRC);
+    insint_c(d, "FOLLOWLOCATION", CURLOPT_FOLLOWLOCATION);
+    insint_c(d, "TRANSFERTEXT", CURLOPT_TRANSFERTEXT);
+    insint_c(d, "PUT", CURLOPT_PUT);
+    insint_c(d, "USERPWD", CURLOPT_USERPWD);
+    insint_c(d, "PROXYUSERPWD", CURLOPT_PROXYUSERPWD);
+    insint_c(d, "RANGE", CURLOPT_RANGE);
+    insint_c(d, "TIMEOUT", CURLOPT_TIMEOUT);
+    insint_c(d, "POSTFIELDS", CURLOPT_POSTFIELDS);
+    insint_c(d, "POSTFIELDSIZE", CURLOPT_POSTFIELDSIZE);
+    insint_c(d, "REFERER", CURLOPT_REFERER);
+    insint_c(d, "USERAGENT", CURLOPT_USERAGENT);
+    insint_c(d, "FTPPORT", CURLOPT_FTPPORT);
+    insint_c(d, "LOW_SPEED_LIMIT", CURLOPT_LOW_SPEED_LIMIT);
+    insint_c(d, "LOW_SPEED_TIME", CURLOPT_LOW_SPEED_TIME);
+    insint_c(d, "CURLOPT_RESUME_FROM", CURLOPT_RESUME_FROM);
+    insint_c(d, "COOKIE", CURLOPT_COOKIE);
+    insint_c(d, "HTTPHEADER", CURLOPT_HTTPHEADER);
+    insint_c(d, "HTTPPOST", CURLOPT_HTTPPOST);
+    insint_c(d, "SSLCERT", CURLOPT_SSLCERT);
+    insint_c(d, "SSLCERTPASSWD", CURLOPT_SSLCERTPASSWD);
+    insint_c(d, "CRLF", CURLOPT_CRLF);
+    insint_c(d, "QUOTE", CURLOPT_QUOTE);
+    insint_c(d, "POSTQUOTE", CURLOPT_POSTQUOTE);
+    insint_c(d, "PREQUOTE", CURLOPT_PREQUOTE);
+    insint_c(d, "WRITEHEADER", CURLOPT_WRITEHEADER);
+    insint_c(d, "HEADERFUNCTION", CURLOPT_HEADERFUNCTION);
+    insint_c(d, "COOKIEFILE", CURLOPT_COOKIEFILE);
+    insint_c(d, "SSLVERSION", CURLOPT_SSLVERSION);
+    insint_c(d, "TIMECONDITION", CURLOPT_TIMECONDITION);
+    insint_c(d, "TIMEVALUE", CURLOPT_TIMEVALUE);
+    insint_c(d, "CUSTOMREQUEST", CURLOPT_CUSTOMREQUEST);
+    insint_c(d, "STDERR", CURLOPT_STDERR);
+    insint_c(d, "INTERFACE", CURLOPT_INTERFACE);
+    insint_c(d, "KRB4LEVEL", CURLOPT_KRB4LEVEL);
+    insint_c(d, "PROGRESSFUNCTION", CURLOPT_PROGRESSFUNCTION);
+    insint_c(d, "SSL_VERIFYPEER", CURLOPT_SSL_VERIFYPEER);
+    insint_c(d, "CAPATH", CURLOPT_CAPATH);
+    insint_c(d, "CAINFO", CURLOPT_CAINFO);
+    insint_c(d, "OPT_FILETIME", CURLOPT_FILETIME);
+    insint_c(d, "MAXREDIRS", CURLOPT_MAXREDIRS);
+    insint_c(d, "MAXCONNECTS", CURLOPT_MAXCONNECTS);
+    insint_c(d, "CLOSEPOLICY", CURLOPT_CLOSEPOLICY);
+    insint_c(d, "FRESH_CONNECT", CURLOPT_FRESH_CONNECT);
+    insint_c(d, "FORBID_REUSE", CURLOPT_FORBID_REUSE);
+    insint_c(d, "RANDOM_FILE", CURLOPT_RANDOM_FILE);
+    insint_c(d, "EGDSOCKET", CURLOPT_EGDSOCKET);
+    insint_c(d, "CONNECTTIMEOUT", CURLOPT_CONNECTTIMEOUT);
+    insint_c(d, "HTTPGET", CURLOPT_HTTPGET);
+    insint_c(d, "SSL_VERIFYHOST", CURLOPT_SSL_VERIFYHOST);
+    insint_c(d, "COOKIEJAR", CURLOPT_COOKIEJAR);
+    insint_c(d, "SSL_CIPHER_LIST", CURLOPT_SSL_CIPHER_LIST);
+    insint_c(d, "HTTP_VERSION", CURLOPT_HTTP_VERSION);
+    insint_c(d, "FTP_USE_EPSV", CURLOPT_FTP_USE_EPSV);
+    insint_c(d, "SSLCERTTYPE", CURLOPT_SSLCERTTYPE);
+    insint_c(d, "SSLKEY", CURLOPT_SSLKEY);
+    insint_c(d, "SSLKEYTYPE", CURLOPT_SSLKEYTYPE);
+    insint_c(d, "SSLKEYPASSWD", CURLOPT_SSLKEYPASSWD);
+    insint_c(d, "SSLENGINE", CURLOPT_SSLENGINE);
+    insint_c(d, "SSLENGINE_DEFAULT", CURLOPT_SSLENGINE_DEFAULT);
+    insint_c(d, "DNS_CACHE_TIMEOUT", CURLOPT_DNS_CACHE_TIMEOUT);
+    insint_c(d, "DNS_USE_GLOBAL_CACHE", CURLOPT_DNS_USE_GLOBAL_CACHE);
+    insint_c(d, "DEBUGFUNCTION", CURLOPT_DEBUGFUNCTION);
+    insint_c(d, "BUFFERSIZE", CURLOPT_BUFFERSIZE);
+    insint_c(d, "NOSIGNAL", CURLOPT_NOSIGNAL);
+    insint_c(d, "SHARE", CURLOPT_SHARE);
+    insint_c(d, "PROXYTYPE", CURLOPT_PROXYTYPE);
+    insint_c(d, "ENCODING", CURLOPT_ENCODING);
+    insint_c(d, "HTTP200ALIASES", CURLOPT_HTTP200ALIASES);
+    insint_c(d, "UNRESTRICTED_AUTH", CURLOPT_UNRESTRICTED_AUTH);
+    insint_c(d, "FTP_USE_EPRT", CURLOPT_FTP_USE_EPRT);
+    insint_c(d, "HTTPAUTH", CURLOPT_HTTPAUTH);
+    insint_c(d, "FTP_CREATE_MISSING_DIRS", CURLOPT_FTP_CREATE_MISSING_DIRS);
+    insint_c(d, "PROXYAUTH", CURLOPT_PROXYAUTH);
+    insint_c(d, "FTP_RESPONSE_TIMEOUT", CURLOPT_FTP_RESPONSE_TIMEOUT);
+    insint_c(d, "IPRESOLVE", CURLOPT_IPRESOLVE);
+    insint_c(d, "MAXFILESIZE", CURLOPT_MAXFILESIZE);
+    insint_c(d, "INFILESIZE_LARGE", CURLOPT_INFILESIZE_LARGE);
+    insint_c(d, "RESUME_FROM_LARGE", CURLOPT_RESUME_FROM_LARGE);
+    insint_c(d, "MAXFILESIZE_LARGE", CURLOPT_MAXFILESIZE_LARGE);
+    insint_c(d, "NETRC_FILE", CURLOPT_NETRC_FILE);
+    insint_c(d, "FTP_SSL", CURLOPT_FTP_SSL);
+    insint_c(d, "POSTFIELDSIZE_LARGE", CURLOPT_POSTFIELDSIZE_LARGE);
+    insint_c(d, "TCP_NODELAY", CURLOPT_TCP_NODELAY);
+    insint_c(d, "SOURCE_USERPWD", CURLOPT_SOURCE_USERPWD);
+    insint_c(d, "SOURCE_PREQUOTE", CURLOPT_SOURCE_PREQUOTE);
+    insint_c(d, "SOURCE_POSTQUOTE", CURLOPT_SOURCE_POSTQUOTE);
+    insint_c(d, "FTPSSLAUTH", CURLOPT_FTPSSLAUTH);
+    insint_c(d, "FTP_ACCOUNT", CURLOPT_FTP_ACCOUNT);
+    insint_c(d, "SOURCE_URL", CURLOPT_SOURCE_URL);
+    insint_c(d, "SOURCE_QUOTE", CURLOPT_SOURCE_QUOTE);
+    insint_c(d, "IOCTLFUNCTION", CURLOPT_IOCTLFUNCTION);
+    insint_c(d, "IOCTLDATA", CURLOPT_IOCTLDATA);
+
+    /* constants for ioctl callback return values */
+    insint_c(d, "IOE_OK", CURLIOE_OK);
+    insint_c(d, "IOE_UNKNOWNCMD", CURLIOE_UNKNOWNCMD);
+    insint_c(d, "IOE_FAILRESTART", CURLIOE_FAILRESTART);
+
+    /* constants for setopt(IPRESOLVE, x) */
+    insint_c(d, "IPRESOLVE_WHATEVER", CURL_IPRESOLVE_WHATEVER);
+    insint_c(d, "IPRESOLVE_V4", CURL_IPRESOLVE_V4);
+    insint_c(d, "IPRESOLVE_V6", CURL_IPRESOLVE_V6);
+
+    /* constants for setopt(HTTP_VERSION, x) */
+    insint_c(d, "CURL_HTTP_VERSION_NONE", CURL_HTTP_VERSION_NONE);
+    insint_c(d, "CURL_HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0);
+    insint_c(d, "CURL_HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1);
+    insint_c(d, "CURL_HTTP_VERSION_LAST", CURL_HTTP_VERSION_LAST);
+
+    /* CURL_NETRC_OPTION: constants for setopt(NETRC, x) */
+    insint_c(d, "NETRC_OPTIONAL", CURL_NETRC_OPTIONAL);
+    insint_c(d, "NETRC_IGNORED", CURL_NETRC_IGNORED);
+    insint_c(d, "NETRC_REQUIRED", CURL_NETRC_REQUIRED);
+
+    /* constants for setopt(SSLVERSION, x) */
+    insint_c(d, "SSLVERSION_DEFAULT", CURL_SSLVERSION_DEFAULT);
+    insint_c(d, "SSLVERSION_TLSv1", CURL_SSLVERSION_TLSv1);
+    insint_c(d, "SSLVERSION_SSLv2", CURL_SSLVERSION_SSLv2);
+    insint_c(d, "SSLVERSION_SSLv3", CURL_SSLVERSION_SSLv3);
+
+    /* curl_TimeCond: constants for setopt(TIMECONDITION, x) */
+    insint_c(d, "TIMECONDITION_NONE", CURL_TIMECOND_NONE);
+    insint_c(d, "TIMECONDITION_IFMODSINCE", CURL_TIMECOND_IFMODSINCE);
+    insint_c(d, "TIMECONDITION_IFUNMODSINCE", CURL_TIMECOND_IFUNMODSINCE);
+    insint_c(d, "TIMECONDITION_LASTMOD", CURL_TIMECOND_LASTMOD);
+
+    /* CURLINFO: symbolic constants for getinfo(x) */
+    insint_c(d, "EFFECTIVE_URL", CURLINFO_EFFECTIVE_URL);
+    insint_c(d, "HTTP_CODE", CURLINFO_HTTP_CODE);
+    insint_c(d, "RESPONSE_CODE", CURLINFO_HTTP_CODE);
+    insint_c(d, "TOTAL_TIME", CURLINFO_TOTAL_TIME);
+    insint_c(d, "NAMELOOKUP_TIME", CURLINFO_NAMELOOKUP_TIME);
+    insint_c(d, "CONNECT_TIME", CURLINFO_CONNECT_TIME);
+    insint_c(d, "PRETRANSFER_TIME", CURLINFO_PRETRANSFER_TIME);
+    insint_c(d, "SIZE_UPLOAD", CURLINFO_SIZE_UPLOAD);
+    insint_c(d, "SIZE_DOWNLOAD", CURLINFO_SIZE_DOWNLOAD);
+    insint_c(d, "SPEED_DOWNLOAD", CURLINFO_SPEED_DOWNLOAD);
+    insint_c(d, "SPEED_UPLOAD", CURLINFO_SPEED_UPLOAD);
+    insint_c(d, "HEADER_SIZE", CURLINFO_HEADER_SIZE);
+    insint_c(d, "REQUEST_SIZE", CURLINFO_REQUEST_SIZE);
+    insint_c(d, "SSL_VERIFYRESULT", CURLINFO_SSL_VERIFYRESULT);
+    insint_c(d, "INFO_FILETIME", CURLINFO_FILETIME);
+    insint_c(d, "CONTENT_LENGTH_DOWNLOAD", CURLINFO_CONTENT_LENGTH_DOWNLOAD);
+    insint_c(d, "CONTENT_LENGTH_UPLOAD", CURLINFO_CONTENT_LENGTH_UPLOAD);
+    insint_c(d, "STARTTRANSFER_TIME", CURLINFO_STARTTRANSFER_TIME);
+    insint_c(d, "CONTENT_TYPE", CURLINFO_CONTENT_TYPE);
+    insint_c(d, "REDIRECT_TIME", CURLINFO_REDIRECT_TIME);
+    insint_c(d, "REDIRECT_COUNT", CURLINFO_REDIRECT_COUNT);
+    insint_c(d, "HTTP_CONNECTCODE", CURLINFO_HTTP_CONNECTCODE);
+    insint_c(d, "HTTPAUTH_AVAIL", CURLINFO_HTTPAUTH_AVAIL);
+    insint_c(d, "PROXYAUTH_AVAIL", CURLINFO_PROXYAUTH_AVAIL);
+    insint_c(d, "OS_ERRNO", CURLINFO_OS_ERRNO);
+    insint_c(d, "NUM_CONNECTS", CURLINFO_NUM_CONNECTS);
+    insint_c(d, "SSL_ENGINES", CURLINFO_SSL_ENGINES);
+
+    /* curl_closepolicy: constants for setopt(CLOSEPOLICY, x) */
+    insint_c(d, "CLOSEPOLICY_OLDEST", CURLCLOSEPOLICY_OLDEST);
+    insint_c(d, "CLOSEPOLICY_LEAST_RECENTLY_USED", CURLCLOSEPOLICY_LEAST_RECENTLY_USED);
+    insint_c(d, "CLOSEPOLICY_LEAST_TRAFFIC", CURLCLOSEPOLICY_LEAST_TRAFFIC);
+    insint_c(d, "CLOSEPOLICY_SLOWEST", CURLCLOSEPOLICY_SLOWEST);
+    insint_c(d, "CLOSEPOLICY_CALLBACK", CURLCLOSEPOLICY_CALLBACK);
+
+    /* options for global_init() */
+    insint(d, "GLOBAL_SSL", CURL_GLOBAL_SSL);
+    insint(d, "GLOBAL_WIN32", CURL_GLOBAL_WIN32);
+    insint(d, "GLOBAL_ALL", CURL_GLOBAL_ALL);
+    insint(d, "GLOBAL_NOTHING", CURL_GLOBAL_NOTHING);
+    insint(d, "GLOBAL_DEFAULT", CURL_GLOBAL_DEFAULT);
+
+    /* curl_lock_data: XXX do we need this in pycurl ??? */
+    /* curl_lock_access: XXX do we need this in pycurl ??? */
+    /* CURLSHcode: XXX do we need this in pycurl ??? */
+    /* CURLSHoption: XXX do we need this in pycurl ??? */
+
+    /* CURLversion: constants for curl_version_info(x) */
+#if 0
+    /* XXX - do we need these ?? */
+    insint(d, "VERSION_FIRST", CURLVERSION_FIRST);
+    insint(d, "VERSION_SECOND", CURLVERSION_SECOND);
+    insint(d, "VERSION_THIRD", CURLVERSION_THIRD);
+    insint(d, "VERSION_NOW", CURLVERSION_NOW);
+#endif
+
+    /* version features - bitmasks for curl_version_info_data.features */
+#if 0
+    /* XXX - do we need these ?? */
+    /* XXX - should we really rename these ?? */
+    insint(d, "VERSION_FEATURE_IPV6", CURL_VERSION_IPV6);
+    insint(d, "VERSION_FEATURE_KERBEROS4", CURL_VERSION_KERBEROS4);
+    insint(d, "VERSION_FEATURE_SSL", CURL_VERSION_SSL);
+    insint(d, "VERSION_FEATURE_LIBZ", CURL_VERSION_LIBZ);
+    insint(d, "VERSION_FEATURE_NTLM", CURL_VERSION_NTLM);
+    insint(d, "VERSION_FEATURE_GSSNEGOTIATE", CURL_VERSION_GSSNEGOTIATE);
+    insint(d, "VERSION_FEATURE_DEBUG", CURL_VERSION_DEBUG);
+    insint(d, "VERSION_FEATURE_ASYNCHDNS", CURL_VERSION_ASYNCHDNS);
+    insint(d, "VERSION_FEATURE_SPNEGO", CURL_VERSION_SPNEGO);
+    insint(d, "VERSION_FEATURE_LARGEFILE", CURL_VERSION_LARGEFILE);
+    insint(d, "VERSION_FEATURE_IDN", CURL_VERSION_IDN);
+#endif
+
+    /**
+     ** the order of these constants mostly follows <curl/multi.h>
+     **/
+
+    /* CURLMcode: multi error codes */
+    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);
+    insint_m(d, "E_MULTI_BAD_EASY_HANDLE", CURLM_BAD_EASY_HANDLE);
+    insint_m(d, "E_MULTI_OUT_OF_MEMORY", CURLM_OUT_OF_MEMORY);
+    insint_m(d, "E_MULTI_INTERNAL_ERROR", CURLM_INTERNAL_ERROR);
+
+    /* Check the version, as this has caused nasty problems in
+     * some cases. */
+    vi = curl_version_info(CURLVERSION_NOW);
+    if (vi == NULL) {
+        Py_FatalError("pycurl: FATAL: curl_version_info() failed");
+        assert(0);
+    }
+    if (vi->version_num < LIBCURL_VERSION_NUM) {
+        Py_FatalError("pycurl: FATAL: libcurl link-time version is older than compile-time version");
+        assert(0);
+    }
+
+    /* Finally initialize global interpreter lock */
+    PyEval_InitThreads();
+}
+
+/* vi:ts=4:et:nowrap
+ */
diff --git a/tests/test.py b/tests/test.py
new file mode 100644
index 0000000..f5afada
--- /dev/null
+++ b/tests/test.py
@@ -0,0 +1,74 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test.py,v 1.16 2004/12/26 17:31:53 mfx Exp $
+
+import sys, threading, time
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+class Test(threading.Thread):
+    def __init__(self, url, ofile):
+        threading.Thread.__init__(self)
+        self.curl = pycurl.Curl()
+        self.curl.setopt(pycurl.URL, url)
+        self.curl.setopt(pycurl.WRITEDATA, ofile)
+        self.curl.setopt(pycurl.FOLLOWLOCATION, 1)
+        self.curl.setopt(pycurl.MAXREDIRS, 5)
+        self.curl.setopt(pycurl.NOSIGNAL, 1)
+
+    def run(self):
+        self.curl.perform()
+        self.curl.close()
+        sys.stdout.write(".")
+        sys.stdout.flush()
+
+
+# Read list of URIs from file specified on commandline
+try:
+    urls = open(sys.argv[1]).readlines()
+except IndexError:
+    # No file was specified, show usage string
+    print "Usage: %s <file with uris to fetch>" % sys.argv[0]
+    raise SystemExit
+
+# Initialize thread array and the file number
+threads = []
+fileno = 0
+
+# Start one thread per URI in parallel
+t1 = time.time()
+for url in urls:
+    f = open(str(fileno), "wb")
+    t = Test(url, f)
+    t.start()
+    threads.append((t, f))
+    fileno = fileno + 1
+# Wait for all threads to finish
+for thread, file in threads:
+    thread.join()
+    file.close()
+t2 = time.time()
+print "\n** Multithreading, %d seconds elapsed for %d uris" % (int(t2-t1), len(urls))
+
+# Start one thread per URI in sequence
+fileno = 0
+t1 = time.time()
+for url in urls:
+    f = open(str(fileno), "wb")
+    t = Test(url, f)
+    t.start()
+    fileno = fileno + 1
+    t.join()
+    f.close()
+t2 = time.time()
+print "\n** Singlethreading, %d seconds elapsed for %d uris" % (int(t2-t1), len(urls))
diff --git a/tests/test_cb.py b/tests/test_cb.py
new file mode 100644
index 0000000..509f49e
--- /dev/null
+++ b/tests/test_cb.py
@@ -0,0 +1,28 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_cb.py,v 1.14 2003/04/21 18:46:10 mfx Exp $
+
+import sys
+import pycurl
+
+## Callback function invoked when body data is ready
+def body(buf):
+    # Print body data to stdout
+    sys.stdout.write(buf)
+
+## Callback function invoked when header data is ready
+def header(buf):
+    # Print header data to stderr
+    sys.stderr.write(buf)
+
+c = pycurl.Curl()
+c.setopt(pycurl.URL, 'http://www.python.org/')
+c.setopt(pycurl.WRITEFUNCTION, body)
+c.setopt(pycurl.HEADERFUNCTION, header)
+c.setopt(pycurl.FOLLOWLOCATION, 1)
+c.setopt(pycurl.MAXREDIRS, 5)
+c.perform()
+c.setopt(pycurl.URL, 'http://curl.haxx.se/')
+c.perform()
+c.close()
diff --git a/tests/test_debug.py b/tests/test_debug.py
new file mode 100644
index 0000000..d3a6356
--- /dev/null
+++ b/tests/test_debug.py
@@ -0,0 +1,16 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_debug.py,v 1.6 2003/04/21 18:46:10 mfx Exp $
+
+import pycurl
+
+def test(t, b):
+    print "debug(%d): %s" % (t, b)
+
+c = pycurl.Curl()
+c.setopt(pycurl.URL, 'http://curl.haxx.se/')
+c.setopt(pycurl.VERBOSE, 1)
+c.setopt(pycurl.DEBUGFUNCTION, test)
+c.perform()
+c.close()
diff --git a/tests/test_getinfo.py b/tests/test_getinfo.py
new file mode 100644
index 0000000..dd13570
--- /dev/null
+++ b/tests/test_getinfo.py
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_getinfo.py,v 1.18 2003/05/01 19:35:01 mfx Exp $
+
+import time
+import pycurl
+
+
+## Callback function invoked when progress information is updated
+def progress(download_t, download_d, upload_t, upload_d):
+    print "Total to download %d bytes, have %d bytes so far" % \
+          (download_t, download_d)
+
+url = "http://www.cnn.com"
+
+print "Starting downloading", url
+print
+f = open("body", "wb")
+h = open("header", "wb")
+c = pycurl.Curl()
+c.setopt(c.URL, url)
+c.setopt(c.WRITEDATA, f)
+c.setopt(c.NOPROGRESS, 0)
+c.setopt(c.PROGRESSFUNCTION, progress)
+c.setopt(c.FOLLOWLOCATION, 1)
+c.setopt(c.MAXREDIRS, 5)
+c.setopt(c.WRITEHEADER, h)
+c.setopt(c.OPT_FILETIME, 1)
+c.perform()
+
+print
+print "HTTP-code:", c.getinfo(c.HTTP_CODE)
+print "Total-time:", c.getinfo(c.TOTAL_TIME)
+print "Download speed: %.2f bytes/second" % c.getinfo(c.SPEED_DOWNLOAD)
+print "Document size: %d bytes" % c.getinfo(c.SIZE_DOWNLOAD)
+print "Effective URL:", c.getinfo(c.EFFECTIVE_URL)
+print "Content-type:", c.getinfo(c.CONTENT_TYPE)
+print "Namelookup-time:", c.getinfo(c.NAMELOOKUP_TIME)
+print "Redirect-time:", c.getinfo(c.REDIRECT_TIME)
+print "Redirect-count:", c.getinfo(c.REDIRECT_COUNT)
+epoch = c.getinfo(c.INFO_FILETIME)
+print "Filetime: %d (%s)" % (epoch, time.ctime(epoch))
+print
+print "Header is in file 'header', body is in file 'body'"
+
+c.close()
+f.close()
+h.close()
diff --git a/tests/test_gtk.py b/tests/test_gtk.py
new file mode 100644
index 0000000..b3c28c6
--- /dev/null
+++ b/tests/test_gtk.py
@@ -0,0 +1,93 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_gtk.py,v 1.23 2004/12/26 17:31:53 mfx Exp $
+
+import sys, threading
+from gtk import *
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+class ProgressBar:
+    def __init__(self, uri):
+        self.round = 0.0
+        win = GtkDialog()
+        win.set_title("PycURL progress")
+        win.show()
+        vbox = GtkVBox(spacing=5)
+        vbox.set_border_width(10)
+        win.vbox.pack_start(vbox)
+        win.set_default_size(200, 20)
+        vbox.show()
+        label = GtkLabel("Downloading %s" % uri)
+        label.set_alignment(0, 0.5)
+        vbox.pack_start(label, expand=FALSE)
+        label.show()
+        pbar = GtkProgressBar()
+        pbar.show()
+        self.pbar = pbar
+        vbox.pack_start(pbar)
+        win.connect("destroy", self.close_app)
+        win.connect("delete_event", self.close_app)
+
+    def progress(self, download_t, download_d, upload_t, upload_d):
+        threads_enter()
+        if download_t == 0:
+            self.round = self.round + 0.1
+            if self.round >= 1.0:  self.round = 0.0
+        else:
+            self.round = float(download_d) / float(download_t)
+        self.pbar.update(self.round)
+        threads_leave()
+
+    def mainloop(self):
+        threads_enter()
+        mainloop()
+        threads_leave()
+
+    def close_app(self, *args):
+        args[0].destroy()
+        mainquit()
+
+
+class Test(threading.Thread):
+    def __init__(self, url, target_file, progress):
+        threading.Thread.__init__(self)
+        self.target_file = target_file
+        self.progress = progress
+        self.curl = pycurl.Curl()
+        self.curl.setopt(pycurl.URL, url)
+        self.curl.setopt(pycurl.WRITEDATA, self.target_file)
+        self.curl.setopt(pycurl.FOLLOWLOCATION, 1)
+        self.curl.setopt(pycurl.NOPROGRESS, 0)
+        self.curl.setopt(pycurl.PROGRESSFUNCTION, self.progress)
+        self.curl.setopt(pycurl.MAXREDIRS, 5)
+        self.curl.setopt(pycurl.NOSIGNAL, 1)
+
+    def run(self):
+        self.curl.perform()
+        self.curl.close()
+        self.target_file.close()
+        self.progress(1.0, 1.0, 0, 0)
+
+
+# Check command line args
+if len(sys.argv) < 3:
+    print "Usage: %s <URL> <filename>" % sys.argv[0]
+    raise SystemExit
+
+# Make a progress bar window
+p = ProgressBar(sys.argv[1])
+# Start thread for fetching url
+Test(sys.argv[1], open(sys.argv[2], 'wb'), p.progress).start()
+# Enter the GTK mainloop
+p.mainloop()
diff --git a/tests/test_internals.py b/tests/test_internals.py
new file mode 100644
index 0000000..afcc53d
--- /dev/null
+++ b/tests/test_internals.py
@@ -0,0 +1,253 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_internals.py,v 1.17 2003/05/01 16:48:54 mfx Exp $
+
+#
+# a simple self-test
+#
+
+try:
+    # need Python 2.2 or better for garbage collection
+    from gc import get_objects
+    import gc
+    del get_objects
+    gc.enable()
+except ImportError:
+    gc = None
+import copy, os, sys
+from StringIO import StringIO
+try:
+    import cPickle
+except ImportError:
+    cPickle = None
+try:
+    import pickle
+except ImportError:
+    pickle = None
+
+# update sys.path when running in the build directory
+from util import get_sys_path
+sys.path = get_sys_path()
+
+import pycurl
+from pycurl import Curl, CurlMulti
+
+
+class opts:
+    verbose = 1
+
+if "-q" in sys.argv:
+    opts.verbose = opts.verbose - 1
+
+
+print "Python", sys.version
+print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
+print "PycURL version info", pycurl.version_info()
+print "  %s, compiled %s" % (pycurl.__file__, pycurl.COMPILE_DATE)
+
+
+# /***********************************************************************
+# // test misc
+# ************************************************************************/
+
+if 1:
+    c = Curl()
+    assert c.URL is pycurl.URL
+    del c
+
+
+# /***********************************************************************
+# // test handles
+# ************************************************************************/
+
+# remove an invalid handle: this should fail
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    try:
+        m.remove_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    del m, c
+
+
+# remove an invalid but closed handle
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    c.close()
+    m.remove_handle(c)
+    del m, c
+
+
+# add a closed handle: this should fail
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    c.close()
+    try:
+        m.add_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    m.close()
+    del m, c
+
+
+# add a handle twice: this should fail
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    m.add_handle(c)
+    try:
+        m.add_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    del m, c
+
+
+# add a handle on multiple stacks: this should fail
+if 1:
+    m1 = CurlMulti()
+    m2 = CurlMulti()
+    c = Curl()
+    m1.add_handle(c)
+    try:
+        m2.add_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    del m1, m2, c
+
+
+# move a handle
+if 1:
+    m1 = CurlMulti()
+    m2 = CurlMulti()
+    c = Curl()
+    m1.add_handle(c)
+    m1.remove_handle(c)
+    m2.add_handle(c)
+    del m1, m2, c
+
+
+# /***********************************************************************
+# // test copying and pickling - copying and pickling of
+# // instances of Curl and CurlMulti is not allowed
+# ************************************************************************/
+
+if 1 and copy:
+    c = Curl()
+    m = CurlMulti()
+    try:
+        copy.copy(c)
+    except copy.Error:
+        pass
+    else:
+        assert 0, "internal error - copying should fail"
+    try:
+        copy.copy(m)
+    except copy.Error:
+        pass
+    else:
+        assert 0, "internal error - copying should fail"
+
+if 1 and pickle:
+    c = Curl()
+    m = CurlMulti()
+    fp = StringIO()
+    p = pickle.Pickler(fp, 1)
+    try:
+        p.dump(c)
+    except pickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    try:
+        p.dump(m)
+    except pickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    del c, m, fp, p
+
+if 1 and cPickle:
+    c = Curl()
+    m = CurlMulti()
+    fp = StringIO()
+    p = cPickle.Pickler(fp, 1)
+    try:
+        p.dump(c)
+    except cPickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    try:
+        p.dump(m)
+    except cPickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    del c, m, fp, p
+
+
+# /***********************************************************************
+# // test refcounts
+# ************************************************************************/
+
+# basic check of reference counting (use a memory checker like valgrind)
+if 1:
+    c = Curl()
+    m = CurlMulti()
+    m.add_handle(c)
+    del m
+    m = CurlMulti()
+    c.close()
+    del m, c
+
+# basic check of cyclic garbage collection
+if 1 and gc:
+    gc.collect()
+    c = Curl()
+    c.m = 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 | 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())
+    # The `del' below should delete these 4 objects:
+    #   Curl + internal dict, CurlMulti + internal dict
+    del c
+    gc.collect()
+    if opts.verbose >= 1:
+        print "Tracked objects:", len(gc.get_objects())
+
+
+# /***********************************************************************
+# // done
+# ************************************************************************/
+
+print "All tests passed."
diff --git a/tests/test_memleak.py b/tests/test_memleak.py
new file mode 100644
index 0000000..284df62
--- /dev/null
+++ b/tests/test_memleak.py
@@ -0,0 +1,53 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_memleak.py,v 1.4 2003/05/01 16:48:54 mfx Exp $
+
+#
+# just a simple self-test
+# need Python 2.2 or better for garbage collection
+#
+
+import gc, pycurl, sys
+gc.enable()
+
+
+print "Python", sys.version
+print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
+##print "PycURL version info", pycurl.version_info()
+print "  %s, compiled %s" % (pycurl.__file__, pycurl.COMPILE_DATE)
+
+
+gc.collect()
+flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | 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 = []
+for a in range(100):
+    curl = pycurl.Curl()
+    multi.add_handle(curl)
+    t.append(curl)
+
+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())
+
+
diff --git a/tests/test_multi.py b/tests/test_multi.py
new file mode 100644
index 0000000..5be86f0
--- /dev/null
+++ b/tests/test_multi.py
@@ -0,0 +1,33 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi.py,v 1.9 2003/04/21 18:46:10 mfx Exp $
+
+import pycurl
+
+m = pycurl.CurlMulti()
+m.handles = []
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c1.setopt(c1.URL, 'http://curl.haxx.se')
+c2.setopt(c2.URL, 'http://cnn.com')
+c2.setopt(c2.FOLLOWLOCATION, 1)
+m.add_handle(c1)
+m.add_handle(c2)
+m.handles.append(c1)
+m.handles.append(c2)
+
+num_handles = len(m.handles)
+while num_handles:
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+    m.select()
+
+m.remove_handle(c2)
+m.remove_handle(c1)
+del m.handles
+m.close()
+c1.close()
+c2.close()
diff --git a/tests/test_multi2.py b/tests/test_multi2.py
new file mode 100644
index 0000000..3d8ab1b
--- /dev/null
+++ b/tests/test_multi2.py
@@ -0,0 +1,72 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi2.py,v 1.13 2003/04/21 18:46:10 mfx Exp $
+
+import os, sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+
+urls = (
+    "http://curl.haxx.se",
+    "http://www.python.org",
+    "http://pycurl.sourceforge.net",
+    "http://pycurl.sourceforge.net/tests/403_FORBIDDEN",  # that actually exists ;-)
+    "http://pycurl.sourceforge.net/tests/404_NOT_FOUND",
+)
+
+# Read list of URIs from file specified on commandline
+try:
+    urls = open(sys.argv[1], "rb").readlines()
+except IndexError:
+    # No file was specified
+    pass
+
+# init
+m = pycurl.CurlMulti()
+m.handles = []
+for url in urls:
+    c = pycurl.Curl()
+    # save info in standard Python attributes
+    c.url = url
+    c.body = StringIO()
+    c.http_code = -1
+    m.handles.append(c)
+    # pycurl API calls
+    c.setopt(c.URL, c.url)
+    c.setopt(c.WRITEFUNCTION, c.body.write)
+    m.add_handle(c)
+
+# get data
+num_handles = len(m.handles)
+while num_handles:
+     while 1:
+         ret, num_handles = m.perform()
+         if ret != pycurl.E_CALL_MULTI_PERFORM:
+             break
+     # currently no more I/O is pending, could do something in the meantime
+     # (display a progress bar, etc.)
+     m.select()
+
+# close handles
+for c in m.handles:
+    # save info in standard Python attributes
+    c.http_code = c.getinfo(c.HTTP_CODE)
+    # pycurl API calls
+    m.remove_handle(c)
+    c.close()
+m.close()
+
+# print result
+for c in m.handles:
+    data = c.body.getvalue()
+    if 0:
+        print "**********", c.url, "**********"
+        print data
+    else:
+        print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data))
+
diff --git a/tests/test_multi3.py b/tests/test_multi3.py
new file mode 100644
index 0000000..9b52159
--- /dev/null
+++ b/tests/test_multi3.py
@@ -0,0 +1,87 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi3.py,v 1.12 2003/04/21 18:46:10 mfx Exp $
+
+# same as test_multi2.py, but enforce some debugging and strange API-calls
+
+import os, sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+
+urls = (
+    "http://curl.haxx.se",
+    "http://www.python.org",
+    "http://pycurl.sourceforge.net",
+    "http://pycurl.sourceforge.net/THIS_HANDLE_IS_CLOSED",
+)
+
+# init
+m = pycurl.CurlMulti()
+m.handles = []
+for url in urls:
+    c = pycurl.Curl()
+    # save info in standard Python attributes
+    c.url = url
+    c.body = StringIO()
+    c.http_code = -1
+    c.debug = 0
+    m.handles.append(c)
+    # pycurl API calls
+    c.setopt(c.URL, c.url)
+    c.setopt(c.WRITEFUNCTION, c.body.write)
+    m.add_handle(c)
+
+# debug - close a handle
+if 1:
+    c = m.handles[3]
+    c.debug = 1
+    c.close()
+
+# get data
+num_handles = len(m.handles)
+while num_handles:
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+    # currently no more I/O is pending, could do something in the meantime
+    # (display a progress bar, etc.)
+    m.select()
+
+# close handles
+for c in m.handles:
+    # save info in standard Python attributes
+    try:
+        c.http_code = c.getinfo(c.HTTP_CODE)
+    except pycurl.error:
+        # handle already closed - see debug above
+        assert c.debug
+        c.http_code = -1
+    # pycurl API calls
+    if 0:
+        m.remove_handle(c)
+        c.close()
+    elif 0:
+        # in the C API this is the wrong calling order, but pycurl
+        # handles this automatically
+        c.close()
+        m.remove_handle(c)
+    else:
+        # actually, remove_handle is called automatically on close
+        c.close()
+m.close()
+
+# print result
+for c in m.handles:
+    data = c.body.getvalue()
+    if 0:
+        print "**********", c.url, "**********"
+        print data
+    else:
+        print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data))
+
diff --git a/tests/test_multi4.py b/tests/test_multi4.py
new file mode 100644
index 0000000..6b143aa
--- /dev/null
+++ b/tests/test_multi4.py
@@ -0,0 +1,57 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi4.py,v 1.13 2003/04/21 18:46:10 mfx Exp $
+
+import sys, select, time
+import pycurl
+
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c3 = pycurl.Curl()
+c1.setopt(c1.URL, "http://www.python.org")
+c2.setopt(c2.URL, "http://curl.haxx.se")
+c3.setopt(c3.URL, "http://slashdot.org")
+c1.body = open("doc1", "wb")
+c2.body = open("doc2", "wb")
+c3.body = open("doc3", "wb")
+c1.setopt(c1.WRITEFUNCTION, c1.body.write)
+c2.setopt(c2.WRITEFUNCTION, c2.body.write)
+c3.setopt(c3.WRITEFUNCTION, c3.body.write)
+
+m = pycurl.CurlMulti()
+m.add_handle(c1)
+m.add_handle(c2)
+m.add_handle(c3)
+
+# Number of seconds to wait for a timeout to happen
+SELECT_TIMEOUT = 10
+
+# Stir the state machine into action
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM:
+        break
+
+# Keep going until all the connections have terminated
+while num_handles:
+    apply(select.select, m.fdset() + (SELECT_TIMEOUT,))
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+# Cleanup
+m.remove_handle(c3)
+m.remove_handle(c2)
+m.remove_handle(c1)
+m.close()
+c1.body.close()
+c2.body.close()
+c3.body.close()
+c1.close()
+c2.close()
+c3.close()
+print "http://www.python.org is in file doc1"
+print "http://curl.haxx.se is in file doc2"
+print "http://slashdot.org is in file doc3"
diff --git a/tests/test_multi5.py b/tests/test_multi5.py
new file mode 100644
index 0000000..10f799e
--- /dev/null
+++ b/tests/test_multi5.py
@@ -0,0 +1,60 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi5.py,v 1.11 2003/04/21 18:46:10 mfx Exp $
+
+import sys, select, time
+import pycurl
+
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c3 = pycurl.Curl()
+c1.setopt(c1.URL, "http://www.python.org")
+c2.setopt(c2.URL, "http://curl.haxx.se")
+c3.setopt(c3.URL, "http://slashdot.org")
+c1.body = open("doc1", "wb")
+c2.body = open("doc2", "wb")
+c3.body = open("doc3", "wb")
+c1.setopt(c1.WRITEFUNCTION, c1.body.write)
+c2.setopt(c2.WRITEFUNCTION, c2.body.write)
+c3.setopt(c3.WRITEFUNCTION, c3.body.write)
+
+m = pycurl.CurlMulti()
+m.add_handle(c1)
+m.add_handle(c2)
+m.add_handle(c3)
+
+# Number of seconds to wait for a timeout to happen
+SELECT_TIMEOUT = 10
+
+# Stir the state machine into action
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM:
+        break
+
+# Keep going until all the connections have terminated
+while num_handles:
+    # The select method uses fdset internally to determine which file descriptors
+    # to check.
+    m.select(SELECT_TIMEOUT)
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+# Cleanup
+m.remove_handle(c3)
+m.remove_handle(c2)
+m.remove_handle(c1)
+m.close()
+c1.body.close()
+c2.body.close()
+c3.body.close()
+c1.close()
+c2.close()
+c3.close()
+print "http://www.python.org is in file doc1"
+print "http://curl.haxx.se is in file doc2"
+print "http://slashdot.org is in file doc3"
+
diff --git a/tests/test_multi6.py b/tests/test_multi6.py
new file mode 100644
index 0000000..77585e5
--- /dev/null
+++ b/tests/test_multi6.py
@@ -0,0 +1,62 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi6.py,v 1.5 2003/04/21 18:46:10 mfx Exp $
+
+import sys, select, time
+import pycurl
+
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c3 = pycurl.Curl()
+c1.setopt(c1.URL, "http://www.python.org")
+c2.setopt(c2.URL, "http://curl.haxx.se")
+c3.setopt(c3.URL, "http://slashdot.org")
+c1.body = open("doc1", "wb")
+c2.body = open("doc2", "wb")
+c3.body = open("doc3", "wb")
+c1.setopt(c1.WRITEFUNCTION, c1.body.write)
+c2.setopt(c2.WRITEFUNCTION, c2.body.write)
+c3.setopt(c3.WRITEFUNCTION, c3.body.write)
+
+m = pycurl.CurlMulti()
+m.add_handle(c1)
+m.add_handle(c2)
+m.add_handle(c3)
+
+# Number of seconds to wait for a timeout to happen
+SELECT_TIMEOUT = 10
+
+# Stir the state machine into action
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM:
+        break
+
+# Keep going until all the connections have terminated
+while num_handles:
+    # The select method uses fdset internally to determine which file descriptors
+    # to check.
+    m.select(SELECT_TIMEOUT)
+    while 1:
+        ret, num_handles = m.perform()
+        # Print the message, if any
+        print m.info_read(1)
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+# Cleanup
+m.remove_handle(c3)
+m.remove_handle(c2)
+m.remove_handle(c1)
+m.close()
+c1.body.close()
+c2.body.close()
+c3.body.close()
+c1.close()
+c2.close()
+c3.close()
+print "http://www.python.org is in file doc1"
+print "http://curl.haxx.se is in file doc2"
+print "http://slashdot.org is in file doc3"
+
diff --git a/tests/test_multi_vs_thread.py b/tests/test_multi_vs_thread.py
new file mode 100644
index 0000000..a6030cc
--- /dev/null
+++ b/tests/test_multi_vs_thread.py
@@ -0,0 +1,262 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi_vs_thread.py,v 1.15 2004/12/26 17:31:53 mfx Exp $
+
+import os, sys, time
+from threading import Thread, RLock
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+# The conclusion is: the multi interface is fastest!
+
+NUM_PAGES = 30
+NUM_THREADS = 10
+assert NUM_PAGES % NUM_THREADS == 0
+
+##URL = "http://pycurl.sourceforge.net/tests/testgetvars.php?%d"
+URL = "http://pycurl.sourceforge.net/tests/teststaticpage.html?%d"
+
+
+#
+# util
+#
+
+class Curl:
+    def __init__(self, url):
+        self.url = url
+        self.body = StringIO()
+        self.http_code = -1
+        # pycurl API calls
+        self._curl = pycurl.Curl()
+        self._curl.setopt(pycurl.URL, self.url)
+        self._curl.setopt(pycurl.WRITEFUNCTION, self.body.write)
+        self._curl.setopt(pycurl.NOSIGNAL, 1)
+
+    def perform(self):
+        self._curl.perform()
+
+    def close(self):
+        self.http_code = self._curl.getinfo(pycurl.HTTP_CODE)
+        self._curl.close()
+
+
+def print_result(items):
+    return  # DO NOTHING
+    #
+    for c in items:
+        data = c.body.getvalue()
+        if 0:
+            print "**********", c.url, "**********"
+            print data
+        elif 1:
+            print "%-60s   %3d   %6d" % (c.url, c.http_code, len(data))
+
+
+###
+### 1) multi
+###
+
+def test_multi():
+    clock1 = time.time()
+
+    # init
+    handles = []
+    m = pycurl.CurlMulti()
+    for i in range(NUM_PAGES):
+        c = Curl(URL %i)
+        m.add_handle(c._curl)
+        handles.append(c)
+
+    clock2 = time.time()
+
+    # stir state machine into action
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+    # get data
+    while num_handles:
+        m.select()
+        while 1:
+            ret, num_handles = m.perform()
+            if ret != pycurl.E_CALL_MULTI_PERFORM:
+                break
+
+    clock3 = time.time()
+
+    # close handles
+    for c in handles:
+        c.close()
+    m.close()
+
+    clock4 = time.time()
+    print "multi  interface:        %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+
+    # print result
+    print_result(handles)
+
+
+
+###
+### 2) thread
+###
+
+class Test(Thread):
+    def __init__(self, lock=None):
+        Thread.__init__(self)
+        self.lock = lock
+        self.items = []
+
+    def run(self):
+        if self.lock:
+            self.lock.acquire()
+            self.lock.release()
+        for c in self.items:
+            c.perform()
+
+
+def test_threads(lock=None):
+    clock1 = time.time()
+
+    # create and start threads, but block them
+    if lock:
+        lock.acquire()
+
+    # init (FIXME - this is ugly)
+    threads = []
+    handles = []
+    t = None
+    for i in range(NUM_PAGES):
+        if i % (NUM_PAGES / NUM_THREADS) == 0:
+            t = Test(lock)
+            if lock:
+                t.start()
+            threads.append(t)
+        c = Curl(URL % i)
+        t.items.append(c)
+        handles.append(c)
+    assert len(handles) == NUM_PAGES
+    assert len(threads) == NUM_THREADS
+
+    clock2 = time.time()
+
+    #
+    if lock:
+        # release lock to let the blocked threads run
+        lock.release()
+    else:
+        # start threads
+        for t in threads:
+            t.start()
+    # wait for threads to finish
+    for t in threads:
+        t.join()
+
+    clock3 = time.time()
+
+    # close handles
+    for c in handles:
+        c.close()
+
+    clock4 = time.time()
+    if lock:
+        print "thread interface [lock]: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+    else:
+        print "thread interface:        %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+
+    # print result
+    print_result(handles)
+
+
+
+###
+### 3) thread - threads grab curl objects on demand from a shared pool
+###
+
+class TestPool(Thread):
+    def __init__(self, lock, pool):
+        Thread.__init__(self)
+        self.lock = lock
+        self.pool = pool
+
+    def run(self):
+        while 1:
+            self.lock.acquire()
+            c = None
+            if self.pool:
+                c = self.pool.pop()
+            self.lock.release()
+            if c is None:
+                break
+            c.perform()
+
+
+def test_thread_pool(lock):
+    clock1 = time.time()
+
+    # init
+    handles = []
+    for i in range(NUM_PAGES):
+        c = Curl(URL %i)
+        handles.append(c)
+
+    # create and start threads, but block them
+    lock.acquire()
+    threads = []
+    pool = handles[:]   # shallow copy of the list, shared for pop()
+    for i in range(NUM_THREADS):
+        t = TestPool(lock, pool)
+        t.start()
+        threads.append(t)
+    assert len(pool) == NUM_PAGES
+    assert len(threads) == NUM_THREADS
+
+    clock2 = time.time()
+
+    # release lock to let the blocked threads run
+    lock.release()
+
+    # wait for threads to finish
+    for t in threads:
+        t.join()
+
+    clock3 = time.time()
+
+    # close handles
+    for c in handles:
+        c.close()
+
+    clock4 = time.time()
+    print "thread interface [pool]: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+
+    # print result
+    print_result(handles)
+
+
+
+lock = RLock()
+if 1:
+    test_multi()
+    test_threads()
+    test_threads(lock)
+    test_thread_pool(lock)
+else:
+    test_thread_pool(lock)
+    test_threads(lock)
+    test_threads()
+    test_multi()
+
diff --git a/tests/test_post.py b/tests/test_post.py
new file mode 100644
index 0000000..574bbda
--- /dev/null
+++ b/tests/test_post.py
@@ -0,0 +1,24 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_post.py,v 1.9 2003/04/21 18:46:11 mfx Exp $
+
+import urllib
+import pycurl
+
+# simple
+pf = {'field1': 'value1'}
+
+# multiple fields
+pf = {'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'}
+
+# multiple fields with & in field
+pf = {'field1':'value1', 'field2':'value2 with blanks and & chars',
+      'field3':'value3'}
+
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://pycurl.sourceforge.net/tests/testpostvars.php')
+c.setopt(c.POSTFIELDS, urllib.urlencode(pf))
+c.setopt(c.VERBOSE, 1)
+c.perform()
+c.close()
diff --git a/tests/test_post2.py b/tests/test_post2.py
new file mode 100644
index 0000000..b4fd9b0
--- /dev/null
+++ b/tests/test_post2.py
@@ -0,0 +1,16 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_post2.py,v 1.12 2004/04/30 15:12:28 kjetilja Exp $
+
+import pycurl
+
+
+pf = [('field1', 'this is a test using httppost & stuff'), ('field2', 'value2')]
+
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://www.contactor.se/~dast/postit.cgi')
+c.setopt(c.HTTPPOST, pf)
+c.setopt(c.VERBOSE, 1)
+c.perform()
+c.close()
diff --git a/tests/test_post3.py b/tests/test_post3.py
new file mode 100644
index 0000000..3ebdd55
--- /dev/null
+++ b/tests/test_post3.py
@@ -0,0 +1,32 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_post3.py,v 1.1 2004/06/21 11:24:18 kjetilja Exp $
+
+import urllib
+POSTSTRING = urllib.urlencode({'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'})
+
+class test:
+
+    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 ""
+
+import pycurl
+c = pycurl.Curl()
+t = test()
+c.setopt(c.URL, 'http://pycurl.sourceforge.net/tests/testpostvars.php')
+c.setopt(c.POST, 1)
+c.setopt(c.POSTFIELDSIZE, len(POSTSTRING))
+c.setopt(c.READFUNCTION, t.read_cb)
+c.setopt(c.VERBOSE, 1)
+c.perform()
+c.close()
diff --git a/tests/test_stringio.py b/tests/test_stringio.py
new file mode 100644
index 0000000..7fdf153
--- /dev/null
+++ b/tests/test_stringio.py
@@ -0,0 +1,25 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_stringio.py,v 1.6 2003/04/21 18:46:11 mfx Exp $
+
+import sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+url = "http://curl.haxx.se/dev/"
+
+print "Testing", pycurl.version
+
+body = StringIO()
+c = pycurl.Curl()
+c.setopt(c.URL, url)
+c.setopt(c.WRITEFUNCTION, body.write)
+c.perform()
+c.close()
+
+contents = body.getvalue()
+print contents
diff --git a/tests/test_xmlrpc.py b/tests/test_xmlrpc.py
new file mode 100644
index 0000000..c2ff86d
--- /dev/null
+++ b/tests/test_xmlrpc.py
@@ -0,0 +1,29 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_xmlrpc.py,v 1.7 2003/04/21 18:46:11 mfx Exp $
+
+## XML-RPC lib included in python2.2
+import xmlrpclib
+import pycurl
+
+# Header fields passed in request
+xmlrpc_header = [
+    "User-Agent: PycURL XML-RPC Test", "Content-Type: text/xml"
+    ]
+
+# XML-RPC request template
+xmlrpc_template = """
+<?xml version='1.0'?><methodCall><methodName>%s</methodName>%s</methodCall>
+"""
+
+# Engage
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://betty.userland.com/RPC2')
+c.setopt(c.POST, 1)
+c.setopt(c.HTTPHEADER, xmlrpc_header)
+c.setopt(c.POSTFIELDS, xmlrpc_template % ("examples.getStateName", xmlrpclib.dumps((5,))))
+
+print 'Response from http://betty.userland.com/'
+c.perform()
+c.close()
diff --git a/tests/util.py b/tests/util.py
new file mode 100644
index 0000000..945ad0d
--- /dev/null
+++ b/tests/util.py
@@ -0,0 +1,38 @@
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: util.py,v 1.4 2003/04/21 18:46:11 mfx Exp $
+
+import os, sys
+
+#
+# prepare sys.path in case we are still in the build directory
+# see also: distutils/command/build.py (build_platlib)
+#
+
+def get_sys_path(p=None):
+    if p is None: p = sys.path
+    p = p[:]
+    try:
+        from distutils.util import get_platform
+    except ImportError:
+        return p
+    p0 = ""
+    if p: p0 = p[0]
+    #
+    plat = get_platform()
+    plat_specifier = "%s-%s" % (plat, sys.version[:3])
+    ##print plat, plat_specifier
+    #
+    for prefix in (p0, os.curdir, os.pardir,):
+        if not prefix:
+            continue
+        d = os.path.join(prefix, "build")
+        for subdir in ("lib", "lib." + plat_specifier, "lib." + plat):
+            dir = os.path.normpath(os.path.join(d, subdir))
+            if os.path.isdir(dir):
+                if dir not in p:
+                    p.insert(1, dir)
+    #
+    return p
+
+

-- 
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