├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── ci ├── .cargo │ └── config.toml ├── Dockerfile-alma8 ├── Dockerfile-linux32 ├── Dockerfile-linux64 ├── Dockerfile-linux64-curl ├── Dockerfile-mingw ├── Dockerfile-musl └── run.sh ├── curl-sys ├── Cargo.toml ├── LICENSE ├── build.rs └── lib.rs ├── examples ├── aws_sigv4.rs ├── doh.rs ├── https.rs ├── multi-dl.rs ├── ssl_cert_blob.rs ├── ssl_client_auth.rs └── ssl_proxy.rs ├── src ├── easy │ ├── form.rs │ ├── handle.rs │ ├── handler.rs │ ├── list.rs │ ├── mod.rs │ └── windows.rs ├── error.rs ├── lib.rs ├── multi.rs ├── panic.rs └── version.rs ├── systest ├── Cargo.toml ├── build.rs ├── src │ └── main.rs └── version_detect.c └── tests ├── atexit.rs ├── easy.rs ├── formdata ├── multi.rs ├── post.rs ├── protocols.rs └── server └── mod.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | test: 10 | name: Test 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | build: [x86_64, i686, x86_64-musl, mingw, system-curl, openssl-110, x86_64-beta, x86_64-nightly, macos, win64, win32] 16 | include: 17 | - build: x86_64 18 | os: ubuntu-latest 19 | rust: stable 20 | docker: linux64 21 | target: x86_64-unknown-linux-gnu 22 | - build: ubuntu-lts 23 | os: ubuntu-24.04 24 | rust: 1.75 25 | docker: linux64 26 | target: x86_64-unknown-linux-gnu 27 | - build: x86_64-beta 28 | os: ubuntu-latest 29 | rust: beta 30 | docker: linux64 31 | target: x86_64-unknown-linux-gnu 32 | - build: x86_64-nightly 33 | os: ubuntu-latest 34 | rust: nightly 35 | docker: linux64 36 | target: x86_64-unknown-linux-gnu 37 | - build: i686 38 | os: ubuntu-latest 39 | rust: stable 40 | docker: linux32 41 | target: i686-unknown-linux-gnu 42 | - build: x86_64-musl 43 | os: ubuntu-latest 44 | rust: stable 45 | docker: musl 46 | target: x86_64-unknown-linux-musl 47 | - build: mingw 48 | os: ubuntu-latest 49 | rust: stable 50 | docker: mingw 51 | target: x86_64-pc-windows-gnu 52 | no_run: true 53 | - build: system-curl 54 | os: ubuntu-latest 55 | rust: stable 56 | docker: linux64-curl 57 | target: x86_64-unknown-linux-gnu 58 | - build: openssl-110 59 | os: ubuntu-latest 60 | rust: stable 61 | docker: alma8 62 | target: x86_64-unknown-linux-gnu 63 | - build: macos 64 | os: macos-latest 65 | rust: stable 66 | target: x86_64-apple-darwin 67 | - build: win32 68 | os: windows-latest 69 | rust: stable 70 | target: i686-pc-windows-msvc 71 | - build: win64 72 | os: windows-latest 73 | rust: stable 74 | target: x86_64-pc-windows-msvc 75 | steps: 76 | - uses: actions/checkout@v1 77 | with: 78 | submodules: true 79 | - name: Install Rust (rustup) 80 | run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} 81 | shell: bash 82 | - run: rustup target add ${{ matrix.target }} 83 | - run: TARGET=${{ matrix.target }} ./ci/run.sh 84 | if: matrix.os != 'ubuntu-latest' 85 | name: Run non-docker tests 86 | shell: bash 87 | - run: | 88 | set -e 89 | cargo generate-lockfile 90 | mkdir .cargo target 91 | docker build -t rust -f ci/Dockerfile-${{ matrix.docker }} ci 92 | docker run \ 93 | -w /src \ 94 | -v `pwd`:/src:ro \ 95 | -v `pwd`/target:/src/target \ 96 | -v `pwd`/ci/.cargo:/src/.cargo:ro \ 97 | -v `rustc --print sysroot`:/usr/local:ro \ 98 | -e TARGET=${{ matrix.target }} \ 99 | -e NO_RUN=${{ matrix.no_run }} \ 100 | -e CARGO_TARGET_DIR=/src/target \ 101 | rust \ 102 | sh ci/run.sh 103 | if: matrix.os == 'ubuntu-latest' 104 | name: Run docker tests 105 | 106 | rustfmt: 107 | name: Rustfmt 108 | runs-on: ubuntu-latest 109 | steps: 110 | - uses: actions/checkout@v1 111 | with: 112 | submodules: true 113 | - name: Install Rust 114 | run: rustup update stable && rustup default stable && rustup component add rustfmt 115 | - run: cargo fmt -- --check 116 | 117 | publish_docs: 118 | name: Publish Documentation 119 | runs-on: ubuntu-latest 120 | steps: 121 | - uses: actions/checkout@v1 122 | with: 123 | submodules: true 124 | - name: Install Rust 125 | run: rustup update stable && rustup default stable 126 | - name: Install dependencies 127 | run: | 128 | sudo apt update 129 | sudo apt install libkrb5-dev 130 | - name: Build documentation 131 | run: cargo doc --no-deps --all-features 132 | - name: Publish documentation 133 | run: | 134 | cd target/doc 135 | git init 136 | git add . 137 | git -c user.name='ci' -c user.email='ci' commit -m init 138 | git push -f -q https://git:${{ secrets.github_token }}@github.com/${{ github.repository }} HEAD:gh-pages 139 | if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' 140 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "curl-sys/curl"] 2 | path = curl-sys/curl 3 | url = https://github.com/curl/curl.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "curl" 3 | version = "0.4.47" 4 | authors = ["Alex Crichton "] 5 | license = "MIT" 6 | repository = "https://github.com/alexcrichton/curl-rust" 7 | homepage = "https://github.com/alexcrichton/curl-rust" 8 | documentation = "https://docs.rs/curl" 9 | description = "Rust bindings to libcurl for making HTTP requests" 10 | categories = ["api-bindings", "web-programming::http-client"] 11 | readme = "README.md" 12 | autotests = true 13 | edition = "2018" 14 | 15 | [dependencies] 16 | libc = "0.2.42" 17 | curl-sys = { path = "curl-sys", version = "0.4.77", default-features = false } 18 | socket2 = "0.5.0" 19 | 20 | # Unix platforms use OpenSSL for now to provide SSL functionality 21 | [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] 22 | openssl-sys = { version = "0.9.64", optional = true } 23 | openssl-probe = { version = "0.1.2", optional = true } 24 | 25 | [target.'cfg(target_env = "msvc")'.dependencies] 26 | schannel = "0.1.13" 27 | windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_LibraryLoader", "Win32_Security_Cryptography"] } 28 | 29 | [dev-dependencies] 30 | mio = "0.6" 31 | mio-extras = "2.0.3" 32 | anyhow = "1.0.31" 33 | 34 | [workspace] 35 | members = ["systest"] 36 | 37 | [features] 38 | default = ["ssl"] 39 | ssl = ["openssl-sys", "openssl-probe", "curl-sys/ssl"] # OpenSSL/system TLS backend 40 | mesalink = ["curl-sys/mesalink"] # MesaLink TLS backend 41 | http2 = ["curl-sys/http2"] 42 | spnego = ["curl-sys/spnego"] 43 | rustls = ["curl-sys/rustls"] 44 | static-curl = ["curl-sys/static-curl"] 45 | static-ssl = ["curl-sys/static-ssl"] 46 | windows-static-ssl = ["static-curl", "curl-sys/windows-static-ssl"] 47 | force-system-lib-on-osx = ['curl-sys/force-system-lib-on-osx'] 48 | protocol-ftp = ["curl-sys/protocol-ftp"] 49 | zlib-ng-compat = ["curl-sys/zlib-ng-compat", "static-curl"] 50 | upkeep_7_62_0 = ["curl-sys/upkeep_7_62_0"] 51 | poll_7_68_0 = ["curl-sys/poll_7_68_0"] 52 | ntlm = ["curl-sys/ntlm"] 53 | 54 | [[test]] 55 | name = "atexit" 56 | harness = false 57 | 58 | [[example]] 59 | name = "https" 60 | path = "examples/https.rs" 61 | 62 | [[example]] 63 | name = "ssl_proxy" 64 | path = "examples/ssl_proxy.rs" 65 | required-features = ["ssl"] 66 | 67 | [[example]] 68 | name = "ssl_cert_blob" 69 | path = "examples/ssl_cert_blob.rs" 70 | required-features = ["ssl"] 71 | 72 | [[example]] 73 | name = "ssl_client_auth" 74 | path = "examples/ssl_client_auth.rs" 75 | required-features = [] 76 | 77 | [[example]] 78 | name = "aws_sigv4" 79 | path = "examples/aws_sigv4.rs" 80 | required-features = ["static-curl", "ssl"] 81 | 82 | [[example]] 83 | name = "multi-dl" 84 | path = "examples/multi-dl.rs" 85 | required-features = ["ssl"] 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Carl Lerche 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 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. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # curl-rust 2 | 3 | [libcurl] bindings for Rust 4 | 5 | [![Latest Version](https://img.shields.io/crates/v/curl.svg)](https://crates.io/crates/curl) 6 | [![Documentation](https://docs.rs/curl/badge.svg)](https://docs.rs/curl) 7 | [![License](https://img.shields.io/github/license/alexcrichton/curl-rust.svg)](LICENSE) 8 | [![Build](https://github.com/alexcrichton/curl-rust/workflows/CI/badge.svg)](https://github.com/alexcrichton/curl-rust/actions) 9 | 10 | ## Quick Start 11 | 12 | ```rust 13 | use std::io::{stdout, Write}; 14 | 15 | use curl::easy::Easy; 16 | 17 | // Print a web page onto stdout 18 | fn main() { 19 | let mut easy = Easy::new(); 20 | easy.url("https://www.rust-lang.org/").unwrap(); 21 | easy.write_function(|data| { 22 | stdout().write_all(data).unwrap(); 23 | Ok(data.len()) 24 | }).unwrap(); 25 | easy.perform().unwrap(); 26 | 27 | println!("{}", easy.response_code().unwrap()); 28 | } 29 | ``` 30 | 31 | ```rust 32 | use curl::easy::Easy; 33 | 34 | // Capture output into a local `Vec`. 35 | fn main() { 36 | let mut dst = Vec::new(); 37 | let mut easy = Easy::new(); 38 | easy.url("https://www.rust-lang.org/").unwrap(); 39 | 40 | let mut transfer = easy.transfer(); 41 | transfer.write_function(|data| { 42 | dst.extend_from_slice(data); 43 | Ok(data.len()) 44 | }).unwrap(); 45 | transfer.perform().unwrap(); 46 | } 47 | ``` 48 | 49 | ## Post / Put requests 50 | 51 | The `put` and `post` methods on `Easy` can configure the method of the HTTP 52 | request, and then `read_function` can be used to specify how data is filled in. 53 | This interface works particularly well with types that implement `Read`. 54 | 55 | ```rust,no_run 56 | use std::io::Read; 57 | use curl::easy::Easy; 58 | 59 | fn main() { 60 | let mut data = "this is the body".as_bytes(); 61 | 62 | let mut easy = Easy::new(); 63 | easy.url("http://www.example.com/upload").unwrap(); 64 | easy.post(true).unwrap(); 65 | easy.post_field_size(data.len() as u64).unwrap(); 66 | 67 | let mut transfer = easy.transfer(); 68 | transfer.read_function(|buf| { 69 | Ok(data.read(buf).unwrap_or(0)) 70 | }).unwrap(); 71 | transfer.perform().unwrap(); 72 | } 73 | ``` 74 | 75 | ## Custom headers 76 | 77 | Custom headers can be specified as part of the request: 78 | 79 | ```rust,no_run 80 | use curl::easy::{Easy, List}; 81 | 82 | fn main() { 83 | let mut easy = Easy::new(); 84 | easy.url("http://www.example.com").unwrap(); 85 | 86 | let mut list = List::new(); 87 | list.append("Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==").unwrap(); 88 | easy.http_headers(list).unwrap(); 89 | easy.perform().unwrap(); 90 | } 91 | ``` 92 | 93 | ## Keep alive 94 | 95 | The handle can be re-used across multiple requests. Curl will attempt to 96 | keep the connections alive. 97 | 98 | ```rust,no_run 99 | use curl::easy::Easy; 100 | 101 | fn main() { 102 | let mut handle = Easy::new(); 103 | 104 | handle.url("http://www.example.com/foo").unwrap(); 105 | handle.perform().unwrap(); 106 | 107 | handle.url("http://www.example.com/bar").unwrap(); 108 | handle.perform().unwrap(); 109 | } 110 | ``` 111 | 112 | ## Multiple requests 113 | 114 | The libcurl library provides support for sending multiple requests 115 | simultaneously through the "multi" interface. This is currently bound in the 116 | `multi` module of this crate and provides the ability to execute multiple 117 | transfers simultaneously. For more information, see that module. 118 | 119 | ## Building 120 | 121 | By default, this crate will attempt to dynamically link to the system-wide 122 | libcurl and the system-wide SSL library. Some of this behavior can be customized 123 | with various Cargo features: 124 | 125 | - `ssl`: Enable SSL/TLS support using the platform-default TLS backend. On Windows this is [Schannel], on macOS [Secure Transport], and [OpenSSL] (or equivalent) on all other platforms. Enabled by default. 126 | - `rustls` Enable SSL/TLS support via [Rustls], a well-received alternative TLS backend written in Rust. Rustls is always statically linked. Disabled by default. 127 | 128 | Note that Rustls support is experimental within Curl itself and may have significant bugs, so we don't offer any sort of stability guarantee with this feature. 129 | - `http2`: Enable HTTP/2 support via libnghttp2. Disabled by default. 130 | - `static-curl`: Use a bundled libcurl version and statically link to it. Disabled by default. 131 | - `static-ssl`: Use a bundled OpenSSL version and statically link to it. Only applies on platforms that use OpenSSL. Disabled by default. 132 | - `spnego`: Enable SPNEGO support. Disabled by default. 133 | - `upkeep_7_62_0`: Enable curl_easy_upkeep() support, introduced in curl 7.62.0. Disabled by default. 134 | - `poll_7_68_0`: Enable curl_multi_poll()/curl_multi_wakeup() support, requires curl 7.68.0 or later. Disabled by default. 135 | - `ntlm`: Enable NTLM support in curl. Disabled by default. 136 | - `windows-static-ssl`: Enable Openssl support on Windows via the static build provided by vcpkg. Incompatible with `ssl` (use `--no-default-features`). Disabled by default. 137 | 138 | Note that to install openssl on windows via vcpkg the following commands needs to be ran: 139 | ```shell 140 | git clone https://github.com/microsoft/vcpkg 141 | cd vcpkg 142 | ./bootstrap-vcpkg.bat -disableMetrics 143 | ./vcpkg.exe integrate install 144 | ./vcpkg.exe install openssl:x64-windows-static-md 145 | ``` 146 | 147 | ## Version Support 148 | 149 | The bindings have been developed using curl version 7.24.0. They should 150 | work with any newer version of curl and possibly with older versions, 151 | but this has not been tested. 152 | 153 | ## Troubleshooting 154 | 155 | ### Curl built against the NSS SSL library 156 | 157 | If you encounter the following error message: 158 | 159 | ``` 160 | [77] Problem with the SSL CA cert (path? access rights?) 161 | ``` 162 | 163 | That means most likely, that curl was linked against `libcurl-nss.so` due to 164 | installed libcurl NSS development files, and that the required library 165 | `libnsspem.so` is missing. See also the curl man page: "If curl is built 166 | against the NSS SSL library, the NSS PEM PKCS#11 module (`libnsspem.so`) needs to be available for this option to work properly." 167 | 168 | In order to avoid this failure you can either 169 | 170 | * install the missing library (e.g. Debian: `nss-plugin-pem`), or 171 | * remove the libcurl NSS development files (e.g. Debian: `libcurl4-nss-dev`) and 172 | rebuild curl-rust. 173 | 174 | ## License 175 | 176 | The `curl-rust` crate is licensed under the MIT license, see [`LICENSE`](LICENSE) for more 177 | details. 178 | 179 | 180 | [libcurl]: https://curl.haxx.se/libcurl/ 181 | [OpenSSL]: https://www.openssl.org/ 182 | [Rustls]: https://github.com/ctz/rustls 183 | [Schannel]: https://docs.microsoft.com/en-us/windows/win32/com/schannel 184 | [Secure Transport]: https://developer.apple.com/documentation/security/secure_transport 185 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | println!( 5 | "cargo:rustc-check-cfg=cfg(\ 6 | need_openssl_init,\ 7 | need_openssl_probe,\ 8 | )" 9 | ); 10 | // OpenSSL >= 1.1.0 can be initialized concurrently and is initialized correctly by libcurl. 11 | // <= 1.0.2 need locking callbacks, which are provided by openssl_sys::init(). 12 | let use_openssl = match env::var("DEP_OPENSSL_VERSION_NUMBER") { 13 | Ok(version) => { 14 | let version = u64::from_str_radix(&version, 16).unwrap(); 15 | if version < 0x1_01_00_00_0 { 16 | println!("cargo:rustc-cfg=need_openssl_init"); 17 | } 18 | true 19 | } 20 | Err(_) => false, 21 | }; 22 | 23 | if use_openssl { 24 | // The system libcurl should have the default certificate paths configured. 25 | if env::var_os("DEP_CURL_STATIC").is_some() { 26 | println!("cargo:rustc-cfg=need_openssl_probe"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ci/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-gnu] 2 | linker = "x86_64-w64-mingw32-gcc" 3 | -------------------------------------------------------------------------------- /ci/Dockerfile-alma8: -------------------------------------------------------------------------------- 1 | FROM almalinux:8 2 | 3 | RUN dnf update -y 4 | RUN dnf install -y \ 5 | gcc ca-certificates make \ 6 | openssl-devel \ 7 | pkgconfig 8 | -------------------------------------------------------------------------------- /ci/Dockerfile-linux32: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN dpkg --add-architecture i386 && \ 4 | apt-get update && \ 5 | apt-get install -y --no-install-recommends \ 6 | gcc-multilib \ 7 | ca-certificates \ 8 | make \ 9 | libc6-dev \ 10 | libssl-dev:i386 \ 11 | pkg-config 12 | 13 | ENV PKG_CONFIG=i686-linux-gnu-pkg-config \ 14 | PKG_CONFIG_ALLOW_CROSS=1 15 | -------------------------------------------------------------------------------- /ci/Dockerfile-linux64: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y --no-install-recommends \ 5 | gcc ca-certificates make libc6-dev \ 6 | libssl-dev \ 7 | pkg-config 8 | -------------------------------------------------------------------------------- /ci/Dockerfile-linux64-curl: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y --no-install-recommends \ 5 | gcc ca-certificates make libc6-dev \ 6 | libssl-dev libcurl4-openssl-dev pkg-config 7 | -------------------------------------------------------------------------------- /ci/Dockerfile-mingw: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y --no-install-recommends \ 5 | gcc ca-certificates make libc6-dev \ 6 | gcc-mingw-w64-x86-64 libz-mingw-w64-dev 7 | -------------------------------------------------------------------------------- /ci/Dockerfile-musl: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y --no-install-recommends \ 5 | gcc ca-certificates make libc6-dev curl \ 6 | musl-tools perl 7 | -------------------------------------------------------------------------------- /ci/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | # For musl on CI always use openssl-src dependency and build from there. 6 | if [ "$TARGET" = "x86_64-unknown-linux-musl" ]; then 7 | features="--features static-ssl" 8 | fi 9 | 10 | cargo test --target $TARGET --no-run $features 11 | # First test with no extra protocols enabled. 12 | cargo test --target $TARGET --no-run --features static-curl $features 13 | # Then with rustls TLS backend. 14 | # 15 | # Note: Cross-compiling rustls on windows doesn't work due to requiring some 16 | # NASM build stuff in aws_lc_rs, which may soon be fixed by 17 | # https://github.com/aws/aws-lc-rs/pull/528. 18 | # 19 | # Compiling on i686-windows requires nasm to be installed (other platforms 20 | # have pre-compiled object files), which is just slightly too much 21 | # inconvenience for me. 22 | if [ "$TARGET" != "x86_64-pc-windows-gnu" ] && [ "$TARGET" != "i686-pc-windows-msvc" ] 23 | then 24 | cargo test --target $TARGET --no-run --features rustls,static-curl $features 25 | fi 26 | # Then with all extra protocols enabled. 27 | cargo test --target $TARGET --no-run --features static-curl,protocol-ftp,ntlm $features 28 | if [ -z "$NO_RUN" ]; then 29 | cargo test --target $TARGET $features 30 | cargo test --target $TARGET --features static-curl $features 31 | cargo test --target $TARGET --features static-curl,protocol-ftp $features 32 | 33 | # Note that `-Clink-dead-code` is passed here to suppress `--gc-sections` to 34 | # help confirm that we're compiling everything necessary for curl itself. 35 | RUSTFLAGS=-Clink-dead-code \ 36 | cargo run --manifest-path systest/Cargo.toml --target $TARGET $features 37 | RUSTFLAGS=-Clink-dead-code \ 38 | cargo run --manifest-path systest/Cargo.toml --target $TARGET --features curl-sys/static-curl,curl-sys/protocol-ftp $features 39 | 40 | cargo doc --no-deps --target $TARGET $features 41 | cargo doc --no-deps -p curl-sys --target $TARGET $features 42 | fi 43 | -------------------------------------------------------------------------------- /curl-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "curl-sys" 3 | version = "0.4.80+curl-8.12.1" 4 | authors = ["Alex Crichton "] 5 | links = "curl" 6 | build = "build.rs" 7 | license = "MIT" 8 | repository = "https://github.com/alexcrichton/curl-rust" 9 | description = "Native bindings to the libcurl library" 10 | documentation = "https://docs.rs/curl-sys" 11 | categories = ["external-ffi-bindings"] 12 | edition = "2018" 13 | exclude = ["curl/docs/", "curl/tests/"] 14 | 15 | [lib] 16 | name = "curl_sys" 17 | path = "lib.rs" 18 | 19 | [dependencies] 20 | libz-sys = { version = "1.0.18", default-features = false, features = ["libc"] } 21 | libc = "0.2.2" 22 | libnghttp2-sys = { optional = true, version = "0.1.3" } 23 | 24 | [dependencies.rustls-ffi] 25 | version = "0.14" 26 | optional = true 27 | features = ["no_log_capture"] 28 | 29 | [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] 30 | openssl-sys = { version = "0.9.64", optional = true } 31 | 32 | [target.'cfg(windows)'.dependencies] 33 | windows-sys = { version = "0.59", features = ["Win32_Networking_WinSock"] } 34 | 35 | [target.'cfg(target_env = "msvc")'.build-dependencies] 36 | vcpkg = "0.2" 37 | 38 | [build-dependencies] 39 | pkg-config = "0.3.3" 40 | cc = "1.0" 41 | 42 | [features] 43 | default = ["ssl"] 44 | ssl = ["openssl-sys"] 45 | http2 = ["libnghttp2-sys"] 46 | mesalink = [] 47 | rustls = ["rustls-ffi"] 48 | static-curl = [] 49 | windows-static-ssl = [] 50 | static-ssl = ["openssl-sys/vendored"] 51 | spnego = [] 52 | force-system-lib-on-osx = [] 53 | protocol-ftp = [] 54 | zlib-ng-compat = ["libz-sys/zlib-ng", "static-curl"] 55 | upkeep_7_62_0 = [] 56 | poll_7_68_0 = [] 57 | ntlm = [] 58 | -------------------------------------------------------------------------------- /curl-sys/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /curl-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::{Path, PathBuf}; 4 | use std::process::Command; 5 | 6 | fn main() { 7 | println!("cargo:rerun-if-changed=curl"); 8 | println!( 9 | "cargo:rustc-check-cfg=cfg(\ 10 | libcurl_vendored,\ 11 | link_libnghttp2,\ 12 | link_libz,\ 13 | link_openssl,\ 14 | )" 15 | ); 16 | let target = env::var("TARGET").unwrap(); 17 | let windows = target.contains("windows"); 18 | 19 | if cfg!(feature = "mesalink") { 20 | println!("cargo:warning=MesaLink support has been removed as of curl 7.82.0, will use default TLS backend instead."); 21 | } 22 | 23 | // This feature trumps all others, and is largely set by rustbuild to force 24 | // usage of the system library to ensure that we're always building an 25 | // ABI-compatible Cargo. 26 | if cfg!(feature = "force-system-lib-on-osx") && target.contains("apple") { 27 | return println!("cargo:rustc-flags=-l curl"); 28 | } 29 | 30 | // If the static-curl feature is disabled, probe for a system-wide libcurl. 31 | if !cfg!(feature = "static-curl") { 32 | // OSX ships libcurl by default, so we just use that version 33 | // so long as it has the right features enabled. 34 | if target.contains("apple") && (!cfg!(feature = "http2") || curl_config_reports_http2()) { 35 | return println!("cargo:rustc-flags=-l curl"); 36 | } 37 | 38 | // Next, fall back and try to use pkg-config if its available. 39 | if windows { 40 | if try_vcpkg() { 41 | return; 42 | } 43 | } else if try_pkg_config() { 44 | return; 45 | } 46 | } 47 | 48 | if !Path::new("curl/.git").exists() { 49 | let _ = Command::new("git") 50 | .args(&["submodule", "update", "--init", "curl"]) 51 | .status(); 52 | } 53 | 54 | if target.contains("apple") { 55 | // On (older) OSX we need to link against the clang runtime, 56 | // which is hidden in some non-default path. 57 | // 58 | // More details at https://github.com/alexcrichton/curl-rust/issues/279. 59 | if let Some(path) = macos_link_search_path() { 60 | println!("cargo:rustc-link-lib=clang_rt.osx"); 61 | println!("cargo:rustc-link-search={}", path); 62 | } 63 | } 64 | 65 | let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 66 | let include = dst.join("include"); 67 | let build = dst.join("build"); 68 | println!("cargo:root={}", dst.display()); 69 | println!("cargo:include={}", include.display()); 70 | println!("cargo:static=1"); 71 | println!("cargo:rustc-cfg=libcurl_vendored"); 72 | fs::create_dir_all(include.join("curl")).unwrap(); 73 | 74 | for header in [ 75 | "curl.h", 76 | "curlver.h", 77 | "easy.h", 78 | "options.h", 79 | "header.h", 80 | "mprintf.h", 81 | "multi.h", 82 | "stdcheaders.h", 83 | "system.h", 84 | "urlapi.h", 85 | "typecheck-gcc.h", 86 | "websockets.h", 87 | ] 88 | .iter() 89 | { 90 | fs::copy( 91 | format!("curl/include/curl/{}", header), 92 | include.join("curl").join(header), 93 | ) 94 | .unwrap(); 95 | } 96 | 97 | let pkgconfig = dst.join("lib/pkgconfig"); 98 | fs::create_dir_all(&pkgconfig).unwrap(); 99 | let contents = fs::read_to_string("curl/libcurl.pc.in").unwrap(); 100 | fs::write( 101 | pkgconfig.join("libcurl.pc"), 102 | contents 103 | .replace("@prefix@", dst.to_str().unwrap()) 104 | .replace("@exec_prefix@", "") 105 | .replace("@libdir@", dst.join("lib").to_str().unwrap()) 106 | .replace("@includedir@", include.to_str().unwrap()) 107 | .replace("@CPPFLAG_CURL_STATICLIB@", "-DCURL_STATICLIB") 108 | .replace("@LIBCURL_LIBS@", "") 109 | .replace("@SUPPORT_FEATURES@", "") 110 | .replace("@SUPPORT_PROTOCOLS@", "") 111 | .replace("@CURLVERSION@", "8.12.1"), 112 | ) 113 | .unwrap(); 114 | 115 | let mut cfg = cc::Build::new(); 116 | cfg.out_dir(&build) 117 | .include("curl/lib") 118 | .include("curl/include") 119 | .define("BUILDING_LIBCURL", None) 120 | .define("CURL_DISABLE_DICT", None) 121 | .define("CURL_DISABLE_GOPHER", None) 122 | .define("CURL_DISABLE_IMAP", None) 123 | .define("CURL_DISABLE_LDAP", None) 124 | .define("CURL_DISABLE_LDAPS", None) 125 | .define("CURL_DISABLE_POP3", None) 126 | .define("CURL_DISABLE_RTSP", None) 127 | .define("CURL_DISABLE_SMB", None) 128 | .define("CURL_DISABLE_SMTP", None) 129 | .define("CURL_DISABLE_TELNET", None) 130 | .define("CURL_DISABLE_TFTP", None) 131 | .define("CURL_STATICLIB", None) 132 | .define("ENABLE_IPV6", None) 133 | .define("HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID", None) 134 | .define("HAVE_ASSERT_H", None) 135 | .define("CURL_OS", "\"unknown\"") // TODO 136 | .define("HAVE_ZLIB_H", None) 137 | .define("HAVE_LONGLONG", None) 138 | .define("HAVE_LIBZ", None) 139 | .define("HAVE_BOOL_T", None) 140 | .define("HAVE_STDBOOL_H", None) 141 | .file("curl/lib/asyn-thread.c") 142 | .file("curl/lib/altsvc.c") 143 | .file("curl/lib/base64.c") 144 | .file("curl/lib/bufq.c") 145 | .file("curl/lib/bufref.c") 146 | .file("curl/lib/cfilters.c") 147 | .file("curl/lib/cf-h1-proxy.c") 148 | .file("curl/lib/cf-haproxy.c") 149 | .file("curl/lib/cf-https-connect.c") 150 | .file("curl/lib/cf-socket.c") 151 | .file("curl/lib/conncache.c") 152 | .file("curl/lib/connect.c") 153 | .file("curl/lib/content_encoding.c") 154 | .file("curl/lib/cookie.c") 155 | .file("curl/lib/curl_addrinfo.c") 156 | .file("curl/lib/curl_get_line.c") 157 | .file("curl/lib/curl_memrchr.c") 158 | .file("curl/lib/curl_range.c") 159 | .file("curl/lib/curl_sha512_256.c") 160 | .file("curl/lib/curl_threads.c") 161 | .file("curl/lib/curl_trc.c") 162 | .file("curl/lib/cw-out.c") 163 | .file("curl/lib/doh.c") 164 | .file("curl/lib/dynbuf.c") 165 | .file("curl/lib/dynhds.c") 166 | .file("curl/lib/easy.c") 167 | .file("curl/lib/escape.c") 168 | .file("curl/lib/file.c") 169 | .file("curl/lib/fileinfo.c") 170 | .file("curl/lib/fopen.c") 171 | .file("curl/lib/formdata.c") 172 | .file("curl/lib/getenv.c") 173 | .file("curl/lib/getinfo.c") 174 | .file("curl/lib/hash.c") 175 | .file("curl/lib/headers.c") 176 | .file("curl/lib/hmac.c") 177 | .file("curl/lib/hostasyn.c") 178 | .file("curl/lib/hostip.c") 179 | .file("curl/lib/hostip6.c") 180 | .file("curl/lib/hsts.c") 181 | .file("curl/lib/http.c") 182 | .file("curl/lib/http1.c") 183 | .file("curl/lib/http_aws_sigv4.c") 184 | .file("curl/lib/http_chunks.c") 185 | .file("curl/lib/http_digest.c") 186 | .file("curl/lib/http_proxy.c") 187 | .file("curl/lib/idn.c") 188 | .file("curl/lib/if2ip.c") 189 | .file("curl/lib/inet_ntop.c") 190 | .file("curl/lib/inet_pton.c") 191 | .file("curl/lib/llist.c") 192 | .file("curl/lib/md5.c") 193 | .file("curl/lib/mime.c") 194 | .file("curl/lib/macos.c") 195 | .file("curl/lib/mprintf.c") 196 | .file("curl/lib/mqtt.c") 197 | .file("curl/lib/multi.c") 198 | .file("curl/lib/netrc.c") 199 | .file("curl/lib/nonblock.c") 200 | .file("curl/lib/noproxy.c") 201 | .file("curl/lib/parsedate.c") 202 | .file("curl/lib/progress.c") 203 | .file("curl/lib/rand.c") 204 | .file("curl/lib/rename.c") 205 | .file("curl/lib/request.c") 206 | .file("curl/lib/select.c") 207 | .file("curl/lib/sendf.c") 208 | .file("curl/lib/setopt.c") 209 | .file("curl/lib/sha256.c") 210 | .file("curl/lib/share.c") 211 | .file("curl/lib/slist.c") 212 | .file("curl/lib/socks.c") 213 | .file("curl/lib/socketpair.c") 214 | .file("curl/lib/speedcheck.c") 215 | .file("curl/lib/splay.c") 216 | .file("curl/lib/strcase.c") 217 | .file("curl/lib/strdup.c") 218 | .file("curl/lib/strerror.c") 219 | .file("curl/lib/strparse.c") 220 | .file("curl/lib/strtok.c") 221 | .file("curl/lib/strtoofft.c") 222 | .file("curl/lib/timeval.c") 223 | .file("curl/lib/transfer.c") 224 | .file("curl/lib/url.c") 225 | .file("curl/lib/urlapi.c") 226 | .file("curl/lib/version.c") 227 | .file("curl/lib/vauth/digest.c") 228 | .file("curl/lib/vauth/vauth.c") 229 | .file("curl/lib/vquic/curl_msh3.c") 230 | .file("curl/lib/vquic/curl_ngtcp2.c") 231 | .file("curl/lib/vquic/curl_osslq.c") 232 | .file("curl/lib/vquic/curl_quiche.c") 233 | .file("curl/lib/vquic/vquic.c") 234 | .file("curl/lib/vquic/vquic-tls.c") 235 | .file("curl/lib/vtls/hostcheck.c") 236 | .file("curl/lib/vtls/keylog.c") 237 | .file("curl/lib/vtls/vtls.c") 238 | .file("curl/lib/vtls/vtls_scache.c") 239 | .file("curl/lib/warnless.c") 240 | .file("curl/lib/timediff.c") 241 | .file("curl/lib/ws.c") 242 | .define("HAVE_GETADDRINFO", None) 243 | .define("HAVE_GETPEERNAME", None) 244 | .define("HAVE_GETSOCKNAME", None) 245 | .warnings(false); 246 | 247 | if cfg!(feature = "ntlm") { 248 | cfg.file("curl/lib/curl_des.c") 249 | .file("curl/lib/curl_endian.c") 250 | .file("curl/lib/curl_gethostname.c") 251 | .file("curl/lib/curl_ntlm_core.c") 252 | .file("curl/lib/http_ntlm.c") 253 | .file("curl/lib/md4.c") 254 | .file("curl/lib/vauth/ntlm.c") 255 | .file("curl/lib/vauth/ntlm_sspi.c"); 256 | } else { 257 | cfg.define("CURL_DISABLE_NTLM", None); 258 | } 259 | 260 | if cfg!(feature = "protocol-ftp") { 261 | cfg.file("curl/lib/curl_fnmatch.c") 262 | .file("curl/lib/ftp.c") 263 | .file("curl/lib/ftplistparser.c") 264 | .file("curl/lib/pingpong.c"); 265 | } else { 266 | cfg.define("CURL_DISABLE_FTP", None); 267 | } 268 | 269 | if cfg!(feature = "http2") { 270 | cfg.define("USE_NGHTTP2", None) 271 | .define("NGHTTP2_STATICLIB", None) 272 | .file("curl/lib/cf-h2-proxy.c") 273 | .file("curl/lib/http2.c"); 274 | 275 | println!("cargo:rustc-cfg=link_libnghttp2"); 276 | if let Some(path) = env::var_os("DEP_NGHTTP2_ROOT") { 277 | let path = PathBuf::from(path); 278 | cfg.include(path.join("include")); 279 | } 280 | } 281 | 282 | println!("cargo:rustc-cfg=link_libz"); 283 | if let Some(path) = env::var_os("DEP_Z_INCLUDE") { 284 | cfg.include(path); 285 | } 286 | 287 | if cfg!(feature = "spnego") { 288 | cfg.define("USE_SPNEGO", None) 289 | .file("curl/lib/http_negotiate.c") 290 | .file("curl/lib/vauth/vauth.c"); 291 | } 292 | 293 | // Configure TLS backend. Since Cargo does not support mutually exclusive 294 | // features, make sure we only compile one vtls. 295 | if cfg!(feature = "rustls") { 296 | cfg.define("USE_RUSTLS", None) 297 | .file("curl/lib/vtls/cipher_suite.c") 298 | .file("curl/lib/vtls/rustls.c") 299 | .include(env::var_os("DEP_RUSTLS_FFI_INCLUDE").unwrap()); 300 | } else if cfg!(feature = "windows-static-ssl") { 301 | if windows { 302 | cfg.define("USE_OPENSSL", None) 303 | .file("curl/lib/vtls/openssl.c"); 304 | // We need both openssl and zlib 305 | // Those can be installed with 306 | // ```shell 307 | // git clone https://github.com/microsoft/vcpkg 308 | // cd vcpkg 309 | // ./bootstrap-vcpkg.bat -disableMetrics 310 | // ./vcpkg.exe integrate install 311 | // ./vcpkg.exe install openssl:x64-windows-static-md 312 | // ``` 313 | #[cfg(target_env = "msvc")] 314 | vcpkg::Config::new().find_package("openssl").ok(); 315 | #[cfg(target_env = "msvc")] 316 | vcpkg::Config::new().find_package("zlib").ok(); 317 | } else { 318 | panic!("Not available on non windows platform") 319 | } 320 | } else if cfg!(feature = "ssl") { 321 | if windows { 322 | // For windows, spnego feature is auto on in case ssl feature is on. 323 | // Please see definition of USE_SPNEGO in curl_setup.h for more info. 324 | cfg.define("USE_WINDOWS_SSPI", None) 325 | .define("USE_SCHANNEL", None) 326 | .file("curl/lib/http_negotiate.c") 327 | .file("curl/lib/curl_sspi.c") 328 | .file("curl/lib/socks_sspi.c") 329 | .file("curl/lib/vauth/spnego_sspi.c") 330 | .file("curl/lib/vauth/vauth.c") 331 | .file("curl/lib/vtls/schannel.c") 332 | .file("curl/lib/vtls/schannel_verify.c") 333 | .file("curl/lib/vtls/x509asn1.c"); 334 | } else if target.contains("-apple-") { 335 | cfg.define("USE_SECTRANSP", None) 336 | .file("curl/lib/vtls/cipher_suite.c") 337 | .file("curl/lib/vtls/sectransp.c") 338 | .file("curl/lib/vtls/x509asn1.c"); 339 | if xcode_major_version().map_or(true, |v| v >= 9) { 340 | // On earlier Xcode versions (<9), defining HAVE_BUILTIN_AVAILABLE 341 | // would cause __bultin_available() to fail to compile due to 342 | // unrecognized platform names, so we try to check for Xcode 343 | // version first (if unknown, assume it's recent, as in >= 9). 344 | cfg.define("HAVE_BUILTIN_AVAILABLE", "1"); 345 | } 346 | } else { 347 | cfg.define("USE_OPENSSL", None) 348 | .file("curl/lib/vtls/openssl.c"); 349 | 350 | println!("cargo:rustc-cfg=link_openssl"); 351 | if let Some(path) = env::var_os("DEP_OPENSSL_INCLUDE") { 352 | cfg.include(path); 353 | } 354 | } 355 | } 356 | 357 | // Configure platform-specific details. 358 | if windows { 359 | cfg.define("WIN32", None) 360 | .define("USE_THREADS_WIN32", None) 361 | .define("HAVE_IOCTLSOCKET_FIONBIO", None) 362 | .define("USE_WINSOCK", None) 363 | .file("curl/lib/bufref.c") 364 | .file("curl/lib/system_win32.c") 365 | .file("curl/lib/version_win32.c") 366 | .file("curl/lib/vauth/digest_sspi.c") 367 | .file("curl/lib/curl_multibyte.c"); 368 | 369 | if cfg!(feature = "spnego") { 370 | cfg.file("curl/lib/vauth/spnego_sspi.c"); 371 | } 372 | } else { 373 | cfg.define("RECV_TYPE_ARG1", "int") 374 | .define("HAVE_PTHREAD_H", None) 375 | .define("HAVE_ARPA_INET_H", None) 376 | .define("HAVE_ERRNO_H", None) 377 | .define("HAVE_FCNTL_H", None) 378 | .define("HAVE_NETDB_H", None) 379 | .define("HAVE_NETINET_IN_H", None) 380 | .define("HAVE_NETINET_TCP_H", None) 381 | .define("HAVE_POLL_H", None) 382 | .define("HAVE_FCNTL_O_NONBLOCK", None) 383 | .define("HAVE_SYS_SELECT_H", None) 384 | .define("HAVE_SYS_STAT_H", None) 385 | .define("HAVE_SYS_TIME_H", None) 386 | .define("HAVE_UNISTD_H", None) 387 | .define("HAVE_RECV", None) 388 | .define("HAVE_SELECT", None) 389 | .define("HAVE_SEND", None) 390 | .define("HAVE_SOCKET", None) 391 | .define("HAVE_STERRROR_R", None) 392 | .define("HAVE_SOCKETPAIR", None) 393 | .define("HAVE_STRUCT_TIMEVAL", None) 394 | .define("HAVE_SYS_UN_H", None) 395 | .define("USE_THREADS_POSIX", None) 396 | .define("USE_UNIX_SOCKETS", None) 397 | .define("RECV_TYPE_ARG2", "void*") 398 | .define("RECV_TYPE_ARG3", "size_t") 399 | .define("RECV_TYPE_ARG4", "int") 400 | .define("RECV_TYPE_RETV", "ssize_t") 401 | .define("SEND_QUAL_ARG2", "const") 402 | .define("SEND_TYPE_ARG1", "int") 403 | .define("SEND_TYPE_ARG2", "void*") 404 | .define("SEND_TYPE_ARG3", "size_t") 405 | .define("SEND_TYPE_ARG4", "int") 406 | .define("SEND_TYPE_RETV", "ssize_t") 407 | .define("SIZEOF_CURL_OFF_T", "8") 408 | .define("SIZEOF_INT", "4") 409 | .define("SIZEOF_SHORT", "2"); 410 | 411 | if target.contains("-apple-") { 412 | cfg.define("__APPLE__", None) 413 | .define("HAVE_MACH_ABSOLUTE_TIME", None); 414 | } else { 415 | cfg.define("HAVE_CLOCK_GETTIME_MONOTONIC", None) 416 | .define("HAVE_GETTIMEOFDAY", None) 417 | // poll() on various versions of macOS are janky, so only use it 418 | // on non-macOS unix-likes. This matches the official default 419 | // build configuration as well. 420 | .define("HAVE_POLL_FINE", None); 421 | } 422 | 423 | if cfg!(feature = "spnego") { 424 | cfg.define("HAVE_GSSAPI", None) 425 | .file("curl/lib/curl_gssapi.c") 426 | .file("curl/lib/socks_gssapi.c") 427 | .file("curl/lib/vauth/spnego_gssapi.c"); 428 | if let Some(path) = env::var_os("GSSAPI_ROOT") { 429 | let path = PathBuf::from(path); 430 | cfg.include(path.join("include")); 431 | } 432 | 433 | // Link against the MIT gssapi library. It might be desirable to add support for 434 | // choosing between MIT and Heimdal libraries in the future. 435 | println!("cargo:rustc-link-lib=gssapi_krb5"); 436 | } 437 | 438 | let width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH") 439 | .unwrap() 440 | .parse::() 441 | .unwrap(); 442 | cfg.define("SIZEOF_SSIZE_T", Some(&(width / 8).to_string()[..])); 443 | cfg.define("SIZEOF_SIZE_T", Some(&(width / 8).to_string()[..])); 444 | cfg.define("SIZEOF_LONG", Some(&(width / 8).to_string()[..])); 445 | 446 | cfg.flag("-fvisibility=hidden"); 447 | } 448 | 449 | cfg.compile("curl"); 450 | 451 | if windows { 452 | println!("cargo:rustc-link-lib=ws2_32"); 453 | println!("cargo:rustc-link-lib=crypt32"); 454 | } 455 | 456 | // Illumos/Solaris requires explicit linking with libnsl 457 | if target.contains("solaris") { 458 | println!("cargo:rustc-link-lib=nsl"); 459 | } 460 | 461 | if target.contains("-apple-") { 462 | println!("cargo:rustc-link-lib=framework=Security"); 463 | println!("cargo:rustc-link-lib=framework=CoreFoundation"); 464 | println!("cargo:rustc-link-lib=framework=CoreServices"); 465 | println!("cargo:rustc-link-lib=framework=SystemConfiguration"); 466 | } 467 | } 468 | 469 | #[cfg(not(target_env = "msvc"))] 470 | fn try_vcpkg() -> bool { 471 | false 472 | } 473 | 474 | #[cfg(target_env = "msvc")] 475 | fn try_vcpkg() -> bool { 476 | // the import library for the dll is called libcurl_imp 477 | let mut successful_probe_details = match vcpkg::Config::new() 478 | .lib_names("libcurl_imp", "libcurl") 479 | .emit_includes(true) 480 | .probe("curl") 481 | { 482 | Ok(details) => Some(details), 483 | Err(e) => { 484 | println!("first run of vcpkg did not find libcurl: {}", e); 485 | None 486 | } 487 | }; 488 | 489 | if successful_probe_details.is_none() { 490 | match vcpkg::Config::new() 491 | .lib_name("libcurl") 492 | .emit_includes(true) 493 | .probe("curl") 494 | { 495 | Ok(details) => successful_probe_details = Some(details), 496 | Err(e) => println!("second run of vcpkg did not find libcurl: {}", e), 497 | } 498 | } 499 | 500 | if successful_probe_details.is_some() { 501 | // Found libcurl which depends on openssl, libssh2 and zlib 502 | // in the a default vcpkg installation. Probe for them 503 | // but do not fail if they are not present as we may be working 504 | // with a customized vcpkg installation. 505 | vcpkg::Config::new() 506 | .lib_name("libeay32") 507 | .lib_name("ssleay32") 508 | .probe("openssl") 509 | .ok(); 510 | 511 | vcpkg::probe_package("libssh2").ok(); 512 | 513 | vcpkg::Config::new() 514 | .lib_names("zlib", "zlib1") 515 | .probe("zlib") 516 | .ok(); 517 | 518 | println!("cargo:rustc-link-lib=crypt32"); 519 | println!("cargo:rustc-link-lib=gdi32"); 520 | println!("cargo:rustc-link-lib=user32"); 521 | println!("cargo:rustc-link-lib=wldap32"); 522 | return true; 523 | } 524 | false 525 | } 526 | 527 | fn try_pkg_config() -> bool { 528 | let mut cfg = pkg_config::Config::new(); 529 | cfg.cargo_metadata(false); 530 | let lib = match cfg.probe("libcurl") { 531 | Ok(lib) => lib, 532 | Err(e) => { 533 | println!( 534 | "Couldn't find libcurl from pkgconfig ({:?}), \ 535 | compiling it from source...", 536 | e 537 | ); 538 | return false; 539 | } 540 | }; 541 | 542 | // Not all system builds of libcurl have http2 features enabled, so if we've 543 | // got a http2-requested build then we may fall back to a build from source. 544 | if cfg!(feature = "http2") && !curl_config_reports_http2() { 545 | return false; 546 | } 547 | 548 | // Re-find the library to print cargo's metadata, then print some extra 549 | // metadata as well. 550 | cfg.cargo_metadata(true).probe("libcurl").unwrap(); 551 | for path in lib.include_paths.iter() { 552 | println!("cargo:include={}", path.display()); 553 | } 554 | true 555 | } 556 | 557 | fn xcode_major_version() -> Option { 558 | let status = Command::new("xcode-select").arg("-p").status().ok()?; 559 | if status.success() { 560 | let output = Command::new("xcodebuild").arg("-version").output().ok()?; 561 | if output.status.success() { 562 | let stdout = String::from_utf8_lossy(&output.stdout); 563 | println!("xcode version: {}", stdout); 564 | let mut words = stdout.split_whitespace(); 565 | if words.next()? == "Xcode" { 566 | let version = words.next()?; 567 | return version[..version.find('.')?].parse().ok(); 568 | } 569 | } 570 | } 571 | println!("unable to determine Xcode version, assuming >= 9"); 572 | None 573 | } 574 | 575 | fn curl_config_reports_http2() -> bool { 576 | let output = Command::new("curl-config").arg("--features").output(); 577 | let output = match output { 578 | Ok(out) => out, 579 | Err(e) => { 580 | println!("failed to run curl-config ({}), building from source", e); 581 | return false; 582 | } 583 | }; 584 | if !output.status.success() { 585 | println!("curl-config failed: {}", output.status); 586 | return false; 587 | } 588 | let stdout = String::from_utf8_lossy(&output.stdout); 589 | if !stdout.contains("HTTP2") { 590 | println!( 591 | "failed to find http-2 feature enabled in pkg-config-found \ 592 | libcurl, building from source" 593 | ); 594 | return false; 595 | } 596 | 597 | true 598 | } 599 | 600 | fn macos_link_search_path() -> Option { 601 | let output = cc::Build::new() 602 | .get_compiler() 603 | .to_command() 604 | .arg("--print-search-dirs") 605 | .output() 606 | .ok()?; 607 | if !output.status.success() { 608 | println!( 609 | "failed to run 'clang --print-search-dirs', continuing without a link search path" 610 | ); 611 | return None; 612 | } 613 | 614 | let stdout = String::from_utf8_lossy(&output.stdout); 615 | for line in stdout.lines() { 616 | if line.contains("libraries: =") { 617 | let path = line.split('=').nth(1)?; 618 | if !path.is_empty() { 619 | return Some(format!("{}/lib/darwin", path)); 620 | } 621 | } 622 | } 623 | 624 | println!("failed to determine link search path, continuing without it"); 625 | None 626 | } 627 | -------------------------------------------------------------------------------- /examples/aws_sigv4.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use curl::easy::Easy; 4 | 5 | fn main() -> Result<()> { 6 | let mut handle = Easy::new(); 7 | handle.verbose(true)?; 8 | handle.url("https://ec2.us-east-1.amazonaws.com/?Action=DescribeRegions&Version=2013-10-15")?; 9 | handle.aws_sigv4("aws:amz")?; 10 | handle.username("myAccessKeyId")?; 11 | handle.password("mySecretAccessKey")?; 12 | handle.perform()?; 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /examples/doh.rs: -------------------------------------------------------------------------------- 1 | use curl::easy::Easy; 2 | use std::io::{stdout, Write}; 3 | 4 | fn main() -> Result<(), curl::Error> { 5 | let mut curl = Easy::new(); 6 | 7 | curl.url("https://example.com")?; 8 | curl.doh_url(Some("https://cloudflare-dns.com/dns-query"))?; 9 | curl.write_function(|data| { 10 | stdout().write_all(data).unwrap(); 11 | Ok(data.len()) 12 | })?; 13 | 14 | curl.perform()?; 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /examples/https.rs: -------------------------------------------------------------------------------- 1 | //! Simple HTTPS GET 2 | //! 3 | //! This example is a Rust adaptation of the [C example of the same 4 | //! name](https://curl.se/libcurl/c/https.html). 5 | 6 | extern crate curl; 7 | 8 | use curl::easy::Easy; 9 | use std::io::{stdout, Write}; 10 | 11 | fn main() -> Result<(), curl::Error> { 12 | let mut curl = Easy::new(); 13 | 14 | curl.url("https://example.com/")?; 15 | curl.write_function(|data| { 16 | stdout().write_all(data).unwrap(); 17 | Ok(data.len()) 18 | })?; 19 | 20 | curl.perform()?; 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /examples/multi-dl.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::Duration; 3 | 4 | use anyhow::Result; 5 | 6 | use curl::easy::{Easy2, Handler, WriteError}; 7 | use curl::multi::{Easy2Handle, Multi}; 8 | 9 | const URLS: &[&str] = &[ 10 | "https://www.microsoft.com", 11 | "https://www.google.com", 12 | "https://www.amazon.com", 13 | "https://www.apple.com", 14 | ]; 15 | 16 | struct Collector(Vec); 17 | impl Handler for Collector { 18 | fn write(&mut self, data: &[u8]) -> Result { 19 | self.0.extend_from_slice(data); 20 | Ok(data.len()) 21 | } 22 | } 23 | 24 | fn download(multi: &mut Multi, token: usize, url: &str) -> Result> { 25 | let version = curl::Version::get(); 26 | let mut request = Easy2::new(Collector(Vec::new())); 27 | request.url(&url)?; 28 | request.useragent(&format!("curl/{}", version.version()))?; 29 | 30 | let mut handle = multi.add2(request)?; 31 | handle.set_token(token)?; 32 | Ok(handle) 33 | } 34 | 35 | fn main() -> Result<()> { 36 | let mut multi = Multi::new(); 37 | let mut handles = URLS 38 | .iter() 39 | .enumerate() 40 | .map(|(token, url)| Ok((token, download(&mut multi, token, url)?))) 41 | .collect::>>()?; 42 | 43 | let mut still_alive = true; 44 | while still_alive { 45 | // We still need to process the last messages when 46 | // `Multi::perform` returns "0". 47 | if multi.perform()? == 0 { 48 | still_alive = false; 49 | } 50 | 51 | multi.messages(|message| { 52 | let token = message.token().expect("failed to get the token"); 53 | let handle = handles 54 | .get_mut(&token) 55 | .expect("the download value should exist in the HashMap"); 56 | 57 | match message 58 | .result_for2(&handle) 59 | .expect("token mismatch with the `EasyHandle`") 60 | { 61 | Ok(()) => { 62 | let http_status = handle 63 | .response_code() 64 | .expect("HTTP request finished without status code"); 65 | 66 | println!( 67 | "R: Transfer succeeded (Status: {}) {} (Download length: {})", 68 | http_status, 69 | URLS[token], 70 | handle.get_ref().0.len() 71 | ); 72 | } 73 | Err(error) => { 74 | println!("E: {} - <{}>", error, URLS[token]); 75 | } 76 | } 77 | }); 78 | 79 | if still_alive { 80 | // The sleeping time could be reduced to allow other processing. 81 | // For instance, a thread could check a condition signalling the 82 | // thread shutdown. 83 | multi.wait(&mut [], Duration::from_secs(60))?; 84 | } 85 | } 86 | 87 | Ok(()) 88 | } 89 | -------------------------------------------------------------------------------- /examples/ssl_cert_blob.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::{stdout, Read, Write}; 4 | use std::path::Path; 5 | 6 | use anyhow::{bail, Result}; 7 | use curl::easy::Easy; 8 | 9 | fn read_file(path: impl AsRef) -> Result> { 10 | let mut f = File::open(path)?; 11 | let mut buf = Vec::new(); 12 | f.read_to_end(&mut buf)?; 13 | Ok(buf) 14 | } 15 | 16 | fn main() -> Result<()> { 17 | let argv = env::args().collect::>(); 18 | if argv.len() < 4 { 19 | bail!("usage: ssl_cert_blob URL CERT KEY CAINFO? PASSWORD?"); 20 | } 21 | let url = &argv[1]; 22 | let cert_path = &argv[2]; 23 | let key_path = &argv[3]; 24 | let cainfo = if argv.len() >= 5 { 25 | Some(&argv[4]) 26 | } else { 27 | None 28 | }; 29 | let password = if argv.len() >= 6 { 30 | Some(&argv[5]) 31 | } else { 32 | None 33 | }; 34 | 35 | let mut handle = Easy::new(); 36 | 37 | handle.url(url)?; 38 | handle.verbose(true)?; 39 | handle.write_function(|data| { 40 | stdout().write_all(data).unwrap(); 41 | Ok(data.len()) 42 | })?; 43 | 44 | let cert_blob = read_file(cert_path)?; 45 | let key_blob = read_file(key_path)?; 46 | let ca_blob = if let Some(cainfo) = cainfo { 47 | Some(read_file(cainfo)?) 48 | } else { 49 | None 50 | }; 51 | 52 | handle.ssl_cert_blob(&cert_blob)?; 53 | handle.ssl_key_blob(&key_blob)?; 54 | if let Some(password) = password { 55 | handle.key_password(password)?; 56 | } 57 | if let Some(ca_blob) = ca_blob { 58 | handle.ssl_cainfo_blob(&ca_blob)?; 59 | } 60 | 61 | handle.perform()?; 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /examples/ssl_client_auth.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::{stdout, Write}; 3 | 4 | use anyhow::{bail, Result}; 5 | use curl::easy::Easy; 6 | 7 | fn main() -> Result<()> { 8 | let argv = env::args().collect::>(); 9 | if argv.len() < 4 { 10 | bail!("usage: ssl_client_auth URL CERT KEY CAINFO? PASSWORD?"); 11 | } 12 | let url = &argv[1]; 13 | let cert_path = &argv[2]; 14 | let key_path = &argv[3]; 15 | let cainfo = if argv.len() >= 5 { 16 | Some(&argv[4]) 17 | } else { 18 | None 19 | }; 20 | let password = if argv.len() >= 6 { 21 | Some(&argv[5]) 22 | } else { 23 | None 24 | }; 25 | 26 | let mut handle = Easy::new(); 27 | 28 | handle.url(url)?; 29 | handle.verbose(true)?; 30 | handle.write_function(|data| { 31 | stdout().write_all(data).unwrap(); 32 | Ok(data.len()) 33 | })?; 34 | 35 | handle.ssl_cert(&cert_path)?; 36 | handle.ssl_key(&key_path)?; 37 | if let Some(password) = password { 38 | handle.key_password(password)?; 39 | } 40 | if let Some(cainfo) = cainfo { 41 | handle.cainfo(cainfo)?; 42 | } 43 | 44 | handle.perform()?; 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /examples/ssl_proxy.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | fn main() -> Result<()> { 4 | let mut handle = curl::easy::Easy::new(); 5 | 6 | let proxy_url = "https://fwdproxy"; 7 | let proxy_port = 8082; 8 | let cainfo = "/var/credentials/root/ca.pem"; 9 | let sslcert = "/var/credentials/user/x509.pem"; 10 | let sslkey = "/var/credentials/user/x509.pem"; 11 | 12 | handle.connect_timeout(std::time::Duration::from_secs(5))?; 13 | handle.connect_only(true)?; 14 | handle.verbose(true)?; 15 | handle.url("https://www.google.com")?; 16 | 17 | handle.proxy(proxy_url)?; 18 | handle.proxy_port(proxy_port)?; 19 | handle.proxy_cainfo(cainfo)?; 20 | handle.proxy_sslcert(sslcert)?; 21 | handle.proxy_sslkey(sslkey)?; 22 | println!("ssl proxy setup done"); 23 | 24 | handle.perform()?; 25 | println!("connected done"); 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /src/easy/form.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::fmt; 3 | use std::path::Path; 4 | use std::ptr; 5 | 6 | use crate::easy::{list, List}; 7 | use crate::FormError; 8 | 9 | /// Multipart/formdata for an HTTP POST request. 10 | /// 11 | /// This structure is built up and then passed to the `Easy::httppost` method to 12 | /// be sent off with a request. 13 | pub struct Form { 14 | head: *mut curl_sys::curl_httppost, 15 | tail: *mut curl_sys::curl_httppost, 16 | headers: Vec, 17 | buffers: Vec>, 18 | strings: Vec, 19 | } 20 | 21 | /// One part in a multipart upload, added to a `Form`. 22 | pub struct Part<'form, 'data> { 23 | form: &'form mut Form, 24 | name: &'data str, 25 | array: Vec, 26 | error: Option, 27 | } 28 | 29 | pub fn raw(form: &Form) -> *mut curl_sys::curl_httppost { 30 | form.head 31 | } 32 | 33 | impl Form { 34 | /// Creates a new blank form ready for the addition of new data. 35 | pub fn new() -> Form { 36 | Form { 37 | head: ptr::null_mut(), 38 | tail: ptr::null_mut(), 39 | headers: Vec::new(), 40 | buffers: Vec::new(), 41 | strings: Vec::new(), 42 | } 43 | } 44 | 45 | /// Prepares adding a new part to this `Form` 46 | /// 47 | /// Note that the part is not actually added to the form until the `add` 48 | /// method is called on `Part`, which may or may not fail. 49 | pub fn part<'a, 'data>(&'a mut self, name: &'data str) -> Part<'a, 'data> { 50 | Part { 51 | error: None, 52 | form: self, 53 | name, 54 | array: vec![curl_sys::curl_forms { 55 | option: curl_sys::CURLFORM_END, 56 | value: ptr::null_mut(), 57 | }], 58 | } 59 | } 60 | } 61 | 62 | impl fmt::Debug for Form { 63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 64 | // TODO: fill this out more 65 | f.debug_struct("Form").field("fields", &"...").finish() 66 | } 67 | } 68 | 69 | impl Drop for Form { 70 | fn drop(&mut self) { 71 | unsafe { 72 | curl_sys::curl_formfree(self.head); 73 | } 74 | } 75 | } 76 | 77 | impl<'form, 'data> Part<'form, 'data> { 78 | /// A pointer to the contents of this part, the actual data to send away. 79 | pub fn contents(&mut self, contents: &'data [u8]) -> &mut Self { 80 | let pos = self.array.len() - 1; 81 | 82 | // curl has an oddity where if the length if 0 it will call strlen 83 | // on the value. This means that if someone wants to add empty form 84 | // contents we need to make sure the buffer contains a null byte. 85 | let ptr = if contents.is_empty() { 86 | b"\x00" 87 | } else { 88 | contents 89 | } 90 | .as_ptr(); 91 | 92 | self.array.insert( 93 | pos, 94 | curl_sys::curl_forms { 95 | option: curl_sys::CURLFORM_COPYCONTENTS, 96 | value: ptr as *mut _, 97 | }, 98 | ); 99 | self.array.insert( 100 | pos + 1, 101 | curl_sys::curl_forms { 102 | option: curl_sys::CURLFORM_CONTENTSLENGTH, 103 | value: contents.len() as *mut _, 104 | }, 105 | ); 106 | self 107 | } 108 | 109 | /// Causes this file to be read and its contents used as data in this part 110 | /// 111 | /// This part does not automatically become a file upload part simply 112 | /// because its data was read from a file. 113 | /// 114 | /// # Errors 115 | /// 116 | /// If the filename has any internal nul bytes or if on Windows it does not 117 | /// contain a unicode filename then the `add` function will eventually 118 | /// return an error. 119 | pub fn file_content

