mirror of
https://github.com/jart/cosmopolitan.git
synced 2026-11-08 01:58:20 -10:00
- Open flags (e.g. O_CREAT) are now constants - Error numbers (e.g. EINVAL) are now constants - Signal numbers (e.g. SIGINT) are now constants - fcntl() file locks now work properly on Windows - Significantly improve Windows path handling code - Implement true vfork() on Windows and make fork() faster - Implement scanf %[charset] support and improve conformance - Introduce $COSMOSDRIVE so you can use Windows 11 dev drives - Fix stdio seek/tell bug and make stdio locks faster and safer - Change TLS ABI to use tlscc wrapper that inserts function calls - Make malloc() faster on all platforms using new cosmo_shard() api - Make malloc() fast like tcmalloc on Linux using restartable sequences - Improve fsync() and fdatasync() polyfills so they abstract robustness - Fix CLOCK_BOOTTIME and CLOCK_MONOTONIC on XNU so it reflects uptime - Fix bugs in Windows overlapped code that could possibly cause crash - Fix bugs in Windows signal code that could possibly cause crash - Make symlinks with multiple components work right on Windows - Add FreeBSD ELF note to remove SIGBUS issue and other quirks - Support symlinks on Windows under non-administrator accounts - Make kisdangerous() scalable by adding radix trie to mmap() - Make large realloc() operations zero-cost on all platforms - Fix bugs in readdir() on Windows where d_reclen wasn't set - Make all system call wrappers memory safe (raises EFAULT) - Fix bugs in poll() and select() on Windows with 64+ fds - Fix bug in madvise() on Windows that could cause crash - Support WUNTRACED, SIGSTOP, and SIGCONT on Windows - Increase x86 image base from 0x400000 to 0x4200000 - Introduce -msysv and -mtinylinux flags to cosmocc - Fix issues with siglongjmp() on XNU and NetBSD - Make pthread_barrier_wait() work correctly - Make clock_gettime() faster on FreeBSD ARM - Support file mode bits better on Windows - Improve performance of string libraries - Raise EILSEQ when creating evil paths - Fix std::terminate to not use malloc - Modernize more c11 and c23 features - Make pthread_once() exception safe - Support RLIMIT_CPU on Windows - Introduce HACL* cryptography - Introduce cosmopolitan make - Introduce fasterzip program - Introduce open_memstream() - Introduce dedupe program - Introduce arc4random() - Introduce f2fs support
425 lines
13 KiB
C
425 lines
13 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2025 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "net/https/fetch.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/intrin/kprintf.h"
|
|
#include "libc/intrin/likely.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/serialize.h"
|
|
#include "libc/sock/sock.h"
|
|
#include "libc/stdio/append.h"
|
|
#include "libc/stdio/rand.h"
|
|
#include "libc/str/slice.h"
|
|
#include "libc/sysv/consts/af.h"
|
|
#include "libc/sysv/consts/ipproto.h"
|
|
#include "libc/sysv/consts/sock.h"
|
|
#include "libc/sysv/consts/sol.h"
|
|
#include "libc/sysv/consts/tcp.h"
|
|
#include "net/http/http.h"
|
|
#include "net/http/url.h"
|
|
#include "net/https/https.h"
|
|
#include "third_party/mbedtls/aes.h"
|
|
#include "third_party/mbedtls/ctr_drbg.h"
|
|
#include "third_party/mbedtls/ssl.h"
|
|
#include "third_party/musl/netdb.h"
|
|
|
|
#define FAILNEG1(x) \
|
|
do { \
|
|
if (UNLIKELY((x) == -1)) \
|
|
goto OnFailure; \
|
|
} while (0)
|
|
|
|
#define FAILZERO(x) \
|
|
do { \
|
|
if (UNLIKELY(!(x))) \
|
|
goto OnFailure; \
|
|
} while (0)
|
|
|
|
#define HasHeader(H) (!!msg.headers[H].a)
|
|
#define HeaderData(H) (p + msg.headers[H].a)
|
|
#define HeaderLength(H) (msg.headers[H].b - msg.headers[H].a)
|
|
#define HeaderEqualCase(H, S) \
|
|
SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H))
|
|
|
|
struct Fetch {
|
|
int sock;
|
|
};
|
|
|
|
static bool Tune(int fd, int a, int b, int x) {
|
|
if (!b)
|
|
return false;
|
|
return setsockopt(fd, a, b, &x, sizeof(x)) != -1;
|
|
}
|
|
|
|
static int Socket(int family, int type, int protocol) {
|
|
int fd;
|
|
if ((fd = socket(family, type, protocol)) != -1) {
|
|
Tune(fd, SOL_TCP, TCP_FASTOPEN_CONNECT, 1);
|
|
Tune(fd, SOL_TCP, TCP_QUICKACK, 1);
|
|
Tune(fd, SOL_TCP, TCP_NODELAY, 1);
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
static int GetSslEntropy(void *c, unsigned char *p, size_t n) {
|
|
if (getentropy(p, n))
|
|
notpossible;
|
|
return 0;
|
|
}
|
|
|
|
static int TlsSend(void *c, const unsigned char *p, size_t n) {
|
|
ssize_t rc;
|
|
struct Fetch *f = c;
|
|
if ((rc = write(f->sock, p, n)) == -1)
|
|
return -errno;
|
|
return rc;
|
|
}
|
|
|
|
static int TlsRecv(void *c, unsigned char *p, size_t n, uint32_t waitms) {
|
|
ssize_t rc;
|
|
struct Fetch *f = c;
|
|
if ((rc = read(f->sock, p, n)) == -1)
|
|
return -errno;
|
|
return rc;
|
|
}
|
|
|
|
static int PerformFetch(char **response_data, const char *urlarg, int tries) {
|
|
if (tries == 10) {
|
|
errno = ELOOP;
|
|
return -1;
|
|
}
|
|
|
|
int retcode;
|
|
uint64_t method = kHttpGet;
|
|
struct Fetch f = {.sock = -1};
|
|
struct Url url = {0};
|
|
char *host = 0;
|
|
char *port = 0;
|
|
char *urldata = 0;
|
|
char *request = 0;
|
|
char *funpath = 0;
|
|
bool usessl = false;
|
|
char *url2 = 0;
|
|
char *p = 0;
|
|
|
|
struct HttpMessage msg;
|
|
InitHttpMessage(&msg, kHttpResponse);
|
|
|
|
mbedtls_ssl_config conf;
|
|
mbedtls_ssl_context ssl;
|
|
mbedtls_ctr_drbg_context drbg;
|
|
mbedtls_ssl_init(&ssl);
|
|
mbedtls_ctr_drbg_init(&drbg);
|
|
mbedtls_ssl_config_init(&conf);
|
|
|
|
if (!(urldata = ParseUrl(urlarg, -1, &url, kUrlPlus)))
|
|
goto BadParameter;
|
|
if (url.scheme.n) {
|
|
if (url.scheme.n == 5 && !memcasecmp(url.scheme.p, "https", 5)) {
|
|
usessl = true;
|
|
} else if (!(url.scheme.n == 4 && !memcasecmp(url.scheme.p, "http", 4))) {
|
|
goto BadParameter;
|
|
}
|
|
}
|
|
if (url.host.n) {
|
|
FAILZERO((host = strndup(url.host.p, url.host.n)));
|
|
if (url.port.n) {
|
|
FAILZERO((port = strndup(url.port.p, url.port.n)));
|
|
} else {
|
|
FAILZERO((port = strdup(usessl ? "443" : "80")));
|
|
}
|
|
} else {
|
|
FAILZERO((host = strdup("127.0.0.1")));
|
|
FAILZERO((port = strdup(usessl ? "443" : "80")));
|
|
}
|
|
url.fragment.p = 0, url.fragment.n = 0;
|
|
url.scheme.p = 0, url.scheme.n = 0;
|
|
url.user.p = 0, url.user.n = 0;
|
|
url.pass.p = 0, url.pass.n = 0;
|
|
url.host.p = 0, url.host.n = 0;
|
|
url.port.p = 0, url.port.n = 0;
|
|
if (!url.path.n || url.path.p[0] != '/') {
|
|
FAILZERO((funpath = malloc(1 + url.path.n)));
|
|
mempcpy(mempcpy(funpath, "/", 1), url.path.p, url.path.n);
|
|
url.path.p = funpath;
|
|
++url.path.n;
|
|
}
|
|
|
|
// build message
|
|
char methodstr[9] = {0};
|
|
WRITE64LE(methodstr, method);
|
|
FAILNEG1(appends(&request, methodstr));
|
|
FAILNEG1(appends(&request, " "));
|
|
char *encoded_url;
|
|
FAILZERO((encoded_url = EncodeUrl(&url, 0)));
|
|
FAILNEG1(appends(&request, encoded_url));
|
|
free(encoded_url);
|
|
FAILNEG1(appends(&request, " HTTP/1.1\r\n"
|
|
"Connection: close\r\n"));
|
|
|
|
// append host header
|
|
FAILNEG1(appends(&request, "Host: "));
|
|
FAILNEG1(appends(&request, host));
|
|
FAILNEG1(appendw(&request, ':'));
|
|
FAILNEG1(appends(&request, port));
|
|
FAILNEG1(appends(&request, "\r\n"));
|
|
|
|
// finish headers
|
|
FAILNEG1(appends(&request, "\r\n"));
|
|
|
|
// initialize crypto
|
|
if (usessl) {
|
|
if (mbedtls_ctr_drbg_seed(&drbg, GetSslEntropy, 0, "justine", 7))
|
|
goto OutOfMemory;
|
|
mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
|
|
MBEDTLS_SSL_TRANSPORT_STREAM,
|
|
MBEDTLS_SSL_PRESET_DEFAULT);
|
|
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
|
mbedtls_ssl_conf_ca_chain(&conf, GetSslRoots(), 0);
|
|
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &drbg);
|
|
if (mbedtls_ssl_setup(&ssl, &conf))
|
|
goto OutOfMemory;
|
|
if (mbedtls_ssl_set_hostname(&ssl, host))
|
|
goto BadParameter;
|
|
}
|
|
|
|
// perform dns lookup
|
|
struct addrinfo *addr;
|
|
struct addrinfo hints = {.ai_family = AF_UNSPEC,
|
|
.ai_socktype = SOCK_STREAM,
|
|
.ai_protocol = IPPROTO_TCP,
|
|
.ai_flags = AI_NUMERICSERV};
|
|
if (getaddrinfo(host, port, &hints, &addr)) {
|
|
errno = ENETUNREACH;
|
|
goto OnFailure;
|
|
}
|
|
|
|
// connect to server
|
|
if ((f.sock = Socket(addr->ai_family, addr->ai_socktype,
|
|
addr->ai_protocol)) == -1 ||
|
|
connect(f.sock, addr->ai_addr, addr->ai_addrlen)) {
|
|
freeaddrinfo(addr);
|
|
goto OnFailure;
|
|
}
|
|
freeaddrinfo(addr);
|
|
|
|
// secure connection
|
|
if (usessl) {
|
|
mbedtls_ssl_set_bio(&ssl, &f.sock, TlsSend, 0, TlsRecv);
|
|
if (mbedtls_ssl_handshake(&ssl))
|
|
goto AccessDenied;
|
|
}
|
|
|
|
// send message
|
|
ssize_t rc;
|
|
size_t i, n;
|
|
n = appendz(request).i;
|
|
for (i = 0; i < n; i += rc) {
|
|
if (usessl) {
|
|
rc = mbedtls_ssl_write(&ssl, request + i, n - i);
|
|
if (rc <= 0)
|
|
goto OnFailure;
|
|
} else {
|
|
rc = write(f.sock, request + i, n - i);
|
|
if (rc <= 0)
|
|
goto OnFailure;
|
|
}
|
|
}
|
|
|
|
// handle response
|
|
int t;
|
|
struct HttpUnchunker u;
|
|
size_t g, hdrlen, paylen;
|
|
FAILZERO((p = malloc((n = 32768))));
|
|
for (hdrlen = paylen = t = i = 0;;) {
|
|
if (i >= n) {
|
|
n = i + 4096;
|
|
char *p2;
|
|
FAILZERO((p2 = realloc(p, n)));
|
|
p = p2;
|
|
}
|
|
if (usessl) {
|
|
if ((rc = mbedtls_ssl_read(&ssl, p + i, n - i)) < 0) {
|
|
if (rc == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
|
|
rc = 0;
|
|
} else {
|
|
goto OnFailure;
|
|
}
|
|
}
|
|
} else {
|
|
FAILNEG1((rc = read(f.sock, p + i, n - i)));
|
|
}
|
|
g = rc;
|
|
i += g;
|
|
switch (t) {
|
|
case kHttpClientStateHeaders:
|
|
if ((rc = ParseHttpMessage(&msg, p, i, n)) == -1)
|
|
goto BadMessage;
|
|
if (rc) {
|
|
hdrlen = rc;
|
|
|
|
// handle provisional responses
|
|
if (100 <= msg.status && msg.status <= 199) {
|
|
DestroyHttpMessage(&msg);
|
|
InitHttpMessage(&msg, kHttpResponse);
|
|
memmove(p, p + hdrlen, i - hdrlen);
|
|
i -= hdrlen;
|
|
break;
|
|
}
|
|
|
|
// handle redirects
|
|
if (300 <= msg.status && msg.status <= 399) {
|
|
if (!HasHeader(kHttpLocation))
|
|
goto BadMessage;
|
|
if (HeaderLength(kHttpLocation) &&
|
|
HeaderData(kHttpLocation)[0] == '/') {
|
|
FAILNEG1(appends(&url2, usessl ? "https://" : "http://"));
|
|
FAILNEG1(appends(&url2, host));
|
|
FAILNEG1(appends(&url2, ":"));
|
|
FAILNEG1(appends(&url2, port));
|
|
}
|
|
FAILNEG1(appendd(&url2, HeaderData(kHttpLocation),
|
|
HeaderLength(kHttpLocation)));
|
|
close(f.sock);
|
|
f.sock = -1;
|
|
free(p);
|
|
p = 0;
|
|
retcode = PerformFetch(response_data, url2, tries + 1);
|
|
if (retcode == -1 && errno == EINVAL)
|
|
errno = EBADMSG;
|
|
goto OnReturn;
|
|
}
|
|
|
|
// handle empty responses
|
|
if (method == kHttpHead || msg.status == 204 || msg.status == 304)
|
|
goto Finished;
|
|
|
|
// handle content
|
|
if (HasHeader(kHttpTransferEncoding) &&
|
|
!HeaderEqualCase(kHttpTransferEncoding, "identity")) {
|
|
if (!HeaderEqualCase(kHttpTransferEncoding, "chunked"))
|
|
goto BadMessage;
|
|
t = kHttpClientStateBodyChunked;
|
|
memset(&u, 0, sizeof(u));
|
|
goto Chunked;
|
|
} else if (HasHeader(kHttpContentLength)) {
|
|
if ((rc = ParseContentLength(HeaderData(kHttpContentLength),
|
|
HeaderLength(kHttpContentLength))) ==
|
|
-1)
|
|
goto BadMessage;
|
|
t = kHttpClientStateBodyLengthed;
|
|
paylen = rc;
|
|
if (paylen > i - hdrlen) {
|
|
FAILNEG1(appendd(response_data, p + hdrlen, i - hdrlen));
|
|
} else {
|
|
FAILNEG1(appendd(response_data, p + hdrlen, paylen));
|
|
goto Finished;
|
|
}
|
|
} else {
|
|
t = kHttpClientStateBody;
|
|
FAILNEG1(appendd(response_data, p + hdrlen, i - hdrlen));
|
|
}
|
|
}
|
|
break;
|
|
case kHttpClientStateBody:
|
|
FAILNEG1(appendd(response_data, p + i - g, g));
|
|
if (!g)
|
|
goto Finished;
|
|
break;
|
|
case kHttpClientStateBodyLengthed:
|
|
if (i - hdrlen > paylen)
|
|
g = hdrlen + paylen - (i - g);
|
|
FAILNEG1(appendd(response_data, p + i - g, g));
|
|
if (i - hdrlen >= paylen)
|
|
goto Finished;
|
|
break;
|
|
case kHttpClientStateBodyChunked:
|
|
Chunked:
|
|
if ((rc = Unchunk(&u, p + hdrlen, i - hdrlen, &paylen)) == -1)
|
|
goto BadMessage;
|
|
if (rc) {
|
|
FAILNEG1(appendd(response_data, p + hdrlen, paylen));
|
|
goto Finished;
|
|
}
|
|
break;
|
|
default:
|
|
goto BadMessage;
|
|
}
|
|
}
|
|
|
|
// we're done
|
|
Finished:
|
|
retcode = msg.status;
|
|
OnReturn:
|
|
if (f.sock != -1)
|
|
close(f.sock);
|
|
free(p);
|
|
free(url2);
|
|
free(host);
|
|
free(port);
|
|
free(urldata);
|
|
free(request);
|
|
free(funpath);
|
|
free(url.params.p);
|
|
mbedtls_ssl_free(&ssl);
|
|
mbedtls_ssl_config_free(&conf);
|
|
mbedtls_ctr_drbg_free(&drbg);
|
|
DestroyHttpMessage(&msg);
|
|
return retcode;
|
|
OnFailure:
|
|
retcode = -1;
|
|
goto OnReturn;
|
|
OutOfMemory:
|
|
errno = ENOMEM;
|
|
goto OnFailure;
|
|
BadParameter:
|
|
errno = EINVAL;
|
|
goto OnFailure;
|
|
BadMessage:
|
|
errno = EBADMSG;
|
|
goto OnFailure;
|
|
AccessDenied:
|
|
errno = EPERM;
|
|
goto OnFailure;
|
|
}
|
|
|
|
/**
|
|
* Appends content at URL to `response_data`.
|
|
*
|
|
* - http and https URLs are supported.
|
|
* - Chunked responses will be decoded.
|
|
* - 30x redirect codes will be followed.
|
|
*
|
|
* @return http status code on success, or -1 w/ errno
|
|
* @raise EINVAL if URL parsing failed
|
|
* @raise EPERM if SSL handshake failed
|
|
* @raise ENOMEM if we ran out of memory
|
|
* @raise ENETUNREACH if DNS lookup failed
|
|
* @raise EBADMSG if HTTP message parsing broke
|
|
* @raise ELOOP if more than ten redirects happened
|
|
*/
|
|
int AppendFetch(char **response_data, const char *url) {
|
|
int sav = errno;
|
|
int res = PerformFetch(response_data, url, 0);
|
|
if (res != -1)
|
|
errno = sav;
|
|
return res;
|
|
}
|