curl/curl
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()`.
- `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()`.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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()`.
- 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`.
- 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.
- 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`.
- `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.