From 31f76aa36b03a1baa5711062ba76f069699e2668 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Fri, 15 Dec 2006 16:18:06 +0000 Subject: [PATCH 1/1] pycurl-7.13.1 (FC4 compatible) from SourceForge --- pycurl/COPYING | 504 +++++ pycurl/ChangeLog | 759 +++++++ pycurl/INSTALL | 44 + pycurl/MANIFEST.in | 22 + pycurl/Makefile | 60 + pycurl/PKG-INFO | 11 + pycurl/README | 12 + pycurl/TODO | 27 + pycurl/doc/callbacks.html | 140 ++ pycurl/doc/curlmultiobject.html | 136 ++ pycurl/doc/curlobject.html | 102 + pycurl/doc/pycurl.html | 120 ++ pycurl/examples/basicfirst.py | 25 + pycurl/examples/file_upload.py | 46 + pycurl/examples/linksys.py | 563 +++++ pycurl/examples/retriever-multi.py | 122 ++ pycurl/examples/retriever.py | 99 + pycurl/examples/sfquery.py | 64 + pycurl/examples/xmlrpc_curl.py | 61 + pycurl/python/curl/__init__.py | 146 ++ pycurl/setup.py | 199 ++ pycurl/setup_win32_ssl.py | 34 + pycurl/src/Makefile | 19 + pycurl/src/pycurl.c | 2828 ++++++++++++++++++++++++++ pycurl/tests/test.py | 74 + pycurl/tests/test_cb.py | 28 + pycurl/tests/test_debug.py | 16 + pycurl/tests/test_getinfo.py | 49 + pycurl/tests/test_gtk.py | 93 + pycurl/tests/test_internals.py | 253 +++ pycurl/tests/test_memleak.py | 53 + pycurl/tests/test_multi.py | 33 + pycurl/tests/test_multi2.py | 72 + pycurl/tests/test_multi3.py | 87 + pycurl/tests/test_multi4.py | 57 + pycurl/tests/test_multi5.py | 60 + pycurl/tests/test_multi6.py | 62 + pycurl/tests/test_multi_vs_thread.py | 262 +++ pycurl/tests/test_post.py | 24 + pycurl/tests/test_post2.py | 18 + pycurl/tests/test_post3.py | 32 + pycurl/tests/test_stringio.py | 25 + pycurl/tests/test_xmlrpc.py | 29 + pycurl/tests/util.py | 38 + 44 files changed, 7508 insertions(+) create mode 100644 pycurl/COPYING create mode 100644 pycurl/ChangeLog create mode 100644 pycurl/INSTALL create mode 100644 pycurl/MANIFEST.in create mode 100644 pycurl/Makefile create mode 100644 pycurl/PKG-INFO create mode 100644 pycurl/README create mode 100644 pycurl/TODO create mode 100644 pycurl/doc/callbacks.html create mode 100644 pycurl/doc/curlmultiobject.html create mode 100644 pycurl/doc/curlobject.html create mode 100644 pycurl/doc/pycurl.html create mode 100644 pycurl/examples/basicfirst.py create mode 100644 pycurl/examples/file_upload.py create mode 100755 pycurl/examples/linksys.py create mode 100644 pycurl/examples/retriever-multi.py create mode 100644 pycurl/examples/retriever.py create mode 100644 pycurl/examples/sfquery.py create mode 100644 pycurl/examples/xmlrpc_curl.py create mode 100644 pycurl/python/curl/__init__.py create mode 100644 pycurl/setup.py create mode 100644 pycurl/setup_win32_ssl.py create mode 100644 pycurl/src/Makefile create mode 100644 pycurl/src/pycurl.c create mode 100644 pycurl/tests/test.py create mode 100644 pycurl/tests/test_cb.py create mode 100644 pycurl/tests/test_debug.py create mode 100644 pycurl/tests/test_getinfo.py create mode 100644 pycurl/tests/test_gtk.py create mode 100644 pycurl/tests/test_internals.py create mode 100644 pycurl/tests/test_memleak.py create mode 100644 pycurl/tests/test_multi.py create mode 100644 pycurl/tests/test_multi2.py create mode 100644 pycurl/tests/test_multi3.py create mode 100644 pycurl/tests/test_multi4.py create mode 100644 pycurl/tests/test_multi5.py create mode 100644 pycurl/tests/test_multi6.py create mode 100644 pycurl/tests/test_multi_vs_thread.py create mode 100644 pycurl/tests/test_post.py create mode 100644 pycurl/tests/test_post2.py create mode 100644 pycurl/tests/test_post3.py create mode 100644 pycurl/tests/test_stringio.py create mode 100644 pycurl/tests/test_xmlrpc.py create mode 100644 pycurl/tests/util.py diff --git a/pycurl/COPYING b/pycurl/COPYING new file mode 100644 index 0000000..99dce33 --- /dev/null +++ b/pycurl/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. + + + Copyright (C) + + 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/pycurl/ChangeLog b/pycurl/ChangeLog new file mode 100644 index 0000000..0e5ae7e --- /dev/null +++ b/pycurl/ChangeLog @@ -0,0 +1,759 @@ +Version 7.13.1 [requires libcurl-7.13.1 or better] +-------------- + +2005-03-04 Kjetil Jacobsen + + * Use METH_NOARGS where appropriate. + +2005-03-03 Kjetil Jacobsen + + * Added support for CURLFORM API with HTTPPOST: Supports a + a tuple with pairs of options and values instead of just + supporting string contents. See tests/test_post2.py + for example usage. Options are FORM_CONTENTS, FORM_FILE and + FORM_CONTENTTYPE, corresponding to the CURLFORM_* options, + and values are strings. + +2005-02-13 Markus F.X.J. Oberhumer + + * Read callbacks (pycurl.READFUNCTION) can now return + pycurl.READFUNC_ABORT to immediately abort the current transfer. + + * The INFILESIZE, MAXFILESIZE, POSTFIELDSIZE and RESUME_FROM + options now automatically use the largefile version to handle + files > 2GB. + + * Added missing pycurl.PORT constant. + + +Version 7.13.0 +-------------- + +2005-02-10 Kjetil Jacobsen + + * 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 + + * 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 + + * Added CURLOPT_FTPSSLAUTH (and CURLFTPAUTH_*). + + * Added CURLINFO_OS_ERRNO. + +2004-08-17 Kjetil Jacobsen + + * Use LONG_LONG instead of PY_LONG_LONG to make pycurl compile + on Python versions < 2.3 (fix from Domenico Andreoli + ). + + +Version 7.12.1 +-------------- + +2004-08-02 Kjetil Jacobsen + + * Added INFOTYPE_SSL_DATA_IN/OUT. + +2004-07-16 Markus F.X.J. Oberhumer + + * WARNING: removed deprecated PROXY_, TIMECOND_ and non-prefixed + INFOTYPE constant names. See ChangeLog entry 2003-06-10. + +2004-06-21 Kjetil Jacobsen + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * Added support for libcurl 7.11.2 CURLOPT features: + CURLOPT_TCP_NODELAY. + +2004-03-25 Kjetil Jacobsen + + * 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 + + * WARNING: Removed support for the PASSWDFUNCTION callback, which + is no longer supported by libcurl. + +2004-03-15 Kjetil Jacobsen + + * Added support for libcurl 7.11.1 CURLOPT features: + CURLOPT_POSTFIELDSIZE_LARGE. + + +Version 7.11.0 +-------------- + +2004-02-11 Kjetil Jacobsen + + * 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 + + * 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 + + * 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 + + * Added missing CURLOPT_ENCODING option (patch by Martijn + Boerwinkel ) + + +Version 7.10.6 +-------------- + +2003-07-29 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Added support for CURLOPT_FTP_USE_EPRT (libcurl-7.10.5). + + * Documentation updates. + +2003-05-07 Eric S. Raymond + + * Lifted all HTML docs to clean XHTML, verified by tidy. + +2003-05-02 Markus F.X.J. Oberhumer + + * Fixed some `int' vs. `long' mismatches that affected 64-bit systems. + + * Fixed wrong pycurl.CAPATH constant. + +2003-05-01 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Markus added CURLOPT_UNRESTRICTED_AUTH (libcurl-7.10.4). + +2003-02-25 Kjetil Jacobsen + + * Fixed some broken test code and removed the fileupload test + since it didn't work properly. + +2003-01-28 Kjetil Jacobsen + + * Some documentation updates by Markus and me. + +2003-01-22 Kjetil Jacobsen + + * 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 + + * PycURL memory usage has been reduced. + +2003-01-10 Kjetil Jacobsen + + * Added 'examples/retriever-multi.py' which shows how to retrieve + a set of URLs concurrently using the multi interface. + +2003-01-09 Kjetil Jacobsen + + * Added support for CURLOPT_HTTP200ALIASES. + +2002-11-22 Kjetil Jacobsen + + * Updated pycurl documentation in the 'doc' directory. + +2002-11-21 Kjetil Jacobsen + + * 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 + + * Added new method CurlMulti.info_read(). + + +Version 7.10.2 +-------------- + +2002-11-14 Kjetil Jacobsen + + * 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 + . + +2002-11-06 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Added new module-level function version_info() from + libcurl-7.10. + + +Version 7.10 +------------ + +2002-09-13 Kjetil Jacobsen + + * 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 + + * 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 + + * 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 + + * Added support for curl_multi_fdset. See the file + 'tests/test_multi4.py' for example usage. Contributed by Conrad + Steenberg . + + * perform() on multi objects now returns a tuple (result, number + of handles) like the libcurl interface does. + +2002-08-08 Kjetil Jacobsen + + * 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 . + +2002-07-20 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Curl and CurlMulti objects now support standard Python attributes. + See tests/test_multi2.py for an example. + +2002-07-02 Kjetil Jacobsen + + * Added support for the multi-interface. + + +Version 7.9.8.1 +--------------- + +2002-06-25 Markus F.X.J. Oberhumer + + * Fixed a couple of `int' vs. `size_t' mismatches in callbacks + and Py_BuildValue() calls. + +2002-06-25 Kjetil Jacobsen + + * Use 'double' type instead of 'size_t' for progress callbacks + (by Conrad Steenberg ). Also cleaned up + some other type mismatches in the callback interfaces. + +2002-06-24 Kjetil Jacobsen + + * Added example code on how to upload a file using HTTPPOST in + pycurl (code by Amit Mongia ). See the + file 'test_fileupload.py' for details. + + +Version 7.9.8 +------------- + +2002-06-24 Kjetil Jacobsen + + * Resolved some build problems on Windows (by Markus Oberhumer). + +2002-06-19 Kjetil Jacobsen + + * 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 + + * Made some changes to setup.py which should fix the build + problems on RedHat 7.3 (suggested by Benji ). + + * 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 + + * 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 + + * Added CURLINFO_REDIRECT_TIME and CURLINFO_REDIRECT_COUNT. + +2002-04-27 Kjetil Jacobsen + + * Fixed potential memory leak and thread race (by Markus + Oberhumer). + + +Version 0.4.9 +------------- + +2002-04-15 Kjetil Jacobsen + + * 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 + + * Added 'test_post2.py' file which is another example on how to + issue POST requests. + +2002-04-11 Markus F.X.J. Oberhumer + + * Added the 'test_post.py' file which demonstrates the use of + POST requests. + + +Version 0.4.8 +------------- + +2002-03-07 Kjetil Jacobsen + + * Added CURLOPT_PREQUOTE. + + * Now requires libcurl 7.9.5 or greater. + + * Other minor code cleanups and bugfixes. + +2002-03-05 Kjetil Jacobsen + + * Do not allow WRITEFUNCTION and WRITEHEADER on the same handle. + + +Version 0.4.7 +------------- + +2002-02-27 Kjetil Jacobsen + + * 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 + + * Clarence Garnder found a bug where string + arguments to setopt sometimes were prematurely deallocated, this + should now be fixed. + +2002-02-21 Kjetil Jacobsen + + * 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 + + * Fixed docstring for getopt() function. + +2001-12-18 Kjetil Jacobsen + + * Updated the INSTALL information for Win32. + +2001-12-12 Kjetil Jacobsen + + * Added missing link flag to make pycurl build on MacOS X (by Matt + King ). + +2001-12-06 Kjetil Jacobsen + + * Added CURLINFO_STARTTRANSFER_TIME and CURLOPT_FTP_USE_EPSV from + libcurl 7.9.2. + +2001-12-01 Markus F.X.J. Oberhumer + + * Added the 'test_stringio.py' file which demonstrates the use of + StringIO objects as callback. + +2001-12-01 Markus F.X.J. Oberhumer + + * setup.py: Do not remove entries from a list while iterating + over it. + +2001-11-29 Kjetil Jacobsen + + * Added code in setup.py to install on Windows. Requires some + manual configuration (by Tino Lange ). + +2001-11-27 Kjetil Jacobsen + + * 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 + + * Some of the newer options to setopt were missing, this should + now be fixed. + +2001-11-04 Kjetil Jacobsen + + * Exception handling has been improved and should no longer throw + spurious exceptions (by Markus F.X.J. Oberhumer + ). + +2001-10-15 Kjetil Jacobsen + + * Refactored the test_gtk.py script to avoid global variables. + +2001-10-12 Kjetil Jacobsen + + * 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 ). + + * Allow C-functions and Python methods as callbacks (by Markus + F.X.J. Oberhumer ). + + * Allow None as success result of write, header and progress + callback invocations (by Markus F.X.J. Oberhumer + ). + + * 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 + + * Cleaned up the script with GNOME/PycURL integration. + +2001-08-20 Kjetil Jacobsen + + * 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 + + * Added test script for using PycURL and GNOME (tests/test_gtk.py). + +2001-08-20 Kjetil Jacobsen + + * Added test script for using XML-RPC (tests/test_xmlrpc.py). + + * Added more comments to the test sources. + +2001-08-06 Kjetil Jacobsen + + * Renamed module namespace to pycurl instead of curl. + +2001-08-06 Kjetil Jacobsen + + * Set CURLOPT_VERBOSE to 0 by default. + +2001-06-29 Kjetil Jacobsen + + * Updated INSTALL, curl version 7.8 or greater is now mandatory to + use pycurl. + +2001-06-13 Kjetil Jacobsen + + * Set NOPROGRESS to 1 by default. + +2001-06-07 Kjetil Jacobsen + + * Added global_init/cleanup. + +2001-06-06 Kjetil Jacobsen + + * Added HEADER/PROGRESSFUNCTION callbacks (see files in tests/). + + * Added PASSWDFUNCTION callback (untested). + + * Added READFUNCTION callback (untested). + +2001-06-05 Kjetil Jacobsen + + * 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 + + * Return -1 on error from Python callback in WRITEFUNCTION callback. + +2001-06-01 Kjetil Jacobsen + + * Moved source to src and tests to tests directory. + +2001-05-31 Kjetil Jacobsen + + * Added better type checking for setopt. + +2001-05-30 Kjetil Jacobsen + + * Moved code to sourceforge. + + * Added getinfo support. + + +# vi:ts=8:et diff --git a/pycurl/INSTALL b/pycurl/INSTALL new file mode 100644 index 0000000..ef42cf6 --- /dev/null +++ b/pycurl/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/pycurl/MANIFEST.in b/pycurl/MANIFEST.in new file mode 100644 index 0000000..f4e3837 --- /dev/null +++ b/pycurl/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/pycurl/Makefile b/pycurl/Makefile new file mode 100644 index 0000000..9b2369d --- /dev/null +++ b/pycurl/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/pycurl/PKG-INFO b/pycurl/PKG-INFO new file mode 100644 index 0000000..f0c0e97 --- /dev/null +++ b/pycurl/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 1.0 +Name: pycurl +Version: 7.13.1 +Summary: PycURL -- cURL library module for Python +Home-page: http://pycurl.sourceforge.net/ +Author: Kjetil Jacobsen, Markus F.X.J. Oberhumer +Author-email: kjetilja@cs.uit.no, markus@oberhumer.com +License: GNU Lesser General Public License (LGPL) +Description: + This module provides Python bindings for the cURL library. +Platform: All diff --git a/pycurl/README b/pycurl/README new file mode 100644 index 0000000..bd04ab6 --- /dev/null +++ b/pycurl/README @@ -0,0 +1,12 @@ +LICENSE +------- + +Copyright (C) 2001-2005 by Kjetil Jacobsen +Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer + +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/pycurl/TODO b/pycurl/TODO new file mode 100644 index 0000000..0ed534a --- /dev/null +++ b/pycurl/TODO @@ -0,0 +1,27 @@ +# $Id: TODO,v 1.95 2005/03/04 14:43:04 kjetilja Exp $ +# vi:ts=4:et + +If you want to hack on pycurl, here's our list of unresolved issues: + + +NEW FEATURES/IMPROVEMENTS: + + * 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. + + +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/pycurl/doc/callbacks.html b/pycurl/doc/callbacks.html new file mode 100644 index 0000000..e300c4d --- /dev/null +++ b/pycurl/doc/callbacks.html @@ -0,0 +1,140 @@ + + + + + PyCurl: Callbacks + + + + + + +

