# System map — curl/curl

> Canonical page: https://codevetter.com/unpack/curl/system-map

# System map — curl/curl

Scanned commit c362f034692e05ecb4d33edb740ba64c63a20add (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Two build artefacts share one repo: `lib/` builds libcurl, `src/` builds the `curl` tool on top of the public headers in `include/curl/`. The tool owns argv parsing, output files, globbing and retries; libcurl owns URL parsing, connection reuse, the multi state machine, and the filter chain that actually moves bytes. Backends live in `lib/vtls` (TLS), `lib/vquic` (QUIC/HTTP3), `lib/vssh` (SCP/SFTP), `lib/vauth` (SASL/NTLM/Kerberos), `lib/vdns` (resolvers + DoH), with `lib/curlx/` as the portability/utility layer shared by both. There is no database — persistence is flat files (cookie jar, HSTS, alt-svc, TLS session cache) plus whatever the user redirects output to. Two parallel build systems (autotools via `configure.ac`/`Makefile.am` and CMake via a 93KB `CMakeLists.txt`) are both first-class and CI-verified against each other.
- The CLI process entrypoint is `main()` in src/tool_main.c, which runs `tool_init_stderr()`, `main_checkfds()` (reopens fds 0/1/2 on /dev/null so sockets can never land on stdin/stdout/stderr), ignores SIGPIPE, then calls `globalconf_init()` → `operate(argc, argv)` → `globalconf_free()`. — [src/tool_main.c#L152-L213](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_main.c#L152-L213) [src/tool_main.c#L86-L108](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_main.c#L86-L108)
- `operate()` is the tool's real driver: it forces `LC_NUMERIC=C`, reads `.curlrc` via `parseconfig()` unless argv[1] is `-q`/`--disable`, calls `parse_args()`, creates one `CURLSH` share handle, then hands off to `run_all_transfers()`. — [src/tool_operate.c#L2422-L2560](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2422-L2560)
- The public API surface is 12 headers under include/curl/ — `curl.h` (options/CURLcode), `multi.h`, `easy.h`, `urlapi.h`, `websockets.h`, `header.h`, `options.h`, `mprintf.h`, `system.h`, `curlver.h` — and `curlver.h` pins this tree at LIBCURL_VERSION "8.23.0-DEV" / LIBCURL_VERSION_NUM 0x081700. — [include/curl/curlver.h#L35-L60](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/include/curl/curlver.h#L35-L60) [include/curl/curl.h](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/include/curl/curl.h) [include/curl/multi.h](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/include/curl/multi.h)
- Every URI scheme libcurl knows is a `const struct Curl_scheme` (name, `run` vtable pointer, protocol bit, family bit, PROTOPT_* flags, default port) defined in lib/protocol.c and externed in lib/protocol.h; a scheme compiled out (e.g. `CURL_DISABLE_FTP`) keeps its record but gets a `ZERO_NULL` `run` pointer. — [lib/protocol.h#L240-L288](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/protocol.h#L240-L288) [lib/protocol.c#L40-L110](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/protocol.c#L40-L110)
- Scheme lookup avoids a linear table scan: `Curl_getn_scheme()` dispatches on name length into `two_letter_scheme()` … `seven_letter_scheme()`, each a hand-written switch on the first character, and rejects any name shorter than 2 or longer than 7 characters. — [lib/protocol.c#L608-L628](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/protocol.c#L608-L628) [lib/protocol.c#L560-L604](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/protocol.c#L560-L604)
- Transport is a chain of connection filters: `struct Curl_cftype` supplies do_connect/do_send/do_recv/do_shutdown/adjust_pollset/query, and `struct Curl_cfilter` links instances via `->next`, so TLS writes into whatever is below it without knowing whether that is a socket, a proxy tunnel, or HTTP/2. — [lib/cfilters.h#L217-L242](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cfilters.h#L217-L242) [docs/internals/CONNECTION-FILTERS.md#L14-L65](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/docs/internals/CONNECTION-FILTERS.md#L14-L65)
- Concrete filter types are registered as `struct Curl_cftype Curl_cft_*` across the tree: tcp/udp/unix/tcp_accept (lib/cf-socket.c), ip_happy, socks_proxy, http_proxy, h1/h2 proxy, haproxy, http_connect, nghttp2, http3 (both ngtcp2 and quiche), capsule, dns, and recvbuf. — [lib/cf-socket.c#L1844](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cf-socket.c#L1844) [lib/cf-ip-happy.c#L964](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cf-ip-happy.c#L964) [lib/socks.c#L1307](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/socks.c#L1307) [lib/vquic/cf-ngtcp2.c#L1089](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/vquic/cf-ngtcp2.c#L1089)
- The runtime boundary between easy and multi is one-way: the multi interface is the engine and `curl_easy_perform()` is a wrapper that creates (and caches on `data->multi_easy`) a 1-handle multi and pumps it. — [lib/easy.c#L773-L845](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/easy.c#L773-L845) [lib/easy.c#L852-L862](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/easy.c#L852-L862)
- Each transfer is a 16-state machine (`MSTATE_INIT` … `MSTATE_MSGSENT`) declared in lib/multihandle.h and driven by `multi_runsingle()` in lib/multi.c, which is 4,262 lines and the single busiest file in the library. — [lib/multihandle.h#L48-L64](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/multihandle.h#L48-L64) [lib/multi.c#L2714-L2890](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/multi.c#L2714-L2890)
- TLS is selected from a compile-ordered array `available_backends[]` (wolfSSL, GnuTLS, mbedTLS, OpenSSL, Schannel, Rustls); `CURL_SSL_BACKEND` in the environment can pick among them at runtime when multiple are built in. — [lib/vtls/vtls.c#L706-L726](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/vtls/vtls.c#L706-L726) [lib/vtls/vtls.c#L790-L820](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/vtls/vtls.c#L790-L820)
- The storage layer is plain files, not a database: cookie jars via `cookie_output()`/`Curl_flush_cookies()`, HSTS via `Curl_hsts_save()`, Alt-Svc via `Curl_altsvc_save()`, and TLS session blobs via `tool_ssls_save()` called from `operate()`. — [lib/cookie.c#L1530-L1690](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/cookie.c#L1530-L1690) [lib/hsts.c#L369-L400](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/hsts.c#L369-L400) [lib/altsvc.c#L392-L425](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/altsvc.c#L392-L425) [src/tool_operate.c#L2513-L2522](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/src/tool_operate.c#L2513-L2522)
- Build/test commands live in the top Makefile.am: `make check` = test + examples + check-docs; `make test`, `test-full`, `test-nonflaky`, `test-torture`, `test-event`, `test-ci`, `pytest`; plus `make checksrc`, `make badwords`, `make lint`, `make tidy`, and `make ca-bundle`. — [Makefile.am#L113-L210](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/Makefile.am#L113-L210) [tests/Makefile.am#L140-L180](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/tests/Makefile.am#L140-L180)
- CI is spread over 15 GitHub Actions workflows (linux, linux-old, macos, windows, non-native, http3-linux, distcheck, codeql, checksrc, checkdocs, checkurls, configure-vs-cmake, curl-for-win, appveyor-status, label), with AppVeyor and CircleCI configs alongside; dependency versions are pinned in workflow `env:` and bumped by Renovate. — [.github/workflows/linux.yml#L34-L60](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/.github/workflows/linux.yml#L34-L60) [.github/workflows/checksrc.yml#L37-L60](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/.github/workflows/checksrc.yml#L37-L60) [renovate.json](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/renovate.json) [appveyor.yml](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/appveyor.yml)
- The Dockerfile is not a deployment artefact — it is a pinned-digest debian:bookworm-slim reproducible *release* environment, used to run `autoreconf`, `configure`, `make`, and `scripts/maketgz` with a fixed `SOURCE_DATE_EPOCH`. — [Dockerfile#L1-L42](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/Dockerfile#L1-L42)
- `lib/curlx/` is a deliberately separate portability layer (dynbuf, strparse, base64, inet_pton/ntop, timeval, nonblock, multibyte, win32-fopen, warnless) that both `lib/` and `src/` link, which is why tool code calls `curlx_*` rather than libc directly. — [lib/curlx/dynbuf.h](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/curlx/dynbuf.h) [lib/curlx/strparse.h](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/lib/curlx/strparse.h) [docs/internals/CURLX.md](https://github.com/curl/curl/blob/c362f034692e05ecb4d33edb740ba64c63a20add/docs/internals/CURLX.md)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
