summary refs log tree commit diff
diff options
context:
space:
mode:
authorCurtis McEnroe2018-08-09 00:24:49 -0400
committerCurtis McEnroe2018-08-09 00:24:49 -0400
commita64f1a4ea2962e534673e27d85d92703c64201b0 (patch)
tree1bcf8ffdf2ae7280b406fe021619e9c61b83de6b
parentc024147504a4a1b3a6b1e40f5120bc57248f0531 (diff)
Add URL detection, listing and opening
Might also add /copy, like /open.
-rw-r--r--Makefile2
-rw-r--r--README1
-rw-r--r--chat.c2
-rw-r--r--chat.h5
-rw-r--r--handle.c8
-rw-r--r--input.c17
-rw-r--r--irc.c12
-rw-r--r--ui.c13
-rw-r--r--url.c95
9 files changed, 145 insertions, 10 deletions
diff --git a/Makefile b/Makefile
index 4a956b2..57369d6 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic
 CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include
 LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib
 LDLIBS = -lcursesw -ltls
-OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o ui.o
+OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o ui.o url.o
 
 all: tags chat
 
diff --git a/README b/README
index b94a55a..8fa4ad7 100644
--- a/README
+++ b/README
@@ -10,4 +10,5 @@ This software requires LibreSSL and targets FreeBSD and Darwin.
 	input.c     Input command handling
 	handle.c    Incoming command handling
 	tab.c       Tab-complete
+	url.c       URL detection
 	pls.c       Functions which should not have to be written
diff --git a/chat.c b/chat.c
index 4d3f505..b966fd1 100644
--- a/chat.c
+++ b/chat.c
@@ -31,7 +31,7 @@
 static void sigint(int sig) {
 	(void)sig;
 	input("/quit");
-	uiHide();
+	uiExit();
 	exit(EX_OK);
 }
 
diff --git a/chat.h b/chat.h
index 3ded5db..3a84b6c 100644
--- a/chat.h
+++ b/chat.h
@@ -50,6 +50,7 @@ void ircFmt(const char *format, ...);
 
 void uiInit(void);
 void uiHide(void);
+void uiExit(void);
 void uiDraw(void);
 void uiBeep(void);
 void uiRead(void);
@@ -77,6 +78,10 @@ void handle(char *line);
 void inputTab(void);
 void input(char *line);
 
+void urlScan(const char *str);
+void urlList(void);
+void urlOpen(size_t i);
+
 void tabTouch(const char *word);
 void tabRemove(const char *word);
 void tabReplace(const char *prev, const char *next);
diff --git a/handle.c b/handle.c
index 10bce42..f6b3e5d 100644
--- a/handle.c
+++ b/handle.c
@@ -141,11 +141,12 @@ static void handle332(char *prefix, char *params) {
 	shift(&params);
 	char *chan = shift(&params);
 	char *topic = shift(&params);
+	urlScan(topic);
+	uiTopicStr(topic);
 	uiFmt(
 		"The sign in \3%d%s\3 reads, \"%s\"",
 		color(chan), chan, topic
 	);
-	uiTopicStr(topic);
 }
 
 static void handleTopic(char *prefix, char *params) {
@@ -153,11 +154,12 @@ static void handleTopic(char *prefix, char *params) {
 	char *user = prift(&prefix);
 	char *chan = shift(&params);
 	char *topic = shift(&params);
+	urlScan(topic);
+	uiTopicStr(topic);
 	uiFmt(
 		"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
 		color(user), nick, color(chan), chan, topic
 	);
-	uiTopicStr(topic);
 }
 
 static void handle366(char *prefix, char *params) {
@@ -222,6 +224,7 @@ static void handlePrivmsg(char *prefix, char *params) {
 	shift(&params);
 	char *mesg = shift(&params);
 	tabTouch(nick);
+	urlScan(mesg);
 	bool self = !strcmp(user, chat.user);
 	bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick));
 	if (ping) uiBeep();
@@ -244,6 +247,7 @@ static void handleNotice(char *prefix, char *params) {
 	char *mesg = shift(&params);
 	if (strcmp(chat.chan, chan)) return;
 	tabTouch(nick);
+	urlScan(mesg);
 	uiFmt("-\3%d%s\3- %s", color(user), nick, mesg);
 }
 
diff --git a/input.c b/input.c
index 0ac64f5..64102e2 100644
--- a/input.c
+++ b/input.c
@@ -73,6 +73,21 @@ static void inputQuit(char *params) {
 	}
 }
 
+static void inputUrl(char *params) {
+	(void)params;
+	urlList();
+}
+static void inputOpen(char *params) {
+	if (!params) { urlOpen(1); return; }
+	size_t from = strtoul(strsep(&params, "-,"), NULL, 0);
+	if (!params) { urlOpen(from); return; }
+	size_t to = strtoul(strsep(&params, "-,"), NULL, 0);
+	if (to < from) to = from;
+	for (size_t i = from; i <= to; ++i) {
+		urlOpen(i);
+	}
+}
+
 static const struct {
 	const char *command;
 	Handler handler;
@@ -80,8 +95,10 @@ static const struct {
 	{ "/me", inputMe },
 	{ "/names", inputWho },
 	{ "/nick", inputNick },
+	{ "/open", inputOpen },
 	{ "/quit", inputQuit },
 	{ "/topic", inputTopic },
+	{ "/url", inputUrl },
 	{ "/who", inputWho },
 };
 static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]);
