Add URL detection, listing and opening
Might also add /copy, like /open.
This commit is contained in:
		
							parent
							
								
									c024147504
								
							
						
					
					
						commit
						a64f1a4ea2
					
				
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic | |||||||
| CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include | CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include | ||||||
| LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib | LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib | ||||||
| LDLIBS = -lcursesw -ltls | 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 | all: tags chat | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								README
									
									
									
									
									
								
							| @ -10,4 +10,5 @@ This software requires LibreSSL and targets FreeBSD and Darwin. | |||||||
| 	input.c     Input command handling | 	input.c     Input command handling | ||||||
| 	handle.c    Incoming command handling | 	handle.c    Incoming command handling | ||||||
| 	tab.c       Tab-complete | 	tab.c       Tab-complete | ||||||
|  | 	url.c       URL detection | ||||||
| 	pls.c       Functions which should not have to be written | 	pls.c       Functions which should not have to be written | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								chat.c
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								chat.c
									
									
									
									
									
								
							| @ -31,7 +31,7 @@ | |||||||
| static void sigint(int sig) { | static void sigint(int sig) { | ||||||
| 	(void)sig; | 	(void)sig; | ||||||
| 	input("/quit"); | 	input("/quit"); | ||||||
| 	uiHide(); | 	uiExit(); | ||||||
| 	exit(EX_OK); | 	exit(EX_OK); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								chat.h
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								chat.h
									
									
									
									
									
								
							| @ -50,6 +50,7 @@ void ircFmt(const char *format, ...); | |||||||
| 
 | 
 | ||||||
| void uiInit(void); | void uiInit(void); | ||||||
| void uiHide(void); | void uiHide(void); | ||||||
|  | void uiExit(void); | ||||||
| void uiDraw(void); | void uiDraw(void); | ||||||
| void uiBeep(void); | void uiBeep(void); | ||||||
| void uiRead(void); | void uiRead(void); | ||||||
| @ -77,6 +78,10 @@ void handle(char *line); | |||||||
| void inputTab(void); | void inputTab(void); | ||||||
| void input(char *line); | void input(char *line); | ||||||
| 
 | 
 | ||||||
|  | void urlScan(const char *str); | ||||||
|  | void urlList(void); | ||||||
|  | void urlOpen(size_t i); | ||||||
|  | 
 | ||||||
| void tabTouch(const char *word); | void tabTouch(const char *word); | ||||||
| void tabRemove(const char *word); | void tabRemove(const char *word); | ||||||
| void tabReplace(const char *prev, const char *next); | void tabReplace(const char *prev, const char *next); | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								handle.c
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								handle.c
									
									
									
									
									
								
							| @ -141,11 +141,12 @@ static void handle332(char *prefix, char *params) { | |||||||
| 	shift(¶ms); | 	shift(¶ms); | ||||||
| 	char *chan = shift(¶ms); | 	char *chan = shift(¶ms); | ||||||
| 	char *topic = shift(¶ms); | 	char *topic = shift(¶ms); | ||||||
|  | 	urlScan(topic); | ||||||
|  | 	uiTopicStr(topic); | ||||||
| 	uiFmt( | 	uiFmt( | ||||||
| 		"The sign in \3%d%s\3 reads, \"%s\"", | 		"The sign in \3%d%s\3 reads, \"%s\"", | ||||||
| 		color(chan), chan, topic | 		color(chan), chan, topic | ||||||
| 	); | 	); | ||||||
| 	uiTopicStr(topic); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void handleTopic(char *prefix, char *params) { | static void handleTopic(char *prefix, char *params) { | ||||||
| @ -153,11 +154,12 @@ static void handleTopic(char *prefix, char *params) { | |||||||
| 	char *user = prift(&prefix); | 	char *user = prift(&prefix); | ||||||
| 	char *chan = shift(¶ms); | 	char *chan = shift(¶ms); | ||||||
| 	char *topic = shift(¶ms); | 	char *topic = shift(¶ms); | ||||||
|  | 	urlScan(topic); | ||||||
|  | 	uiTopicStr(topic); | ||||||
| 	uiFmt( | 	uiFmt( | ||||||
| 		"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"", | 		"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"", | ||||||
| 		color(user), nick, color(chan), chan, topic | 		color(user), nick, color(chan), chan, topic | ||||||
| 	); | 	); | ||||||
| 	uiTopicStr(topic); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void handle366(char *prefix, char *params) { | static void handle366(char *prefix, char *params) { | ||||||
| @ -222,6 +224,7 @@ static void handlePrivmsg(char *prefix, char *params) { | |||||||
| 	shift(¶ms); | 	shift(¶ms); | ||||||
| 	char *mesg = shift(¶ms); | 	char *mesg = shift(¶ms); | ||||||
| 	tabTouch(nick); | 	tabTouch(nick); | ||||||
|  | 	urlScan(mesg); | ||||||
| 	bool self = !strcmp(user, chat.user); | 	bool self = !strcmp(user, chat.user); | ||||||
| 	bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick)); | 	bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick)); | ||||||
| 	if (ping) uiBeep(); | 	if (ping) uiBeep(); | ||||||
| @ -244,6 +247,7 @@ static void handleNotice(char *prefix, char *params) { | |||||||
| 	char *mesg = shift(¶ms); | 	char *mesg = shift(¶ms); | ||||||
| 	if (strcmp(chat.chan, chan)) return; | 	if (strcmp(chat.chan, chan)) return; | ||||||
| 	tabTouch(nick); | 	tabTouch(nick); | ||||||
|  | 	urlScan(mesg); | ||||||
| 	uiFmt("-\3%d%s\3- %s", color(user), nick, mesg); | 	uiFmt("-\3%d%s\3- %s", color(user), nick, mesg); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								input.c
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								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(¶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 { | static const struct { | ||||||
| 	const char *command; | 	const char *command; | ||||||
| 	Handler handler; | 	Handler handler; | ||||||
| @ -80,8 +95,10 @@ static const struct { | |||||||
| 	{ "/me", inputMe }, | 	{ "/me", inputMe }, | ||||||
| 	{ "/names", inputWho }, | 	{ "/names", inputWho }, | ||||||
| 	{ "/nick", inputNick }, | 	{ "/nick", inputNick }, | ||||||
|  | 	{ "/open", inputOpen }, | ||||||
| 	{ "/quit", inputQuit }, | 	{ "/quit", inputQuit }, | ||||||
| 	{ "/topic", inputTopic }, | 	{ "/topic", inputTopic }, | ||||||
|  | 	{ "/url", inputUrl }, | ||||||
| 	{ "/who", inputWho }, | 	{ "/who", inputWho }, | ||||||
| }; | }; | ||||||
| static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]); | static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]); | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								irc.c
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								irc.c
									
									
									
									
									
								
							| @ -15,16 +15,17 @@ | |||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include <err.h> | #include <err.h> | ||||||
|  | #include <fcntl.h> | ||||||
|  | #include <netdb.h> | ||||||
|  | #include <netinet/in.h> | ||||||
| #include <stdarg.h> | #include <stdarg.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
|  | #include <sys/socket.h> | ||||||
| #include <sysexits.h> | #include <sysexits.h> | ||||||
| #include <tls.h> | #include <tls.h> | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| #include <netdb.h> |  | ||||||
| #include <netinet/in.h> |  | ||||||
| #include <sys/socket.h> |  | ||||||
| 
 | 
 | ||||||
| #include "chat.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); | 	int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); | ||||||
| 	if (sock < 0) err(EX_OSERR, "socket"); | 	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); | 	error = connect(sock, ai->ai_addr, ai->ai_addrlen); | ||||||
| 	if (error) err(EX_UNAVAILABLE, "connect"); | 	if (error) err(EX_UNAVAILABLE, "connect"); | ||||||
| 	freeaddrinfo(ai); | 	freeaddrinfo(ai); | ||||||
| @ -111,7 +115,7 @@ void ircRead(void) { | |||||||
| 	ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len); | 	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 < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client)); | ||||||
| 	if (!read) { | 	if (!read) { | ||||||
| 		uiHide(); | 		uiExit(); | ||||||
| 		exit(EX_OK); | 		exit(EX_OK); | ||||||
| 	} | 	} | ||||||
| 	len += read; | 	len += read; | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								ui.c
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								ui.c
									
									
									
									
									
								
							| @ -99,8 +99,9 @@ static struct { | |||||||
| 	WINDOW *topic; | 	WINDOW *topic; | ||||||
| 	WINDOW *log; | 	WINDOW *log; | ||||||
| 	WINDOW *input; | 	WINDOW *input; | ||||||
| 	int scroll; | 	bool hide; | ||||||
| 	bool mark; | 	bool mark; | ||||||
|  | 	int scroll; | ||||||
| } ui; | } ui; | ||||||
| 
 | 
 | ||||||
