giochannel: ensure line terminator remains nul-terminated if needed

If the user passes -1 length to g_io_channel_set_line_term() along with
a nul-terminated string, then calls g_io_channel_get_line_term() and
decides to treat the result as nul-terminated rather than checking the
length parameter, then the application will have a problem, because it's
not nul-terminated. That's weird, since the input string was. Let's
ensure the result is consistent: if you pass a nul-terminated string,
the result is nul-terminated. If not, it's not.

Also add a warning to g_io_channel_get_line_term(), since it's very
strange for a gchar * return value to be anything other than a
nul-terminated UTF-8 string. This is an API design bug, but we cannot
fix it.
This commit is contained in:
Michael Catanzaro 2024-09-10 16:50:38 -05:00 committed by Philip Withnall
parent a893d622c0
commit 7c6b11df2d
2 changed files with 24 additions and 11 deletions

View File

@ -877,26 +877,31 @@ g_io_channel_set_line_term (GIOChannel *channel,
const gchar *line_term,
gint length)
{
guint length_unsigned;
g_return_if_fail (channel != NULL);
g_return_if_fail (line_term == NULL || length != 0); /* Disallow "" */
g_free (channel->line_term);
if (line_term == NULL)
length_unsigned = 0;
{
channel->line_term = NULL;
channel->line_term_len = 0;
}
else if (length >= 0)
length_unsigned = (guint) length;
{
/* We store the value nul-terminated even if the input is not */
channel->line_term = g_malloc0 (length + 1);
memcpy (channel->line_term, line_term, length);
channel->line_term_len = (guint) length;
}
else
{
/* FIXME: Were constrained by line_term_len being a guint here */
/* Were constrained by line_term_len being a guint here */
gsize length_size = strlen (line_term);
g_return_if_fail (length_size <= G_MAXUINT);
length_unsigned = (guint) length_size;
channel->line_term = g_strdup (line_term);
channel->line_term_len = (guint) length_size;
}
g_free (channel->line_term);
channel->line_term = line_term ? g_memdup2 (line_term, length_unsigned) : NULL;
channel->line_term_len = length_unsigned;
}
/**
@ -906,7 +911,8 @@ g_io_channel_set_line_term (GIOChannel *channel,
*
* This returns the string that #GIOChannel uses to determine
* where in the file a line break occurs. A value of %NULL
* indicates autodetection.
* indicates autodetection. Since 2.84, the return value is always
* nul-terminated.
*
* Returns: The line termination string. This value
* is owned by GLib and must not be freed.

View File

@ -178,6 +178,8 @@ test_read_line_embedded_nuls (void)
GError *local_error = NULL;
gchar *line = NULL;
gsize line_length, terminator_pos;
const gchar *line_term;
gint line_term_length;
GIOStatus status;
g_test_summary ("Test that reading a line containing embedded nuls works "
@ -200,6 +202,11 @@ test_read_line_embedded_nuls (void)
* Use length -1 here to exercise glib#2323; the case where length > 0
* is covered in glib/tests/protocol.c. */
g_io_channel_set_line_term (channel, "\n", -1);
line_term = g_io_channel_get_line_term (channel, &line_term_length);
g_assert_cmpstr (line_term, ==, "\n");
g_assert_cmpint (line_term_length, ==, 1);
g_io_channel_set_encoding (channel, NULL, &local_error);
g_assert_no_error (local_error);