diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | README | 1 | ||||
| -rw-r--r-- | chat.c | 2 | ||||
| -rw-r--r-- | chat.h | 5 | ||||
| -rw-r--r-- | handle.c | 8 | ||||
| -rw-r--r-- | input.c | 17 | ||||
| -rw-r--r-- | irc.c | 12 | ||||
| -rw-r--r-- | ui.c | 13 | ||||
| -rw-r--r-- | url.c | 95 | 
9 files changed, 145 insertions, 10 deletions
| @@ -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 @@ -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 @@ -31,7 +31,7 @@  static void sigint(int sig) {  	(void)sig;  	input("/quit"); -	uiHide(); +	uiExit();  	exit(EX_OK);  } @@ -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); @@ -141,11 +141,12 @@ static void handle332(char *prefix, char *params) {  	shift(¶ms);  	char *chan = shift(¶ms);  	char *topic = shift(¶ms); +	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(¶ms);  	char *topic = shift(¶ms); +	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(¶ms);  	char *mesg = shift(¶ms);  	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(¶ms);  	if (strcmp(chat.chan, chan)) return;  	tabTouch(nick); +	urlScan(mesg);  	uiFmt("-\3%d%s\3- %s", color(user), nick, mesg);  } @@ -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(¶ms, "-,"), NULL, 0); +	if (!params) { urlOpen(from); return; } +	size_t to = strtoul(strsep(¶ms, "-,"), 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]); @@ -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; @@ -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; @@ -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]); +} | 