Callbacks

+ +

For more fine-grained control, libcurl allows a +number of callbacks to be associated with each connection. In +pycurl, callbacks are defined using the setopt() 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.

+ +

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

+ +The signature of each callback used in pycurl is as follows:
+
+WRITEFUNCTION(string) -> number of characters written
+
+
+READFUNCTION(number of characters to read)-> +string
+
+HEADERFUNCTION(string) -> number of characters written
+

+PROGRESSFUNCTION(download total, downloaded, upload total, uploaded) -> status
+
+DEBUGFUNCTION(debug message type, debug message string) +-> None
+
+IOCTLFUNCTION(ioctl cmd) +-> status
+
+
+ +

Example: Callbacks for document header and body

+ +

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.

+ +
+    ## 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()
+
+ +

Example: Download/upload progress callback

+ +

This example shows how to use the progress callback. When downloading +a document, the arguments related to uploads are zero, and vice versa.

+ +
+    ## 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()
+
+ +

Example: Debug callbacks

+ +

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.

+ +
+    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()
+
+ +

Other examples

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

+ + +
+

+ Valid XHTML 1.0! + $Id: callbacks.html,v 1.15 2005/02/10 11:35:23 kjetilja Exp $ +

+ + + diff --git a/pycurl/doc/curlmultiobject.html b/pycurl/doc/curlmultiobject.html new file mode 100644 index 0000000..812111d --- /dev/null +++ b/pycurl/doc/curlmultiobject.html @@ -0,0 +1,136 @@ + + + + + PycURL: CurlMulti Objects + + + + + + +

