6 Commits

Author SHA1 Message Date
2336340ad8 Bump version, update NEWS 2020-10-31 23:50:32 +01:00
8f5dec0456 degesch: buffer creation cleanup 2020-10-31 23:44:18 +01:00
3dc6ee9a5b degesch: sanitize IRC nicknames/channel names
Don't trust the IRCd to have them in a subset of UTF-8.
2020-10-31 23:25:08 +01:00
821ce04915 degesch: implement autocompletion for /set
It was super annoying to just slightly modify strings and
string arrays, now you can have existing values filled in.

complete_word() looks a bit cleaner now as well.
2020-10-31 23:18:31 +01:00
2fe3b95ecd README.adoc: improve backlog helper invocation
When fancy-prompt.lua is enabled, tho prompt is two-lined
and a simple PageUp would skip one line of content.

It works slightly better than it should: when there's under
a page of content to scroll, there is no shift at all.
2020-10-31 20:00:23 +01:00
32c99c9d66 kike: avoid crash with a wildcard address
A most unfortunate 06d3b3b regression, mostly stemming from
forgetting why the `break` was in place and not documenting it.
2020-10-31 17:34:32 +01:00
5 changed files with 137 additions and 54 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.0) cmake_minimum_required (VERSION 3.0)
project (uirc3 VERSION 1.0.0 LANGUAGES C) project (uirc3 VERSION 1.1.0 LANGUAGES C)
# Options # Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON) option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)

9
NEWS
View File

@@ -1,3 +1,12 @@
1.1.0 (2020-10-31) "What Do You Mean By 'This Isn't Germany'?"
* degesch: made fancy-prompt.lua work with libedit
* kike: fixed a regression with an unspecified "bind_host"
* Miscellaneous minor improvements
1.0.0 (2020-10-29) "We're Finally There!" 1.0.0 (2020-10-29) "We're Finally There!"
* Coming with real manual pages instead of help2man-generated stubs * Coming with real manual pages instead of help2man-generated stubs

View File

@@ -159,7 +159,7 @@ black on white terminals.
/set behaviour.date_change_line = "%a %e %b %Y" /set behaviour.date_change_line = "%a %e %b %Y"
/set behaviour.plugin_autoload += "fancy-prompt.lua" /set behaviour.plugin_autoload += "fancy-prompt.lua"
/set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'" /set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb1d -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'"
/set attributes.userhost = "\x1b[38;5;109m" /set attributes.userhost = "\x1b[38;5;109m"
/set attributes.join = "\x1b[38;5;108m" /set attributes.join = "\x1b[38;5;108m"
/set attributes.part = "\x1b[38;5;138m" /set attributes.part = "\x1b[38;5;138m"

149
degesch.c
View File