(&mut self, file: P) -> &mut Self 120 | where 121 | P: AsRef, 122 | { 123 | self._file_content(file.as_ref()) 124 | } 125 | 126 | fn _file_content(&mut self, file: &Path) -> &mut Self { 127 | if let Some(bytes) = self.path2cstr(file) { 128 | let pos = self.array.len() - 1; 129 | self.array.insert( 130 | pos, 131 | curl_sys::curl_forms { 132 | option: curl_sys::CURLFORM_FILECONTENT, 133 | value: bytes.as_ptr() as *mut _, 134 | }, 135 | ); 136 | self.form.strings.push(bytes); 137 | } 138 | self 139 | } 140 | 141 | /// Makes this part a file upload part of the given file. 142 | /// 143 | /// Sets the filename field to the basename of the provided file name, and 144 | /// it reads the contents of the file and passes them as data and sets the 145 | /// content type if the given file matches one of the internally known file 146 | /// extensions. 147 | /// 148 | /// The given upload file must exist entirely on the filesystem before the 149 | /// upload is started because libcurl needs to read the size of it 150 | /// beforehand. 151 | /// 152 | /// Multiple files can be uploaded by calling this method multiple times and 153 | /// content types can also be configured for each file (by calling that 154 | /// next). 155 | /// 156 | /// # Errors 157 | /// 158 | /// If the filename has any internal nul bytes or if on Windows it does not 159 | /// contain a unicode filename then this function will cause `add` to return 160 | /// an error when called. 161 | pub fn file(&mut self, file: &'data P) -> &mut Self 162 | where 163 | P: AsRef, 164 | { 165 | self._file(file.as_ref()) 166 | } 167 | 168 | fn _file(&mut self, file: &'data Path) -> &mut Self { 169 | if let Some(bytes) = self.path2cstr(file) { 170 | let pos = self.array.len() - 1; 171 | self.array.insert( 172 | pos, 173 | curl_sys::curl_forms { 174 | option: curl_sys::CURLFORM_FILE, 175 | value: bytes.as_ptr() as *mut _, 176 | }, 177 | ); 178 | self.form.strings.push(bytes); 179 | } 180 | self 181 | } 182 | 183 | /// Used in combination with `Part::file`, provides the content-type for 184 | /// this part, possibly instead of choosing an internal one. 185 | /// 186 | /// # Panics 187 | /// 188 | /// This function will panic if `content_type` contains an internal nul 189 | /// byte. 190 | pub fn content_type(&mut self, content_type: &'data str) -> &mut Self { 191 | if let Some(bytes) = self.bytes2cstr(content_type.as_bytes()) { 192 | let pos = self.array.len() - 1; 193 | self.array.insert( 194 | pos, 195 | curl_sys::curl_forms { 196 | option: curl_sys::CURLFORM_CONTENTTYPE, 197 | value: bytes.as_ptr() as *mut _, 198 | }, 199 | ); 200 | self.form.strings.push(bytes); 201 | } 202 | self 203 | } 204 | 205 | /// Used in combination with `Part::file`, provides the filename for 206 | /// this part instead of the actual one. 207 | /// 208 | /// # Errors 209 | /// 210 | /// If `name` contains an internal nul byte, or if on Windows the path is 211 | /// not valid unicode then this function will return an error when `add` is 212 | /// called. 213 | pub fn filename(&mut self, name: &'data P) -> &mut Self 214 | where 215 | P: AsRef, 216 | { 217 | self._filename(name.as_ref()) 218 | } 219 | 220 | fn _filename(&mut self, name: &'data Path) -> &mut Self { 221 | if let Some(bytes) = self.path2cstr(name) { 222 | let pos = self.array.len() - 1; 223 | self.array.insert( 224 | pos, 225 | curl_sys::curl_forms { 226 | option: curl_sys::CURLFORM_FILENAME, 227 | value: bytes.as_ptr() as *mut _, 228 | }, 229 | ); 230 | self.form.strings.push(bytes); 231 | } 232 | self 233 | } 234 | 235 | /// This is used to provide a custom file upload part without using the 236 | /// `file` method above. 237 | /// 238 | /// The first parameter is for the filename field and the second is the 239 | /// in-memory contents. 240 | /// 241 | /// # Errors 242 | /// 243 | /// If `name` contains an internal nul byte, or if on Windows the path is 244 | /// not valid unicode then this function will return an error when `add` is 245 | /// called. 246 | pub fn buffer(&mut self, name: &'data P, data: Vec) -> &mut Self 247 | where 248 | P: AsRef, 249 | { 250 | self._buffer(name.as_ref(), data) 251 | } 252 | 253 | fn _buffer(&mut self, name: &'data Path, mut data: Vec) -> &mut Self { 254 | if let Some(bytes) = self.path2cstr(name) { 255 | // If `CURLFORM_BUFFERLENGTH` is set to `0`, libcurl will instead do a strlen() on the 256 | // contents to figure out the size so we need to make sure the buffer is actually 257 | // zero terminated. 258 | let length = data.len(); 259 | if length == 0 { 260 | data.push(0); 261 | } 262 | 263 | let pos = self.array.len() - 1; 264 | self.array.insert( 265 | pos, 266 | curl_sys::curl_forms { 267 | option: curl_sys::CURLFORM_BUFFER, 268 | value: bytes.as_ptr() as *mut _, 269 | }, 270 | ); 271 | self.form.strings.push(bytes); 272 | self.array.insert( 273 | pos + 1, 274 | curl_sys::curl_forms { 275 | option: curl_sys::CURLFORM_BUFFERPTR, 276 | value: data.as_ptr() as *mut _, 277 | }, 278 | ); 279 | self.array.insert( 280 | pos + 2, 281 | curl_sys::curl_forms { 282 | option: curl_sys::CURLFORM_BUFFERLENGTH, 283 | value: length as *mut _, 284 | }, 285 | ); 286 | self.form.buffers.push(data); 287 | } 288 | self 289 | } 290 | 291 | /// Specifies extra headers for the form POST section. 292 | /// 293 | /// Appends the list of headers to those libcurl automatically generates. 294 | pub fn content_header(&mut self, headers: List) -> &mut Self { 295 | let pos = self.array.len() - 1; 296 | self.array.insert( 297 | pos, 298 | curl_sys::curl_forms { 299 | option: curl_sys::CURLFORM_CONTENTHEADER, 300 | value: list::raw(&headers) as *mut _, 301 | }, 302 | ); 303 | self.form.headers.push(headers); 304 | self 305 | } 306 | 307 | /// Attempts to add this part to the `Form` that it was created from. 308 | /// 309 | /// If any error happens while adding, that error is returned, otherwise 310 | /// `Ok(())` is returned. 311 | pub fn add(&mut self) -> Result<(), FormError> { 312 | if let Some(err) = self.error.clone() { 313 | return Err(err); 314 | } 315 | let rc = unsafe { 316 | curl_sys::curl_formadd( 317 | &mut self.form.head, 318 | &mut self.form.tail, 319 | curl_sys::CURLFORM_COPYNAME, 320 | self.name.as_ptr(), 321 | curl_sys::CURLFORM_NAMELENGTH, 322 | self.name.len(), 323 | curl_sys::CURLFORM_ARRAY, 324 | self.array.as_ptr(), 325 | curl_sys::CURLFORM_END, 326 | ) 327 | }; 328 | if rc == curl_sys::CURL_FORMADD_OK { 329 | Ok(()) 330 | } else { 331 | Err(FormError::new(rc)) 332 | } 333 | } 334 | 335 | #[cfg(unix)] 336 | fn path2cstr(&mut self, p: &Path) -> Option { 337 | use std::os::unix::prelude::*; 338 | self.bytes2cstr(p.as_os_str().as_bytes()) 339 | } 340 | 341 | #[cfg(windows)] 342 | fn path2cstr(&mut self, p: &Path) -> Option { 343 | match p.to_str() { 344 | Some(bytes) => self.bytes2cstr(bytes.as_bytes()), 345 | None if self.error.is_none() => { 346 | // TODO: better error code 347 | self.error = Some(FormError::new(curl_sys::CURL_FORMADD_INCOMPLETE)); 348 | None 349 | } 350 | None => None, 351 | } 352 | } 353 | 354 | fn bytes2cstr(&mut self, bytes: &[u8]) -> Option { 355 | match CString::new(bytes) { 356 | Ok(c) => Some(c), 357 | Err(..) if self.error.is_none() => { 358 | // TODO: better error code 359 | self.error = Some(FormError::new(curl_sys::CURL_FORMADD_INCOMPLETE)); 360 | None 361 | } 362 | Err(..) => None, 363 | } 364 | } 365 | } 366 | 367 | impl<'form, 'data> fmt::Debug for Part<'form, 'data> { 368 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 369 | // TODO: fill this out more 370 | f.debug_struct("Part") 371 | .field("name", &self.name) 372 | .field("form", &self.form) 373 | .finish() 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/easy/list.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | use std::fmt; 3 | use std::ptr; 4 | 5 | use crate::Error; 6 | 7 | /// A linked list of a strings 8 | pub struct List { 9 | raw: *mut curl_sys::curl_slist, 10 | } 11 | 12 | /// An iterator over `List` 13 | #[derive(Clone)] 14 | pub struct Iter<'a> { 15 | _me: &'a List, 16 | cur: *mut curl_sys::curl_slist, 17 | } 18 | 19 | pub fn raw(list: &List) -> *mut curl_sys::curl_slist { 20 | list.raw 21 | } 22 | 23 | pub unsafe fn from_raw(raw: *mut curl_sys::curl_slist) -> List { 24 | List { raw } 25 | } 26 | 27 | unsafe impl Send for List {} 28 | 29 | impl List { 30 | /// Creates a new empty list of strings. 31 | pub fn new() -> List { 32 | List { 33 | raw: ptr::null_mut(), 34 | } 35 | } 36 | 37 | /// Appends some data into this list. 38 | pub fn append(&mut self, data: &str) -> Result<(), Error> { 39 | let data = CString::new(data)?; 40 | unsafe { 41 | let raw = curl_sys::curl_slist_append(self.raw, data.as_ptr()); 42 | assert!(!raw.is_null()); 43 | self.raw = raw; 44 | Ok(()) 45 | } 46 | } 47 | 48 | /// Returns an iterator over the nodes in this list. 49 | pub fn iter(&self) -> Iter { 50 | Iter { 51 | _me: self, 52 | cur: self.raw, 53 | } 54 | } 55 | } 56 | 57 | impl fmt::Debug for List { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | f.debug_list() 60 | .entries(self.iter().map(String::from_utf8_lossy)) 61 | .finish() 62 | } 63 | } 64 | 65 | impl<'a> IntoIterator for &'a List { 66 | type IntoIter = Iter<'a>; 67 | type Item = &'a [u8]; 68 | 69 | fn into_iter(self) -> Iter<'a> { 70 | self.iter() 71 | } 72 | } 73 | 74 | impl Drop for List { 75 | fn drop(&mut self) { 76 | unsafe { curl_sys::curl_slist_free_all(self.raw) } 77 | } 78 | } 79 | 80 | impl<'a> Iterator for Iter<'a> { 81 | type Item = &'a [u8]; 82 | 83 | fn next(&mut self) -> Option<&'a [u8]> { 84 | if self.cur.is_null() { 85 | return None; 86 | } 87 | 88 | unsafe { 89 | let ret = Some(CStr::from_ptr((*self.cur).data).to_bytes()); 90 | self.cur = (*self.cur).next; 91 | ret 92 | } 93 | } 94 | } 95 | 96 | impl<'a> fmt::Debug for Iter<'a> { 97 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 98 | f.debug_list() 99 | .entries(self.clone().map(String::from_utf8_lossy)) 100 | .finish() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/easy/mod.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to the "easy" libcurl API. 2 | //! 3 | //! This module contains some simple types like `Easy` and `List` which are just 4 | //! wrappers around the corresponding libcurl types. There's also a few enums 5 | //! scattered about for various options here and there. 6 | //! 7 | //! Most simple usage of libcurl will likely use the `Easy` structure here, and 8 | //! you can find more docs about its usage on that struct. 9 | 10 | mod form; 11 | mod handle; 12 | mod handler; 13 | mod list; 14 | mod windows; 15 | 16 | pub use self::form::{Form, Part}; 17 | pub use self::handle::{Easy, Transfer}; 18 | pub use self::handler::{Auth, NetRc, PostRedirections, ProxyType, SslOpt}; 19 | pub use self::handler::{Easy2, Handler}; 20 | pub use self::handler::{HttpVersion, IpResolve, SslVersion, TimeCondition}; 21 | pub use self::handler::{InfoType, ReadError, SeekResult, WriteError}; 22 | pub use self::list::{Iter, List}; 23 | -------------------------------------------------------------------------------- /src/easy/windows.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, non_snake_case)] 2 | 3 | use libc::c_void; 4 | 5 | #[cfg(target_env = "msvc")] 6 | mod win { 7 | use schannel::cert_context::ValidUses; 8 | use schannel::cert_store::CertStore; 9 | use std::ffi::*; 10 | use std::mem; 11 | use std::ptr; 12 | use windows_sys::Win32::Security::Cryptography::*; 13 | use windows_sys::Win32::System::LibraryLoader::*; 14 | 15 | fn lookup(module: &str, symbol: &str) -> Option<*const c_void> { 16 | unsafe { 17 | let mut mod_buf: Vec = module.encode_utf16().collect(); 18 | mod_buf.push(0); 19 | let handle = GetModuleHandleW(mod_buf.as_mut_ptr()); 20 | GetProcAddress(handle, symbol.as_ptr()).map(|n| n as *const c_void) 21 | } 22 | } 23 | 24 | pub enum X509_STORE {} 25 | pub enum X509 {} 26 | pub enum SSL_CTX {} 27 | 28 | type d2i_X509_fn = unsafe extern "C" fn( 29 | a: *mut *mut X509, 30 | pp: *mut *const c_uchar, 31 | length: c_long, 32 | ) -> *mut X509; 33 | type X509_free_fn = unsafe extern "C" fn(x: *mut X509); 34 | type X509_STORE_add_cert_fn = 35 | unsafe extern "C" fn(store: *mut X509_STORE, x: *mut X509) -> c_int; 36 | type SSL_CTX_get_cert_store_fn = unsafe extern "C" fn(ctx: *const SSL_CTX) -> *mut X509_STORE; 37 | 38 | struct OpenSSL { 39 | d2i_X509: d2i_X509_fn, 40 | X509_free: X509_free_fn, 41 | X509_STORE_add_cert: X509_STORE_add_cert_fn, 42 | SSL_CTX_get_cert_store: SSL_CTX_get_cert_store_fn, 43 | } 44 | 45 | unsafe fn lookup_functions(crypto_module: &str, ssl_module: &str) -> Option { 46 | macro_rules! get { 47 | ($(let $sym:ident in $module:expr;)*) => ($( 48 | let $sym = match lookup($module, stringify!($sym)) { 49 | Some(p) => p, 50 | None => return None, 51 | }; 52 | )*) 53 | } 54 | get! { 55 | let d2i_X509 in crypto_module; 56 | let X509_free in crypto_module; 57 | let X509_STORE_add_cert in crypto_module; 58 | let SSL_CTX_get_cert_store in ssl_module; 59 | } 60 | Some(OpenSSL { 61 | d2i_X509: mem::transmute(d2i_X509), 62 | X509_free: mem::transmute(X509_free), 63 | X509_STORE_add_cert: mem::transmute(X509_STORE_add_cert), 64 | SSL_CTX_get_cert_store: mem::transmute(SSL_CTX_get_cert_store), 65 | }) 66 | } 67 | 68 | pub unsafe fn add_certs_to_context(ssl_ctx: *mut c_void) { 69 | // check the runtime version of OpenSSL 70 | let openssl = match crate::version::Version::get().ssl_version() { 71 | Some(ssl_ver) if ssl_ver.starts_with("OpenSSL/1.1.0") => { 72 | lookup_functions("libcrypto", "libssl") 73 | } 74 | Some(ssl_ver) if ssl_ver.starts_with("OpenSSL/1.0.2") => { 75 | lookup_functions("libeay32", "ssleay32") 76 | } 77 | _ => return, 78 | }; 79 | let openssl = match openssl { 80 | Some(s) => s, 81 | None => return, 82 | }; 83 | 84 | let openssl_store = (openssl.SSL_CTX_get_cert_store)(ssl_ctx as *const SSL_CTX); 85 | let store = match CertStore::open_current_user("ROOT") { 86 | Ok(s) => s, 87 | Err(_) => return, 88 | }; 89 | 90 | for cert in store.certs() { 91 | let valid_uses = match cert.valid_uses() { 92 | Ok(v) => v, 93 | Err(_) => continue, 94 | }; 95 | 96 | // check the extended key usage for the "Server Authentication" OID 97 | match valid_uses { 98 | ValidUses::All => {} 99 | ValidUses::Oids(ref oids) => { 100 | let oid = CStr::from_ptr(szOID_PKIX_KP_SERVER_AUTH as *const _) 101 | .to_string_lossy() 102 | .into_owned(); 103 | if !oids.contains(&oid) { 104 | continue; 105 | } 106 | } 107 | } 108 | 109 | let der = cert.to_der(); 110 | let x509 = (openssl.d2i_X509)(ptr::null_mut(), &mut der.as_ptr(), der.len() as c_long); 111 | if !x509.is_null() { 112 | (openssl.X509_STORE_add_cert)(openssl_store, x509); 113 | (openssl.X509_free)(x509); 114 | } 115 | } 116 | } 117 | } 118 | 119 | #[cfg(target_env = "msvc")] 120 | pub fn add_certs_to_context(ssl_ctx: *mut c_void) { 121 | unsafe { 122 | win::add_certs_to_context(ssl_ctx as *mut _); 123 | } 124 | } 125 | 126 | #[cfg(not(target_env = "msvc"))] 127 | pub fn add_certs_to_context(_: *mut c_void) {} 128 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::ffi::{self, CStr}; 3 | use std::fmt; 4 | use std::io; 5 | use std::str; 6 | 7 | /// An error returned from various "easy" operations. 8 | /// 9 | /// This structure wraps a `CURLcode`. 10 | #[derive(Clone, PartialEq)] 11 | pub struct Error { 12 | code: curl_sys::CURLcode, 13 | extra: Option>, 14 | } 15 | 16 | impl Error { 17 | /// Creates a new error from the underlying code returned by libcurl. 18 | pub fn new(code: curl_sys::CURLcode) -> Error { 19 | Error { code, extra: None } 20 | } 21 | 22 | /// Stores some extra information about this error inside this error. 23 | /// 24 | /// This is typically used with `take_error_buf` on the easy handles to 25 | /// couple the extra `CURLOPT_ERRORBUFFER` information with an `Error` being 26 | /// returned. 27 | pub fn set_extra(&mut self, extra: String) { 28 | self.extra = Some(extra.into()); 29 | } 30 | 31 | /// Returns whether this error corresponds to CURLE_UNSUPPORTED_PROTOCOL. 32 | pub fn is_unsupported_protocol(&self) -> bool { 33 | self.code == curl_sys::CURLE_UNSUPPORTED_PROTOCOL 34 | } 35 | 36 | /// Returns whether this error corresponds to CURLE_FAILED_INIT. 37 | pub fn is_failed_init(&self) -> bool { 38 | self.code == curl_sys::CURLE_FAILED_INIT 39 | } 40 | 41 | /// Returns whether this error corresponds to CURLE_URL_MALFORMAT. 42 | pub fn is_url_malformed(&self) -> bool { 43 | self.code == curl_sys::CURLE_URL_MALFORMAT 44 | } 45 | 46 | // /// Returns whether this error corresponds to CURLE_NOT_BUILT_IN. 47 | // pub fn is_not_built_in(&self) -> bool { 48 | // self.code == curl_sys::CURLE_NOT_BUILT_IN 49 | // } 50 | 51 | /// Returns whether this error corresponds to CURLE_COULDNT_RESOLVE_PROXY. 52 | pub fn is_couldnt_resolve_proxy(&self) -> bool { 53 | self.code == curl_sys::CURLE_COULDNT_RESOLVE_PROXY 54 | } 55 | 56 | /// Returns whether this error corresponds to CURLE_COULDNT_RESOLVE_HOST. 57 | pub fn is_couldnt_resolve_host(&self) -> bool { 58 | self.code == curl_sys::CURLE_COULDNT_RESOLVE_HOST 59 | } 60 | 61 | /// Returns whether this error corresponds to CURLE_COULDNT_CONNECT. 62 | pub fn is_couldnt_connect(&self) -> bool { 63 | self.code == curl_sys::CURLE_COULDNT_CONNECT 64 | } 65 | 66 | /// Returns whether this error corresponds to CURLE_REMOTE_ACCESS_DENIED. 67 | pub fn is_remote_access_denied(&self) -> bool { 68 | self.code == curl_sys::CURLE_REMOTE_ACCESS_DENIED 69 | } 70 | 71 | /// Returns whether this error corresponds to CURLE_PARTIAL_FILE. 72 | pub fn is_partial_file(&self) -> bool { 73 | self.code == curl_sys::CURLE_PARTIAL_FILE 74 | } 75 | 76 | /// Returns whether this error corresponds to CURLE_QUOTE_ERROR. 77 | pub fn is_quote_error(&self) -> bool { 78 | self.code == curl_sys::CURLE_QUOTE_ERROR 79 | } 80 | 81 | /// Returns whether this error corresponds to CURLE_HTTP_RETURNED_ERROR. 82 | pub fn is_http_returned_error(&self) -> bool { 83 | self.code == curl_sys::CURLE_HTTP_RETURNED_ERROR 84 | } 85 | 86 | /// Returns whether this error corresponds to CURLE_READ_ERROR. 87 | pub fn is_read_error(&self) -> bool { 88 | self.code == curl_sys::CURLE_READ_ERROR 89 | } 90 | 91 | /// Returns whether this error corresponds to CURLE_WRITE_ERROR. 92 | pub fn is_write_error(&self) -> bool { 93 | self.code == curl_sys::CURLE_WRITE_ERROR 94 | } 95 | 96 | /// Returns whether this error corresponds to CURLE_UPLOAD_FAILED. 97 | pub fn is_upload_failed(&self) -> bool { 98 | self.code == curl_sys::CURLE_UPLOAD_FAILED 99 | } 100 | 101 | /// Returns whether this error corresponds to CURLE_OUT_OF_MEMORY. 102 | pub fn is_out_of_memory(&self) -> bool { 103 | self.code == curl_sys::CURLE_OUT_OF_MEMORY 104 | } 105 | 106 | /// Returns whether this error corresponds to CURLE_OPERATION_TIMEDOUT. 107 | pub fn is_operation_timedout(&self) -> bool { 108 | self.code == curl_sys::CURLE_OPERATION_TIMEDOUT 109 | } 110 | 111 | /// Returns whether this error corresponds to CURLE_RANGE_ERROR. 112 | pub fn is_range_error(&self) -> bool { 113 | self.code == curl_sys::CURLE_RANGE_ERROR 114 | } 115 | 116 | /// Returns whether this error corresponds to CURLE_HTTP_POST_ERROR. 117 | pub fn is_http_post_error(&self) -> bool { 118 | self.code == curl_sys::CURLE_HTTP_POST_ERROR 119 | } 120 | 121 | /// Returns whether this error corresponds to CURLE_SSL_CONNECT_ERROR. 122 | pub fn is_ssl_connect_error(&self) -> bool { 123 | self.code == curl_sys::CURLE_SSL_CONNECT_ERROR 124 | } 125 | 126 | /// Returns whether this error corresponds to CURLE_BAD_DOWNLOAD_RESUME. 127 | pub fn is_bad_download_resume(&self) -> bool { 128 | self.code == curl_sys::CURLE_BAD_DOWNLOAD_RESUME 129 | } 130 | 131 | /// Returns whether this error corresponds to CURLE_FILE_COULDNT_READ_FILE. 132 | pub fn is_file_couldnt_read_file(&self) -> bool { 133 | self.code == curl_sys::CURLE_FILE_COULDNT_READ_FILE 134 | } 135 | 136 | /// Returns whether this error corresponds to CURLE_FUNCTION_NOT_FOUND. 137 | pub fn is_function_not_found(&self) -> bool { 138 | self.code == curl_sys::CURLE_FUNCTION_NOT_FOUND 139 | } 140 | 141 | /// Returns whether this error corresponds to CURLE_ABORTED_BY_CALLBACK. 142 | pub fn is_aborted_by_callback(&self) -> bool { 143 | self.code == curl_sys::CURLE_ABORTED_BY_CALLBACK 144 | } 145 | 146 | /// Returns whether this error corresponds to CURLE_BAD_FUNCTION_ARGUMENT. 147 | pub fn is_bad_function_argument(&self) -> bool { 148 | self.code == curl_sys::CURLE_BAD_FUNCTION_ARGUMENT 149 | } 150 | 151 | /// Returns whether this error corresponds to CURLE_INTERFACE_FAILED. 152 | pub fn is_interface_failed(&self) -> bool { 153 | self.code == curl_sys::CURLE_INTERFACE_FAILED 154 | } 155 | 156 | /// Returns whether this error corresponds to CURLE_TOO_MANY_REDIRECTS. 157 | pub fn is_too_many_redirects(&self) -> bool { 158 | self.code == curl_sys::CURLE_TOO_MANY_REDIRECTS 159 | } 160 | 161 | /// Returns whether this error corresponds to CURLE_UNKNOWN_OPTION. 162 | pub fn is_unknown_option(&self) -> bool { 163 | self.code == curl_sys::CURLE_UNKNOWN_OPTION 164 | } 165 | 166 | /// Returns whether this error corresponds to CURLE_PEER_FAILED_VERIFICATION. 167 | pub fn is_peer_failed_verification(&self) -> bool { 168 | self.code == curl_sys::CURLE_PEER_FAILED_VERIFICATION 169 | } 170 | 171 | /// Returns whether this error corresponds to CURLE_GOT_NOTHING. 172 | pub fn is_got_nothing(&self) -> bool { 173 | self.code == curl_sys::CURLE_GOT_NOTHING 174 | } 175 | 176 | /// Returns whether this error corresponds to CURLE_SSL_ENGINE_NOTFOUND. 177 | pub fn is_ssl_engine_notfound(&self) -> bool { 178 | self.code == curl_sys::CURLE_SSL_ENGINE_NOTFOUND 179 | } 180 | 181 | /// Returns whether this error corresponds to CURLE_SSL_ENGINE_SETFAILED. 182 | pub fn is_ssl_engine_setfailed(&self) -> bool { 183 | self.code == curl_sys::CURLE_SSL_ENGINE_SETFAILED 184 | } 185 | 186 | /// Returns whether this error corresponds to CURLE_SEND_ERROR. 187 | pub fn is_send_error(&self) -> bool { 188 | self.code == curl_sys::CURLE_SEND_ERROR 189 | } 190 | 191 | /// Returns whether this error corresponds to CURLE_RECV_ERROR. 192 | pub fn is_recv_error(&self) -> bool { 193 | self.code == curl_sys::CURLE_RECV_ERROR 194 | } 195 | 196 | /// Returns whether this error corresponds to CURLE_SSL_CERTPROBLEM. 197 | pub fn is_ssl_certproblem(&self) -> bool { 198 | self.code == curl_sys::CURLE_SSL_CERTPROBLEM 199 | } 200 | 201 | /// Returns whether this error corresponds to CURLE_SSL_CIPHER. 202 | pub fn is_ssl_cipher(&self) -> bool { 203 | self.code == curl_sys::CURLE_SSL_CIPHER 204 | } 205 | 206 | /// Returns whether this error corresponds to CURLE_SSL_CACERT. 207 | pub fn is_ssl_cacert(&self) -> bool { 208 | self.code == curl_sys::CURLE_SSL_CACERT 209 | } 210 | 211 | /// Returns whether this error corresponds to CURLE_BAD_CONTENT_ENCODING. 212 | pub fn is_bad_content_encoding(&self) -> bool { 213 | self.code == curl_sys::CURLE_BAD_CONTENT_ENCODING 214 | } 215 | 216 | /// Returns whether this error corresponds to CURLE_FILESIZE_EXCEEDED. 217 | pub fn is_filesize_exceeded(&self) -> bool { 218 | self.code == curl_sys::CURLE_FILESIZE_EXCEEDED 219 | } 220 | 221 | /// Returns whether this error corresponds to CURLE_USE_SSL_FAILED. 222 | pub fn is_use_ssl_failed(&self) -> bool { 223 | self.code == curl_sys::CURLE_USE_SSL_FAILED 224 | } 225 | 226 | /// Returns whether this error corresponds to CURLE_SEND_FAIL_REWIND. 227 | pub fn is_send_fail_rewind(&self) -> bool { 228 | self.code == curl_sys::CURLE_SEND_FAIL_REWIND 229 | } 230 | 231 | /// Returns whether this error corresponds to CURLE_SSL_ENGINE_INITFAILED. 232 | pub fn is_ssl_engine_initfailed(&self) -> bool { 233 | self.code == curl_sys::CURLE_SSL_ENGINE_INITFAILED 234 | } 235 | 236 | /// Returns whether this error corresponds to CURLE_LOGIN_DENIED. 237 | pub fn is_login_denied(&self) -> bool { 238 | self.code == curl_sys::CURLE_LOGIN_DENIED 239 | } 240 | 241 | /// Returns whether this error corresponds to CURLE_CONV_FAILED. 242 | pub fn is_conv_failed(&self) -> bool { 243 | self.code == curl_sys::CURLE_CONV_FAILED 244 | } 245 | 246 | /// Returns whether this error corresponds to CURLE_CONV_REQD. 247 | pub fn is_conv_required(&self) -> bool { 248 | self.code == curl_sys::CURLE_CONV_REQD 249 | } 250 | 251 | /// Returns whether this error corresponds to CURLE_SSL_CACERT_BADFILE. 252 | pub fn is_ssl_cacert_badfile(&self) -> bool { 253 | self.code == curl_sys::CURLE_SSL_CACERT_BADFILE 254 | } 255 | 256 | /// Returns whether this error corresponds to CURLE_SSL_CRL_BADFILE. 257 | pub fn is_ssl_crl_badfile(&self) -> bool { 258 | self.code == curl_sys::CURLE_SSL_CRL_BADFILE 259 | } 260 | 261 | /// Returns whether this error corresponds to CURLE_SSL_SHUTDOWN_FAILED. 262 | pub fn is_ssl_shutdown_failed(&self) -> bool { 263 | self.code == curl_sys::CURLE_SSL_SHUTDOWN_FAILED 264 | } 265 | 266 | /// Returns whether this error corresponds to CURLE_AGAIN. 267 | pub fn is_again(&self) -> bool { 268 | self.code == curl_sys::CURLE_AGAIN 269 | } 270 | 271 | /// Returns whether this error corresponds to CURLE_SSL_ISSUER_ERROR. 272 | pub fn is_ssl_issuer_error(&self) -> bool { 273 | self.code == curl_sys::CURLE_SSL_ISSUER_ERROR 274 | } 275 | 276 | /// Returns whether this error corresponds to CURLE_CHUNK_FAILED. 277 | pub fn is_chunk_failed(&self) -> bool { 278 | self.code == curl_sys::CURLE_CHUNK_FAILED 279 | } 280 | 281 | /// Returns whether this error corresponds to CURLE_HTTP2. 282 | pub fn is_http2_error(&self) -> bool { 283 | self.code == curl_sys::CURLE_HTTP2 284 | } 285 | 286 | /// Returns whether this error corresponds to CURLE_HTTP2_STREAM. 287 | pub fn is_http2_stream_error(&self) -> bool { 288 | self.code == curl_sys::CURLE_HTTP2_STREAM 289 | } 290 | 291 | // /// Returns whether this error corresponds to CURLE_NO_CONNECTION_AVAILABLE. 292 | // pub fn is_no_connection_available(&self) -> bool { 293 | // self.code == curl_sys::CURLE_NO_CONNECTION_AVAILABLE 294 | // } 295 | 296 | /// Returns the value of the underlying error corresponding to libcurl. 297 | pub fn code(&self) -> curl_sys::CURLcode { 298 | self.code 299 | } 300 | 301 | /// Returns the general description of this error code, using curl's 302 | /// builtin `strerror`-like functionality. 303 | pub fn description(&self) -> &str { 304 | unsafe { 305 | let s = curl_sys::curl_easy_strerror(self.code); 306 | assert!(!s.is_null()); 307 | str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap() 308 | } 309 | } 310 | 311 | /// Returns the extra description of this error, if any is available. 312 | pub fn extra_description(&self) -> Option<&str> { 313 | self.extra.as_deref() 314 | } 315 | } 316 | 317 | impl fmt::Display for Error { 318 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 319 | let desc = self.description(); 320 | match self.extra { 321 | Some(ref s) => write!(f, "[{}] {} ({})", self.code(), desc, s), 322 | None => write!(f, "[{}] {}", self.code(), desc), 323 | } 324 | } 325 | } 326 | 327 | impl fmt::Debug for Error { 328 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 329 | f.debug_struct("Error") 330 | .field("description", &self.description()) 331 | .field("code", &self.code) 332 | .field("extra", &self.extra) 333 | .finish() 334 | } 335 | } 336 | 337 | impl error::Error for Error {} 338 | 339 | /// An error returned from "share" operations. 340 | /// 341 | /// This structure wraps a `CURLSHcode`. 342 | #[derive(Clone, PartialEq)] 343 | pub struct ShareError { 344 | code: curl_sys::CURLSHcode, 345 | } 346 | 347 | impl ShareError { 348 | /// Creates a new error from the underlying code returned by libcurl. 349 | pub fn new(code: curl_sys::CURLSHcode) -> ShareError { 350 | ShareError { code } 351 | } 352 | 353 | /// Returns whether this error corresponds to CURLSHE_BAD_OPTION. 354 | pub fn is_bad_option(&self) -> bool { 355 | self.code == curl_sys::CURLSHE_BAD_OPTION 356 | } 357 | 358 | /// Returns whether this error corresponds to CURLSHE_IN_USE. 359 | pub fn is_in_use(&self) -> bool { 360 | self.code == curl_sys::CURLSHE_IN_USE 361 | } 362 | 363 | /// Returns whether this error corresponds to CURLSHE_INVALID. 364 | pub fn is_invalid(&self) -> bool { 365 | self.code == curl_sys::CURLSHE_INVALID 366 | } 367 | 368 | /// Returns whether this error corresponds to CURLSHE_NOMEM. 369 | pub fn is_nomem(&self) -> bool { 370 | self.code == curl_sys::CURLSHE_NOMEM 371 | } 372 | 373 | // /// Returns whether this error corresponds to CURLSHE_NOT_BUILT_IN. 374 | // pub fn is_not_built_in(&self) -> bool { 375 | // self.code == curl_sys::CURLSHE_NOT_BUILT_IN 376 | // } 377 | 378 | /// Returns the value of the underlying error corresponding to libcurl. 379 | pub fn code(&self) -> curl_sys::CURLSHcode { 380 | self.code 381 | } 382 | 383 | /// Returns curl's human-readable version of this error. 384 | pub fn description(&self) -> &str { 385 | unsafe { 386 | let s = curl_sys::curl_share_strerror(self.code); 387 | assert!(!s.is_null()); 388 | str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap() 389 | } 390 | } 391 | } 392 | 393 | impl fmt::Display for ShareError { 394 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 395 | self.description().fmt(f) 396 | } 397 | } 398 | 399 | impl fmt::Debug for ShareError { 400 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 401 | write!( 402 | f, 403 | "ShareError {{ description: {:?}, code: {} }}", 404 | self.description(), 405 | self.code 406 | ) 407 | } 408 | } 409 | 410 | impl error::Error for ShareError {} 411 | 412 | /// An error from "multi" operations. 413 | /// 414 | /// THis structure wraps a `CURLMcode`. 415 | #[derive(Clone, PartialEq)] 416 | pub struct MultiError { 417 | code: curl_sys::CURLMcode, 418 | } 419 | 420 | impl MultiError { 421 | /// Creates a new error from the underlying code returned by libcurl. 422 | pub fn new(code: curl_sys::CURLMcode) -> MultiError { 423 | MultiError { code } 424 | } 425 | 426 | /// Returns whether this error corresponds to CURLM_BAD_HANDLE. 427 | pub fn is_bad_handle(&self) -> bool { 428 | self.code == curl_sys::CURLM_BAD_HANDLE 429 | } 430 | 431 | /// Returns whether this error corresponds to CURLM_BAD_EASY_HANDLE. 432 | pub fn is_bad_easy_handle(&self) -> bool { 433 | self.code == curl_sys::CURLM_BAD_EASY_HANDLE 434 | } 435 | 436 | /// Returns whether this error corresponds to CURLM_OUT_OF_MEMORY. 437 | pub fn is_out_of_memory(&self) -> bool { 438 | self.code == curl_sys::CURLM_OUT_OF_MEMORY 439 | } 440 | 441 | /// Returns whether this error corresponds to CURLM_INTERNAL_ERROR. 442 | pub fn is_internal_error(&self) -> bool { 443 | self.code == curl_sys::CURLM_INTERNAL_ERROR 444 | } 445 | 446 | /// Returns whether this error corresponds to CURLM_BAD_SOCKET. 447 | pub fn is_bad_socket(&self) -> bool { 448 | self.code == curl_sys::CURLM_BAD_SOCKET 449 | } 450 | 451 | /// Returns whether this error corresponds to CURLM_UNKNOWN_OPTION. 452 | pub fn is_unknown_option(&self) -> bool { 453 | self.code == curl_sys::CURLM_UNKNOWN_OPTION 454 | } 455 | 456 | /// Returns whether this error corresponds to CURLM_CALL_MULTI_PERFORM. 457 | pub fn is_call_perform(&self) -> bool { 458 | self.code == curl_sys::CURLM_CALL_MULTI_PERFORM 459 | } 460 | 461 | // /// Returns whether this error corresponds to CURLM_ADDED_ALREADY. 462 | // pub fn is_added_already(&self) -> bool { 463 | // self.code == curl_sys::CURLM_ADDED_ALREADY 464 | // } 465 | 466 | /// Returns the value of the underlying error corresponding to libcurl. 467 | pub fn code(&self) -> curl_sys::CURLMcode { 468 | self.code 469 | } 470 | 471 | /// Returns curl's human-readable description of this error. 472 | pub fn description(&self) -> &str { 473 | unsafe { 474 | let s = curl_sys::curl_multi_strerror(self.code); 475 | assert!(!s.is_null()); 476 | str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap() 477 | } 478 | } 479 | } 480 | 481 | impl fmt::Display for MultiError { 482 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 483 | self.description().fmt(f) 484 | } 485 | } 486 | 487 | impl fmt::Debug for MultiError { 488 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 489 | f.debug_struct("MultiError") 490 | .field("description", &self.description()) 491 | .field("code", &self.code) 492 | .finish() 493 | } 494 | } 495 | 496 | impl error::Error for MultiError {} 497 | 498 | /// An error from "form add" operations. 499 | /// 500 | /// THis structure wraps a `CURLFORMcode`. 501 | #[derive(Clone, PartialEq)] 502 | pub struct FormError { 503 | code: curl_sys::CURLFORMcode, 504 | } 505 | 506 | impl FormError { 507 | /// Creates a new error from the underlying code returned by libcurl. 508 | pub fn new(code: curl_sys::CURLFORMcode) -> FormError { 509 | FormError { code } 510 | } 511 | 512 | /// Returns whether this error corresponds to CURL_FORMADD_MEMORY. 513 | pub fn is_memory(&self) -> bool { 514 | self.code == curl_sys::CURL_FORMADD_MEMORY 515 | } 516 | 517 | /// Returns whether this error corresponds to CURL_FORMADD_OPTION_TWICE. 518 | pub fn is_option_twice(&self) -> bool { 519 | self.code == curl_sys::CURL_FORMADD_OPTION_TWICE 520 | } 521 | 522 | /// Returns whether this error corresponds to CURL_FORMADD_NULL. 523 | pub fn is_null(&self) -> bool { 524 | self.code == curl_sys::CURL_FORMADD_NULL 525 | } 526 | 527 | /// Returns whether this error corresponds to CURL_FORMADD_UNKNOWN_OPTION. 528 | pub fn is_unknown_option(&self) -> bool { 529 | self.code == curl_sys::CURL_FORMADD_UNKNOWN_OPTION 530 | } 531 | 532 | /// Returns whether this error corresponds to CURL_FORMADD_INCOMPLETE. 533 | pub fn is_incomplete(&self) -> bool { 534 | self.code == curl_sys::CURL_FORMADD_INCOMPLETE 535 | } 536 | 537 | /// Returns whether this error corresponds to CURL_FORMADD_ILLEGAL_ARRAY. 538 | pub fn is_illegal_array(&self) -> bool { 539 | self.code == curl_sys::CURL_FORMADD_ILLEGAL_ARRAY 540 | } 541 | 542 | /// Returns whether this error corresponds to CURL_FORMADD_DISABLED. 543 | pub fn is_disabled(&self) -> bool { 544 | self.code == curl_sys::CURL_FORMADD_DISABLED 545 | } 546 | 547 | /// Returns the value of the underlying error corresponding to libcurl. 548 | pub fn code(&self) -> curl_sys::CURLFORMcode { 549 | self.code 550 | } 551 | 552 | /// Returns a human-readable description of this error code. 553 | pub fn description(&self) -> &str { 554 | match self.code { 555 | curl_sys::CURL_FORMADD_MEMORY => "allocation failure", 556 | curl_sys::CURL_FORMADD_OPTION_TWICE => "one option passed twice", 557 | curl_sys::CURL_FORMADD_NULL => "null pointer given for string", 558 | curl_sys::CURL_FORMADD_UNKNOWN_OPTION => "unknown option", 559 | curl_sys::CURL_FORMADD_INCOMPLETE => "form information not complete", 560 | curl_sys::CURL_FORMADD_ILLEGAL_ARRAY => "illegal array in option", 561 | curl_sys::CURL_FORMADD_DISABLED => { 562 | "libcurl does not have support for this option compiled in" 563 | } 564 | _ => "unknown form error", 565 | } 566 | } 567 | } 568 | 569 | impl fmt::Display for FormError { 570 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 571 | self.description().fmt(f) 572 | } 573 | } 574 | 575 | impl fmt::Debug for FormError { 576 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 577 | f.debug_struct("FormError") 578 | .field("description", &self.description()) 579 | .field("code", &self.code) 580 | .finish() 581 | } 582 | } 583 | 584 | impl error::Error for FormError {} 585 | 586 | impl From for Error { 587 | fn from(_: ffi::NulError) -> Error { 588 | Error { 589 | code: curl_sys::CURLE_CONV_FAILED, 590 | extra: None, 591 | } 592 | } 593 | } 594 | 595 | impl From for io::Error { 596 | fn from(e: Error) -> io::Error { 597 | io::Error::new(io::ErrorKind::Other, e) 598 | } 599 | } 600 | 601 | impl From for io::Error { 602 | fn from(e: ShareError) -> io::Error { 603 | io::Error::new(io::ErrorKind::Other, e) 604 | } 605 | } 606 | 607 | impl From for io::Error { 608 | fn from(e: MultiError) -> io::Error { 609 | io::Error::new(io::ErrorKind::Other, e) 610 | } 611 | } 612 | 613 | impl From for io::Error { 614 | fn from(e: FormError) -> io::Error { 615 | io::Error::new(io::ErrorKind::Other, e) 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust bindings to the libcurl C library 2 | //! 3 | //! This crate contains bindings for an HTTP/HTTPS client which is powered by 4 | //! [libcurl], the same library behind the `curl` command line tool. The API 5 | //! currently closely matches that of libcurl itself, except that a Rustic layer 6 | //! of safety is applied on top. 7 | //! 8 | //! [libcurl]: https://curl.haxx.se/libcurl/ 9 | //! 10 | //! # The "Easy" API 11 | //! 12 | //! The easiest way to send a request is to use the `Easy` api which corresponds 13 | //! to `CURL` in libcurl. This handle supports a wide variety of options and can 14 | //! be used to make a single blocking request in a thread. Callbacks can be 15 | //! specified to deal with data as it arrives and a handle can be reused to 16 | //! cache connections and such. 17 | //! 18 | //! ```rust,no_run 19 | //! use std::io::{stdout, Write}; 20 | //! 21 | //! use curl::easy::Easy; 22 | //! 23 | //! // Write the contents of rust-lang.org to stdout 24 | //! let mut easy = Easy::new(); 25 | //! easy.url("https://www.rust-lang.org/").unwrap(); 26 | //! easy.write_function(|data| { 27 | //! stdout().write_all(data).unwrap(); 28 | //! Ok(data.len()) 29 | //! }).unwrap(); 30 | //! easy.perform().unwrap(); 31 | //! ``` 32 | //! 33 | //! # What about multiple concurrent HTTP requests? 34 | //! 35 | //! One option you have currently is to send multiple requests in multiple 36 | //! threads, but otherwise libcurl has a "multi" interface for doing this 37 | //! operation. Initial bindings of this interface can be found in the `multi` 38 | //! module, but feedback is welcome! 39 | //! 40 | //! # Where does libcurl come from? 41 | //! 42 | //! This crate links to the `curl-sys` crate which is in turn responsible for 43 | //! acquiring and linking to the libcurl library. Currently this crate will 44 | //! build libcurl from source if one is not already detected on the system. 45 | //! 46 | //! There is a large number of releases for libcurl, all with different sets of 47 | //! capabilities. Robust programs may wish to inspect `Version::get()` to test 48 | //! what features are implemented in the linked build of libcurl at runtime. 49 | //! 50 | //! # Initialization 51 | //! 52 | //! The underlying libcurl library must be initialized before use and has 53 | //! certain requirements on how this is done. Check the documentation for 54 | //! [`init`] for more details. 55 | 56 | #![deny(missing_docs, missing_debug_implementations)] 57 | #![doc(html_root_url = "https://docs.rs/curl/0.4")] 58 | 59 | use std::ffi::CStr; 60 | use std::str; 61 | use std::sync::Once; 62 | 63 | pub use crate::error::{Error, FormError, MultiError, ShareError}; 64 | mod error; 65 | 66 | pub use crate::version::{Protocols, Version}; 67 | mod version; 68 | 69 | pub mod easy; 70 | pub mod multi; 71 | mod panic; 72 | 73 | #[cfg(test)] 74 | static INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); 75 | 76 | /// Initializes the underlying libcurl library. 77 | /// 78 | /// The underlying libcurl library must be initialized before use, and must be 79 | /// done so on the main thread before any other threads are created by the 80 | /// program. This crate will do this for you automatically in the following 81 | /// scenarios: 82 | /// 83 | /// - Creating a new [`Easy`][easy::Easy] or [`Multi`][multi::Multi] handle 84 | /// - At program startup on Windows, macOS, Linux, Android, or FreeBSD systems 85 | /// 86 | /// This should be sufficient for most applications and scenarios, but in any 87 | /// other case, it is strongly recommended that you call this function manually 88 | /// as soon as your program starts. 89 | /// 90 | /// Calling this function more than once is harmless and has no effect. 91 | #[inline] 92 | pub fn init() { 93 | /// Used to prevent concurrent or duplicate initialization. 94 | static INIT: Once = Once::new(); 95 | 96 | INIT.call_once(|| { 97 | #[cfg(need_openssl_init)] 98 | openssl_probe::init_ssl_cert_env_vars(); 99 | #[cfg(need_openssl_init)] 100 | openssl_sys::init(); 101 | 102 | unsafe { 103 | assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0); 104 | } 105 | 106 | #[cfg(test)] 107 | { 108 | INITIALIZED.store(true, std::sync::atomic::Ordering::SeqCst); 109 | } 110 | 111 | // Note that we explicitly don't schedule a call to 112 | // `curl_global_cleanup`. The documentation for that function says 113 | // 114 | // > You must not call it when any other thread in the program (i.e. a 115 | // > thread sharing the same memory) is running. This doesn't just mean 116 | // > no other thread that is using libcurl. 117 | // 118 | // We can't ever be sure of that, so unfortunately we can't call the 119 | // function. 120 | }); 121 | } 122 | 123 | /// An exported constructor function. On supported platforms, this will be 124 | /// invoked automatically before the program's `main` is called. This is done 125 | /// for the convenience of library users since otherwise the thread-safety rules 126 | /// around initialization can be difficult to fulfill. 127 | /// 128 | /// This is a hidden public item to ensure the symbol isn't optimized away by a 129 | /// rustc/LLVM bug: https://github.com/rust-lang/rust/issues/47384. As long as 130 | /// any item in this module is used by the final binary (which `init` will be) 131 | /// then this symbol should be preserved. 132 | #[used] 133 | #[doc(hidden)] 134 | #[cfg_attr( 135 | any(target_os = "linux", target_os = "freebsd", target_os = "android"), 136 | link_section = ".init_array" 137 | )] 138 | #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")] 139 | #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")] 140 | pub static INIT_CTOR: extern "C" fn() = { 141 | /// This is the body of our constructor function. 142 | #[cfg_attr( 143 | any(target_os = "linux", target_os = "android"), 144 | link_section = ".text.startup" 145 | )] 146 | extern "C" fn init_ctor() { 147 | init(); 148 | } 149 | 150 | init_ctor 151 | }; 152 | 153 | unsafe fn opt_str<'a>(ptr: *const libc::c_char) -> Option<&'a str> { 154 | if ptr.is_null() { 155 | None 156 | } else { 157 | Some(str::from_utf8(CStr::from_ptr(ptr).to_bytes()).unwrap()) 158 | } 159 | } 160 | 161 | fn cvt(r: curl_sys::CURLcode) -> Result<(), Error> { 162 | if r == curl_sys::CURLE_OK { 163 | Ok(()) 164 | } else { 165 | Err(Error::new(r)) 166 | } 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use super::*; 172 | 173 | #[test] 174 | #[cfg(any( 175 | target_os = "linux", 176 | target_os = "macos", 177 | target_os = "windows", 178 | target_os = "freebsd", 179 | target_os = "android" 180 | ))] 181 | fn is_initialized_before_main() { 182 | assert!(INITIALIZED.load(std::sync::atomic::Ordering::SeqCst)); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::panic::{self, AssertUnwindSafe}; 4 | 5 | thread_local!(static LAST_ERROR: RefCell>> = { 6 | RefCell::new(None) 7 | }); 8 | 9 | pub fn catch T>(f: F) -> Option { 10 | match LAST_ERROR.try_with(|slot| slot.borrow().is_some()) { 11 | Ok(true) => return None, 12 | Ok(false) => {} 13 | // we're in thread shutdown, so we're for sure not panicking and 14 | // panicking again will abort, so no need to worry! 15 | Err(_) => {} 16 | } 17 | 18 | // Note that `AssertUnwindSafe` is used here as we prevent reentering 19 | // arbitrary code due to the `LAST_ERROR` check above plus propagation of a 20 | // panic after we return back to user code from C. 21 | match panic::catch_unwind(AssertUnwindSafe(f)) { 22 | Ok(ret) => Some(ret), 23 | Err(e) => { 24 | LAST_ERROR.with(|slot| *slot.borrow_mut() = Some(e)); 25 | None 26 | } 27 | } 28 | } 29 | 30 | pub fn propagate() { 31 | if let Ok(Some(t)) = LAST_ERROR.try_with(|slot| slot.borrow_mut().take()) { 32 | panic::resume_unwind(t) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::fmt; 3 | use std::str; 4 | 5 | use libc::{c_char, c_int}; 6 | 7 | /// Version information about libcurl and the capabilities that it supports. 8 | pub struct Version { 9 | inner: *mut curl_sys::curl_version_info_data, 10 | } 11 | 12 | unsafe impl Send for Version {} 13 | unsafe impl Sync for Version {} 14 | 15 | /// An iterator over the list of protocols a version supports. 16 | #[derive(Clone)] 17 | pub struct Protocols<'a> { 18 | cur: *const *const c_char, 19 | _inner: &'a Version, 20 | } 21 | 22 | impl Version { 23 | /// Returns the libcurl version that this library is currently linked against. 24 | pub fn num() -> &'static str { 25 | unsafe { 26 | let s = CStr::from_ptr(curl_sys::curl_version() as *const _); 27 | str::from_utf8(s.to_bytes()).unwrap() 28 | } 29 | } 30 | 31 | /// Returns the libcurl version that this library is currently linked against. 32 | pub fn get() -> Version { 33 | unsafe { 34 | let ptr = curl_sys::curl_version_info(curl_sys::CURLVERSION_NOW); 35 | assert!(!ptr.is_null()); 36 | Version { inner: ptr } 37 | } 38 | } 39 | 40 | /// Returns the human readable version string, 41 | pub fn version(&self) -> &str { 42 | unsafe { crate::opt_str((*self.inner).version).unwrap() } 43 | } 44 | 45 | /// Returns a numeric representation of the version number 46 | /// 47 | /// This is a 24 bit number made up of the major number, minor, and then 48 | /// patch number. For example 7.9.8 will return 0x070908. 49 | pub fn version_num(&self) -> u32 { 50 | unsafe { (*self.inner).version_num as u32 } 51 | } 52 | 53 | /// Returns true if this was built with the vendored version of libcurl. 54 | pub fn vendored(&self) -> bool { 55 | curl_sys::vendored() 56 | } 57 | 58 | /// Returns a human readable string of the host libcurl is built for. 59 | /// 60 | /// This is discovered as part of the build environment. 61 | pub fn host(&self) -> &str { 62 | unsafe { crate::opt_str((*self.inner).host).unwrap() } 63 | } 64 | 65 | /// Returns whether libcurl supports IPv6 66 | pub fn feature_ipv6(&self) -> bool { 67 | self.flag(curl_sys::CURL_VERSION_IPV6) 68 | } 69 | 70 | /// Returns whether libcurl supports SSL 71 | pub fn feature_ssl(&self) -> bool { 72 | self.flag(curl_sys::CURL_VERSION_SSL) 73 | } 74 | 75 | /// Returns whether libcurl supports HTTP deflate via libz 76 | pub fn feature_libz(&self) -> bool { 77 | self.flag(curl_sys::CURL_VERSION_LIBZ) 78 | } 79 | 80 | /// Returns whether libcurl supports HTTP NTLM 81 | pub fn feature_ntlm(&self) -> bool { 82 | self.flag(curl_sys::CURL_VERSION_NTLM) 83 | } 84 | 85 | /// Returns whether libcurl supports HTTP GSSNEGOTIATE 86 | pub fn feature_gss_negotiate(&self) -> bool { 87 | self.flag(curl_sys::CURL_VERSION_GSSNEGOTIATE) 88 | } 89 | 90 | /// Returns whether libcurl was built with debug capabilities 91 | pub fn feature_debug(&self) -> bool { 92 | self.flag(curl_sys::CURL_VERSION_DEBUG) 93 | } 94 | 95 | /// Returns whether libcurl was built with SPNEGO authentication 96 | pub fn feature_spnego(&self) -> bool { 97 | self.flag(curl_sys::CURL_VERSION_SPNEGO) 98 | } 99 | 100 | /// Returns whether libcurl was built with large file support 101 | pub fn feature_largefile(&self) -> bool { 102 | self.flag(curl_sys::CURL_VERSION_LARGEFILE) 103 | } 104 | 105 | /// Returns whether libcurl was built with support for IDNA, domain names 106 | /// with international letters. 107 | pub fn feature_idn(&self) -> bool { 108 | self.flag(curl_sys::CURL_VERSION_IDN) 109 | } 110 | 111 | /// Returns whether libcurl was built with support for SSPI. 112 | pub fn feature_sspi(&self) -> bool { 113 | self.flag(curl_sys::CURL_VERSION_SSPI) 114 | } 115 | 116 | /// Returns whether libcurl was built with asynchronous name lookups. 117 | pub fn feature_async_dns(&self) -> bool { 118 | self.flag(curl_sys::CURL_VERSION_ASYNCHDNS) 119 | } 120 | 121 | /// Returns whether libcurl was built with support for character 122 | /// conversions. 123 | pub fn feature_conv(&self) -> bool { 124 | self.flag(curl_sys::CURL_VERSION_CONV) 125 | } 126 | 127 | /// Returns whether libcurl was built with support for TLS-SRP. 128 | pub fn feature_tlsauth_srp(&self) -> bool { 129 | self.flag(curl_sys::CURL_VERSION_TLSAUTH_SRP) 130 | } 131 | 132 | /// Returns whether libcurl was built with support for NTLM delegation to 133 | /// winbind helper. 134 | pub fn feature_ntlm_wb(&self) -> bool { 135 | self.flag(curl_sys::CURL_VERSION_NTLM_WB) 136 | } 137 | 138 | /// Returns whether libcurl was built with support for unix domain socket 139 | pub fn feature_unix_domain_socket(&self) -> bool { 140 | self.flag(curl_sys::CURL_VERSION_UNIX_SOCKETS) 141 | } 142 | 143 | /// Returns whether libcurl was built with support for HTTPS proxy. 144 | pub fn feature_https_proxy(&self) -> bool { 145 | self.flag(curl_sys::CURL_VERSION_HTTPS_PROXY) 146 | } 147 | 148 | /// Returns whether libcurl was built with support for HTTP2. 149 | pub fn feature_http2(&self) -> bool { 150 | self.flag(curl_sys::CURL_VERSION_HTTP2) 151 | } 152 | 153 | /// Returns whether libcurl was built with support for HTTP3. 154 | pub fn feature_http3(&self) -> bool { 155 | self.flag(curl_sys::CURL_VERSION_HTTP3) 156 | } 157 | 158 | /// Returns whether libcurl was built with support for Brotli. 159 | pub fn feature_brotli(&self) -> bool { 160 | self.flag(curl_sys::CURL_VERSION_BROTLI) 161 | } 162 | 163 | /// Returns whether libcurl was built with support for Alt-Svc. 164 | pub fn feature_altsvc(&self) -> bool { 165 | self.flag(curl_sys::CURL_VERSION_ALTSVC) 166 | } 167 | 168 | /// Returns whether libcurl was built with support for zstd 169 | pub fn feature_zstd(&self) -> bool { 170 | self.flag(curl_sys::CURL_VERSION_ZSTD) 171 | } 172 | 173 | /// Returns whether libcurl was built with support for unicode 174 | pub fn feature_unicode(&self) -> bool { 175 | self.flag(curl_sys::CURL_VERSION_UNICODE) 176 | } 177 | 178 | /// Returns whether libcurl was built with support for hsts 179 | pub fn feature_hsts(&self) -> bool { 180 | self.flag(curl_sys::CURL_VERSION_HSTS) 181 | } 182 | 183 | /// Returns whether libcurl was built with support for gsasl 184 | pub fn feature_gsasl(&self) -> bool { 185 | self.flag(curl_sys::CURL_VERSION_GSASL) 186 | } 187 | 188 | fn flag(&self, flag: c_int) -> bool { 189 | unsafe { (*self.inner).features & flag != 0 } 190 | } 191 | 192 | /// Returns the version of OpenSSL that is used, or None if there is no SSL 193 | /// support. 194 | pub fn ssl_version(&self) -> Option<&str> { 195 | unsafe { crate::opt_str((*self.inner).ssl_version) } 196 | } 197 | 198 | /// Returns the version of libz that is used, or None if there is no libz 199 | /// support. 200 | pub fn libz_version(&self) -> Option<&str> { 201 | unsafe { crate::opt_str((*self.inner).libz_version) } 202 | } 203 | 204 | /// Returns an iterator over the list of protocols that this build of 205 | /// libcurl supports. 206 | pub fn protocols(&self) -> Protocols { 207 | unsafe { 208 | Protocols { 209 | _inner: self, 210 | cur: (*self.inner).protocols, 211 | } 212 | } 213 | } 214 | 215 | /// If available, the human readable version of ares that libcurl is linked 216 | /// against. 217 | pub fn ares_version(&self) -> Option<&str> { 218 | unsafe { 219 | if (*self.inner).age >= curl_sys::CURLVERSION_SECOND { 220 | crate::opt_str((*self.inner).ares) 221 | } else { 222 | None 223 | } 224 | } 225 | } 226 | 227 | /// If available, the version of ares that libcurl is linked against. 228 | pub fn ares_version_num(&self) -> Option { 229 | unsafe { 230 | if (*self.inner).age >= curl_sys::CURLVERSION_SECOND { 231 | Some((*self.inner).ares_num as u32) 232 | } else { 233 | None 234 | } 235 | } 236 | } 237 | 238 | /// If available, the version of libidn that libcurl is linked against. 239 | pub fn libidn_version(&self) -> Option<&str> { 240 | unsafe { 241 | if (*self.inner).age >= curl_sys::CURLVERSION_THIRD { 242 | crate::opt_str((*self.inner).libidn) 243 | } else { 244 | None 245 | } 246 | } 247 | } 248 | 249 | /// If available, the version of iconv libcurl is linked against. 250 | pub fn iconv_version_num(&self) -> Option { 251 | unsafe { 252 | if (*self.inner).age >= curl_sys::CURLVERSION_FOURTH { 253 | Some((*self.inner).iconv_ver_num as u32) 254 | } else { 255 | None 256 | } 257 | } 258 | } 259 | 260 | /// If available, the version of libssh that libcurl is linked against. 261 | pub fn libssh_version(&self) -> Option<&str> { 262 | unsafe { 263 | if (*self.inner).age >= curl_sys::CURLVERSION_FOURTH { 264 | crate::opt_str((*self.inner).libssh_version) 265 | } else { 266 | None 267 | } 268 | } 269 | } 270 | 271 | /// If available, the version of brotli libcurl is linked against. 272 | pub fn brotli_version_num(&self) -> Option { 273 | unsafe { 274 | if (*self.inner).age >= curl_sys::CURLVERSION_FIFTH { 275 | Some((*self.inner).brotli_ver_num) 276 | } else { 277 | None 278 | } 279 | } 280 | } 281 | 282 | /// If available, the version of brotli libcurl is linked against. 283 | pub fn brotli_version(&self) -> Option<&str> { 284 | unsafe { 285 | if (*self.inner).age >= curl_sys::CURLVERSION_FIFTH { 286 | crate::opt_str((*self.inner).brotli_version) 287 | } else { 288 | None 289 | } 290 | } 291 | } 292 | 293 | /// If available, the version of nghttp2 libcurl is linked against. 294 | pub fn nghttp2_version_num(&self) -> Option { 295 | unsafe { 296 | if (*self.inner).age >= curl_sys::CURLVERSION_SIXTH { 297 | Some((*self.inner).nghttp2_ver_num) 298 | } else { 299 | None 300 | } 301 | } 302 | } 303 | 304 | /// If available, the version of nghttp2 libcurl is linked against. 305 | pub fn nghttp2_version(&self) -> Option<&str> { 306 | unsafe { 307 | if (*self.inner).age >= curl_sys::CURLVERSION_SIXTH { 308 | crate::opt_str((*self.inner).nghttp2_version) 309 | } else { 310 | None 311 | } 312 | } 313 | } 314 | 315 | /// If available, the version of quic libcurl is linked against. 316 | pub fn quic_version(&self) -> Option<&str> { 317 | unsafe { 318 | if (*self.inner).age >= curl_sys::CURLVERSION_SIXTH { 319 | crate::opt_str((*self.inner).quic_version) 320 | } else { 321 | None 322 | } 323 | } 324 | } 325 | 326 | /// If available, the built-in default of CURLOPT_CAINFO. 327 | pub fn cainfo(&self) -> Option<&str> { 328 | unsafe { 329 | if (*self.inner).age >= curl_sys::CURLVERSION_SEVENTH { 330 | crate::opt_str((*self.inner).cainfo) 331 | } else { 332 | None 333 | } 334 | } 335 | } 336 | 337 | /// If available, the built-in default of CURLOPT_CAPATH. 338 | pub fn capath(&self) -> Option<&str> { 339 | unsafe { 340 | if (*self.inner).age >= curl_sys::CURLVERSION_SEVENTH { 341 | crate::opt_str((*self.inner).capath) 342 | } else { 343 | None 344 | } 345 | } 346 | } 347 | 348 | /// If avaiable, the numeric zstd version 349 | /// 350 | /// Represented as `(MAJOR << 24) | (MINOR << 12) | PATCH` 351 | pub fn zstd_ver_num(&self) -> Option { 352 | unsafe { 353 | if (*self.inner).age >= curl_sys::CURLVERSION_EIGHTH { 354 | Some((*self.inner).zstd_ver_num) 355 | } else { 356 | None 357 | } 358 | } 359 | } 360 | 361 | /// If available, the human readable version of zstd 362 | pub fn zstd_version(&self) -> Option<&str> { 363 | unsafe { 364 | if (*self.inner).age >= curl_sys::CURLVERSION_EIGHTH { 365 | crate::opt_str((*self.inner).zstd_version) 366 | } else { 367 | None 368 | } 369 | } 370 | } 371 | 372 | /// If available, the human readable version of hyper 373 | pub fn hyper_version(&self) -> Option<&str> { 374 | unsafe { 375 | if (*self.inner).age >= curl_sys::CURLVERSION_NINTH { 376 | crate::opt_str((*self.inner).hyper_version) 377 | } else { 378 | None 379 | } 380 | } 381 | } 382 | 383 | /// If available, the human readable version of hyper 384 | pub fn gsasl_version(&self) -> Option<&str> { 385 | unsafe { 386 | if (*self.inner).age >= curl_sys::CURLVERSION_TENTH { 387 | crate::opt_str((*self.inner).gsasl_version) 388 | } else { 389 | None 390 | } 391 | } 392 | } 393 | } 394 | 395 | impl fmt::Debug for Version { 396 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 397 | let mut f = f.debug_struct("Version"); 398 | f.field("version", &self.version()) 399 | .field("rust_crate_version", &env!("CARGO_PKG_VERSION")) 400 | .field("rust_sys_crate_version", &curl_sys::rust_crate_version()) 401 | .field("vendored", &self.vendored()) 402 | .field("host", &self.host()) 403 | .field("feature_ipv6", &self.feature_ipv6()) 404 | .field("feature_ssl", &self.feature_ssl()) 405 | .field("feature_libz", &self.feature_libz()) 406 | .field("feature_ntlm", &self.feature_ntlm()) 407 | .field("feature_gss_negotiate", &self.feature_gss_negotiate()) 408 | .field("feature_debug", &self.feature_debug()) 409 | .field("feature_spnego", &self.feature_spnego()) 410 | .field("feature_largefile", &self.feature_largefile()) 411 | .field("feature_idn", &self.feature_idn()) 412 | .field("feature_sspi", &self.feature_sspi()) 413 | .field("feature_async_dns", &self.feature_async_dns()) 414 | .field("feature_conv", &self.feature_conv()) 415 | .field("feature_tlsauth_srp", &self.feature_tlsauth_srp()) 416 | .field("feature_ntlm_wb", &self.feature_ntlm_wb()) 417 | .field( 418 | "feature_unix_domain_socket", 419 | &self.feature_unix_domain_socket(), 420 | ) 421 | .field("feature_https_proxy", &self.feature_https_proxy()) 422 | .field("feature_altsvc", &self.feature_altsvc()) 423 | .field("feature_zstd", &self.feature_zstd()) 424 | .field("feature_unicode", &self.feature_unicode()) 425 | .field("feature_http3", &self.feature_http3()) 426 | .field("feature_http2", &self.feature_http2()) 427 | .field("feature_gsasl", &self.feature_gsasl()) 428 | .field("feature_brotli", &self.feature_brotli()); 429 | 430 | if let Some(s) = self.ssl_version() { 431 | f.field("ssl_version", &s); 432 | } 433 | if let Some(s) = self.libz_version() { 434 | f.field("libz_version", &s); 435 | } 436 | if let Some(s) = self.ares_version() { 437 | f.field("ares_version", &s); 438 | } 439 | if let Some(s) = self.libidn_version() { 440 | f.field("libidn_version", &s); 441 | } 442 | if let Some(s) = self.iconv_version_num() { 443 | f.field("iconv_version_num", &format!("{:x}", s)); 444 | } 445 | if let Some(s) = self.libssh_version() { 446 | f.field("libssh_version", &s); 447 | } 448 | if let Some(s) = self.brotli_version_num() { 449 | f.field("brotli_version_num", &format!("{:x}", s)); 450 | } 451 | if let Some(s) = self.brotli_version() { 452 | f.field("brotli_version", &s); 453 | } 454 | if let Some(s) = self.nghttp2_version_num() { 455 | f.field("nghttp2_version_num", &format!("{:x}", s)); 456 | } 457 | if let Some(s) = self.nghttp2_version() { 458 | f.field("nghttp2_version", &s); 459 | } 460 | if let Some(s) = self.quic_version() { 461 | f.field("quic_version", &s); 462 | } 463 | if let Some(s) = self.zstd_ver_num() { 464 | f.field("zstd_ver_num", &format!("{:x}", s)); 465 | } 466 | if let Some(s) = self.zstd_version() { 467 | f.field("zstd_version", &s); 468 | } 469 | if let Some(s) = self.cainfo() { 470 | f.field("cainfo", &s); 471 | } 472 | if let Some(s) = self.capath() { 473 | f.field("capath", &s); 474 | } 475 | if let Some(s) = self.hyper_version() { 476 | f.field("hyper_version", &s); 477 | } 478 | if let Some(s) = self.gsasl_version() { 479 | f.field("gsasl_version", &s); 480 | } 481 | 482 | f.field("protocols", &self.protocols().collect::>()); 483 | 484 | f.finish() 485 | } 486 | } 487 | 488 | impl<'a> Iterator for Protocols<'a> { 489 | type Item = &'a str; 490 | 491 | fn next(&mut self) -> Option<&'a str> { 492 | unsafe { 493 | if (*self.cur).is_null() { 494 | return None; 495 | } 496 | let ret = crate::opt_str(*self.cur).unwrap(); 497 | self.cur = self.cur.offset(1); 498 | Some(ret) 499 | } 500 | } 501 | } 502 | 503 | impl<'a> fmt::Debug for Protocols<'a> { 504 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 505 | f.debug_list().entries(self.clone()).finish() 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /systest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "systest" 3 | version = "0.1.0" 4 | authors = ["Alex Crichton "] 5 | build = "build.rs" 6 | publish = false 7 | edition = "2018" 8 | 9 | [dependencies] 10 | curl-sys = { path = "../curl-sys" } 11 | libc = "0.2" 12 | 13 | [build-dependencies] 14 | ctest2 = "0.4" 15 | cc = "1.0" 16 | 17 | [features] 18 | static-ssl = ['curl-sys/static-ssl'] 19 | -------------------------------------------------------------------------------- /systest/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::str; 3 | 4 | fn main() { 5 | let mut cfg = ctest2::TestGenerator::new(); 6 | 7 | let mut build = cc::Build::new(); 8 | build.file("version_detect.c"); 9 | if let Ok(out) = env::var("DEP_CURL_INCLUDE") { 10 | cfg.include(&out); 11 | build.include(&out); 12 | } 13 | let version = build.expand(); 14 | let version = str::from_utf8(&version).unwrap(); 15 | let version = version 16 | .lines() 17 | .find(|l| !l.is_empty() && !l.starts_with('#')) 18 | .and_then(|s| { 19 | let mut parts = s.split_whitespace(); 20 | let major = parts.next()?.parse::().ok()?; 21 | let minor = parts.next()?.parse::().ok()?; 22 | Some((major, minor)) 23 | }) 24 | .unwrap_or((10000, 0)); 25 | println!("got version: {version:?}"); 26 | 27 | if env::var("TARGET").unwrap().contains("msvc") { 28 | cfg.flag("/wd4574"); // did you mean to use '#if INCL_WINSOCK_API_TYPEDEFS' 29 | } 30 | 31 | cfg.header("curl/curl.h"); 32 | cfg.define("CURL_STATICLIB", None); 33 | cfg.field_name(|s, field| { 34 | if s == "curl_fileinfo" { 35 | field.replace("strings_", "strings.") 36 | } else if s == "CURLMsg" && field == "data" { 37 | "data.whatever".to_string() 38 | } else { 39 | field.to_string() 40 | } 41 | }); 42 | cfg.type_name(|s, is_struct, _is_union| match s { 43 | "CURL" | "CURLM" | "CURLSH" | "curl_version_info_data" => s.to_string(), 44 | "curl_khtype" | "curl_khstat" | "curl_khmatch" => format!("enum {}", s), 45 | s if is_struct => format!("struct {}", s), 46 | "sockaddr" => "struct sockaddr".to_string(), 47 | "__enum_ty" => "unsigned".to_string(), 48 | s => s.to_string(), 49 | }); 50 | // cfg.fn_cname(|s, l| l.unwrap_or(s).to_string()); 51 | cfg.skip_type(|n| n == "__enum_ty"); 52 | cfg.skip_signededness(|s| s.ends_with("callback") || s.ends_with("function")); 53 | 54 | cfg.skip_struct(move |s| { 55 | if version < (7, 71) { 56 | match s { 57 | "curl_blob" => return true, 58 | _ => {} 59 | } 60 | } 61 | if version < (8, 10) { 62 | match s { 63 | "curl_version_info_data" => return true, 64 | _ => {} 65 | } 66 | } 67 | 68 | false 69 | }); 70 | 71 | // Version symbols are extracted from https://curl.se/libcurl/c/symbols-in-versions.html 72 | cfg.skip_const(move |s| { 73 | if version < (8, 10) { 74 | match s { 75 | "CURLVERSION_TWELFTH" 76 | | "CURLVERSION_NOW" 77 | | "CURLOPT_WRITEINFO" 78 | | "CURLOPT_CLOSEPOLICY" => return true, 79 | _ => {} 80 | } 81 | } 82 | if version < (7, 87) { 83 | match s { 84 | "CURLVERSION_ELEVENTH" => return true, 85 | _ => {} 86 | } 87 | } 88 | if version < (7, 77) { 89 | match s { 90 | "CURLVERSION_TENTH" 91 | | "CURLOPT_CAINFO_BLOB" 92 | | "CURLOPT_PROXY_CAINFO_BLOB" 93 | | "CURL_VERSION_ALTSVC" 94 | | "CURL_VERSION_ZSTD" 95 | | "CURL_VERSION_UNICODE" 96 | | "CURL_VERSION_HSTS" 97 | | "CURL_VERSION_GSASL" 98 | | "CURLSSLOPT_AUTO_CLIENT_CERT" => return true, 99 | _ => {} 100 | } 101 | } 102 | if version < (7, 76) { 103 | match s { 104 | "CURLOPT_DOH_SSL_VERIFYHOST" => return true, 105 | "CURLOPT_DOH_SSL_VERIFYPEER" => return true, 106 | "CURLOPT_DOH_SSL_VERIFYSTATUS" => return true, 107 | _ => {} 108 | } 109 | } 110 | if version < (7, 75) { 111 | match s { 112 | "CURLAUTH_AWS_SIGV4" => return true, 113 | "CURLOPT_AWS_SIGV4" => return true, 114 | "CURLVERSION_NINTH" => return true, 115 | _ => {} 116 | } 117 | } 118 | if version < (7, 72) { 119 | match s { 120 | "CURLVERSION_EIGHTH" => return true, 121 | _ => {} 122 | } 123 | } 124 | if version < (7, 71) { 125 | match s { 126 | "CURLOPT_SSLCERT_BLOB" 127 | | "CURLOPT_SSLKEY_BLOB" 128 | | "CURLOPT_PROXY_ISSUERCERT_BLOB" 129 | | "CURLOPT_PROXY_ISSUERCERT" 130 | | "CURLOPT_PROXY_SSLCERT_BLOB" 131 | | "CURLOPT_PROXY_SSLKEY_BLOB" 132 | | "CURLOPT_ISSUERCERT_BLOB" 133 | | "CURLOPTTYPE_BLOB" 134 | | "CURL_BLOB_NOCOPY" 135 | | "CURL_BLOB_COPY" 136 | | "CURLSSLOPT_NATIVE_CA" => return true, 137 | _ => {} 138 | } 139 | } 140 | if version < (7, 70) { 141 | match s { 142 | "CURL_VERSION_HTTP3" 143 | | "CURL_VERSION_BROTLI" 144 | | "CURLVERSION_SEVENTH" 145 | | "CURLSSLOPT_REVOKE_BEST_EFFORT" => return true, 146 | _ => {} 147 | } 148 | } 149 | if version < (7, 68) { 150 | match s { 151 | "CURLSSLOPT_NO_PARTIALCHAIN" => return true, 152 | _ => {} 153 | } 154 | } 155 | if version < (7, 67) { 156 | match s { 157 | "CURLMOPT_MAX_CONCURRENT_STREAMS" => return true, 158 | _ => {} 159 | } 160 | } 161 | if version < (7, 66) { 162 | match s { 163 | "CURL_HTTP_VERSION_3" => return true, 164 | "CURLOPT_MAXAGE_CONN" => return true, 165 | _ => {} 166 | } 167 | } 168 | if version < (7, 65) { 169 | match s { 170 | "CURLVERSION_SIXTH" => return true, 171 | _ => {} 172 | } 173 | } 174 | if version < (7, 64) { 175 | match s { 176 | "CURLE_HTTP2" => return true, 177 | "CURLE_PEER_FAILED_VERIFICATION" => return true, 178 | "CURLE_NO_CONNECTION_AVAILABLE" => return true, 179 | "CURLE_SSL_PINNEDPUBKEYNOTMATCH" => return true, 180 | "CURLE_SSL_INVALIDCERTSTATUS" => return true, 181 | "CURLE_HTTP2_STREAM" => return true, 182 | "CURLE_RECURSIVE_API_CALL" => return true, 183 | "CURLOPT_HTTP09_ALLOWED" => return true, 184 | _ => {} 185 | } 186 | } 187 | if version < (7, 62) { 188 | match s { 189 | "CURLOPT_DOH_URL" => return true, 190 | "CURLOPT_UPLOAD_BUFFERSIZE" => return true, 191 | _ => {} 192 | } 193 | } 194 | if version < (7, 61) { 195 | match s { 196 | "CURLOPT_PIPEWAIT" => return true, 197 | "CURLE_PEER_FAILED_VERIFICATION" => return true, 198 | _ => {} 199 | } 200 | } 201 | if version < (7, 60) { 202 | match s { 203 | "CURLVERSION_FIFTH" => return true, 204 | _ => {} 205 | } 206 | } 207 | if version < (7, 54) { 208 | match s { 209 | "CURL_SSLVERSION_TLSv1_3" | "CURLOPT_PROXY_SSLCERT" | "CURLOPT_PROXY_SSLKEY" => { 210 | return true 211 | } 212 | _ => {} 213 | } 214 | } 215 | if version < (7, 52) { 216 | match s { 217 | "CURLOPT_PROXY_CAINFO" 218 | | "CURLOPT_PROXY_CAPATH" 219 | | "CURLOPT_PROXY_CRLFILE" 220 | | "CURLOPT_PROXY_KEYPASSWD" 221 | | "CURLOPT_PROXY_SSL_CIPHER_LIST" 222 | | "CURLOPT_PROXY_SSL_OPTIONS" 223 | | "CURLOPT_PROXY_SSL_VERIFYHOST" 224 | | "CURLOPT_PROXY_SSL_VERIFYPEER" 225 | | "CURLOPT_PROXY_SSLCERT" 226 | | "CURLOPT_PROXY_SSLCERTTYPE" 227 | | "CURLOPT_PROXY_SSLKEY" 228 | | "CURLOPT_PROXY_SSLKEYTYPE" 229 | | "CURLOPT_PROXY_SSLVERSION" 230 | | "CURL_VERSION_HTTPS_PROXY" => return true, 231 | _ => {} 232 | } 233 | } 234 | 235 | if version < (7, 49) { 236 | match s { 237 | "CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE" | "CURLOPT_CONNECT_TO" => return true, 238 | _ => {} 239 | } 240 | } 241 | if version < (7, 47) { 242 | if s.starts_with("CURL_HTTP_VERSION_2") { 243 | return true; 244 | } 245 | } 246 | if version < (7, 44) { 247 | match s { 248 | "CURLMOPT_PUSHDATA" | "CURLMOPT_PUSHFUNCTION" => return true, 249 | _ => {} 250 | } 251 | } 252 | if version < (7, 43) { 253 | if s.starts_with("CURLPIPE_") { 254 | return true; 255 | } 256 | } 257 | if version < (7, 25) { 258 | match s { 259 | "CURLSSLOPT_ALLOW_BEAST" => return true, 260 | _ => {} 261 | } 262 | } 263 | 264 | // OSX doesn't have this yet 265 | s == "CURLSSLOPT_NO_REVOKE" || 266 | 267 | // A lot of curl versions doesn't support unix sockets 268 | s == "CURLOPT_UNIX_SOCKET_PATH" || s == "CURL_VERSION_UNIX_SOCKETS" || s == 269 | "CURLOPT_ABSTRACT_UNIX_SOCKET" 270 | }); 271 | 272 | if cfg!(target_env = "msvc") { 273 | cfg.skip_fn_ptrcheck(|s| s.starts_with("curl_")); 274 | } 275 | 276 | cfg.generate("../curl-sys/lib.rs", "all.rs"); 277 | } 278 | -------------------------------------------------------------------------------- /systest/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(bad_style)] 2 | 3 | use curl_sys::*; 4 | use libc::*; 5 | 6 | include!(concat!(env!("OUT_DIR"), "/all.rs")); 7 | -------------------------------------------------------------------------------- /systest/version_detect.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | LIBCURL_VERSION_MAJOR LIBCURL_VERSION_MINOR 4 | -------------------------------------------------------------------------------- /tests/atexit.rs: -------------------------------------------------------------------------------- 1 | use curl::easy::Easy; 2 | 3 | pub extern "C" fn hook() { 4 | let mut easy = Easy::new(); 5 | easy.url("google.com").unwrap(); 6 | easy.write_function(|data| Ok(data.len())).unwrap(); 7 | easy.perform().unwrap(); 8 | } 9 | 10 | fn main() { 11 | curl::init(); 12 | hook(); 13 | unsafe { 14 | libc::atexit(hook); 15 | } 16 | println!("Finishing...") 17 | } 18 | -------------------------------------------------------------------------------- /tests/easy.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use std::io::Read; 3 | use std::rc::Rc; 4 | use std::str; 5 | use std::time::Duration; 6 | 7 | macro_rules! t { 8 | ($e:expr) => { 9 | match $e { 10 | Ok(e) => e, 11 | Err(e) => panic!("{} failed with {:?}", stringify!($e), e), 12 | } 13 | }; 14 | } 15 | 16 | use curl::easy::{Easy, Easy2, List, ReadError, Transfer, WriteError}; 17 | use curl::{Error, Version}; 18 | 19 | use crate::server::Server; 20 | mod server; 21 | 22 | fn handle() -> Easy { 23 | let mut e = Easy::new(); 24 | t!(e.timeout(Duration::new(20, 0))); 25 | e 26 | } 27 | 28 | fn sink(data: &[u8]) -> Result { 29 | Ok(data.len()) 30 | } 31 | 32 | #[test] 33 | fn get_smoke() { 34 | let s = Server::new(); 35 | s.receive( 36 | "\ 37 | GET / HTTP/1.1\r\n\ 38 | Host: 127.0.0.1:$PORT\r\n\ 39 | Accept: */*\r\n\ 40 | \r\n", 41 | ); 42 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 43 | 44 | let mut handle = handle(); 45 | t!(handle.url(&s.url("/"))); 46 | t!(handle.perform()); 47 | } 48 | 49 | #[test] 50 | fn download_zero_size() { 51 | let s = Server::new(); 52 | s.receive( 53 | "\ 54 | GET / HTTP/1.1\r\n\ 55 | Host: 127.0.0.1:$PORT\r\n\ 56 | Accept: */*\r\n\ 57 | \r\n", 58 | ); 59 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 60 | 61 | let mut handle = handle(); 62 | t!(handle.url(&s.url("/"))); 63 | t!(handle.perform()); 64 | assert_eq!(handle.download_size().unwrap(), 0_f64); 65 | } 66 | 67 | #[test] 68 | fn download_nonzero_size() { 69 | let s = Server::new(); 70 | s.receive( 71 | "\ 72 | GET / HTTP/1.1\r\n\ 73 | Host: 127.0.0.1:$PORT\r\n\ 74 | Accept: */*\r\n\ 75 | \r\n", 76 | ); 77 | s.send("HTTP/1.1 200 OK\r\n\r\nHello!"); 78 | 79 | let mut handle = handle(); 80 | t!(handle.url(&s.url("/"))); 81 | t!(handle.perform()); 82 | assert_eq!(handle.download_size().unwrap(), 6_f64); 83 | } 84 | 85 | #[test] 86 | fn upload_zero_size() { 87 | let s = Server::new(); 88 | s.receive( 89 | "\ 90 | GET / HTTP/1.1\r\n\ 91 | Host: 127.0.0.1:$PORT\r\n\ 92 | Accept: */*\r\n\ 93 | \r\n", 94 | ); 95 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 96 | 97 | let mut handle = handle(); 98 | t!(handle.url(&s.url("/"))); 99 | t!(handle.perform()); 100 | assert_eq!(handle.upload_size().unwrap(), 0_f64); 101 | } 102 | 103 | #[test] 104 | fn upload_nonzero_size() { 105 | let s = Server::new(); 106 | s.receive( 107 | "\ 108 | PUT / HTTP/1.1\r\n\ 109 | Host: 127.0.0.1:$PORT\r\n\ 110 | Accept: */*\r\n\ 111 | Content-Length: 5\r\n\ 112 | \r\n\ 113 | data\n", 114 | ); 115 | s.send( 116 | "\ 117 | HTTP/1.1 200 OK\r\n\ 118 | \r\n", 119 | ); 120 | 121 | let mut data = "data\n".as_bytes(); 122 | let mut list = List::new(); 123 | t!(list.append("Expect:")); 124 | let mut h = handle(); 125 | t!(h.url(&s.url("/"))); 126 | t!(h.put(true)); 127 | t!(h.in_filesize(5)); 128 | t!(h.upload(true)); 129 | t!(h.http_headers(list)); 130 | { 131 | let mut h = h.transfer(); 132 | t!(h.read_function(|buf| Ok(data.read(buf).unwrap()))); 133 | t!(h.perform()); 134 | } 135 | 136 | assert_eq!(h.upload_size().unwrap(), 5_f64); 137 | } 138 | 139 | #[test] 140 | fn get_path() { 141 | let s = Server::new(); 142 | s.receive( 143 | "\ 144 | GET /foo HTTP/1.1\r\n\ 145 | Host: 127.0.0.1:$PORT\r\n\ 146 | Accept: */*\r\n\ 147 | \r\n", 148 | ); 149 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 150 | 151 | let mut handle = handle(); 152 | t!(handle.url(&s.url("/foo"))); 153 | t!(handle.perform()); 154 | } 155 | 156 | #[test] 157 | fn write_callback() { 158 | let s = Server::new(); 159 | s.receive( 160 | "\ 161 | GET / HTTP/1.1\r\n\ 162 | Host: 127.0.0.1:$PORT\r\n\ 163 | Accept: */*\r\n\ 164 | \r\n", 165 | ); 166 | s.send("HTTP/1.1 200 OK\r\n\r\nhello!"); 167 | 168 | let mut all = Vec::::new(); 169 | { 170 | let mut handle = handle(); 171 | t!(handle.url(&s.url("/"))); 172 | let mut handle = handle.transfer(); 173 | t!(handle.write_function(|data| { 174 | all.extend(data); 175 | Ok(data.len()) 176 | })); 177 | t!(handle.perform()); 178 | } 179 | assert_eq!(all, b"hello!"); 180 | } 181 | 182 | #[test] 183 | fn resolve() { 184 | let s = Server::new(); 185 | s.receive( 186 | "\ 187 | GET / HTTP/1.1\r\n\ 188 | Host: example.com:$PORT\r\n\ 189 | Accept: */*\r\n\ 190 | \r\n", 191 | ); 192 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 193 | 194 | let mut list = List::new(); 195 | t!(list.append(&format!("example.com:{}:127.0.0.1", s.addr().port()))); 196 | let mut handle = handle(); 197 | t!(handle.url(&format!("http://example.com:{}/", s.addr().port()))); 198 | t!(handle.resolve(list)); 199 | t!(handle.perform()); 200 | } 201 | 202 | #[test] 203 | fn progress() { 204 | let s = Server::new(); 205 | s.receive( 206 | "\ 207 | GET /foo HTTP/1.1\r\n\ 208 | Host: 127.0.0.1:$PORT\r\n\ 209 | Accept: */*\r\n\ 210 | \r\n", 211 | ); 212 | s.send("HTTP/1.1 200 OK\r\n\r\nHello!"); 213 | 214 | let mut hits = 0; 215 | let mut dl = 0.0; 216 | { 217 | let mut handle = handle(); 218 | t!(handle.url(&s.url("/foo"))); 219 | t!(handle.progress(true)); 220 | t!(handle.write_function(sink)); 221 | 222 | let mut handle = handle.transfer(); 223 | t!(handle.progress_function(|_, a, _, _| { 224 | hits += 1; 225 | dl = a; 226 | true 227 | })); 228 | t!(handle.perform()); 229 | } 230 | assert!(hits > 0); 231 | assert_eq!(dl, 6.0); 232 | } 233 | 234 | #[test] 235 | fn headers() { 236 | let s = Server::new(); 237 | s.receive( 238 | "\ 239 | GET / HTTP/1.1\r\n\ 240 | Host: 127.0.0.1:$PORT\r\n\ 241 | Accept: */*\r\n\ 242 | \r\n", 243 | ); 244 | s.send( 245 | "\ 246 | HTTP/1.1 200 OK\r\n\ 247 | Foo: bar\r\n\ 248 | Bar: baz\r\n\ 249 | \r\n 250 | Hello!", 251 | ); 252 | 253 | let mut headers = Vec::new(); 254 | { 255 | let mut handle = handle(); 256 | t!(handle.url(&s.url("/"))); 257 | 258 | let mut handle = handle.transfer(); 259 | t!(handle.header_function(|h| { 260 | headers.push(str::from_utf8(h).unwrap().to_string()); 261 | true 262 | })); 263 | t!(handle.write_function(sink)); 264 | t!(handle.perform()); 265 | } 266 | assert_eq!( 267 | headers, 268 | vec![ 269 | "HTTP/1.1 200 OK\r\n".to_string(), 270 | "Foo: bar\r\n".to_string(), 271 | "Bar: baz\r\n".to_string(), 272 | "\r\n".to_string(), 273 | ] 274 | ); 275 | } 276 | 277 | #[test] 278 | fn fail_on_error() { 279 | let s = Server::new(); 280 | s.receive( 281 | "\ 282 | GET / HTTP/1.1\r\n\ 283 | Host: 127.0.0.1:$PORT\r\n\ 284 | Accept: */*\r\n\ 285 | \r\n", 286 | ); 287 | s.send( 288 | "\ 289 | HTTP/1.1 401 Not so good\r\n\ 290 | \r\n", 291 | ); 292 | 293 | let mut h = handle(); 294 | t!(h.url(&s.url("/"))); 295 | t!(h.fail_on_error(true)); 296 | assert!(h.perform().is_err()); 297 | 298 | let s = Server::new(); 299 | s.receive( 300 | "\ 301 | GET / HTTP/1.1\r\n\ 302 | Host: 127.0.0.1:$PORT\r\n\ 303 | Accept: */*\r\n\ 304 | \r\n", 305 | ); 306 | s.send( 307 | "\ 308 | HTTP/1.1 401 Not so good\r\n\ 309 | \r\n", 310 | ); 311 | 312 | let mut h = handle(); 313 | t!(h.url(&s.url("/"))); 314 | t!(h.fail_on_error(false)); 315 | t!(h.perform()); 316 | } 317 | 318 | #[test] 319 | fn port() { 320 | let s = Server::new(); 321 | s.receive( 322 | "\ 323 | GET / HTTP/1.1\r\n\ 324 | Host: localhost:$PORT\r\n\ 325 | Accept: */*\r\n\ 326 | \r\n", 327 | ); 328 | s.send( 329 | "\ 330 | HTTP/1.1 200 OK\r\n\ 331 | \r\n", 332 | ); 333 | 334 | let mut h = handle(); 335 | t!(h.url("http://localhost/")); 336 | t!(h.port(s.addr().port())); 337 | t!(h.perform()); 338 | } 339 | 340 | #[test] 341 | fn proxy() { 342 | let s = Server::new(); 343 | s.receive( 344 | "\ 345 | GET http://example.com/ HTTP/1.1\r\n\ 346 | Host: example.com\r\n\ 347 | Accept: */*\r\n\ 348 | \r\n", 349 | ); 350 | s.send( 351 | "\ 352 | HTTP/1.1 200 OK\r\n\ 353 | \r\n", 354 | ); 355 | 356 | let mut h = handle(); 357 | t!(h.url("http://example.com/")); 358 | t!(h.proxy(&s.url("/"))); 359 | t!(h.perform()); 360 | } 361 | 362 | #[test] 363 | #[ignore] // fails on newer curl versions? seems benign 364 | fn noproxy() { 365 | let s = Server::new(); 366 | s.receive( 367 | "\ 368 | GET / HTTP/1.1\r\n\ 369 | Host: 127.0.0.1:$PORT\r\n\ 370 | Accept: */*\r\n\ 371 | \r\n", 372 | ); 373 | s.send( 374 | "\ 375 | HTTP/1.1 200 OK\r\n\ 376 | \r\n", 377 | ); 378 | 379 | let mut h = handle(); 380 | t!(h.url(&s.url("/"))); 381 | t!(h.proxy(&s.url("/"))); 382 | t!(h.noproxy("127.0.0.1")); 383 | t!(h.perform()); 384 | } 385 | 386 | #[test] 387 | fn misc() { 388 | let mut h = handle(); 389 | t!(h.tcp_nodelay(true)); 390 | // t!(h.tcp_keepalive(true)); 391 | // t!(h.tcp_keepidle(Duration::new(3, 0))); 392 | // t!(h.tcp_keepintvl(Duration::new(3, 0))); 393 | t!(h.buffer_size(10)); 394 | 395 | if Version::get().version_num() >= 0x073e00 { 396 | // only available on curl 7.62.0 or later: 397 | t!(h.upload_buffer_size(10)); 398 | } 399 | 400 | t!(h.dns_cache_timeout(Duration::new(1, 0))); 401 | } 402 | 403 | #[test] 404 | fn dns_servers() { 405 | let mut h = handle(); 406 | // Tests are not using a libcurl with c-ares, so this 407 | // always fails. Test anyway to make sure it returns 408 | // an error instead of panicing. 409 | assert!(h.dns_servers("").is_err()); 410 | assert!(h.dns_servers("nonsense").is_err()); 411 | assert!(h.dns_servers("8.8.8.8,8.8.4.4").is_err()); 412 | } 413 | 414 | #[test] 415 | fn userpass() { 416 | let s = Server::new(); 417 | s.receive( 418 | "\ 419 | GET / HTTP/1.1\r\n\ 420 | Authorization: Basic YmFyOg==\r\n\ 421 | Host: 127.0.0.1:$PORT\r\n\ 422 | Accept: */*\r\n\ 423 | \r\n", 424 | ); 425 | s.send( 426 | "\ 427 | HTTP/1.1 200 OK\r\n\ 428 | \r\n", 429 | ); 430 | 431 | let mut h = handle(); 432 | t!(h.url(&s.url("/"))); 433 | t!(h.username("foo")); 434 | t!(h.username("bar")); 435 | t!(h.perform()); 436 | } 437 | 438 | #[test] 439 | fn accept_encoding() { 440 | let s = Server::new(); 441 | s.receive( 442 | "\ 443 | GET / HTTP/1.1\r\n\ 444 | Host: 127.0.0.1:$PORT\r\n\ 445 | Accept: */*\r\n\ 446 | Accept-Encoding: gzip\r\n\ 447 | \r\n", 448 | ); 449 | s.send( 450 | "\ 451 | HTTP/1.1 200 OK\r\n\ 452 | \r\n", 453 | ); 454 | 455 | let mut h = handle(); 456 | t!(h.url(&s.url("/"))); 457 | t!(h.accept_encoding("gzip")); 458 | t!(h.perform()); 459 | } 460 | 461 | #[test] 462 | fn follow_location() { 463 | let s1 = Server::new(); 464 | let s2 = Server::new(); 465 | s1.receive( 466 | "\ 467 | GET / HTTP/1.1\r\n\ 468 | Host: 127.0.0.1:$PORT\r\n\ 469 | Accept: */*\r\n\ 470 | \r\n", 471 | ); 472 | s1.send(&format!( 473 | "\ 474 | HTTP/1.1 301 Moved Permanently\r\n\ 475 | Location: http://{}/foo\r\n\ 476 | \r\n", 477 | s2.addr() 478 | )); 479 | 480 | s2.receive( 481 | "\ 482 | GET /foo HTTP/1.1\r\n\ 483 | Host: 127.0.0.1:$PORT\r\n\ 484 | Accept: */*\r\n\ 485 | \r\n", 486 | ); 487 | s2.send( 488 | "\ 489 | HTTP/1.1 200 OK\r\n\ 490 | \r\n", 491 | ); 492 | 493 | let mut h = handle(); 494 | t!(h.url(&s1.url("/"))); 495 | t!(h.follow_location(true)); 496 | t!(h.perform()); 497 | } 498 | 499 | #[test] 500 | fn put() { 501 | let s = Server::new(); 502 | s.receive( 503 | "\ 504 | PUT / HTTP/1.1\r\n\ 505 | Host: 127.0.0.1:$PORT\r\n\ 506 | Accept: */*\r\n\ 507 | Content-Length: 5\r\n\ 508 | \r\n\ 509 | data\n", 510 | ); 511 | s.send( 512 | "\ 513 | HTTP/1.1 200 OK\r\n\ 514 | \r\n", 515 | ); 516 | 517 | let mut data = "data\n".as_bytes(); 518 | let mut list = List::new(); 519 | t!(list.append("Expect:")); 520 | let mut h = handle(); 521 | t!(h.url(&s.url("/"))); 522 | t!(h.put(true)); 523 | t!(h.in_filesize(5)); 524 | t!(h.upload(true)); 525 | t!(h.http_headers(list)); 526 | let mut h = h.transfer(); 527 | t!(h.read_function(|buf| Ok(data.read(buf).unwrap()))); 528 | t!(h.perform()); 529 | } 530 | 531 | #[test] 532 | fn post1() { 533 | let s = Server::new(); 534 | s.receive( 535 | "\ 536 | POST / HTTP/1.1\r\n\ 537 | Host: 127.0.0.1:$PORT\r\n\ 538 | Accept: */*\r\n\ 539 | Content-Length: 5\r\n\ 540 | Content-Type: application/x-www-form-urlencoded\r\n\ 541 | \r\n\ 542 | data\n", 543 | ); 544 | s.send( 545 | "\ 546 | HTTP/1.1 200 OK\r\n\ 547 | \r\n", 548 | ); 549 | 550 | let mut h = handle(); 551 | t!(h.url(&s.url("/"))); 552 | t!(h.post(true)); 553 | t!(h.post_fields_copy(b"data\n")); 554 | t!(h.perform()); 555 | } 556 | 557 | #[test] 558 | fn post2() { 559 | let s = Server::new(); 560 | s.receive( 561 | "\ 562 | POST / HTTP/1.1\r\n\ 563 | Host: 127.0.0.1:$PORT\r\n\ 564 | Accept: */*\r\n\ 565 | Content-Length: 5\r\n\ 566 | Content-Type: application/x-www-form-urlencoded\r\n\ 567 | \r\n\ 568 | data\n", 569 | ); 570 | s.send( 571 | "\ 572 | HTTP/1.1 200 OK\r\n\ 573 | \r\n", 574 | ); 575 | 576 | let mut h = handle(); 577 | t!(h.url(&s.url("/"))); 578 | t!(h.post(true)); 579 | t!(h.post_fields_copy(b"data\n")); 580 | t!(h.write_function(sink)); 581 | t!(h.perform()); 582 | } 583 | 584 | #[test] 585 | fn post3() { 586 | let s = Server::new(); 587 | s.receive( 588 | "\ 589 | POST / HTTP/1.1\r\n\ 590 | Host: 127.0.0.1:$PORT\r\n\ 591 | Accept: */*\r\n\ 592 | Content-Length: 5\r\n\ 593 | Content-Type: application/x-www-form-urlencoded\r\n\ 594 | \r\n\ 595 | data\n", 596 | ); 597 | s.send( 598 | "\ 599 | HTTP/1.1 200 OK\r\n\ 600 | \r\n", 601 | ); 602 | 603 | let mut data = "data\n".as_bytes(); 604 | let mut h = handle(); 605 | t!(h.url(&s.url("/"))); 606 | t!(h.post(true)); 607 | t!(h.post_field_size(5)); 608 | let mut h = h.transfer(); 609 | t!(h.read_function(|buf| Ok(data.read(buf).unwrap()))); 610 | t!(h.perform()); 611 | } 612 | 613 | #[test] 614 | fn referer() { 615 | let s = Server::new(); 616 | s.receive( 617 | "\ 618 | GET / HTTP/1.1\r\n\ 619 | Host: 127.0.0.1:$PORT\r\n\ 620 | Accept: */*\r\n\ 621 | Referer: foo\r\n\ 622 | \r\n", 623 | ); 624 | s.send( 625 | "\ 626 | HTTP/1.1 200 OK\r\n\ 627 | \r\n", 628 | ); 629 | 630 | let mut h = handle(); 631 | t!(h.url(&s.url("/"))); 632 | t!(h.referer("foo")); 633 | t!(h.perform()); 634 | } 635 | 636 | #[test] 637 | fn useragent() { 638 | let s = Server::new(); 639 | s.receive( 640 | "\ 641 | GET / HTTP/1.1\r\n\ 642 | User-Agent: foo\r\n\ 643 | Host: 127.0.0.1:$PORT\r\n\ 644 | Accept: */*\r\n\ 645 | \r\n", 646 | ); 647 | s.send( 648 | "\ 649 | HTTP/1.1 200 OK\r\n\ 650 | \r\n", 651 | ); 652 | 653 | let mut h = handle(); 654 | t!(h.url(&s.url("/"))); 655 | t!(h.useragent("foo")); 656 | t!(h.perform()); 657 | } 658 | 659 | #[test] 660 | fn custom_headers() { 661 | let s = Server::new(); 662 | s.receive( 663 | "\ 664 | GET / HTTP/1.1\r\n\ 665 | Host: 127.0.0.1:$PORT\r\n\ 666 | Foo: bar\r\n\ 667 | \r\n", 668 | ); 669 | s.send( 670 | "\ 671 | HTTP/1.1 200 OK\r\n\ 672 | \r\n", 673 | ); 674 | 675 | let mut custom = List::new(); 676 | t!(custom.append("Foo: bar")); 677 | t!(custom.append("Accept:")); 678 | let mut h = handle(); 679 | t!(h.url(&s.url("/"))); 680 | t!(h.http_headers(custom)); 681 | t!(h.perform()); 682 | } 683 | 684 | #[test] 685 | fn cookie() { 686 | let s = Server::new(); 687 | s.receive( 688 | "\ 689 | GET / HTTP/1.1\r\n\ 690 | Host: 127.0.0.1:$PORT\r\n\ 691 | Accept: */*\r\n\ 692 | Cookie: foo\r\n\ 693 | \r\n", 694 | ); 695 | s.send( 696 | "\ 697 | HTTP/1.1 200 OK\r\n\ 698 | \r\n", 699 | ); 700 | 701 | let mut h = handle(); 702 | t!(h.url(&s.url("/"))); 703 | t!(h.cookie("foo")); 704 | t!(h.perform()); 705 | } 706 | 707 | #[test] 708 | fn url_encoding() { 709 | let mut h = handle(); 710 | assert_eq!(h.url_encode(b"foo"), "foo"); 711 | assert_eq!(h.url_encode(b"foo bar"), "foo%20bar"); 712 | assert_eq!(h.url_encode(b"foo bar\xff"), "foo%20bar%FF"); 713 | assert_eq!(h.url_encode(b""), ""); 714 | assert_eq!(h.url_decode("foo"), b"foo"); 715 | assert_eq!(h.url_decode("foo%20bar"), b"foo bar"); 716 | assert_eq!(h.url_decode("foo%2"), b"foo%2"); 717 | assert_eq!(h.url_decode("foo%xx"), b"foo%xx"); 718 | assert_eq!(h.url_decode("foo%ff"), b"foo\xff"); 719 | assert_eq!(h.url_decode(""), b""); 720 | } 721 | 722 | #[test] 723 | fn getters() { 724 | let s = Server::new(); 725 | s.receive( 726 | "\ 727 | GET / HTTP/1.1\r\n\ 728 | Host: 127.0.0.1:$PORT\r\n\ 729 | Accept: */*\r\n\ 730 | \r\n", 731 | ); 732 | s.send( 733 | "\ 734 | HTTP/1.1 200 OK\r\n\ 735 | \r\n", 736 | ); 737 | 738 | let mut h = handle(); 739 | t!(h.url(&s.url("/"))); 740 | t!(h.cookie_file("/dev/null")); 741 | t!(h.perform()); 742 | assert_eq!(t!(h.response_code()), 200); 743 | assert_eq!(t!(h.redirect_count()), 0); 744 | assert_eq!(t!(h.redirect_url()), None); 745 | assert_eq!(t!(h.content_type()), None); 746 | 747 | let addr = format!("http://{}/", s.addr()); 748 | assert_eq!(t!(h.effective_url()), Some(&addr[..])); 749 | 750 | // TODO: test this 751 | // let cookies = t!(h.cookies()).iter() 752 | // .map(|s| s.to_vec()) 753 | // .collect::>(); 754 | // assert_eq!(cookies.len(), 1); 755 | } 756 | 757 | #[test] 758 | #[should_panic] 759 | fn panic_in_callback() { 760 | let s = Server::new(); 761 | s.receive( 762 | "\ 763 | GET / HTTP/1.1\r\n\ 764 | Host: 127.0.0.1:$PORT\r\n\ 765 | Accept: */*\r\n\ 766 | \r\n", 767 | ); 768 | s.send( 769 | "\ 770 | HTTP/1.1 200 OK\r\n\ 771 | \r\n", 772 | ); 773 | 774 | let mut h = handle(); 775 | t!(h.url(&s.url("/"))); 776 | t!(h.header_function(|_| panic!())); 777 | t!(h.perform()); 778 | } 779 | 780 | #[test] 781 | fn abort_read() { 782 | let s = Server::new(); 783 | // 8.7.0 seems to have changed the behavior that curl will call the read 784 | // function before sending headers (I'm guessing so that it batches 785 | // everything up into a single write). 786 | if Version::get().version_num() < 0x080700 { 787 | s.receive( 788 | "\ 789 | PUT / HTTP/1.1\r\n\ 790 | Host: 127.0.0.1:$PORT\r\n\ 791 | Accept: */*\r\n\ 792 | Content-Length: 2\r\n\ 793 | \r\n", 794 | ); 795 | s.send( 796 | "\ 797 | HTTP/1.1 200 OK\r\n\ 798 | \r\n", 799 | ); 800 | } else { 801 | s.receive(""); 802 | } 803 | 804 | let mut h = handle(); 805 | t!(h.url(&s.url("/"))); 806 | t!(h.read_function(|_| Err(ReadError::Abort))); 807 | t!(h.put(true)); 808 | t!(h.in_filesize(2)); 809 | let mut list = List::new(); 810 | t!(list.append("Expect:")); 811 | t!(h.http_headers(list)); 812 | let err = h.perform().unwrap_err(); 813 | assert!(err.is_aborted_by_callback()); 814 | } 815 | 816 | #[test] 817 | fn pause_write_then_resume() { 818 | let s = Server::new(); 819 | s.receive( 820 | "\ 821 | GET / HTTP/1.1\r\n\ 822 | Host: 127.0.0.1:$PORT\r\n\ 823 | Accept: */*\r\n\ 824 | \r\n", 825 | ); 826 | s.send( 827 | "\ 828 | HTTP/1.1 200 OK\r\n\ 829 | \r\n 830 | a\n 831 | b", 832 | ); 833 | 834 | let mut h = handle(); 835 | t!(h.url(&s.url("/"))); 836 | t!(h.progress(true)); 837 | 838 | struct State<'a, 'b> { 839 | paused: Cell, 840 | unpaused: Cell, 841 | transfer: RefCell>, 842 | } 843 | 844 | let h = Rc::new(State { 845 | paused: Cell::new(false), 846 | unpaused: Cell::new(false), 847 | transfer: RefCell::new(h.transfer()), 848 | }); 849 | 850 | let h2 = h.clone(); 851 | t!(h.transfer 852 | .borrow_mut() 853 | .write_function(move |data| if h2.unpaused.get() { 854 | h2.unpaused.set(false); 855 | Ok(data.len()) 856 | } else { 857 | h2.paused.set(true); 858 | Err(WriteError::Pause) 859 | })); 860 | let h2 = h.clone(); 861 | t!(h.transfer 862 | .borrow_mut() 863 | .progress_function(move |_, _, _, _| { 864 | if h2.paused.get() { 865 | h2.paused.set(false); 866 | h2.unpaused.set(true); 867 | t!(h2.transfer.borrow().unpause_write()); 868 | } 869 | true 870 | })); 871 | t!(h.transfer.borrow().perform()); 872 | } 873 | 874 | #[test] 875 | fn perform_in_perform_is_bad() { 876 | let s = Server::new(); 877 | s.receive( 878 | "\ 879 | GET / HTTP/1.1\r\n\ 880 | Host: 127.0.0.1:$PORT\r\n\ 881 | Accept: */*\r\n\ 882 | \r\n", 883 | ); 884 | s.send( 885 | "\ 886 | HTTP/1.1 200 OK\r\n\ 887 | \r\n 888 | a\n 889 | b", 890 | ); 891 | 892 | let mut h = handle(); 893 | t!(h.url(&s.url("/"))); 894 | t!(h.progress(true)); 895 | 896 | let h = Rc::new(RefCell::new(h.transfer())); 897 | 898 | let h2 = h.clone(); 899 | t!(h.borrow_mut().write_function(move |data| { 900 | assert!(h2.borrow().perform().is_err()); 901 | Ok(data.len()) 902 | })); 903 | t!(h.borrow().perform()); 904 | } 905 | 906 | #[cfg(not(windows))] 907 | #[test] 908 | fn check_unix_socket() { 909 | let s = Server::new_unix(); 910 | s.receive( 911 | "\ 912 | POST / HTTP/1.1\r\n\ 913 | Host: localhost\r\n\ 914 | Accept: */*\r\n\ 915 | Content-Length: 5\r\n\ 916 | Content-Type: application/x-www-form-urlencoded\r\n\ 917 | \r\n\ 918 | data\n", 919 | ); 920 | s.send( 921 | "\ 922 | HTTP/1.1 200 OK\r\n\ 923 | \r\n", 924 | ); 925 | 926 | let mut h = handle(); 927 | t!(h.unix_socket(s.path())); 928 | t!(h.url(&s.url("/"))); 929 | t!(h.post(true)); 930 | t!(h.post_fields_copy(b"data\n")); 931 | t!(h.perform()); 932 | } 933 | 934 | #[cfg(feature = "upkeep_7_62_0")] 935 | #[test] 936 | fn test_upkeep() { 937 | let s = Server::new(); 938 | s.receive( 939 | "\ 940 | GET / HTTP/1.1\r\n\ 941 | Host: 127.0.0.1:$PORT\r\n\ 942 | Accept: */*\r\n\ 943 | \r\n", 944 | ); 945 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 946 | 947 | let mut handle = handle(); 948 | t!(handle.url(&s.url("/"))); 949 | t!(handle.perform()); 950 | 951 | // Ensure that upkeep can be called on the handle without problem. 952 | t!(handle.upkeep()); 953 | } 954 | 955 | #[test] 956 | fn path_as_is() { 957 | let s = Server::new(); 958 | s.receive( 959 | "\ 960 | GET /test/../ HTTP/1.1\r\n\ 961 | Host: 127.0.0.1:$PORT\r\n\ 962 | Accept: */*\r\n\ 963 | \r\n", 964 | ); 965 | s.send( 966 | "\ 967 | HTTP/1.1 200 OK\r\n\ 968 | \r\n", 969 | ); 970 | 971 | let mut h = handle(); 972 | t!(h.url(&s.url("/test/../"))); 973 | t!(h.path_as_is(true)); 974 | t!(h.perform()); 975 | 976 | let addr = format!("http://{}/test/../", s.addr()); 977 | assert_eq!(t!(h.response_code()), 200); 978 | assert_eq!(t!(h.effective_url()), Some(&addr[..])); 979 | } 980 | 981 | #[test] 982 | fn test_connect_timeout() { 983 | use curl::easy::Handler; 984 | struct Collector(Vec); 985 | 986 | impl Handler for Collector { 987 | fn write(&mut self, data: &[u8]) -> Result { 988 | self.0.extend_from_slice(data); 989 | Ok(data.len()) 990 | } 991 | } 992 | let mut easy2 = Easy2::new(Collector(Vec::new())); 993 | 994 | // Overflow value test must return an Error 995 | assert_eq!( 996 | Error::new(curl_sys::CURLE_BAD_FUNCTION_ARGUMENT), 997 | easy2 998 | .connect_timeout(Duration::from_secs(std::u64::MAX)) 999 | .unwrap_err() 1000 | ); 1001 | 1002 | // Valid value 1003 | assert_eq!( 1004 | (), 1005 | easy2 1006 | .connect_timeout(Duration::from_millis(i32::MAX as u64)) 1007 | .unwrap() 1008 | ); 1009 | } 1010 | 1011 | #[test] 1012 | fn test_timeout() { 1013 | use curl::easy::Handler; 1014 | struct Collector(Vec); 1015 | 1016 | impl Handler for Collector { 1017 | fn write(&mut self, data: &[u8]) -> Result { 1018 | self.0.extend_from_slice(data); 1019 | Ok(data.len()) 1020 | } 1021 | } 1022 | let mut easy2 = Easy2::new(Collector(Vec::new())); 1023 | 1024 | // Overflow value test must return an Error 1025 | assert_eq!( 1026 | Error::new(curl_sys::CURLE_BAD_FUNCTION_ARGUMENT), 1027 | easy2 1028 | .timeout(Duration::from_secs(std::u64::MAX)) 1029 | .unwrap_err() 1030 | ); 1031 | 1032 | // Valid value 1033 | assert_eq!( 1034 | (), 1035 | easy2 1036 | .timeout(Duration::from_millis(i32::MAX as u64)) 1037 | .unwrap() 1038 | ); 1039 | } 1040 | -------------------------------------------------------------------------------- /tests/formdata: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /tests/multi.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | use std::collections::HashMap; 4 | use std::io::{Cursor, Read}; 5 | use std::time::Duration; 6 | 7 | use curl::easy::{Easy, List}; 8 | use curl::multi::Multi; 9 | 10 | macro_rules! t { 11 | ($e:expr) => { 12 | match $e { 13 | Ok(e) => e, 14 | Err(e) => panic!("{} failed with {:?}", stringify!($e), e), 15 | } 16 | }; 17 | } 18 | 19 | use crate::server::Server; 20 | mod server; 21 | 22 | #[test] 23 | fn smoke() { 24 | let m = Multi::new(); 25 | let mut e = Easy::new(); 26 | 27 | let s = Server::new(); 28 | s.receive( 29 | "\ 30 | GET / HTTP/1.1\r\n\ 31 | Host: 127.0.0.1:$PORT\r\n\ 32 | Accept: */*\r\n\ 33 | \r\n", 34 | ); 35 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 36 | 37 | t!(e.url(&s.url("/"))); 38 | let _e = t!(m.add(e)); 39 | while t!(m.perform()) > 0 { 40 | t!(m.wait(&mut [], Duration::from_secs(1))); 41 | } 42 | } 43 | 44 | #[test] 45 | fn smoke2() { 46 | let m = Multi::new(); 47 | 48 | let s1 = Server::new(); 49 | s1.receive( 50 | "\ 51 | GET / HTTP/1.1\r\n\ 52 | Host: 127.0.0.1:$PORT\r\n\ 53 | Accept: */*\r\n\ 54 | \r\n", 55 | ); 56 | s1.send("HTTP/1.1 200 OK\r\n\r\n"); 57 | 58 | let s2 = Server::new(); 59 | s2.receive( 60 | "\ 61 | GET / HTTP/1.1\r\n\ 62 | Host: 127.0.0.1:$PORT\r\n\ 63 | Accept: */*\r\n\ 64 | \r\n", 65 | ); 66 | s2.send("HTTP/1.1 200 OK\r\n\r\n"); 67 | 68 | let mut e1 = Easy::new(); 69 | t!(e1.url(&s1.url("/"))); 70 | let _e1 = t!(m.add(e1)); 71 | let mut e2 = Easy::new(); 72 | t!(e2.url(&s2.url("/"))); 73 | let _e2 = t!(m.add(e2)); 74 | 75 | while t!(m.perform()) > 0 { 76 | t!(m.wait(&mut [], Duration::from_secs(1))); 77 | } 78 | 79 | let mut done = 0; 80 | m.messages(|msg| { 81 | msg.result().unwrap().unwrap(); 82 | done += 1; 83 | }); 84 | assert_eq!(done, 2); 85 | } 86 | 87 | #[test] 88 | fn upload_lots() { 89 | use curl::multi::{Events, Socket, SocketEvents}; 90 | 91 | #[derive(Debug)] 92 | enum Message { 93 | Timeout(Option), 94 | Wait(Socket, SocketEvents, usize), 95 | } 96 | 97 | let mut m = Multi::new(); 98 | let poll = t!(mio::Poll::new()); 99 | let (tx, rx) = mio_extras::channel::channel(); 100 | let tx2 = tx.clone(); 101 | t!(m.socket_function(move |socket, events, token| { 102 | t!(tx2.send(Message::Wait(socket, events, token))); 103 | })); 104 | t!(m.timer_function(move |dur| { 105 | t!(tx.send(Message::Timeout(dur))); 106 | true 107 | })); 108 | 109 | let s = Server::new(); 110 | s.receive(&format!( 111 | "\ 112 | PUT / HTTP/1.1\r\n\ 113 | Host: 127.0.0.1:$PORT\r\n\ 114 | Accept: */*\r\n\ 115 | Content-Length: 131072\r\n\ 116 | \r\n\ 117 | {}\n", 118 | vec!["a"; 128 * 1024 - 1].join("") 119 | )); 120 | s.send( 121 | "\ 122 | HTTP/1.1 200 OK\r\n\ 123 | \r\n", 124 | ); 125 | 126 | let mut data = vec![b'a'; 128 * 1024 - 1]; 127 | data.push(b'\n'); 128 | let mut data = Cursor::new(data); 129 | let mut list = List::new(); 130 | t!(list.append("Expect:")); 131 | let mut h = Easy::new(); 132 | t!(h.url(&s.url("/"))); 133 | t!(h.put(true)); 134 | t!(h.read_function(move |buf| Ok(data.read(buf).unwrap()))); 135 | t!(h.in_filesize(128 * 1024)); 136 | t!(h.upload(true)); 137 | t!(h.http_headers(list)); 138 | 139 | t!(poll.register(&rx, mio::Token(0), mio::Ready::all(), mio::PollOpt::level())); 140 | 141 | let e = t!(m.add(h)); 142 | 143 | let mut next_token = 1; 144 | let mut token_map = HashMap::new(); 145 | let mut cur_timeout = None; 146 | let mut events = mio::Events::with_capacity(128); 147 | let mut running = true; 148 | 149 | while running { 150 | let n = t!(poll.poll(&mut events, cur_timeout)); 151 | 152 | if n == 0 && t!(m.timeout()) == 0 { 153 | running = false; 154 | } 155 | 156 | for event in events.iter() { 157 | while event.token() == mio::Token(0) { 158 | match rx.try_recv() { 159 | Ok(Message::Timeout(dur)) => cur_timeout = dur, 160 | Ok(Message::Wait(socket, events, token)) => { 161 | let evented = mio::unix::EventedFd(&socket); 162 | if events.remove() { 163 | token_map.remove(&token).unwrap(); 164 | } else { 165 | let mut e = mio::Ready::empty(); 166 | if events.input() { 167 | e |= mio::Ready::readable(); 168 | } 169 | if events.output() { 170 | e |= mio::Ready::writable(); 171 | } 172 | if token == 0 { 173 | let token = next_token; 174 | next_token += 1; 175 | t!(m.assign(socket, token)); 176 | token_map.insert(token, socket); 177 | t!(poll.register( 178 | &evented, 179 | mio::Token(token), 180 | e, 181 | mio::PollOpt::level() 182 | )); 183 | } else { 184 | t!(poll.reregister( 185 | &evented, 186 | mio::Token(token), 187 | e, 188 | mio::PollOpt::level() 189 | )); 190 | } 191 | } 192 | } 193 | Err(_) => break, 194 | } 195 | } 196 | 197 | if event.token() == mio::Token(0) { 198 | continue; 199 | } 200 | 201 | let token = event.token(); 202 | let socket = token_map[&token.into()]; 203 | let mut e = Events::new(); 204 | if event.readiness().is_readable() { 205 | e.input(true); 206 | } 207 | if event.readiness().is_writable() { 208 | e.output(true); 209 | } 210 | if mio::unix::UnixReady::from(event.readiness()).is_error() { 211 | e.error(true); 212 | } 213 | let remaining = t!(m.action(socket, &e)); 214 | if remaining == 0 { 215 | running = false; 216 | } 217 | } 218 | } 219 | 220 | let mut done = 0; 221 | m.messages(|m| { 222 | m.result().unwrap().unwrap(); 223 | done += 1; 224 | }); 225 | assert_eq!(done, 1); 226 | 227 | let mut e = t!(m.remove(e)); 228 | assert_eq!(t!(e.response_code()), 200); 229 | } 230 | 231 | // Tests passing raw file descriptors to Multi::wait. The test is limited to Linux only as the 232 | // semantics of the underlying poll(2) system call used by curl apparently differ on other 233 | // platforms, making the test fail. 234 | #[cfg(target_os = "linux")] 235 | #[test] 236 | fn waitfds() { 237 | use curl::multi::WaitFd; 238 | use std::fs::File; 239 | use std::os::unix::io::AsRawFd; 240 | 241 | let filenames = ["/dev/null", "/dev/zero", "/dev/urandom"]; 242 | let files: Vec = filenames 243 | .iter() 244 | .map(|filename| File::open(filename).unwrap()) 245 | .collect(); 246 | let mut waitfds: Vec = files 247 | .iter() 248 | .map(|f| { 249 | let mut waitfd = WaitFd::new(); 250 | waitfd.set_fd(f.as_raw_fd()); 251 | waitfd.poll_on_read(true); 252 | waitfd 253 | }) 254 | .collect(); 255 | 256 | let m = Multi::new(); 257 | let events = t!(m.wait(&mut waitfds, Duration::from_secs(1))); 258 | assert_eq!(events, 3); 259 | for waitfd in waitfds { 260 | assert!(waitfd.received_read()); 261 | } 262 | } 263 | 264 | // Tests passing raw file descriptors to Multi::wait. The test is limited to Linux only as the 265 | // semantics of the underlying poll(2) system call used by curl apparently differ on other 266 | // platforms, making the test fail. 267 | #[cfg(feature = "poll_7_68_0")] 268 | #[cfg(target_os = "linux")] 269 | #[test] 270 | fn pollfds() { 271 | use curl::multi::WaitFd; 272 | use std::fs::File; 273 | use std::os::unix::io::AsRawFd; 274 | 275 | let filenames = ["/dev/null", "/dev/zero", "/dev/urandom"]; 276 | let files: Vec = filenames 277 | .iter() 278 | .map(|filename| File::open(filename).unwrap()) 279 | .collect(); 280 | let mut waitfds: Vec = files 281 | .iter() 282 | .map(|f| { 283 | let mut waitfd = WaitFd::new(); 284 | waitfd.set_fd(f.as_raw_fd()); 285 | waitfd.poll_on_read(true); 286 | waitfd 287 | }) 288 | .collect(); 289 | 290 | let m = Multi::new(); 291 | let events = t!(m.poll(&mut waitfds, Duration::from_secs(1))); 292 | assert_eq!(events, 3); 293 | for waitfd in waitfds { 294 | assert!(waitfd.received_read()); 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /tests/post.rs: -------------------------------------------------------------------------------- 1 | use curl::Version; 2 | use std::time::Duration; 3 | 4 | macro_rules! t { 5 | ($e:expr) => { 6 | match $e { 7 | Ok(e) => e, 8 | Err(e) => panic!("{} failed with {:?}", stringify!($e), e), 9 | } 10 | }; 11 | } 12 | 13 | use curl::easy::{Easy, Form, List}; 14 | 15 | use crate::server::Server; 16 | mod server; 17 | 18 | fn handle() -> Easy { 19 | let mut e = Easy::new(); 20 | t!(e.timeout(Duration::new(20, 0))); 21 | let mut list = List::new(); 22 | t!(list.append("Expect:")); 23 | t!(e.http_headers(list)); 24 | e 25 | } 26 | 27 | fn multipart_boundary_size() -> usize { 28 | // Versions before 8.4.0 used a smaller multipart mime boundary, so the 29 | // exact content-length will differ between versions. 30 | if Version::get().version_num() >= 0x80400 { 31 | 148 32 | } else { 33 | 136 34 | } 35 | } 36 | 37 | #[test] 38 | fn custom() { 39 | multipart_boundary_size(); 40 | let s = Server::new(); 41 | s.receive(&format!( 42 | "\ 43 | POST / HTTP/1.1\r\n\ 44 | Host: 127.0.0.1:$PORT\r\n\ 45 | Accept: */*\r\n\ 46 | Content-Length: {}\r\n\ 47 | Content-Type: multipart/form-data; boundary=--[..]\r\n\ 48 | \r\n\ 49 | --[..]\r\n\ 50 | Content-Disposition: form-data; name=\"foo\"\r\n\ 51 | \r\n\ 52 | 1234\r\n\ 53 | --[..]\r\n", 54 | multipart_boundary_size() + 6 55 | )); 56 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 57 | 58 | let mut handle = handle(); 59 | let mut form = Form::new(); 60 | t!(form.part("foo").contents(b"1234").add()); 61 | t!(handle.url(&s.url("/"))); 62 | t!(handle.httppost(form)); 63 | t!(handle.perform()); 64 | } 65 | 66 | #[cfg(feature = "static-curl")] 67 | #[test] 68 | fn buffer() { 69 | let s = Server::new(); 70 | s.receive(&format!( 71 | "\ 72 | POST / HTTP/1.1\r\n\ 73 | Host: 127.0.0.1:$PORT\r\n\ 74 | Accept: */*\r\n\ 75 | Content-Length: {}\r\n\ 76 | Content-Type: multipart/form-data; boundary=--[..]\r\n\ 77 | \r\n\ 78 | --[..]\r\n\ 79 | Content-Disposition: form-data; name=\"foo\"; filename=\"bar\"\r\n\ 80 | Content-Type: foo/bar\r\n\ 81 | \r\n\ 82 | 1234\r\n\ 83 | --[..]\r\n", 84 | multipart_boundary_size() + 45 85 | )); 86 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 87 | 88 | let mut handle = handle(); 89 | let mut form = Form::new(); 90 | t!(form 91 | .part("foo") 92 | .buffer("bar", b"1234".to_vec()) 93 | .content_type("foo/bar") 94 | .add()); 95 | t!(handle.url(&s.url("/"))); 96 | t!(handle.httppost(form)); 97 | t!(handle.perform()); 98 | } 99 | 100 | #[cfg(feature = "static-curl")] 101 | #[test] 102 | fn file() { 103 | let s = Server::new(); 104 | let formdata = include_str!("formdata"); 105 | s.receive( 106 | format!( 107 | "\ 108 | POST / HTTP/1.1\r\n\ 109 | Host: 127.0.0.1:$PORT\r\n\ 110 | Accept: */*\r\n\ 111 | Content-Length: {}\r\n\ 112 | Content-Type: multipart/form-data; boundary=--[..]\r\n\ 113 | \r\n\ 114 | --[..]\r\n\ 115 | Content-Disposition: form-data; name=\"foo\"; filename=\"formdata\"\r\n\ 116 | Content-Type: application/octet-stream\r\n\ 117 | \r\n\ 118 | {}\ 119 | \r\n\ 120 | --[..]\r\n", 121 | multipart_boundary_size() + 63 + formdata.len(), 122 | formdata 123 | ) 124 | .as_str(), 125 | ); 126 | s.send("HTTP/1.1 200 OK\r\n\r\n"); 127 | 128 | let mut handle = handle(); 129 | let mut form = Form::new(); 130 | t!(form.part("foo").file("tests/formdata").add()); 131 | t!(handle.url(&s.url("/"))); 132 | t!(handle.httppost(form)); 133 | t!(handle.perform()); 134 | } 135 | -------------------------------------------------------------------------------- /tests/protocols.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature = "static-curl", not(feature = "protocol-ftp")))] 2 | #[test] 3 | fn static_with_ftp_disabled() { 4 | assert!(curl::Version::get() 5 | .protocols() 6 | .filter(|&p| p == "ftp") 7 | .next() 8 | .is_none()); 9 | } 10 | 11 | #[cfg(all(feature = "static-curl", feature = "protocol-ftp"))] 12 | #[test] 13 | fn static_with_ftp_enabled() { 14 | assert!(curl::Version::get() 15 | .protocols() 16 | .filter(|&p| p == "ftp") 17 | .next() 18 | .is_some()); 19 | } 20 | -------------------------------------------------------------------------------- /tests/server/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::collections::HashSet; 4 | use std::io::prelude::*; 5 | use std::io::BufReader; 6 | use std::net::{SocketAddr, TcpListener, TcpStream}; 7 | use std::path::PathBuf; 8 | use std::sync::mpsc::{channel, Receiver, Sender}; 9 | use std::thread; 10 | 11 | pub struct Server { 12 | messages: Option>, 13 | addr: Addr, 14 | thread: Option>, 15 | } 16 | 17 | enum Message { 18 | Read(String), 19 | Write(String), 20 | } 21 | 22 | enum Addr { 23 | Tcp(SocketAddr), 24 | Unix(PathBuf), 25 | } 26 | 27 | fn run(stream: impl Read + Write, rx: &Receiver) { 28 | let mut socket = BufReader::new(stream); 29 | for msg in rx.iter() { 30 | match msg { 31 | Message::Read(ref expected) => { 32 | let mut expected = &expected[..]; 33 | let mut expected_headers = HashSet::new(); 34 | while let Some(i) = expected.find('\n') { 35 | let line = &expected[..i + 1]; 36 | expected = &expected[i + 1..]; 37 | expected_headers.insert(line); 38 | if line == "\r\n" { 39 | break; 40 | } 41 | } 42 | 43 | let mut expected_len = None; 44 | while !expected_headers.is_empty() { 45 | let mut actual = String::new(); 46 | t!(socket.read_line(&mut actual)); 47 | if actual.starts_with("Content-Length") { 48 | let len = actual.split(": ").nth(1).unwrap(); 49 | expected_len = len.trim().parse().ok(); 50 | } 51 | // various versions of libcurl do different things here 52 | if actual == "Proxy-Connection: Keep-Alive\r\n" { 53 | continue; 54 | } 55 | if expected_headers.remove(&actual[..]) { 56 | continue; 57 | } 58 | 59 | let mut found = None; 60 | for header in expected_headers.iter() { 61 | if lines_match(header, &actual) { 62 | found = Some(*header); 63 | break; 64 | } 65 | } 66 | if let Some(found) = found { 67 | expected_headers.remove(&found); 68 | continue; 69 | } 70 | panic!( 71 | "unexpected header: {:?} (remaining headers {:?})", 72 | actual, expected_headers 73 | ); 74 | } 75 | for header in expected_headers { 76 | panic!("expected header but not found: {:?}", header); 77 | } 78 | 79 | let mut line = String::new(); 80 | let mut socket = match expected_len { 81 | Some(amt) => socket.by_ref().take(amt), 82 | None => socket.by_ref().take(expected.len() as u64), 83 | }; 84 | while socket.limit() > 0 { 85 | line.truncate(0); 86 | t!(socket.read_line(&mut line)); 87 | if line.is_empty() { 88 | break; 89 | } 90 | if expected.is_empty() { 91 | panic!("unexpected line: {:?}", line); 92 | } 93 | let i = expected.find('\n').unwrap_or(expected.len() - 1); 94 | let expected_line = &expected[..i + 1]; 95 | expected = &expected[i + 1..]; 96 | if lines_match(expected_line, &line) { 97 | continue; 98 | } 99 | panic!( 100 | "lines didn't match:\n\ 101 | expected: {:?}\n\ 102 | actual: {:?}\n", 103 | expected_line, line 104 | ) 105 | } 106 | if !expected.is_empty() { 107 | println!("didn't get expected data: {:?}", expected); 108 | } 109 | } 110 | Message::Write(ref to_write) => { 111 | t!(socket.get_mut().write_all(to_write.as_bytes())); 112 | return; 113 | } 114 | } 115 | } 116 | 117 | let mut dst = Vec::new(); 118 | t!(socket.read_to_end(&mut dst)); 119 | assert_eq!(dst.len(), 0); 120 | } 121 | 122 | fn lines_match(expected: &str, mut actual: &str) -> bool { 123 | for (i, part) in expected.split("[..]").enumerate() { 124 | match actual.find(part) { 125 | Some(j) => { 126 | if i == 0 && j != 0 { 127 | return false; 128 | } 129 | actual = &actual[j + part.len()..]; 130 | } 131 | None => return false, 132 | } 133 | } 134 | actual.is_empty() || expected.ends_with("[..]") 135 | } 136 | 137 | impl Server { 138 | pub fn new() -> Server { 139 | let listener = t!(TcpListener::bind("127.0.0.1:0")); 140 | let addr = t!(listener.local_addr()); 141 | let (tx, rx) = channel(); 142 | let thread = thread::spawn(move || run(listener.accept().unwrap().0, &rx)); 143 | Server { 144 | messages: Some(tx), 145 | addr: Addr::Tcp(addr), 146 | thread: Some(thread), 147 | } 148 | } 149 | 150 | #[cfg(not(windows))] 151 | pub fn new_unix() -> Server { 152 | use std::os::unix::net::UnixListener; 153 | 154 | let path = "/tmp/easy_server.sock"; 155 | std::fs::remove_file(path).ok(); 156 | let listener = t!(UnixListener::bind(path)); 157 | let (tx, rx) = channel(); 158 | let thread = thread::spawn(move || run(listener.incoming().next().unwrap().unwrap(), &rx)); 159 | Server { 160 | messages: Some(tx), 161 | addr: Addr::Unix(path.into()), 162 | thread: Some(thread), 163 | } 164 | } 165 | 166 | pub fn receive(&self, msg: &str) { 167 | self.msg(Message::Read(self.replace_port(msg))); 168 | } 169 | 170 | fn replace_port(&self, msg: &str) -> String { 171 | match &self.addr { 172 | Addr::Tcp(addr) => msg.replace("$PORT", &addr.port().to_string()), 173 | Addr::Unix(_) => msg.to_string(), 174 | } 175 | } 176 | 177 | pub fn send(&self, msg: &str) { 178 | self.msg(Message::Write(self.replace_port(msg))); 179 | } 180 | 181 | fn msg(&self, msg: Message) { 182 | t!(self.messages.as_ref().unwrap().send(msg)); 183 | } 184 | 185 | pub fn addr(&self) -> &SocketAddr { 186 | match &self.addr { 187 | Addr::Tcp(addr) => addr, 188 | Addr::Unix(_) => panic!("server is a UnixListener"), 189 | } 190 | } 191 | 192 | #[cfg(not(windows))] 193 | pub fn path(&self) -> &str { 194 | match &self.addr { 195 | Addr::Tcp(_) => panic!("server is a TcpListener"), 196 | Addr::Unix(p) => p.as_os_str().to_str().unwrap(), 197 | } 198 | } 199 | 200 | pub fn url(&self, path: &str) -> String { 201 | match &self.addr { 202 | Addr::Tcp(addr) => format!("http://{}{}", addr, path), 203 | Addr::Unix(_) => format!("http://localhost{}", path), 204 | } 205 | } 206 | } 207 | 208 | impl Drop for Server { 209 | fn drop(&mut self) { 210 | match &self.addr { 211 | Addr::Tcp(addr) => drop(TcpStream::connect(addr)), 212 | Addr::Unix(p) => t!(std::fs::remove_file(p)), 213 | } 214 | 215 | drop(self.messages.take()); 216 | let res = self.thread.take().unwrap().join(); 217 | if !thread::panicking() { 218 | t!(res); 219 | } else if let Err(e) = res { 220 | println!("child server thread also failed: {:?}", e); 221 | } 222 | } 223 | } 224 | --------------------------------------------------------------------------------