Add URL detection, listing and opening

Might also add /copy, like /open.
master
Curtis McEnroe 2018-08-09 00:24:49 -04:00
parent c024147504
commit a64f1a4ea2
No known key found for this signature in database
GPG Key ID: CEA2F97ADCFCD77C
9 changed files with 145 additions and 10 deletions

View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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);

View File

@ -141,11 +141,12 @@ static void handle332(char *prefix, char *params) {
shift(&params); shift(&params);
char *chan = shift(&params); char *chan = shift(&params);
char *topic = shift(&params); char *topic = shift(&params);
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(&params); char *chan = shift(&params);
char *topic = shift(&params); char *topic = shift(&params);
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(&params); shift(&params);
char *mesg = shift(&params); char *mesg = shift(&params);
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(&params); char *mesg = shift(&params);
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
View File

@ -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 { 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
View File

@ -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
View File

@ -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 100644
View 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]);
}