commit 985ba08eccf5127729d7b0904cca31ed3ffd575a
parent 04cee379643425520034c1262e58aa52f428a6a1
Author: phoebos <ben@bvnf.space>
Date: Thu, 6 Jan 2022 22:00:42 +0000
add basic gemini support
note: does not verify certificates at all
Diffstat:
M | hurl.c | | | 157 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 157 insertions(+), 0 deletions(-)
diff --git a/hurl.c b/hurl.c
@@ -692,6 +692,149 @@ err:
return ret;
}
+
+int
+gemini_request(void)
+{
+ struct tls *t;
+ char buf[READ_BUF_SIZ], *p;
+ const char *errstr, *path;
+ size_t len = 0;
+ ssize_t r;
+ int fd = -1, ret = 1, geminiok = 0;
+
+ if (pledge("stdio dns inet rpath unveil", NULL) == -1)
+ err(1, "pledge");
+
+ if (!(t = tls_client())) {
+ errstr = tls_error(t);
+ fprintf(stderr, "tls_client: %s\n", errstr ? errstr : "");
+ goto err;
+ }
+ if (tls_configure(t, tls_config)) {
+ errstr = tls_error(t);
+ fprintf(stderr, "tls_configure: %s\n", errstr ? errstr : "");
+ goto err;
+ }
+ tls_config_insecure_noverifycert(tls_config);
+ tls_config_insecure_noverifyname(tls_config);
+
+ fd = edial(u.host, u.port);
+ if (tls_connect_socket(t, fd, u.host) == -1)
+ errx(1, "tls_connect: %s", tls_error(t));
+
+ if (pledge("stdio", NULL) == -1)
+ err(1, "pledge");
+
+ r = snprintf(buf, sizeof(buf), "%s%s%s%s%s\r\n",
+ u.proto,
+ u.host,
+ u.path[0] ? u.path : "/",
+ u.query[0] ? "?" : "", u.query);
+
+ if (r < 0 || (size_t)r >= sizeof(buf)) {
+ fprintf(stderr, "not writing header because it is truncated");
+ goto err;
+ }
+
+ for (len = r, p = buf; len > 0; ) {
+ r = tls_write(t, p, len);
+ if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
+ continue;
+ } else if (r == -1) {
+ fprintf(stderr, "tls_write: %s\n", tls_error(t));
+ goto err;
+ }
+ p += r;
+ len -= r;
+ }
+
+ for (len = 0; len < sizeof(buf);) {
+ /* NOTE: buffer size is -1 to NUL terminate the buffer for a
+ string comparison. */
+ r = tls_read(t, &buf[len], sizeof(buf) - len - 1);
+ if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
+ continue;
+ } else if (r == 0) {
+ break;
+ } else if (r == -1) {
+ errstr = tls_error(t);
+ fprintf(stderr, "tls_read: %s\n", errstr ? errstr : "");
+ goto err;
+ }
+ len += r;
+ }
+ buf[len] = '\0';
+
+ if (!strncmp(buf, "20", sizeof("20") - 1))
+ geminiok = 1;
+
+ if (!(p = strstr(buf, "\r\n"))) {
+ fprintf(stderr, "no gemini header found or header too big\n");
+ goto err;
+ }
+ *p = '\0'; /* NUL terminate header part */
+ //cs = parse_content_length(buf, &expectedlen);
+ p += strlen("\r\n");
+ //bodylen = len - (p - buf); /* (partial) body after header */
+
+ if (geminiok) {
+ int n = len - (p - buf);
+ r = fwrite(p, 1, n, stdout);
+ if (ferror(stdout)) {
+ fprintf(stderr, "fwrite: stdout: %s\n", strerror(errno));
+ goto err;
+ }
+ } else {
+ /* if not 20 print header */
+ fputs(buf, stderr);
+ fputs("\r\n", stderr);
+ /* NOTE: we are nice and keep reading (not closing) until the server is done. */
+ }
+
+ while (1) {
+ r = tls_read(t, &buf, sizeof(buf));
+ if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
+ continue;
+ } else if (r == 0) {
+ break;
+ } else if (r == -1) {
+ errstr = tls_error(t);
+ fprintf(stderr, "tls_read: %s\n", errstr ? errstr : "");
+ goto err;
+ }
+ len += r;
+
+ if (geminiok) {
+ r = fwrite(buf, 1, r, stdout);
+ if (ferror(stdout)) {
+ fprintf(stderr, "fwrite: stdout: %s\n", strerror(errno));
+ goto err;
+ }
+ }
+
+ if (config_maxresponsesiz && len >= config_maxresponsesiz)
+ break;
+
+ }
+ if (config_maxresponsesiz && len >= config_maxresponsesiz) {
+ fprintf(stderr, "response too big: %zu >= %zu\n",
+ len, config_maxresponsesiz);
+ goto err;
+ }
+ ret = 0;
+
+err:
+ if (t) {
+ tls_close(t);
+ tls_free(t);
+ }
+
+ if (fd != -1)
+ close(fd);
+ return geminiok ? ret : 2;
+}
+
void
usage(void)
{
@@ -783,6 +926,20 @@ main(int argc, char **argv)
if (!u.port[0])
memcpy(u.port, "70", 3);
statuscode = gophers_request();
+ } else if (!strcmp(u.proto, "gemini://")) {
+ if (tls_init())
+ errx(1, "tls_init failed");
+ if (!(tls_config = tls_config_new()))
+ errx(1, "tls config failed");
+ if (config_legacy) {
+ /* enable legacy cipher and negotiation. */
+ if (tls_config_set_ciphers(tls_config, "legacy"))
+ errx(1, "tls set ciphers failed: %s",
+ tls_config_error(tls_config));
+ }
+ if (!u.port[0])
+ memcpy(u.port, "1965", 5);
+ statuscode = gemini_request();
} else {
if (u.proto[0])
errx(1, "unsupported protocol specified: %s", u.proto);