Zero-copy FTP/HTTP Daemon compatible with all POSIX systems
| 1 | /* |
| 2 | * GNU GPLv3 License — Copyright (c) 2026 SeregonWar |
| 3 | * See LICENSE for full text. |
| 4 | */ |
| 5 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 6 | * PAL CURL — Complete libcurl shim for PS4/PS5 |
| 7 | * |
| 8 | * Drop-in replacement for the libcurl subset used by http_api.c. |
| 9 | * Implemented on top of BSD sockets + HTTP/1.1. No TLS (HTTPS is |
| 10 | * rejected with CURLE_UNSUPPORTED_PROTOCOL; the console uses plain HTTP |
| 11 | * for all local-network transfers). |
| 12 | * |
| 13 | * Supported API surface: |
| 14 | * curl_global_init / curl_global_cleanup (no-ops; for source compat) |
| 15 | * curl_easy_init / curl_easy_cleanup |
| 16 | * curl_easy_reset |
| 17 | * curl_easy_setopt |
| 18 | * curl_easy_perform |
| 19 | * curl_easy_getinfo |
| 20 | * curl_easy_strerror |
| 21 | * curl_slist_append / curl_slist_free_all |
| 22 | * curl_version |
| 23 | * |
| 24 | * Supported methods : GET, POST, HEAD (CURLOPT_NOBODY) |
| 25 | * Transfer encodings: Content-Length and chunked (HTTP/1.1 §4.1) |
| 26 | * Redirect : 3xx with Location, including relative URLs |
| 27 | * Timeouts : connect and transfer, via select(2) |
| 28 | * Speed guard : CURLOPT_LOW_SPEED_LIMIT / LOW_SPEED_TIME |
| 29 | * Progress : CURLOPT_XFERINFOFUNCTION |
| 30 | * |
| 31 | * ── Thread-safety ────────────────────────────────────────────────────────── |
| 32 | * NO function in this unit is thread-safe. Each CURL handle must be |
| 33 | * used from a single thread at a time. |
| 34 | * |
| 35 | * ── Build guard ──────────────────────────────────────────────────────────── |
| 36 | * Compiled only when ENABLE_LIBCURL=1 && (PS4 || PS5). |
| 37 | * On desktop the real libcurl is linked; this file is excluded. |
| 38 | * ═══════════════════════════════════════════════════════════════════════════ */ |
| 39 | |
| 40 | #ifndef PAL_CURL_H |
| 41 | #define PAL_CURL_H |
| 42 | |
| 43 | #include <stddef.h> /* size_t */ |
| 44 | #include <stdint.h> /* int64_t */ |
| 45 | |
| 46 | #ifdef __cplusplus |
| 47 | extern "C" { |
| 48 | #endif |
| 49 | |
| 50 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 51 | * Scalar types |
| 52 | * ═════════════════════════════════════════════════════════════════════════*/ |
| 53 | |
| 54 | /** Error / result code (matches real libcurl values for ABI compat). */ |
| 55 | typedef int CURLcode; |
| 56 | |
| 57 | /** Option identifier passed to curl_easy_setopt(). */ |
| 58 | typedef int CURLoption; |
| 59 | |
| 60 | /** Info identifier passed to curl_easy_getinfo(). */ |
| 61 | typedef int CURLINFO; |
| 62 | |
| 63 | /** Signed 64-bit file-size type (matches real libcurl's curl_off_t). */ |
| 64 | typedef long long curl_off_t; |
| 65 | |
| 66 | /** Opaque handle type (allocated by curl_easy_init). */ |
| 67 | typedef void CURL; |
| 68 | |
| 69 | /** Size of the error message buffer supplied via CURLOPT_ERRORBUFFER. */ |
| 70 | #define CURL_ERROR_SIZE 256 |
| 71 | |
| 72 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 73 | * Error codes (values match real libcurl for source-level compatibility) |
| 74 | * ═════════════════════════════════════════════════════════════════════════*/ |
| 75 | #define CURLE_OK 0 |
| 76 | #define CURLE_UNSUPPORTED_PROTOCOL 1 |
| 77 | #define CURLE_URL_MALFORMAT 3 |
| 78 | #define CURLE_COULDNT_RESOLVE_HOST 6 |
| 79 | #define CURLE_COULDNT_CONNECT 7 |
| 80 | #define CURLE_PARTIAL_FILE 18 |
| 81 | #define CURLE_HTTP_RETURNED_ERROR 22 |
| 82 | #define CURLE_WRITE_ERROR 23 |
| 83 | #define CURLE_OUT_OF_MEMORY 27 |
| 84 | #define CURLE_OPERATION_TIMEDOUT 28 |
| 85 | #define CURLE_ABORTED_BY_CALLBACK 42 |
| 86 | #define CURLE_TOO_MANY_REDIRECTS 47 |
| 87 | #define CURLE_UNKNOWN_OPTION 48 |
| 88 | #define CURLE_GOT_NOTHING 52 |
| 89 | #define CURLE_SEND_ERROR 55 |
| 90 | #define CURLE_RECV_ERROR 56 |
| 91 | |
| 92 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 93 | * curl_slist — linked list of strings (for CURLOPT_HTTPHEADER etc.) |
| 94 | * ═════════════════════════════════════════════════════════════════════════*/ |
| 95 | |
| 96 | typedef struct curl_slist { |
| 97 | char *data; |
| 98 | struct curl_slist *next; |
| 99 | } curl_slist; |
| 100 | |
| 101 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 102 | * Callback signatures |
| 103 | * ═════════════════════════════════════════════════════════════════════════*/ |
| 104 | |
| 105 | /** |
| 106 | * Write callback. Called by curl_easy_perform() for each received data |
| 107 | * chunk. Must return nmemb on success. Returning CURL_WRITEFUNC_PAUSE |
| 108 | * requests a brief pause (implementation re-invokes after a short delay; |
| 109 | * up to PAL_PAUSE_MAX_RETRIES times, then aborts with |
| 110 | * CURLE_ABORTED_BY_CALLBACK). |
| 111 | */ |
| 112 | typedef size_t (*curl_write_callback)(void *ptr, size_t size, size_t nmemb, |
| 113 | void *userdata); |
| 114 | |
| 115 | #define CURL_WRITEFUNC_PAUSE 0x10000001U |
| 116 | |
| 117 | /** |
| 118 | * Transfer-info / progress callback. Called periodically during body |
| 119 | * download if CURLOPT_NOPROGRESS is 0 (the default). |
| 120 | * Return 0 to continue; any other value aborts with |
| 121 | * CURLE_ABORTED_BY_CALLBACK. |
| 122 | */ |
| 123 | typedef int (*curl_xferinfo_callback)(void *clientp, |
| 124 | curl_off_t dltotal, |
| 125 | curl_off_t dlnow, |
| 126 | curl_off_t ultotal, |
| 127 | curl_off_t ulnow); |
| 128 | |
| 129 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 130 | * CURLoption IDs (values match real libcurl) |
| 131 | * ═════════════════════════════════════════════════════════════════════════*/ |
| 132 | |
| 133 | /* |
| 134 | * OBJECTPOINT options (base 10000 in libcurl) |
| 135 | * CURLOPTTYPE_OBJECTPOINT = 10000 |
| 136 | */ |
| 137 | #define CURLOPT_WRITEDATA 10001 /* void * */ |
| 138 | #define CURLOPT_URL 10002 /* const char * */ |
| 139 | #define CURLOPT_RANGE 10007 /* const char * e.g. "0-4095" */ |
| 140 | #define CURLOPT_ERRORBUFFER 10010 /* char[CURL_ERROR_SIZE] */ |
| 141 | #define CURLOPT_POSTFIELDS 10015 /* const char * (not copied) */ |
| 142 | #define CURLOPT_USERAGENT 10018 /* const char * */ |
| 143 | #define CURLOPT_HTTPHEADER 10023 /* curl_slist * */ |
| 144 | #define CURLOPT_XFERINFODATA 10057 /* void * */ |
| 145 | |
| 146 | /* |
| 147 | * LONG options (base 0 in libcurl) |
| 148 | */ |
| 149 | #define CURLOPT_VERBOSE 41 /* long bool */ |
| 150 | #define CURLOPT_NOPROGRESS 43 /* long bool (default 1) */ |
| 151 | #define CURLOPT_NOBODY 44 /* long bool — HEAD request */ |
| 152 | #define CURLOPT_POST 47 /* long bool */ |
| 153 | #define CURLOPT_FOLLOWLOCATION 52 /* long bool */ |
| 154 | #define CURLOPT_TIMEOUT 13 /* long seconds */ |
| 155 | #define CURLOPT_LOW_SPEED_LIMIT 19 /* long bytes/s */ |
| 156 | #define CURLOPT_LOW_SPEED_TIME 20 /* long seconds */ |
| 157 | #define CURLOPT_SSL_VERIFYPEER 64 /* long — accepted, ignored */ |
| 158 | #define CURLOPT_MAXREDIRS 68 /* long (-1 = unlimited) */ |
| 159 | #define CURLOPT_POSTFIELDSIZE 60 /* long (-1 = use strlen) */ |
| 160 | #define CURLOPT_CONNECTTIMEOUT 78 /* long seconds */ |
| 161 | #define CURLOPT_TIMEOUT_MS 155 /* long milliseconds */ |
| 162 | #define CURLOPT_CONNECTTIMEOUT_MS 156 /* long milliseconds */ |
| 163 | |
| 164 | /* |
| 165 | * FUNCTIONPOINT options (base 20000 in libcurl) |
| 166 | */ |
| 167 | #define CURLOPT_WRITEFUNCTION 20011 /* curl_write_callback */ |
| 168 | #define CURLOPT_XFERINFOFUNCTION 20219 /* curl_xferinfo_callback */ |
| 169 | |
| 170 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 171 | * CURLINFO IDs (values match real libcurl) |
| 172 | * ═════════════════════════════════════════════════════════════════════════*/ |
| 173 | |
| 174 | /* |
| 175 | * LONG info (base 0x200000) |
| 176 | */ |
| 177 | #define CURLINFO_RESPONSE_CODE 0x200002 /* long */ |
| 178 | |
| 179 | /* |
| 180 | * DOUBLE info (base 0x300000) |
| 181 | */ |
| 182 | #define CURLINFO_TOTAL_TIME 0x300003 /* double (seconds) */ |
| 183 | #define CURLINFO_SIZE_DOWNLOAD 0x300008 /* double (bytes) */ |
| 184 | #define CURLINFO_SPEED_DOWNLOAD 0x30000B /* double (bytes/s) */ |
| 185 | #define CURLINFO_CONTENT_LENGTH_DOWNLOAD 0x30000F /* double (-1 if unknown) */ |
| 186 | |
| 187 | /* ═══════════════════════════════════════════════════════════════════════════ |
| 188 | * API |
| 189 | * ═════════════════════════════════════════════════════════════════════════*/ |
| 190 | |
| 191 | /** |
| 192 | * @brief No-op stub for source-level compatibility with libcurl callers. |
| 193 | * Must be called before any other curl_* function (libcurl requires |
| 194 | * this; our implementation has nothing to initialise globally). |
| 195 | * |
| 196 | * @param flags Ignored. |
| 197 | * @return Always CURLE_OK. |
| 198 | * |
| 199 | * @note Thread-safety: safe to call from any thread. |
| 200 | * @note WCET: O(1). |
| 201 | */ |
| 202 | CURLcode curl_global_init(long flags); |
| 203 | |
| 204 | /** |
| 205 | * @brief No-op stub counterpart to curl_global_init(). |
| 206 | * |
| 207 | * @note Thread-safety: safe to call from any thread. |
| 208 | * @note WCET: O(1). |
| 209 | */ |
| 210 | void curl_global_cleanup(void); |
| 211 | |
| 212 | /** |
| 213 | * @brief Allocate and initialise a new easy handle. |
| 214 | * |
| 215 | * Default values after init: |
| 216 | * max_redirs = 10 |
| 217 | * connect_timeout = 30 s |
| 218 | * noprogress = 1 (progress callback disabled) |
| 219 | * |
| 220 | * @return New handle on success, NULL on allocation failure. |
| 221 | * |
| 222 | * @note Thread-safety: NOT thread-safe. |
| 223 | * @note WCET: O(1), one calloc. |
| 224 | */ |
| 225 | CURL *curl_easy_init(void); |
| 226 | |
| 227 | /** |
| 228 | * @brief Reset all options on an existing handle to their defaults. |
| 229 | * |
| 230 | * The handle remains valid and may be re-used. The file handle and |
| 231 | * response info fields are also cleared. |
| 232 | * |
| 233 | * @param handle Non-NULL easy handle. |
| 234 | * |
| 235 | * @note Thread-safety: NOT thread-safe. |
| 236 | * @note WCET: O(1). |
| 237 | */ |
| 238 | void curl_easy_reset(CURL *handle); |
| 239 | |
| 240 | /** |
| 241 | * @brief Release all resources owned by handle and free it. |
| 242 | * |
| 243 | * After this call the handle pointer is invalid. Idempotent on NULL. |
| 244 | * |
| 245 | * @param handle Easy handle or NULL (ignored). |
| 246 | * |
| 247 | * @note Thread-safety: NOT thread-safe. |
| 248 | * @note WCET: O(1). |
| 249 | */ |
| 250 | void curl_easy_cleanup(CURL *handle); |
| 251 | |
| 252 | /** |
| 253 | * @brief Set an option on a handle. |
| 254 | * |
| 255 | * The third argument type depends on the option: |
| 256 | * OBJECTPOINT options expect const char * or void * or curl_slist * |
| 257 | * FUNCTIONPOINT options expect a function pointer |
| 258 | * LONG options expect long |
| 259 | * |
| 260 | * @param handle Non-NULL easy handle. |
| 261 | * @param option CURLOPT_* constant. |
| 262 | * @param ... Option value (type depends on option). |
| 263 | * |
| 264 | * @return CURLE_OK on success. |
| 265 | * @retval CURLE_UNKNOWN_OPTION handle is NULL or option is unrecognised. |
| 266 | * |
| 267 | * @note Thread-safety: NOT thread-safe. |
| 268 | * @note WCET: O(1) for all options except CURLOPT_HTTPHEADER (no copy made). |
| 269 | */ |
| 270 | CURLcode curl_easy_setopt(CURL *handle, CURLoption option, ...); |
| 271 | |
| 272 | /** |
| 273 | * @brief Perform the transfer described by the handle's options. |
| 274 | * |
| 275 | * Executes: DNS resolve → TCP connect → HTTP request → response stream. |
| 276 | * Redirects (3xx) are followed up to max_redirs times if |
| 277 | * CURLOPT_FOLLOWLOCATION is set. |
| 278 | * |
| 279 | * Response info fields (CURLINFO_*) are populated on return, regardless |
| 280 | * of success or failure. |
| 281 | * |
| 282 | * @param handle Fully configured easy handle. |
| 283 | * |
| 284 | * @return CURLE_OK on a completed transfer (HTTP status < 400). |
| 285 | * Other CURLcode values on error. |
| 286 | * |
| 287 | * @note Thread-safety: NOT thread-safe. |
| 288 | * @note WCET: Unbounded (network I/O). |
| 289 | * @warning Must not be called from an interrupt context. |
| 290 | */ |
| 291 | CURLcode curl_easy_perform(CURL *handle); |
| 292 | |
| 293 | /** |
| 294 | * @brief Retrieve information about the last completed transfer. |
| 295 | * |
| 296 | * @param handle Easy handle (must have completed at least one perform). |
| 297 | * @param info CURLINFO_* constant. |
| 298 | * @param ... Pointer to receive the value (type depends on info). |
| 299 | * |
| 300 | * @return CURLE_OK on success. |
| 301 | * @retval CURLE_UNKNOWN_OPTION handle is NULL or info is unrecognised. |
| 302 | * |
| 303 | * @note Thread-safety: NOT thread-safe. |
| 304 | * @note WCET: O(1). |
| 305 | */ |
| 306 | CURLcode curl_easy_getinfo(CURL *handle, CURLINFO info, ...); |
| 307 | |
| 308 | /** |
| 309 | * @brief Map a CURLcode to a human-readable English string. |
| 310 | * |
| 311 | * The returned pointer is to a string literal; do not free or modify it. |
| 312 | * |
| 313 | * @param code Any CURLcode value. |
| 314 | * @return NUL-terminated string; never NULL. |
| 315 | * |
| 316 | * @note Thread-safety: safe (returns a pointer to a constant literal). |
| 317 | * @note WCET: O(1). |
| 318 | */ |
| 319 | const char *curl_easy_strerror(CURLcode code); |
| 320 | |
| 321 | /** |
| 322 | * @brief Return a brief version identifier string. |
| 323 | * |
| 324 | * @return Pointer to a string literal. Never NULL. |
| 325 | * |
| 326 | * @note Thread-safety: safe. |
| 327 | * @note WCET: O(1). |
| 328 | */ |
| 329 | const char *curl_version(void); |
| 330 | |
| 331 | /** |
| 332 | * @brief Append a copy of data to a slist, or create a new slist. |
| 333 | * |
| 334 | * If allocation fails the original list is returned unchanged (the caller |
| 335 | * should check whether the list grew by comparing pointers or list length). |
| 336 | * |
| 337 | * @param list Existing slist or NULL to create a new one. |
| 338 | * @param data NUL-terminated string to append; must not be NULL. |
| 339 | * |
| 340 | * @return Updated (or newly created) slist head, or the original list on |
| 341 | * allocation failure. |
| 342 | * |
| 343 | * @note Thread-safety: NOT thread-safe. |
| 344 | * @note WCET: O(n) for traversal to the tail. |
| 345 | */ |
| 346 | curl_slist *curl_slist_append(curl_slist *list, const char *data); |
| 347 | |
| 348 | /** |
| 349 | * @brief Free all nodes in a slist and their string data. |
| 350 | * |
| 351 | * Safe to call with NULL. The caller must not use the list after this call. |
| 352 | * |
| 353 | * @param list Head of the slist, or NULL. |
| 354 | * |
| 355 | * @note Thread-safety: NOT thread-safe. |
| 356 | * @note WCET: O(n). |
| 357 | */ |
| 358 | void curl_slist_free_all(curl_slist *list); |
| 359 | |
| 360 | #ifdef __cplusplus |
| 361 | } |
| 362 | #endif |
| 363 | |
| 364 | #endif /* PAL_CURL_H */ |