diff --git a/gio/gbase64decoder.c b/gio/gbase64decoder.c new file mode 100644 index 000000000..e3020cf99 --- /dev/null +++ b/gio/gbase64decoder.c @@ -0,0 +1,115 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * SPDX-FileCopyrightText: 2009 Red Hat, Inc. + * SPDX-FileCopyrightText: 2010 Christian Persch + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include "gbase64decoder.h" + +#include + +#include + +#include "gioenums.h" +#include "gioenumtypes.h" +#include "gioerror.h" +#include "glibintl.h" + +/** + * GBase64Decoder: + * + * `GBase64Decoder` is an implementation of `GConverter` that + * converts data from base64 encoding. + * + * Since: 2.82 + */ + +struct _GBase64Decoder +{ + GObject parent_instance; + + int state; + guint save; +}; + +static void +g_base64_decoder_reset (GConverter *converter) +{ + GBase64Decoder *decoder = G_BASE64_DECODER (converter); + + decoder->state = 0; + decoder->save = 0; +} + +static GConverterResult +g_base64_decoder_convert (GConverter *converter, + const void *inbuf, + gsize inbuf_size, + void *outbuf, + gsize outbuf_size, + GConverterFlags flags, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + GBase64Decoder *decoder = G_BASE64_DECODER (converter); + + if (outbuf_size < ((inbuf_size / 4) * 3 + 3)) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "Not enough space in dest"); + return G_CONVERTER_ERROR; + } + + *bytes_read = inbuf_size; + *bytes_written = g_base64_decode_step (inbuf, inbuf_size, outbuf, + &decoder->state, &decoder->save); + + if (flags & G_CONVERTER_FLUSH) + return G_CONVERTER_FLUSHED; + if (*bytes_read == inbuf_size && (flags & G_CONVERTER_INPUT_AT_END)) + return G_CONVERTER_FINISHED; + + return G_CONVERTER_CONVERTED; +} + +static void +g_base64_decoder_iface_init (GConverterIface *iface) +{ + iface->convert = g_base64_decoder_convert; + iface->reset = g_base64_decoder_reset; +} + +G_DEFINE_TYPE_WITH_CODE (GBase64Decoder, g_base64_decoder, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, g_base64_decoder_iface_init)) + +static void +g_base64_decoder_init (GBase64Decoder *decoder) +{ + decoder->state = 0; + decoder->save = 0; +} + +static void +g_base64_decoder_class_init (GBase64DecoderClass *klass) +{ +} + +/** + * g_base64_decoder_new: + * + * Creates a new `GBase64Decoder`. + * + * Returns: a new `GBase64Decoder` + * + * Since: 2.82 + */ +GConverter * +g_base64_decoder_new (void) +{ + return g_object_new (G_TYPE_BASE64_DECODER, NULL); +} diff --git a/gio/gbase64decoder.h b/gio/gbase64decoder.h new file mode 100644 index 000000000..d41fa63ab --- /dev/null +++ b/gio/gbase64decoder.h @@ -0,0 +1,25 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * SPDX-FileCopyrightText: 2009 Red Hat, Inc. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define G_TYPE_BASE64_DECODER (g_base64_decoder_get_type ()) + +GIO_AVAILABLE_IN_2_82 +G_DECLARE_FINAL_TYPE (GBase64Decoder, g_base64_decoder, G, BASE64_DECODER, GObject) + +GIO_AVAILABLE_IN_2_82 +GConverter *g_base64_decoder_new (void); + +G_END_DECLS diff --git a/gio/gbase64encoder.c b/gio/gbase64encoder.c new file mode 100644 index 000000000..6c3c0b886 --- /dev/null +++ b/gio/gbase64encoder.c @@ -0,0 +1,216 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * SPDX-FileCopyrightText: 2009 Red Hat, Inc. + * SPDX-FileCopyrightText: 2010 Christian Persch + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include "gbase64encoder.h" + +#include + +#include + +#include "gioenums.h" +#include "gioenumtypes.h" +#include "gioerror.h" +#include "glib.h" +#include "glibintl.h" + + +#define BASE64_ENCODING_OUTPUT_SIZE(len, break_lines) \ + (((len) / 3 + 1) * 4 + 4 + ((break_lines) ? ((((len) / 3 + 1) * 4 + 4) / 72 + 1) : 0)) + +/** + * GBase64Encoder: + * + * GBase64Encoder is an implementation of `GConverter` that + * converts data to base64 encoding. + * + * Since: 2.82 + */ + +struct _GBase64Encoder +{ + GObject parent_instance; + + gboolean break_lines; + int state[2]; +}; + +enum +{ + PROP_0, + PROP_BREAK_LINES +}; + +static void +g_base64_encoder_reset (GConverter *converter) +{ + GBase64Encoder *encoder = G_BASE64_ENCODER (converter); + + encoder->state[0] = encoder->state[1] = 0; +} + +static GConverterResult +g_base64_encoder_convert (GConverter *converter, + const void *inbuf, + gsize inbuf_size, + void *outbuf, + gsize outbuf_size, + GConverterFlags flags, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + GBase64Encoder *encoder = G_BASE64_ENCODER (converter); + + if (inbuf_size == 0 || (flags & G_CONVERTER_FLUSH)) + { + if (outbuf_size < (encoder->break_lines ? 5 : 4)) + { + g_set_error_literal (error, + G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "Need more output space"); + return G_CONVERTER_ERROR; + } + + *bytes_read = 0; + *bytes_written = g_base64_encode_close (encoder->break_lines, outbuf, + &encoder->state[0], + &encoder->state[1]); + + if (flags & G_CONVERTER_FLUSH) + return G_CONVERTER_FLUSHED; + if (flags & G_CONVERTER_INPUT_AT_END) + return G_CONVERTER_FINISHED; + + return G_CONVERTER_CONVERTED; + } + + if (outbuf_size < BASE64_ENCODING_OUTPUT_SIZE (inbuf_size, encoder->break_lines)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "Need more output space"); + return G_CONVERTER_ERROR; + } + + *bytes_read = inbuf_size; + *bytes_written = g_base64_encode_step (inbuf, inbuf_size, + encoder->break_lines, + outbuf, + &encoder->state[0], + &encoder->state[1]); + if (flags & G_CONVERTER_INPUT_AT_END) + return G_CONVERTER_FINISHED; + + return G_CONVERTER_CONVERTED; +} + +static void +g_base64_encoder_iface_init (GConverterIface *iface) +{ + iface->convert = g_base64_encoder_convert; + iface->reset = g_base64_encoder_reset; +} + +G_DEFINE_TYPE_WITH_CODE (GBase64Encoder, g_base64_encoder, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, g_base64_encoder_iface_init)) + +static void +g_base64_encoder_init (GBase64Encoder *encoder) +{ + encoder->break_lines = FALSE; + encoder->state[0] = encoder->state[1] = 0; +} + +static void +g_base64_encoder_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GBase64Encoder *encoder = G_BASE64_ENCODER (object); + + switch (prop_id) + { + case PROP_BREAK_LINES: + encoder->break_lines = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_base64_encoder_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GBase64Encoder *encoder = G_BASE64_ENCODER (object); + + switch (prop_id) + { + case PROP_BREAK_LINES: + g_value_set_boolean (value, encoder->break_lines); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_base64_encoder_class_init (GBase64EncoderClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = g_base64_encoder_get_property; + gobject_class->set_property = g_base64_encoder_set_property; + + /** + * GBase64Encoder:break-lines: + * + * Whether to break lines. + * + * This is typically used when putting base64-encoded data in emails. + * It breaks the lines at 72 columns instead of putting all of the text on + * the same line. This avoids problems with long lines in the email system. + */ + g_object_class_install_property (gobject_class, + PROP_BREAK_LINES, + g_param_spec_boolean ("break-lines", NULL, NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +/** + * g_base64_encoder_new: + * @break_lines: whether to break long lines + * + * Creates a new `GBase64Encoder`. + * + * Setting @break_lines to `TRUE` is typically used when putting + * base64-encoded data in emails. It breaks the lines at 72 columns + * instead of putting all of the text on the same line. This avoids + * problems with long lines in the email system. + * + * Returns: a new `GBase64Encoder` + * + * Since: 2.82 + */ +GConverter * +g_base64_encoder_new (gboolean break_lines) +{ + return g_object_new (G_TYPE_BASE64_ENCODER, + "break-lines", break_lines, + NULL); +} diff --git a/gio/gbase64encoder.h b/gio/gbase64encoder.h new file mode 100644 index 000000000..48d0528ed --- /dev/null +++ b/gio/gbase64encoder.h @@ -0,0 +1,25 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * SPDX-FileCopyrightText: 2009 Red Hat, Inc. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define G_TYPE_BASE64_ENCODER (g_base64_encoder_get_type ()) + +GIO_AVAILABLE_IN_2_82 +G_DECLARE_FINAL_TYPE (GBase64Encoder, g_base64_encoder, G, BASE64_ENCODER, GObject) + +GIO_AVAILABLE_IN_2_82 +GConverter *g_base64_encoder_new (gboolean break_lines); + +G_END_DECLS diff --git a/gio/gio.h b/gio/gio.h index c17657db0..4893fef48 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/gio/meson.build b/gio/meson.build index 59c2b0fc0..d735956bf 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -480,6 +480,8 @@ gio_base_sources = files( 'gasynchelper.c', 'gasyncinitable.c', 'gasyncresult.c', + 'gbase64decoder.c', + 'gbase64encoder.c', 'gbufferedinputstream.c', 'gbufferedoutputstream.c', 'gbytesicon.c', diff --git a/gio/tests/converter-stream.c b/gio/tests/converter-stream.c index 761be0c67..d1f866eb0 100644 --- a/gio/tests/converter-stream.c +++ b/gio/tests/converter-stream.c @@ -1189,6 +1189,70 @@ test_converter_basics (void) g_object_unref (converter); } +static void +test_converter_base64 (void) +{ + guchar data[8192]; + gchar *encoded; + GInputStream *input; + GOutputStream *output; + GOutputStream *converter; + GInputStream *converter2; + GConverter *conv; + GOutputStreamSpliceFlags flags; + gssize size; + GError *error = NULL; + char *result; + + for (gsize i = 0; i < sizeof (data); i++) + data[i] = g_test_rand_int_range (0, 255); + + encoded = g_base64_encode (data, sizeof (data)); + + conv = g_base64_encoder_new (FALSE); + + input = g_memory_input_stream_new_from_data (data, sizeof (data), NULL); + output = g_memory_output_stream_new_resizable (); + converter = g_converter_output_stream_new (output, conv); + + flags = G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET; + size = g_output_stream_splice (converter, input, flags, NULL, &error); + g_assert_true (size != -1); + g_assert_no_error (error); + + result = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (output)); + g_assert_cmpstr (encoded, ==, result); + + g_object_unref (converter); + g_object_unref (output); + g_object_unref (input); + g_object_unref (conv); + g_free (result); + + conv = g_base64_decoder_new (); + + input = g_memory_input_stream_new_from_data (encoded, strlen (encoded), NULL); + converter2 = g_converter_input_stream_new (input, conv); + output = g_memory_output_stream_new_resizable (); + + flags = G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET; + size = g_output_stream_splice (output, converter2, flags, NULL, &error); + g_assert_true (size != -1); + g_assert_no_error (error); + + result = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (output)); + + g_assert_true (memcmp (data, result, sizeof (data)) == 0); + + g_object_unref (converter2); + g_object_unref (output); + g_object_unref (input); + g_object_unref (conv); + g_free (result); + + g_free (encoded); +} + int main (int argc, char *argv[]) @@ -1232,5 +1296,7 @@ main (int argc, g_test_add_func ("/converter-stream/pollable", test_converter_pollable); g_test_add_func ("/converter-stream/leftover", test_converter_leftover); + g_test_add_func ("/converter-stream/base64", test_converter_base64); + return g_test_run(); } diff --git a/gio/tests/filter-cat.c b/gio/tests/filter-cat.c index 3688cefde..1dbe06fff 100644 --- a/gio/tests/filter-cat.c +++ b/gio/tests/filter-cat.c @@ -47,6 +47,9 @@ static gboolean decompress = FALSE; static gboolean compress = FALSE; static gboolean gzip = FALSE; static gboolean fallback = FALSE; +static gboolean base64_decode = FALSE; +static gboolean base64_encode = FALSE; +static gboolean base64_break_lines = FALSE; static GOptionEntry entries[] = { {"decompress", 0, 0, G_OPTION_ARG_NONE, &decompress, "decompress", NULL}, @@ -55,14 +58,17 @@ static GOptionEntry entries[] = { {"from-charset", 0, 0, G_OPTION_ARG_STRING, &from_charset, "from charset", NULL}, {"to-charset", 0, 0, G_OPTION_ARG_STRING, &to_charset, "to charset", NULL}, {"fallback", 0, 0, G_OPTION_ARG_NONE, &fallback, "use fallback", NULL}, + {"from-base64", 0, 0, G_OPTION_ARG_NONE, &base64_decode, "decode from Base-64", NULL}, + {"to-base64", 0, 0, G_OPTION_ARG_NONE, &base64_encode, "encode to Base-64", NULL}, + {"break-lines", 0, 0, G_OPTION_ARG_NONE, &base64_break_lines, "break lines in Base-64", NULL}, {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &locations, "locations", NULL}, G_OPTION_ENTRY_NULL }; static void decompressor_file_info_notify_cb (GZlibDecompressor *decompressor, - GParamSpec *pspec, - gpointer data) + GParamSpec *pspec, + gpointer data) { GFileInfo *file_info; const gchar *filename; @@ -100,10 +106,22 @@ cat (GFile * file) return; } + if (base64_decode) + { + GInputStream *old; + conv = (GConverter *) g_base64_decoder_new (); + old = in; + in = (GInputStream *) g_converter_input_stream_new (in, conv); + g_object_unref (conv); + g_object_unref (old); + } + if (decompress) { GInputStream *old; - conv = (GConverter *)g_zlib_decompressor_new (gzip?G_ZLIB_COMPRESSOR_FORMAT_GZIP:G_ZLIB_COMPRESSOR_FORMAT_ZLIB); + conv = (GConverter *) g_zlib_decompressor_new (gzip + ? G_ZLIB_COMPRESSOR_FORMAT_GZIP + : G_ZLIB_COMPRESSOR_FORMAT_ZLIB); old = in; in = (GInputStream *) g_converter_input_stream_new (in, conv); g_signal_connect (conv, "notify::file-info", G_CALLBACK (decompressor_file_info_notify_cb), NULL); @@ -152,7 +170,10 @@ cat (GFile * file) return; } - conv = (GConverter *)g_zlib_compressor_new(gzip?G_ZLIB_COMPRESSOR_FORMAT_GZIP:G_ZLIB_COMPRESSOR_FORMAT_ZLIB, -1); + conv = (GConverter *) g_zlib_compressor_new (gzip + ? G_ZLIB_COMPRESSOR_FORMAT_GZIP + : G_ZLIB_COMPRESSOR_FORMAT_ZLIB, + -1); g_zlib_compressor_set_file_info (G_ZLIB_COMPRESSOR (conv), in_file_info); old = in; in = (GInputStream *) g_converter_input_stream_new (in, conv); @@ -161,6 +182,16 @@ cat (GFile * file) g_object_unref (in_file_info); } + if (base64_encode) + { + GInputStream *old; + conv = (GConverter *) g_base64_encoder_new (base64_break_lines); + old = in; + in = (GInputStream *) g_converter_input_stream_new (in, conv); + g_object_unref (conv); + g_object_unref (old); + } + while (1) { res =