From a64f1a4ea2962e534673e27d85d92703c64201b0 Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Thu, 9 Aug 2018 00:24:49 -0400 Subject: [PATCH] Add URL detection, listing and opening Might also add /copy, like /open. --- Makefile | 2 +- README | 1 + chat.c | 2 +- chat.h | 5 +++ handle.c | 8 +++-- input.c | 17 ++++++++++ irc.c | 12 ++++--- ui.c | 13 ++++++-- url.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 url.c 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(¶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); } 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(¶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]); diff --git a/irc.c b/irc.c index 02a9f64..b718b13 100644 --- a/irc.c +++ b/irc.c @@ -15,16 +15,17 @@ */ #include +#include +#include +#include #include #include #include #include +#include #include #include #include -#include -#include -#include #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 + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#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]); +}