CurlMulti Object

+ +

CurlMulti objects have the following methods:

+ +
+
close() -> None
+
+

Corresponds to +curl_multi_cleanup() 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.

+
+ +
perform() -> tuple of status and the number of active Curl objects
+
+

Corresponds to +curl_multi_perform() in libcurl.

+
+ +
add_handle(Curl object) -> None
+
+

Corresponds to +curl_multi_add_handle() in libcurl. +This method adds an existing and valid Curl object to the CurlMulti +object.

+ +

IMPORTANT NOTE: add_handle does not implicitly add a Python reference +to the Curl object (and thus does not increase the reference count on the Curl +object).

+
+ +
remove_handle(Curl object) -> None
+
+

Corresponds to +curl_multi_remove_handle() in libcurl. +This method removes an existing and valid Curl object from the CurlMulti +object.

+ +

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

+
+ +
fdset() -> +triple of lists with active file descriptors, +readable, writeable, exceptions.
+
+

Corresponds to +curl_multi_fdset() in libcurl. +This method extracts the file descriptor information from a CurlMulti object. +The returned lists can be used with the select module to +poll for events.

+ +

Example usage:

+ +
+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
+
+
+ +
select([timeout]) -> +number of ready file descriptors or -1 on timeout
+
+

This is a convenience function which simplifies the combined +use of fdset() and the select module.

