curl/curl
Architecture
This page is a map of the curl source tree and the runtime stack that delivers a transfer. Read it before diving into any specific subsystem — almost every other wiki page assumes you already know what an "easy handle", a "connection filter", and the "multi loop" are.
Source tree at a glance
curl/
├── include/curl/ # Public C API headers (curl.h, multi.h, urlapi.h, …)
├── lib/ # libcurl implementation (~184 .c, ~178 .h)
│ ├── vtls/ # TLS backends (OpenSSL, GnuTLS, mbedTLS, Schannel, wolfSSL, Rustls, Apple)
│ ├── vquic/ # QUIC/HTTP-3 backends (ngtcp2, quiche)
│ ├── vauth/ # SASL, NTLM, Digest, Kerberos, SPNEGO authentication
│ ├── vssh/ # SSH backends (libssh, libssh2)
│ └── curlx/ # Internal portability helpers shared between lib/ and src/
├── src/ # The `curl` command-line tool
│ └── toolx/ # Helpers shared between src/ tools
├── docs/ # Markdown sources rendered to man pages and the website
├── tests/ # Test harness and ~1995 functional + ~300 unit tests
├── scripts/ # Maintenance scripts (managen, checksrc, mk-ca-bundle, …)
└── m4/, CMake/ # Autotools and CMake build helpersThree layered models
curl's architecture is best understood as three intersecting layers: the API surface the application sees, the transfer engine that schedules work, and the filter stack that turns bytes into protocol exchanges.
graph TD
subgraph "Application"
App[curl tool or third-party app]
end
subgraph "API surface (include/curl/)"
Easy[easy interface\ncurl_easy_*]
Multi[multi interface\ncurl_multi_*]
URL[URL API\ncurl_url_*]
Hdr[header API\ncurl_easy_header]
WS[websockets\ncurl_ws_*]
end
subgraph "Transfer engine (lib/)"
Multih[multi.c<br/>Curl_multi state machine]
Trans[transfer.c<br/>SINGLEREQUEST]
Conn[conncache.c<br/>connection cache]
DNS[dnscache.c + asyn-*<br/>name resolver]
end
subgraph "Connection filter stack (lib/)"
Cfilters[cfilters.c<br/>Curl_cfilter chain]
Socket[cf-socket]
TLS[vtls/* TLS filter]
H2[http2.c HTTP/2 filter]
H3[vquic/* HTTP/3 filter]
Proxy[cf-h1-proxy / cf-h2-proxy / socks]
end
subgraph "Protocol handlers (lib/)"
HTTP[http.c]
FTP[ftp.c]
SMTP[smtp.c, imap.c, pop3.c]
Misc[telnet.c, tftp.c, dict.c, smb.c, mqtt.c, ldap.c, ws.c, rtsp.c, gopher.c, file.c]
end
App --> Easy
App --> Multi
App --> URL
App --> Hdr
App --> WS
Easy --> Multih
Multi --> Multih
Multih --> Trans
Multih --> Conn
Multih --> DNS
Trans --> HTTP
Trans --> FTP
Trans --> SMTP
Trans --> Misc
HTTP --> Cfilters
FTP --> Cfilters
SMTP --> Cfilters
Misc --> Cfilters
Cfilters --> Socket
Cfilters --> TLS
Cfilters --> H2
Cfilters --> H3
Cfilters --> Proxy1. The API surface
The public API is in include/curl/. Applications interact with libcurl via:
- The easy interface (
curl_easy_init,curl_easy_setopt,curl_easy_perform,curl_easy_getinfo) for synchronous one-off transfers (include/curl/easy.h) - The multi interface (
curl_multi_init,curl_multi_add_handle,curl_multi_perform,curl_multi_socket_action) for non-blocking, concurrent transfers (include/curl/multi.h) - The URL API for parsing and composing URLs without performing a transfer (
include/curl/urlapi.h) - The header API for inspecting HTTP response headers (
include/curl/header.h) - The websockets API for
ws:///wss://framed messaging (include/curl/websockets.h)
The "easy" interface is implemented as a wrapper around the multi interface, sharing all the heavy lifting. See API reference for details.
2. The transfer engine
The heart of libcurl is Curl_multi_runsingle() in lib/multi.c — a state machine that walks an "easy handle" through every step of a transfer:
INIT → PENDING → SETUP → CONNECT → RESOLVING → CONNECTING → PROTOCONNECT
→ PROTOCONNECTING → DO → DOING → DID → PERFORMING → RATELIMITING
→ DONE → COMPLETEDEach state has a handler. The state machine is non-blocking: it advances handles only when sockets are ready, and yields back to the application between events. The "multi" loop is the only loop; "easy_perform" is just multi_add + multi_perform + multi_remove + multi_cleanup wrapped in a blocking call.
Two long-lived caches sit alongside the state machine:
- The connection cache (
lib/conncache.c) keeps idle connections alive for reuse, indexed by destination hash. A single shared cache can be attached viacurl_share. - The DNS cache (
lib/dnscache.c) keeps resolved addresses for the configured TTL. The actual resolution work runs throughlib/asyn-base.cplus one ofasyn-ares.c(c-ares) orasyn-thrdd.c(thread pool), orlib/doh.cfor DNS-over-HTTPS.
See Transfer engine for the full state walk.
3. The connection filter stack
Once a connection is up, every byte going to or from the network passes through a chain of connection filters (lib/cfilters.c, lib/cfilters.h). A filter has do_connect, cntrl, send, and recv callbacks, and each filter has zero or one filter beneath it. From the protocol handler's perspective the chain looks like a single byte stream; underneath it might be:
http.c → cfilter[HTTP/2] → cfilter[TLS=OpenSSL] → cfilter[HAProxy] → cfilter[socket]This is how curl supports HTTPS-over-HTTPS-proxy, HTTP/3 (QUIC) on top of UDP, HTTP/2 multiplexing, and TLS-on-TLS without any of the protocol handlers needing to know about it. New transports are added by writing a new filter type and inserting it into the chain. See Connection filters.
Protocol handlers
Each supported scheme exports a const struct Curl_protocol (lib/protocol.h) describing its scheme, default port, capabilities, and per-state callbacks (do_it, done, connecting, doing, connect_it, disconnect, etc.). Examples: Curl_protocol_http in lib/http.c, Curl_protocol_ftp in lib/ftp.c, Curl_protocol_smtp in lib/smtp.c. The transfer engine looks up the handler by URL scheme and calls into it from each transfer state.
Build systems
curl supports two parallel build systems and they are kept feature-parity:
- GNU Autotools:
configure.ac(162k lines) +acinclude.m4+Makefile.amfiles. The classic POSIX flow. - CMake:
CMakeLists.txt(92k lines) + helpers underCMake/. Required for Windows, increasingly used elsewhere.
Both produce the same libcurl artifact and the same curl binary; the workflow files under .github/workflows/ exercise both. A dedicated configure-vs-cmake.yml workflow guards against drift.
Where each subsystem lives
| Subsystem | Source | Wiki page |
|---|---|---|
| Command-line tool | src/ |
apps/curl |
| libcurl public headers | include/curl/ |
libraries/libcurl |
| Internal portability helpers | lib/curlx/ |
libraries/curlx |
| Multi state machine | lib/multi.c, lib/multiif.h |
systems/transfer-engine |
| Connection cache + setup | lib/conncache.c, lib/connect.c, lib/url.c |
systems/connection-management |
| Connection filters | lib/cfilters.c, lib/cf-*.c |
systems/connection-filters |
| DNS resolution | lib/dnscache.c, lib/hostip*.c, lib/asyn-*.c, lib/doh.c |
systems/dns-resolution |
| Protocol handler registry | lib/protocol.c, lib/protocol.h |
systems/protocol-handlers |
| TLS backends | lib/vtls/ |
features/tls-backends |
| HTTP/2 + HTTP/3 | lib/http2.c, lib/vquic/ |
features/http2-http3 |
| Authentication | lib/vauth/, lib/http_negotiate.c, lib/http_ntlm.c, lib/http_aws_sigv4.c |
features/authentication |
| SSH | lib/vssh/ |
features/ssh |
| Cookies, HSTS, alt-svc | lib/cookie.c, lib/hsts.c, lib/altsvc.c |
features/cookies-and-state |
| Proxies (HTTP/SOCKS) | lib/cf-h1-proxy.c, lib/cf-h2-proxy.c, lib/socks*.c |
features/proxies |
| WebSockets | lib/ws.c |
features/websockets |
A typical HTTPS request
To make the architecture concrete, here is what happens when an application calls curl_easy_perform() against https://example.com/:
sequenceDiagram
participant App
participant Easy as easy.c
participant Multi as multi.c
participant DNS as dnscache.c
participant Conn as conncache.c
participant CF as cfilters
participant TLS as vtls/openssl.c
participant HTTP as http.c
App->>Easy: curl_easy_perform()
Easy->>Multi: curl_multi_add_handle + loop
Multi->>Multi: SETUP → CONNECT
Multi->>DNS: Curl_resolv()
DNS-->>Multi: addresses
Multi->>Conn: existing reusable conn?
Conn-->>Multi: no
Multi->>CF: build filter chain socket→tls→h2
CF->>TLS: SSL_connect()
TLS-->>CF: handshake done
Multi->>HTTP: do_it() — write headers
HTTP->>CF: send request
CF->>TLS: encrypt + write
TLS-->>CF: bytes flushed
HTTP->>CF: recv() response
CF-->>HTTP: bytes
HTTP->>Multi: DONE
Multi-->>Easy: completed
Easy-->>App: CURLE_OKThe same pipeline handles http://, ftp://, smtp://, etc. — only the protocol handler and the contents of the filter chain change.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.