jcs
/detritus
/amendments
/19
finger: Add finger support
jcs made amendment 19 about 1 year ago
--- browser.c Sun Nov 3 11:23:08 2024
+++ browser.c Mon Nov 4 09:29:10 2024
@@ -36,10 +36,12 @@ static Pattern fill_pattern;
extern struct uri_handler gemini_handler;
extern struct uri_handler gopher_handler;
+extern struct uri_handler finger_handler;
struct uri_handler * uri_handlers[] = {
&gemini_handler,
&gopher_handler,
+ &finger_handler,
};
bool browser_close(struct focusable *focusable);
--- finger.c Mon Nov 4 10:47:04 2024
+++ finger.c Mon Nov 4 10:47:04 2024
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2024 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and 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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "detritus.h"
+#include "browser.h"
+
+#define FINGER_PORT 79
+
+struct finger_request {
+ unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */
+
+ TCPiopb tcp_iopb;
+ StreamPtr tcp_stream;
+ wdsEntry tcp_wds[2];
+ TCPStatusPB tcp_status_pb;
+
+ char query[128];
+ size_t query_len;
+};
+
+struct URI * finger_parse_uri(char *uristr);
+bool finger_init_request(page_handle pageh);
+bool finger_process(page_handle pageh);
+void finger_cleanup(page_handle pageh);
+
+static bool parse_content(struct page *page);
+
+struct uri_handler finger_handler = {
+ finger_parse_uri,
+ finger_init_request,
+ finger_process,
+ finger_cleanup,
+};
+
+struct URI *
+finger_parse_uri(char *uristr)
+{
+ return parse_uri(uristr, "finger");
+}
+
+bool
+finger_init_request(page_handle pageh)
+{
+ struct page *page;
+ struct finger_request *req = NULL;
+ size_t len;
+ char ip_s[12 + 3 + 1];
+ short err;
+ ip_addr ip, local_ip;
+ tcp_port port, local_port;
+
+ HLock(pageh);
+ page = *pageh;
+ if (page->content_len) {
+ /* already fetched, nothing to do here but reset */
+ page->content_pos = 0;
+ goto done;
+ }
+
+ req = xmalloczero(sizeof(struct finger_request));
+ if (req == NULL) {
+ warn("Out of memory");
+ goto fail;
+ }
+
+ if (page->uri->port == 0)
+ page->uri->port = FINGER_PORT;
+
+ browser_statusf(page->browser, "Resolving \"%s\"...",
+ page->uri->hostname);
+
+ err = DNSResolveName(page->uri->hostname, &ip, NULL);
+ if (err) {
+ browser_statusf(page->browser, "Error: Failed resolving: %d", err);
+ goto fail;
+ }
+
+ long2ip(ip, (char *)&ip_s);
+ browser_statusf(page->browser, "Connecting to %s port %d...", ip_s,
+ page->uri->port);
+
+ err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf,
+ sizeof(req->tcp_buf), NULL, NULL, NULL, false);
+ if (err) {
+ browser_statusf(page->browser, "Error: TCPCreate failed: %d", err);
+ goto fail;
+ }
+
+ err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip,
+ page->uri->port, &local_ip, &local_port, NULL, NULL, false);
+ if (err) {
+ browser_statusf(page->browser,
+ "Error: Failed connecting to %s (%s) port %d: %d",
+ page->uri->hostname, ip_s, page->uri->port, err);
+ goto fail;
+ }
+
+ err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
+ NULL, NULL, false);
+ if (err) {
+ browser_statusf(page->browser,
+ "Error: Failed TCPStatus on connection to %s (%s) port %d: %d",
+ page->uri->hostname, ip_s, page->uri->port, err);
+ goto fail;
+ }
+
+ if (page->uri->path[0] == '\0' ||
+ (page->uri->path[0] == '/' && page->uri->path[1] == '\0'))
+ req->query_len = snprintf(req->query, sizeof(req->query),
+ "\r\n", page->uri->hostname);
+ else
+ req->query_len = snprintf(req->query, sizeof(req->query),
+ "%s\r\n", page->uri->path + 1);
+
+ browser_statusf(page->browser, "Connected to %s, sending request...",
+ page->uri->hostname);
+
+ memset(&req->tcp_wds, 0, sizeof(req->tcp_wds));
+ req->tcp_wds[0].ptr = (Ptr)req->query;
+ req->tcp_wds[0].length = req->query_len;
+
+ err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL,
+ NULL, false);
+ if (err) {
+ browser_statusf(page->browser, "Error: TCPSend failed: %d", err);
+ goto fail;
+ }
+
+ page->handler_cookie = req;
+
+done:
+ HUnlock(pageh);
+ browser_commit_to_page(page->browser, pageh);
+
+ return true;
+
+fail:
+ if (req) {
+ if (req->tcp_stream) {
+ _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ }
+
+ xfree(&req);
+ page->handler_cookie = NULL;
+ }
+
+ HUnlock(pageh);
+ return false;
+}
+
+void
+finger_cleanup(page_handle pageh)
+{
+ struct finger_request *req;
+ struct page *page;
+
+ HLock(pageh);
+ page = *pageh;
+ req = (struct finger_request *)(page->handler_cookie);
+
+ if (req) {
+ if (req->tcp_stream) {
+ _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ }
+
+ xfree(&page->handler_cookie);
+ }
+
+ HUnlock(pageh);
+}
+
+bool
+finger_process(page_handle pageh)
+{
+ struct finger_request *req;
+ struct page *page;
+ size_t size, len, n;
+ short err;
+ unsigned short slen;
+ bool ret;
+
+ HLock(pageh);
+ page = *pageh;
+
+ if (page->handler_cookie == NULL)
+ goto parse;
+
+ req = (struct finger_request *)(page->handler_cookie);
+
+ if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) {
+ BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting",
+ req->tcp_iopb.ioResult));
+ goto finish_request;
+ }
+
+ err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
+ NULL, NULL, false);
+ if (err) {
+ browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err);
+ goto finish_request;
+ }
+
+ if (req->tcp_status_pb.connectionState !=
+ ConnectionStateEstablished) {
+ BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)",
+ req->tcp_status_pb.connectionState));
+ goto finish_request;
+ }
+ if (req->tcp_status_pb.amtUnreadData == 0)
+ goto parse;
+
+ if (page->content_len + req->tcp_status_pb.amtUnreadData >=
+ page->content_size) {
+ if (!browser_grow_page_content(pageh,
+ req->tcp_status_pb.amtUnreadData))
+ return false;
+ HLock(pageh);
+ page = *pageh;
+ req = (struct finger_request *)(page->handler_cookie);
+ }
+
+ slen = req->tcp_status_pb.amtUnreadData;
+ err = _TCPRcv(&req->tcp_iopb, req->tcp_stream,
+ (Ptr)(page->content + page->content_len), &slen, NULL, NULL, false);
+ if (err) {
+ browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err);
+ goto finish_request;
+ }
+ page->content_len += slen;
+ browser_statusf(page->browser, "Read %ld bytes", page->content_len);
+
+ goto parse;
+
+finish_request:
+ finger_cleanup(pageh);
+
+parse:
+ HLock(pageh);
+ page = *pageh;
+
+ if (page->content_pos == page->content_len) {
+ if (!page->handler_cookie) {
+ /* nothing left to do */
+ return false;
+ }
+ HUnlock(pageh);
+ return true;
+ }
+
+ TVAutoCalc(page->browser->output_tv, false);
+ ret = parse_content(page);
+ TVAutoCalc(page->browser->output_tv, true);
+ TVCalcLines(page->browser->output_tv);
+ TVUpdate(page->browser->output_tv);
+ TVUpdateScrollbar(page->browser->output_tv,
+ page->browser->output_tv_scroller);
+
+ if (ret) {
+ HUnlock(pageh);
+ return true;
+ }
+
+ browser_statusf(page->browser, "Finished loading %ld bytes",
+ page->content_len);
+
+ /* release some memory if we can */
+ if (page->content_size > page->content_len) {
+ size = page->content_len;
+ page->content_size = size;
+ HUnlock(pageh);
+ SetHandleSize(pageh, sizeof(struct page) + size);
+ }
+
+request_fail:
+ HUnlock(pageh);
+ return false;
+}
+
+static bool
+parse_content(struct page *page)
+{
+ long n;
+
+ page->browser->style = STYLE_PRE;
+
+ /* text file, convert newlines and display */
+ for (n = page->content_pos; n < page->content_len; n++) {
+ if (page->content[n] == '\r') {
+ if (n > page->content_pos)
+ browser_print(page->browser,
+ page->content + page->content_pos,
+ n - page->content_pos + 1);
+ page->content_pos = n + 1;
+ if (page->content[n + 1] == '\n') {
+ page->content_pos++;
+ n++;
+ }
+ } else if (page->content[n] == '\n') {
+ /* lone \n */
+ if (n > page->content_pos)
+ browser_print(page->browser,
+ page->content + page->content_pos,
+ n - page->content_pos);
+ browser_print(page->browser, "\r", 1);
+ page->content_pos = n + 1;
+ }
+ }
+
+ if (!page->handler_cookie &&
+ page->content_pos < page->content_len) {
+ browser_print(page->browser, page->content + page->content_pos,
+ page->content_len - page->content_pos);
+ page->content_pos = page->content_len;
+ return false;
+ }
+
+ if (page->handler_cookie)
+ return true;
+
+ return false;
+}