| void uiInit(void) { | void uiInit(void) { | ||||||
| @ -135,8 +136,13 @@ static void uiResize(void) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void uiHide(void) { | void uiHide(void) { | ||||||
| 	focusDisable(); | 	ui.hide = true; | ||||||
| 	endwin(); | 	endwin(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void uiExit(void) { | ||||||
|  | 	uiHide(); | ||||||
|  | 	focusDisable(); | ||||||
| 	printf( | 	printf( | ||||||
| 		"This program is AGPLv3 free software!\n" | 		"This program is AGPLv3 free software!\n" | ||||||
| 		"The source is available at <" SOURCE_URL ">.\n" | 		"The source is available at <" SOURCE_URL ">.\n" | ||||||
| @ -144,6 +150,7 @@ void uiHide(void) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void uiDraw(void) { | void uiDraw(void) { | ||||||
|  | 	if (ui.hide) return; | ||||||
| 	pnoutrefresh( | 	pnoutrefresh( | ||||||
| 		ui.topic, | 		ui.topic, | ||||||
| 		0, 0, | 		0, 0, | ||||||
| @ -359,6 +366,8 @@ static bool keyCode(wint_t ch) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void uiRead(void) { | void uiRead(void) { | ||||||
|  | 	ui.hide = false; | ||||||
|  | 
 | ||||||
| 	bool update = false; | 	bool update = false; | ||||||
| 	int ret; | 	int ret; | ||||||
| 	wint_t ch; | 	wint_t ch; | ||||||
|  | |||||||
							
								
								
									
										95
									
								
								url.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								url.c
									
									
									
									
									
										Normal file
									
								
							| @ -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]); | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user