+ +

Example usage:

+ +
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
+
+
+ +
info_read([max]) -> +numberof queued messages, a list of successful objects, a list of +failed objects
+
+

Corresponds to the +curl_multi_info_read() function in libcurl. +This method extracts at most max 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 <curl object, curl error number, curl +error message> for each failed curl object. The number +of queued messages after this method has been called is also +returned.

+
+
+ +
+

+ Valid XHTML 1.0! + $Id: curlmultiobject.html,v 1.4 2004/06/05 17:59:01 mfx Exp $ +

+ + + diff --git a/pycurl/doc/curlobject.html b/pycurl/doc/curlobject.html new file mode 100644 index 0000000..4a6fe78 --- /dev/null +++ b/pycurl/doc/curlobject.html @@ -0,0 +1,102 @@ + + + + + PycURL: Curl Objects + + + + + + +

Curl Object

+ +

Curl objects have the following methods:

+ +
+
close() -> None
+
+

Corresponds to +curl_easy_cleanup 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.

+
+ +
perform() -> None
+
+

Corresponds to +curl_easy_perform in libcurl.

+
+ +
setopt(option, value) -> None
+
+ +

Corresponds to +curl_easy_setopt in libcurl, where +option is specified with the CURLOPT_* constants in libcurl, +except that the CURLOPT_ prefix has been removed. The type for +value depends on the option, and can be either a string, +integer, long integer, file objects, lists, or functions.

+ +

Example usage:

+ +
+import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://www.python.org/")
+c.setopt(pycurl.HTTPHEADER, ["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()
+...
+
+
+ +
getinfo(option) -> Result
+
+ +

Corresponds to +curl_easy_getinfo in libcurl, where +option is the same as the CURLINFO_* constants in libcurl, +except that the CURLINFO_ prefix has been removed. +Result contains an integer, float or string, depending on +which option is given. The getinfo method should +not be called unless perform has been called and +finished.

+ +

Example usage:

+ +
+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/"
+
+
+ +
errstr() -> String
+
+

Returns the internal libcurl error buffer of this handle as a string.

+
+
+ + +
+

+ Valid XHTML 1.0! + $Id: curlobject.html,v 1.14 2005/02/11 11:09:09 mfx Exp $ +

+ + + diff --git a/pycurl/doc/pycurl.html b/pycurl/doc/pycurl.html new file mode 100644 index 0000000..39c34fb --- /dev/null +++ b/pycurl/doc/pycurl.html @@ -0,0 +1,120 @@ + + + + + PycURL Documentation + + + + + + +

pycurl — A Python interface to the cURL library

+ +

The pycurl package is a Python interface to libcurl (http://curl.haxx.se/libcurl/). pycurl +has been successfully built and tested with Python versions from +2.2 to the current 2.4.x releases.

+ +

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.

+ +

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 +(http://curl.haxx.se/libcurl/c/).

+ +
+ +

Module Functionality

+ +
+
pycurl.global_init(option) ->None
+ +

option is one of the constants +pycurl.GLOBAL_SSL, pycurl.GLOBAL_WIN32, pycurl.GLOBAL_ALL, +pycurl.GLOBAL_NOTHING, pycurl.GLOBAL_DEFAULT. Corresponds to +curl_global_init() in libcurl.

+
+ +
pycurl.global_cleanup() -> None
+
+

Corresponds to +curl_global_cleanup() in libcurl.

+
+ +
pycurl.version
+ +

This is a string with version information on libcurl, +corresponding to +curl_version() in libcurl.

+ +

Example usage:

+
+>>> import pycurl
+>>> pycurl.version
+'libcurl/7.12.3 OpenSSL/0.9.7e zlib/1.2.2.1 libidn/0.5.12'
+
+
+ +
pycurl.version_info() -> Tuple
+
+

Corresponds to +curl_version_info() in libcurl. +Returns a tuple of information which is similar to the +curl_version_info_data struct returned by +curl_version_info() in libcurl.

+ +

Example usage:

+
+>>> 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')
+
+
+ +
pycurl.Curl() -> Curl object
+
+

This function creates a new +Curl object which corresponds to a +CURL handle in libcurl. Curl objects automatically +set CURLOPT_VERBOSE to 0, CURLOPT_NOPROGRESS to 1, +provide a default CURLOPT_USERAGENT and setup +CURLOPT_ERRORBUFFER to point to a private error buffer.

+
+ +
pycurl.CurlMulti() -> CurlMulti object
+
+

This function creates a new +CurlMulti object which corresponds to +a CURLM handle in libcurl.

+
+
+ +
+ +

Subsections

+ + + +
+

+ Valid XHTML 1.0! + $Id: pycurl.html,v 1.28 2005/02/11 11:09:10 mfx Exp $ +

+ + + diff --git a/pycurl/examples/basicfirst.py b/pycurl/examples/basicfirst.py new file mode 100644 index 0000000..4e49465 --- /dev/null +++ b/pycurl/examples/basicfirst.py @@ -0,0 +1,25 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vi:ts=4:et +# $Id: basicfirst.py,v 1.5 2005/02/11 11:09:11 mfx 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.perform() +c.close() + +print t.contents diff --git a/pycurl/examples/file_upload.py b/pycurl/examples/file_upload.py new file mode 100644 index 0000000..e513e7d --- /dev/null +++ b/pycurl/examples/file_upload.py @@ -0,0 +1,46 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vi:ts=4:et +# $Id: file_upload.py,v 1.5 2005/02/13 08:53:13 mfx Exp $ + +import os, sys +import pycurl + +# Class which holds a file reference and the read callback +class FileReader: + def __init__(self, fp): + self.fp = fp + def read_callback(self, size): + return self.fp.read(size) + +# Check commandline arguments +if len(sys.argv) < 3: + print "Usage: %s " % sys.argv[0] + raise SystemExit +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. +filesize = os.path.getsize(filename) +c.setopt(pycurl.INFILESIZE, filesize) + +# Start transfer +print 'Uploading file %s to url %s' % (filename, url) +c.perform() +c.close() diff --git a/pycurl/examples/linksys.py b/pycurl/examples/linksys.py new file mode 100755 index 0000000..a60eba1 --- /dev/null +++ b/pycurl/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 []" + 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 " + print "Sets the Host field to be queried by the ISP." + + def do_domain(self, line): + print "Usage: host " + 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 " + 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 " + 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 " + 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 " + 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 " + 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 " + 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 " + 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} " + 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 " + 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 " + 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 " + 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 " + 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} " + 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 " + 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/pycurl/examples/retriever-multi.py b/pycurl/examples/retriever-multi.py new file mode 100644 index 0000000..488d0f7 --- /dev/null +++ b/pycurl/examples/retriever-multi.py @@ -0,0 +1,122 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vi:ts=4:et +# $Id: retriever-multi.py,v 1.25 2005/02/13 08:28:01 mfx Exp $ + +# +# Usage: python retriever-multi.py [<# of +# concurrent connections>] +# + +import 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: + if sys.argv[1] == "-": + urls = sys.stdin.readlines() + else: + urls = open(sys.argv[1]).readlines() + if len(sys.argv) >= 3: + num_conn = int(sys.argv[2]) +except: + print "Usage: %s [<# of concurrent connections>]" % sys.argv[0] + raise SystemExit + + +# Make a queue with (url, filename) tuples +queue = [] +for url in urls: + url = url.strip() + if not url or url[0] == "#": + continue + filename = "doc_%03d.dat" % (len(queue) + 1) + queue.append((url, filename)) + + +# 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 -----" + + +# Pre-allocate 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.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() + diff --git a/pycurl/examples/retriever.py b/pycurl/examples/retriever.py new file mode 100644 index 0000000..bd20da7 --- /dev/null +++ b/pycurl/examples/retriever.py @@ -0,0 +1,99 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vi:ts=4:et +# $Id: retriever.py,v 1.17 2005/02/13 08:28:01 mfx Exp $ + +# +# Usage: python retriever.py [<# of +# concurrent connections>] +# + +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 + + +# Get args +num_conn = 10 +try: + if sys.argv[1] == "-": + urls = sys.stdin.readlines() + else: + urls = open(sys.argv[1]).readlines() + if len(sys.argv) >= 3: + num_conn = int(sys.argv[2]) +except: + print "Usage: %s [<# of concurrent connections>]" % sys.argv[0] + raise SystemExit + + +# Make a queue with (url, filename) tuples +queue = Queue.Queue() +for url in urls: + url = url.strip() + if not url or url[0] == "#": + continue + filename = "doc_%03d.dat" % (len(queue.queue) + 1) + queue.put((url, filename)) + + +# Check args +assert queue.queue, "no URLs given" +num_urls = len(queue.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 -----" + + +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 + fp = open(filename, "wb") + curl = pycurl.Curl() + curl.setopt(pycurl.URL, url) + curl.setopt(pycurl.FOLLOWLOCATION, 1) + curl.setopt(pycurl.MAXREDIRS, 5) + curl.setopt(pycurl.CONNECTTIMEOUT, 30) + curl.setopt(pycurl.TIMEOUT, 300) + curl.setopt(pycurl.NOSIGNAL, 1) + curl.setopt(pycurl.WRITEDATA, fp) + try: + curl.perform() + except: + import traceback + traceback.print_exc(file=sys.stderr) + sys.stderr.flush() + curl.close() + fp.close() + sys.stdout.write(".") + sys.stdout.flush() + + +# Start a bunch of threads +threads = [] +for dummy in range(num_conn): + t = WorkerThread(queue) + t.start() + threads.append(t) + + +# Wait for all threads to finish +for thread in threads: + thread.join() diff --git a/pycurl/examples/sfquery.py b/pycurl/examples/sfquery.py new file mode 100644 index 0000000..0c63f61 --- /dev/null +++ b/pycurl/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/pycurl/examples/xmlrpc_curl.py b/pycurl/examples/xmlrpc_curl.py new file mode 100644 index 0000000..a175d23 --- /dev/null +++ b/pycurl/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.10 2005/02/11 11:09:12 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 = [ "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/pycurl/python/curl/__init__.py b/pycurl/python/curl/__init__.py new file mode 100644 index 0000000..8fecb4d --- /dev/null +++ b/pycurl/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/pycurl/setup.py b/pycurl/setup.py new file mode 100644 index 0000000..a9799db --- /dev/null +++ b/pycurl/setup.py @@ -0,0 +1,199 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vi:ts=4:et +# $Id: setup.py,v 1.123 2005/02/17 10:13:23 mfx Exp $ + +"""Setup script for the PycURL module distribution.""" + +PACKAGE = "pycurl" +PY_PACKAGE = "curl" +VERSION = "7.13.1" + +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.13.1" + 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@cs.uit.no, markus@oberhumer.com", + maintainer="Kjetil Jacobsen, Markus F.X.J. Oberhumer", + maintainer_email="kjetilja@cs.uit.no, markus@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/pycurl/setup_win32_ssl.py b/pycurl/setup_win32_ssl.py new file mode 100644 index 0000000..b900874 --- /dev/null +++ b/pycurl/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.27 2005/02/17 10:13:23 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.1-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/pycurl/src/Makefile b/pycurl/src/Makefile new file mode 100644 index 0000000..a4828d3 --- /dev/null +++ b/pycurl/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/pycurl/src/pycurl.c b/pycurl/src/pycurl.c new file mode 100644 index 0000000..6f5f699 --- /dev/null +++ b/pycurl/src/pycurl.c @@ -0,0 +1,2828 @@ +/* $Id: pycurl.c,v 1.86 2005/03/04 08:39:30 kjetilja Exp $ */ + +/* PycURL -- cURL Python module + * + * Authors: + * Copyright (C) 2001-2005 by Kjetil Jacobsen + * Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer + * + * Contributions: + * Tino Lange + * Matt King + * Conrad Steenberg + * Amit Mongia + * Eric S. Raymond + * Martin Muenstermann + * Domenico Andreoli + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#undef NDEBUG +#include + +/* 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 < 0x070d01) +# error "Need libcurl version 7.13.1 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) +{ + CurlObject *self = NULL; + int res; + char *s = NULL; + + UNUSED(dummy); + + /* 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)); + + /* Set backreference */ + res = curl_easy_setopt(self->handle, CURLOPT_PRIVATE, (char *) self); + if (res != CURLE_OK) + goto 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 FTP_ACCOUNT to NULL by default */ + res = curl_easy_setopt(self->handle, CURLOPT_FTP_ACCOUNT, NULL); + if (res != CURLE_OK) + goto error; + + /* Set default USERAGENT */ + s = (char *) malloc(7 + strlen(LIBCURL_VERSION) + 1); + if (s == NULL) + goto error; + strcpy(s, "PycURL/"); strcpy(s+7, LIBCURL_VERSION); + res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) s); + if (res != CURLE_OK) { + free(s); + goto error; + } + self->options[ OPT_INDEX(CURLOPT_USERAGENT) ] = s; s = NULL; + + /* 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) +{ + 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) +{ + 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) +{ + int res; + + 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 if (PyInt_Check(result)) { + long r = PyInt_AsLong(result); + if (r != CURL_READFUNC_ABORT) { + goto type_error; + } + /* ret is CURL_READUNC_ABORT */ + } + else if (PyLong_Check(result)) { + long r = PyLong_AsLong(result); + if (r != CURL_READFUNC_ABORT) { + goto type_error; + } + /* ret is CURL_READUNC_ABORT */ + } + else { + type_error: + 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 (curlioerr) 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 (curlioerr) 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 elements (name, value)"); + return NULL; + } + if (PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 0), &nstr, &nlen) != 0) { + curl_formfree(post); + PyErr_SetString(PyExc_TypeError, "tuple must contain string as first element"); + return NULL; + } + if (PyString_Check(PyTuple_GET_ITEM(listitem, 1))) { + /* Handle strings as second argument for backwards compatibility */ + PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 1), &cstr, &clen); + /* 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(); + } + } + else if (PyTuple_Check(PyTuple_GET_ITEM(listitem, 1))) { + /* Supports content, file and content-type */ + PyObject *t = PyTuple_GET_ITEM(listitem, 1); + int tlen = PyTuple_Size(t); + int j, k, l; + struct curl_forms *forms = NULL; + + /* Sanity check that there are at least two tuple items */ + if (tlen < 2) { + curl_formfree(post); + PyErr_SetString(PyExc_TypeError, "tuple must contain at least one option and one value"); + return NULL; + } + + /* Allocate enough space to accommodate length options for content */ + forms = PyMem_Malloc(sizeof(struct curl_forms) * ((tlen*2) + 1)); + if (forms == NULL) { + curl_formfree(post); + PyErr_NoMemory(); + return NULL; + } + + /* Iterate all the tuple members pairwise */ + for (j = 0, k = 0, l = 0; j < tlen; j += 2, l++) { + char *ostr; + int olen, val; + + if (j == (tlen-1)) { + PyErr_SetString(PyExc_TypeError, "expected value"); + PyMem_Free(forms); + curl_formfree(post); + return NULL; + } + if (!PyInt_Check(PyTuple_GET_ITEM(t, j))) { + PyErr_SetString(PyExc_TypeError, "option must be long"); + PyMem_Free(forms); + curl_formfree(post); + return NULL; + } + if (!PyString_Check(PyTuple_GET_ITEM(t, j+1))) { + PyErr_SetString(PyExc_TypeError, "value must be string"); + PyMem_Free(forms); + curl_formfree(post); + return NULL; + } + + val = PyLong_AsLong(PyTuple_GET_ITEM(t, j)); + if (val != CURLFORM_COPYCONTENTS && + val != CURLFORM_FILE && + val != CURLFORM_CONTENTTYPE) + { + PyErr_SetString(PyExc_TypeError, "unsupported option"); + PyMem_Free(forms); + curl_formfree(post); + return NULL; + } + PyString_AsStringAndSize(PyTuple_GET_ITEM(t, j+1), &ostr, &olen); + forms[k].option = val; + forms[k].value = ostr; + ++k; + if (val == CURLFORM_COPYCONTENTS) { + /* Contents can contain \0 bytes so we specify the length */ + forms[k].option = CURLFORM_CONTENTSLENGTH; + forms[k].value = (char *)olen; + ++k; + } + } + forms[k].option = CURLFORM_END; + res = curl_formadd(&post, &last, + CURLFORM_COPYNAME, nstr, + CURLFORM_NAMELENGTH, (long) nlen, + CURLFORM_ARRAY, forms, + CURLFORM_END); + PyMem_Free(forms); + if (res != CURLE_OK) { + curl_formfree(post); + CURLERROR_RETVAL(); + } + } else { + /* Some other type was given, ignore */ + curl_formfree(post); + PyErr_SetString(PyExc_TypeError, "unsupported second type in tuple"); + return NULL; + } + } + 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 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) +{ + CurlMultiObject *self; + + UNUSED(dummy); + + /* 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) +{ + 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) +{ + CURLMcode res; + int running = -1; + + 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) +{ + 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 (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_NOARGS, co_close_doc}, + {"errstr", (PyCFunction)do_curl_errstr, METH_NOARGS, co_errstr_doc}, + {"getinfo", (PyCFunction)do_curl_getinfo, METH_VARARGS, co_getinfo_doc}, + {"perform", (PyCFunction)do_curl_perform, METH_NOARGS, co_perform_doc}, + {"setopt", (PyCFunction)do_curl_setopt, METH_VARARGS, co_setopt_doc}, + {"unsetopt", (PyCFunction)do_curl_unsetopt, METH_VARARGS, co_unsetopt_doc}, + {NULL, NULL, 0, NULL} +}; + +static PyMethodDef curlmultiobject_methods[] = { + {"add_handle", (PyCFunction)do_multi_add_handle, METH_VARARGS, NULL}, + {"close", (PyCFunction)do_multi_close, METH_NOARGS, NULL}, + {"fdset", (PyCFunction)do_multi_fdset, METH_NOARGS, co_multi_fdset_doc}, + {"info_read", (PyCFunction)do_multi_info_read, METH_VARARGS, co_multi_info_read_doc}, + {"perform", (PyCFunction)do_multi_perform, METH_NOARGS, 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) +{ + UNUSED(dummy); + 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_NOARGS, pycurl_global_cleanup_doc}, + {"version_info", (PyCFunction)do_version_info, METH_VARARGS, pycurl_version_info_doc}, + {"Curl", (PyCFunction)do_curl_new, METH_NOARGS, pycurl_curl_new_doc}, + {"CurlMulti", (PyCFunction)do_multi_new, METH_NOARGS, 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 + **/ + + /* Abort curl_read_callback(). */ + insint_c(d, "READFUNC_ABORT", CURL_READFUNC_ABORT); + + /* 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); + + /* 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); + + /* curl_ftpauth: constants for setopt(FTPSSLAUTH, x) */ + insint_c(d, "FORM_CONTENTS", CURLFORM_COPYCONTENTS); + insint_c(d, "FORM_FILE", CURLFORM_FILE); + insint_c(d, "FORM_CONTENTTYPE", CURLFORM_CONTENTTYPE); + + /* CURLoption: symbolic constants for setopt() */ +/* FIXME: reorder these to match */ + insint_c(d, "FILE", CURLOPT_WRITEDATA); + insint_c(d, "URL", CURLOPT_URL); + insint_c(d, "PORT", CURLOPT_PORT); + insint_c(d, "PROXY", CURLOPT_PROXY); + insint_c(d, "USERPWD", CURLOPT_USERPWD); + insint_c(d, "PROXYUSERPWD", CURLOPT_PROXYUSERPWD); + insint_c(d, "RANGE", CURLOPT_RANGE); + insint_c(d, "INFILE", CURLOPT_READDATA); + /* ERRORBUFFER is not supported */ + insint_c(d, "WRITEFUNCTION", CURLOPT_WRITEFUNCTION); + insint_c(d, "READFUNCTION", CURLOPT_READFUNCTION); + insint_c(d, "TIMEOUT", CURLOPT_TIMEOUT); + insint_c(d, "INFILESIZE", CURLOPT_INFILESIZE_LARGE); /* _LARGE ! */ + insint_c(d, "POSTFIELDS", CURLOPT_POSTFIELDS); + insint_c(d, "REFERER", CURLOPT_REFERER); + insint_c(d, "FTPPORT", CURLOPT_FTPPORT); + insint_c(d, "USERAGENT", CURLOPT_USERAGENT); + insint_c(d, "LOW_SPEED_LIMIT", CURLOPT_LOW_SPEED_LIMIT); + insint_c(d, "LOW_SPEED_TIME", CURLOPT_LOW_SPEED_TIME); + insint_c(d, "RESUME_FROM", CURLOPT_RESUME_FROM_LARGE); /* _LARGE ! */ + insint_c(d, "WRITEDATA", CURLOPT_WRITEDATA); + insint_c(d, "READDATA", CURLOPT_READDATA); + 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, "POSTFIELDSIZE", CURLOPT_POSTFIELDSIZE_LARGE); /* _LARGE ! */ + 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_LARGE); /* _LARGE ! */ + 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, "IOCTLFUNCTION", CURLOPT_IOCTLFUNCTION); + insint_c(d, "IOCTLDATA", CURLOPT_IOCTLDATA); + insint_c(d, "SOURCE_URL", CURLOPT_SOURCE_URL); + insint_c(d, "SOURCE_QUOTE", CURLOPT_SOURCE_QUOTE); + insint_c(d, "FTP_ACCOUNT", CURLOPT_FTP_ACCOUNT); + + /* 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 + **/ + + /* 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/pycurl/tests/test.py b/pycurl/tests/test.py new file mode 100644 index 0000000..f5afada --- /dev/null +++ b/pycurl/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 " % 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/pycurl/tests/test_cb.py b/pycurl/tests/test_cb.py new file mode 100644 index 0000000..509f49e --- /dev/null +++ b/pycurl/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/pycurl/tests/test_debug.py b/pycurl/tests/test_debug.py new file mode 100644 index 0000000..d3a6356 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_getinfo.py b/pycurl/tests/test_getinfo.py new file mode 100644 index 0000000..dd13570 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_gtk.py b/pycurl/tests/test_gtk.py new file mode 100644 index 0000000..b3c28c6 --- /dev/null +++ b/pycurl/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 " % 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/pycurl/tests/test_internals.py b/pycurl/tests/test_internals.py new file mode 100644 index 0000000..afcc53d --- /dev/null +++ b/pycurl/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/pycurl/tests/test_memleak.py b/pycurl/tests/test_memleak.py new file mode 100644 index 0000000..284df62 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_multi.py b/pycurl/tests/test_multi.py new file mode 100644 index 0000000..5be86f0 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_multi2.py b/pycurl/tests/test_multi2.py new file mode 100644 index 0000000..3d8ab1b --- /dev/null +++ b/pycurl/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/pycurl/tests/test_multi3.py b/pycurl/tests/test_multi3.py new file mode 100644 index 0000000..9b52159 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_multi4.py b/pycurl/tests/test_multi4.py new file mode 100644 index 0000000..6b143aa --- /dev/null +++ b/pycurl/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/pycurl/tests/test_multi5.py b/pycurl/tests/test_multi5.py new file mode 100644 index 0000000..10f799e --- /dev/null +++ b/pycurl/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/pycurl/tests/test_multi6.py b/pycurl/tests/test_multi6.py new file mode 100644 index 0000000..77585e5 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_multi_vs_thread.py b/pycurl/tests/test_multi_vs_thread.py new file mode 100644 index 0000000..a6030cc --- /dev/null +++ b/pycurl/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/pycurl/tests/test_post.py b/pycurl/tests/test_post.py new file mode 100644 index 0000000..574bbda --- /dev/null +++ b/pycurl/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/pycurl/tests/test_post2.py b/pycurl/tests/test_post2.py new file mode 100644 index 0000000..fd1395a --- /dev/null +++ b/pycurl/tests/test_post2.py @@ -0,0 +1,18 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vi:ts=4:et +# $Id: test_post2.py,v 1.13 2005/03/03 10:00:40 kjetilja Exp $ + +import pycurl + +pf = [('field1', 'this is a test using httppost & stuff'), + ('field2', (pycurl.FORM_FILE, 'test_post.py', pycurl.FORM_FILE, 'test_post2.py')), + ('field3', (pycurl.FORM_CONTENTS, 'this is wei\000rd, but null-bytes are okay')) + ] + +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/pycurl/tests/test_post3.py b/pycurl/tests/test_post3.py new file mode 100644 index 0000000..3ebdd55 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_stringio.py b/pycurl/tests/test_stringio.py new file mode 100644 index 0000000..7fdf153 --- /dev/null +++ b/pycurl/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/pycurl/tests/test_xmlrpc.py b/pycurl/tests/test_xmlrpc.py new file mode 100644 index 0000000..c2ff86d --- /dev/null +++ b/pycurl/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 = """ +%s%s +""" + +# 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/pycurl/tests/util.py b/pycurl/tests/util.py new file mode 100644 index 0000000..945ad0d --- /dev/null +++ b/pycurl/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 + + -- 2.43.0