@@ -1591,12 +1591,14 @@ static struct ispect_field g_buffer_ispect[] =
}; };
static struct buffer * static struct buffer *
buffer_new (struct input *input) buffer_new (struct input *input, enum buffer_type type, char *name)
{ {
struct buffer *self = xcalloc (1, sizeof *self); struct buffer *self = xcalloc (1, sizeof *self);
self->ref_count = 1; self->ref_count = 1;
self->input = input; self->input = input;
self->input_data = CALL (input, buffer_new); self->input_data = CALL (input, buffer_new);
self->type = type;
self->name = name;
return self; return self;
} }
@@ -4446,9 +4448,8 @@ buffer_remove_safe (struct app_context *ctx, struct buffer *buffer)
static void static void
init_global_buffer (struct app_context *ctx) init_global_buffer (struct app_context *ctx)
{ {
struct buffer *global = ctx->global_buffer = buffer_new (ctx->input); struct buffer *global = ctx->global_buffer =
global->type = BUFFER_GLOBAL; buffer_new (ctx->input, BUFFER_GLOBAL, xstrdup (PROGRAM_NAME));
global->name = xstrdup (PROGRAM_NAME);
buffer_add (ctx, global); buffer_add (ctx, global);
buffer_activate (ctx, global); buffer_activate (ctx, global);
@@ -4456,6 +4457,19 @@ init_global_buffer (struct app_context *ctx)
// --- Users, channels --------------------------------------------------------- // --- Users, channels ---------------------------------------------------------
static char *
irc_make_buffer_name (struct server *s, const char *target)
{
if (!target)
return xstrdup (s->name);
// XXX: this may be able to trigger the uniqueness assertion with non-UTF-8
char *target_utf8 = irc_to_utf8 (target);
char *result = xstrdup_printf ("%s.%s", s->name, target_utf8);
free (target_utf8);
return result;
}
static void static void
irc_user_on_destroy (void *object, void *user_data) irc_user_on_destroy (void *object, void *user_data)
{ {
@@ -4495,10 +4509,8 @@ irc_get_or_make_user_buffer (struct server *s, const char *nickname)
struct user *user = irc_get_or_make_user (s, nickname); struct user *user = irc_get_or_make_user (s, nickname);
// Open a new buffer for the user // Open a new buffer for the user
buffer = buffer_new (s->ctx->input); buffer = buffer_new (s->ctx->input,
buffer->type = BUFFER_PM; BUFFER_PM, irc_make_buffer_name (s, nickname));
// FIXME: this probably needs to be converted to UTF-8
buffer->name = xstrdup_printf ("%s.%s", s->name, nickname);
buffer->server = s; buffer->server = s;
buffer->user = user; buffer->user = user;
str_map_set (&s->irc_buffer_map, user->nickname, buffer); str_map_set (&s->irc_buffer_map, user->nickname, buffer);
@@ -6643,10 +6655,8 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
if (!irc_is_this_us (s, msg->prefix)) if (!irc_is_this_us (s, msg->prefix))
return; return;
buffer = buffer_new (s->ctx->input); buffer = buffer_new (s->ctx->input,
buffer->type = BUFFER_CHANNEL; BUFFER_CHANNEL, irc_make_buffer_name (s, channel_name));
// FIXME: this probably needs to be converted to UTF-8
buffer->name = xstrdup_printf ("%s.%s", s->name, channel_name);
buffer->server = s; buffer->server = s;
buffer->channel = channel = buffer->channel = channel =
irc_make_channel (s, xstrdup (channel_name)); irc_make_channel (s, xstrdup (channel_name));
@@ -8316,9 +8326,8 @@ server_add (struct app_context *ctx,
s->config = subtree; s->config = subtree;
// Add a buffer and activate it // Add a buffer and activate it
struct buffer *buffer = s->buffer = buffer_new (ctx->input); struct buffer *buffer = s->buffer = buffer_new (ctx->input,
buffer->type = BUFFER_SERVER; BUFFER_SERVER, irc_make_buffer_name (s, NULL));
buffer->name = xstrdup (s->name);
buffer->server = s; buffer->server = s;
buffer_add (ctx, buffer); buffer_add (ctx, buffer);
@@ -12358,15 +12367,12 @@ completion_locate (struct completion *self, size_t offset)
self->location = i - 1; self->location = i - 1;
} }
static bool static char *
completion_matches (struct completion *self, int word, const char *pattern) completion_word (struct completion *self, int word)
{ {
hard_assert (word >= 0 && word < (int) self->words_len); hard_assert (word >= 0 && word < (int) self->words_len);
char *text = xstrndup (self->line + self->words[word].start, return xstrndup (self->line + self->words[word].start,
self->words[word].end - self->words[word].start); self->words[word].end - self->words[word].start);
bool result = !fnmatch (pattern, text, 0);
free (text);
return result;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -12461,6 +12467,66 @@ complete_option (struct app_context *ctx, struct completion *data,
strv_free (&options); strv_free (&options);
} }
static void
complete_set_value (struct config_item *item, const char *word,
struct strv *output)
{
struct str serialized = str_make ();
config_item_write (item, false, &serialized);
if (!strncmp (serialized.str, word, strlen (word)))
strv_append_owned (output, str_steal (&serialized));
else
str_free (&serialized);
}
static void
complete_set_value_array (struct config_item *item, const char *word,
struct strv *output)
{
if (!item->schema || item->schema->type != CONFIG_ITEM_STRING_ARRAY)
return;
struct strv items = strv_make ();
cstr_split (item->value.string.str, ",", false, &items);
for (size_t i = 0; i < items.len; i++)
{
struct str wrapped = str_make (), serialized = str_make ();
str_append (&wrapped, items.vector[i]);
config_item_write_string (&serialized, &wrapped);
str_free (&wrapped);
if (!strncmp (serialized.str, word, strlen (word)))
strv_append_owned (output, str_steal (&serialized));
else
str_free (&serialized);
}
strv_free (&items);
}
static void
complete_set (struct app_context *ctx, struct completion *data,
const char *word, struct strv *output)
{
if (data->location == 1)
{
complete_option (ctx, data, word, output);
return;
}
if (data->location != 3)
return;
char *key = completion_word (data, 1);
struct config_item *item = config_item_get (ctx->config.root, key, NULL);
if (item)
{
char *op = completion_word (data, 2);
if (!strcmp (op, "-=")) complete_set_value_array (item, word, output);
if (!strcmp (op, "=")) complete_set_value (item, word, output);
free (op);
}
free (key);
}
static void static void
complete_topic (struct app_context *ctx, struct completion *data, complete_topic (struct app_context *ctx, struct completion *data,
const char *word, struct strv *output) const char *word, struct strv *output)
@@ -12514,33 +12580,30 @@ static char **
complete_word (struct app_context *ctx, struct completion *data, complete_word (struct app_context *ctx, struct completion *data,
const char *word) const char *word)
{ {
// First figure out what exactly we need to complete char *initial = completion_word (data, 0);
bool try_commands = false;
bool try_options = false;
bool try_topic = false;
bool try_nicknames = false;
if (data->location == 0 && completion_matches (data, 0, "/*"))
try_commands = true;
else if (data->location == 1 && completion_matches (data, 0, "/set"))
try_options = true;
else if (data->location == 1 && completion_matches (data, 0, "/help"))
try_commands = try_options = true;
else if (data->location == 1 && completion_matches (data, 0, "/topic"))
try_topic = try_nicknames = true;
else
try_nicknames = true;
// Start with a placeholder for the longest common prefix
struct strv words = strv_make (); struct strv words = strv_make ();
// Add placeholder
strv_append_owned (&words, NULL); strv_append_owned (&words, NULL);
if (try_commands) complete_command (ctx, data, word, &words); if (data->location == 0 && *initial == '/')
if (try_options) complete_option (ctx, data, word, &words); complete_command (ctx, data, word, &words);
if (try_topic) complete_topic (ctx, data, word, &words); else if (data->location >= 1 && !strcmp (initial, "/set"))
if (try_nicknames) complete_nicknames (ctx, data, word, &words); complete_set (ctx, data, word, &words);
else if (data->location == 1 && !strcmp (initial, "/help"))
{
complete_command (ctx, data, word, &words);
complete_option (ctx, data, word, &words);
}
else if (data->location == 1 && !strcmp (initial, "/topic"))
{
complete_topic (ctx, data, word, &words);
complete_nicknames (ctx, data, word, &words);
}
else
complete_nicknames (ctx, data, word, &words);
cstr_set (&initial, NULL);
LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks) LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks)
{ {
struct completion_hook *hook = (struct completion_hook *) iter; struct completion_hook *hook = (struct completion_hook *) iter;

29
kike.c
View File

@@ -614,7 +614,8 @@ struct server_context
{ {
int *listen_fds; ///< Listening socket FD's int *listen_fds; ///< Listening socket FD's
struct poller_fd *listen_events; ///< New connections available struct poller_fd *listen_events; ///< New connections available
size_t n_listen_fds; ///< Number of listening sockets size_t listen_len; ///< Number of listening sockets
size_t listen_alloc; ///< How many we've allocated
time_t started; ///< When has the server been started time_t started; ///< When has the server been started
@@ -695,7 +696,7 @@ server_context_free (struct server_context *self)
{ {
str_map_free (&self->config); str_map_free (&self->config);
for (size_t i = 0; i < self->n_listen_fds; i++) for (size_t i = 0; i < self->listen_len; i++)
{ {
poller_fd_reset (&self->listen_events[i]); poller_fd_reset (&self->listen_events[i]);
xclose (self->listen_fds[i]); xclose (self->listen_fds[i]);
@@ -746,12 +747,12 @@ irc_initiate_quit (struct server_context *ctx)
{ {
print_status ("shutting down"); print_status ("shutting down");
for (size_t i = 0; i < ctx->n_listen_fds; i++) for (size_t i = 0; i < ctx->listen_len; i++)
{ {
poller_fd_reset (&ctx->listen_events[i]); poller_fd_reset (&ctx->listen_events[i]);
xclose (ctx->listen_fds[i]); xclose (ctx->listen_fds[i]);
} }
ctx->n_listen_fds = 0; ctx->listen_len = 0;
for (struct client *iter = ctx->clients; iter; iter = iter->next) for (struct client *iter = ctx->clients; iter; iter = iter->next)
if (!iter->closing_link) if (!iter->closing_link)
@@ -3852,16 +3853,19 @@ irc_listen_resolve (struct server_context *ctx,
int fd; int fd;
for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next) for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
{ {
if (ctx->listen_len == ctx->listen_alloc)
break;
if ((fd = irc_listen (gai_iter)) == -1) if ((fd = irc_listen (gai_iter)) == -1)
continue; continue;
set_blocking (fd, false); set_blocking (fd, false);
struct poller_fd *event = &ctx->listen_events[ctx->n_listen_fds]; struct poller_fd *event = &ctx->listen_events[ctx->listen_len];
*event = poller_fd_make (&ctx->poller, fd); *event = poller_fd_make (&ctx->poller, fd);
event->dispatcher = (poller_fd_fn) on_irc_client_available; event->dispatcher = (poller_fd_fn) on_irc_client_available;
event->user_data = ctx; event->user_data = ctx;
ctx->listen_fds[ctx->n_listen_fds++] = fd; ctx->listen_fds[ctx->listen_len++] = fd;
poller_fd_set (event, POLLIN); poller_fd_set (event, POLLIN);
} }
freeaddrinfo (gai_result); freeaddrinfo (gai_result);
@@ -3882,13 +3886,20 @@ irc_setup_listen_fds (struct server_context *ctx, struct error **e)
struct strv ports = strv_make (); struct strv ports = strv_make ();
cstr_split (bind_port, ",", true, &ports); cstr_split (bind_port, ",", true, &ports);
ctx->listen_fds = xcalloc (ports.len, sizeof *ctx->listen_fds);
ctx->listen_events = xcalloc (ports.len, sizeof *ctx->listen_events); // For C and simplicity's sake let's assume that the host will resolve
// to at most two different addresses: IPv4 and IPv6 in case it is NULL
ctx->listen_alloc = ports.len * 2;
ctx->listen_fds =
xcalloc (ctx->listen_alloc, sizeof *ctx->listen_fds);
ctx->listen_events =
xcalloc (ctx->listen_alloc, sizeof *ctx->listen_events);
for (size_t i = 0; i < ports.len; i++) for (size_t i = 0; i < ports.len; i++)
irc_listen_resolve (ctx, bind_host, ports.vector[i], &gai_hints); irc_listen_resolve (ctx, bind_host, ports.vector[i], &gai_hints);
strv_free (&ports); strv_free (&ports);
if (!ctx->n_listen_fds) if (!ctx->listen_len)
{ {
error_set (e, "%s: %s", error_set (e, "%s: %s",
"network setup failed", "no ports to listen on"); "network setup failed", "no ports to listen on");