mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
6f7d0cb1c3
This makes breaking changes to add underscores to many non-standard function names provided by the c library. MODE=tiny is now tinier and we now use smaller locks that are better for tiny apps in this mode. Some headers have been renamed to be in the same folder as the build package, so it'll be easier to know which build dependency is needed. Certain old misguided interfaces have been removed. Intel intrinsics headers are now listed in libc/isystem (but not in the amalgamation) to help further improve open source compatibility. Header complexity has also been reduced. Lastly, more shell scripts are now available.
419 lines
12 KiB
C
419 lines
12 KiB
C
#if 0
|
|
/*─────────────────────────────────────────────────────────────────╗
|
|
│ To the extent possible under law, Justine Tunney has waived │
|
|
│ all copyright and related or neighboring rights to this file, │
|
|
│ as it is written in the following disclaimers: │
|
|
│ • http://unlicense.org/ │
|
|
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
|
╚─────────────────────────────────────────────────────────────────*/
|
|
#endif
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/struct/iovec.h"
|
|
#include "libc/dce.h"
|
|
#include "libc/dns/dns.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/conv.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/intrin/safemacros.internal.h"
|
|
#include "libc/log/check.h"
|
|
#include "libc/log/log.h"
|
|
#include "libc/macros.internal.h"
|
|
#include "libc/mem/gc.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/sock/goodsocket.internal.h"
|
|
#include "libc/sock/sock.h"
|
|
#include "libc/stdio/append.h"
|
|
#include "libc/stdio/rand.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/slice.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/af.h"
|
|
#include "libc/sysv/consts/dt.h"
|
|
#include "libc/sysv/consts/ex.h"
|
|
#include "libc/sysv/consts/exit.h"
|
|
#include "libc/sysv/consts/ipproto.h"
|
|
#include "libc/sysv/consts/shut.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/sysv/consts/so.h"
|
|
#include "libc/sysv/consts/sock.h"
|
|
#include "libc/sysv/consts/sol.h"
|
|
#include "libc/sysv/consts/tcp.h"
|
|
#include "libc/time/struct/tm.h"
|
|
#include "libc/x/x.h"
|
|
#include "libc/x/xsigaction.h"
|
|
#include "net/http/http.h"
|
|
#include "net/http/url.h"
|
|
#include "net/https/https.h"
|
|
#include "net/https/sslcache.h"
|
|
#include "third_party/getopt/getopt.h"
|
|
#include "third_party/mbedtls/ctr_drbg.h"
|
|
#include "third_party/mbedtls/debug.h"
|
|
#include "third_party/mbedtls/error.h"
|
|
#include "third_party/mbedtls/pk.h"
|
|
#include "third_party/mbedtls/ssl.h"
|
|
#include "third_party/mbedtls/ssl_ticket.h"
|
|
|
|
/**
|
|
* @fileoverview Downloads HTTP URL to stdout.
|
|
*
|
|
* make -j8 o//examples/curl.com
|
|
* o//examples/curl.com http://justine.lol/ape.html
|
|
*/
|
|
|
|
#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))
|
|
|
|
int sock;
|
|
|
|
static bool TuneSocket(int fd, int a, int b, int x) {
|
|
if (!b) return false;
|
|
return setsockopt(fd, a, b, &x, sizeof(x)) != -1;
|
|
}
|
|
|
|
static void Write(const void *p, size_t n) {
|
|
ssize_t rc;
|
|
rc = write(1, p, n);
|
|
if (rc == -1 && errno == EPIPE) {
|
|
close(sock);
|
|
exit(128 + SIGPIPE);
|
|
}
|
|
if (rc != n) {
|
|
fprintf(stderr, "write failed: %m\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static int TlsSend(void *c, const unsigned char *p, size_t n) {
|
|
int rc;
|
|
NOISEF("begin send %zu", n);
|
|
CHECK_NE(-1, (rc = write(*(int *)c, p, n)));
|
|
NOISEF("end send %zu", n);
|
|
return rc;
|
|
}
|
|
|
|
static int TlsRecv(void *c, unsigned char *p, size_t n, uint32_t o) {
|
|
int r;
|
|
struct iovec v[2];
|
|
static unsigned a, b;
|
|
static unsigned char t[4096];
|
|
if (a < b) {
|
|
r = MIN(n, b - a);
|
|
memcpy(p, t + a, r);
|
|
if ((a += r) == b) a = b = 0;
|
|
return r;
|
|
}
|
|
v[0].iov_base = p;
|
|
v[0].iov_len = n;
|
|
v[1].iov_base = t;
|
|
v[1].iov_len = sizeof(t);
|
|
NOISEF("begin recv %zu", n + sizeof(t) - b);
|
|
CHECK_NE(-1, (r = readv(*(int *)c, v, 2)));
|
|
NOISEF("end recv %zu", r);
|
|
if (r > n) b = r - n;
|
|
return MIN(n, r);
|
|
}
|
|
|
|
static wontreturn void PrintUsage(FILE *f, int rc) {
|
|
fprintf(f, "usage: %s [-ksvV] URL\n", program_invocation_name);
|
|
exit(rc);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (!NoDebug()) ShowCrashReports();
|
|
|
|
/*
|
|
* Read flags.
|
|
*/
|
|
int opt;
|
|
struct Headers {
|
|
size_t n;
|
|
char **p;
|
|
} headers = {0};
|
|
int method = kHttpGet;
|
|
bool authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
|
|
const char *agent = "hurl/1.o (https://github.com/jart/cosmopolitan)";
|
|
__log_level = kLogWarn;
|
|
while ((opt = getopt(argc, argv, "qksvVIX:H:A:")) != -1) {
|
|
switch (opt) {
|
|
case 's':
|
|
case 'q':
|
|
break;
|
|
case 'v':
|
|
++__log_level;
|
|
break;
|
|
case 'I':
|
|
method = kHttpHead;
|
|
break;
|
|
case 'A':
|
|
agent = optarg;
|
|
break;
|
|
case 'H':
|
|
headers.p = realloc(headers.p, ++headers.n * sizeof(*headers.p));
|
|
headers.p[headers.n - 1] = optarg;
|
|
break;
|
|
case 'X':
|
|
CHECK((method = GetHttpMethod(optarg, strlen(optarg))));
|
|
break;
|
|
case 'V':
|
|
++mbedtls_debug_threshold;
|
|
break;
|
|
case 'k':
|
|
authmode = MBEDTLS_SSL_VERIFY_NONE;
|
|
break;
|
|
case 'h':
|
|
PrintUsage(stdout, EXIT_SUCCESS);
|
|
default:
|
|
PrintUsage(stderr, EX_USAGE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get argument.
|
|
*/
|
|
const char *urlarg;
|
|
if (optind == argc) PrintUsage(stderr, EX_USAGE);
|
|
urlarg = argv[optind];
|
|
|
|
/*
|
|
* Parse URL.
|
|
*/
|
|
struct Url url;
|
|
char *host, *port;
|
|
bool usessl = false;
|
|
_gc(ParseUrl(urlarg, -1, &url));
|
|
_gc(url.params.p);
|
|
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))) {
|
|
fprintf(stderr, "error: not an http/https url: %s\n", urlarg);
|
|
exit(1);
|
|
}
|
|
}
|
|
if (url.host.n) {
|
|
host = _gc(strndup(url.host.p, url.host.n));
|
|
if (url.port.n) {
|
|
port = _gc(strndup(url.port.p, url.port.n));
|
|
} else {
|
|
port = usessl ? "443" : "80";
|
|
}
|
|
} else {
|
|
host = "127.0.0.1";
|
|
port = usessl ? "443" : "80";
|
|
}
|
|
if (!IsAcceptableHost(host, -1)) {
|
|
fprintf(stderr, "error: invalid host: %s\n", urlarg);
|
|
exit(1);
|
|
}
|
|
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] != '/') {
|
|
char *p = _gc(xmalloc(1 + url.path.n));
|
|
mempcpy(mempcpy(p, "/", 1), url.path.p, url.path.n);
|
|
url.path.p = p;
|
|
++url.path.n;
|
|
}
|
|
|
|
/*
|
|
* Create HTTP message.
|
|
*/
|
|
char *request = 0;
|
|
appendf(&request,
|
|
"%s %s HTTP/1.1\r\n"
|
|
"Host: %s:%s\r\n"
|
|
"Connection: close\r\n"
|
|
"User-Agent: %s\r\n",
|
|
kHttpMethod[method], _gc(EncodeUrl(&url, 0)), host, port, agent);
|
|
for (int i = 0; i < headers.n; ++i) {
|
|
appendf(&request, "%s\r\n", headers.p[i]);
|
|
}
|
|
appendf(&request, "\r\n");
|
|
|
|
/*
|
|
* Setup crypto.
|
|
*/
|
|
mbedtls_ssl_config conf;
|
|
mbedtls_ssl_context ssl;
|
|
mbedtls_ctr_drbg_context drbg;
|
|
if (usessl) {
|
|
mbedtls_ssl_init(&ssl);
|
|
mbedtls_ctr_drbg_init(&drbg);
|
|
mbedtls_ssl_config_init(&conf);
|
|
CHECK_EQ(0, mbedtls_ctr_drbg_seed(&drbg, GetEntropy, 0, "justine", 7));
|
|
CHECK_EQ(0, mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
|
|
MBEDTLS_SSL_TRANSPORT_STREAM,
|
|
MBEDTLS_SSL_PRESET_DEFAULT));
|
|
mbedtls_ssl_conf_authmode(&conf, authmode);
|
|
mbedtls_ssl_conf_ca_chain(&conf, GetSslRoots(), 0);
|
|
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &drbg);
|
|
if (!IsTiny()) mbedtls_ssl_conf_dbg(&conf, TlsDebug, 0);
|
|
CHECK_EQ(0, mbedtls_ssl_setup(&ssl, &conf));
|
|
CHECK_EQ(0, mbedtls_ssl_set_hostname(&ssl, host));
|
|
}
|
|
|
|
/*
|
|
* Perform DNS lookup.
|
|
*/
|
|
struct addrinfo *addr;
|
|
struct addrinfo hints = {.ai_family = AF_INET,
|
|
.ai_socktype = SOCK_STREAM,
|
|
.ai_protocol = IPPROTO_TCP,
|
|
.ai_flags = AI_NUMERICSERV};
|
|
CHECK_EQ(EAI_SUCCESS, getaddrinfo(host, port, &hints, &addr));
|
|
|
|
/*
|
|
* Connect to server.
|
|
*/
|
|
int ret;
|
|
CHECK_NE(-1, (sock = GoodSocket(addr->ai_family, addr->ai_socktype,
|
|
addr->ai_protocol, false,
|
|
&(struct timeval){-60})));
|
|
CHECK_NE(-1, connect(sock, addr->ai_addr, addr->ai_addrlen));
|
|
freeaddrinfo(addr);
|
|
if (usessl) {
|
|
mbedtls_ssl_set_bio(&ssl, &sock, TlsSend, 0, TlsRecv);
|
|
if ((ret = mbedtls_ssl_handshake(&ssl))) {
|
|
TlsDie("ssl handshake", ret);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send HTTP Message.
|
|
*/
|
|
size_t n;
|
|
n = appendz(request).i;
|
|
if (usessl) {
|
|
ret = mbedtls_ssl_write(&ssl, request, n);
|
|
if (ret != n) TlsDie("ssl write", ret);
|
|
} else {
|
|
CHECK_EQ(n, write(sock, request, n));
|
|
}
|
|
|
|
xsigaction(SIGPIPE, SIG_IGN, 0, 0, 0);
|
|
|
|
/*
|
|
* Handle response.
|
|
*/
|
|
int t;
|
|
char *p;
|
|
ssize_t rc;
|
|
struct HttpMessage msg;
|
|
struct HttpUnchunker u;
|
|
size_t g, i, hdrlen, paylen;
|
|
InitHttpMessage(&msg, kHttpResponse);
|
|
for (p = 0, hdrlen = paylen = t = i = n = 0;;) {
|
|
if (i == n) {
|
|
n += 1000;
|
|
n += n >> 1;
|
|
p = realloc(p, n);
|
|
}
|
|
if (usessl) {
|
|
if ((rc = mbedtls_ssl_read(&ssl, p + i, n - i)) < 0) {
|
|
if (rc == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
|
|
rc = 0;
|
|
} else {
|
|
TlsDie("ssl read", rc);
|
|
}
|
|
}
|
|
} else {
|
|
CHECK_NE(-1, (rc = read(sock, p + i, n - i)));
|
|
}
|
|
g = rc;
|
|
i += g;
|
|
switch (t) {
|
|
case kHttpClientStateHeaders:
|
|
CHECK(g);
|
|
CHECK_NE(-1, (rc = ParseHttpMessage(&msg, p, i)));
|
|
if (rc) {
|
|
hdrlen = rc;
|
|
if (100 <= msg.status && msg.status <= 199) {
|
|
CHECK(!HasHeader(kHttpContentLength) ||
|
|
HeaderEqualCase(kHttpContentLength, "0"));
|
|
CHECK(!HasHeader(kHttpTransferEncoding) ||
|
|
HeaderEqualCase(kHttpTransferEncoding, "identity"));
|
|
DestroyHttpMessage(&msg);
|
|
InitHttpMessage(&msg, kHttpResponse);
|
|
memmove(p, p + hdrlen, i - hdrlen);
|
|
i -= hdrlen;
|
|
break;
|
|
}
|
|
if (method == kHttpHead) {
|
|
Write(p, hdrlen);
|
|
goto Finished;
|
|
} else if (msg.status == 204 || msg.status == 304) {
|
|
goto Finished;
|
|
}
|
|
if (HasHeader(kHttpTransferEncoding) &&
|
|
!HeaderEqualCase(kHttpTransferEncoding, "identity")) {
|
|
CHECK(HeaderEqualCase(kHttpTransferEncoding, "chunked"));
|
|
t = kHttpClientStateBodyChunked;
|
|
memset(&u, 0, sizeof(u));
|
|
goto Chunked;
|
|
} else if (HasHeader(kHttpContentLength)) {
|
|
CHECK_NE(-1, (rc = ParseContentLength(
|
|
HeaderData(kHttpContentLength),
|
|
HeaderLength(kHttpContentLength))));
|
|
t = kHttpClientStateBodyLengthed;
|
|
paylen = rc;
|
|
if (paylen > i - hdrlen) {
|
|
Write(p + hdrlen, i - hdrlen);
|
|
} else {
|
|
Write(p + hdrlen, paylen);
|
|
goto Finished;
|
|
}
|
|
} else {
|
|
t = kHttpClientStateBody;
|
|
Write(p + hdrlen, i - hdrlen);
|
|
}
|
|
}
|
|
break;
|
|
case kHttpClientStateBody:
|
|
if (!g) goto Finished;
|
|
Write(p + i - g, g);
|
|
break;
|
|
case kHttpClientStateBodyLengthed:
|
|
CHECK(g);
|
|
if (i - hdrlen > paylen) g = hdrlen + paylen - (i - g);
|
|
Write(p + i - g, g);
|
|
if (i - hdrlen >= paylen) goto Finished;
|
|
break;
|
|
case kHttpClientStateBodyChunked:
|
|
Chunked:
|
|
CHECK_NE(-1, (rc = Unchunk(&u, p + hdrlen, i - hdrlen, &paylen)));
|
|
if (rc) {
|
|
Write(p + hdrlen, paylen);
|
|
goto Finished;
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Close connection.
|
|
*/
|
|
Finished:
|
|
CHECK_NE(-1, close(sock));
|
|
|
|
/*
|
|
* Free memory.
|
|
*/
|
|
free(p);
|
|
free(headers.p);
|
|
if (usessl) {
|
|
mbedtls_ssl_free(&ssl);
|
|
mbedtls_ctr_drbg_free(&drbg);
|
|
mbedtls_ssl_config_free(&conf);
|
|
mbedtls_ctr_drbg_free(&drbg);
|
|
}
|
|
|
|
return 0;
|
|
}
|