Files
jak-project/third-party/curl/lib/request.h
T
Alexander J. Semenuk 5d5e35fb9b fix: Windows toolchain compatibility (curl 8.21 re-vendor, endless reconfigure loop) (#4355)
## Problem

Windows builds break with a current local toolchain (Scoop LLVM 22.1.8,
CMake 4.4.0, VS 2026), in two independent ways:

1. The build stops at curl's deliberate guard: `#error "no non-blocking
method was found/used/set"` in `third-party/curl/lib/nonblock.c`.
2. From the second configure onward, `cmake --build` re-runs CMake in an
endless loop (observed 42 consecutive reconfigure cycles in a single
build). Likely the same mechanism behind the "endlessly building" VS
2026 note in `docs/setup/dev/vs.md`.

## Root cause

1. `third-party/curl/CMake/CurlTests.c` passes `int *` to
`ioctlsocket()`, whose third parameter is `u_long *`. Clang 22 promotes
`-Wincompatible-pointer-types` to a hard error in C, so the
`HAVE_IOCTLSOCKET_FIONBIO` try_compile silently fails and
`curl_config.h` never defines it. Upstream CI does not see this because
the windows-2022 runner image ships an older LLVM. GCC 14 promotes the
same warning to a hard error, which is very likely the `CurlTests.c.obj`
failure reported from MSYS2 in open-goal/jak-project#3551. Upstream curl
hit the identical problem with GCC 14 and fixed the probe in curl 8.8.0
(curl/curl#13578).
2. The root CMakeLists copies the build tree's `compile_commands.json`
into `<src>/build/` for clangd using `configure_file()`, which registers
its input as a configure dependency. CMake rewrites
`compile_commands.json` late in every generation, after
`CTestTestfile.cmake` and `cmake_install.cmake` (outputs of the same
Ninja regen rule), so once the dependency is registered the rule is
deterministically dirty and every `ninja` invocation re-runs CMake. A
pristine first configure is safe (the file does not exist yet, so the
`if(EXISTS ...)` guard skips the copy), which is why the loop looks
machine- or IDE-specific.

## Fix

1. Per review, re-vendor `third-party/curl` at the `curl-8_21_0` tag
(previously `curl-8_3_0`), which carries the upstream probe fix plus two
years of upstream development; `vendor.yaml` updated to match.
Adjustments the version jump forced:
- curl 8.15 removed the native macOS Secure Transport backend
(`CURL_USE_SECTRANSP`), so macOS now builds curl against OpenSSL like
Linux. The two macOS workflows install Homebrew `openssl@3` and export
`OPENSSL_ROOT_DIR` (keg-only), and the macOS setup docs gained the same
two lines.
- `CURL_BROTLI` / `CURL_ZSTD` switched to AUTO-detection in curl 8.10;
pinned OFF to keep the previous no-compression behavior and avoid
silently linking whatever the CI images happen to have.
- curl's new top-level `BUILD_EXAMPLES` cache option (default ON) leaked
into discord-rpc's identically named option and broke configure at a
nonexistent `examples/send-presence` directory; pinned OFF ahead of the
third-party subdirectories.

The diff is dominated by the mechanical tag-tree swap under
`third-party/curl` (linguist-vendored, collapsed in review). The
hand-written changes are `CMakeLists.txt`, the two macOS workflows,
`docs/setup/system/macos.md`, and `vendor.yaml`.
2. Swap `configure_file()` for `file(COPY ...)`: the same clangd copy
with no configure dependency registered. (`file(COPY_FILE ...
ONLY_IF_DIFFERENT)` would be cleaner still but requires CMake 3.21,
above the declared `cmake_minimum_required(VERSION 3.10)`.)

## Test plan

- [x] Fresh `cmake --preset Release-windows-clang` (LLVM 22, no cache
seeding) completes and logs `Enabled SSL backends: Schannel`; the
FIONBIO probe passes without the previous `#error`
- [x] Full Windows Release build from scratch in the branch worktree
(all 1422 targets)
- [x] goalc-test suite: 1509 passed, 0 failed
- [x] Second consecutive configure with `compile_commands.json` present:
the regen rule in `build.ninja` has no `compile_commands.json` input;
`<src>/build/compile_commands.json` is still refreshed for clangd
- [x] Repeated `ninja` invocations after a full build no longer re-run
CMake
- [x] macOS Intel and ARM CI green (first exercise of the OpenSSL
backend switch)

---

I work off a self-hosted forge, so this GitHub account is quiet; the
configure logs and ninja dirty-node traces from the investigation are
available if anyone wants the raw data.

(AI-assisted)
2026-07-27 19:19:18 -04:00

252 lines
10 KiB
C
Vendored
Generated

#ifndef HEADER_CURL_REQUEST_H
#define HEADER_CURL_REQUEST_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
/* This file is for lib internal stuff */
#include "curl_setup.h"
#include "bufq.h"
/* forward declarations */
struct UserDefined;
/* Bits on the io_flags member of SingleRequest */
#define REQ_IO_RECV (1 << 0) /* there is or may be data to read */
#define REQ_IO_SEND (1 << 1) /* there is or may be data to write */
/* Low level request receive/send io_flags checks. */
#define CURL_REQ_WANT_SEND(d) ((d)->req.io_flags & REQ_IO_SEND)
#define CURL_REQ_WANT_RECV(d) ((d)->req.io_flags & REQ_IO_RECV)
#define CURL_REQ_WANT_IO(d) \
((d)->req.io_flags & (REQ_IO_RECV | REQ_IO_SEND))
/* Low level request receive/send io_flags manipulations. */
#define CURL_REQ_SET_SEND(d) ((d)->req.io_flags |= REQ_IO_SEND)
#define CURL_REQ_SET_RECV(d) ((d)->req.io_flags |= REQ_IO_RECV)
#define CURL_REQ_CLEAR_SEND(d) \
((d)->req.io_flags &= (uint8_t)~REQ_IO_SEND)
#define CURL_REQ_CLEAR_RECV(d) \
((d)->req.io_flags &= (uint8_t)~REQ_IO_RECV)
#define CURL_REQ_CLEAR_IO(d) \
((d)->req.io_flags &= (uint8_t)~(REQ_IO_RECV | REQ_IO_SEND))
enum expect100 {
EXP100_SEND_DATA, /* enough waiting, send the body now */
EXP100_AWAITING_CONTINUE, /* waiting for the 100 Continue header */
EXP100_SENDING_REQUEST, /* still sending the request but will wait for
the 100 header once done with the request */
EXP100_FAILED /* used on 417 Expectation Failed */
};
enum upgrade101 {
UPGR101_NONE, /* default state */
UPGR101_WS, /* upgrade to WebSocket requested */
UPGR101_H2, /* upgrade to HTTP/2 requested */
UPGR101_RECEIVED /* 101 response received */
};
/*
* Request specific data in the easy handle (Curl_easy). Previously,
* these members were on the connectdata struct but since a conn struct may
* now be shared between different Curl_easys, we store connection-specific
* data here. This struct only keeps stuff that is interesting for *this*
* request, as it will be cleared between multiple ones
*/
struct SingleRequest {
curl_off_t size; /* -1 if unknown at this point */
curl_off_t maxdownload; /* in bytes, the maximum amount of data to fetch,
-1 means unlimited */
curl_off_t bytecount; /* total number of bytes read */
curl_off_t writebytecount; /* number of bytes written */
curl_off_t offset; /* possible resume offset read from the
Content-Range: header */
struct curltime start; /* transfer started at this time */
unsigned int headerbytecount; /* received server headers (not CONNECT
headers) */
unsigned int allheadercount; /* all received headers (server + CONNECT) */
unsigned int deductheadercount; /* this amount of bytes does not count when
we check if anything has been transferred
at the end of a connection. We use this
counter to make only a 100 reply (without
a following second response code) result
in a CURLE_GOT_NOTHING error code */
int headerline; /* counts header lines to better track the
first one */
int httpcode; /* error code from the 'HTTP/1.? XXX' or
'RTSP/1.? XXX' line */
unsigned char httpversion_sent; /* Version in request (09, 10, 11, etc.) */
unsigned char httpversion; /* Version in response (09, 10, 11, etc.) */
enum upgrade101 upgr101; /* 101 upgrade state */
/* Client Writer stack, handles transfer- and content-encodings, protocol
* checks, pausing by client callbacks. */
struct Curl_cwriter *writer_stack;
/* Client Reader stack, handles transfer- and content-encodings, protocol
* checks, pausing by client callbacks. */
struct Curl_creader *reader_stack;
struct bufq sendbuf; /* data which needs to be send to the server */
size_t sendbuf_hds_len; /* amount of header bytes in sendbuf */
time_t timeofdoc;
char *location; /* This points to an allocated version of the Location:
header data */
char *newurl; /* Set to the new URL to use when a redirect or a retry is
wanted */
uint8_t io_flags; /* REQ_IO_RECV | REQ_IO_SEND */
char *hd_auth; /* Authorization header, full HTTP/1.x line */
#ifndef CURL_DISABLE_PROXY
char *hd_proxy_auth; /* Proxy-Authorization header, full HTTP/1.x line */
#endif
#ifndef CURL_DISABLE_COOKIES
char *cookiehost;
#endif
#ifndef CURL_DISABLE_COOKIES
unsigned char setcookies;
#endif
BIT(header); /* incoming data has HTTP header */
BIT(done); /* request is done, e.g. no more send/recv should
* happen. This can be TRUE before `upload_done` or
* `download_done` is TRUE. */
BIT(content_range); /* set TRUE if Content-Range: was found */
BIT(download_done); /* set to TRUE when download is complete */
BIT(eos_written); /* iff EOS has been written to client */
BIT(eos_read); /* iff EOS has been read from the client */
BIT(eos_sent); /* iff EOS has been sent to the server */
BIT(rewind_read); /* iff reader needs rewind at next start */
BIT(upload_done); /* set to TRUE when all request data has been sent */
BIT(upload_aborted); /* set to TRUE when upload was aborted. Also
* shows `upload_done` as TRUE. */
BIT(ignorebody); /* we read a response-body but we ignore it! */
BIT(http_bodyless); /* HTTP response status code is between 100 and 199,
204 or 304 */
BIT(chunk); /* if set, this is a chunked transfer-encoding */
BIT(resp_trailer); /* response carried 'Trailer:' header field */
BIT(ignore_cl); /* ignore content-length */
BIT(upload_chunky); /* set TRUE if we are doing chunked transfer-encoding
on upload */
BIT(no_body); /* the response has no body */
BIT(authneg); /* TRUE when the auth phase has started, which means
that we are creating a request with an auth header,
but it is not the final request in the auth
negotiation. */
BIT(sendbuf_init); /* sendbuf is initialized */
BIT(shutdown); /* request end will shutdown connection */
BIT(shutdown_err_ignore); /* errors in shutdown will not fail request */
BIT(reader_started); /* client reads have started */
};
/**
* Initialize the state of the request for first use.
*/
void Curl_req_init(struct SingleRequest *req);
/**
* The request is about to start. Record time and do a soft reset.
*/
CURLcode Curl_req_start(struct SingleRequest *req,
struct Curl_easy *data);
/**
* The request may continue with a follow up. Reset
* members, but keep start time for overall duration calc.
*/
CURLcode Curl_req_soft_reset(struct SingleRequest *req,
struct Curl_easy *data);
/**
* The request is done. If not aborted, make sure that buffers are
* flushed to the client.
* @param req the request
* @param data the transfer
* @param aborted TRUE iff the request was aborted/errored
*/
CURLcode Curl_req_done(struct SingleRequest *req,
struct Curl_easy *data, bool aborted);
/**
* Free the state of the request, not usable afterwards.
*/
void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data);
/**
* Hard reset the state of the request to virgin state base on
* transfer settings.
*/
void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data);
/**
* Send request headers. If not all could be sent
* they will be buffered. Use `Curl_req_flush()` to make sure
* bytes are really send.
* @param data the transfer making the request
* @param req the complete header bytes, no body
* @param httpversion version used in request (09, 10, 11, etc.)
* @return CURLE_OK (on blocking with *pnwritten == 0) or error.
*/
CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req,
unsigned char httpversion);
/**
* TRUE iff the request has sent all request headers and data.
*/
bool Curl_req_done_sending(struct Curl_easy *data);
/*
* Read more from client and flush all buffered request bytes.
* @return CURLE_OK on success or the error on the sending.
* Never returns CURLE_AGAIN.
*/
CURLcode Curl_req_send_more(struct Curl_easy *data);
/* TRUE if the request wants to send, e.g. is not done sending
* and is not blocked. */
bool Curl_req_want_send(struct Curl_easy *data);
/* TRUE if the request wants to receive and is not blocked. */
bool Curl_req_want_recv(struct Curl_easy *data);
/**
* TRUE iff the request has no buffered bytes yet to send.
*/
bool Curl_req_sendbuf_empty(struct Curl_easy *data);
/**
* Stop sending any more request data to the server.
* Clear the send buffer and mark request sending as done.
*/
CURLcode Curl_req_abort_sending(struct Curl_easy *data);
/**
* Stop sending and receiving any more request data.
* Abort sending if not done.
*/
CURLcode Curl_req_stop_send_recv(struct Curl_easy *data);
/**
* Invoked when all request data has been uploaded.
*/
CURLcode Curl_req_set_upload_done(struct Curl_easy *data);
#endif /* HEADER_CURL_REQUEST_H */