├── .appveyor ├── install.ps1 └── run_with_env.cmd ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── workflows │ ├── ci-macos.yml │ ├── ci-windows.yml │ ├── ci.yml │ └── cibuildwheel.yml ├── .gitignore ├── .travis.yml ├── AUTHORS ├── COPYING-LGPL ├── COPYING-MIT ├── ChangeLog ├── INSTALL.rst ├── MANIFEST.in ├── Makefile ├── README.kr.rst ├── README.rst ├── RELEASE-NOTES.rst ├── appveyor.yml ├── doc ├── callbacks.rst ├── conf.py ├── curl.rst ├── curlmultiobject.rst ├── curlobject.rst ├── curlshareobject.rst ├── docstrings │ ├── curl.rst │ ├── curl_close.rst │ ├── curl_duphandle.rst │ ├── curl_errstr.rst │ ├── curl_errstr_raw.rst │ ├── curl_getinfo.rst │ ├── curl_getinfo_raw.rst │ ├── curl_pause.rst │ ├── curl_perform.rst │ ├── curl_perform_rb.rst │ ├── curl_perform_rs.rst │ ├── curl_reset.rst │ ├── curl_set_ca_certs.rst │ ├── curl_setopt.rst │ ├── curl_setopt_string.rst │ ├── curl_unsetopt.rst │ ├── multi.rst │ ├── multi_add_handle.rst │ ├── multi_assign.rst │ ├── multi_close.rst │ ├── multi_fdset.rst │ ├── multi_info_read.rst │ ├── multi_perform.rst │ ├── multi_remove_handle.rst │ ├── multi_select.rst │ ├── multi_setopt.rst │ ├── multi_socket_action.rst │ ├── multi_socket_all.rst │ ├── multi_timeout.rst │ ├── pycurl_global_cleanup.rst │ ├── pycurl_global_init.rst │ ├── pycurl_module.rst │ ├── pycurl_version_info.rst │ ├── share.rst │ ├── share_close.rst │ └── share_setopt.rst ├── files.rst ├── index.rst ├── install.rst ├── internals.rst ├── pycurl.rst ├── quickstart.rst ├── release-notes.rst ├── release-process.rst ├── static │ └── favicon.ico ├── thread-safety.rst ├── troubleshooting.rst ├── unicode.rst └── unimplemented.rst ├── examples ├── basicfirst.py ├── file_upload.py ├── linksys.py ├── multi-socket_action-select.py ├── opensocketexception.py ├── quickstart │ ├── file_upload_buffer.py │ ├── file_upload_real.py │ ├── file_upload_real_fancy.py │ ├── follow_redirect.py │ ├── form_post.py │ ├── get.py │ ├── get_python2.py │ ├── get_python2_https.py │ ├── get_python3.py │ ├── get_python3_https.py │ ├── put_buffer.py │ ├── put_file.py │ ├── response_headers.py │ ├── response_info.py │ └── write_file.py ├── retriever-multi.py ├── retriever.py ├── sfquery.py ├── smtp.py ├── ssh_keyfunction.py ├── tests │ ├── test_build_config.py │ ├── test_gtk.py │ └── test_xmlrpc.py └── xmlrpc_curl.py ├── linux_install.txt ├── pytest.ini ├── python └── curl │ └── __init__.py ├── requests_curl ├── __init__.py ├── adapter.py ├── error.py ├── pool.py ├── pool_provider.py ├── request.py └── response.py ├── requirements-dev.txt ├── scripts └── missing-symbols ├── setup.py ├── src ├── easy.c ├── easycb.c ├── easyinfo.c ├── easyopt.c ├── easyperform.c ├── module.c ├── multi.c ├── oscompat.c ├── pycurl.h ├── pythoncompat.c ├── share.c ├── stringcompat.c ├── threadsupport.c └── util.c ├── test.py ├── tests ├── __init__.py ├── app.py ├── appmanager.py ├── bin │ └── realpath ├── c │ └── winsockdup.c ├── cadata_test.py ├── certinfo_test.py ├── certs │ ├── ca.crt │ ├── ca.key │ ├── server.crt │ └── server.key ├── close_socket_cb_test.py ├── curl_object_test.py ├── debug_test.py ├── default_write_cb_test.py ├── duphandle_test.py ├── error_constants_test.py ├── error_test.py ├── ext │ ├── test-lib.sh │ └── test-suite.sh ├── failonerror_test.py ├── fake-curl │ ├── curl-config-empty │ ├── curl-config-libs-and-static-libs │ ├── curl-config-ssl-feature-only │ ├── curl-config-ssl-in-libs │ ├── curl-config-ssl-in-static-libs │ └── libcurl │ │ ├── Makefile │ │ ├── with_gnutls.c │ │ ├── with_nss.c │ │ ├── with_openssl.c │ │ ├── with_unknown_ssl.c │ │ └── without_ssl.c ├── fixtures │ └── form_submission.txt ├── ftp_test.py ├── getinfo_test.py ├── global_init_test.py ├── header_cb_test.py ├── header_test.py ├── high_level_curl_test.py ├── info_constants_test.py ├── info_test.py ├── internals_test.py ├── matrix.py ├── matrix │ ├── check-python.py │ ├── curl-7.19.0-sslv2-2b0e09b0f98.patch │ ├── curl-7.19.0-sslv2-c66b0b32fba-modified.patch │ └── openssl-1.0.1e-fix_pod_syntax-1.patch ├── memory_mgmt_test.py ├── multi_callback_test.py ├── multi_memory_mgmt_test.py ├── multi_option_constants_test.py ├── multi_socket_select_test.py ├── multi_socket_test.py ├── multi_test.py ├── multi_timer_test.py ├── open_socket_cb_test.py ├── option_constants_test.py ├── pause_test.py ├── perform_test.py ├── post_test.py ├── procmgr.py ├── protocol_constants_test.py ├── read_cb_test.py ├── readdata_test.py ├── relative_url_test.py ├── reload_test.py ├── reset_test.py ├── resolve_test.py ├── run-quickstart.sh ├── run.sh ├── runwsgi.py ├── seek_cb_constants_test.py ├── seek_cb_test.py ├── setopt_lifecycle_test.py ├── setopt_string_test.py ├── setopt_test.py ├── setopt_unicode_test.py ├── setup_test.py ├── share_test.py ├── sockopt_cb_test.py ├── ssh_key_cb_test.py ├── subclass_test.py ├── travis │ ├── run.sh │ └── setup.sh ├── unset_range_test.py ├── user_agent_string_test.py ├── util.py ├── version_comparison_test.py ├── version_constants_test.py ├── version_test.py ├── vsftpd.conf ├── weakref_test.py ├── win │ └── opensocketcrash.py ├── write_abort_test.py ├── write_cb_bogus_test.py ├── write_test.py ├── write_to_stringio_test.py └── xferinfo_cb_test.py ├── winbuild.py └── winbuild ├── __init__.py ├── builder.py ├── c-ares-vs2015.patch ├── cares.py ├── config.py ├── curl.py ├── iconv.py ├── idn.py ├── libcurl-fix-zlib-references.patch ├── libssh2-vs2015.patch ├── nghttp_cmake.py ├── nghttp_gmake.py ├── openssl-fix-crt-1.0.2.patch ├── openssl-fix-crt-1.1.0.patch ├── openssl-fix-crt-1.1.1.patch ├── openssl.py ├── pycurl.py ├── pythons.py ├── ssh.py ├── tools.py ├── utils.py ├── vcvars-vc14-32.sh ├── vcvars-vc14-64.sh └── zlib.py /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PycURL 2 | 3 | ## Issues 4 | 5 | Please take a moment to consider whether your issue is a bug report or a 6 | support request. 7 | 8 | A bug report should contain code to reproduce the bug, patch to fix the bug, 9 | or at the very least a pointer to some code in PycURL that you suspect to be 10 | the problem. If you don't have any of these, your issue is likely a support 11 | request. 12 | 13 | Please send support requests to our mailing list: 14 | 15 | http://cool.haxx.se/mailman/listinfo/curl-and-python 16 | 17 | People have also had success with getting help at Stack Overflow. 18 | 19 | If you are not sure whether your issue is a support request or a bug report, 20 | please post it to the mailing list. 21 | 22 | ## Pull Requests 23 | 24 | Thanks for writing a patch! 25 | 26 | PycURL supports many Python versions, libcurl versions and SSL backends. 27 | When you submit a pull request, Travis CI will run PycURL test suite 28 | against it on a bunch of different configurations. Please check back after 29 | 10-15 minutes to see if the tests passed. A message will be shown in 30 | the pull request as to whether the build and test suite succeeded with your 31 | patch applied. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Prior to creating an issue, please review the troubleshooting documentation: 2 | http://pycurl.io/docs/dev/troubleshooting.html 3 | 4 | 5 | ## What did you try to do? 6 | 7 | 8 | ## What happened? 9 | 10 | If reporting a problem with PycURL installation, include exact commands 11 | executed as well as complete output. 12 | 13 | If reporting a problem with using PycURL, include a SHORT code snippet 14 | which includes setting `pycurl.VERBOSE` to 1 and reproduces the problem 15 | and its complete output. 16 | 17 | 18 | ## What did you expect to happen? 19 | 20 | 21 | ## What is the PycURL version? 22 | 23 | (output of `pycurl.version`) 24 | 25 | 26 | ## What is your Python version? 27 | 28 | (output of `python -V`) 29 | 30 | 31 | ## What is your operating system and its version? 32 | 33 | 34 | ## Other versions 35 | 36 | If your report references any software besides PycURL, for example pip, 37 | what are the versions of this software? (`pip --version`, etc.) 38 | 39 | 40 | ## Is this the most recent PycURL release? 41 | 42 | (if not, please check most recent PycURL release before opening the issue) 43 | 44 | 45 | ## Did you check libcurl behavior? 46 | 47 | (if possible, did you use `curl` command-line tool to verify that the behavior 48 | you are experiencing is specific to PycURL?) 49 | -------------------------------------------------------------------------------- /.github/workflows/ci-macos.yml: -------------------------------------------------------------------------------- 1 | name: CI-macOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '.github/workflows/ci.yml' 8 | - '.github/workflows/ci-windows.yml' 9 | - '.github/workflows/cibuildwheel.yml' 10 | pull_request: 11 | branches: [ master ] 12 | paths-ignore: 13 | - '.github/workflows/ci.yml' 14 | - '.github/workflows/ci-windows.yml' 15 | - '.github/workflows/cibuildwheel.yml' 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: macOS-12 21 | env: 22 | PYCURL_CURL_CONFIG: /usr/bin/curl-config 23 | PYCURL_SSL_LIBRARY: sectransp 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | - name: Set up Python ${{ matrix.python-version }} 32 | uses: actions/setup-python@v4 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | - name: Install dependencies 36 | run: | 37 | python -m pip install --upgrade pip 38 | pip install flake8 pytest 39 | if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi 40 | - name: Build 41 | run: make 42 | - name: Test with pytest 43 | run: make test 44 | -------------------------------------------------------------------------------- /.github/workflows/ci-windows.yml: -------------------------------------------------------------------------------- 1 | name: CI-Windows 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '.github/workflows/ci.yml' 8 | - '.github/workflows/ci-macos.yml' 9 | - '.github/workflows/cibuildwheel.yml' 10 | pull_request: 11 | branches: [ master ] 12 | paths-ignore: 13 | - '.github/workflows/ci.yml' 14 | - '.github/workflows/ci-macos.yml' 15 | - '.github/workflows/cibuildwheel.yml' 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: windows-latest 21 | env: 22 | VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' 23 | strategy: 24 | matrix: 25 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 26 | fail-fast: false 27 | permissions: 28 | packages: write 29 | 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: ilammy/msvc-dev-cmd@v1 33 | with: 34 | arch: 'x86' 35 | - name: Set up Python ${{ matrix.python-version }} 36 | uses: actions/setup-python@v4 37 | with: 38 | python-version: ${{ matrix.python-version }} 39 | architecture: 'x86' 40 | - name: 'Setup NuGet credentials' 41 | shell: 'bash' 42 | run: | 43 | nuget="$(vcpkg fetch nuget | tail -n 1)" 44 | "${nuget}" \ 45 | sources add \ 46 | -source "https://nuget.pkg.github.com/${GITHUB_REPOSITORY_OWNER}/index.json" \ 47 | -storepasswordincleartext \ 48 | -name "GitHub" \ 49 | -username "${GITHUB_REPOSITORY_OWNER}" \ 50 | -password "${{ secrets.GITHUB_TOKEN }}" 51 | "${nuget}" \ 52 | setapikey "${{ secrets.GITHUB_TOKEN }}" \ 53 | -source "https://nuget.pkg.github.com/${GITHUB_REPOSITORY_OWNER}/index.json" 54 | - name: Install packages 55 | run: vcpkg install curl[core,http2,non-http,openssl,ssh]:x86-windows 56 | - name: Install dependencies 57 | run: | 58 | python -m pip install --upgrade pip 59 | pip install wheel delvewheel 60 | pip install flake8 pytest -r requirements-dev.txt 61 | - name: Build 62 | run: python setup.py bdist_wheel --with-openssl --curl-dir=$env:VCPKG_INSTALLATION_ROOT/packages/curl_x86-windows --openssl-dir=$env:VCPKG_INSTALLATION_ROOT/packages/openssl_x86-windows --openssl-lib-name=libssl.lib --link-arg=libcrypto.lib 63 | - name: Repair & install built wheel 64 | run: | 65 | delvewheel repair --add-path $VCPKG_INSTALLATION_ROOT/installed/x86-windows/bin dist/*.whl 66 | pip install wheelhouse/*.whl 67 | shell: bash 68 | - name: Test with pytest 69 | run: pytest -v 70 | - name: Upload wheel 71 | uses: actions/upload-artifact@v3 72 | with: 73 | path: wheelhouse/*.whl 74 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '.github/workflows/ci-macos.yml' 8 | - '.github/workflows/ci-windows.yml' 9 | - '.github/workflows/cibuildwheel.yml' 10 | pull_request: 11 | branches: [ master ] 12 | paths-ignore: 13 | - '.github/workflows/ci-macos.yml' 14 | - '.github/workflows/ci-windows.yml' 15 | - '.github/workflows/cibuildwheel.yml' 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v4 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install packages 33 | run: | 34 | sudo apt-get update 35 | sudo apt-get install libcurl4-gnutls-dev libgnutls28-dev 36 | - name: Install dependencies 37 | run: | 38 | python -m pip install --upgrade pip 39 | pip install flake8 pytest 40 | if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi 41 | - name: Lint with flake8 42 | run: | 43 | # stop the build if there are Python syntax errors or undefined names 44 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 45 | # exit-zero treats all errors as warnings. 46 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics 47 | - name: Build 48 | run: make 49 | - name: Test with pytest 50 | run: make test 51 | -------------------------------------------------------------------------------- /.github/workflows/cibuildwheel.yml: -------------------------------------------------------------------------------- 1 | name: Linux Wheels 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | package-wheel: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | arch: [x86_64, aarch64] 16 | python-version: ["3.7", "3.8", "3.9", "3.10"] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-python@v4 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Set up QEMU 24 | if: ${{ matrix.arch == 'aarch64' }} 25 | uses: docker/setup-qemu-action@v1 26 | - name: Build wheels 27 | env: 28 | CIBW_ARCHS: ${{matrix.arch}} 29 | CIBW_BUILD: cp3* 30 | CIBW_SKIP: '*-musllinux*' 31 | CIBW_BEFORE_ALL: yum install -y libcurl-devel 32 | run: | 33 | python -m pip install --upgrade pip setuptools 34 | pip install cibuildwheel 35 | cibuildwheel --output-dir dist 36 | shell: bash 37 | - name: Upload wheels 38 | uses: actions/upload-artifact@v2 39 | with: 40 | name: dist 41 | path: dist/ 42 | 43 | deploy: 44 | 45 | runs-on: ubuntu-latest 46 | needs: package-wheel 47 | 48 | steps: 49 | - uses: actions/checkout@v2 50 | - name: Download distributions 51 | uses: actions/download-artifact@v2 52 | with: 53 | name: dist 54 | path: dist/ 55 | - name: Publish to PyPI 56 | uses: pypa/gh-action-pypi-publish@master 57 | with: 58 | user: ${{ secrets.PYPI_USERNAME }} 59 | password: ${{ secrets.PYPI_PASSWORD }} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | /MANIFEST 4 | /build 5 | /dist 6 | /src/allpycurl.c 7 | /src/docstrings.c 8 | /src/docstrings.h 9 | /tests/tmp 10 | /tests/fake-curl/libcurl/*.so 11 | /www/htdocs/download/*.bz2 12 | /www/htdocs/download/*.exe 13 | /www/htdocs/download/*.gz 14 | /www/upload/* 15 | pycurl.egg-info 16 | -------------------------------------------------------------------------------- /COPYING-MIT: -------------------------------------------------------------------------------- 1 | COPYRIGHT AND PERMISSION NOTICE 2 | 3 | Copyright (C) 2001-2008 by Kjetil Jacobsen 4 | Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer 5 | Copyright (C) 2013-2022 by Oleg Pudeyev 6 | 7 | All rights reserved. 8 | 9 | Permission to use, copy, modify, and distribute this software for any purpose 10 | with or without fee is hereby granted, provided that the above copyright 11 | notice and this permission notice appear in all copies. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN 16 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | Except as contained in this notice, the name of a copyright holder shall not 22 | be used in advertising or otherwise to promote the sale, use or other dealings 23 | in this Software without prior written authorization of the copyright holder. 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # 2 | # MANIFEST.in 3 | # Manifest template for creating the source distribution. 4 | # 5 | 6 | include AUTHORS 7 | include COPYING-LGPL 8 | include COPYING-MIT 9 | include ChangeLog 10 | include INSTALL.rst 11 | include MANIFEST.in 12 | include Makefile 13 | include pytest.ini 14 | include README.rst 15 | include RELEASE-NOTES.rst 16 | include doc/*.py 17 | include doc/*.rst 18 | include doc/docstrings/*.rst 19 | include doc/static/favicon.ico 20 | include examples/*.py 21 | include examples/quickstart/*.py 22 | include examples/tests/*.py 23 | include src/Makefile 24 | include src/docstrings.c 25 | include src/docstrings.h 26 | include src/easy.c 27 | include src/easycb.c 28 | include src/easyinfo.c 29 | include src/easyopt.c 30 | include src/easyperform.c 31 | include src/module.c 32 | include src/multi.c 33 | include src/oscompat.c 34 | include src/pycurl.h 35 | include src/pythoncompat.c 36 | include src/share.c 37 | include src/stringcompat.c 38 | include src/threadsupport.c 39 | include src/util.c 40 | include python/curl/*.py 41 | include requirements*.txt 42 | include tests/*.py 43 | include tests/certs/*.crt 44 | include tests/certs/*.key 45 | include tests/ext/*.sh 46 | include tests/fake-curl/* 47 | include tests/fake-curl/libcurl/* 48 | include tests/fixtures/form_submission.txt 49 | include tests/matrix/*.patch 50 | include tests/run.sh 51 | include tests/run-quickstart.sh 52 | include tests/vsftpd.conf 53 | include winbuild.py 54 | include winbuild/* 55 | exclude tests/fake-curl/libcurl/*.so 56 | -------------------------------------------------------------------------------- /doc/curl.rst: -------------------------------------------------------------------------------- 1 | curl Module Functionality 2 | ========================= 3 | 4 | .. automodule:: curl 5 | 6 | High Level Curl Object 7 | ---------------------- 8 | 9 | .. autoclass:: curl.Curl 10 | :members: 11 | -------------------------------------------------------------------------------- /doc/curlmultiobject.rst: -------------------------------------------------------------------------------- 1 | .. _curlmultiobject: 2 | 3 | CurlMulti Object 4 | ================ 5 | 6 | .. autoclass:: pycurl.CurlMulti 7 | 8 | CurlMulti objects have the following methods: 9 | 10 | .. automethod:: pycurl.CurlMulti.close 11 | 12 | .. automethod:: pycurl.CurlMulti.add_handle 13 | 14 | .. automethod:: pycurl.CurlMulti.remove_handle 15 | 16 | .. _multi-perform: 17 | .. automethod:: pycurl.CurlMulti.perform 18 | 19 | .. _multi-socket_action: 20 | .. automethod:: pycurl.CurlMulti.socket_action 21 | 22 | .. _multi-socket_all: 23 | .. automethod:: pycurl.CurlMulti.socket_all 24 | 25 | .. automethod:: pycurl.CurlMulti.setopt 26 | 27 | .. automethod:: pycurl.CurlMulti.fdset 28 | 29 | .. automethod:: pycurl.CurlMulti.select 30 | 31 | .. automethod:: pycurl.CurlMulti.info_read 32 | 33 | .. automethod:: pycurl.CurlMulti.timeout 34 | 35 | .. _multi-assign: 36 | .. automethod:: pycurl.CurlMulti.assign 37 | -------------------------------------------------------------------------------- /doc/curlobject.rst: -------------------------------------------------------------------------------- 1 | .. _curlobject: 2 | 3 | Curl Object 4 | =========== 5 | 6 | .. autoclass:: pycurl.Curl 7 | 8 | Curl objects have the following methods: 9 | 10 | .. automethod:: pycurl.Curl.close 11 | 12 | .. _setopt: 13 | .. automethod:: pycurl.Curl.setopt 14 | 15 | .. _perform: 16 | .. automethod:: pycurl.Curl.perform 17 | 18 | .. _perform_rb: 19 | .. automethod:: pycurl.Curl.perform_rb 20 | 21 | .. _perform_rs: 22 | .. automethod:: pycurl.Curl.perform_rs 23 | 24 | .. _getinfo: 25 | .. automethod:: pycurl.Curl.getinfo 26 | 27 | .. _getinfo_raw: 28 | .. automethod:: pycurl.Curl.getinfo_raw 29 | 30 | .. automethod:: pycurl.Curl.reset 31 | 32 | .. _unsetopt: 33 | .. automethod:: pycurl.Curl.unsetopt 34 | 35 | .. automethod:: pycurl.Curl.pause 36 | 37 | .. _errstr: 38 | .. automethod:: pycurl.Curl.errstr 39 | 40 | .. _errstr_raw: 41 | .. automethod:: pycurl.Curl.errstr_raw 42 | 43 | .. automethod:: pycurl.Curl.setopt_string 44 | -------------------------------------------------------------------------------- /doc/curlshareobject.rst: -------------------------------------------------------------------------------- 1 | .. _curlshareobject: 2 | 3 | CurlShare Object 4 | ================ 5 | 6 | .. autoclass:: pycurl.CurlShare 7 | 8 | CurlShare objects have the following methods: 9 | 10 | .. automethod:: pycurl.CurlShare.close 11 | 12 | .. automethod:: pycurl.CurlShare.setopt 13 | -------------------------------------------------------------------------------- /doc/docstrings/curl.rst: -------------------------------------------------------------------------------- 1 | Curl() -> New Curl object 2 | 3 | Creates a new :ref:`curlobject` which corresponds to a 4 | ``CURL`` handle in libcurl. Curl objects automatically set 5 | CURLOPT_VERBOSE to 0, CURLOPT_NOPROGRESS to 1, provide a default 6 | CURLOPT_USERAGENT and setup CURLOPT_ERRORBUFFER to point to a 7 | private error buffer. 8 | 9 | Implicitly calls :py:func:`pycurl.global_init` if the latter has not yet been called. 10 | -------------------------------------------------------------------------------- /doc/docstrings/curl_close.rst: -------------------------------------------------------------------------------- 1 | close() -> None 2 | 3 | Close handle and end curl session. 4 | 5 | Corresponds to `curl_easy_cleanup`_ in libcurl. This method is 6 | automatically called by pycurl when a Curl object no longer has any 7 | references to it, but can also be called explicitly. 8 | 9 | .. _curl_easy_cleanup: 10 | https://curl.haxx.se/libcurl/c/curl_easy_cleanup.html 11 | -------------------------------------------------------------------------------- /doc/docstrings/curl_duphandle.rst: -------------------------------------------------------------------------------- 1 | duphandle() -> Curl 2 | 3 | Clone a curl handle. This function will return a new curl handle, 4 | a duplicate, using all the options previously set in the input curl handle. 5 | Both handles can subsequently be used independently. 6 | 7 | The new handle will not inherit any state information, no connections, 8 | no SSL sessions and no cookies. It also will not inherit any share object 9 | states or options (it will be made as if SHARE was unset). 10 | 11 | Corresponds to `curl_easy_duphandle`_ in libcurl. 12 | 13 | Example usage:: 14 | 15 | import pycurl 16 | curl = pycurl.Curl() 17 | curl.setopt(pycurl.URL, "https://python.org") 18 | dup = curl.duphandle() 19 | curl.perform() 20 | dup.perform() 21 | 22 | .. _curl_easy_duphandle: 23 | https://curl.se/libcurl/c/curl_easy_duphandle.html 24 | -------------------------------------------------------------------------------- /doc/docstrings/curl_errstr.rst: -------------------------------------------------------------------------------- 1 | errstr() -> string 2 | 3 | Return the internal libcurl error buffer of this handle as a string. 4 | 5 | Return value is a ``str`` instance on all Python versions. 6 | On Python 3, error buffer data is decoded using Python's default encoding 7 | at the time of the call. If this decoding fails, ``UnicodeDecodeError`` is 8 | raised. Use :ref:`errstr_raw ` to retrieve the error buffer 9 | as a byte string in this case. 10 | 11 | On Python 2, ``errstr`` and ``errstr_raw`` behave identically. 12 | -------------------------------------------------------------------------------- /doc/docstrings/curl_errstr_raw.rst: -------------------------------------------------------------------------------- 1 | errstr_raw() -> byte string 2 | 3 | Return the internal libcurl error buffer of this handle as a byte string. 4 | 5 | Return value is a ``str`` instance on Python 2 and ``bytes`` instance 6 | on Python 3. Unlike :ref:`errstr_raw `, ``errstr_raw`` 7 | allows reading libcurl error buffer in Python 3 when its contents is not 8 | valid in Python's default encoding. 9 | 10 | On Python 2, ``errstr`` and ``errstr_raw`` behave identically. 11 | 12 | *Added in version 7.43.0.2.* 13 | -------------------------------------------------------------------------------- /doc/docstrings/curl_getinfo_raw.rst: -------------------------------------------------------------------------------- 1 | getinfo_raw(option) -> Result 2 | 3 | Extract and return information from a curl session, 4 | returning string data as byte strings. 5 | Corresponds to `curl_easy_getinfo`_ in libcurl. 6 | The ``getinfo_raw`` method should not be called unless 7 | ``perform`` has been called and finished. 8 | 9 | *option* is a constant corresponding to one of the 10 | ``CURLINFO_*`` constants in libcurl. Most option constant names match 11 | the respective ``CURLINFO_*`` constant names with the ``CURLINFO_`` prefix 12 | removed, for example ``CURLINFO_CONTENT_TYPE`` is accessible as 13 | ``pycurl.CONTENT_TYPE``. Exceptions to this rule are as follows: 14 | 15 | - ``CURLINFO_FILETIME`` is mapped as ``pycurl.INFO_FILETIME`` 16 | - ``CURLINFO_COOKIELIST`` is mapped as ``pycurl.INFO_COOKIELIST`` 17 | - ``CURLINFO_CERTINFO`` is mapped as ``pycurl.INFO_CERTINFO`` 18 | - ``CURLINFO_RTSP_CLIENT_CSEQ`` is mapped as ``pycurl.INFO_RTSP_CLIENT_CSEQ`` 19 | - ``CURLINFO_RTSP_CSEQ_RECV`` is mapped as ``pycurl.INFO_RTSP_CSEQ_RECV`` 20 | - ``CURLINFO_RTSP_SERVER_CSEQ`` is mapped as ``pycurl.INFO_RTSP_SERVER_CSEQ`` 21 | - ``CURLINFO_RTSP_SESSION_ID`` is mapped as ``pycurl.INFO_RTSP_SESSION_ID`` 22 | 23 | The type of return value depends on the option, as follows: 24 | 25 | - Options documented by libcurl to return an integer value return a 26 | Python integer (``long`` on Python 2, ``int`` on Python 3). 27 | - Options documented by libcurl to return a floating point value 28 | return a Python ``float``. 29 | - Options documented by libcurl to return a string value 30 | return a Python byte string (``str`` on Python 2, ``bytes`` on Python 3). 31 | The string contains whatever data libcurl returned. 32 | Use :ref:`getinfo ` to retrieve this data as a Unicode string on Python 3. 33 | - ``SSL_ENGINES`` and ``INFO_COOKIELIST`` return a list of byte strings. 34 | The same encoding caveats apply; use :ref:`getinfo ` to retrieve the 35 | data as a list of potentially Unicode strings. 36 | - ``INFO_CERTINFO`` returns a list with one element 37 | per certificate in the chain, starting with the leaf; each element is a 38 | sequence of *(key, value)* tuples where both ``key`` and ``value`` are 39 | byte strings. String encoding caveats apply; use :ref:`getinfo ` 40 | to retrieve 41 | certificate data as potentially Unicode strings. 42 | 43 | On Python 2, ``getinfo`` and ``getinfo_raw`` behave identically. 44 | 45 | Example usage:: 46 | 47 | import pycurl 48 | c = pycurl.Curl() 49 | c.setopt(pycurl.OPT_CERTINFO, 1) 50 | c.setopt(pycurl.URL, "https://python.org") 51 | c.setopt(pycurl.FOLLOWLOCATION, 1) 52 | c.perform() 53 | print(c.getinfo_raw(pycurl.HTTP_CODE)) 54 | # --> 200 55 | print(c.getinfo_raw(pycurl.EFFECTIVE_URL)) 56 | # --> b"https://www.python.org/" 57 | certinfo = c.getinfo_raw(pycurl.INFO_CERTINFO) 58 | print(certinfo) 59 | # --> [((b'Subject', b'C = AU, ST = Some-State, O = PycURL test suite, 60 | CN = localhost'), (b'Issuer', b'C = AU, ST = Some-State, 61 | O = PycURL test suite, OU = localhost, CN = localhost'), 62 | (b'Version', b'0'), ...)] 63 | 64 | 65 | Raises pycurl.error exception upon failure. 66 | 67 | *Added in version 7.43.0.2.* 68 | 69 | .. _curl_easy_getinfo: 70 | https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html 71 | -------------------------------------------------------------------------------- /doc/docstrings/curl_pause.rst: -------------------------------------------------------------------------------- 1 | pause(bitmask) -> None 2 | 3 | Pause or unpause a curl handle. Bitmask should be a value such as 4 | PAUSE_RECV or PAUSE_CONT. 5 | 6 | Corresponds to `curl_easy_pause`_ in libcurl. The argument should be 7 | derived from the ``PAUSE_RECV``, ``PAUSE_SEND``, ``PAUSE_ALL`` and 8 | ``PAUSE_CONT`` constants. 9 | 10 | Raises pycurl.error exception upon failure. 11 | 12 | .. _curl_easy_pause: https://curl.haxx.se/libcurl/c/curl_easy_pause.html 13 | -------------------------------------------------------------------------------- /doc/docstrings/curl_perform.rst: -------------------------------------------------------------------------------- 1 | perform() -> None 2 | 3 | Perform a file transfer. 4 | 5 | Corresponds to `curl_easy_perform`_ in libcurl. 6 | 7 | Raises pycurl.error exception upon failure. 8 | 9 | .. _curl_easy_perform: 10 | https://curl.haxx.se/libcurl/c/curl_easy_perform.html 11 | -------------------------------------------------------------------------------- /doc/docstrings/curl_perform_rb.rst: -------------------------------------------------------------------------------- 1 | perform_rb() -> response_body 2 | 3 | Perform a file transfer and return response body as a byte string. 4 | 5 | This method arranges for response body to be saved in a StringIO 6 | (Python 2) or BytesIO (Python 3) instance, then invokes :ref:`perform ` 7 | to perform the file transfer, then returns the value of the StringIO/BytesIO 8 | instance which is a ``str`` instance on Python 2 and ``bytes`` instance 9 | on Python 3. Errors during transfer raise ``pycurl.error`` exceptions 10 | just like in :ref:`perform `. 11 | 12 | Use :ref:`perform_rs ` to retrieve response body as a string 13 | (``str`` instance on both Python 2 and 3). 14 | 15 | Raises ``pycurl.error`` exception upon failure. 16 | 17 | *Added in version 7.43.0.2.* 18 | -------------------------------------------------------------------------------- /doc/docstrings/curl_perform_rs.rst: -------------------------------------------------------------------------------- 1 | perform_rs() -> response_body 2 | 3 | Perform a file transfer and return response body as a string. 4 | 5 | On Python 2, this method arranges for response body to be saved in a StringIO 6 | instance, then invokes :ref:`perform ` 7 | to perform the file transfer, then returns the value of the StringIO instance. 8 | This behavior is identical to :ref:`perform_rb `. 9 | 10 | On Python 3, this method arranges for response body to be saved in a BytesIO 11 | instance, then invokes :ref:`perform ` 12 | to perform the file transfer, then decodes the response body in Python's 13 | default encoding and returns the decoded body as a Unicode string 14 | (``str`` instance). *Note:* decoding happens after the transfer finishes, 15 | thus an encoding error implies the transfer/network operation succeeded. 16 | 17 | Any transfer errors raise ``pycurl.error`` exception, 18 | just like in :ref:`perform `. 19 | 20 | Use :ref:`perform_rb ` to retrieve response body as a byte 21 | string (``bytes`` instance on Python 3) without attempting to decode it. 22 | 23 | Raises ``pycurl.error`` exception upon failure. 24 | 25 | *Added in version 7.43.0.2.* 26 | -------------------------------------------------------------------------------- /doc/docstrings/curl_reset.rst: -------------------------------------------------------------------------------- 1 | reset() -> None 2 | 3 | Reset all options set on curl handle to default values, but preserves 4 | live connections, session ID cache, DNS cache, cookies, and shares. 5 | 6 | Corresponds to `curl_easy_reset`_ in libcurl. 7 | 8 | .. _curl_easy_reset: https://curl.haxx.se/libcurl/c/curl_easy_reset.html 9 | -------------------------------------------------------------------------------- /doc/docstrings/curl_set_ca_certs.rst: -------------------------------------------------------------------------------- 1 | set_ca_certs() -> None 2 | 3 | Load ca certs from provided unicode string. 4 | 5 | Note that certificates will be added only when cURL starts new connection. 6 | -------------------------------------------------------------------------------- /doc/docstrings/curl_setopt_string.rst: -------------------------------------------------------------------------------- 1 | setopt_string(option, value) -> None 2 | 3 | Set curl session option to a string value. 4 | 5 | This method allows setting string options that are not officially supported 6 | by PycURL, for example because they did not exist when the version of PycURL 7 | being used was released. 8 | :py:meth:`pycurl.Curl.setopt` should be used for setting options that 9 | PycURL knows about. 10 | 11 | **Warning:** No checking is performed that *option* does, in fact, 12 | expect a string value. Using this method incorrectly can crash the program 13 | and may lead to a security vulnerability. 14 | Furthermore, it is on the application to ensure that the *value* object 15 | does not get garbage collected while libcurl is using it. 16 | libcurl copies most string options but not all; one option whose value 17 | is not copied by libcurl is `CURLOPT_POSTFIELDS`_. 18 | 19 | *option* would generally need to be given as an integer literal rather than 20 | a symbolic constant. 21 | 22 | *value* can be a binary string or a Unicode string using ASCII code points, 23 | same as with string options given to PycURL elsewhere. 24 | 25 | Example setting URL via ``setopt_string``:: 26 | 27 | import pycurl 28 | c = pycurl.Curl() 29 | c.setopt_string(10002, "http://www.python.org/") 30 | 31 | .. _CURLOPT_POSTFIELDS: https://curl.haxx.se/libcurl/c/CURLOPT_POSTFIELDS.html 32 | -------------------------------------------------------------------------------- /doc/docstrings/curl_unsetopt.rst: -------------------------------------------------------------------------------- 1 | unsetopt(option) -> None 2 | 3 | Reset curl session option to its default value. 4 | 5 | Only some curl options may be reset via this method. 6 | 7 | libcurl does not provide a general way to reset a single option to its default value; 8 | :py:meth:`pycurl.Curl.reset` resets all options to their default values, 9 | otherwise :py:meth:`pycurl.Curl.setopt` must be called with whatever value 10 | is the default. For convenience, PycURL provides this unsetopt method 11 | to reset some of the options to their default values. 12 | 13 | Raises pycurl.error exception on failure. 14 | 15 | ``c.unsetopt(option)`` is equivalent to ``c.setopt(option, None)``. 16 | -------------------------------------------------------------------------------- /doc/docstrings/multi.rst: -------------------------------------------------------------------------------- 1 | CurlMulti() -> New CurlMulti object 2 | 3 | Creates a new :ref:`curlmultiobject` which corresponds to 4 | a ``CURLM`` handle in libcurl. 5 | -------------------------------------------------------------------------------- /doc/docstrings/multi_add_handle.rst: -------------------------------------------------------------------------------- 1 | add_handle(Curl object) -> None 2 | 3 | Corresponds to `curl_multi_add_handle`_ in libcurl. This method adds an 4 | existing and valid Curl object to the CurlMulti object. 5 | 6 | *Changed in version 7.43.0.2:* add_handle now ensures that the Curl object 7 | is not garbage collected while it is being used by a CurlMulti object. 8 | Previously application had to maintain an outstanding reference to the Curl 9 | object to keep it from being garbage collected. 10 | 11 | .. _curl_multi_add_handle: 12 | https://curl.haxx.se/libcurl/c/curl_multi_add_handle.html 13 | -------------------------------------------------------------------------------- /doc/docstrings/multi_assign.rst: -------------------------------------------------------------------------------- 1 | assign(sock_fd, object) -> None 2 | 3 | Creates an association in the multi handle between the given socket and 4 | a private object in the application. 5 | Corresponds to `curl_multi_assign`_ in libcurl. 6 | 7 | .. _curl_multi_assign: https://curl.haxx.se/libcurl/c/curl_multi_assign.html 8 | -------------------------------------------------------------------------------- /doc/docstrings/multi_close.rst: -------------------------------------------------------------------------------- 1 | close() -> None 2 | 3 | Corresponds to `curl_multi_cleanup`_ in libcurl. This method is 4 | automatically called by pycurl when a CurlMulti object no longer has any 5 | references to it, but can also be called explicitly. 6 | 7 | .. _curl_multi_cleanup: 8 | https://curl.haxx.se/libcurl/c/curl_multi_cleanup.html 9 | -------------------------------------------------------------------------------- /doc/docstrings/multi_fdset.rst: -------------------------------------------------------------------------------- 1 | fdset() -> tuple of lists with active file descriptors, readable, writeable, exceptions 2 | 3 | Returns a tuple of three lists that can be passed to the select.select() method. 4 | 5 | Corresponds to `curl_multi_fdset`_ in libcurl. This method extracts the 6 | file descriptor information from a CurlMulti object. The returned lists can 7 | be used with the ``select`` module to poll for events. 8 | 9 | Example usage:: 10 | 11 | import pycurl 12 | c = pycurl.Curl() 13 | c.setopt(pycurl.URL, "https://curl.haxx.se") 14 | m = pycurl.CurlMulti() 15 | m.add_handle(c) 16 | while 1: 17 | ret, num_handles = m.perform() 18 | if ret != pycurl.E_CALL_MULTI_PERFORM: break 19 | while num_handles: 20 | apply(select.select, m.fdset() + (1,)) 21 | while 1: 22 | ret, num_handles = m.perform() 23 | if ret != pycurl.E_CALL_MULTI_PERFORM: break 24 | 25 | .. _curl_multi_fdset: 26 | https://curl.haxx.se/libcurl/c/curl_multi_fdset.html 27 | -------------------------------------------------------------------------------- /doc/docstrings/multi_info_read.rst: -------------------------------------------------------------------------------- 1 | info_read([max_objects]) -> tuple(number of queued messages, a list of successful objects, a list of failed objects) 2 | 3 | Corresponds to the `curl_multi_info_read`_ function in libcurl. 4 | 5 | This method extracts at most *max* messages from the multi stack and returns 6 | them in two lists. The first list contains the handles which completed 7 | successfully and the second list contains a tuple *(curl object, curl error 8 | number, curl error message)* for each failed curl object. The curl error 9 | message is returned as a Python string which is decoded from the curl error 10 | string using the `surrogateescape`_ error handler. The number of 11 | queued messages after this method has been called is also returned. 12 | 13 | .. _curl_multi_info_read: 14 | https://curl.haxx.se/libcurl/c/curl_multi_info_read.html 15 | 16 | .. _surrogateescape: 17 | https://www.python.org/dev/peps/pep-0383/ 18 | -------------------------------------------------------------------------------- /doc/docstrings/multi_perform.rst: -------------------------------------------------------------------------------- 1 | perform() -> tuple of status and the number of active Curl objects 2 | 3 | Corresponds to `curl_multi_perform`_ in libcurl. 4 | 5 | .. _curl_multi_perform: 6 | https://curl.haxx.se/libcurl/c/curl_multi_perform.html 7 | -------------------------------------------------------------------------------- /doc/docstrings/multi_remove_handle.rst: -------------------------------------------------------------------------------- 1 | remove_handle(Curl object) -> None 2 | 3 | Corresponds to `curl_multi_remove_handle`_ in libcurl. This method 4 | removes an existing and valid Curl object from the CurlMulti object. 5 | 6 | .. _curl_multi_remove_handle: 7 | https://curl.haxx.se/libcurl/c/curl_multi_remove_handle.html 8 | -------------------------------------------------------------------------------- /doc/docstrings/multi_select.rst: -------------------------------------------------------------------------------- 1 | select([timeout]) -> number of ready file descriptors or 0 on timeout 2 | 3 | Returns result from doing a select() on the curl multi file descriptor 4 | with the given timeout. 5 | 6 | This is a convenience function which simplifies the combined use of 7 | ``fdset()`` and the ``select`` module. 8 | 9 | Example usage:: 10 | 11 | import pycurl 12 | c = pycurl.Curl() 13 | c.setopt(pycurl.URL, "https://curl.haxx.se") 14 | m = pycurl.CurlMulti() 15 | m.add_handle(c) 16 | while 1: 17 | ret, num_handles = m.perform() 18 | if ret != pycurl.E_CALL_MULTI_PERFORM: break 19 | while num_handles: 20 | ret = m.select(1.0) 21 | if ret == 0: continue 22 | while 1: 23 | ret, num_handles = m.perform() 24 | if ret != pycurl.E_CALL_MULTI_PERFORM: break 25 | -------------------------------------------------------------------------------- /doc/docstrings/multi_setopt.rst: -------------------------------------------------------------------------------- 1 | setopt(option, value) -> None 2 | 3 | Set curl multi option. Corresponds to `curl_multi_setopt`_ in libcurl. 4 | 5 | *option* specifies which option to set. PycURL defines constants 6 | corresponding to ``CURLMOPT_*`` constants in libcurl, except that 7 | the ``CURLMOPT_`` prefix is replaced with ``M_`` prefix. 8 | For example, ``CURLMOPT_PIPELINING`` is 9 | exposed in PycURL as ``pycurl.M_PIPELINING``. For convenience, ``CURLMOPT_*`` 10 | constants are also exposed on CurlMulti objects:: 11 | 12 | import pycurl 13 | m = pycurl.CurlMulti() 14 | m.setopt(pycurl.M_PIPELINING, 1) 15 | # Same as: 16 | m.setopt(m.M_PIPELINING, 1) 17 | 18 | *value* specifies the value to set the option to. Different options accept 19 | values of different types: 20 | 21 | - Options specified by `curl_multi_setopt`_ as accepting ``1`` or an 22 | integer value accept Python integers, long integers (on Python 2.x) and 23 | booleans:: 24 | 25 | m.setopt(pycurl.M_PIPELINING, True) 26 | m.setopt(pycurl.M_PIPELINING, 1) 27 | # Python 2.x only: 28 | m.setopt(pycurl.M_PIPELINING, 1L) 29 | 30 | - ``*FUNCTION`` options accept a function. Supported callbacks are 31 | ``CURLMOPT_SOCKETFUNCTION`` AND ``CURLMOPT_TIMERFUNCTION``. Please refer to 32 | the PycURL test suite for examples on using the callbacks. 33 | 34 | Raises TypeError when the option value is not of a type accepted by the 35 | respective option, and pycurl.error exception when libcurl rejects the 36 | option or its value. 37 | 38 | .. _curl_multi_setopt: https://curl.haxx.se/libcurl/c/curl_multi_setopt.html 39 | -------------------------------------------------------------------------------- /doc/docstrings/multi_socket_action.rst: -------------------------------------------------------------------------------- 1 | socket_action(sock_fd, ev_bitmask) -> (result, num_running_handles) 2 | 3 | Returns result from doing a socket_action() on the curl multi file descriptor 4 | with the given timeout. 5 | Corresponds to `curl_multi_socket_action`_ in libcurl. 6 | 7 | The return value is a two-element tuple. The first element is the return 8 | value of the underlying ``curl_multi_socket_action`` function, and it is 9 | always zero (``CURLE_OK``) because any other return value would cause 10 | ``socket_action`` to raise an exception. The second element is the number of 11 | running easy handles within this multi handle. When the number of running 12 | handles reaches zero, all transfers have completed. Note that if the number 13 | of running handles has decreased by one compared to the previous invocation, 14 | this is not mean the handle corresponding to the ``sock_fd`` provided as 15 | the argument to this function was the completed handle. 16 | 17 | .. _curl_multi_socket_action: https://curl.haxx.se/libcurl/c/curl_multi_socket_action.html 18 | -------------------------------------------------------------------------------- /doc/docstrings/multi_socket_all.rst: -------------------------------------------------------------------------------- 1 | socket_all() -> tuple 2 | 3 | Returns result from doing a socket_all() on the curl multi file descriptor 4 | with the given timeout. 5 | -------------------------------------------------------------------------------- /doc/docstrings/multi_timeout.rst: -------------------------------------------------------------------------------- 1 | timeout() -> int 2 | 3 | Returns how long to wait for action before proceeding. 4 | Corresponds to `curl_multi_timeout`_ in libcurl. 5 | 6 | .. _curl_multi_timeout: https://curl.haxx.se/libcurl/c/curl_multi_timeout.html 7 | -------------------------------------------------------------------------------- /doc/docstrings/pycurl_global_cleanup.rst: -------------------------------------------------------------------------------- 1 | global_cleanup() -> None 2 | 3 | Cleanup curl environment. 4 | 5 | Corresponds to `curl_global_cleanup`_ in libcurl. 6 | 7 | .. _curl_global_cleanup: https://curl.haxx.se/libcurl/c/curl_global_cleanup.html 8 | -------------------------------------------------------------------------------- /doc/docstrings/pycurl_global_init.rst: -------------------------------------------------------------------------------- 1 | global_init(option) -> None 2 | 3 | Initialize curl environment. 4 | 5 | *option* is one of the constants pycurl.GLOBAL_SSL, pycurl.GLOBAL_WIN32, 6 | pycurl.GLOBAL_ALL, pycurl.GLOBAL_NOTHING, pycurl.GLOBAL_DEFAULT. 7 | 8 | Corresponds to `curl_global_init`_ in libcurl. 9 | 10 | .. _curl_global_init: https://curl.haxx.se/libcurl/c/curl_global_init.html 11 | -------------------------------------------------------------------------------- /doc/docstrings/pycurl_module.rst: -------------------------------------------------------------------------------- 1 | This module implements an interface to the cURL library. 2 | 3 | Types: 4 | 5 | Curl() -> New object. Create a new curl object. 6 | CurlMulti() -> New object. Create a new curl multi object. 7 | CurlShare() -> New object. Create a new curl share object. 8 | 9 | Functions: 10 | 11 | global_init(option) -> None. Initialize curl environment. 12 | global_cleanup() -> None. Cleanup curl environment. 13 | version_info() -> tuple. Return version information. 14 | -------------------------------------------------------------------------------- /doc/docstrings/pycurl_version_info.rst: -------------------------------------------------------------------------------- 1 | version_info() -> tuple 2 | 3 | Returns a 12-tuple with the version info. 4 | 5 | Corresponds to `curl_version_info`_ in libcurl. Returns a tuple of 6 | information which is similar to the ``curl_version_info_data`` struct 7 | returned by ``curl_version_info()`` in libcurl. 8 | 9 | Example usage:: 10 | 11 | >>> import pycurl 12 | >>> pycurl.version_info() 13 | (3, '7.33.0', 467200, 'amd64-portbld-freebsd9.1', 33436, 'OpenSSL/0.9.8x', 14 | 0, '1.2.7', ('dict', 'file', 'ftp', 'ftps', 'gopher', 'http', 'https', 15 | 'imap', 'imaps', 'pop3', 'pop3s', 'rtsp', 'smtp', 'smtps', 'telnet', 16 | 'tftp'), None, 0, None) 17 | 18 | .. _curl_version_info: https://curl.haxx.se/libcurl/c/curl_version_info.html 19 | -------------------------------------------------------------------------------- /doc/docstrings/share.rst: -------------------------------------------------------------------------------- 1 | CurlShare() -> New CurlShare object 2 | 3 | Creates a new :ref:`curlshareobject` which corresponds to a 4 | ``CURLSH`` handle in libcurl. CurlShare objects is what you pass as an 5 | argument to the SHARE option on :ref:`Curl objects `. 6 | -------------------------------------------------------------------------------- /doc/docstrings/share_close.rst: -------------------------------------------------------------------------------- 1 | close() -> None 2 | 3 | Close shared handle. 4 | 5 | Corresponds to `curl_share_cleanup`_ in libcurl. This method is 6 | automatically called by pycurl when a CurlShare object no longer has 7 | any references to it, but can also be called explicitly. 8 | 9 | .. _curl_share_cleanup: 10 | https://curl.haxx.se/libcurl/c/curl_share_cleanup.html 11 | -------------------------------------------------------------------------------- /doc/docstrings/share_setopt.rst: -------------------------------------------------------------------------------- 1 | setopt(option, value) -> None 2 | 3 | Set curl share option. 4 | 5 | Corresponds to `curl_share_setopt`_ in libcurl, where *option* is 6 | specified with the ``CURLSHOPT_*`` constants in libcurl, except that the 7 | ``CURLSHOPT_`` prefix has been changed to ``SH_``. Currently, *value* must be 8 | one of: ``LOCK_DATA_COOKIE``, ``LOCK_DATA_DNS``, ``LOCK_DATA_SSL_SESSION`` or 9 | ``LOCK_DATA_CONNECT``. 10 | 11 | Example usage:: 12 | 13 | import pycurl 14 | curl = pycurl.Curl() 15 | s = pycurl.CurlShare() 16 | s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_COOKIE) 17 | s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_DNS) 18 | curl.setopt(pycurl.URL, 'https://curl.haxx.se') 19 | curl.setopt(pycurl.SHARE, s) 20 | curl.perform() 21 | curl.close() 22 | 23 | Raises pycurl.error exception upon failure. 24 | 25 | .. _curl_share_setopt: 26 | https://curl.haxx.se/libcurl/c/curl_share_setopt.html 27 | -------------------------------------------------------------------------------- /doc/files.rst: -------------------------------------------------------------------------------- 1 | File Handling 2 | ============= 3 | 4 | In PycURL 7.19.0.3 and below, ``CURLOPT_READDATA``, ``CURLOPT_WRITEDATA`` and 5 | ``CURLOPT_WRITEHEADER`` options accepted file objects and directly passed 6 | the underlying C library ``FILE`` pointers to libcurl. 7 | 8 | Python 3 no longer implements files as C library ``FILE`` objects. 9 | In PycURL 7.19.3 and above, when running on Python 3, these options 10 | are implemented as calls to ``CURLOPT_READFUNCTION``, ``CURLOPT_WRITEFUNCTION`` 11 | and ``CURLOPT_HEADERFUNCTION``, respectively, with the write method of the 12 | Python file object as the parameter. As a result, any Python file-like 13 | object implementing a ``read`` method can be passed to ``CURLOPT_READDATA``, 14 | and any Python file-like object implementing a ``write`` method can be 15 | passed to ``CURLOPT_WRITEDATA`` or ``CURLOPT_WRITEHEADER`` options. 16 | 17 | When running PycURL 7.19.3 and above on Python 2, the old behavior of 18 | passing ``FILE`` pointers to libcurl remains when a true file object is given 19 | to ``CURLOPT_READDATA``, ``CURLOPT_WRITEDATA`` and ``CURLOPT_WRITEHEADER`` 20 | options. For consistency with Python 3 behavior these options also accept 21 | file-like objects implementing a ``read`` or ``write`` method, as appropriate, 22 | as arguments, in which case the Python 3 code path is used converting these 23 | options to ``CURLOPT_*FUNCTION`` option calls. 24 | 25 | Files given to PycURL as arguments to ``CURLOPT_READDATA``, 26 | ``CURLOPT_WRITEDATA`` or ``CURLOPT_WRITEHEADER`` must be opened for reading or 27 | writing in binary mode. Files opened in text mode (without ``"b"`` flag to 28 | ``open()``) expect string objects and reading from or writing to them from 29 | PycURL will fail. Similarly when passing ``f.write`` method of an open file to 30 | ``CURLOPT_WRITEFUNCTION`` or ``CURLOPT_HEADERFUNCTION``, or ``f.read`` to 31 | ``CURLOPT_READFUNCTION``, the file must have been be opened in binary mode. 32 | -------------------------------------------------------------------------------- /doc/install.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../INSTALL.rst 2 | -------------------------------------------------------------------------------- /doc/internals.rst: -------------------------------------------------------------------------------- 1 | Internals 2 | ========= 3 | 4 | Cleanup sequence: 5 | 6 | x=curl/multi/share 7 | 8 | x.close() -> do_x_close -> util_x_close 9 | del x -> do_x_dealloc -> util_x_close 10 | 11 | do_* functions are directly invoked by user code. 12 | They check pycurl object state. 13 | 14 | util_* functions are only invoked by other pycurl C functions. 15 | They do not check pycurl object state. 16 | -------------------------------------------------------------------------------- /doc/pycurl.rst: -------------------------------------------------------------------------------- 1 | pycurl Module Functionality 2 | =========================== 3 | 4 | .. module:: pycurl 5 | 6 | .. autofunction:: pycurl.global_init 7 | 8 | .. autofunction:: pycurl.global_cleanup 9 | 10 | .. data:: version 11 | 12 | This is a string with version information on libcurl, corresponding to 13 | `curl_version`_ in libcurl. 14 | 15 | Example usage: 16 | 17 | :: 18 | 19 | >>> import pycurl 20 | >>> pycurl.version 21 | 'PycURL/7.19.3 libcurl/7.33.0 OpenSSL/0.9.8x zlib/1.2.7' 22 | 23 | .. autofunction:: pycurl.version_info 24 | 25 | .. autoclass:: pycurl.Curl 26 | :noindex: 27 | 28 | .. autoclass:: pycurl.CurlMulti 29 | :noindex: 30 | 31 | .. autoclass:: pycurl.CurlShare 32 | :noindex: 33 | 34 | .. _curl_version: https://curl.haxx.se/libcurl/c/curl_version.html 35 | -------------------------------------------------------------------------------- /doc/release-notes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../RELEASE-NOTES.rst 2 | -------------------------------------------------------------------------------- /doc/release-process.rst: -------------------------------------------------------------------------------- 1 | Release Process 2 | =============== 3 | 4 | 1. Ensure changelog is up to date with commits in master. 5 | 2. Run ``python setup.py authors`` and review the updated AUTHORS file. 6 | 3. Run ``git shortlog REL_...`` and add new contributors 7 | missed by the authors script to AUTHORS. 8 | 4. Run ``python setup.py manifest``, check that none of the listed files 9 | should be in MANIFEST.in. 10 | 5. Check ``get_data_files()`` in ``setup.py`` to see if any new files should 11 | be included in binary distributions. 12 | 6. Make sure Travis and AppVeyor are green for master. 13 | 7. Update version numbers in: 14 | - Changelog (also record release date) 15 | - doc/conf.py 16 | - setup.py 17 | - winbuild.py 18 | 8. Update copyright years if necessary. 19 | 9. Draft release notes, add to RELEASE-NOTES.rst. 20 | 10. ``make gen docs``. 21 | 11. ``python setup.py sdist``. 22 | 12. Manually test install the built package. 23 | 13. Build windows packages using winbuild.py. 24 | 14. Add sdist and windows packages to downloads repo on github. 25 | 15. Tag the new version. 26 | 16. Upload source distribution to pypi using twine. 27 | 17. Upload windows wheels to pypi using twine. 28 | 18. Upload windows exe installers to pypi using twine. 29 | 19. Upload release files to bintray. 30 | 20. Push tag to github pycurl repo. 31 | 21. Generate and upload documentation to web site. 32 | 22. Update web site home page. 33 | 23. Announce release on mailing list. 34 | -------------------------------------------------------------------------------- /doc/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycq0125/pycurl/7b5e12c80cc1ed4578c7276545a7b2be2e4dcc4b/doc/static/favicon.ico -------------------------------------------------------------------------------- /doc/thread-safety.rst: -------------------------------------------------------------------------------- 1 | .. _thread-safety: 2 | 3 | Thread Safety 4 | ============= 5 | 6 | Per `libcurl thread safety documentation`_, libcurl is thread-safe but 7 | has no internal thread synchronization. 8 | 9 | For Python programs using PycURL, this means: 10 | 11 | * Accessing the same PycURL object from different threads is OK when 12 | this object is not involved in active transfers, as Python internally 13 | has a Global Interpreter Lock and only one operating system thread can 14 | be executing Python code at a time. 15 | 16 | * Accessing a PycURL object that is involved in an active transfer from 17 | Python code *inside a libcurl callback for the PycURL object in question* 18 | is OK, because PycURL takes out the appropriate locks. 19 | 20 | * Accessing a PycURL object that is involved in an active transfer from 21 | Python code *outside of a libcurl callback for the PycURL object in question* 22 | is unsafe. 23 | 24 | PycURL handles the necessary SSL locks for OpenSSL/LibreSSL/BoringSSL, 25 | GnuTLS, NSS, mbedTLS and wolfSSL. 26 | 27 | A special situation exists when libcurl uses the standard C library 28 | name resolver (i.e., not threaded nor c-ares resolver). By default libcurl 29 | uses signals for timeouts with the C library resolver, and signals do not 30 | work properly in multi-threaded programs. When using PycURL objects from 31 | multiple Python threads ``NOSIGNAL`` option `must be given`_. 32 | 33 | .. _libcurl thread safety documentation: https://curl.haxx.se/libcurl/c/threadsafe.html 34 | .. _must be given: https://github.com/curl/curl/issues/1003 35 | -------------------------------------------------------------------------------- /doc/unimplemented.rst: -------------------------------------------------------------------------------- 1 | Unimplemented Options And Constants 2 | =================================== 3 | 4 | PycURL intentionally does not expose some of the libcurl options and constants. 5 | This document explains libcurl symbols that were omitted from PycURL. 6 | 7 | 8 | ``*DATA`` options 9 | ----------------- 10 | 11 | In libcurl, the ``*aDATA`` options set *client data* for various callbacks. 12 | Each callback has a corresponding ``*DATA`` option. 13 | 14 | In Python - a language with closures - such options are unnecessary. 15 | For example, the following code invokes an instance's ``write`` method 16 | which has full access to its class instance:: 17 | 18 | class Writer(object): 19 | def __init__(self): 20 | self.foo = True 21 | 22 | def write(chunk): 23 | # can use self.foo 24 | 25 | writer = Writer() 26 | curl = pycurl.Curl() 27 | curl.setopt(curl.WRITEFUNCTION, writer.write) 28 | 29 | As of version 7.19.3, PycURL does implement three ``*DATA`` options for 30 | convenience: 31 | ``WRITEDATA``, ``HEADERDATA`` and ``READDATA``. These are equivalent to 32 | setting the respective callback option with either a ``write`` or ``read`` 33 | method, as appropriate:: 34 | 35 | # equivalent pairs: 36 | curl.setopt(curl.WRITEDATA, writer) 37 | curl.setopt(curl.WRITEFUNCTION, writer.write) 38 | 39 | curl.setopt(curl.HEADERDATA, writer) 40 | curl.setopt(curl.HEADERFUNCTION, writer.write) 41 | 42 | curl.setopt(curl.READDATA, reader) 43 | curl.setopt(curl.READFUNCTION, reader.read) 44 | 45 | 46 | ``CURLINFO_TLS_SESSION`` 47 | ------------------------ 48 | 49 | It is unclear how the SSL context should be exposed to Python code. 50 | This option can be implemented if it finds a use case. 51 | 52 | 53 | 54 | Undocumented symbols 55 | -------------------- 56 | 57 | Some symbols are present in libcurl's `symbols in versions`_ document but 58 | are not documented by libcurl. These symbols are not implemented by PycURL. 59 | 60 | As of this writing, the following symbols are thusly omitted: 61 | 62 | - ``CURLPAUSE_RECV_CONT`` 63 | - ``CURLPAUSE_SEND_CONT`` 64 | 65 | .. _symbols in versions: https://curl.haxx.se/libcurl/c/symbols-in-versions.html 66 | -------------------------------------------------------------------------------- /examples/basicfirst.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | import sys 5 | import pycurl 6 | 7 | PY3 = sys.version_info[0] > 2 8 | 9 | 10 | class Test: 11 | def __init__(self): 12 | self.contents = '' 13 | if PY3: 14 | self.contents = self.contents.encode('ascii') 15 | 16 | def body_callback(self, buf): 17 | self.contents = self.contents + buf 18 | 19 | 20 | sys.stderr.write("Testing %s\n" % pycurl.version) 21 | 22 | t = Test() 23 | c = pycurl.Curl() 24 | c.setopt(c.URL, 'https://curl.haxx.se/dev/') 25 | c.setopt(c.WRITEFUNCTION, t.body_callback) 26 | c.perform() 27 | c.close() 28 | 29 | print(t.contents) 30 | -------------------------------------------------------------------------------- /examples/file_upload.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import os, sys 6 | import pycurl 7 | 8 | # Class which holds a file reference and the read callback 9 | class FileReader: 10 | def __init__(self, fp): 11 | self.fp = fp 12 | def read_callback(self, size): 13 | return self.fp.read(size) 14 | 15 | # Check commandline arguments 16 | if len(sys.argv) < 3: 17 | print("Usage: %s " % sys.argv[0]) 18 | raise SystemExit 19 | url = sys.argv[1] 20 | filename = sys.argv[2] 21 | 22 | if not os.path.exists(filename): 23 | print("Error: the file '%s' does not exist" % filename) 24 | raise SystemExit 25 | 26 | # Initialize pycurl 27 | c = pycurl.Curl() 28 | c.setopt(pycurl.URL, url) 29 | c.setopt(pycurl.UPLOAD, 1) 30 | 31 | # Two versions with the same semantics here, but the filereader version 32 | # is useful when you have to process the data which is read before returning 33 | if 1: 34 | c.setopt(pycurl.READFUNCTION, FileReader(open(filename, 'rb')).read_callback) 35 | else: 36 | c.setopt(pycurl.READFUNCTION, open(filename, 'rb').read) 37 | 38 | # Set size of file to be uploaded. 39 | filesize = os.path.getsize(filename) 40 | c.setopt(pycurl.INFILESIZE, filesize) 41 | 42 | # Start transfer 43 | print('Uploading file %s to url %s' % (filename, url)) 44 | c.perform() 45 | c.close() 46 | -------------------------------------------------------------------------------- /examples/opensocketexception.py: -------------------------------------------------------------------------------- 1 | # Exposing rich exception information from callbacks example 2 | 3 | import pycurl, random, socket 4 | 5 | class ConnectionRejected(Exception): 6 | pass 7 | 8 | def opensocket(curl, purpose, curl_address): 9 | if random.random() < 0.5: 10 | curl.exception = ConnectionRejected('Rejecting connection attempt in opensocket callback') 11 | return pycurl.SOCKET_BAD 12 | 13 | family, socktype, protocol, address = curl_address 14 | s = socket.socket(family, socktype, protocol) 15 | s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 16 | return s 17 | 18 | c = pycurl.Curl() 19 | c.setopt(c.URL, 'http://pycurl.io') 20 | c.exception = None 21 | c.setopt(c.OPENSOCKETFUNCTION, 22 | lambda purpose, address: opensocket(c, purpose, address)) 23 | 24 | try: 25 | c.perform() 26 | except pycurl.error as e: 27 | if e.args[0] == pycurl.E_COULDNT_CONNECT and c.exception: 28 | print(c.exception) 29 | else: 30 | print(e) 31 | -------------------------------------------------------------------------------- /examples/quickstart/file_upload_buffer.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | 7 | c = pycurl.Curl() 8 | c.setopt(c.URL, 'https://httpbin.org/post') 9 | 10 | c.setopt(c.HTTPPOST, [ 11 | ('fileupload', ( 12 | c.FORM_BUFFER, 'readme.txt', 13 | c.FORM_BUFFERPTR, 'This is a fancy readme file', 14 | )), 15 | ]) 16 | 17 | c.perform() 18 | c.close() 19 | -------------------------------------------------------------------------------- /examples/quickstart/file_upload_real.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | 7 | c = pycurl.Curl() 8 | c.setopt(c.URL, 'https://httpbin.org/post') 9 | 10 | c.setopt(c.HTTPPOST, [ 11 | ('fileupload', ( 12 | # upload the contents of this file 13 | c.FORM_FILE, __file__, 14 | )), 15 | ]) 16 | 17 | c.perform() 18 | c.close() 19 | -------------------------------------------------------------------------------- /examples/quickstart/file_upload_real_fancy.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | 7 | c = pycurl.Curl() 8 | c.setopt(c.URL, 'https://httpbin.org/post') 9 | 10 | c.setopt(c.HTTPPOST, [ 11 | ('fileupload', ( 12 | # upload the contents of this file 13 | c.FORM_FILE, __file__, 14 | # specify a different file name for the upload 15 | c.FORM_FILENAME, 'helloworld.py', 16 | # specify a different content type 17 | c.FORM_CONTENTTYPE, 'application/x-python', 18 | )), 19 | ]) 20 | 21 | c.perform() 22 | c.close() 23 | -------------------------------------------------------------------------------- /examples/quickstart/follow_redirect.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | 7 | c = pycurl.Curl() 8 | # Redirects to https://www.python.org/. 9 | c.setopt(c.URL, 'http://www.python.org/') 10 | # Follow redirect. 11 | c.setopt(c.FOLLOWLOCATION, True) 12 | c.perform() 13 | c.close() 14 | -------------------------------------------------------------------------------- /examples/quickstart/form_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | try: 7 | # python 3 8 | from urllib.parse import urlencode 9 | except ImportError: 10 | # python 2 11 | from urllib import urlencode 12 | 13 | c = pycurl.Curl() 14 | c.setopt(c.URL, 'https://httpbin.org/post') 15 | 16 | post_data = {'field': 'value'} 17 | # Form data must be provided already urlencoded. 18 | postfields = urlencode(post_data) 19 | # Sets request method to POST, 20 | # Content-Type header to application/x-www-form-urlencoded 21 | # and data to send in request body. 22 | c.setopt(c.POSTFIELDS, postfields) 23 | 24 | c.perform() 25 | c.close() 26 | -------------------------------------------------------------------------------- /examples/quickstart/get.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | try: 7 | from io import BytesIO 8 | except ImportError: 9 | from StringIO import StringIO as BytesIO 10 | 11 | buffer = BytesIO() 12 | c = pycurl.Curl() 13 | c.setopt(c.URL, 'http://pycurl.io/') 14 | c.setopt(c.WRITEDATA, buffer) 15 | # For older PycURL versions: 16 | #c.setopt(c.WRITEFUNCTION, buffer.write) 17 | c.perform() 18 | c.close() 19 | 20 | body = buffer.getvalue() 21 | # Body is a string on Python 2 and a byte string on Python 3. 22 | # If we know the encoding, we can always decode the body and 23 | # end up with a Unicode string. 24 | print(body.decode('iso-8859-1')) 25 | -------------------------------------------------------------------------------- /examples/quickstart/get_python2.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | from StringIO import StringIO 7 | 8 | buffer = StringIO() 9 | c = pycurl.Curl() 10 | c.setopt(c.URL, 'http://pycurl.io/') 11 | c.setopt(c.WRITEDATA, buffer) 12 | # For older PycURL versions: 13 | #c.setopt(c.WRITEFUNCTION, buffer.write) 14 | c.perform() 15 | c.close() 16 | 17 | body = buffer.getvalue() 18 | # Body is a string in some encoding. 19 | # In Python 2, we can print it without knowing what the encoding is. 20 | print(body) 21 | -------------------------------------------------------------------------------- /examples/quickstart/get_python2_https.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import certifi 7 | from StringIO import StringIO 8 | 9 | buffer = StringIO() 10 | c = pycurl.Curl() 11 | c.setopt(c.URL, 'http://pycurl.io/') 12 | c.setopt(c.WRITEDATA, buffer) 13 | # For older PycURL versions: 14 | #c.setopt(c.WRITEFUNCTION, buffer.write) 15 | c.setopt(c.CAINFO, certifi.where()) 16 | c.perform() 17 | c.close() 18 | 19 | body = buffer.getvalue() 20 | # Body is a string in some encoding. 21 | # In Python 2, we can print it without knowing what the encoding is. 22 | print(body) 23 | -------------------------------------------------------------------------------- /examples/quickstart/get_python3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | from io import BytesIO 7 | 8 | buffer = BytesIO() 9 | c = pycurl.Curl() 10 | c.setopt(c.URL, 'http://pycurl.io/') 11 | c.setopt(c.WRITEDATA, buffer) 12 | c.perform() 13 | c.close() 14 | 15 | body = buffer.getvalue() 16 | # Body is a byte string. 17 | # We have to know the encoding in order to print it to a text file 18 | # such as standard output. 19 | print(body.decode('iso-8859-1')) 20 | -------------------------------------------------------------------------------- /examples/quickstart/get_python3_https.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import certifi 7 | from io import BytesIO 8 | 9 | buffer = BytesIO() 10 | c = pycurl.Curl() 11 | c.setopt(c.URL, 'http://pycurl.io/') 12 | c.setopt(c.WRITEDATA, buffer) 13 | c.setopt(c.CAINFO, certifi.where()) 14 | c.perform() 15 | c.close() 16 | 17 | body = buffer.getvalue() 18 | # Body is a byte string. 19 | # We have to know the encoding in order to print it to a text file 20 | # such as standard output. 21 | print(body.decode('iso-8859-1')) 22 | -------------------------------------------------------------------------------- /examples/quickstart/put_buffer.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | try: 7 | from io import BytesIO 8 | except ImportError: 9 | from StringIO import StringIO as BytesIO 10 | 11 | c = pycurl.Curl() 12 | c.setopt(c.URL, 'https://httpbin.org/put') 13 | 14 | c.setopt(c.UPLOAD, 1) 15 | data = '{"json":true}' 16 | # READDATA requires an IO-like object; a string is not accepted 17 | # encode() is necessary for Python 3 18 | buffer = BytesIO(data.encode('utf-8')) 19 | c.setopt(c.READDATA, buffer) 20 | 21 | c.perform() 22 | c.close() 23 | -------------------------------------------------------------------------------- /examples/quickstart/put_file.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | 7 | c = pycurl.Curl() 8 | c.setopt(c.URL, 'https://httpbin.org/put') 9 | 10 | c.setopt(c.UPLOAD, 1) 11 | file = open(__file__) 12 | c.setopt(c.READDATA, file) 13 | 14 | c.perform() 15 | c.close() 16 | # File must be kept open while Curl object is using it 17 | file.close() 18 | -------------------------------------------------------------------------------- /examples/quickstart/response_headers.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import re 7 | try: 8 | from io import BytesIO 9 | except ImportError: 10 | from StringIO import StringIO as BytesIO 11 | 12 | headers = {} 13 | def header_function(header_line): 14 | # HTTP standard specifies that headers are encoded in iso-8859-1. 15 | # On Python 2, decoding step can be skipped. 16 | # On Python 3, decoding step is required. 17 | header_line = header_line.decode('iso-8859-1') 18 | 19 | # Header lines include the first status line (HTTP/1.x ...). 20 | # We are going to ignore all lines that don't have a colon in them. 21 | # This will botch headers that are split on multiple lines... 22 | if ':' not in header_line: 23 | return 24 | 25 | # Break the header line into header name and value. 26 | name, value = header_line.split(':', 1) 27 | 28 | # Remove whitespace that may be present. 29 | # Header lines include the trailing newline, and there may be whitespace 30 | # around the colon. 31 | name = name.strip() 32 | value = value.strip() 33 | 34 | # Header names are case insensitive. 35 | # Lowercase name here. 36 | name = name.lower() 37 | 38 | # Now we can actually record the header name and value. 39 | headers[name] = value 40 | 41 | buffer = BytesIO() 42 | c = pycurl.Curl() 43 | c.setopt(c.URL, 'http://pycurl.io') 44 | c.setopt(c.WRITEFUNCTION, buffer.write) 45 | # Set our header function. 46 | c.setopt(c.HEADERFUNCTION, header_function) 47 | c.perform() 48 | c.close() 49 | 50 | # Figure out what encoding was sent with the response, if any. 51 | # Check against lowercased header name. 52 | encoding = None 53 | if 'content-type' in headers: 54 | content_type = headers['content-type'].lower() 55 | match = re.search('charset=(\S+)', content_type) 56 | if match: 57 | encoding = match.group(1) 58 | print('Decoding using %s' % encoding) 59 | if encoding is None: 60 | # Default encoding for HTML is iso-8859-1. 61 | # Other content types may have different default encoding, 62 | # or in case of binary data, may have no encoding at all. 63 | encoding = 'iso-8859-1' 64 | print('Assuming encoding is %s' % encoding) 65 | 66 | body = buffer.getvalue() 67 | # Decode using the encoding we figured out. 68 | print(body.decode(encoding)) 69 | -------------------------------------------------------------------------------- /examples/quickstart/response_info.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | try: 7 | from io import BytesIO 8 | except ImportError: 9 | from StringIO import StringIO as BytesIO 10 | 11 | buffer = BytesIO() 12 | c = pycurl.Curl() 13 | c.setopt(c.URL, 'http://pycurl.io/') 14 | c.setopt(c.WRITEDATA, buffer) 15 | c.perform() 16 | 17 | # HTTP response code, e.g. 200. 18 | print('Status: %d' % c.getinfo(c.RESPONSE_CODE)) 19 | # Elapsed time for the transfer. 20 | print('Time: %f' % c.getinfo(c.TOTAL_TIME)) 21 | 22 | # getinfo must be called before close. 23 | c.close() 24 | -------------------------------------------------------------------------------- /examples/quickstart/write_file.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | 7 | # As long as the file is opened in binary mode, both Python 2 and Python 3 8 | # can write response body to it without decoding. 9 | with open('out.html', 'wb') as f: 10 | c = pycurl.Curl() 11 | c.setopt(c.URL, 'http://pycurl.io/') 12 | c.setopt(c.WRITEDATA, f) 13 | c.perform() 14 | c.close() 15 | -------------------------------------------------------------------------------- /examples/retriever.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | # 6 | # Usage: python retriever.py [<# of 7 | # concurrent connections>] 8 | # 9 | 10 | import sys, threading 11 | try: 12 | import Queue 13 | except ImportError: 14 | import queue as Queue 15 | import pycurl 16 | 17 | # We should ignore SIGPIPE when using pycurl.NOSIGNAL - see 18 | # the libcurl tutorial for more info. 19 | try: 20 | import signal 21 | from signal import SIGPIPE, SIG_IGN 22 | except ImportError: 23 | pass 24 | else: 25 | signal.signal(SIGPIPE, SIG_IGN) 26 | 27 | 28 | # Get args 29 | num_conn = 10 30 | try: 31 | if sys.argv[1] == "-": 32 | urls = sys.stdin.readlines() 33 | else: 34 | urls = open(sys.argv[1]).readlines() 35 | if len(sys.argv) >= 3: 36 | num_conn = int(sys.argv[2]) 37 | except: 38 | print("Usage: %s [<# of concurrent connections>]" % sys.argv[0]) 39 | raise SystemExit 40 | 41 | 42 | # Make a queue with (url, filename) tuples 43 | queue = Queue.Queue() 44 | for url in urls: 45 | url = url.strip() 46 | if not url or url[0] == "#": 47 | continue 48 | filename = "doc_%03d.dat" % (len(queue.queue) + 1) 49 | queue.put((url, filename)) 50 | 51 | 52 | # Check args 53 | assert queue.queue, "no URLs given" 54 | num_urls = len(queue.queue) 55 | num_conn = min(num_conn, num_urls) 56 | assert 1 <= num_conn <= 10000, "invalid number of concurrent connections" 57 | print("PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)) 58 | print("----- Getting", num_urls, "URLs using", num_conn, "connections -----") 59 | 60 | 61 | class WorkerThread(threading.Thread): 62 | def __init__(self, queue): 63 | threading.Thread.__init__(self) 64 | self.queue = queue 65 | 66 | def run(self): 67 | while 1: 68 | try: 69 | url, filename = self.queue.get_nowait() 70 | except Queue.Empty: 71 | raise SystemExit 72 | fp = open(filename, "wb") 73 | curl = pycurl.Curl() 74 | curl.setopt(pycurl.URL, url) 75 | curl.setopt(pycurl.FOLLOWLOCATION, 1) 76 | curl.setopt(pycurl.MAXREDIRS, 5) 77 | curl.setopt(pycurl.CONNECTTIMEOUT, 30) 78 | curl.setopt(pycurl.TIMEOUT, 300) 79 | curl.setopt(pycurl.NOSIGNAL, 1) 80 | curl.setopt(pycurl.WRITEDATA, fp) 81 | try: 82 | curl.perform() 83 | except: 84 | import traceback 85 | traceback.print_exc(file=sys.stderr) 86 | sys.stderr.flush() 87 | curl.close() 88 | fp.close() 89 | sys.stdout.write(".") 90 | sys.stdout.flush() 91 | 92 | 93 | # Start a bunch of threads 94 | threads = [] 95 | for dummy in range(num_conn): 96 | t = WorkerThread(queue) 97 | t.start() 98 | threads.append(t) 99 | 100 | 101 | # Wait for all threads to finish 102 | for thread in threads: 103 | thread.join() 104 | -------------------------------------------------------------------------------- /examples/sfquery.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | # 5 | # sfquery -- Source Forge query script using the ClientCGI high-level interface 6 | # 7 | # Retrieves a SourceForge XML export object for a given project. 8 | # Specify the *numeric* project ID. the user name, and the password, 9 | # as arguments. If you have a valid ~/.netrc entry for sourceforge.net, 10 | # you can just give the project ID. 11 | # 12 | # By Eric S. Raymond, August 2002. All rites reversed. 13 | 14 | import sys, netrc 15 | import curl 16 | 17 | class SourceForgeUserSession(curl.Curl): 18 | # SourceForge-specific methods. Sensitive to changes in site design. 19 | def login(self, name, password): 20 | "Establish a login session." 21 | self.post("account/login.php", (("form_loginname", name), 22 | ("form_pw", password), 23 | ("return_to", ""), 24 | ("stay_in_ssl", "1"), 25 | ("login", "Login With SSL"))) 26 | def logout(self): 27 | "Log out of SourceForge." 28 | self.get("account/logout.php") 29 | def fetch_xml(self, numid): 30 | self.get("export/xml_export.php?group_id=%s" % numid) 31 | 32 | if __name__ == "__main__": 33 | if len(sys.argv) == 1: 34 | project_id = '28236' # PyCurl project ID 35 | else: 36 | project_id = sys.argv[1] 37 | # Try to grab authenticators out of your .netrc 38 | try: 39 | auth = netrc.netrc().authenticators("sourceforge.net") 40 | name, account, password = auth 41 | except: 42 | if len(sys.argv) < 4: 43 | print("Usage: %s " % sys.argv[0]) 44 | raise SystemExit 45 | name = sys.argv[2] 46 | password = sys.argv[3] 47 | session = SourceForgeUserSession("https://sourceforge.net/") 48 | session.set_verbosity(0) 49 | session.login(name, password) 50 | # Login could fail. 51 | if session.answered("Invalid Password or User Name"): 52 | sys.stderr.write("Login/password not accepted (%d bytes)\n" % len(session.body())) 53 | sys.exit(1) 54 | # We'll see this if we get the right thing. 55 | elif session.answered("Personal Page For: " + name): 56 | session.fetch_xml(project_id) 57 | sys.stdout.write(session.body()) 58 | session.logout() 59 | sys.exit(0) 60 | # Or maybe SourceForge has changed its site design so our check strings 61 | # are no longer valid. 62 | else: 63 | sys.stderr.write("Unexpected page (%d bytes)\n"%len(session.body())) 64 | sys.exit(1) 65 | 66 | -------------------------------------------------------------------------------- /examples/smtp.py: -------------------------------------------------------------------------------- 1 | # Based on the simple libcurl SMTP example: 2 | # https://github.com/bagder/curl/blob/master/docs/examples/smtp-mail.c 3 | # There are other SMTP examples in that directory that you may find helpful. 4 | 5 | from . import localhost 6 | import pycurl 7 | try: 8 | from io import BytesIO 9 | except ImportError: 10 | from StringIO import StringIO as BytesIO 11 | import sys 12 | 13 | PY3 = sys.version_info[0] > 2 14 | 15 | mail_server = 'smtp://%s' % localhost 16 | mail_from = 'sender@example.org' 17 | mail_to = 'addressee@example.net' 18 | 19 | c = pycurl.Curl() 20 | c.setopt(c.URL, mail_server) 21 | c.setopt(c.MAIL_FROM, mail_from) 22 | c.setopt(c.MAIL_RCPT, [mail_to]) 23 | 24 | message = '''\ 25 | From: %s 26 | To: %s 27 | Subject: PycURL SMTP example 28 | 29 | SMTP example via PycURL 30 | ''' % (mail_from, mail_to) 31 | 32 | if PY3: 33 | message = message.encode('ascii') 34 | 35 | # libcurl does not perform buffering, therefore 36 | # we need to wrap the message string into a BytesIO or StringIO. 37 | io = BytesIO(message) 38 | c.setopt(c.READDATA, io) 39 | 40 | # If UPLOAD is not set, libcurl performs SMTP VRFY. 41 | # Setting UPLOAD to True sends a message. 42 | c.setopt(c.UPLOAD, True) 43 | 44 | # Observe SMTP conversation. 45 | c.setopt(c.VERBOSE, True) 46 | c.perform() 47 | -------------------------------------------------------------------------------- /examples/ssh_keyfunction.py: -------------------------------------------------------------------------------- 1 | import pycurl 2 | 3 | sftp_server = 'sftp://web.sourceforge.net' 4 | 5 | c = pycurl.Curl() 6 | c.setopt(c.URL, sftp_server) 7 | c.setopt(c.VERBOSE, True) 8 | 9 | def keyfunction(known_key, found_key, match): 10 | return c.KHSTAT_FINE 11 | 12 | c.setopt(c.SSH_KNOWNHOSTS, '.known_hosts') 13 | c.setopt(c.SSH_KEYFUNCTION, keyfunction) 14 | 15 | c.perform() 16 | -------------------------------------------------------------------------------- /examples/tests/test_build_config.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import zlib 7 | try: 8 | from io import BytesIO 9 | except ImportError: 10 | try: 11 | from cStringIO import StringIO as BytesIO 12 | except ImportError: 13 | from StringIO import StringIO as BytesIO 14 | 15 | c = pycurl.Curl() 16 | c.setopt(c.URL, 'http://pycurl.io') 17 | #c.setopt(c.ENCODING, 'deflate') 18 | c.setopt(c.HTTPHEADER, ['Accept-Encoding: deflate']) 19 | body = BytesIO() 20 | c.setopt(c.WRITEFUNCTION, body.write) 21 | encoding_found = False 22 | def header_function(header): 23 | global encoding_found 24 | if header.decode('iso-8859-1').lower().startswith('content-encoding: deflate'): 25 | encoding_found = True 26 | c.setopt(c.HEADERFUNCTION, header_function) 27 | c.perform() 28 | assert encoding_found 29 | print('Server supports deflate encoding') 30 | encoded = body.getvalue() 31 | # should not raise exceptions 32 | zlib.decompress(encoded, -zlib.MAX_WBITS) 33 | print('Server served deflated body') 34 | 35 | c.reset() 36 | c.setopt(c.URL, 'http://pycurl.io') 37 | c.setopt(c.ENCODING, 'deflate') 38 | body = BytesIO() 39 | c.setopt(c.WRITEFUNCTION, body.write) 40 | encoding_found = False 41 | def header_function(header): 42 | global encoding_found 43 | if header.decode('iso-8859-1').lower().startswith('content-encoding: deflate'): 44 | encoding_found = True 45 | c.setopt(c.HEADERFUNCTION, header_function) 46 | c.perform() 47 | assert encoding_found 48 | print('Server claimed deflate encoding as expected') 49 | # body should be decoded 50 | encoded = body.getvalue() 51 | if '= 1.0: self.round = 0.0 47 | else: 48 | self.round = float(download_d) / float(download_t) 49 | gtk.threads_enter() 50 | self.pbar.set_fraction(self.round) 51 | gtk.threads_leave() 52 | 53 | def mainloop(self): 54 | gtk.threads_enter() 55 | gtk.main() 56 | gtk.threads_leave() 57 | 58 | def close_app(self, *args): 59 | args[0].destroy() 60 | gtk.main_quit() 61 | 62 | 63 | class Test(threading.Thread): 64 | def __init__(self, url, target_file, progress): 65 | threading.Thread.__init__(self) 66 | self.target_file = target_file 67 | self.progress = progress 68 | self.curl = pycurl.Curl() 69 | self.curl.setopt(pycurl.URL, url) 70 | self.curl.setopt(pycurl.WRITEDATA, self.target_file) 71 | self.curl.setopt(pycurl.FOLLOWLOCATION, 1) 72 | self.curl.setopt(pycurl.NOPROGRESS, 0) 73 | self.curl.setopt(pycurl.PROGRESSFUNCTION, self.progress) 74 | self.curl.setopt(pycurl.MAXREDIRS, 5) 75 | self.curl.setopt(pycurl.NOSIGNAL, 1) 76 | 77 | def run(self): 78 | self.curl.perform() 79 | self.curl.close() 80 | self.target_file.close() 81 | self.progress(1.0, 1.0, 0, 0) 82 | 83 | 84 | # Check command line args 85 | if len(sys.argv) < 3: 86 | print("Usage: %s " % sys.argv[0]) 87 | raise SystemExit 88 | 89 | # Make a progress bar window 90 | p = ProgressBar(sys.argv[1]) 91 | # Start thread for fetching url 92 | Test(sys.argv[1], open(sys.argv[2], 'wb'), p.progress).start() 93 | # Enter the GTK mainloop 94 | gtk.threads_init() 95 | try: 96 | p.mainloop() 97 | except KeyboardInterrupt: 98 | pass 99 | -------------------------------------------------------------------------------- /examples/tests/test_xmlrpc.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | ## XML-RPC lib included in python2.2 6 | try: 7 | import xmlrpclib 8 | except ImportError: 9 | import xmlrpc.client as xmlrpclib 10 | import pycurl 11 | 12 | # Header fields passed in request 13 | xmlrpc_header = [ 14 | "User-Agent: PycURL XML-RPC Test", "Content-Type: text/xml" 15 | ] 16 | 17 | # XML-RPC request template 18 | xmlrpc_template = """ 19 | %s%s 20 | """ 21 | 22 | # Engage 23 | c = pycurl.Curl() 24 | c.setopt(c.URL, 'http://betty.userland.com/RPC2') 25 | c.setopt(c.POST, 1) 26 | c.setopt(c.HTTPHEADER, xmlrpc_header) 27 | c.setopt(c.POSTFIELDS, xmlrpc_template % ("examples.getStateName", xmlrpclib.dumps((5,)))) 28 | 29 | print('Response from http://betty.userland.com/') 30 | c.perform() 31 | c.close() 32 | -------------------------------------------------------------------------------- /examples/xmlrpc_curl.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | # We should ignore SIGPIPE when using pycurl.NOSIGNAL - see 6 | # the libcurl tutorial for more info. 7 | try: 8 | import signal 9 | from signal import SIGPIPE, SIG_IGN 10 | except ImportError: 11 | pass 12 | else: 13 | signal.signal(SIGPIPE, SIG_IGN) 14 | 15 | try: 16 | from cStringIO import StringIO 17 | except ImportError: 18 | try: 19 | from StringIO import StringIO 20 | except ImportError: 21 | from io import StringIO 22 | try: 23 | import xmlrpclib 24 | except ImportError: 25 | import xmlrpc.client as xmlrpclib 26 | import pycurl 27 | import sys 28 | 29 | PY3 = sys.version_info[0] > 2 30 | 31 | 32 | class CURLTransport(xmlrpclib.Transport): 33 | """Handles a cURL HTTP transaction to an XML-RPC server.""" 34 | 35 | xmlrpc_h = [ "Content-Type: text/xml" ] 36 | 37 | def __init__(self, username=None, password=None): 38 | self.c = pycurl.Curl() 39 | self.c.setopt(pycurl.POST, 1) 40 | self.c.setopt(pycurl.NOSIGNAL, 1) 41 | self.c.setopt(pycurl.CONNECTTIMEOUT, 30) 42 | self.c.setopt(pycurl.HTTPHEADER, self.xmlrpc_h) 43 | if username != None and password != None: 44 | self.c.setopt(pycurl.USERPWD, '%s:%s' % (username, password)) 45 | self._use_datetime = False 46 | 47 | def request(self, host, handler, request_body, verbose=0): 48 | b = StringIO() 49 | self.c.setopt(pycurl.URL, 'http://%s%s' % (host, handler)) 50 | self.c.setopt(pycurl.POSTFIELDS, request_body) 51 | self.c.setopt(pycurl.WRITEFUNCTION, b.write) 52 | self.c.setopt(pycurl.VERBOSE, verbose) 53 | self.verbose = verbose 54 | try: 55 | self.c.perform() 56 | except pycurl.error: 57 | v = sys.exc_info()[1] 58 | if PY3: 59 | v = v.args 60 | raise xmlrpclib.ProtocolError( 61 | host + handler, 62 | v[0], v[1], None 63 | ) 64 | b.seek(0) 65 | return self.parse_response(b) 66 | 67 | 68 | if __name__ == "__main__": 69 | ## Test 70 | server = xmlrpclib.ServerProxy("http://betty.userland.com", 71 | transport=CURLTransport()) 72 | print(server) 73 | try: 74 | print(server.examples.getStateName(41)) 75 | except xmlrpclib.Error: 76 | v = sys.exc_info()[1] 77 | print("ERROR", v) 78 | -------------------------------------------------------------------------------- /linux_install.txt: -------------------------------------------------------------------------------- 1 | lsb_release -a 2 | No LSB modules are available. 3 | Distributor ID: Debian 4 | Description: Debian GNU/Linux 10 (buster) 5 | Release: 10 6 | Codename: buster 7 | openssl version 8 | OpenSSL 1.1.1n 15 Mar 2022 9 | python3 -V 10 | Python 3.7.3 11 | 12 | wget https://github.com/lwthiker/curl-impersonate/archive/refs/heads/main.zip 13 | unzip main -x 14 | mv curl-impersonate-main curl-impersonate 15 | 16 | sudo apt install build-essential pkg-config cmake ninja-build curl autoconf automake libtool git 17 | #sudo apt upgrade build-essential pkg-config cmake ninja-build curl autoconf automake libtool 18 | sudo apt install golang-go unzip 19 | 20 | # https://github.com/lwthiker/curl-impersonate/blob/main/INSTALL.md 21 | cd curl-impersonate 22 | mkdir build && cd build 23 | ../configure 24 | gmake chrome-build 25 | sudo gmake chrome-install 26 | 27 | curl-impersonate-chrome -V 28 | curl 7.84.0 (x86_64-pc-linux-gnu) libcurl/7.84.0 BoringSSL zlib/1.2.11 brotli/1.0.9 nghttp2/1.46.0 29 | Release-Date: 2022-06-27 30 | Protocols: dict file ftp ftps gopher gophers http https imap imaps mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 31 | Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IPv6 Largefile libz NTLM NTLM_WB SSL threadsafe UnixSockets 32 | 33 | wget https://github.com/ycq0125/pycurl/archive/refs/heads/master.zip 34 | unzip master -x 35 | mv pycurl-master pycurl 36 | cd pycurl 37 | #gcc找到头文件的路径, 注意/MyLib为实际位置 38 | # 参考C_INCLUDE_PATH=/usr/include/libxml2:/Users/yubanzhan/curl_test/curl-impersonate/build/curl-7.84.0/include 39 | C_INCLUDE_PATH=/usr/include/libxml2:/MyLib 40 | export C_INCLUDE_PATH 41 | 42 | pip3 install --upgrade setuptools 43 | python3 setup.py install --curl-config=/usr/local/bin/curl-impersonate-chrome-config 44 | 45 | python3 test.py 46 | pip3 show requests 47 | Location:/usr/lib/python3/dist-packages 48 | cp -r ./requests_curl/ /usr/lib/python3/dist-packages 49 | 50 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = tests/*.py 3 | norecursedirs = examples win 4 | markers = 5 | ssh: mark a test as requiring ssh 6 | online: mark a test as requiring internet access 7 | gssapi: mark a test as requiring GSSAPI 8 | http2: mark a test as requiring HTTP/2 9 | standalone: mark a test as being standalone 10 | -------------------------------------------------------------------------------- /requests_curl/__init__.py: -------------------------------------------------------------------------------- 1 | # https://github.com/paivett/requests-curl 2 | from .adapter import CURLAdapter 3 | 4 | __all__ = [ 5 | "CURLAdapter", 6 | ] 7 | -------------------------------------------------------------------------------- /requests_curl/error.py: -------------------------------------------------------------------------------- 1 | import pycurl 2 | import re 3 | 4 | from requests.exceptions import ( 5 | ConnectionError, 6 | ConnectTimeout, 7 | ReadTimeout, 8 | SSLError, 9 | ProxyError, 10 | ) 11 | 12 | _PYCURL_SSL_ERRORS = { 13 | pycurl.E_SSL_CACERT, 14 | pycurl.E_SSL_CACERT_BADFILE, 15 | pycurl.E_SSL_CERTPROBLEM, 16 | pycurl.E_SSL_CIPHER, 17 | pycurl.E_SSL_CONNECT_ERROR, 18 | pycurl.E_SSL_CRL_BADFILE, 19 | pycurl.E_SSL_ENGINE_INITFAILED, 20 | pycurl.E_SSL_ENGINE_NOTFOUND, 21 | pycurl.E_SSL_ENGINE_SETFAILED, 22 | pycurl.E_SSL_INVALIDCERTSTATUS, 23 | pycurl.E_SSL_ISSUER_ERROR, 24 | pycurl.E_SSL_PEER_CERTIFICATE, 25 | pycurl.E_SSL_PINNEDPUBKEYNOTMATCH, 26 | pycurl.E_SSL_SHUTDOWN_FAILED, 27 | } 28 | 29 | 30 | _PYCURL_TIMEOUT_ERRORS = {pycurl.E_OPERATION_TIMEOUTED, pycurl.E_OPERATION_TIMEDOUT} 31 | 32 | 33 | _PROXY_AUTH_ERR_PATTERN = re.compile( 34 | r"Received HTTP code \d{3} from proxy after CONNECT" 35 | ) 36 | 37 | 38 | def _to_ssl_error(error_code, error_msg): 39 | if error_code in _PYCURL_SSL_ERRORS: 40 | return SSLError 41 | 42 | 43 | def _to_proxy_error(error_code, error_msg): 44 | is_proxy_error = ( 45 | error_code == pycurl.E_COULDNT_RESOLVE_PROXY 46 | or _PROXY_AUTH_ERR_PATTERN.match(error_msg) is not None 47 | ) 48 | 49 | if is_proxy_error: 50 | return ProxyError 51 | 52 | 53 | def _to_timeout_error(error_code, error_msg): 54 | if error_code in _PYCURL_TIMEOUT_ERRORS: 55 | if error_msg.startswith("Connection timed out"): 56 | return ConnectTimeout 57 | else: 58 | return ReadTimeout 59 | 60 | 61 | _ERROR_TRANSLATE_FUNCS = (_to_ssl_error, _to_proxy_error, _to_timeout_error) 62 | 63 | 64 | def translate_curl_exception(curl_exception): 65 | """This function will make the best effort to translate a given PyCURL error 66 | to a requests exception. 67 | 68 | Args: 69 | curl_exception (pycurl.error): PyCURL error to be translated. 70 | 71 | Returns: 72 | requests.exceptions.RequestException: the requests exception that 73 | matches to the CURL error. 74 | """ 75 | 76 | error_code, error_msg = curl_exception.args 77 | default_error = ConnectionError 78 | 79 | for translate_func in _ERROR_TRANSLATE_FUNCS: 80 | requests_error = translate_func(error_code, error_msg) 81 | if requests_error is not None: 82 | return requests_error 83 | 84 | return default_error 85 | -------------------------------------------------------------------------------- /requests_curl/pool_provider.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | from urllib3.poolmanager import PoolManager 3 | from urllib3.util import parse_url 4 | from requests.utils import prepend_scheme_if_needed 5 | from requests.exceptions import InvalidProxyURL 6 | 7 | from .pool import CURLHandlerPool, ProxyCURLHandlerPool 8 | 9 | 10 | class CURLPoolProvider(object): 11 | """This class provides a pool for a given URL. The pool then will handle all 12 | connections for that specific URL.""" 13 | 14 | def __init__(self, max_pools, max_pool_size, pool_block): 15 | self._max_pools = max_pools 16 | self._max_pool_size = max_pool_size 17 | self._pool_block = pool_block 18 | 19 | self._pool_manager = self._create_pool_manager( 20 | lambda url, port, **kwargs: CURLHandlerPool(**kwargs) 21 | ) 22 | 23 | self._pool_manager_per_proxy = {} 24 | 25 | def _create_pool_manager(self, pool_factory): 26 | pool_manager = PoolManager( 27 | num_pools=self._max_pools, 28 | maxsize=self._max_pool_size, 29 | block=self._pool_block, 30 | strict=True, 31 | ) 32 | 33 | pool_manager.pool_classes_by_scheme = { 34 | "http": pool_factory, 35 | "https": pool_factory, 36 | } 37 | 38 | return pool_manager 39 | 40 | def get_pool_for_url(self, url): 41 | """Returns an instance of a CURLHandlerPool for a given URL""" 42 | return self._pool_manager.connection_from_url(url) 43 | 44 | def get_pool_for_proxied_url(self, proxy_url, url): 45 | """Returns an instance of a CURLHandlerPool for a given URL, but using a Proxy""" 46 | parsed_proxy_url = _parse_proxy_url(proxy_url) 47 | 48 | if parsed_proxy_url not in self._pool_manager_per_proxy: 49 | # Create here the poolmanager for proxy 50 | self._pool_manager_per_proxy[parsed_proxy_url] = self._create_pool_manager( 51 | lambda url, port, maxsize=1, **kwargs: ProxyCURLHandlerPool( 52 | parsed_proxy_url, maxsize=maxsize, **kwargs 53 | ) 54 | ) 55 | 56 | pool_manager = self._pool_manager_per_proxy[parsed_proxy_url] 57 | 58 | return pool_manager.connection_from_url(url) 59 | 60 | @property 61 | def _pool_managers(self): 62 | return chain((self._pool_manager,), self._pool_manager_per_proxy.values()) 63 | 64 | def clear(self): 65 | for pool_manager in self._pool_managers: 66 | pool_manager.clear() 67 | 68 | def __len__(self): 69 | """Returns the number of pools that this provider currently handles""" 70 | proxy_pools_count = sum( 71 | len(pool_manager.pools) for pool_manager in self._pool_managers 72 | ) 73 | return proxy_pools_count 74 | 75 | 76 | def _parse_proxy_url(proxy_url): 77 | proxy_url = prepend_scheme_if_needed(proxy_url, "http") 78 | parsed_proxy_url = parse_url(proxy_url) 79 | 80 | if not parsed_proxy_url.host: 81 | raise InvalidProxyURL( 82 | "Please check proxy URL. It is malformed" " and could be missing the host." 83 | ) 84 | 85 | return parsed_proxy_url 86 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # bottle 0.12.17 changed behavior 2 | # https://github.com/pycurl/pycurl/issues/573 3 | bottle 4 | flaky 5 | pyflakes 6 | pytest>=5 7 | sphinx 8 | -------------------------------------------------------------------------------- /scripts/missing-symbols: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys, re, subprocess 4 | 5 | def process(siv_path): 6 | with open(siv_path) as f: 7 | for line in f: 8 | if line[0] == ' ': 9 | # comment 10 | continue 11 | line = line.strip() 12 | if line == '': 13 | continue 14 | parts = re.split(r'\s+', line) 15 | if len(parts) >= 4: 16 | # removed symbol, all are very old 17 | continue 18 | if parts[0] == 'CURLOPT_CLOSEPOLICY' or \ 19 | parts[0].startswith('CURLCLOSEPOLICY_') or \ 20 | parts[0] == 'CURLOPT_WRITEINFO': 21 | # no docs for these options 22 | continue 23 | try: 24 | subprocess.check_call(['git', 'grep', '-q', parts[0], 'src']) 25 | except subprocess.CalledProcessError: 26 | print('Missing %s (since %s)' % (parts[0], parts[1])) 27 | 28 | process(sys.argv[1]) 29 | -------------------------------------------------------------------------------- /src/easyperform.c: -------------------------------------------------------------------------------- 1 | #include "pycurl.h" 2 | 3 | 4 | /* --------------- perform --------------- */ 5 | 6 | PYCURL_INTERNAL PyObject * 7 | do_curl_perform(CurlObject *self, PyObject *Py_UNUSED(ignored)) 8 | { 9 | int res; 10 | 11 | if (check_curl_state(self, 1 | 2, "perform") != 0) { 12 | return NULL; 13 | } 14 | 15 | PYCURL_BEGIN_ALLOW_THREADS 16 | res = curl_easy_perform(self->handle); 17 | PYCURL_END_ALLOW_THREADS 18 | 19 | if (res != CURLE_OK) { 20 | CURLERROR_RETVAL(); 21 | } 22 | Py_RETURN_NONE; 23 | } 24 | 25 | 26 | PYCURL_INTERNAL PyObject * 27 | do_curl_perform_rb(CurlObject *self, PyObject *Py_UNUSED(ignored)) 28 | { 29 | PyObject *v, *io; 30 | 31 | /* NOTE: this tuple is never freed. */ 32 | static PyObject *empty_tuple = NULL; 33 | 34 | if (empty_tuple == NULL) { 35 | empty_tuple = PyTuple_New(0); 36 | if (empty_tuple == NULL) { 37 | return NULL; 38 | } 39 | } 40 | 41 | io = PyObject_Call(bytesio, empty_tuple, NULL); 42 | if (io == NULL) { 43 | return NULL; 44 | } 45 | 46 | v = do_curl_setopt_filelike(self, CURLOPT_WRITEDATA, io); 47 | if (v == NULL) { 48 | Py_DECREF(io); 49 | return NULL; 50 | } 51 | 52 | v = do_curl_perform(self, NULL); 53 | if (v == NULL) { 54 | return NULL; 55 | } 56 | 57 | v = PyObject_CallMethod(io, "getvalue", NULL); 58 | Py_DECREF(io); 59 | return v; 60 | } 61 | 62 | #if PY_MAJOR_VERSION >= 3 63 | PYCURL_INTERNAL PyObject * 64 | do_curl_perform_rs(CurlObject *self, PyObject *Py_UNUSED(ignored)) 65 | { 66 | PyObject *v, *decoded; 67 | 68 | v = do_curl_perform_rb(self, NULL); 69 | if (v == NULL) { 70 | return NULL; 71 | } 72 | 73 | decoded = PyUnicode_FromEncodedObject(v, NULL, NULL); 74 | Py_DECREF(v); 75 | return decoded; 76 | } 77 | #endif 78 | 79 | 80 | /* --------------- pause --------------- */ 81 | 82 | 83 | /* curl_easy_pause() can be called from inside a callback or outside */ 84 | PYCURL_INTERNAL PyObject * 85 | do_curl_pause(CurlObject *self, PyObject *args) 86 | { 87 | int bitmask; 88 | CURLcode res; 89 | #ifdef WITH_THREAD 90 | PyThreadState *saved_state; 91 | #endif 92 | 93 | if (!PyArg_ParseTuple(args, "i:pause", &bitmask)) { 94 | return NULL; 95 | } 96 | if (check_curl_state(self, 1, "pause") != 0) { 97 | return NULL; 98 | } 99 | 100 | #ifdef WITH_THREAD 101 | /* Save handle to current thread (used as context for python callbacks) */ 102 | saved_state = self->state; 103 | PYCURL_BEGIN_ALLOW_THREADS_EASY 104 | 105 | /* We must allow threads here because unpausing a handle can cause 106 | some of its callbacks to be invoked immediately, from inside 107 | curl_easy_pause() */ 108 | #endif 109 | 110 | res = curl_easy_pause(self->handle, bitmask); 111 | 112 | #ifdef WITH_THREAD 113 | PYCURL_END_ALLOW_THREADS_EASY 114 | 115 | /* Restore the thread-state to whatever it was on entry */ 116 | self->state = saved_state; 117 | #endif 118 | 119 | if (res != CURLE_OK) { 120 | CURLERROR_MSG("pause/unpause failed"); 121 | } else { 122 | Py_INCREF(Py_None); 123 | return Py_None; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/oscompat.c: -------------------------------------------------------------------------------- 1 | #include "pycurl.h" 2 | 3 | #if defined(WIN32) 4 | PYCURL_INTERNAL int 5 | dup_winsock(int sock, const struct curl_sockaddr *address) 6 | { 7 | int rv; 8 | WSAPROTOCOL_INFO pi; 9 | 10 | rv = WSADuplicateSocket(sock, GetCurrentProcessId(), &pi); 11 | if (rv) { 12 | return CURL_SOCKET_BAD; 13 | } 14 | 15 | /* not sure if WSA_FLAG_OVERLAPPED is needed, but it does not seem to hurt */ 16 | return (int) WSASocket(address->family, address->socktype, address->protocol, &pi, 0, WSA_FLAG_OVERLAPPED); 17 | } 18 | #endif 19 | 20 | #if defined(WIN32) && ((_WIN32_WINNT < 0x0600) || (NTDDI_VERSION < NTDDI_VISTA)) 21 | /* 22 | * Only Winsock on Vista+ has inet_ntop(). 23 | */ 24 | PYCURL_INTERNAL const char * 25 | pycurl_inet_ntop (int family, void *addr, char *string, size_t string_size) 26 | { 27 | SOCKADDR *sa; 28 | int sa_len; 29 | /* both size_t and DWORD should be unsigned ints */ 30 | DWORD string_size_dword = (DWORD) string_size; 31 | 32 | if (family == AF_INET6) { 33 | struct sockaddr_in6 sa6; 34 | memset(&sa6, 0, sizeof(sa6)); 35 | sa6.sin6_family = AF_INET6; 36 | memcpy(&sa6.sin6_addr, addr, sizeof(sa6.sin6_addr)); 37 | sa = (SOCKADDR*) &sa6; 38 | sa_len = sizeof(sa6); 39 | } else if (family == AF_INET) { 40 | struct sockaddr_in sa4; 41 | memset(&sa4, 0, sizeof(sa4)); 42 | sa4.sin_family = AF_INET; 43 | memcpy(&sa4.sin_addr, addr, sizeof(sa4.sin_addr)); 44 | sa = (SOCKADDR*) &sa4; 45 | sa_len = sizeof(sa4); 46 | } else { 47 | errno = EAFNOSUPPORT; 48 | return NULL; 49 | } 50 | if (WSAAddressToString(sa, sa_len, NULL, string, &string_size_dword)) 51 | return NULL; 52 | return string; 53 | } 54 | #endif 55 | 56 | /* vi:ts=4:et:nowrap 57 | */ 58 | -------------------------------------------------------------------------------- /src/stringcompat.c: -------------------------------------------------------------------------------- 1 | #include "pycurl.h" 2 | 3 | /************************************************************************* 4 | // python utility functions 5 | **************************************************************************/ 6 | 7 | PYCURL_INTERNAL int 8 | PyText_AsStringAndSize(PyObject *obj, char **buffer, Py_ssize_t *length, PyObject **encoded_obj) 9 | { 10 | if (PyByteStr_Check(obj)) { 11 | *encoded_obj = NULL; 12 | return PyByteStr_AsStringAndSize(obj, buffer, length); 13 | } else { 14 | int rv; 15 | assert(PyUnicode_Check(obj)); 16 | *encoded_obj = PyUnicode_AsEncodedString(obj, "ascii", "strict"); 17 | if (*encoded_obj == NULL) { 18 | return -1; 19 | } 20 | rv = PyByteStr_AsStringAndSize(*encoded_obj, buffer, length); 21 | if (rv != 0) { 22 | /* If we free the object, pointer must be reset to NULL */ 23 | Py_CLEAR(*encoded_obj); 24 | } 25 | return rv; 26 | } 27 | } 28 | 29 | 30 | /* Like PyString_AsString(), but set an exception if the string contains 31 | * embedded NULs. Actually PyString_AsStringAndSize() already does that for 32 | * us if the `len' parameter is NULL - see Objects/stringobject.c. 33 | */ 34 | 35 | PYCURL_INTERNAL char * 36 | PyText_AsString_NoNUL(PyObject *obj, PyObject **encoded_obj) 37 | { 38 | char *s = NULL; 39 | Py_ssize_t r; 40 | r = PyText_AsStringAndSize(obj, &s, NULL, encoded_obj); 41 | if (r != 0) 42 | return NULL; /* exception already set */ 43 | assert(s != NULL); 44 | return s; 45 | } 46 | 47 | 48 | /* Returns true if the object is of a type that can be given to 49 | * curl_easy_setopt and such - either a byte string or a Unicode string 50 | * with ASCII code points only. 51 | */ 52 | #if PY_MAJOR_VERSION >= 3 53 | PYCURL_INTERNAL int 54 | PyText_Check(PyObject *o) 55 | { 56 | return PyUnicode_Check(o) || PyBytes_Check(o); 57 | } 58 | #else 59 | PYCURL_INTERNAL int 60 | PyText_Check(PyObject *o) 61 | { 62 | return PyUnicode_Check(o) || PyString_Check(o); 63 | } 64 | #endif 65 | 66 | PYCURL_INTERNAL PyObject * 67 | PyText_FromString_Ignore(const char *string) 68 | { 69 | PyObject *v; 70 | 71 | #if PY_MAJOR_VERSION >= 3 72 | PyObject *u; 73 | 74 | v = Py_BuildValue("y", string); 75 | if (v == NULL) { 76 | return NULL; 77 | } 78 | 79 | u = PyUnicode_FromEncodedObject(v, NULL, "replace"); 80 | Py_DECREF(v); 81 | return u; 82 | #else 83 | v = Py_BuildValue("s", string); 84 | return v; 85 | #endif 86 | } 87 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include "pycurl.h" 2 | 3 | static PyObject * 4 | create_error_object(CurlObject *self, int code) 5 | { 6 | PyObject *s, *v; 7 | 8 | s = PyText_FromString_Ignore(self->error); 9 | if (s == NULL) { 10 | return NULL; 11 | } 12 | v = Py_BuildValue("(iO)", code, s); 13 | if (v == NULL) { 14 | Py_DECREF(s); 15 | return NULL; 16 | } 17 | return v; 18 | } 19 | 20 | PYCURL_INTERNAL void 21 | create_and_set_error_object(CurlObject *self, int code) 22 | { 23 | PyObject *e; 24 | 25 | self->error[sizeof(self->error) - 1] = 0; 26 | e = create_error_object(self, code); 27 | if (e != NULL) { 28 | PyErr_SetObject(ErrorObject, e); 29 | Py_DECREF(e); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # On recent windowses there is no localhost entry in hosts file, 2 | # hence localhost resolves fail. https://github.com/c-ares/c-ares/issues/85 3 | # FTP tests also seem to want the numeric IP address rather than localhost. 4 | localhost = '127.0.0.1' 5 | 6 | def setup_package(): 7 | # import here, not globally, so that running 8 | # python -m tests.appmanager 9 | # to launch the app manager is possible without having pycurl installed 10 | # (as the test app does not depend on pycurl) 11 | import pycurl 12 | 13 | print('Testing %s' % pycurl.version) 14 | -------------------------------------------------------------------------------- /tests/appmanager.py: -------------------------------------------------------------------------------- 1 | import sys, time, os 2 | 3 | def noop(*args): 4 | pass 5 | 6 | def setup(*specs): 7 | if os.environ.get('PYCURL_STANDALONE_APP') and os.environ['PYCURL_STANDALONE_APP'].lower() in ['1', 'yes', 'true']: 8 | return (noop, noop) 9 | else: 10 | return perform_setup(*specs) 11 | 12 | def perform_setup(*specs): 13 | from . import runwsgi 14 | 15 | app_specs = [] 16 | for spec in specs: 17 | app_module = __import__(spec[0], globals(), locals(), ['app'], 1) 18 | app = getattr(app_module, 'app') 19 | app_specs.append([app] + list(spec[1:])) 20 | 21 | return runwsgi.app_runner_setup(*app_specs) 22 | 23 | quit = False 24 | 25 | def sigterm_handler(*args): 26 | global quit 27 | quit = True 28 | 29 | def run_standalone(): 30 | import signal 31 | 32 | funcs = [] 33 | 34 | signal.signal(signal.SIGTERM, sigterm_handler) 35 | 36 | funcs.append(setup(('app', 8380))) 37 | funcs.append(setup(('app', 8381))) 38 | funcs.append(setup(('app', 8382))) 39 | funcs.append(setup(('app', 8383, dict(ssl=True)))) 40 | funcs.append(setup(('app', 8384, dict(ssl=True)))) 41 | 42 | for setup_func, teardown_func in funcs: 43 | setup_func(sys.modules[__name__]) 44 | 45 | sys.stdout.write("Running, use SIGTERM or SIGINT to stop\n") 46 | 47 | try: 48 | while not quit: 49 | time.sleep(1) 50 | except KeyboardInterrupt: 51 | pass 52 | 53 | for setup_func, teardown_func in funcs: 54 | teardown_func(sys.modules[__name__]) 55 | 56 | if __name__ == '__main__': 57 | run_standalone() 58 | -------------------------------------------------------------------------------- /tests/bin/realpath: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os.path 5 | 6 | if len(sys.argv) < 2: 7 | sys.stderr.write("Usage: realpath path\n") 8 | exit(2) 9 | 10 | sys.stdout.write(os.path.realpath(sys.argv[1]) + "\n") 11 | -------------------------------------------------------------------------------- /tests/c/winsockdup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define sassert assert 5 | 6 | void dump() { 7 | int err; 8 | LPTSTR buf; 9 | err = WSAGetLastError(); 10 | FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, &buf, 0, NULL); 11 | printf("%d %s\n", err, buf); 12 | LocalFree(buf); 13 | } 14 | 15 | int main() { 16 | SOCKET s1, s2, s1d, s2d; 17 | int val, rv, size; 18 | WSADATA wsadata; 19 | WSAPROTOCOL_INFO pi; 20 | 21 | rv = WSAStartup(MAKEWORD(2, 2), &wsadata); 22 | assert(!rv); 23 | 24 | s1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 25 | assert(s1 > 0); 26 | val = 1; 27 | rv = setsockopt(s1, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); 28 | dump(); 29 | sassert(!rv); 30 | 31 | s2 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 32 | assert(s2 > 0); 33 | val = 0; 34 | rv = setsockopt(s2, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); 35 | sassert(!rv); 36 | 37 | size = sizeof(val); 38 | rv = getsockopt(s1, SOL_SOCKET, SO_KEEPALIVE, &val, &size); 39 | assert(!rv); 40 | printf("%d\n", val); 41 | sassert(val == 1); 42 | 43 | rv = getsockopt(s2, SOL_SOCKET, SO_KEEPALIVE, &val, &size); 44 | assert(!rv); 45 | sassert(val == 0); 46 | 47 | rv = WSADuplicateSocket(s1, GetCurrentProcessId(), &pi); 48 | assert(!rv); 49 | s1d = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &pi, 0, WSA_FLAG_OVERLAPPED); 50 | assert(s1d > 0); 51 | 52 | rv = getsockopt(s1d, SOL_SOCKET, SO_KEEPALIVE, &val, &size); 53 | assert(!rv); 54 | printf("%d\n", val); 55 | sassert(val == 1); 56 | 57 | rv = WSADuplicateSocket(s2, GetCurrentProcessId(), &pi); 58 | assert(!rv); 59 | s2d = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &pi, 0, WSA_FLAG_OVERLAPPED); 60 | assert(s2d > 0); 61 | 62 | rv = getsockopt(s2d, SOL_SOCKET, SO_KEEPALIVE, &val, &size); 63 | assert(!rv); 64 | printf("%d\n", val); 65 | sassert(val == 0); 66 | } 67 | -------------------------------------------------------------------------------- /tests/cadata_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import os 6 | import pycurl 7 | import unittest 8 | 9 | from . import appmanager 10 | from . import util 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8384, dict(ssl=True))) 13 | 14 | class CaCertsTest(unittest.TestCase): 15 | def setUp(self): 16 | self.curl = util.DefaultCurlLocalhost(8384) 17 | 18 | def tearDown(self): 19 | self.curl.close() 20 | 21 | @util.only_ssl_backends('openssl') 22 | def test_request_with_verifypeer(self): 23 | with open(os.path.join(os.path.dirname(__file__), 'certs', 'ca.crt'), 'rb') as stream: 24 | cadata = stream.read().decode('ASCII') 25 | self.curl.setopt(pycurl.URL, 'https://localhost:8384/success') 26 | sio = util.BytesIO() 27 | self.curl.set_ca_certs(cadata) 28 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 29 | # self signed certificate, but ca cert should be loaded 30 | self.curl.setopt(pycurl.SSL_VERIFYPEER, 1) 31 | self.curl.perform() 32 | assert sio.getvalue().decode() == 'success' 33 | 34 | @util.only_ssl_backends('openssl') 35 | def test_set_ca_certs_bytes(self): 36 | self.curl.set_ca_certs(util.b('hello world\x02\xe0')) 37 | 38 | @util.only_ssl_backends('openssl') 39 | def test_set_ca_certs_bogus_type(self): 40 | try: 41 | self.curl.set_ca_certs(42) 42 | except TypeError as e: 43 | self.assertEqual('set_ca_certs argument must be a byte string or a Unicode string with ASCII code points only', str(e)) 44 | -------------------------------------------------------------------------------- /tests/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDwzCCAqugAwIBAgIUSxsCNrFED1qO/AQe5iz0sFzgdRowDQYJKoZIhvcNAQEL 3 | BQAwZjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxGjAYBgNVBAoM 4 | EVB5Y1VSTCB0ZXN0IHN1aXRlMRIwEAYDVQQLDAlsb2NhbGhvc3QxEjAQBgNVBAMM 5 | CWxvY2FsaG9zdDAeFw0yMjA1MDYxNTI5MDVaFw00OTA5MjExNTI5MDVaMGYxCzAJ 6 | BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRowGAYDVQQKDBFQeWNVUkwg 7 | dGVzdCBzdWl0ZTESMBAGA1UECwwJbG9jYWxob3N0MRIwEAYDVQQDDAlsb2NhbGhv 8 | c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtrtJgPWnNZCHEmvJg 9 | p/0C4o8l8tqaRypjTrZgFSChIMLHRmMV8xNra7TOobZW3YRO69WQOEEDD/QAXu8s 10 | KFT22MA0pe6FEgrMauoST/A4wXG1tVgbBz5W58Hc0EHd85cWPB7/IA/k39nDj4/c 11 | uSfg4BVEW+lGs2FGCLElRWmrOPPMQsyP5llwuVhaRQ5QN8wQgkd5n2wXF2tsQ2dO 12 | YmJ5fVDjs0P0f0TNCWhS9zxd/orV7UqWIiGWiZt2jdEsAZTNmVaUbZaisXNfXrUT 13 | aFjYUcUh31K6xYc0nEqyY5R6s2/StZh7Png47BdaH/Y4pw1XWErUgUqQQdQ/tQwu 14 | G8BTAgMBAAGjaTBnMB0GA1UdDgQWBBQu1NQH28vT0GEQe3ZIFM2R/ZpGwTAfBgNV 15 | HSMEGDAWgBQu1NQH28vT0GEQe3ZIFM2R/ZpGwTAPBgNVHRMBAf8EBTADAQH/MBQG 16 | A1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAQOYupzgDcLn3 17 | dT7lPXDrWSWFQQoNGDD3suu3UPIHuLIBTghADOTgAW9QxmcB5z7EWXj8TLRssAZ7 18 | 6bwPR1g466IgDpR7U+q9YIyBVW98MgYyaSX6TuRyrRxEugH5mzIKQ7Ed9qYiJrVA 19 | 5npHkQOXdlcdsjLsjD/itfbk/M13GuCMkixXM3RGcruUbd143aKFGVvGPhl31L14 20 | AOjQsbC1OQQ6rPsA5FObSqLjm6L+cq53PqOaRIiTRdiaKD2Xj8hXWrs5zivx60qM 21 | GQkUnTfyPuQ7EXf7jQrjwrCYN8lk+KbuE9FKKS89b6ZyfK7iZp6AR3IdYRAVo1FE 22 | H7ZnvduIZQ== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /tests/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAra7SYD1pzWQhxJryYKf9AuKPJfLamkcqY062YBUgoSDCx0Zj 3 | FfMTa2u0zqG2Vt2ETuvVkDhBAw/0AF7vLChU9tjANKXuhRIKzGrqEk/wOMFxtbVY 4 | Gwc+VufB3NBB3fOXFjwe/yAP5N/Zw4+P3Lkn4OAVRFvpRrNhRgixJUVpqzjzzELM 5 | j+ZZcLlYWkUOUDfMEIJHeZ9sFxdrbENnTmJieX1Q47ND9H9EzQloUvc8Xf6K1e1K 6 | liIhlombdo3RLAGUzZlWlG2WorFzX161E2hY2FHFId9SusWHNJxKsmOUerNv0rWY 7 | ez54OOwXWh/2OKcNV1hK1IFKkEHUP7UMLhvAUwIDAQABAoIBAGLsEpiMAgngwTbo 8 | hao1o+6TubKEiquaYvMi7s702ZvMPAQh++eRhfsF4npaMq9xBZ2pxv6Ye7bRzEi1 9 | yYWeBx59P6P86khSiWH6dw0tCIZa73fuLJtgWcpHv+wTlaBj0Cby4TiwOz1Bnhc7 10 | WlX+A0+acaJ4svn4yyuHYdX3ngLNwe0WkP0a6M0KOv+rxRW4FFITrO98Yz9PtPZP 11 | Z4nLvt6dsX6m879WIcFA+wkab4aMrWMI6b80wKN72sKAVC3LNU4iJ7PTYmbyIwJP 12 | YDOD/+UAp4T/VV+CIKOlS6YEozMpOD4kt4SxzEZQu8cuAovtKdKcOn0WhJYBJVGc 13 | LCJpSfkCgYEA2+JC/VCtF91d4/70S+Jswqc3T3ldifZXYRVH+iFeBSGrg++GHeKW 14 | JKRk0+v1Ul+P/6Ygd3ycsw/leKUr5c7+Fi3z2vk8q0i0815yQJuqFJIg+WTbeutv 15 | DiUj3UTycvFQtAreEMyAMB99fWmm4CuAuYVQIHojhm5SI3lQUjhBnu8CgYEAyjXj 16 | 0DygA8obXQaB2ljoj75ukDMN+0JCmZ+WqI2+obREAR2f+X7wHtbdwyQiwRBIV+b7 17 | wCnAXAACXFDEoFboN+0Ex7aFVjAc2BawdHmwilhxk2nYaCTgxQZLYUzEWSNp20WA 18 | BTUU5Tjs4fdDoKT7G6xMEaBqvooaImXxFVmjNN0CgYBeJPZBt3UlLqawo8y9YOjo 19 | Pugzoucl1s96xb3Xnsm+sLfa+YcW7JkUfz6cbf7PkhL5houIHVaKZFf/29h7wLCR 20 | loM+UlBjlfHD8cBBYWTlAdwUa9Z9PqiCCezdJFQaWrAPJkgGMUkBUbpNJBtLB9VJ 21 | mYbBIQps2HdasOpvCZ8vCQKBgQDF9jwxgSimjRZ83AIEYUZMc4KKaXEmqpfJDhPQ 22 | r/QRGwn4jagv+bXae0Bf6uCbYfVxGREd78ICT4AAIJJe5rYxCjnDy0x+NFwIsS3O 23 | 2dObnTqTtuvGCVSDjsX9W8pd+e2IXWIXtv/d6Pz/u7LZcqrjTKqsFwBpyYoMYwDC 24 | hh7hgQKBgCI2wr3QrDfabD9vZd/pO8v7jD3mk84fj4pO6D1c8wg4n6IffOqQih/z 25 | 1AU/RBIUIPERJweNGeG+YOlA2pE02u/J/0UpH5663vM76GQ7nY/Vr6rd4OcNJsR3 26 | xLlOz8XMzkqt+BcTsLfzjO4wAFEutUywDrT8DBkQR5nuUqHjVj8f 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDXjCCAkagAwIBAgIUGSiMniv/FqlCAJXut6b+TJx1gzYwDQYJKoZIhvcNAQEL 3 | BQAwZjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxGjAYBgNVBAoM 4 | EVB5Y1VSTCB0ZXN0IHN1aXRlMRIwEAYDVQQLDAlsb2NhbGhvc3QxEjAQBgNVBAMM 5 | CWxvY2FsaG9zdDAeFw0yMjA1MDYyMTU4NTJaFw00OTA5MjEyMTU4NTJaMFIxCzAJ 6 | BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRowGAYDVQQKDBFQeWNVUkwg 7 | dGVzdCBzdWl0ZTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEA5icu/uU40MkMpthiEhxV9Y1F2p8aT6EWk8dWLxF8Pj2D 9 | 3RYp1K+GYis8K1+h5dVm9KCH61e4NlGK5zO6hGrS44zimQ0xfJBqeUOfkAW2EE2c 10 | NtzgNdvqUFch1RslkH5hemIxC9CyY7RcUwP2dnwJ55m62TEXfhNwMTVgTjIJkiCm 11 | 20tfVUbw5UUmiZd5q+t9Dx+OtGkN85+a4zgjtiAKXkjE4BXKEU8Yc2GkTwz98meT 12 | tRnuK4HTxBxrzv9vSG+UZpwvYDBZ/AB3PYlzFV2a0oyF3Tf48yptQ1gPteUcmIrD 13 | 7z1Uu/2VkYskQ73aU1GUnTqy7kfNBITJwumIq8McEQIDAQABoxgwFjAUBgNVHREE 14 | DTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAC0y9o5FUCzpSEqBK7Ns 15 | 1FiAR/cNS6MKmpCKjN4sNvALSThCTdDB4QVBEOe+eTZP/q105oyf8boSktCG/3MO 16 | B6Jwdo5AnBHiE2QGfacMluUkuYRGf1XqWl9oa1AeuqCS+ilGk485akiI0A/z6ZRz 17 | ynGvk/9bqYYhqIPV2ioxFdHaXNlNKT36BV1NFrW3ebZSa3w7nHIrEakZuVmc67vH 18 | dCuxHf8l2Bya/xT1yktq4MaiFzUY9ZZLSnpWPuGzXnika0IeREF7rm9ubozq8mSq 19 | JyTC0KAZvwt7BmbEO98NcfL8gtYAVqDBR/t6gW4TSxKGc0PB3j/+73Nj5hGBasxd 20 | 2cc= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /tests/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA5icu/uU40MkMpthiEhxV9Y1F2p8aT6EWk8dWLxF8Pj2D3RYp 3 | 1K+GYis8K1+h5dVm9KCH61e4NlGK5zO6hGrS44zimQ0xfJBqeUOfkAW2EE2cNtzg 4 | NdvqUFch1RslkH5hemIxC9CyY7RcUwP2dnwJ55m62TEXfhNwMTVgTjIJkiCm20tf 5 | VUbw5UUmiZd5q+t9Dx+OtGkN85+a4zgjtiAKXkjE4BXKEU8Yc2GkTwz98meTtRnu 6 | K4HTxBxrzv9vSG+UZpwvYDBZ/AB3PYlzFV2a0oyF3Tf48yptQ1gPteUcmIrD7z1U 7 | u/2VkYskQ73aU1GUnTqy7kfNBITJwumIq8McEQIDAQABAoIBAEYzcXxCQsA8cuV5 8 | XwCTMA0EGGiE2yuqwQ42YS1eMf1yGgSXvA6ps13CPkokk2ddXlgDlzHLwd6fpLS8 9 | 7IlzY/wQfxWcFpoeGrv+Sm9NrqjuY1XArYsAF0qGKUWtUBnw0p7X0IoAEEmlO/v+ 10 | W3DsiMDh/UI+XSIRn8kCtOtlC9JMG0M7HAAOYsQHEpCqxPVp72mNR3zYnmQiWhSm 11 | cecKGr+EpAqRGSe8Yj8CjhVYuaM+qptaOYhZlShr3pv0HWnZTEvVIp1TzSJ7gypg 12 | uUvAIhvL6zSAjwMpDkIXicDUJxb08uwn/xjVOvgeJx5HjzTWDk6FwuUB52PUdVBn 13 | oWPUwaECgYEA9RqI7J4TGmF0abvYPDBx9BVzvA/pqWWPGT5EKfBT557V5LV9XTEW 14 | 8Fw0U85XFdxeW+E/0ULUmh0e7yN8T+uy6Hb97HXhqm8JCQSXimvLptyqP+5YCHPD 15 | CcTx4941TLTuEe+KNAZAd1syOrA92m9BwFjJSVcfXbXGVZxpFKwntV0CgYEA8GJ/ 16 | 6ndYRkIe3+WGNIqGDJSYHP60Xq4SKyMW4Pxt7z7Nrb6zHg34QLzhsQNZ5I1JDA4V 17 | umuYVNphxgb96xzS7WNK/QmgYnpjkoRm1eHSusSav9g8bvow1z+6KFq/qt1JBRKX 18 | F2BQ2u+QlC1zLJSElVRL5ay2tXboIv97OxFugkUCgYB9uaO8xAUGhjDhv7JWhX8e 19 | dhaMxBjWhLrXdwIeBSH08JvFGnd44yJiHtnUl0ZCd2yLcsp6e+50MzXX8vrkQAHg 20 | jpEHxxv/gb8/ufRF069+IzjNXGQZyc+k5jox6ZyrgS+RUa8xqndNAiGMyzSfJGy0 21 | zpZJoX/8YK6g4X9hVEF2HQKBgHHxrsKcKZq8Eth8er4C/4GNGgF8dlD+4BvUeS7S 22 | WOXz9hiqcUsIwiklnzGB7iVZF0wAjSodgEqQbZIplEjTE+R0kYIaAw1LCFHWMsyl 23 | S3c+ZEAVpqfQLkCJs5sXUQ0T8V3XLwlknU76CaVDWfnCuIn0ODm5Qa4InAai5W3d 24 | WG2lAoGBAJ0X6zG21dRN2O35Y5HIt0ydD0NZmuOYk+h8eIIkyAZDEDHeuqKOPwF1 25 | N5tUIBATZ5yHwy2wWOwKn+0+7i+1N8n9aC+qE7tWUsJOpgGLcl2Q0weSuQqIYNcN 26 | /yzGx5WcWbKfMTfn70vOju84f9FuO9DVYiNPg67H7aWJ7roKWzez 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/close_socket_cb_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import socket 7 | import unittest 8 | import pycurl 9 | 10 | from . import util 11 | from . import appmanager 12 | 13 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 14 | 15 | class CloseSocketCbTest(unittest.TestCase): 16 | def setUp(self): 17 | self.curl = util.DefaultCurl() 18 | self.curl.setopt(self.curl.URL, 'http://%s:8380/success' % localhost) 19 | self.curl.setopt(pycurl.FORBID_REUSE, True) 20 | 21 | def tearDown(self): 22 | self.curl.close() 23 | 24 | @util.min_libcurl(7, 21, 7) 25 | def test_closesocketfunction_ok(self): 26 | called = {} 27 | 28 | def closesocketfunction(curlfd): 29 | called['called'] = True 30 | # Unix only 31 | #os.close(curlfd) 32 | # Unix & Windows 33 | socket.fromfd(curlfd, socket.AF_INET, socket.SOCK_STREAM).close() 34 | return 0 35 | 36 | self.curl.setopt(pycurl.CLOSESOCKETFUNCTION, closesocketfunction) 37 | 38 | self.curl.perform() 39 | assert called['called'] 40 | 41 | @util.min_libcurl(7, 21, 7) 42 | def test_closesocketfunction_fail(self): 43 | called = {} 44 | 45 | def closesocketfunction(curlfd): 46 | called['called'] = True 47 | return 1 48 | 49 | self.curl.setopt(pycurl.CLOSESOCKETFUNCTION, closesocketfunction) 50 | 51 | # no exception on errors, apparently 52 | self.curl.perform() 53 | assert called['called'] 54 | 55 | @util.min_libcurl(7, 21, 7) 56 | def test_closesocketfunction_bogus_return(self): 57 | called = {} 58 | 59 | def closesocketfunction(curlfd): 60 | called['called'] = True 61 | return 'bogus' 62 | 63 | self.curl.setopt(pycurl.CLOSESOCKETFUNCTION, closesocketfunction) 64 | 65 | # no exception on errors, apparently 66 | self.curl.perform() 67 | assert called['called'] 68 | 69 | class CloseSocketCbUnsetTest(unittest.TestCase): 70 | def setUp(self): 71 | self.curl = util.DefaultCurl() 72 | 73 | @util.min_libcurl(7, 21, 7) 74 | def test_closesocketfunction_none(self): 75 | self.curl.setopt(pycurl.CLOSESOCKETFUNCTION, None) 76 | 77 | @util.min_libcurl(7, 21, 7) 78 | def test_closesocketfunction_unset(self): 79 | self.curl.unsetopt(pycurl.CLOSESOCKETFUNCTION) 80 | -------------------------------------------------------------------------------- /tests/debug_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import pycurl 7 | import unittest 8 | 9 | from . import appmanager 10 | from . import util 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 13 | 14 | class DebugTest(unittest.TestCase): 15 | def setUp(self): 16 | self.curl = util.DefaultCurl() 17 | self.debug_entries = [] 18 | 19 | def tearDown(self): 20 | self.curl.close() 21 | 22 | def debug_function(self, t, b): 23 | self.debug_entries.append((t, b)) 24 | 25 | def test_perform_get_with_debug_function(self): 26 | self.curl.setopt(pycurl.VERBOSE, 1) 27 | self.curl.setopt(pycurl.DEBUGFUNCTION, self.debug_function) 28 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 29 | sio = util.BytesIO() 30 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 31 | self.curl.perform() 32 | 33 | # Some checks with no particular intent 34 | self.check(0, util.b('Trying')) 35 | if util.pycurl_version_less_than(7, 24): 36 | self.check(0, util.b('connected')) 37 | else: 38 | self.check(0, util.b('Connected to %s' % localhost)) 39 | self.check(0, util.b('port 8380')) 40 | # request 41 | self.check(2, util.b('GET /success HTTP/1.1')) 42 | # response 43 | self.check(1, util.b('HTTP/1.0 200 OK')) 44 | self.check(1, util.b('Content-Length: 7')) 45 | # result 46 | self.check(3, util.b('success')) 47 | 48 | # test for #210 49 | def test_debug_unicode(self): 50 | self.curl.setopt(pycurl.VERBOSE, 1) 51 | self.curl.setopt(pycurl.DEBUGFUNCTION, self.debug_function) 52 | self.curl.setopt(pycurl.URL, 'http://%s:8380/utf8_body' % localhost) 53 | sio = util.BytesIO() 54 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 55 | self.curl.perform() 56 | 57 | # 3 = response body 58 | search = util.b('\xd0\x94\xd1\x80\xd1\x83\xd0\xb6\xd0\xb1\xd0\xb0 \xd0\xbd\xd0\xb0\xd1\x80\xd0\xbe\xd0\xb4\xd0\xbe\xd0\xb2').decode('utf8') 59 | self.check(3, search.encode('utf8')) 60 | 61 | def check(self, wanted_t, wanted_b): 62 | for t, b in self.debug_entries: 63 | if t == wanted_t and wanted_b in b: 64 | return 65 | assert False, "%d: %s not found in debug entries\nEntries are:\n%s" % \ 66 | (wanted_t, repr(wanted_b), repr(self.debug_entries)) 67 | -------------------------------------------------------------------------------- /tests/default_write_cb_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import unittest 7 | import pycurl 8 | import sys 9 | import tempfile 10 | import os 11 | 12 | from . import appmanager, util 13 | 14 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 15 | 16 | STDOUT_FD_NUM = 1 17 | 18 | def try_fsync(fd): 19 | try: 20 | os.fsync(fd) 21 | except OSError: 22 | # On travis: 23 | # OSError: [Errno 22] Invalid argument 24 | # ignore 25 | pass 26 | 27 | class DefaultWriteCbTest(unittest.TestCase): 28 | def setUp(self): 29 | self.curl = util.DefaultCurl() 30 | 31 | def tearDown(self): 32 | self.curl.close() 33 | 34 | def test_perform_get(self): 35 | # This test performs a GET request without doing anything else. 36 | # Unfortunately, the default curl behavior is to print response 37 | # body to standard output, which spams test output. 38 | # As a result this test is commented out. Uncomment for debugging. 39 | # test_perform_get_with_default_write_function is the test 40 | # which exercises default curl write handler. 41 | 42 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 43 | self.curl.perform() 44 | # If this flush is not done, stdout output bleeds into the next test 45 | # that is executed (without nose output capture) 46 | sys.stdout.flush() 47 | try_fsync(STDOUT_FD_NUM) 48 | 49 | # I have a really hard time getting this to work with nose output capture 50 | def skip_perform_get_with_default_write_function(self): 51 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 52 | f = tempfile.NamedTemporaryFile() 53 | try: 54 | #with open('w', 'w+') as f: 55 | # nose output capture plugin replaces sys.stdout with a StringIO 56 | # instance. We want to redirect the underlying file descriptor 57 | # anyway because underlying C code uses it. 58 | # Therefore: 59 | # 1. Use file descriptor 1 rather than sys.stdout.fileno() to 60 | # reference the standard output file descriptor. 61 | # 2. We do not touch sys.stdout. This means anything written to 62 | # sys.stdout will be captured by nose, and not make it to our code. 63 | # But the output we care about happens at libcurl level, below 64 | # nose, therefore this is fine. 65 | saved_stdout_fd = os.dup(STDOUT_FD_NUM) 66 | os.dup2(f.fileno(), STDOUT_FD_NUM) 67 | #os.dup2(1, 100) 68 | #os.dup2(2, 1) 69 | # We also need to flush the output that libcurl wrote to stdout. 70 | # Since sys.stdout might be nose's StringIO instance, open the 71 | # stdout file descriptor manually. 72 | 73 | try: 74 | self.curl.perform() 75 | sys.stdout.flush() 76 | finally: 77 | try_fsync(STDOUT_FD_NUM) 78 | os.dup2(saved_stdout_fd, STDOUT_FD_NUM) 79 | os.close(saved_stdout_fd) 80 | #os.dup2(100, 1) 81 | f.seek(0) 82 | body = f.read() 83 | finally: 84 | f.close() 85 | self.assertEqual('success', body) 86 | -------------------------------------------------------------------------------- /tests/error_constants_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import unittest 7 | 8 | from . import util 9 | 10 | class ErrorConstantsTest(unittest.TestCase): 11 | @util.min_libcurl(7, 21, 5) 12 | def test_not_built_in(self): 13 | assert hasattr(pycurl, 'E_NOT_BUILT_IN') 14 | 15 | @util.min_libcurl(7, 24, 0) 16 | def test_ftp_accept_failed(self): 17 | assert hasattr(pycurl, 'E_FTP_ACCEPT_FAILED') 18 | 19 | @util.min_libcurl(7, 21, 5) 20 | def test_unknown_option(self): 21 | assert hasattr(pycurl, 'E_UNKNOWN_OPTION') 22 | 23 | @util.min_libcurl(7, 39, 0) 24 | def test_pinnedpubkeynotmatch(self): 25 | assert hasattr(pycurl, 'E_SSL_PINNEDPUBKEYNOTMATCH') 26 | -------------------------------------------------------------------------------- /tests/error_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import sys 7 | import unittest 8 | 9 | class ErrorTest(unittest.TestCase): 10 | def setUp(self): 11 | self.curl = pycurl.Curl() 12 | 13 | def tearDown(self): 14 | self.curl.close() 15 | 16 | # error originating in libcurl 17 | def test_pycurl_error_libcurl(self): 18 | try: 19 | # perform without a url 20 | self.curl.perform() 21 | except pycurl.error: 22 | exc_type, exc = sys.exc_info()[:2] 23 | assert exc_type == pycurl.error 24 | # pycurl.error's arguments are libcurl errno and message 25 | self.assertEqual(2, len(exc.args)) 26 | self.assertEqual(int, type(exc.args[0])) 27 | self.assertEqual(str, type(exc.args[1])) 28 | # unpack 29 | err, msg = exc.args 30 | self.assertEqual(pycurl.E_URL_MALFORMAT, err) 31 | # possibly fragile 32 | # curl < 7.83.0 has an exclamation mark in this error message 33 | self.assertIn(msg, ['No URL set!', 'No URL set']) 34 | else: 35 | self.fail('Expected pycurl.error to be raised') 36 | 37 | def test_pycurl_errstr_initially_empty(self): 38 | self.assertEqual('', self.curl.errstr()) 39 | 40 | def test_pycurl_errstr_type(self): 41 | self.assertEqual('', self.curl.errstr()) 42 | try: 43 | # perform without a url 44 | self.curl.perform() 45 | except pycurl.error: 46 | # might be fragile 47 | # curl < 7.83.0 has an exclamation mark in this error message 48 | self.assertIn(self.curl.errstr(), ['No URL set!', 'No URL set']) 49 | # repeated checks do not clear value 50 | self.assertIn(self.curl.errstr(), ['No URL set!', 'No URL set']) 51 | # check the type - on all python versions 52 | self.assertEqual(str, type(self.curl.errstr())) 53 | else: 54 | self.fail('no exception') 55 | 56 | # pycurl raises standard library exceptions in some cases 57 | def test_pycurl_error_stdlib(self): 58 | try: 59 | # set an option of the wrong type 60 | self.curl.setopt(pycurl.WRITEFUNCTION, True) 61 | except TypeError: 62 | exc_type, exc = sys.exc_info()[:2] 63 | else: 64 | self.fail('Expected TypeError to be raised') 65 | 66 | # error originating in pycurl 67 | # looks like currently there are none 68 | def xtest_pycurl_error_pycurl(self): 69 | try: 70 | # invalid option combination 71 | self.curl.setopt(pycurl.WRITEFUNCTION, lambda x: x) 72 | f = open(__file__) 73 | try: 74 | self.curl.setopt(pycurl.WRITEHEADER, f) 75 | finally: 76 | f.close() 77 | except pycurl.error: 78 | exc_type, exc = sys.exc_info()[:2] 79 | assert exc_type == pycurl.error 80 | # for non-libcurl errors, arguments are just the error string 81 | self.assertEqual(1, len(exc.args)) 82 | self.assertEqual(str, type(exc.args[0])) 83 | self.assertEqual('cannot combine WRITEHEADER with WRITEFUNCTION.', exc.args[0]) 84 | else: 85 | self.fail('Expected pycurl.error to be raised') 86 | -------------------------------------------------------------------------------- /tests/ext/test-lib.sh: -------------------------------------------------------------------------------- 1 | # shell test framework based on test framework in rpg: 2 | # https://github.com/rtomayko/rpg 3 | # 4 | # Copyright (c) 2010 Ryan Tomayko 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to 8 | # deal in the Software without restriction, including without limitation the 9 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | # sell copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | # THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | : ${VERBOSE:=false} 24 | 25 | unset CDPATH 26 | 27 | #cd "$(dirname $0)" 28 | if test -z "$TESTDIR"; then 29 | TESTDIR=$(realpath $(pwd)) 30 | fi 31 | 32 | test_count=0 33 | successes=0 34 | failures=0 35 | 36 | output="$TESTDIR/$(basename "$0" .sh).out" 37 | trap "rm -f $output" 0 38 | 39 | succeeds () { 40 | test_count=$(( test_count + 1 )) 41 | echo "\$ ${2:-$1}" > "$output" 42 | eval "( ${2:-$1} )" 1>>"$output" 2>&1 43 | ec=$? 44 | if test $ec -eq 0 45 | then successes=$(( successes + 1 )) 46 | printf 'ok %d - %s\n' $test_count "$1" 47 | else failures=$(( failures + 1 )) 48 | printf 'not ok %d - %s [%d]\n' $test_count "$1" "$ec" 49 | fi 50 | 51 | $VERBOSE && dcat $output 52 | return 0 53 | } 54 | 55 | fails () { 56 | if test $# -eq 1 57 | then succeeds "! $1" 58 | else succeeds "$1" "! $2" 59 | fi 60 | } 61 | 62 | diag () { echo "$@" | sed 's/^/# /'; } 63 | dcat () { cat "$@" | sed 's/^/# /'; } 64 | desc () { diag "$@"; } 65 | 66 | setup () { 67 | rm -rf "$TESTDIR/trash" 68 | return 0 69 | } 70 | -------------------------------------------------------------------------------- /tests/ext/test-suite.sh: -------------------------------------------------------------------------------- 1 | # 2 | 3 | dir=$(dirname "$0") 4 | 5 | export PATH="$(pwd)/tests/bin":$PATH 6 | 7 | . "$dir"/test-lib.sh 8 | 9 | setup 10 | 11 | desc 'setup.py without arguments' 12 | fails 'python setup.py' 13 | succeeds 'python setup.py 2>&1 |grep "usage: setup.py"' 14 | 15 | desc 'setup.py --help' 16 | succeeds 'python setup.py --help' 17 | # .* = Unix|Windows 18 | succeeds 'python setup.py --help |grep "PycURL .* options:"' 19 | # distutils help 20 | succeeds 'python setup.py --help |grep "Common commands:"' 21 | 22 | desc 'setup.py --help with bogus --curl-config' 23 | succeeds 'python setup.py --help --curl-config=/dev/null' 24 | succeeds 'python setup.py --help --curl-config=/dev/null |grep "PycURL .* options:"' 25 | # this checks that --curl-config is consumed prior to 26 | # distutils processing --help 27 | fails 'python setup.py --help --curl-config=/dev/null 2>&1 |grep "option .* not recognized"' 28 | -------------------------------------------------------------------------------- /tests/fake-curl/curl-config-empty: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A curl-config that returns empty responses as much as possible 4 | 5 | output= 6 | 7 | while test -n "$1"; do 8 | case "$1" in 9 | --libs) 10 | # --libs or --static-libs must succeed and produce output 11 | echo '-lcurl' 12 | ;; 13 | esac 14 | shift 15 | done 16 | -------------------------------------------------------------------------------- /tests/fake-curl/curl-config-libs-and-static-libs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A curl-config that returns different libraries in --libs and --static-libs 4 | 5 | output= 6 | 7 | while test -n "$1"; do 8 | case "$1" in 9 | --libs) 10 | echo '-lcurl -lflurby' 11 | ;; 12 | --static-libs) 13 | echo '-lkzzert' 14 | ;; 15 | esac 16 | shift 17 | done 18 | -------------------------------------------------------------------------------- /tests/fake-curl/curl-config-ssl-feature-only: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A curl-config that indicates SSL is supported but does not say 4 | # which SSL library is being used 5 | 6 | output= 7 | 8 | while test -n "$1"; do 9 | case "$1" in 10 | --libs) 11 | echo '-lcurl' 12 | ;; 13 | --features) 14 | echo 'SSL' 15 | ;; 16 | esac 17 | shift 18 | done 19 | -------------------------------------------------------------------------------- /tests/fake-curl/curl-config-ssl-in-libs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A curl-config that returns -lssl in --libs but not in --static-libs 4 | 5 | output= 6 | 7 | while test -n "$1"; do 8 | case "$1" in 9 | --libs) 10 | echo '-lcurl -lssl' 11 | ;; 12 | --features) 13 | echo 'SSL' 14 | ;; 15 | esac 16 | shift 17 | done 18 | -------------------------------------------------------------------------------- /tests/fake-curl/curl-config-ssl-in-static-libs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A curl-config that returns -lssl in --static-libs but not in --libs 4 | 5 | output= 6 | 7 | while test -n "$1"; do 8 | case "$1" in 9 | --libs) 10 | echo '-lcurl' 11 | ;; 12 | --static-libs) 13 | echo '-lssl' 14 | ;; 15 | --features) 16 | echo 'SSL' 17 | ;; 18 | esac 19 | shift 20 | done 21 | -------------------------------------------------------------------------------- /tests/fake-curl/libcurl/Makefile: -------------------------------------------------------------------------------- 1 | ALL = \ 2 | with_gnutls.so \ 3 | with_nss.so \ 4 | with_openssl.so \ 5 | with_unknown_ssl.so \ 6 | without_ssl.so 7 | 8 | all: $(ALL) 9 | clean: 10 | rm -f $(ALL) 11 | 12 | .SUFFIXES: .c .so 13 | 14 | CC = `curl-config --cc` 15 | CFLAGS += `curl-config --cflags` 16 | UNAME := $(shell uname -s) 17 | ifeq ($(UNAME),Darwin) 18 | SONAME_FLAG = -install_name 19 | else 20 | SONAME_FLAG = -soname 21 | endif 22 | 23 | .c.so: 24 | $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fPIC \ 25 | -Wl,$(SONAME_FLAG),$@ -o $@ $< 26 | 27 | show-targets: 28 | ls *c |sed -e 's/.c$$/.so/' | awk '{print $$1 " \\"}' 29 | -------------------------------------------------------------------------------- /tests/fake-curl/libcurl/with_gnutls.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const char *protocols[] = { 4 | }; 5 | 6 | static curl_version_info_data version_info = { 7 | /* age */ 8 | 3, 9 | /* version */ 10 | "", 11 | /* version_num */ 12 | 0, 13 | /* host */ 14 | "", 15 | /* features */ 16 | 0, 17 | /* ssl_version */ 18 | "GnuTLS/2.11", 19 | /* ssl_version_num */ 20 | 0, 21 | /* libz_version */ 22 | "", 23 | /* protocols */ 24 | protocols 25 | }; 26 | 27 | curl_version_info_data *curl_version_info(CURLversion type) { 28 | return &version_info; 29 | } 30 | -------------------------------------------------------------------------------- /tests/fake-curl/libcurl/with_nss.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const char *protocols[] = { 4 | }; 5 | 6 | static curl_version_info_data version_info = { 7 | /* age */ 8 | 3, 9 | /* version */ 10 | "", 11 | /* version_num */ 12 | 0, 13 | /* host */ 14 | "", 15 | /* features */ 16 | 0, 17 | /* ssl_version */ 18 | "NSS/3.0", 19 | /* ssl_version_num */ 20 | 0, 21 | /* libz_version */ 22 | "", 23 | /* protocols */ 24 | protocols 25 | }; 26 | 27 | curl_version_info_data *curl_version_info(CURLversion type) { 28 | return &version_info; 29 | } 30 | -------------------------------------------------------------------------------- /tests/fake-curl/libcurl/with_openssl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const char *protocols[] = { 4 | }; 5 | 6 | static curl_version_info_data version_info = { 7 | /* age */ 8 | 3, 9 | /* version */ 10 | "", 11 | /* version_num */ 12 | 0, 13 | /* host */ 14 | "", 15 | /* features */ 16 | 0, 17 | /* ssl_version */ 18 | "OpenSSL/1.0.1a", 19 | /* ssl_version_num */ 20 | 0, 21 | /* libz_version */ 22 | "", 23 | /* protocols */ 24 | protocols 25 | }; 26 | 27 | curl_version_info_data *curl_version_info(CURLversion type) { 28 | return &version_info; 29 | } 30 | -------------------------------------------------------------------------------- /tests/fake-curl/libcurl/with_unknown_ssl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const char *protocols[] = { 4 | }; 5 | 6 | static curl_version_info_data version_info = { 7 | /* age */ 8 | 3, 9 | /* version */ 10 | "", 11 | /* version_num */ 12 | 0, 13 | /* host */ 14 | "", 15 | /* features */ 16 | 0, 17 | /* ssl_version */ 18 | "HelloWorldSSL/1.0", 19 | /* ssl_version_num */ 20 | 0, 21 | /* libz_version */ 22 | "", 23 | /* protocols */ 24 | protocols 25 | }; 26 | 27 | curl_version_info_data *curl_version_info(CURLversion type) { 28 | return &version_info; 29 | } 30 | -------------------------------------------------------------------------------- /tests/fake-curl/libcurl/without_ssl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const char *protocols[] = { 4 | }; 5 | 6 | static curl_version_info_data version_info = { 7 | /* age */ 8 | 3, 9 | /* version */ 10 | "", 11 | /* version_num */ 12 | 0, 13 | /* host */ 14 | "", 15 | /* features */ 16 | 0, 17 | /* ssl_version */ 18 | "", 19 | /* ssl_version_num */ 20 | 0, 21 | /* libz_version */ 22 | "", 23 | /* protocols */ 24 | protocols 25 | }; 26 | 27 | curl_version_info_data *curl_version_info(CURLversion type) { 28 | return &version_info; 29 | } 30 | -------------------------------------------------------------------------------- /tests/fixtures/form_submission.txt: -------------------------------------------------------------------------------- 1 | foo=bar -------------------------------------------------------------------------------- /tests/ftp_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | # Note: this test is meant to be run from pycurl project root. 6 | 7 | import pycurl 8 | import unittest 9 | 10 | from . import util 11 | from . import procmgr, localhost 12 | 13 | setup_module, teardown_module = procmgr.vsftpd_setup() 14 | 15 | class FtpTest(unittest.TestCase): 16 | def setUp(self): 17 | self.curl = util.DefaultCurl() 18 | 19 | def tearDown(self): 20 | self.curl.close() 21 | 22 | def test_get_ftp(self): 23 | self.curl.setopt(pycurl.URL, 'ftp://%s:8321' % localhost) 24 | sio = util.BytesIO() 25 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 26 | self.curl.perform() 27 | 28 | result = sio.getvalue().decode() 29 | assert 'README.rst' in result 30 | assert 'INSTALL.rst' in result 31 | 32 | # XXX this test needs to be fixed 33 | def test_quote(self): 34 | self.curl.setopt(pycurl.URL, 'ftp://%s:8321' % localhost) 35 | sio = util.BytesIO() 36 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 37 | self.curl.setopt(pycurl.QUOTE, ['CWD tests']) 38 | self.curl.perform() 39 | 40 | result = sio.getvalue().decode() 41 | assert 'README.rst' not in result 42 | assert 'ftp_test.py' in result 43 | 44 | def test_epsv(self): 45 | self.curl.setopt(pycurl.URL, 'ftp://%s:8321' % localhost) 46 | sio = util.BytesIO() 47 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 48 | self.curl.setopt(pycurl.FTP_USE_EPSV, 1) 49 | self.curl.perform() 50 | 51 | result = sio.getvalue().decode() 52 | assert 'README.rst' in result 53 | assert 'INSTALL.rst' in result 54 | -------------------------------------------------------------------------------- /tests/global_init_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import pytest 7 | import unittest 8 | 9 | from . import util 10 | 11 | class GlobalInitTest(unittest.TestCase): 12 | def test_global_init_default(self): 13 | # initialize libcurl with DEFAULT flags 14 | pycurl.global_init(pycurl.GLOBAL_DEFAULT) 15 | pycurl.global_cleanup() 16 | 17 | def test_global_init_ack_eintr(self): 18 | # the GLOBAL_ACK_EINTR flag was introduced in libcurl-7.30, but can also 19 | # be backported for older versions of libcurl at the distribution level 20 | if util.pycurl_version_less_than(7, 30) and not hasattr(pycurl, 'GLOBAL_ACK_EINTR'): 21 | raise unittest.SkipTest('libcurl < 7.30.0 or no GLOBAL_ACK_EINTR') 22 | 23 | # initialize libcurl with the GLOBAL_ACK_EINTR flag 24 | pycurl.global_init(pycurl.GLOBAL_ACK_EINTR) 25 | pycurl.global_cleanup() 26 | 27 | def test_global_init_bogus(self): 28 | # initialize libcurl with bogus flags 29 | with pytest.raises(ValueError): 30 | pycurl.global_init(0xffff) 31 | -------------------------------------------------------------------------------- /tests/header_cb_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import pycurl 7 | import unittest 8 | import time as _time 9 | 10 | from . import appmanager 11 | from . import util 12 | 13 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 14 | 15 | class HeaderCbTest(unittest.TestCase): 16 | def setUp(self): 17 | self.curl = util.DefaultCurl() 18 | self.header_lines = [] 19 | 20 | def tearDown(self): 21 | self.curl.close() 22 | 23 | def header_function(self, line): 24 | self.header_lines.append(line.decode()) 25 | 26 | def test_get(self): 27 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 28 | sio = util.BytesIO() 29 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 30 | self.curl.setopt(pycurl.HEADERFUNCTION, self.header_function) 31 | self.curl.perform() 32 | self.assertEqual('success', sio.getvalue().decode()) 33 | 34 | assert len(self.header_lines) > 0 35 | self.assertEqual("HTTP/1.0 200 OK\r\n", self.header_lines[0]) 36 | # day of week 37 | # important: must be in utc 38 | todays_day = _time.strftime('%a', _time.gmtime()) 39 | # Date: Sun, 03 Mar 2013 05:38:12 GMT\r\n 40 | self.check('Date: %s' % todays_day) 41 | # Server: WSGIServer/0.1 Python/2.7.3\r\n 42 | self.check('Server: WSGIServer') 43 | self.check('Content-Length: 7') 44 | self.check('Content-Type: text/html') 45 | 46 | def check(self, wanted_text): 47 | for line in self.header_lines: 48 | if wanted_text in line: 49 | return 50 | assert False, "%s not found in header lines" % wanted_text 51 | -------------------------------------------------------------------------------- /tests/header_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import pytest 7 | import pycurl 8 | import unittest 9 | 10 | from . import appmanager 11 | from . import util 12 | 13 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 14 | 15 | # NB: HTTP RFC requires headers to be latin1 encoded, which we violate. 16 | # See the comments under /header_utf8 route in app.py. 17 | 18 | class HeaderTest(unittest.TestCase): 19 | def setUp(self): 20 | self.curl = util.DefaultCurl() 21 | 22 | def tearDown(self): 23 | self.curl.close() 24 | 25 | def test_ascii_string_header(self): 26 | self.check('x-test-header: ascii', 'ascii') 27 | 28 | def test_ascii_unicode_header(self): 29 | self.check(util.u('x-test-header: ascii'), 'ascii') 30 | 31 | # on python 2 unicode is accepted in strings because strings are byte strings 32 | @util.only_python3 33 | def test_unicode_string_header(self): 34 | with pytest.raises(UnicodeEncodeError): 35 | self.check('x-test-header: Москва', 'Москва') 36 | 37 | def test_unicode_unicode_header(self): 38 | with pytest.raises(UnicodeEncodeError): 39 | self.check(util.u('x-test-header: Москва'), util.u('Москва')) 40 | 41 | def test_encoded_unicode_header(self): 42 | self.check(util.u('x-test-header: Москва').encode('utf-8'), util.u('Москва')) 43 | 44 | def check(self, send, expected): 45 | # check as list and as tuple, because they may be handled differently 46 | self.do_check([send], expected) 47 | self.do_check((send,), expected) 48 | 49 | def do_check(self, send, expected): 50 | self.curl.setopt(pycurl.URL, 'http://%s:8380/header_utf8?h=x-test-header' % localhost) 51 | sio = util.BytesIO() 52 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 53 | self.curl.setopt(pycurl.HTTPHEADER, send) 54 | self.curl.perform() 55 | self.assertEqual(expected, sio.getvalue().decode('utf-8')) 56 | -------------------------------------------------------------------------------- /tests/high_level_curl_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | # uses the high level interface 7 | import curl 8 | import unittest 9 | 10 | from . import appmanager 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 13 | 14 | class RelativeUrlTest(unittest.TestCase): 15 | def setUp(self): 16 | self.curl = curl.Curl('http://%s:8380/' % localhost) 17 | 18 | def tearDown(self): 19 | self.curl.close() 20 | 21 | def test_get(self): 22 | result = self.curl.get('/success') 23 | self.assertEqual('success', result.decode()) 24 | 25 | def test_head(self): 26 | result = self.curl.head('/success') 27 | self.assertEqual('', result.decode()) 28 | self.assertEqual(200, self.curl.info()['http-code']) 29 | 30 | def test_reuse(self): 31 | result = self.curl.get('/success') 32 | self.assertEqual('success', result.decode()) 33 | 34 | result = self.curl.get('/success') 35 | self.assertEqual('success', result.decode()) 36 | -------------------------------------------------------------------------------- /tests/info_constants_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import unittest 7 | 8 | from . import util 9 | 10 | class InfoConstantsTest(unittest.TestCase): 11 | # CURLINFO_CONDITION_UNMET was introduced in libcurl-7.19.4 12 | @util.min_libcurl(7, 19, 4) 13 | def test_condition_unmet(self): 14 | curl = pycurl.Curl() 15 | assert hasattr(curl, 'CONDITION_UNMET') 16 | curl.close() 17 | -------------------------------------------------------------------------------- /tests/info_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import unittest 7 | 8 | from . import util 9 | 10 | class InfoTest(unittest.TestCase): 11 | @util.only_ssl 12 | def test_ssl_engines(self): 13 | curl = pycurl.Curl() 14 | engines = curl.getinfo(curl.SSL_ENGINES) 15 | # Typical result: 16 | # - an empty list in some configurations 17 | # - ['rdrand', 'dynamic'] 18 | self.assertEqual(type(engines), list) 19 | curl.close() 20 | -------------------------------------------------------------------------------- /tests/matrix/check-python.py: -------------------------------------------------------------------------------- 1 | import zlib 2 | import ssl 3 | 4 | dict(zlib=zlib, ssl=ssl) 5 | -------------------------------------------------------------------------------- /tests/matrix/curl-7.19.0-sslv2-2b0e09b0f98.patch: -------------------------------------------------------------------------------- 1 | commit 2b0e09b0f98e0f67417652dd7f4afd59bf895326 2 | Author: Daniel Stenberg 3 | Date: Tue Dec 6 14:22:45 2011 +0100 4 | 5 | OpenSSL: check for the SSLv2 function in configure 6 | 7 | If no SSLv2 was detected in OpenSSL by configure, then we enforce the 8 | OPENSSL_NO_SSL2 define as it seems some people report it not being 9 | defined properly in the OpenSSL headers. 10 | 11 | diff --git a/configure.ac b/configure.ac 12 | index 94cdd83..4bf25dc 100644 13 | --- a/configure.ac 14 | +++ b/configure.ac 15 | @@ -1514,7 +1514,8 @@ if test X"$OPT_SSL" != Xno; then 16 | RAND_egd \ 17 | ENGINE_cleanup \ 18 | CRYPTO_cleanup_all_ex_data \ 19 | - SSL_get_shutdown ) 20 | + SSL_get_shutdown \ 21 | + SSLv2_client_method ) 22 | 23 | dnl Make an attempt to detect if this is actually yassl's headers and 24 | dnl OpenSSL emulation layer. We still leave everything else believing 25 | diff --git a/lib/ssluse.c b/lib/ssluse.c 26 | index af70fe0..8deea26 100644 27 | --- a/lib/ssluse.c 28 | +++ b/lib/ssluse.c 29 | @@ -127,6 +127,11 @@ 30 | #define HAVE_ERR_REMOVE_THREAD_STATE 1 31 | #endif 32 | 33 | +#ifndef HAVE_SSLV2_CLIENT_METHOD 34 | +#undef OPENSSL_NO_SSL2 /* undef first to avoid compiler warnings */ 35 | +#define OPENSSL_NO_SSL2 36 | +#endif 37 | + 38 | /* 39 | * Number of bytes to read from the random number seed file. This must be 40 | * a finite value (because some entropy "files" like /dev/urandom have 41 | -------------------------------------------------------------------------------- /tests/matrix/curl-7.19.0-sslv2-c66b0b32fba-modified.patch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycq0125/pycurl/7b5e12c80cc1ed4578c7276545a7b2be2e4dcc4b/tests/matrix/curl-7.19.0-sslv2-c66b0b32fba-modified.patch -------------------------------------------------------------------------------- /tests/multi_memory_mgmt_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import unittest 7 | import gc 8 | import flaky 9 | import weakref 10 | 11 | from . import util 12 | 13 | debug = False 14 | 15 | @flaky.flaky(max_runs=3) 16 | class MultiMemoryMgmtTest(unittest.TestCase): 17 | def test_opensocketfunction_collection(self): 18 | self.check_callback(pycurl.M_SOCKETFUNCTION) 19 | 20 | def test_seekfunction_collection(self): 21 | self.check_callback(pycurl.M_TIMERFUNCTION) 22 | 23 | def check_callback(self, callback): 24 | # Note: extracting a context manager seems to result in 25 | # everything being garbage collected even if the C code 26 | # does not clear the callback 27 | object_count = 0 28 | gc.collect() 29 | # gc.collect() can create new objects... running it again here 30 | # settles tracked object count for the actual test below 31 | gc.collect() 32 | object_count = len(gc.get_objects()) 33 | 34 | c = pycurl.CurlMulti() 35 | c.setopt(callback, lambda x: True) 36 | del c 37 | 38 | gc.collect() 39 | new_object_count = len(gc.get_objects()) 40 | # it seems that GC sometimes collects something that existed 41 | # before this test ran, GH issues #273/#274 42 | self.assertIn(new_object_count, (object_count, object_count-1)) 43 | 44 | def test_curl_ref(self): 45 | c = util.DefaultCurl() 46 | m = pycurl.CurlMulti() 47 | 48 | ref = weakref.ref(c) 49 | m.add_handle(c) 50 | del c 51 | 52 | assert ref() 53 | gc.collect() 54 | assert ref() 55 | 56 | m.remove_handle(ref()) 57 | gc.collect() 58 | assert ref() is None 59 | -------------------------------------------------------------------------------- /tests/multi_timer_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import pycurl 7 | import unittest 8 | 9 | from . import appmanager 10 | from . import util 11 | 12 | setup_module_1, teardown_module_1 = appmanager.setup(('app', 8380)) 13 | setup_module_2, teardown_module_2 = appmanager.setup(('app', 8381)) 14 | setup_module_3, teardown_module_3 = appmanager.setup(('app', 8382)) 15 | 16 | def setup_module(mod): 17 | setup_module_1(mod) 18 | setup_module_2(mod) 19 | setup_module_3(mod) 20 | 21 | def teardown_module(mod): 22 | teardown_module_3(mod) 23 | teardown_module_2(mod) 24 | teardown_module_1(mod) 25 | 26 | class MultiSocketTest(unittest.TestCase): 27 | def test_multi_timer(self): 28 | urls = [ 29 | 'http://%s:8380/success' % localhost, 30 | 'http://%s:8381/success' % localhost, 31 | 'http://%s:8382/success' % localhost, 32 | ] 33 | 34 | timers = [] 35 | 36 | # timer callback 37 | def timer(msecs): 38 | #print('Timer callback msecs:', msecs) 39 | timers.append(msecs) 40 | 41 | # init 42 | m = pycurl.CurlMulti() 43 | m.setopt(pycurl.M_TIMERFUNCTION, timer) 44 | m.handles = [] 45 | for url in urls: 46 | c = util.DefaultCurl() 47 | # save info in standard Python attributes 48 | c.url = url 49 | c.body = util.BytesIO() 50 | c.http_code = -1 51 | m.handles.append(c) 52 | # pycurl API calls 53 | c.setopt(c.URL, c.url) 54 | c.setopt(c.WRITEFUNCTION, c.body.write) 55 | m.add_handle(c) 56 | 57 | # get data 58 | num_handles = len(m.handles) 59 | while num_handles: 60 | while 1: 61 | ret, num_handles = m.perform() 62 | if ret != pycurl.E_CALL_MULTI_PERFORM: 63 | break 64 | # currently no more I/O is pending, could do something in the meantime 65 | # (display a progress bar, etc.) 66 | m.select(1.0) 67 | 68 | for c in m.handles: 69 | # save info in standard Python attributes 70 | c.http_code = c.getinfo(c.HTTP_CODE) 71 | 72 | # print result 73 | for c in m.handles: 74 | self.assertEqual('success', c.body.getvalue().decode()) 75 | self.assertEqual(200, c.http_code) 76 | 77 | assert len(timers) > 0 78 | # libcurl 7.23.0 produces a 0 timer 79 | assert timers[0] >= 0 80 | # this assertion does not appear to hold on older libcurls 81 | # or apparently on any linuxes, see 82 | # https://github.com/p/pycurl/issues/19 83 | #if not util.pycurl_version_less_than(7, 24): 84 | # self.assertEqual(-1, timers[-1]) 85 | 86 | # close handles 87 | for c in m.handles: 88 | # pycurl API calls 89 | m.remove_handle(c) 90 | c.close() 91 | m.close() 92 | -------------------------------------------------------------------------------- /tests/pause_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import flaky 7 | import pycurl 8 | import unittest, signal 9 | import time as _time 10 | 11 | from . import appmanager 12 | from . import util 13 | 14 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 15 | 16 | @flaky.flaky(max_runs=3) 17 | class PauseTest(unittest.TestCase): 18 | def setUp(self): 19 | self.curl = util.DefaultCurl() 20 | 21 | def tearDown(self): 22 | self.curl.close() 23 | 24 | def test_pause_via_call(self): 25 | self.check_pause(True) 26 | 27 | def test_pause_via_return(self): 28 | self.check_pause(False) 29 | 30 | @util.only_unix 31 | def check_pause(self, call): 32 | # the app sleeps for 0.5 seconds 33 | self.curl.setopt(pycurl.URL, 'http://%s:8380/pause' % localhost) 34 | sio = util.BytesIO() 35 | state = dict(paused=False, resumed=False) 36 | if call: 37 | def writefunc(data): 38 | rv = sio.write(data) 39 | if not state['paused']: 40 | self.curl.pause(pycurl.PAUSE_ALL) 41 | state['paused'] = True 42 | return rv 43 | else: 44 | def writefunc(data): 45 | if not state['paused']: 46 | # cannot write to sio here, because 47 | # curl takes pause return value to mean that 48 | # nothing was written 49 | state['paused'] = True 50 | return pycurl.READFUNC_PAUSE 51 | else: 52 | return sio.write(data) 53 | def resume(*args): 54 | state['resumed'] = True 55 | self.curl.pause(pycurl.PAUSE_CONT) 56 | signal.signal(signal.SIGALRM, resume) 57 | # alarm for 1 second which is 0.5 seconds more than the server side 58 | # should sleep for 59 | signal.alarm(1) 60 | start = _time.time() 61 | self.curl.setopt(pycurl.WRITEFUNCTION, writefunc) 62 | 63 | m = pycurl.CurlMulti() 64 | m.add_handle(self.curl) 65 | 66 | # Number of seconds to wait for a timeout to happen 67 | SELECT_TIMEOUT = 1.0 68 | 69 | # Stir the state machine into action 70 | while 1: 71 | ret, num_handles = m.perform() 72 | if ret != pycurl.E_CALL_MULTI_PERFORM: 73 | break 74 | 75 | # Keep going until all the connections have terminated 76 | while num_handles: 77 | # The select method uses fdset internally to determine which file descriptors 78 | # to check. 79 | m.select(SELECT_TIMEOUT) 80 | while 1: 81 | if _time.time() - start > 2: 82 | # test is taking too long, fail 83 | assert False, 'Test is taking too long' 84 | ret, num_handles = m.perform() 85 | if ret != pycurl.E_CALL_MULTI_PERFORM: 86 | break 87 | 88 | # Cleanup 89 | m.remove_handle(self.curl) 90 | m.close() 91 | 92 | self.assertEqual('part1part2', sio.getvalue().decode()) 93 | end = _time.time() 94 | # check that client side waited 95 | self.assertTrue(end-start > 1) 96 | 97 | assert state['resumed'] 98 | -------------------------------------------------------------------------------- /tests/perform_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | try: 7 | import unittest2 as unittest 8 | except ImportError: 9 | import unittest 10 | import pycurl 11 | 12 | from . import appmanager 13 | from . import util 14 | 15 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 16 | 17 | class PerformTest(unittest.TestCase): 18 | def setUp(self): 19 | self.curl = util.DefaultCurl() 20 | 21 | def tearDown(self): 22 | self.curl.close() 23 | 24 | def test_perform_rb(self): 25 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 26 | body = self.curl.perform_rb() 27 | self.assertEqual(util.b('success'), body) 28 | 29 | def test_perform_rs(self): 30 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 31 | body = self.curl.perform_rs() 32 | self.assertEqual(util.u('success'), body) 33 | 34 | def test_perform_rb_utf8(self): 35 | self.curl.setopt(pycurl.URL, 'http://%s:8380/utf8_body' % localhost) 36 | body = self.curl.perform_rb() 37 | if util.py3: 38 | self.assertEqual('Дружба народов'.encode('utf8'), body) 39 | else: 40 | self.assertEqual('Дружба народов', body) 41 | 42 | def test_perform_rs_utf8(self): 43 | self.curl.setopt(pycurl.URL, 'http://%s:8380/utf8_body' % localhost) 44 | body = self.curl.perform_rs() 45 | self.assertEqual('Дружба народов', body) 46 | 47 | def test_perform_rb_invalid_utf8(self): 48 | self.curl.setopt(pycurl.URL, 'http://%s:8380/invalid_utf8_body' % localhost) 49 | body = self.curl.perform_rb() 50 | self.assertEqual(util.b('\xb3\xd2\xda\xcd\xd7'), body) 51 | 52 | @util.only_python2 53 | def test_perform_rs_invalid_utf8_python2(self): 54 | self.curl.setopt(pycurl.URL, 'http://%s:8380/invalid_utf8_body' % localhost) 55 | body = self.curl.perform_rs() 56 | self.assertEqual('\xb3\xd2\xda\xcd\xd7', body) 57 | 58 | @util.only_python3 59 | def test_perform_rs_invalid_utf8_python3(self): 60 | self.curl.setopt(pycurl.URL, 'http://%s:8380/invalid_utf8_body' % localhost) 61 | try: 62 | self.curl.perform_rs() 63 | except UnicodeDecodeError: 64 | pass 65 | else: 66 | self.fail('Should have raised') 67 | -------------------------------------------------------------------------------- /tests/procmgr.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import subprocess 3 | import os 4 | import sys 5 | import signal 6 | import unittest 7 | 8 | from . import util, localhost 9 | 10 | class ProcessManager(object): 11 | def __init__(self, cmd): 12 | self.cmd = cmd 13 | self.running = False 14 | 15 | def start(self): 16 | self.process = subprocess.Popen(self.cmd) 17 | self.running = True 18 | 19 | self.thread = threading.Thread(target=self.run) 20 | self.thread.daemon = True 21 | self.thread.start() 22 | 23 | def run(self): 24 | self.process.communicate() 25 | 26 | def stop(self): 27 | try: 28 | os.kill(self.process.pid, signal.SIGTERM) 29 | except OSError: 30 | pass 31 | self.running = False 32 | 33 | managers = {} 34 | 35 | def start(cmd): 36 | if str(cmd) in managers and managers[str(cmd)].running: 37 | # already started 38 | return 39 | 40 | manager = ProcessManager(cmd) 41 | managers[str(cmd)] = manager 42 | manager.start() 43 | 44 | def start_setup(cmd): 45 | def do_start(): 46 | start(cmd) 47 | return do_start 48 | 49 | # Example on FreeBSD: 50 | # PYCURL_VSFTPD_PATH=/usr/local/libexec/vsftpd pytest 51 | 52 | if 'PYCURL_VSFTPD_PATH' in os.environ: 53 | vsftpd_path = os.environ['PYCURL_VSFTPD_PATH'] 54 | else: 55 | vsftpd_path = None 56 | 57 | try: 58 | # python 2 59 | exception_base = StandardError 60 | except NameError: 61 | # python 3 62 | exception_base = Exception 63 | class VsftpdNotConfigured(exception_base): 64 | pass 65 | 66 | def vsftpd_setup(): 67 | config_file_path = os.path.join(os.path.dirname(__file__), 'vsftpd.conf') 68 | root_path = os.path.join(os.path.dirname(__file__), '..') 69 | cmd = [ 70 | vsftpd_path, 71 | config_file_path, 72 | '-oanon_root=%s' % root_path, 73 | ] 74 | if os.environ.get('CI') and os.environ.get('TRAVIS'): 75 | cmd.append('-oftp_username=travis') 76 | setup_module = start_setup(cmd) 77 | def do_setup_module(): 78 | if vsftpd_path is None: 79 | raise unittest.SkipTest('PYCURL_VSFTPD_PATH environment variable not set') 80 | try: 81 | setup_module() 82 | except OSError: 83 | import errno 84 | e = sys.exc_info()[1] 85 | if e.errno == errno.ENOENT: 86 | msg = "Tried to execute `%s`\nTry specifying path to vsftpd via PYCURL_VSFTPD_PATH environment variable\n" % vsftpd_path 87 | raise OSError(e.errno, e.strerror + "\n" + msg) 88 | else: 89 | raise 90 | ok = util.wait_for_network_service((localhost, 8321), 0.1, 10) 91 | if not ok: 92 | import warnings 93 | warnings.warn('vsftpd did not start after 1 second') 94 | 95 | def teardown_module(): 96 | try: 97 | manager = managers[str(cmd)] 98 | except KeyError: 99 | pass 100 | else: 101 | manager.stop() 102 | 103 | return do_setup_module, teardown_module 104 | -------------------------------------------------------------------------------- /tests/protocol_constants_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import unittest 7 | 8 | from . import util 9 | 10 | class ProtocolConstantsTest(unittest.TestCase): 11 | @util.min_libcurl(7, 19, 4) 12 | def test_7_19_4_protocols(self): 13 | assert hasattr(pycurl, 'PROTO_ALL') 14 | assert hasattr(pycurl, 'PROTO_DICT') 15 | assert hasattr(pycurl, 'PROTO_FILE') 16 | assert hasattr(pycurl, 'PROTO_FTP') 17 | assert hasattr(pycurl, 'PROTO_FTPS') 18 | assert hasattr(pycurl, 'PROTO_HTTP') 19 | assert hasattr(pycurl, 'PROTO_HTTPS') 20 | assert hasattr(pycurl, 'PROTO_LDAP') 21 | assert hasattr(pycurl, 'PROTO_LDAPS') 22 | assert hasattr(pycurl, 'PROTO_SCP') 23 | assert hasattr(pycurl, 'PROTO_SFTP') 24 | assert hasattr(pycurl, 'PROTO_TELNET') 25 | assert hasattr(pycurl, 'PROTO_TFTP') 26 | 27 | @util.min_libcurl(7, 20, 0) 28 | def test_7_20_0_protocols(self): 29 | assert hasattr(pycurl, 'PROTO_IMAP') 30 | assert hasattr(pycurl, 'PROTO_IMAPS') 31 | assert hasattr(pycurl, 'PROTO_POP3') 32 | assert hasattr(pycurl, 'PROTO_POP3S') 33 | assert hasattr(pycurl, 'PROTO_RTSP') 34 | assert hasattr(pycurl, 'PROTO_SMTP') 35 | assert hasattr(pycurl, 'PROTO_SMTPS') 36 | 37 | @util.min_libcurl(7, 21, 0) 38 | def test_7_21_0_protocols(self): 39 | assert hasattr(pycurl, 'PROTO_RTMP') 40 | assert hasattr(pycurl, 'PROTO_RTMPE') 41 | assert hasattr(pycurl, 'PROTO_RTMPS') 42 | assert hasattr(pycurl, 'PROTO_RTMPT') 43 | assert hasattr(pycurl, 'PROTO_RTMPTE') 44 | assert hasattr(pycurl, 'PROTO_RTMPTS') 45 | 46 | @util.min_libcurl(7, 21, 2) 47 | def test_7_21_2_protocols(self): 48 | assert hasattr(pycurl, 'PROTO_GOPHER') 49 | -------------------------------------------------------------------------------- /tests/relative_url_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | # uses the high level interface 7 | import curl 8 | import unittest 9 | 10 | from . import appmanager 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 13 | 14 | class RelativeUrlTest(unittest.TestCase): 15 | def setUp(self): 16 | self.curl = curl.Curl('http://%s:8380/' % localhost) 17 | 18 | def tearDown(self): 19 | self.curl.close() 20 | 21 | def test_get_relative(self): 22 | self.curl.get('/success') 23 | self.assertEqual('success', self.curl.body().decode()) 24 | -------------------------------------------------------------------------------- /tests/reload_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import pytest 7 | import unittest 8 | 9 | class ReloadTest(unittest.TestCase): 10 | @pytest.mark.standalone 11 | def test_reloading(self): 12 | try: 13 | # python 2 14 | reload_fn = reload 15 | except NameError: 16 | # python 3 17 | import importlib 18 | reload_fn = importlib.reload 19 | reload_fn(pycurl) 20 | -------------------------------------------------------------------------------- /tests/reset_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import pycurl 7 | import unittest 8 | 9 | from . import appmanager 10 | from . import util 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 13 | 14 | class ResetTest(unittest.TestCase): 15 | def test_reset(self): 16 | c = util.DefaultCurl() 17 | c.setopt(pycurl.USERAGENT, 'Phony/42') 18 | c.setopt(pycurl.URL, 'http://%s:8380/header?h=user-agent' % localhost) 19 | sio = util.BytesIO() 20 | c.setopt(pycurl.WRITEFUNCTION, sio.write) 21 | c.perform() 22 | user_agent = sio.getvalue().decode() 23 | assert user_agent == 'Phony/42' 24 | 25 | c.reset() 26 | c.setopt(pycurl.URL, 'http://%s:8380/header?h=user-agent' % localhost) 27 | sio = util.BytesIO() 28 | c.setopt(pycurl.WRITEFUNCTION, sio.write) 29 | c.perform() 30 | user_agent = sio.getvalue().decode() 31 | # we also check that the request succeeded after curl 32 | # object has been reset 33 | assert user_agent.startswith('PycURL') 34 | 35 | # XXX this test was broken when it was test_reset.py 36 | def skip_reset_with_multi(self): 37 | outf = util.BytesIO() 38 | cm = pycurl.CurlMulti() 39 | 40 | eh = util.DefaultCurl() 41 | 42 | for x in range(1, 20): 43 | eh.setopt(pycurl.WRITEFUNCTION, outf.write) 44 | eh.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 45 | cm.add_handle(eh) 46 | 47 | while 1: 48 | ret, active_handles = cm.perform() 49 | if ret != pycurl.E_CALL_MULTI_PERFORM: 50 | break 51 | 52 | while active_handles: 53 | ret = cm.select(1.0) 54 | if ret == -1: 55 | continue 56 | while 1: 57 | ret, active_handles = cm.perform() 58 | if ret != pycurl.E_CALL_MULTI_PERFORM: 59 | break 60 | 61 | count, good, bad = cm.info_read() 62 | 63 | for h, en, em in bad: 64 | print("Transfer to %s failed with %d, %s\n" % \ 65 | (h.getinfo(pycurl.EFFECTIVE_URL), en, em)) 66 | raise RuntimeError 67 | 68 | for h in good: 69 | httpcode = h.getinfo(pycurl.RESPONSE_CODE) 70 | if httpcode != 200: 71 | print("Transfer to %s failed with code %d\n" %\ 72 | (h.getinfo(pycurl.EFFECTIVE_URL), httpcode)) 73 | raise RuntimeError 74 | 75 | else: 76 | print("Recd %d bytes from %s" % \ 77 | (h.getinfo(pycurl.SIZE_DOWNLOAD), 78 | h.getinfo(pycurl.EFFECTIVE_URL))) 79 | 80 | cm.remove_handle(eh) 81 | eh.reset() 82 | 83 | eh.close() 84 | cm.close() 85 | outf.close() 86 | -------------------------------------------------------------------------------- /tests/resolve_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pycurl 4 | import unittest 5 | 6 | from . import appmanager 7 | from . import util 8 | 9 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 10 | 11 | class ResolveTest(unittest.TestCase): 12 | def setUp(self): 13 | self.curl = util.DefaultCurl() 14 | 15 | def tearDown(self): 16 | self.curl.close() 17 | 18 | def test_resolve(self): 19 | if util.pycurl_version_less_than(7, 21, 3) and not hasattr(pycurl, 'RESOLVE'): 20 | raise unittest.SkipTest('libcurl < 7.21.3 or no RESOLVE') 21 | 22 | self.curl.setopt(pycurl.URL, 'http://p.localhost:8380/success') 23 | self.curl.setopt(pycurl.RESOLVE, ['p.localhost:8380:127.0.0.1']) 24 | self.curl.perform() 25 | self.assertEqual(200, self.curl.getinfo(pycurl.RESPONSE_CODE)) 26 | -------------------------------------------------------------------------------- /tests/run-quickstart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export PYTHONSUFFIX=$(python -V 2>&1 |awk '{print $2}' |awk -F. '{print $1 "." $2}') 6 | export PYTHONPATH="`pwd`"/$(ls -d build/lib.*$PYTHONSUFFIX):$PYTHONPATH 7 | 8 | tmpdir=`mktemp -d` 9 | 10 | finish() { 11 | rm -rf "$tmpdir" 12 | } 13 | 14 | trap finish EXIT 15 | 16 | for file in "`pwd`"/examples/quickstart/*.py; do \ 17 | # skip Python 2-only examples on Python 3 18 | if echo "$file" |grep -q python2 && 19 | python -V 2>&1 |grep -q 'Python 3' 20 | then 21 | continue 22 | fi 23 | 24 | set +e 25 | (cd "$tmpdir" && python "$file" >output) 26 | rv=$? 27 | set -e 28 | if test "$rv" != 0; then 29 | echo "$file failed, standard error contents (if any) is above" 30 | if test -n "`cat "$tmpdir"/output`"; then 31 | echo "Standard output contents:" 32 | cat "$tmpdir"/output 33 | fi 34 | exit $rv 35 | fi 36 | done 37 | 38 | echo 'All ok' 39 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -x 5 | 6 | test -n "$PYTHON" || PYTHON=python 7 | test -n "$PYTEST" || PYTEST=pytest 8 | 9 | mkdir -p tests/tmp 10 | export PYTHONMAJOR=$($PYTHON -V 2>&1 |awk '{print $2}' |awk -F. '{print $1}') 11 | export PYTHONMINOR=$($PYTHON -V 2>&1 |awk '{print $2}' |awk -F. '{print $2}') 12 | export PYTHONPATH=$(ls -d build/lib.*$PYTHONMAJOR*$PYTHONMINOR):$PYTHONPATH 13 | 14 | extra_attrs= 15 | if test "$CI" = true; then 16 | if test -n "$USECURL" && echo "$USECURL" |grep -q gssapi; then 17 | : 18 | else 19 | extra_attrs="$extra_attrs",\!gssapi 20 | fi 21 | if test -n "$USECURL" && echo "$USECURL" |grep -q libssh2; then 22 | : 23 | else 24 | extra_attrs="$extra_attrs",\!ssh 25 | fi 26 | fi 27 | 28 | $PYTHON -c 'import pycurl; print(pycurl.version)' 29 | $PYTEST -v 30 | -------------------------------------------------------------------------------- /tests/seek_cb_constants_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import unittest 7 | 8 | from . import util 9 | 10 | class SeekCbConstantsTest(unittest.TestCase): 11 | # numeric value is understood by older libcurls but 12 | # the constant is only defined in 7.19.5+ 13 | @util.min_libcurl(7, 19, 5) 14 | def test_ok(self): 15 | curl = pycurl.Curl() 16 | self.assertEqual(0, curl.SEEKFUNC_OK) 17 | curl.close() 18 | 19 | # numeric value is understood by older libcurls but 20 | # the constant is only defined in 7.19.5+ 21 | @util.min_libcurl(7, 19, 5) 22 | def test_fail(self): 23 | curl = pycurl.Curl() 24 | self.assertEqual(1, curl.SEEKFUNC_FAIL) 25 | curl.close() 26 | 27 | @util.min_libcurl(7, 19, 5) 28 | def test_cantseek(self): 29 | curl = pycurl.Curl() 30 | self.assertEqual(2, curl.SEEKFUNC_CANTSEEK) 31 | curl.close() 32 | -------------------------------------------------------------------------------- /tests/seek_cb_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | # Note: this test is meant to be run from pycurl project root. 6 | 7 | import pycurl 8 | import unittest 9 | import os.path 10 | 11 | from . import procmgr, localhost, util 12 | 13 | setup_module, teardown_module = procmgr.vsftpd_setup() 14 | 15 | class PartialFileSource: 16 | def __init__(self): 17 | self.__buf = '1234567890.1234567890' 18 | self.__maxread = None 19 | self.__bufptr = 0 20 | 21 | def read(self, size): 22 | p = self.__bufptr 23 | end = p+size 24 | if self.__maxread: 25 | end = min(self.__maxread, end) 26 | ret = self.__buf[p:end] 27 | self.__bufptr+= len(ret) 28 | #print 20*">>>", "read(%s) ==> %s" % (size, len(ret)) 29 | return ret 30 | 31 | def seek(self, offset, origin): 32 | #print 20*">>>", "seek(%s, %s)" % (offset, origin) 33 | self.__bufptr = offset 34 | 35 | def set_maxread(self, maxread): 36 | self.__maxread = maxread 37 | 38 | class SeekCbTest(unittest.TestCase): 39 | def test_seek_function(self): 40 | c = util.DefaultCurl() 41 | c.setopt(pycurl.UPLOAD, 1) 42 | c.setopt(pycurl.URL, "ftp://%s:8321/tests/tmp/upload.txt" % localhost) 43 | c.setopt(pycurl.RESUME_FROM, 0) 44 | #c.setopt(pycurl.VERBOSE, 1) 45 | upload_file = PartialFileSource() 46 | c.setopt(pycurl.READFUNCTION, upload_file.read) 47 | upload_file.set_maxread(10) 48 | c.perform() 49 | 50 | f = open(os.path.join(os.path.dirname(__file__), 'tmp', 'upload.txt')) 51 | try: 52 | content = f.read() 53 | finally: 54 | f.close() 55 | self.assertEqual('1234567890', content) 56 | 57 | c.close() 58 | del c 59 | del upload_file 60 | 61 | c = util.DefaultCurl() 62 | c.setopt(pycurl.URL, "ftp://%s:8321/tests/tmp/upload.txt" % localhost) 63 | c.setopt(pycurl.RESUME_FROM, -1) 64 | c.setopt(pycurl.UPLOAD, 1) 65 | #c.setopt(pycurl.VERBOSE, 1) 66 | upload_file = PartialFileSource() 67 | c.setopt(pycurl.READFUNCTION, upload_file.read) 68 | c.setopt(pycurl.SEEKFUNCTION, upload_file.seek) 69 | c.perform() 70 | c.close() 71 | 72 | f = open(os.path.join(os.path.dirname(__file__), 'tmp', 'upload.txt')) 73 | try: 74 | content = f.read() 75 | finally: 76 | f.close() 77 | self.assertEqual('1234567890.1234567890', content) 78 | -------------------------------------------------------------------------------- /tests/setopt_lifecycle_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import gc 7 | import pycurl 8 | import unittest 9 | try: 10 | import json 11 | except ImportError: 12 | import simplejson as json 13 | 14 | from . import appmanager 15 | from . import util 16 | 17 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 18 | 19 | class TestString(str): 20 | __test__ = False 21 | def __del__(self): 22 | self.replace('1', '2') 23 | #print self 24 | #print 'd' 25 | 26 | class SetoptLifecycleTest(unittest.TestCase): 27 | # separate method to permit pf to go out of scope and be 28 | # garbage collected before perform call 29 | def do_setopt(self, curl, index): 30 | pf = TestString('&'.join(50*['field=value%d' % (index,)])) 31 | curl.setopt(pycurl.URL, 'http://%s:8380/postfields' % localhost) 32 | curl.setopt(pycurl.POSTFIELDS, pf) 33 | 34 | # This test takes 6+ seconds to run. 35 | # It seems to pass with broken pycurl code when run by itself, 36 | # but fails when run as part of the entire test suite. 37 | def test_postfields_lifecycle(self): 38 | requests = [] 39 | for i in range(1000): 40 | curl = util.DefaultCurl() 41 | self.do_setopt(curl, i) 42 | gc.collect() 43 | requests.append(curl) 44 | 45 | # send requests here to permit maximum garbage recycling 46 | for i in range(100): 47 | curl = requests[i] 48 | #self.curl.setopt(pycurl.VERBOSE, 1) 49 | sio = util.BytesIO() 50 | curl.setopt(pycurl.WRITEFUNCTION, sio.write) 51 | curl.perform() 52 | self.assertEqual(200, curl.getinfo(pycurl.HTTP_CODE)) 53 | body = sio.getvalue().decode() 54 | returned_fields = json.loads(body) 55 | self.assertEqual(dict(field='value%d' % i), returned_fields) 56 | 57 | for i in range(100): 58 | curl = requests[i] 59 | curl.close() 60 | -------------------------------------------------------------------------------- /tests/setopt_string_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import pycurl 6 | import pytest 7 | from . import localhost 8 | import unittest 9 | 10 | from . import appmanager 11 | from . import util 12 | 13 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 14 | 15 | class SetoptTest(unittest.TestCase): 16 | def setUp(self): 17 | self.curl = util.DefaultCurl() 18 | 19 | def tearDown(self): 20 | self.curl.close() 21 | 22 | def test_setopt_string(self): 23 | self.curl.setopt_string(pycurl.URL, 'http://%s:8380/success' % localhost) 24 | sio = util.BytesIO() 25 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 26 | self.curl.perform() 27 | self.assertEqual('success', sio.getvalue().decode()) 28 | 29 | def test_setopt_string_integer(self): 30 | with pytest.raises(TypeError): 31 | self.curl.setopt_string(pycurl.VERBOSE, True) 32 | -------------------------------------------------------------------------------- /tests/setopt_unicode_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import pycurl 7 | import pytest 8 | import unittest 9 | 10 | from . import appmanager 11 | from . import util 12 | 13 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 14 | 15 | class SetoptUnicodeTest(unittest.TestCase): 16 | def setUp(self): 17 | self.curl = util.DefaultCurl() 18 | 19 | def tearDown(self): 20 | self.curl.close() 21 | 22 | def test_ascii_string(self): 23 | self.check('p=test', 'test') 24 | 25 | def test_unicode_string(self): 26 | with pytest.raises(UnicodeEncodeError): 27 | self.check(util.u('p=Москва'), util.u('Москва')) 28 | 29 | def test_unicode_encoded(self): 30 | self.check(util.u('p=Москва').encode('utf8'), util.u('Москва')) 31 | 32 | def check(self, send, expected): 33 | self.curl.setopt(pycurl.URL, 'http://%s:8380/param_utf8_hack' % localhost) 34 | sio = util.BytesIO() 35 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 36 | self.curl.setopt(pycurl.POSTFIELDS, send) 37 | self.curl.perform() 38 | self.assertEqual(expected, sio.getvalue().decode('utf-8')) 39 | -------------------------------------------------------------------------------- /tests/share_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import threading 7 | import pycurl 8 | import pytest 9 | import unittest 10 | 11 | from . import appmanager 12 | from . import util 13 | 14 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 15 | 16 | class WorkerThread(threading.Thread): 17 | 18 | def __init__(self, share): 19 | threading.Thread.__init__(self) 20 | self.curl = util.DefaultCurl() 21 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 22 | self.curl.setopt(pycurl.SHARE, share) 23 | self.sio = util.BytesIO() 24 | self.curl.setopt(pycurl.WRITEFUNCTION, self.sio.write) 25 | 26 | def run(self): 27 | self.curl.perform() 28 | self.curl.close() 29 | 30 | class ShareTest(unittest.TestCase): 31 | def test_share(self): 32 | s = pycurl.CurlShare() 33 | s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_COOKIE) 34 | s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_DNS) 35 | s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_SSL_SESSION) 36 | 37 | t1 = WorkerThread(s) 38 | t2 = WorkerThread(s) 39 | 40 | t1.start() 41 | t2.start() 42 | 43 | t1.join() 44 | t2.join() 45 | 46 | del s 47 | 48 | self.assertEqual('success', t1.sio.getvalue().decode()) 49 | self.assertEqual('success', t2.sio.getvalue().decode()) 50 | 51 | def test_share_close(self): 52 | s = pycurl.CurlShare() 53 | s.close() 54 | 55 | def test_share_close_twice(self): 56 | s = pycurl.CurlShare() 57 | s.close() 58 | s.close() 59 | 60 | # positional arguments are rejected 61 | def test_positional_arguments(self): 62 | with pytest.raises(TypeError): 63 | pycurl.CurlShare(1) 64 | 65 | # keyword arguments are rejected 66 | def test_keyword_arguments(self): 67 | with pytest.raises(TypeError): 68 | pycurl.CurlShare(a=1) 69 | -------------------------------------------------------------------------------- /tests/sockopt_cb_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import unittest 7 | import pycurl 8 | 9 | from . import util 10 | from . import appmanager 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 13 | 14 | class SockoptCbTest(unittest.TestCase): 15 | def setUp(self): 16 | self.curl = util.DefaultCurl() 17 | self.curl.setopt(self.curl.URL, 'http://%s:8380/success' % localhost) 18 | 19 | def tearDown(self): 20 | self.curl.close() 21 | 22 | def test_sockoptfunction_ok(self): 23 | called = {} 24 | 25 | def sockoptfunction(curlfd, purpose): 26 | called['called'] = True 27 | return 0 28 | 29 | self.curl.setopt(pycurl.SOCKOPTFUNCTION, sockoptfunction) 30 | 31 | self.curl.perform() 32 | assert called['called'] 33 | 34 | def test_sockoptfunction_fail(self): 35 | called = {} 36 | 37 | def sockoptfunction(curlfd, purpose): 38 | called['called'] = True 39 | return 1 40 | 41 | self.curl.setopt(pycurl.SOCKOPTFUNCTION, sockoptfunction) 42 | 43 | try: 44 | self.curl.perform() 45 | self.fail('should have raised') 46 | except pycurl.error as e: 47 | assert e.args[0] in [pycurl.E_ABORTED_BY_CALLBACK, pycurl.E_COULDNT_CONNECT], \ 48 | 'Unexpected pycurl error code %s' % e.args[0] 49 | assert called['called'] 50 | 51 | def test_sockoptfunction_bogus_return(self): 52 | called = {} 53 | 54 | def sockoptfunction(curlfd, purpose): 55 | called['called'] = True 56 | return 'bogus' 57 | 58 | self.curl.setopt(pycurl.SOCKOPTFUNCTION, sockoptfunction) 59 | 60 | try: 61 | self.curl.perform() 62 | self.fail('should have raised') 63 | except pycurl.error as e: 64 | assert e.args[0] in [pycurl.E_ABORTED_BY_CALLBACK, pycurl.E_COULDNT_CONNECT], \ 65 | 'Unexpected pycurl error code %s' % e.args[0] 66 | assert called['called'] 67 | 68 | @util.min_libcurl(7, 28, 0) 69 | def test_socktype_accept(self): 70 | assert hasattr(pycurl, 'SOCKTYPE_ACCEPT') 71 | assert hasattr(self.curl, 'SOCKTYPE_ACCEPT') 72 | 73 | def test_socktype_ipcxn(self): 74 | assert hasattr(pycurl, 'SOCKTYPE_IPCXN') 75 | assert hasattr(self.curl, 'SOCKTYPE_IPCXN') 76 | 77 | class SockoptCbUnsetTest(unittest.TestCase): 78 | def setUp(self): 79 | self.curl = util.DefaultCurl() 80 | 81 | def test_sockoptfunction_none(self): 82 | self.curl.setopt(pycurl.SOCKOPTFUNCTION, None) 83 | 84 | def test_sockoptfunction_unset(self): 85 | self.curl.unsetopt(pycurl.SOCKOPTFUNCTION) 86 | -------------------------------------------------------------------------------- /tests/ssh_key_cb_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import unittest 6 | import pycurl 7 | import pytest 8 | 9 | from . import util 10 | 11 | sftp_server = 'sftp://web.sourceforge.net' 12 | 13 | @pytest.mark.online 14 | @pytest.mark.ssh 15 | class SshKeyCbTest(unittest.TestCase): 16 | '''This test requires Internet access.''' 17 | 18 | def setUp(self): 19 | self.curl = util.DefaultCurl() 20 | self.curl.setopt(pycurl.URL, sftp_server) 21 | self.curl.setopt(pycurl.VERBOSE, True) 22 | 23 | def tearDown(self): 24 | self.curl.close() 25 | 26 | @util.min_libcurl(7, 19, 6) 27 | # curl compiled with libssh doesn't support 28 | # CURLOPT_SSH_KNOWNHOSTS and CURLOPT_SSH_KEYFUNCTION 29 | @util.guard_unknown_libcurl_option 30 | def test_keyfunction(self): 31 | # with keyfunction returning ok 32 | 33 | def keyfunction(known_key, found_key, match): 34 | return pycurl.KHSTAT_FINE 35 | 36 | self.curl.setopt(pycurl.SSH_KNOWNHOSTS, '.known_hosts') 37 | self.curl.setopt(pycurl.SSH_KEYFUNCTION, keyfunction) 38 | 39 | try: 40 | self.curl.perform() 41 | self.fail('should have raised') 42 | except pycurl.error as e: 43 | self.assertEqual(pycurl.E_LOGIN_DENIED, e.args[0]) 44 | 45 | # with keyfunction returning not ok 46 | 47 | def keyfunction(known_key, found_key, match): 48 | return pycurl.KHSTAT_REJECT 49 | 50 | self.curl.setopt(pycurl.SSH_KNOWNHOSTS, '.known_hosts') 51 | self.curl.setopt(pycurl.SSH_KEYFUNCTION, keyfunction) 52 | 53 | try: 54 | self.curl.perform() 55 | self.fail('should have raised') 56 | except pycurl.error as e: 57 | self.assertEqual(pycurl.E_PEER_FAILED_VERIFICATION, e.args[0]) 58 | 59 | @util.min_libcurl(7, 19, 6) 60 | @util.guard_unknown_libcurl_option 61 | def test_keyfunction_bogus_return(self): 62 | def keyfunction(known_key, found_key, match): 63 | return 'bogus' 64 | 65 | self.curl.setopt(pycurl.SSH_KNOWNHOSTS, '.known_hosts') 66 | self.curl.setopt(pycurl.SSH_KEYFUNCTION, keyfunction) 67 | 68 | try: 69 | self.curl.perform() 70 | self.fail('should have raised') 71 | except pycurl.error as e: 72 | self.assertEqual(pycurl.E_PEER_FAILED_VERIFICATION, e.args[0]) 73 | 74 | 75 | @pytest.mark.ssh 76 | class SshKeyCbUnsetTest(unittest.TestCase): 77 | def setUp(self): 78 | self.curl = util.DefaultCurl() 79 | self.curl.setopt(pycurl.URL, sftp_server) 80 | self.curl.setopt(pycurl.VERBOSE, True) 81 | 82 | @util.min_libcurl(7, 19, 6) 83 | @util.guard_unknown_libcurl_option 84 | def test_keyfunction_none(self): 85 | self.curl.setopt(pycurl.SSH_KEYFUNCTION, None) 86 | 87 | @util.min_libcurl(7, 19, 6) 88 | @util.guard_unknown_libcurl_option 89 | def test_keyfunction_unset(self): 90 | self.curl.unsetopt(pycurl.SSH_KEYFUNCTION) 91 | -------------------------------------------------------------------------------- /tests/travis/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -x 5 | 6 | export PATH=$HOME/opt/bin:$PATH 7 | 8 | if test -n "$USECURL"; then 9 | # FTP support no longer ships with libcurl by default. 10 | # Thus, only run ftp tests against libcurls that we built. 11 | export PYCURL_VSFTPD_PATH=$HOME/opt/bin/vsftpd 12 | 13 | curldirname=curl-"$USECURL" 14 | export PYCURL_CURL_CONFIG="$HOME"/opt/$curldirname/bin/curl-config 15 | $PYCURL_CURL_CONFIG --features 16 | export LD_LIBRARY_PATH="$HOME"/opt/$curldirname/lib 17 | fi 18 | 19 | setup_args= 20 | if test -n "$USESSL"; then 21 | if test "$USESSL" = libressl; then 22 | export PYCURL_SSL_LIBRARY=openssl 23 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$HOME/opt/libressl-$USELIBRESSL/lib" 24 | setup_args="$setup_args --openssl-dir=$HOME/opt/libressl-$USELIBRESSL" 25 | elif test "$USESSL" != none; then 26 | export PYCURL_SSL_LIBRARY="$USESSL" 27 | if test -n "$USEOPENSSL"; then 28 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$HOME/opt/openssl-$USEOPENSSL/lib" 29 | setup_args="$setup_args --openssl-dir=$HOME/opt/openssl-$USEOPENSSL" 30 | fi 31 | if test -n "$USELIBRESSL"; then 32 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$HOME/opt/libressl-$USELIBRESSL/lib" 33 | fi 34 | fi 35 | elif test -z "$USECURL"; then 36 | # default for ubuntu 12 is openssl 37 | # default for ubuntu 14 which is what travis currently uses is gnutls 38 | export PYCURL_SSL_LIBRARY=gnutls 39 | fi 40 | 41 | if test -n "$AVOIDSTDIO"; then 42 | export PYCURL_SETUP_OPTIONS=--avoid-stdio 43 | fi 44 | 45 | make gen 46 | python setup.py build $setup_args 47 | 48 | make -C tests/fake-curl/libcurl 49 | 50 | ldd build/lib*/pycurl*.so 51 | 52 | ./tests/run.sh 53 | ./tests/ext/test-suite.sh 54 | 55 | if test -n "$TESTDOCSEXAMPLES"; then 56 | which pyflakes 57 | pyflakes python examples tests setup.py 58 | ./tests/run-quickstart.sh 59 | 60 | # sphinx requires python 2.7+ or 3.3+ 61 | make docs 62 | fi 63 | -------------------------------------------------------------------------------- /tests/travis/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -x 5 | 6 | wget_once() { 7 | url="$1" 8 | if ! test -f `basename "$url"`; then 9 | wget -O `basename "$url"`.part "$url" 10 | rv=$? 11 | if test $rv = 0; then 12 | mv `basename "$url"`.part `basename "$url"` 13 | else 14 | rm -f `basename "$url"`.part 15 | return $rv 16 | fi 17 | fi 18 | } 19 | 20 | file_host=https://github.com/pycurl/deps/raw/master 21 | distro=bionic 22 | ldlp=$LD_LIBRARY_PATH 23 | 24 | ldlp_exec() { 25 | env LD_LIBRARY_PATH=$ldlp "$@" 26 | } 27 | 28 | (cd && 29 | mkdir -p opt && 30 | cd opt && 31 | wget $file_host/travis-$distro/bin-$distro-64.tar.xz && 32 | tar xfJ bin-$distro-64.tar.xz) 33 | 34 | export PATH=~/opt/bin:$PATH 35 | 36 | pip install -r requirements-dev.txt 37 | 38 | if test -n "$USECURL"; then 39 | if test -n "$USEOPENSSL"; then 40 | (cd && mkdir -p opt && cd opt && 41 | wget $file_host/travis-$distro/openssl-"$USEOPENSSL"-$distro-64.tar.xz && 42 | tar xfJ openssl-"$USEOPENSSL"-$distro-64.tar.xz) 43 | ldlp=$ldlp:$HOME/opt/openssl-$USEOPENSSL/lib 44 | fi 45 | if test -n "$USELIBRESSL"; then 46 | (cd && mkdir -p opt && cd opt && 47 | wget $file_host/travis-$distro/libressl-"$USELIBRESSL"-$distro-64.tar.xz && 48 | tar xfJ libressl-"$USELIBRESSL"-$distro-64.tar.xz) 49 | ldlp=$ldlp:$HOME/opt/libressl-$USELIBRESSL/lib 50 | fi 51 | 52 | curldirname=curl-"$USECURL" 53 | ldlp=$ldlp:$HOME/opt/$curldirname/lib 54 | name=$curldirname-$distro-64.tar.xz 55 | (cd && 56 | mkdir -p opt && 57 | cd opt && 58 | wget $file_host/travis-$distro/"$name" && 59 | tar xfJ "$name") 60 | ldlp_exec "$HOME"/opt/$curldirname/bin/curl -V 61 | else 62 | curl -V 63 | fi 64 | -------------------------------------------------------------------------------- /tests/unset_range_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import os.path 6 | import pycurl 7 | import unittest 8 | import urllib.request 9 | 10 | class UnsetRangeTest(unittest.TestCase): 11 | def setUp(self): 12 | self.curl = pycurl.Curl() 13 | 14 | def tearDown(self): 15 | self.curl.close() 16 | 17 | def test_unset_range(self): 18 | def write_cb(data): 19 | self.read += len(data) 20 | return None 21 | 22 | # download bytes 0-9 of the script itself through the file:// protocol 23 | self.read = 0 24 | url = 'file:' + urllib.request.pathname2url(os.path.abspath(__file__)) 25 | self.curl.setopt(pycurl.URL, url) 26 | self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) 27 | self.curl.setopt(pycurl.RANGE, '0-9') 28 | self.curl.perform() 29 | assert 10 == self.read 30 | 31 | # the RANGE setting should be preserved from the previous transfer 32 | self.read = 0 33 | self.curl.perform() 34 | assert 10 == self.read 35 | 36 | # drop the RANGE setting using unsetopt() and download entire script 37 | self.read = 0 38 | self.curl.unsetopt(pycurl.RANGE) 39 | self.curl.perform() 40 | assert 10 < self.read 41 | 42 | # now set the RANGE again and check that pycurl takes it into account 43 | self.read = 0 44 | self.curl.setopt(pycurl.RANGE, '0-9') 45 | self.curl.perform() 46 | assert 10 == self.read 47 | 48 | # now drop the RANGE setting using setopt(..., None) 49 | self.read = 0 50 | self.curl.setopt(pycurl.RANGE, None) 51 | self.curl.perform() 52 | assert 10 < self.read 53 | -------------------------------------------------------------------------------- /tests/user_agent_string_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import unittest 7 | import pycurl 8 | 9 | from . import appmanager 10 | from . import util 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 13 | 14 | class UserAgentStringTest(unittest.TestCase): 15 | def setUp(self): 16 | self.curl = util.DefaultCurl() 17 | 18 | def tearDown(self): 19 | self.curl.close() 20 | 21 | def test_pycurl_user_agent_string(self): 22 | self.curl.setopt(pycurl.URL, 'http://%s:8380/header?h=user-agent' % localhost) 23 | sio = util.BytesIO() 24 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 25 | self.curl.perform() 26 | user_agent = sio.getvalue().decode() 27 | assert user_agent.startswith('PycURL/') 28 | assert 'libcurl/' in user_agent, 'User agent did not include libcurl/: %s' % user_agent 29 | -------------------------------------------------------------------------------- /tests/version_comparison_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import unittest 6 | 7 | from . import util 8 | 9 | class VersionComparisonTest(unittest.TestCase): 10 | def test_comparison(self): 11 | assert util.version_less_than_spec((7, 22, 0), (7, 23, 0)) 12 | assert util.version_less_than_spec((7, 22, 0), (7, 23)) 13 | assert util.version_less_than_spec((7, 22, 0), (7, 22, 1)) 14 | assert not util.version_less_than_spec((7, 22, 0), (7, 22, 0)) 15 | assert not util.version_less_than_spec((7, 22, 0), (7, 22)) 16 | -------------------------------------------------------------------------------- /tests/version_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import unittest 6 | import pycurl 7 | 8 | class VersionTest(unittest.TestCase): 9 | def test_pycurl_presence_and_case(self): 10 | assert pycurl.version.startswith('PycURL/') 11 | 12 | def test_libcurl_presence(self): 13 | assert 'libcurl/' in pycurl.version 14 | -------------------------------------------------------------------------------- /tests/vsftpd.conf: -------------------------------------------------------------------------------- 1 | anon_world_readable_only=yes 2 | anonymous_enable=yes 3 | background=no 4 | # currently we only list files 5 | download_enable=no 6 | listen=yes 7 | run_as_launching_user=yes 8 | write_enable=yes 9 | anon_upload_enable=yes 10 | anon_other_write_enable=yes 11 | listen_port=8321 12 | # should be supplied on command line 13 | anon_root=/var/empty 14 | -------------------------------------------------------------------------------- /tests/weakref_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import unittest 6 | import weakref 7 | import pycurl 8 | 9 | class WeakrefTest(unittest.TestCase): 10 | def test_easy(self): 11 | c = pycurl.Curl() 12 | weakref.ref(c) 13 | c.close() 14 | 15 | def test_multi(self): 16 | m = pycurl.CurlMulti() 17 | weakref.ref(m) 18 | m.close() 19 | 20 | def test_share(self): 21 | s = pycurl.CurlShare() 22 | weakref.ref(s) 23 | s.close() 24 | -------------------------------------------------------------------------------- /tests/win/opensocketcrash.py: -------------------------------------------------------------------------------- 1 | from . import localhost 2 | import pycurl 3 | from io import BytesIO 4 | import socket 5 | 6 | def socket_open(family, socktype, protocol, address): 7 | global socket_open_called 8 | global socket_open_address 9 | socket_open_called = True 10 | socket_open_address = address 11 | 12 | #print(family, socktype, protocol, address) 13 | s = socket.socket(family, socktype, protocol) 14 | s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 15 | print(2) 16 | return s 17 | 18 | curl = pycurl.Curl() 19 | curl.setopt(pycurl.OPENSOCKETFUNCTION, socket_open) 20 | curl.setopt(curl.URL, 'http://%s:8380/success' % localhost) 21 | sio = BytesIO() 22 | curl.setopt(pycurl.WRITEFUNCTION, sio.write) 23 | print(1) 24 | curl.perform() 25 | print(1) 26 | 27 | assert socket_open_called 28 | assert ("127.0.0.1", 8380) == socket_open_address 29 | assert 'success' == sio.getvalue().decode() 30 | 31 | print(1) 32 | -------------------------------------------------------------------------------- /tests/write_abort_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import os.path 6 | import pycurl 7 | import sys 8 | import unittest 9 | import urllib.request 10 | 11 | class WriteAbortTest(unittest.TestCase): 12 | def setUp(self): 13 | self.curl = pycurl.Curl() 14 | 15 | def tearDown(self): 16 | self.curl.close() 17 | 18 | def test_write_abort(self): 19 | def write_cb(_): 20 | # this should cause pycurl.WRITEFUNCTION (without any range errors) 21 | return -1 22 | 23 | try: 24 | # set when running full test suite if any earlier tests 25 | # failed in Python code called from C 26 | del sys.last_value 27 | except AttributeError: 28 | pass 29 | 30 | # download the script itself through the file:// protocol into write_cb 31 | url = 'file:' + urllib.request.pathname2url(os.path.abspath(__file__)) 32 | self.curl.setopt(pycurl.URL, url) 33 | self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) 34 | try: 35 | self.curl.perform() 36 | except pycurl.error: 37 | err, msg = sys.exc_info()[1].args 38 | # we expect pycurl.E_WRITE_ERROR as the response 39 | assert pycurl.E_WRITE_ERROR == err 40 | 41 | # no additional errors should be reported 42 | assert not hasattr(sys, 'last_value') 43 | -------------------------------------------------------------------------------- /tests/write_cb_bogus_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | import os.path 6 | import pycurl 7 | import sys 8 | import unittest 9 | import urllib.request 10 | 11 | class WriteAbortTest(unittest.TestCase): 12 | def setUp(self): 13 | self.curl = pycurl.Curl() 14 | 15 | def tearDown(self): 16 | self.curl.close() 17 | 18 | def write_cb_returning_string(self, data): 19 | return 'foo' 20 | 21 | def write_cb_returning_float(self, data): 22 | return 0.5 23 | 24 | def test_write_cb_returning_string(self): 25 | self.check(self.write_cb_returning_string) 26 | 27 | def test_write_cb_returning_float(self): 28 | self.check(self.write_cb_returning_float) 29 | 30 | def check(self, write_cb): 31 | # download the script itself through the file:// protocol into write_cb 32 | url = 'file:' + urllib.request.pathname2url(os.path.abspath(__file__)) 33 | self.curl.setopt(pycurl.URL, url) 34 | self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) 35 | try: 36 | self.curl.perform() 37 | 38 | self.fail('Should not get here') 39 | except pycurl.error: 40 | err, msg = sys.exc_info()[1].args 41 | # we expect pycurl.E_WRITE_ERROR as the response 42 | assert pycurl.E_WRITE_ERROR == err 43 | 44 | # actual error 45 | assert hasattr(sys, 'last_type') 46 | self.assertEqual(pycurl.error, sys.last_type) 47 | assert hasattr(sys, 'last_value') 48 | self.assertEqual('write callback must return int or None', str(sys.last_value)) 49 | -------------------------------------------------------------------------------- /tests/write_to_stringio_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import pycurl 7 | import unittest 8 | import sys 9 | 10 | from . import appmanager 11 | from . import util 12 | 13 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 14 | 15 | class WriteToStringioTest(unittest.TestCase): 16 | def setUp(self): 17 | self.curl = util.DefaultCurl() 18 | 19 | def tearDown(self): 20 | self.curl.close() 21 | 22 | def test_write_to_bytesio(self): 23 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 24 | sio = util.BytesIO() 25 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 26 | self.curl.perform() 27 | self.assertEqual('success', sio.getvalue().decode()) 28 | 29 | @util.only_python3 30 | def test_write_to_stringio(self): 31 | self.curl.setopt(pycurl.URL, 'http://%s:8380/success' % localhost) 32 | # stringio in python 3 33 | sio = util.StringIO() 34 | self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) 35 | try: 36 | self.curl.perform() 37 | 38 | self.fail('Should have received a write error') 39 | except pycurl.error: 40 | err, msg = sys.exc_info()[1].args 41 | # we expect pycurl.E_WRITE_ERROR as the response 42 | assert pycurl.E_WRITE_ERROR == err 43 | -------------------------------------------------------------------------------- /tests/xferinfo_cb_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vi:ts=4:et 4 | 5 | from . import localhost 6 | import unittest 7 | import pycurl 8 | 9 | from . import util 10 | from . import appmanager 11 | 12 | setup_module, teardown_module = appmanager.setup(('app', 8380)) 13 | 14 | class XferinfoCbTest(unittest.TestCase): 15 | def setUp(self): 16 | self.curl = util.DefaultCurl() 17 | self.curl.setopt(self.curl.URL, 'http://%s:8380/long_pause' % localhost) 18 | 19 | def tearDown(self): 20 | self.curl.close() 21 | 22 | @util.min_libcurl(7, 32, 0) 23 | def test_xferinfo_cb(self): 24 | all_args = [] 25 | 26 | def xferinfofunction(*args): 27 | all_args.append(args) 28 | 29 | self.curl.setopt(pycurl.XFERINFOFUNCTION, xferinfofunction) 30 | self.curl.setopt(pycurl.NOPROGRESS, False) 31 | 32 | self.curl.perform() 33 | assert len(all_args) > 0 34 | for args in all_args: 35 | assert len(args) == 4 36 | for arg in args: 37 | assert isinstance(arg, util.long_int) 38 | 39 | @util.min_libcurl(7, 32, 0) 40 | def test_sockoptfunction_fail(self): 41 | called = {} 42 | 43 | def xferinfofunction(*args): 44 | called['called'] = True 45 | return -1 46 | 47 | self.curl.setopt(pycurl.XFERINFOFUNCTION, xferinfofunction) 48 | self.curl.setopt(pycurl.NOPROGRESS, False) 49 | 50 | try: 51 | self.curl.perform() 52 | self.fail('should have raised') 53 | except pycurl.error as e: 54 | assert e.args[0] in [pycurl.E_ABORTED_BY_CALLBACK], \ 55 | 'Unexpected pycurl error code %s' % e.args[0] 56 | assert called['called'] 57 | 58 | @util.min_libcurl(7, 32, 0) 59 | def test_sockoptfunction_exception(self): 60 | called = {} 61 | 62 | def xferinfofunction(*args): 63 | called['called'] = True 64 | raise ValueError 65 | 66 | self.curl.setopt(pycurl.XFERINFOFUNCTION, xferinfofunction) 67 | self.curl.setopt(pycurl.NOPROGRESS, False) 68 | 69 | try: 70 | self.curl.perform() 71 | self.fail('should have raised') 72 | except pycurl.error as e: 73 | assert e.args[0] in [pycurl.E_ABORTED_BY_CALLBACK], \ 74 | 'Unexpected pycurl error code %s' % e.args[0] 75 | assert called['called'] 76 | -------------------------------------------------------------------------------- /winbuild/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycq0125/pycurl/7b5e12c80cc1ed4578c7276545a7b2be2e4dcc4b/winbuild/__init__.py -------------------------------------------------------------------------------- /winbuild/c-ares-vs2015.patch: -------------------------------------------------------------------------------- 1 | --- a/Makefile.msvc 2015-12-02 22:40:45 2 | +++ b/Makefile.msvc 2015-12-02 22:46:39 3 | @@ -125,6 +125,12 @@ 4 | CC_VERS_NUM = 110 5 | !ELSEIF "$(_NMAKE_VER)" == "11.00.60315.1" 6 | CC_VERS_NUM = 110 7 | +!ELSEIF "$(_NMAKE_VER)" == "11.00.61030.0" 8 | +CC_VERS_NUM = 110 9 | +!ELSEIF "$(_NMAKE_VER)" == "12.00.21005.1" 10 | +CC_VERS_NUM = 120 11 | +!ELSEIF "$(_NMAKE_VER)" == "14.00.23026.0" 12 | +CC_VERS_NUM = 140 13 | !ELSE 14 | -------------------------------------------------------------------------------- /winbuild/cares.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | from .builder import * 3 | 4 | class CaresBuilder(StandardBuilder): 5 | def build(self): 6 | cares_dir = self.standard_fetch_extract( 7 | 'http://c-ares.haxx.se/download/c-ares-%(my_version)s.tar.gz') 8 | if self.bconf.cares_version == '1.12.0': 9 | # msvc_ver.inc is missing in c-ares-1.12.0.tar.gz 10 | # https://github.com/c-ares/c-ares/issues/69 11 | fetch('https://raw.githubusercontent.com/c-ares/c-ares/cares-1_12_0/msvc_ver.inc', 12 | archive='cares-1.12.0/msvc_ver.inc') 13 | with in_dir(cares_dir): 14 | with self.execute_batch() as b: 15 | if self.bconf.cares_version == '1.10.0': 16 | b.add("patch -p1 < %s" % 17 | require_file_exists(os.path.join(config.winbuild_patch_root, 'c-ares-vs2015.patch'))) 18 | b.add("nmake -f Makefile.msvc") 19 | 20 | # assemble dist 21 | b.add('mkdir dist dist\\include dist\\lib') 22 | if self.bconf.cares_version_tuple < (1, 14, 0): 23 | subdir = 'ms%s0' % self.bconf.vc_version 24 | else: 25 | subdir = 'msvc' 26 | b.add('cp %s/cares/lib-release/*.lib dist/lib' % subdir) 27 | b.add('cp *.h dist/include') 28 | -------------------------------------------------------------------------------- /winbuild/iconv.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | from .builder import * 3 | 4 | class LibiconvBuilder(StandardBuilder): 5 | def build(self): 6 | libiconv_dir = self.standard_fetch_extract( 7 | 'https://ftp.gnu.org/pub/gnu/libiconv/libiconv-%(my_version)s.tar.gz') 8 | with in_dir(libiconv_dir): 9 | with self.execute_batch() as b: 10 | b.add("env LD=link bash ./configure") 11 | b.add(config.gmake_path) 12 | -------------------------------------------------------------------------------- /winbuild/idn.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | from .builder import * 3 | 4 | class LibidnBuilder(StandardBuilder): 5 | def build(self): 6 | libidn_dir = self.standard_fetch_extract( 7 | 'https://ftp.gnu.org/gnu/libidn/libidn-%(my_version)s.tar.gz') 8 | with in_dir(libidn_dir): 9 | with self.execute_batch() as b: 10 | b.add("env LD=link bash ./configure") 11 | -------------------------------------------------------------------------------- /winbuild/libcurl-fix-zlib-references.patch: -------------------------------------------------------------------------------- 1 | --- winbuild/MakefileBuild.vc.orig 2015-11-27 07:00:14.000000000 -0800 2 | +++ winbuild/MakefileBuild.vc 2016-01-01 21:33:44.263840800 -0800 3 | @@ -238,7 +238,7 @@ 4 | 5 | # Runtime library configuration 6 | !IF "$(RTLIBCFG)"=="static" 7 | -RTLIB = /MT 8 | +RTLIB = /MD 9 | RTLIB_DEBUG = /MTd 10 | !ELSE 11 | RTLIB = /MD 12 | -------------------------------------------------------------------------------- /winbuild/libssh2-vs2015.patch: -------------------------------------------------------------------------------- 1 | --- win32/libssh2_config.h.orig 2014-12-04 13:43:57.000000000 -0800 2 | +++ win32/libssh2_config.h 2016-01-02 21:56:50.468363200 -0800 3 | @@ -24,7 +24,6 @@ 4 | #define HAVE_SELECT 5 | 6 | #ifdef _MSC_VER 7 | -#define snprintf _snprintf 8 | #if _MSC_VER < 1500 9 | #define vsnprintf _vsnprintf 10 | #endif 11 | -------------------------------------------------------------------------------- /winbuild/nghttp_gmake.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from .builder import * 3 | 4 | class Nghttp2Builder(StandardBuilder): 5 | def build(self): 6 | nghttp2_dir = self.standard_fetch_extract( 7 | 'https://github.com/nghttp2/nghttp2/releases/download/v%(my_version)s/nghttp2-%(my_version)s.tar.gz') 8 | 9 | with in_dir(os.path.join(nghttp2_dir, 'lib')): 10 | with self.execute_batch() as b: 11 | 12 | b.add('"%s" -f Makefile.msvc' % self.bconf.gmake_path) 13 | 14 | # assemble dist 15 | b.add('mkdir ..\\dist ..\\dist\\include ..\\dist\\include\\nghttp2 ..\\dist\\lib') 16 | b.add('cp msvc_obj/*.lib ../dist/lib') 17 | b.add('cp includes/nghttp2/*.h ../dist/include/nghttp2') 18 | 19 | # libcurl expects nghttp2_static.lib apparently, the makefile 20 | # gives a different name to the static library 21 | if not os.path.exists('../dist/lib/nghttp2_static.lib'): 22 | shutil.copy('../dist/lib/nghttp2-static.lib', '../dist/lib/nghttp2_static.lib') 23 | -------------------------------------------------------------------------------- /winbuild/openssl-fix-crt-1.0.2.patch: -------------------------------------------------------------------------------- 1 | --- util/pl/VC-32.pl.orig 2015-12-03 06:04:23.000000000 -0800 2 | +++ util/pl/VC-32.pl 2016-01-01 23:56:32.542632200 -0800 3 | @@ -45,7 +45,7 @@ 4 | # considered safe to ignore. 5 | # 6 | $base_cflags= " $mf_cflag"; 7 | - my $f = $shlib || $fips ?' /MD':' /MT'; 8 | + my $f = $shlib || $fips ?' /MD':' /MD'; 9 | $opt_cflags=$f.' /Ox'; 10 | $dbg_cflags=$f.'d /Od -DDEBUG -D_DEBUG'; 11 | $lflags="/nologo /subsystem:console /opt:ref"; 12 | @@ -119,7 +119,7 @@ 13 | $base_cflags.=' -I$(WCECOMPAT)/include' if (defined($ENV{'WCECOMPAT'})); 14 | $base_cflags.=' -I$(PORTSDK_LIBPATH)/../../include' if (defined($ENV{'PORTSDK_LIBPATH'})); 15 | if (`$cc 2>&1` =~ /Version ([0-9]+)\./ && $1>=14) { 16 | - $base_cflags.=$shlib?' /MD':' /MT'; 17 | + $base_cflags.=$shlib?' /MD':' /MD'; 18 | } else { 19 | $base_cflags.=' /MC'; 20 | } 21 | @@ -130,13 +130,13 @@ 22 | else # Win32 23 | { 24 | $base_cflags= " $mf_cflag"; 25 | - my $f = $shlib || $fips ?' /MD':' /MT'; 26 | + my $f = $shlib || $fips ?' /MD':' /MD'; 27 | $ff = "/fixed"; 28 | $opt_cflags=$f.' /Ox /O2 /Ob2'; 29 | $dbg_cflags=$f.'d /Od -DDEBUG -D_DEBUG'; 30 | $lflags="/nologo /subsystem:console /opt:ref"; 31 | } 32 | -$lib_cflag='/Zl' if (!$shlib); # remove /DEFAULTLIBs from static lib 33 | +#$lib_cflag='/Zl' if (!$shlib); # remove /DEFAULTLIBs from static lib 34 | $mlflags=''; 35 | 36 | $out_def ="out32"; $out_def.="dll" if ($shlib); 37 | -------------------------------------------------------------------------------- /winbuild/openssl-fix-crt-1.1.0.patch: -------------------------------------------------------------------------------- 1 | --- Configurations/10-main.conf.orig 2016-11-10 06:03:43.000000000 -0800 2 | +++ Configurations/10-main.conf 2016-12-15 20:18:47.576426000 -0800 3 | @@ -1291,7 +1291,7 @@ 4 | ($disabled{shared} ? "" : "/MD") 5 | ." /O2"; 6 | })), 7 | - lib_cflags => add(sub { $disabled{shared} ? "/MT /Zl" : () }), 8 | + lib_cflags => add(sub { $disabled{shared} ? "/MD" : () }), 9 | # Following might/should appears controversial, i.e. defining 10 | # /MDd without evaluating $disabled{shared}. It works in 11 | # non-shared build because static library is compiled with /Zl 12 | @@ -1304,7 +1304,7 @@ 13 | # prefer [non-debug] openssl.exe to be free from Micorosoft RTL 14 | # redistributable. 15 | bin_cflags => add(picker(debug => "/MDd", 16 | - release => sub { $disabled{shared} ? "/MT" : () }, 17 | + release => sub { $disabled{shared} ? "/MD" : () }, 18 | )), 19 | bin_lflags => add("/subsystem:console /opt:ref"), 20 | ex_libs => add(sub { 21 | @@ -1385,7 +1385,7 @@ 22 | sub { defined($ENV{'PORTSDK_LIBPATH'}) 23 | ? '-I$(PORTSDK_LIBPATH)/../../include' : (); }, 24 | sub { `cl 2>&1` =~ /Version ([0-9]+)\./ && $1>=14 25 | - ? ($disabled{shared} ? " /MT" : " /MD") 26 | + ? ($disabled{shared} ? " /MD" : " /MD") 27 | : " /MC"; }), 28 | debug => "/Od -DDEBUG -D_DEBUG", 29 | release => "/O1i"), 30 | -------------------------------------------------------------------------------- /winbuild/openssl-fix-crt-1.1.1.patch: -------------------------------------------------------------------------------- 1 | --- Configurations/10-main.conf.orig 2019-09-10 09:13:07.000000000 -0400 2 | +++ Configurations/10-main.conf 2020-01-27 13:16:41.992273600 -0500 3 | @@ -1252,7 +1252,7 @@ 4 | })), 5 | defines => add(picker(default => [], # works as type cast 6 | debug => [ "DEBUG", "_DEBUG" ])), 7 | - lib_cflags => add(sub { $disabled{shared} ? "/MT /Zl" : () }), 8 | + lib_cflags => add(sub { $disabled{shared} ? "/MD" : () }), 9 | # Following might/should appears controversial, i.e. defining 10 | # /MDd without evaluating $disabled{shared}. It works in 11 | # non-shared build because static library is compiled with /Zl 12 | @@ -1265,7 +1265,7 @@ 13 | # prefer [non-debug] openssl.exe to be free from Micorosoft RTL 14 | # redistributable. 15 | bin_cflags => add(picker(debug => "/MDd", 16 | - release => sub { $disabled{shared} ? "/MT" : () }, 17 | + release => sub { $disabled{shared} ? "/MD" : () }, 18 | )), 19 | bin_lflags => add("/subsystem:console /opt:ref"), 20 | ex_libs => add(sub { 21 | @@ -1335,7 +1335,7 @@ 22 | combine('/GF /Gy', 23 | sub { vc_wince_info()->{cflags}; }, 24 | sub { `cl 2>&1` =~ /Version ([0-9]+)\./ && $1>=14 25 | - ? ($disabled{shared} ? " /MT" : " /MD") 26 | + ? ($disabled{shared} ? " /MD" : " /MD") 27 | : " /MC"; }), 28 | cppflags => sub { vc_wince_info()->{cppflags}; }, 29 | lib_defines => add("NO_CHMOD", "OPENSSL_SMALL_FOOTPRINT"), 30 | -------------------------------------------------------------------------------- /winbuild/pythons.py: -------------------------------------------------------------------------------- 1 | 2 | class PythonRelease(str): 3 | @property 4 | def dotless(self): 5 | return self.replace('.', '') 6 | 7 | class PythonVersion(str): 8 | @property 9 | def release(self): 10 | return PythonRelease('.'.join(self.split('.')[:2])) 11 | 12 | class PythonBinary(object): 13 | def __init__(self, python_release, bitness): 14 | self.python_release = python_release 15 | self.bitness = bitness 16 | 17 | def executable_path(self, config): 18 | return config.python_path_template % dict( 19 | python_release=self.python_release.dotless, 20 | bitness=self.bitness) 21 | -------------------------------------------------------------------------------- /winbuild/ssh.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | from .builder import * 3 | 4 | class Libssh2Builder(StandardBuilder): 5 | def build(self): 6 | libssh2_dir = self.standard_fetch_extract( 7 | 'http://www.libssh2.org/download/libssh2-%(my_version)s.tar.gz') 8 | with in_dir(libssh2_dir): 9 | with self.execute_batch() as b: 10 | if self.bconf.libssh2_version_tuple < (1, 8, 0) and self.bconf.vc_version == 'vc14': 11 | b.add("patch -p0 < %s" % 12 | require_file_exists(os.path.join(config.winbuild_patch_root, 'libssh2-vs2015.patch'))) 13 | zlib_builder = ZlibBuilder(bconf=self.bconf) 14 | openssl_builder = OpensslBuilder(bconf=self.bconf) 15 | vars = ''' 16 | OPENSSLINC=%(openssl_include_path)s 17 | OPENSSLLIB=%(openssl_lib_path)s 18 | ZLIBINC=%(zlib_include_path)s 19 | ZLIBLIB=%(zlib_lib_path)s 20 | WITH_ZLIB=1 21 | BUILD_STATIC_LIB=1 22 | ''' % dict( 23 | openssl_include_path=openssl_builder.include_path, 24 | openssl_lib_path=openssl_builder.lib_path, 25 | zlib_include_path=zlib_builder.include_path, 26 | zlib_lib_path=zlib_builder.lib_path, 27 | ) 28 | with open('win32/config.mk', 'r+') as cf: 29 | contents = cf.read() 30 | cf.seek(0) 31 | cf.write(vars) 32 | cf.write(contents) 33 | b.add("nmake -f NMakefile") 34 | # libcurl loves its _a suffixes on static library names 35 | b.add("cp Release\\src\\libssh2.lib Release\\src\\libssh2_a.lib") 36 | 37 | # assemble dist 38 | b.add('mkdir dist dist\\include dist\\lib') 39 | b.add('cp Release/src/*.lib dist/lib') 40 | b.add('cp -r include dist') 41 | -------------------------------------------------------------------------------- /winbuild/tools.py: -------------------------------------------------------------------------------- 1 | from .config import * 2 | 3 | def short_python_versions(python_versions): 4 | return ['.'.join(python_version.split('.')[:2]) 5 | for python_version in python_versions] 6 | 7 | def needed_vc_versions(config, python_versions): 8 | return [vc_version for vc_version in config.vc_paths.keys() 9 | if vc_version in [ 10 | PYTHON_VC_VERSIONS[short_python_version] 11 | for short_python_version in short_python_versions(python_versions)]] 12 | -------------------------------------------------------------------------------- /winbuild/vcvars-vc14-32.sh: -------------------------------------------------------------------------------- 1 | # Courtesy of libiconv 1.15 2 | 3 | # Set environment variables for using MSVC 14, 4 | # for creating native 32-bit Windows executables. 5 | 6 | # Windows C library headers and libraries. 7 | WindowsCrtIncludeDir='C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt' 8 | WindowsCrtLibDir='C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\' 9 | INCLUDE="${WindowsCrtIncludeDir};$INCLUDE" 10 | LIB="${WindowsCrtLibDir}x86;$LIB" 11 | 12 | # Windows API headers and libraries. 13 | WindowsSdkIncludeDir='C:\Program Files (x86)\Windows Kits\8.1\Include\' 14 | WindowsSdkLibDir='C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\' 15 | INCLUDE="${WindowsSdkIncludeDir}um;${WindowsSdkIncludeDir}shared;$INCLUDE" 16 | LIB="${WindowsSdkLibDir}x86;$LIB" 17 | 18 | # Visual C++ tools, headers and libraries. 19 | VSINSTALLDIR='C:\Program Files (x86)\Microsoft Visual Studio 14.0' 20 | VCINSTALLDIR="${VSINSTALLDIR}"'\VC' 21 | PATH=`cygpath -u "${VCINSTALLDIR}"`/bin:"$PATH" 22 | INCLUDE="${VCINSTALLDIR}"'\include;'"${INCLUDE}" 23 | LIB="${VCINSTALLDIR}"'\lib;'"${LIB}" 24 | 25 | export INCLUDE LIB 26 | -------------------------------------------------------------------------------- /winbuild/vcvars-vc14-64.sh: -------------------------------------------------------------------------------- 1 | # Courtesy of libiconv 1.15 2 | 3 | # Set environment variables for using MSVC 14, 4 | # for creating native 64-bit Windows executables. 5 | 6 | # Windows C library headers and libraries. 7 | WindowsCrtIncludeDir='C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt' 8 | WindowsCrtLibDir='C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\' 9 | INCLUDE="${WindowsCrtIncludeDir};$INCLUDE" 10 | LIB="${WindowsCrtLibDir}x64;$LIB" 11 | 12 | # Windows API headers and libraries. 13 | WindowsSdkIncludeDir='C:\Program Files (x86)\Windows Kits\8.1\Include\' 14 | WindowsSdkLibDir='C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\' 15 | INCLUDE="${WindowsSdkIncludeDir}um;${WindowsSdkIncludeDir}shared;$INCLUDE" 16 | LIB="${WindowsSdkLibDir}x64;$LIB" 17 | 18 | # Visual C++ tools, headers and libraries. 19 | VSINSTALLDIR='C:\Program Files (x86)\Microsoft Visual Studio 14.0' 20 | VCINSTALLDIR="${VSINSTALLDIR}"'\VC' 21 | PATH=`cygpath -u "${VCINSTALLDIR}"`/bin/amd64:"$PATH" 22 | INCLUDE="${VCINSTALLDIR}"'\include;'"${INCLUDE}" 23 | LIB="${VCINSTALLDIR}"'\lib\amd64;'"${LIB}" 24 | 25 | export INCLUDE LIB -------------------------------------------------------------------------------- /winbuild/zlib.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | from .utils import * 3 | from .builder import * 4 | 5 | class ZlibBuilder(StandardBuilder): 6 | def build(self): 7 | zlib_dir = self.standard_fetch_extract( 8 | 'http://downloads.sourceforge.net/project/libpng/zlib/%(my_version)s/zlib-%(my_version)s.tar.gz') 9 | with in_dir(zlib_dir): 10 | with self.execute_batch() as b: 11 | b.add("nmake /f win32/Makefile.msc") 12 | # libcurl loves its _a suffixes on static library names 13 | b.add("cp zlib.lib zlib_a.lib") 14 | 15 | # assemble dist 16 | b.add('mkdir dist dist\\include dist\\lib dist\\bin') 17 | b.add('cp *.lib *.exp dist/lib') 18 | b.add('cp *.dll dist/bin') 19 | b.add('cp *.h dist/include') 20 | 21 | @property 22 | def dll_paths(self): 23 | return [ 24 | os.path.join(self.bin_path, 'zlib1.dll'), 25 | ] 26 | --------------------------------------------------------------------------------