diff --git a/irc.c b/irc.c
index 02a9f64..b718b13 100644
--- a/irc.c
+++ b/irc.c
@@ -15,16 +15,17 @@
  */
 
 #include <err.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/socket.h>
 #include <sysexits.h>
 #include <tls.h>
 #include <unistd.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
 
 #include "chat.h"
 
@@ -68,6 +69,9 @@ int ircConnect(const char *host, const char *port, const char *webPass) {
 	int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
 	if (sock < 0) err(EX_OSERR, "socket");
 
+	error = fcntl(sock, F_SETFD, FD_CLOEXEC);
+	if (error) err(EX_IOERR, "fcntl");
+
 	error = connect(sock, ai->ai_addr, ai->ai_addrlen);
 	if (error) err(EX_UNAVAILABLE, "connect");
 	freeaddrinfo(ai);
@@ -111,7 +115,7 @@ void ircRead(void) {
 	ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len);
 	if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client));
 	if (!read) {
-		uiHide();
+		uiExit();
 		exit(EX_OK);
 	}
 	len += read;
diff --git a/ui.c b/ui.c
index be7fc15..9778473 100644
--- a/ui.c
+++ b/ui.c
@@ -99,8 +99,9 @@ static struct {
 	WINDOW *topic;
 	WINDOW *log;
 	WINDOW *input;
-	int scroll;
+	bool hide;
 	bool mark;
+	int scroll;
 } ui;
 
 void uiInit(void) {
@@ -135,8 +136,13 @@ static void uiResize(void) {
 }
 
 void uiHide(void) {
-	focusDisable();
+	ui.hide = true;
 	endwin();
+}
+
+void uiExit(void) {
+	uiHide();
+	focusDisable();
 	printf(
 		"This program is AGPLv3 free software!\n"
 		"The source is available at <" SOURCE_URL ">.\n"
@@ -144,6 +150,7 @@ void uiHide(void) {
 }
 
 void uiDraw(void) {
+	if (ui.hide) return;
 	pnoutrefresh(
 		ui.topic,
 		0, 0,
@@ -359,6 +366,8 @@ static bool keyCode(wint_t ch) {
 }
 
 void uiRead(void) {
+	ui.hide = false;
+
 	bool update = false;
 	int ret;
 	wint_t ch;
diff --git a/url.c b/url.c
new file mode 100644
index 0000000..33652ff
--- /dev/null
+++ b/url.c
@@ -0,0 +1,95 @@
+/* Copyright (C) 2018  Curtis McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "chat.h"
+
+static const char *SCHEMES[] = {
+	"https:",
+	"http:",
+	"ftp:",
+};
+static const size_t SCHEMES_LEN = sizeof(SCHEMES) / sizeof(SCHEMES[0]);
+
+enum { RING_LEN = 16 };
+static char *ring[RING_LEN];
+static size_t last;
+static_assert(!(RING_LEN & (RING_LEN - 1)), "power of two RING_LEN");
+
+static void push(const char *url, size_t len) {
+	free(ring[last]);
+	ring[last++] = strndup(url, len);
+	last &= RING_LEN - 1;
+}
+
+void urlScan(const char *str) {
+	while (str[0]) {
+		size_t len = 1;
+		for (size_t i = 0; i < SCHEMES_LEN; ++i) {
+			if (strncmp(str, SCHEMES[i], strlen(SCHEMES[i]))) continue;
+			len = strcspn(str, " >\"");
+			push(str, len);
+		}
+		str = &str[len];
+	}
+}
+
+void urlList(void) {
+	uiHide();
+	for (size_t i = 0; i < RING_LEN; ++i) {
+		char *url = ring[(i + last) & (RING_LEN - 1)];
+		if (url) printf("%s\n", url);
+	}
+}
+
+void urlOpen(size_t i) {
+	char *url = ring[(last - i) & (RING_LEN - 1)];
+	if (!url) return;
+
+	int fd[2];
+	int error = pipe(fd);
+	if (error) err(EX_OSERR, "pipe");
+
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+
+	if (!pid) {
+		close(STDIN_FILENO);
+		dup2(fd[1], STDOUT_FILENO);
+		dup2(fd[1], STDERR_FILENO);
+		execlp("open", "open", url, NULL);
+		perror("open");
+		exit(EX_CONFIG);
+	}
+	close(fd[1]);
+
+	// FIXME: This should technically go on the main event loop.
+	char buf[256];
+	ssize_t len = read(fd[0], buf, sizeof(buf) - 1);
+	if (len < 0) err(EX_IOERR, "read");
+	if (len) {
+		buf[len] = '\0';
+		len = strcspn(buf, "\n");
+		uiFmt("%.*s", (int)len, buf);
+	}
+	close(fd[0]);
+}