mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
69e12cd3d5
Reading from a GConverterInputStream with both input_buffer and converted_buffer non-empty would return bogus data (the data from converted_buffer would essentially get skipped over, though the returned nread reflected what the count would be if it hadn't been). This was never noticed before because (a) it can't happen if all of your reads are at least as large as either the internal buffer size or the remaining length of the stream (which covers most real-world use), and (b) it can't happen if all of your reads are 1 byte (which covers most of tests/converter-test). (And (c) it only happens for some converters/input streams.) But this was happening occasionally in libsoup when content-sniffing a gzipped response, because the SoupContentSnifferStream would first read 512 bytes (to sniff), and then pass through larger reads after that. Fixed and added a test to converter-test. https://bugzilla.gnome.org/show_bug.cgi?id=676478
1142 lines
31 KiB
C
1142 lines
31 KiB
C
/* GLib testing framework examples and tests
|
|
* Copyright (C) 2009 Red Hat, Inc.
|
|
* Authors: Alexander Larsson <alexl@redhat.com>
|
|
*
|
|
* This work is provided "as is"; redistribution and modification
|
|
* in whole or in part, in any medium, physical or electronic is
|
|
* permitted without restriction.
|
|
*
|
|
* This work 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.
|
|
*
|
|
* In no event shall the authors or contributors be liable for any
|
|
* direct, indirect, incidental, special, exemplary, or consequential
|
|
* damages (including, but not limited to, procurement of substitute
|
|
* goods or services; loss of use, data, or profits; or business
|
|
* interruption) however caused and on any theory of liability, whether
|
|
* in contract, strict liability, or tort (including negligence or
|
|
* otherwise) arising in any way out of the use of this software, even
|
|
* if advised of the possibility of such damage.
|
|
*/
|
|
|
|
#include <glib/glib.h>
|
|
#include <gio/gio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define G_TYPE_EXPANDER_CONVERTER (g_expander_converter_get_type ())
|
|
#define G_EXPANDER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_EXPANDER_CONVERTER, GExpanderConverter))
|
|
#define G_EXPANDER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_EXPANDER_CONVERTER, GExpanderConverterClass))
|
|
#define G_IS_EXPANDER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_EXPANDER_CONVERTER))
|
|
#define G_IS_EXPANDER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_EXPANDER_CONVERTER))
|
|
#define G_EXPANDER_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_EXPANDER_CONVERTER, GExpanderConverterClass))
|
|
|
|
typedef struct _GExpanderConverter GExpanderConverter;
|
|
typedef struct _GExpanderConverterClass GExpanderConverterClass;
|
|
|
|
struct _GExpanderConverterClass
|
|
{
|
|
GObjectClass parent_class;
|
|
};
|
|
|
|
GType g_expander_converter_get_type (void) G_GNUC_CONST;
|
|
GConverter *g_expander_converter_new (void);
|
|
|
|
|
|
|
|
static void g_expander_converter_iface_init (GConverterIface *iface);
|
|
|
|
struct _GExpanderConverter
|
|
{
|
|
GObject parent_instance;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GExpanderConverter, g_expander_converter, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
|
|
g_expander_converter_iface_init))
|
|
|
|
static void
|
|
g_expander_converter_class_init (GExpanderConverterClass *klass)
|
|
{
|
|
}
|
|
|
|
static void
|
|
g_expander_converter_init (GExpanderConverter *local)
|
|
{
|
|
}
|
|
|
|
GConverter *
|
|
g_expander_converter_new (void)
|
|
{
|
|
GConverter *conv;
|
|
|
|
conv = g_object_new (G_TYPE_EXPANDER_CONVERTER, NULL);
|
|
|
|
return conv;
|
|
}
|
|
|
|
static void
|
|
g_expander_converter_reset (GConverter *converter)
|
|
{
|
|
}
|
|
|
|
static GConverterResult
|
|
g_expander_converter_convert (GConverter *converter,
|
|
const void *inbuf,
|
|
gsize inbuf_size,
|
|
void *outbuf,
|
|
gsize outbuf_size,
|
|
GConverterFlags flags,
|
|
gsize *bytes_read,
|
|
gsize *bytes_written,
|
|
GError **error)
|
|
{
|
|
const guint8 *in, *in_end;
|
|
guint8 v, *out;
|
|
int i;
|
|
gsize block_size;
|
|
|
|
in = inbuf;
|
|
out = outbuf;
|
|
in_end = in + inbuf_size;
|
|
|
|
while (in < in_end)
|
|
{
|
|
v = *in;
|
|
|
|
if (v == 0)
|
|
block_size = 10;
|
|
else
|
|
block_size = v * 1000;
|
|
|
|
if (outbuf_size < block_size)
|
|
{
|
|
if (*bytes_read > 0)
|
|
return G_CONVERTER_CONVERTED;
|
|
|
|
g_set_error_literal (error, G_IO_ERROR,
|
|
G_IO_ERROR_NO_SPACE,
|
|
"No space in dest");
|
|
return G_CONVERTER_ERROR;
|
|
}
|
|
|
|
in++;
|
|
*bytes_read += 1;
|
|
*bytes_written += block_size;
|
|
outbuf_size -= block_size;
|
|
for (i = 0; i < block_size; i++)
|
|
*out++ = v;
|
|
}
|
|
|
|
if (in == in_end && (flags & G_CONVERTER_INPUT_AT_END))
|
|
return G_CONVERTER_FINISHED;
|
|
return G_CONVERTER_CONVERTED;
|
|
}
|
|
|
|
static void
|
|
g_expander_converter_iface_init (GConverterIface *iface)
|
|
{
|
|
iface->convert = g_expander_converter_convert;
|
|
iface->reset = g_expander_converter_reset;
|
|
}
|
|
|
|
#define G_TYPE_COMPRESSOR_CONVERTER (g_compressor_converter_get_type ())
|
|
#define G_COMPRESSOR_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_COMPRESSOR_CONVERTER, GCompressorConverter))
|
|
#define G_COMPRESSOR_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_COMPRESSOR_CONVERTER, GCompressorConverterClass))
|
|
#define G_IS_COMPRESSOR_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_COMPRESSOR_CONVERTER))
|
|
#define G_IS_COMPRESSOR_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_COMPRESSOR_CONVERTER))
|
|
#define G_COMPRESSOR_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_COMPRESSOR_CONVERTER, GCompressorConverterClass))
|
|
|
|
typedef struct _GCompressorConverter GCompressorConverter;
|
|
typedef struct _GCompressorConverterClass GCompressorConverterClass;
|
|
|
|
struct _GCompressorConverterClass
|
|
{
|
|
GObjectClass parent_class;
|
|
};
|
|
|
|
GType g_compressor_converter_get_type (void) G_GNUC_CONST;
|
|
GConverter *g_compressor_converter_new (void);
|
|
|
|
|
|
|
|
static void g_compressor_converter_iface_init (GConverterIface *iface);
|
|
|
|
struct _GCompressorConverter
|
|
{
|
|
GObject parent_instance;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GCompressorConverter, g_compressor_converter, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
|
|
g_compressor_converter_iface_init))
|
|
|
|
static void
|
|
g_compressor_converter_class_init (GCompressorConverterClass *klass)
|
|
{
|
|
}
|
|
|
|
static void
|
|
g_compressor_converter_init (GCompressorConverter *local)
|
|
{
|
|
}
|
|
|
|
GConverter *
|
|
g_compressor_converter_new (void)
|
|
{
|
|
GConverter *conv;
|
|
|
|
conv = g_object_new (G_TYPE_COMPRESSOR_CONVERTER, NULL);
|
|
|
|
return conv;
|
|
}
|
|
|
|
static void
|
|
g_compressor_converter_reset (GConverter *converter)
|
|
{
|
|
}
|
|
|
|
static GConverterResult
|
|
g_compressor_converter_convert (GConverter *converter,
|
|
const void *inbuf,
|
|
gsize inbuf_size,
|
|
void *outbuf,
|
|
gsize outbuf_size,
|
|
GConverterFlags flags,
|
|
gsize *bytes_read,
|
|
gsize *bytes_written,
|
|
GError **error)
|
|
{
|
|
const guint8 *in, *in_end;
|
|
guint8 v, *out;
|
|
int i;
|
|
gsize block_size;
|
|
|
|
in = inbuf;
|
|
out = outbuf;
|
|
in_end = in + inbuf_size;
|
|
|
|
while (in < in_end)
|
|
{
|
|
v = *in;
|
|
|
|
if (v == 0)
|
|
{
|
|
block_size = 0;
|
|
while (in+block_size < in_end && *(in+block_size) == 0)
|
|
block_size ++;
|
|
}
|
|
else
|
|
block_size = v * 1000;
|
|
|
|
/* Not enough data */
|
|
if (in_end - in < block_size)
|
|
{
|
|
if (*bytes_read > 0)
|
|
break;
|
|
g_set_error_literal (error, G_IO_ERROR,
|
|
G_IO_ERROR_PARTIAL_INPUT,
|
|
"Need more data");
|
|
return G_CONVERTER_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < block_size; i++)
|
|
{
|
|
if (*(in + i) != v)
|
|
{
|
|
if (*bytes_read > 0)
|
|
break;
|
|
g_set_error_literal (error, G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid data");
|
|
return G_CONVERTER_ERROR;
|
|
}
|
|
}
|
|
|
|
if (v == 0 && in_end - in == block_size && (flags & G_CONVERTER_INPUT_AT_END) == 0)
|
|
{
|
|
if (*bytes_read > 0)
|
|
break;
|
|
g_set_error_literal (error, G_IO_ERROR,
|
|
G_IO_ERROR_PARTIAL_INPUT,
|
|
"Need more data");
|
|
return G_CONVERTER_ERROR;
|
|
}
|
|
|
|
in += block_size;
|
|
*out++ = v;
|
|
*bytes_read += block_size;
|
|
*bytes_written += 1;
|
|
}
|
|
|
|
if (in == in_end && (flags & G_CONVERTER_INPUT_AT_END))
|
|
return G_CONVERTER_FINISHED;
|
|
return G_CONVERTER_CONVERTED;
|
|
}
|
|
|
|
static void
|
|
g_compressor_converter_iface_init (GConverterIface *iface)
|
|
{
|
|
iface->convert = g_compressor_converter_convert;
|
|
iface->reset = g_compressor_converter_reset;
|
|
}
|
|
|
|
guint8 unexpanded_data[] = { 0,1,3,4,5,6,7,3,12,0,0};
|
|
|
|
static void
|
|
test_expander (void)
|
|
{
|
|
guint8 *converted1, *converted2, *ptr;
|
|
gsize n_read, n_written;
|
|
gsize total_read;
|
|
gssize res;
|
|
GConverterResult cres;
|
|
GInputStream *mem, *cstream;
|
|
GOutputStream *mem_out, *cstream_out;
|
|
GConverter *expander;
|
|
GConverter *converter;
|
|
GError *error;
|
|
int i;
|
|
|
|
expander = g_expander_converter_new ();
|
|
|
|
converted1 = g_malloc (100*1000); /* Large enough */
|
|
converted2 = g_malloc (100*1000); /* Large enough */
|
|
|
|
cres = g_converter_convert (expander,
|
|
unexpanded_data, sizeof(unexpanded_data),
|
|
converted1, 100*1000,
|
|
G_CONVERTER_INPUT_AT_END,
|
|
&n_read, &n_written, NULL);
|
|
|
|
g_assert (cres == G_CONVERTER_FINISHED);
|
|
g_assert (n_read == 11);
|
|
g_assert (n_written == 41030);
|
|
|
|
g_converter_reset (expander);
|
|
|
|
mem = g_memory_input_stream_new_from_data (unexpanded_data,
|
|
sizeof (unexpanded_data),
|
|
NULL);
|
|
cstream = g_converter_input_stream_new (mem, expander);
|
|
g_assert (g_converter_input_stream_get_converter (G_CONVERTER_INPUT_STREAM (cstream)) == expander);
|
|
g_object_get (cstream, "converter", &converter, NULL);
|
|
g_assert (converter == expander);
|
|
g_object_unref (converter);
|
|
g_object_unref (mem);
|
|
|
|
total_read = 0;
|
|
ptr = converted2;
|
|
while (TRUE)
|
|
{
|
|
error = NULL;
|
|
res = g_input_stream_read (cstream,
|
|
ptr, 1,
|
|
NULL, &error);
|
|
g_assert (res != -1);
|
|
if (res == 0)
|
|
break;
|
|
ptr += res;
|
|
total_read += res;
|
|
}
|
|
|
|
g_assert (total_read == n_written);
|
|
g_assert (memcmp (converted1, converted2, n_written) == 0);
|
|
|
|
g_converter_reset (expander);
|
|
|
|
mem_out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
|
|
cstream_out = g_converter_output_stream_new (mem_out, expander);
|
|
g_assert (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (cstream_out)) == expander);
|
|
g_object_get (cstream_out, "converter", &converter, NULL);
|
|
g_assert (converter == expander);
|
|
g_object_unref (converter);
|
|
g_object_unref (mem_out);
|
|
|
|
for (i = 0; i < sizeof(unexpanded_data); i++)
|
|
{
|
|
error = NULL;
|
|
res = g_output_stream_write (cstream_out,
|
|
unexpanded_data + i, 1,
|
|
NULL, &error);
|
|
g_assert (res != -1);
|
|
if (res == 0)
|
|
{
|
|
g_assert (i == sizeof(unexpanded_data) -1);
|
|
break;
|
|
}
|
|
g_assert (res == 1);
|
|
}
|
|
|
|
g_output_stream_close (cstream_out, NULL, NULL);
|
|
|
|
g_assert (g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out)) == n_written);
|
|
g_assert (memcmp (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_out)),
|
|
converted1,
|
|
n_written) == 0);
|
|
|
|
g_free (converted1);
|
|
g_free (converted2);
|
|
g_object_unref (cstream);
|
|
g_object_unref (cstream_out);
|
|
g_object_unref (expander);
|
|
}
|
|
|
|
static void
|
|
test_compressor (void)
|
|
{
|
|
guint8 *converted, *expanded, *ptr;
|
|
gsize n_read, expanded_size;
|
|
gsize total_read;
|
|
gssize res;
|
|
GConverterResult cres;
|
|
GInputStream *mem, *cstream;
|
|
GOutputStream *mem_out, *cstream_out;
|
|
GConverter *expander, *compressor;
|
|
GError *error;
|
|
int i;
|
|
|
|
expander = g_expander_converter_new ();
|
|
expanded = g_malloc (100*1000); /* Large enough */
|
|
cres = g_converter_convert (expander,
|
|
unexpanded_data, sizeof(unexpanded_data),
|
|
expanded, 100*1000,
|
|
G_CONVERTER_INPUT_AT_END,
|
|
&n_read, &expanded_size, NULL);
|
|
g_assert (cres == G_CONVERTER_FINISHED);
|
|
g_assert (n_read == 11);
|
|
g_assert (expanded_size == 41030);
|
|
|
|
compressor = g_compressor_converter_new ();
|
|
|
|
converted = g_malloc (100*1000); /* Large enough */
|
|
|
|
mem = g_memory_input_stream_new_from_data (expanded,
|
|
expanded_size,
|
|
NULL);
|
|
cstream = g_converter_input_stream_new (mem, compressor);
|
|
g_object_unref (mem);
|
|
|
|
total_read = 0;
|
|
ptr = converted;
|
|
while (TRUE)
|
|
{
|
|
error = NULL;
|
|
res = g_input_stream_read (cstream,
|
|
ptr, 1,
|
|
NULL, &error);
|
|
g_assert (res != -1);
|
|
if (res == 0)
|
|
break;
|
|
ptr += res;
|
|
total_read += res;
|
|
}
|
|
|
|
g_assert (total_read == n_read - 1); /* Last 2 zeros are combined */
|
|
g_assert (memcmp (converted, unexpanded_data, total_read) == 0);
|
|
|
|
g_object_unref (cstream);
|
|
|
|
g_converter_reset (compressor);
|
|
|
|
mem_out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
|
|
cstream_out = g_converter_output_stream_new (mem_out, compressor);
|
|
g_object_unref (mem_out);
|
|
|
|
for (i = 0; i < expanded_size; i++)
|
|
{
|
|
error = NULL;
|
|
res = g_output_stream_write (cstream_out,
|
|
expanded + i, 1,
|
|
NULL, &error);
|
|
g_assert (res != -1);
|
|
if (res == 0)
|
|
{
|
|
g_assert (i == expanded_size -1);
|
|
break;
|
|
}
|
|
g_assert (res == 1);
|
|
}
|
|
|
|
g_output_stream_close (cstream_out, NULL, NULL);
|
|
|
|
g_assert (g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out)) == n_read - 1); /* Last 2 zeros are combined */
|
|
g_assert (memcmp (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_out)),
|
|
unexpanded_data,
|
|
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out))) == 0);
|
|
|
|
g_object_unref (cstream_out);
|
|
|
|
g_converter_reset (compressor);
|
|
|
|
memset (expanded, 5, 5*1000*2);
|
|
|
|
mem = g_memory_input_stream_new_from_data (expanded,
|
|
5*1000,
|
|
NULL);
|
|
cstream = g_converter_input_stream_new (mem, compressor);
|
|
g_object_unref (mem);
|
|
|
|
total_read = 0;
|
|
ptr = converted;
|
|
while (TRUE)
|
|
{
|
|
error = NULL;
|
|
res = g_input_stream_read (cstream,
|
|
ptr, 1,
|
|
NULL, &error);
|
|
g_assert (res != -1);
|
|
if (res == 0)
|
|
break;
|
|
ptr += res;
|
|
total_read += res;
|
|
}
|
|
|
|
g_assert (total_read == 1);
|
|
g_assert (*converted == 5);
|
|
|
|
g_object_unref (cstream);
|
|
|
|
mem = g_memory_input_stream_new_from_data (expanded,
|
|
5*1000 * 2,
|
|
NULL);
|
|
cstream = g_converter_input_stream_new (mem, compressor);
|
|
g_object_unref (mem);
|
|
|
|
total_read = 0;
|
|
ptr = converted;
|
|
while (TRUE)
|
|
{
|
|
error = NULL;
|
|
res = g_input_stream_read (cstream,
|
|
ptr, 1,
|
|
NULL, &error);
|
|
g_assert (res != -1);
|
|
if (res == 0)
|
|
break;
|
|
ptr += res;
|
|
total_read += res;
|
|
}
|
|
|
|
g_assert (total_read == 2);
|
|
g_assert (converted[0] == 5);
|
|
g_assert (converted[1] == 5);
|
|
|
|
g_object_unref (cstream);
|
|
|
|
g_converter_reset (compressor);
|
|
|
|
mem = g_memory_input_stream_new_from_data (expanded,
|
|
5*1000 * 2 - 1,
|
|
NULL);
|
|
cstream = g_converter_input_stream_new (mem, compressor);
|
|
g_object_unref (mem);
|
|
|
|
total_read = 0;
|
|
ptr = converted;
|
|
while (TRUE)
|
|
{
|
|
error = NULL;
|
|
res = g_input_stream_read (cstream,
|
|
ptr, 1,
|
|
NULL, &error);
|
|
if (res == -1)
|
|
{
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT);
|
|
g_error_free (error);
|
|
break;
|
|
}
|
|
|
|
g_assert (res != 0);
|
|
ptr += res;
|
|
total_read += res;
|
|
}
|
|
|
|
g_assert (total_read == 1);
|
|
g_assert (converted[0] == 5);
|
|
|
|
g_object_unref (cstream);
|
|
|
|
g_free (expanded);
|
|
g_free (converted);
|
|
g_object_unref (expander);
|
|
g_object_unref (compressor);
|
|
}
|
|
|
|
#define LEFTOVER_SHORT_READ_SIZE 512
|
|
|
|
#define G_TYPE_LEFTOVER_CONVERTER (g_leftover_converter_get_type ())
|
|
#define G_LEFTOVER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LEFTOVER_CONVERTER, GLeftoverConverter))
|
|
#define G_LEFTOVER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_LEFTOVER_CONVERTER, GLeftoverConverterClass))
|
|
#define G_IS_LEFTOVER_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LEFTOVER_CONVERTER))
|
|
#define G_IS_LEFTOVER_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LEFTOVER_CONVERTER))
|
|
#define G_LEFTOVER_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_LEFTOVER_CONVERTER, GLeftoverConverterClass))
|
|
|
|
typedef struct _GLeftoverConverter GLeftoverConverter;
|
|
typedef struct _GLeftoverConverterClass GLeftoverConverterClass;
|
|
|
|
struct _GLeftoverConverterClass
|
|
{
|
|
GObjectClass parent_class;
|
|
};
|
|
|
|
GType g_leftover_converter_get_type (void) G_GNUC_CONST;
|
|
GConverter *g_leftover_converter_new (void);
|
|
|
|
|
|
|
|
static void g_leftover_converter_iface_init (GConverterIface *iface);
|
|
|
|
struct _GLeftoverConverter
|
|
{
|
|
GObject parent_instance;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GLeftoverConverter, g_leftover_converter, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
|
|
g_leftover_converter_iface_init))
|
|
|
|
static void
|
|
g_leftover_converter_class_init (GLeftoverConverterClass *klass)
|
|
{
|
|
}
|
|
|
|
static void
|
|
g_leftover_converter_init (GLeftoverConverter *local)
|
|
{
|
|
}
|
|
|
|
GConverter *
|
|
g_leftover_converter_new (void)
|
|
{
|
|
GConverter *conv;
|
|
|
|
conv = g_object_new (G_TYPE_LEFTOVER_CONVERTER, NULL);
|
|
|
|
return conv;
|
|
}
|
|
|
|
static void
|
|
g_leftover_converter_reset (GConverter *converter)
|
|
{
|
|
}
|
|
|
|
static GConverterResult
|
|
g_leftover_converter_convert (GConverter *converter,
|
|
const void *inbuf,
|
|
gsize inbuf_size,
|
|
void *outbuf,
|
|
gsize outbuf_size,
|
|
GConverterFlags flags,
|
|
gsize *bytes_read,
|
|
gsize *bytes_written,
|
|
GError **error)
|
|
{
|
|
if (outbuf_size == LEFTOVER_SHORT_READ_SIZE)
|
|
{
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_PARTIAL_INPUT,
|
|
"partial input");
|
|
return G_CONVERTER_ERROR;
|
|
}
|
|
|
|
if (inbuf_size < 100)
|
|
*bytes_read = *bytes_written = MIN (inbuf_size, outbuf_size);
|
|
else
|
|
*bytes_read = *bytes_written = MIN (inbuf_size - 10, outbuf_size);
|
|
memcpy (outbuf, inbuf, *bytes_written);
|
|
|
|
if (*bytes_read == inbuf_size && (flags & G_CONVERTER_INPUT_AT_END))
|
|
return G_CONVERTER_FINISHED;
|
|
return G_CONVERTER_CONVERTED;
|
|
}
|
|
|
|
static void
|
|
g_leftover_converter_iface_init (GConverterIface *iface)
|
|
{
|
|
iface->convert = g_leftover_converter_convert;
|
|
iface->reset = g_leftover_converter_reset;
|
|
}
|
|
|
|
#define LEFTOVER_BUFSIZE 8192
|
|
#define INTERNAL_BUFSIZE 4096
|
|
|
|
static void
|
|
test_converter_leftover (void)
|
|
{
|
|
gchar *orig, *converted;
|
|
gsize total_read;
|
|
gssize res;
|
|
goffset offset;
|
|
GInputStream *mem, *cstream;
|
|
GConverter *converter;
|
|
GError *error;
|
|
int i;
|
|
|
|
converter = g_leftover_converter_new ();
|
|
|
|
orig = g_malloc (LEFTOVER_BUFSIZE);
|
|
converted = g_malloc (LEFTOVER_BUFSIZE);
|
|
for (i = 0; i < LEFTOVER_BUFSIZE; i++)
|
|
orig[i] = i % 64 + 32;
|
|
|
|
mem = g_memory_input_stream_new_from_data (orig, LEFTOVER_BUFSIZE, NULL);
|
|
cstream = g_converter_input_stream_new (mem, G_CONVERTER (converter));
|
|
g_object_unref (mem);
|
|
|
|
total_read = 0;
|
|
|
|
error = NULL;
|
|
res = g_input_stream_read (cstream,
|
|
converted, LEFTOVER_SHORT_READ_SIZE,
|
|
NULL, &error);
|
|
g_assert_cmpint (res, ==, LEFTOVER_SHORT_READ_SIZE);
|
|
total_read += res;
|
|
|
|
offset = g_seekable_tell (G_SEEKABLE (mem));
|
|
g_assert_cmpint (offset, >, LEFTOVER_SHORT_READ_SIZE);
|
|
g_assert_cmpint (offset, <, LEFTOVER_BUFSIZE);
|
|
|
|
/* At this point, @cstream has both a non-empty input_buffer
|
|
* and a non-empty converted_buffer, which is the case
|
|
* we want to test.
|
|
*/
|
|
|
|
while (TRUE)
|
|
{
|
|
error = NULL;
|
|
res = g_input_stream_read (cstream,
|
|
converted + total_read,
|
|
LEFTOVER_BUFSIZE - total_read,
|
|
NULL, &error);
|
|
g_assert (res >= 0);
|
|
if (res == 0)
|
|
break;
|
|
total_read += res;
|
|
}
|
|
|
|
g_assert_cmpint (total_read, ==, LEFTOVER_BUFSIZE);
|
|
g_assert (memcmp (converted, orig, LEFTOVER_BUFSIZE) == 0);
|
|
|
|
g_object_unref (cstream);
|
|
g_free (orig);
|
|
g_free (converted);
|
|
g_object_unref (converter);
|
|
}
|
|
|
|
#define DATA_LENGTH 1000000
|
|
|
|
static void
|
|
test_corruption (GZlibCompressorFormat format, gint level)
|
|
{
|
|
GError *error = NULL;
|
|
guint32 *data0, *data1;
|
|
gsize data1_size;
|
|
gint i;
|
|
GInputStream *istream0, *istream1, *cistream1;
|
|
GOutputStream *ostream1, *ostream2, *costream1;
|
|
GConverter *compressor, *decompressor;
|
|
GZlibCompressorFormat fmt;
|
|
gint lvl;
|
|
|
|
data0 = g_malloc (DATA_LENGTH * sizeof (guint32));
|
|
for (i = 0; i < DATA_LENGTH; i++)
|
|
data0[i] = g_random_int ();
|
|
|
|
istream0 = g_memory_input_stream_new_from_data (data0,
|
|
DATA_LENGTH * sizeof (guint32), NULL);
|
|
|
|
ostream1 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
|
|
compressor = G_CONVERTER (g_zlib_compressor_new (format, level));
|
|
costream1 = g_converter_output_stream_new (ostream1, compressor);
|
|
g_assert (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (costream1)) == compressor);
|
|
|
|
g_output_stream_splice (costream1, istream0, 0, NULL, &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_object_unref (costream1);
|
|
|
|
g_converter_reset (compressor);
|
|
g_object_get (compressor, "format", &fmt, "level", &lvl, NULL);
|
|
g_assert_cmpint (fmt, ==, format);
|
|
g_assert_cmpint (lvl, ==, level);
|
|
g_object_unref (compressor);
|
|
data1 = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (ostream1));
|
|
data1_size = g_memory_output_stream_get_data_size (
|
|
G_MEMORY_OUTPUT_STREAM (ostream1));
|
|
g_object_unref (ostream1);
|
|
g_object_unref (istream0);
|
|
|
|
istream1 = g_memory_input_stream_new_from_data (data1, data1_size, g_free);
|
|
decompressor = G_CONVERTER (g_zlib_decompressor_new (format));
|
|
cistream1 = g_converter_input_stream_new (istream1, decompressor);
|
|
|
|
ostream2 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
|
|
|
|
g_output_stream_splice (ostream2, cistream1, 0, NULL, &error);
|
|
g_assert_no_error (error);
|
|
|
|
g_assert_cmpuint (DATA_LENGTH * sizeof (guint32), ==,
|
|
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (ostream2)));
|
|
g_assert (memcmp (data0, g_memory_output_stream_get_data (
|
|
G_MEMORY_OUTPUT_STREAM (ostream2)), DATA_LENGTH * sizeof (guint32)) == 0);
|
|
g_object_unref (istream1);
|
|
g_converter_reset (decompressor);
|
|
g_object_get (decompressor, "format", &fmt, NULL);
|
|
g_assert_cmpint (fmt, ==, format);
|
|
g_object_unref (decompressor);
|
|
g_object_unref (cistream1);
|
|
g_object_unref (ostream2);
|
|
g_free (data0);
|
|
}
|
|
|
|
typedef struct {
|
|
const gchar *path;
|
|
GZlibCompressorFormat format;
|
|
gint level;
|
|
} CompressorTest;
|
|
|
|
static void
|
|
test_roundtrip (gconstpointer data)
|
|
{
|
|
const CompressorTest *test = data;
|
|
|
|
g_test_bug ("162549");
|
|
|
|
test_corruption (test->format, test->level);
|
|
}
|
|
|
|
typedef struct {
|
|
const gchar *path;
|
|
const gchar *charset_in;
|
|
const gchar *text_in;
|
|
const gchar *charset_out;
|
|
const gchar *text_out;
|
|
gint n_fallbacks;
|
|
} CharsetTest;
|
|
|
|
static void
|
|
test_charset (gconstpointer data)
|
|
{
|
|
const CharsetTest *test = data;
|
|
GInputStream *in, *in2;
|
|
GConverter *conv;
|
|
gchar *buffer;
|
|
gsize count;
|
|
gsize bytes_read;
|
|
GError *error;
|
|
gboolean fallback;
|
|
|
|
conv = (GConverter *)g_charset_converter_new (test->charset_out, test->charset_in, NULL);
|
|
g_object_get (conv, "use-fallback", &fallback, NULL);
|
|
g_assert (!fallback);
|
|
|
|
in = g_memory_input_stream_new_from_data (test->text_in, -1, NULL);
|
|
in2 = g_converter_input_stream_new (in, conv);
|
|
|
|
count = 2 * strlen (test->text_out);
|
|
buffer = g_malloc0 (count);
|
|
error = NULL;
|
|
g_input_stream_read_all (in2, buffer, count, &bytes_read, NULL, &error);
|
|
if (test->n_fallbacks == 0)
|
|
{
|
|
g_assert_no_error (error);
|
|
g_assert_cmpint (bytes_read, ==, strlen (test->text_out));
|
|
g_assert_cmpstr (buffer, ==, test->text_out);
|
|
}
|
|
else
|
|
{
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_free (buffer);
|
|
g_object_unref (in2);
|
|
g_object_unref (in);
|
|
|
|
if (test->n_fallbacks == 0)
|
|
{
|
|
g_object_unref (conv);
|
|
return;
|
|
}
|
|
|
|
g_converter_reset (conv);
|
|
|
|
g_assert (!g_charset_converter_get_use_fallback (G_CHARSET_CONVERTER (conv)));
|
|
g_charset_converter_set_use_fallback (G_CHARSET_CONVERTER (conv), TRUE);
|
|
|
|
in = g_memory_input_stream_new_from_data (test->text_in, -1, NULL);
|
|
in2 = g_converter_input_stream_new (in, conv);
|
|
|
|
count = 2 * strlen (test->text_out);
|
|
buffer = g_malloc0 (count);
|
|
error = NULL;
|
|
g_input_stream_read_all (in2, buffer, count, &bytes_read, NULL, &error);
|
|
g_assert_no_error (error);
|
|
g_assert_cmpstr (buffer, ==, test->text_out);
|
|
g_assert_cmpint (bytes_read, ==, strlen (test->text_out));
|
|
g_assert_cmpint (test->n_fallbacks, ==, g_charset_converter_get_num_fallbacks (G_CHARSET_CONVERTER (conv)));
|
|
|
|
g_free (buffer);
|
|
g_object_unref (in2);
|
|
g_object_unref (in);
|
|
|
|
g_object_unref (conv);
|
|
}
|
|
|
|
|
|
static void
|
|
client_connected (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GSocketClient *client = G_SOCKET_CLIENT (source);
|
|
GSocketConnection **conn = user_data;
|
|
GError *error = NULL;
|
|
|
|
*conn = g_socket_client_connect_finish (client, result, &error);
|
|
g_assert_no_error (error);
|
|
}
|
|
|
|
static void
|
|
server_connected (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GSocketListener *listener = G_SOCKET_LISTENER (source);
|
|
GSocketConnection **conn = user_data;
|
|
GError *error = NULL;
|
|
|
|
*conn = g_socket_listener_accept_finish (listener, result, NULL, &error);
|
|
g_assert_no_error (error);
|
|
}
|
|
|
|
static void
|
|
make_socketpair (GIOStream **left,
|
|
GIOStream **right)
|
|
{
|
|
GInetAddress *iaddr;
|
|
GSocketAddress *saddr, *effective_address;
|
|
GSocketListener *listener;
|
|
GSocketClient *client;
|
|
GError *error = NULL;
|
|
GSocketConnection *client_conn = NULL, *server_conn = NULL;
|
|
|
|
iaddr = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
|
|
saddr = g_inet_socket_address_new (iaddr, 0);
|
|
g_object_unref (iaddr);
|
|
|
|
listener = g_socket_listener_new ();
|
|
g_socket_listener_add_address (listener, saddr,
|
|
G_SOCKET_TYPE_STREAM,
|
|
G_SOCKET_PROTOCOL_TCP,
|
|
NULL,
|
|
&effective_address,
|
|
&error);
|
|
g_assert_no_error (error);
|
|
g_object_unref (saddr);
|
|
|
|
client = g_socket_client_new ();
|
|
|
|
g_socket_client_connect_async (client,
|
|
G_SOCKET_CONNECTABLE (effective_address),
|
|
NULL, client_connected, &client_conn);
|
|
g_socket_listener_accept_async (listener, NULL,
|
|
server_connected, &server_conn);
|
|
|
|
while (!client_conn || !server_conn)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
g_object_unref (client);
|
|
g_object_unref (listener);
|
|
g_object_unref (effective_address);
|
|
|
|
*left = G_IO_STREAM (client_conn);
|
|
*right = G_IO_STREAM (server_conn);
|
|
}
|
|
|
|
static void
|
|
test_converter_pollable (void)
|
|
{
|
|
GIOStream *left, *right;
|
|
guint8 *converted, *inptr;
|
|
guint8 *expanded, *outptr, *expanded_end;
|
|
gsize n_read, expanded_size;
|
|
gsize total_read;
|
|
gssize res;
|
|
gboolean is_readable;
|
|
GConverterResult cres;
|
|
GInputStream *cstream;
|
|
GPollableInputStream *pollable_in;
|
|
GOutputStream *socket_out, *mem_out, *cstream_out;
|
|
GPollableOutputStream *pollable_out;
|
|
GConverter *expander, *compressor;
|
|
GError *error;
|
|
int i;
|
|
|
|
expander = g_expander_converter_new ();
|
|
expanded = g_malloc (100*1000); /* Large enough */
|
|
cres = g_converter_convert (expander,
|
|
unexpanded_data, sizeof(unexpanded_data),
|
|
expanded, 100*1000,
|
|
G_CONVERTER_INPUT_AT_END,
|
|
&n_read, &expanded_size, NULL);
|
|
g_assert (cres == G_CONVERTER_FINISHED);
|
|
g_assert (n_read == 11);
|
|
g_assert (expanded_size == 41030);
|
|
expanded_end = expanded + expanded_size;
|
|
|
|
make_socketpair (&left, &right);
|
|
|
|
compressor = g_compressor_converter_new ();
|
|
|
|
converted = g_malloc (100*1000); /* Large enough */
|
|
|
|
cstream = g_converter_input_stream_new (g_io_stream_get_input_stream (left),
|
|
compressor);
|
|
pollable_in = G_POLLABLE_INPUT_STREAM (cstream);
|
|
g_assert (g_pollable_input_stream_can_poll (pollable_in));
|
|
|
|
socket_out = g_io_stream_get_output_stream (right);
|
|
|
|
total_read = 0;
|
|
outptr = expanded;
|
|
inptr = converted;
|
|
while (TRUE)
|
|
{
|
|
error = NULL;
|
|
|
|
if (outptr < expanded_end)
|
|
{
|
|
res = g_output_stream_write (socket_out,
|
|
outptr,
|
|
MIN (1000, (expanded_end - outptr)),
|
|
NULL, &error);
|
|
g_assert_cmpint (res, >, 0);
|
|
outptr += res;
|
|
}
|
|
else if (socket_out)
|
|
{
|
|
g_object_unref (right);
|
|
socket_out = NULL;
|
|
}
|
|
|
|
is_readable = g_pollable_input_stream_is_readable (pollable_in);
|
|
res = g_pollable_input_stream_read_nonblocking (pollable_in,
|
|
inptr, 1,
|
|
NULL, &error);
|
|
|
|
/* is_readable can be a false positive, but not a false negative */
|
|
if (!is_readable)
|
|
g_assert_cmpint (res, ==, -1);
|
|
|
|
/* After closing the write end, we can't get WOULD_BLOCK any more */
|
|
if (!socket_out)
|
|
g_assert_cmpint (res, !=, -1);
|
|
|
|
if (res == -1)
|
|
{
|
|
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
|
|
g_error_free (error);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (res == 0)
|
|
break;
|
|
inptr += res;
|
|
total_read += res;
|
|
}
|
|
|
|
g_assert (total_read == n_read - 1); /* Last 2 zeros are combined */
|
|
g_assert (memcmp (converted, unexpanded_data, total_read) == 0);
|
|
|
|
g_object_unref (cstream);
|
|
g_object_unref (left);
|
|
|
|
g_converter_reset (compressor);
|
|
|
|
/* This doesn't actually test the behavior on
|
|
* G_IO_ERROR_WOULD_BLOCK; to do that we'd need to implement a
|
|
* custom GOutputStream that we could control blocking on.
|
|
*/
|
|
|
|
mem_out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
|
|
cstream_out = g_converter_output_stream_new (mem_out, compressor);
|
|
g_object_unref (mem_out);
|
|
pollable_out = G_POLLABLE_OUTPUT_STREAM (cstream_out);
|
|
|
|
for (i = 0; i < expanded_size; i++)
|
|
{
|
|
error = NULL;
|
|
res = g_pollable_output_stream_write_nonblocking (pollable_out,
|
|
expanded + i, 1,
|
|
NULL, &error);
|
|
g_assert (res != -1);
|
|
if (res == 0)
|
|
{
|
|
g_assert (i == expanded_size -1);
|
|
break;
|
|
}
|
|
g_assert (res == 1);
|
|
}
|
|
|
|
g_output_stream_close (cstream_out, NULL, NULL);
|
|
|
|
g_assert (g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out)) == n_read - 1); /* Last 2 zeros are combined */
|
|
g_assert (memcmp (g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_out)),
|
|
unexpanded_data,
|
|
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_out))) == 0);
|
|
|
|
g_object_unref (cstream_out);
|
|
|
|
g_free (expanded);
|
|
g_free (converted);
|
|
g_object_unref (expander);
|
|
g_object_unref (compressor);
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc,
|
|
char *argv[])
|
|
{
|
|
CompressorTest compressor_tests[] = {
|
|
{ "/converter-output-stream/corruption/zlib-0", G_ZLIB_COMPRESSOR_FORMAT_ZLIB, 0 },
|
|
{ "/converter-output-stream/corruption/zlib-9", G_ZLIB_COMPRESSOR_FORMAT_ZLIB, 9 },
|
|
{ "/converter-output-stream/corruption/gzip-0", G_ZLIB_COMPRESSOR_FORMAT_GZIP, 0 },
|
|
{ "/converter-output-stream/corruption/gzip-9", G_ZLIB_COMPRESSOR_FORMAT_GZIP, 9 },
|
|
{ "/converter-output-stream/corruption/raw-0", G_ZLIB_COMPRESSOR_FORMAT_RAW, 0 },
|
|
{ "/converter-output-stream/corruption/raw-9", G_ZLIB_COMPRESSOR_FORMAT_RAW, 9 },
|
|
};
|
|
CharsetTest charset_tests[] = {
|
|
{ "/converter-input-stream/charset/utf8->latin1", "UTF-8", "\303\205rr Sant\303\251", "ISO-8859-1", "\305rr Sant\351", 0 },
|
|
{ "/converter-input-stream/charset/latin1->utf8", "ISO-8859-1", "\305rr Sant\351", "UTF-8", "\303\205rr Sant\303\251", 0 },
|
|
{ "/converter-input-stream/charset/fallbacks", "UTF-8", "Some characters just don't fit into latin1: πא", "ISO-8859-1", "Some characters just don't fit into latin1: \\CF\\80\\D7\\90", 4 },
|
|
|
|
};
|
|
|
|
gint i;
|
|
|
|
g_type_init ();
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
g_test_bug_base ("http://bugzilla.gnome.org/");
|
|
|
|
g_test_add_func ("/converter-input-stream/expander", test_expander);
|
|
g_test_add_func ("/converter-input-stream/compressor", test_compressor);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (compressor_tests); i++)
|
|
g_test_add_data_func (compressor_tests[i].path, &compressor_tests[i], test_roundtrip);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (charset_tests); i++)
|
|
g_test_add_data_func (charset_tests[i].path, &charset_tests[i], test_charset);
|
|
|
|
g_test_add_func ("/converter-stream/pollable", test_converter_pollable);
|
|
g_test_add_func ("/converter-stream/leftover", test_converter_leftover);
|
|
|
|
return g_test_run();
|
|
}
|