| /* GIO - GLib Input, Output and Streaming Library | |
| * | |
| * Copyright (C) 2009 Red Hat, Inc. | |
| * | |
| * SPDX-License-Identifier: LGPL-2.1-or-later | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library 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 | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General | |
| * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
| * | |
| * Author: Alexander Larsson <alexl@redhat.com> | |
| */ | |
| #include "config.h" | |
| #include "gzlibdecompressor.h" | |
| #include <errno.h> | |
| #include <zlib.h> | |
| #include <string.h> | |
| #include "gfileinfo.h" | |
| #include "gioerror.h" | |
| #include "gioenums.h" | |
| #include "gioenumtypes.h" | |
| #include "glibintl.h" | |
| enum { | |
| PROP_0, | |
| PROP_FORMAT, | |
| PROP_FILE_INFO | |
| }; | |
| /** | |
| * GZlibDecompressor: | |
| * | |
| * `GZlibDecompressor` is an implementation of [iface@Gio.Converter] that | |
| * decompresses data compressed with zlib. | |
| */ | |
| static void g_zlib_decompressor_iface_init (GConverterIface *iface); | |
| typedef struct { | |
| gz_header gzheader; | |
| char filename[257]; | |
| GFileInfo *file_info; | |
| } HeaderData; | |
| struct _GZlibDecompressor | |
| { | |
| GObject parent_instance; | |
| GZlibCompressorFormat format; | |
| z_stream zstream; | |
| HeaderData *header_data; | |
| }; | |
| static void | |
| g_zlib_decompressor_set_gzheader (GZlibDecompressor *decompressor) | |
| { | |
| /* On win32, these functions were not exported before 1.2.4 */ | |
| #if !defined (G_OS_WIN32) || ZLIB_VERNUM >= 0x1240 | |
| if (decompressor->format != G_ZLIB_COMPRESSOR_FORMAT_GZIP) | |
| return; | |
| if (decompressor->header_data != NULL) | |
| { | |
| if (decompressor->header_data->file_info) | |
| g_object_unref (decompressor->header_data->file_info); | |
| memset (decompressor->header_data, 0, sizeof (HeaderData)); | |
| } | |
| else | |
| { | |
| decompressor->header_data = g_new0 (HeaderData, 1); | |
| } | |
| decompressor->header_data->gzheader.name = (Bytef*) &decompressor->header_data->filename; | |
| /* We keep one byte to guarantee the string is 0-terminated */ | |
| decompressor->header_data->gzheader.name_max = 256; | |
| if (inflateGetHeader (&decompressor->zstream, &decompressor->header_data->gzheader) != Z_OK) | |
| g_warning ("unexpected zlib error: %s", decompressor->zstream.msg); | |
| #endif /* !G_OS_WIN32 || ZLIB >= 1.2.4 */ | |
| } | |
| G_DEFINE_TYPE_WITH_CODE (GZlibDecompressor, g_zlib_decompressor, G_TYPE_OBJECT, | |
| G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, | |
| g_zlib_decompressor_iface_init)) | |
| static void | |
| g_zlib_decompressor_finalize (GObject *object) | |
| { | |
| GZlibDecompressor *decompressor; | |
| decompressor = G_ZLIB_DECOMPRESSOR (object); | |
| inflateEnd (&decompressor->zstream); | |
| if (decompressor->header_data != NULL) | |
| { | |
| if (decompressor->header_data->file_info) | |
| g_object_unref (decompressor->header_data->file_info); | |
| g_free (decompressor->header_data); | |
| } | |
| G_OBJECT_CLASS (g_zlib_decompressor_parent_class)->finalize (object); | |
| } | |
| static void | |
| g_zlib_decompressor_set_property (GObject *object, | |
| guint prop_id, | |
| const GValue *value, | |
| GParamSpec *pspec) | |
| { | |
| GZlibDecompressor *decompressor; | |
| decompressor = G_ZLIB_DECOMPRESSOR (object); | |
| switch (prop_id) | |
| { | |
| case PROP_FORMAT: | |
| decompressor->format = g_value_get_enum (value); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
| break; | |
| } | |
| } | |
| static void | |
| g_zlib_decompressor_get_property (GObject *object, | |
| guint prop_id, | |
| GValue *value, | |
| GParamSpec *pspec) | |
| { | |
| GZlibDecompressor *decompressor; | |
| decompressor = G_ZLIB_DECOMPRESSOR (object); | |
| switch (prop_id) | |
| { | |
| case PROP_FORMAT: | |
| g_value_set_enum (value, decompressor->format); | |
| break; | |
| case PROP_FILE_INFO: | |
| if (decompressor->header_data) | |
| g_value_set_object (value, decompressor->header_data->file_info); | |
| else | |
| g_value_set_object (value, NULL); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
| break; | |
| } | |
| } | |
| static void | |
| g_zlib_decompressor_init (GZlibDecompressor *decompressor) | |
| { | |
| } | |
| static void | |
| g_zlib_decompressor_constructed (GObject *object) | |
| { | |
| GZlibDecompressor *decompressor; | |
| int res; | |
| decompressor = G_ZLIB_DECOMPRESSOR (object); | |
| if (decompressor->format == G_ZLIB_COMPRESSOR_FORMAT_GZIP) | |
| { | |
| /* + 16 for gzip */ | |
| res = inflateInit2 (&decompressor->zstream, MAX_WBITS + 16); | |
| } | |
| else if (decompressor->format == G_ZLIB_COMPRESSOR_FORMAT_RAW) | |
| { | |
| /* Negative for raw */ | |
| res = inflateInit2 (&decompressor->zstream, -MAX_WBITS); | |
| } | |
| else /* ZLIB */ | |
| res = inflateInit (&decompressor->zstream); | |
| if (res == Z_MEM_ERROR ) | |
| g_error ("GZlibDecompressor: Not enough memory for zlib use"); | |
| if (res != Z_OK) | |
| g_warning ("unexpected zlib error: %s", decompressor->zstream.msg); | |
| g_zlib_decompressor_set_gzheader (decompressor); | |
| } | |
| static void | |
| g_zlib_decompressor_class_init (GZlibDecompressorClass *klass) | |
| { | |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); | |
| gobject_class->finalize = g_zlib_decompressor_finalize; | |
| gobject_class->constructed = g_zlib_decompressor_constructed; | |
| gobject_class->get_property = g_zlib_decompressor_get_property; | |
| gobject_class->set_property = g_zlib_decompressor_set_property; | |
| /** | |
| * GZlibDecompressor:format: | |
| * | |
| * The format of the compressed data. | |
| * | |
| * Since: 2.24 | |
| */ | |
| g_object_class_install_property (gobject_class, | |
| PROP_FORMAT, | |
| g_param_spec_enum ("format", NULL, NULL, | |
| G_TYPE_ZLIB_COMPRESSOR_FORMAT, | |
| G_ZLIB_COMPRESSOR_FORMAT_ZLIB, | |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | | |
| G_PARAM_STATIC_STRINGS)); | |
| /** | |
| * GZlibDecompressor:file-info: | |
| * | |
| * A #GFileInfo containing the information found in the GZIP header | |
| * of the data stream processed, or %NULL if the header was not yet | |
| * fully processed, is not present at all, or the compressor's | |
| * #GZlibDecompressor:format property is not %G_ZLIB_COMPRESSOR_FORMAT_GZIP. | |
| * | |
| * Since: 2.26 | |
| */ | |
| g_object_class_install_property (gobject_class, | |
| PROP_FILE_INFO, | |
| g_param_spec_object ("file-info", NULL, NULL, | |
| G_TYPE_FILE_INFO, | |
| G_PARAM_READABLE | | |
| G_PARAM_STATIC_STRINGS)); | |
| } | |
| /** | |
| * g_zlib_decompressor_new: | |
| * @format: The format to use for the compressed data | |
| * | |
| * Creates a new #GZlibDecompressor. | |
| * | |
| * Returns: a new #GZlibDecompressor | |
| * | |
| * Since: 2.24 | |
| **/ | |
| GZlibDecompressor * | |
| g_zlib_decompressor_new (GZlibCompressorFormat format) | |
| { | |
| GZlibDecompressor *decompressor; | |
| decompressor = g_object_new (G_TYPE_ZLIB_DECOMPRESSOR, | |
| "format", format, | |
| NULL); | |
| return decompressor; | |
| } | |
| /** | |
| * g_zlib_decompressor_get_file_info: | |
| * @decompressor: a #GZlibDecompressor | |
| * | |
| * Retrieves the #GFileInfo constructed from the GZIP header data | |
| * of compressed data processed by @compressor, or %NULL if @decompressor's | |
| * #GZlibDecompressor:format property is not %G_ZLIB_COMPRESSOR_FORMAT_GZIP, | |
| * or the header data was not fully processed yet, or it not present in the | |
| * data stream at all. | |
| * | |
| * Returns: (nullable) (transfer none): a #GFileInfo, or %NULL | |
| * | |
| * Since: 2.26 | |
| */ | |
| GFileInfo * | |
| g_zlib_decompressor_get_file_info (GZlibDecompressor *decompressor) | |
| { | |
| g_return_val_if_fail (G_IS_ZLIB_DECOMPRESSOR (decompressor), NULL); | |
| if (decompressor->header_data) | |
| return decompressor->header_data->file_info; | |
| return NULL; | |
| } | |
| static void | |
| g_zlib_decompressor_reset (GConverter *converter) | |
| { | |
| GZlibDecompressor *decompressor = G_ZLIB_DECOMPRESSOR (converter); | |
| int res; | |
| res = inflateReset (&decompressor->zstream); | |
| if (res != Z_OK) | |
| g_warning ("unexpected zlib error: %s", decompressor->zstream.msg); | |
| g_zlib_decompressor_set_gzheader (decompressor); | |
| } | |
| static GConverterResult | |
| g_zlib_decompressor_convert (GConverter *converter, | |
| const void *inbuf, | |
| gsize inbuf_size, | |
| void *outbuf, | |
| gsize outbuf_size, | |
| GConverterFlags flags, | |
| gsize *bytes_read, | |
| gsize *bytes_written, | |
| GError **error) | |
| { | |
| GZlibDecompressor *decompressor; | |
| int res; | |
| decompressor = G_ZLIB_DECOMPRESSOR (converter); | |
| decompressor->zstream.next_in = (void *)inbuf; | |
| decompressor->zstream.avail_in = inbuf_size; | |
| decompressor->zstream.next_out = outbuf; | |
| decompressor->zstream.avail_out = outbuf_size; | |
| res = inflate (&decompressor->zstream, Z_NO_FLUSH); | |
| if (res == Z_DATA_ERROR || res == Z_NEED_DICT) | |
| { | |
| g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, | |
| _("Invalid compressed data")); | |
| return G_CONVERTER_ERROR; | |
| } | |
| if (res == Z_MEM_ERROR) | |
| { | |
| g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, | |
| _("Not enough memory")); | |
| return G_CONVERTER_ERROR; | |
| } | |
| if (res == Z_STREAM_ERROR) | |
| { | |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, | |
| _("Internal error: %s"), decompressor->zstream.msg); | |
| return G_CONVERTER_ERROR; | |
| } | |
| if (res == Z_BUF_ERROR) | |
| { | |
| if (flags & G_CONVERTER_FLUSH) | |
| return G_CONVERTER_FLUSHED; | |
| /* Z_FINISH not set, so this means no progress could be made */ | |
| /* We do have output space, so this should only happen if we | |
| have no input but need some */ | |
| g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, | |
| _("Need more input")); | |
| return G_CONVERTER_ERROR; | |
| } | |
| g_assert (res == Z_OK || res == Z_STREAM_END); | |
| *bytes_read = inbuf_size - decompressor->zstream.avail_in; | |
| *bytes_written = outbuf_size - decompressor->zstream.avail_out; | |
| #if !defined (G_OS_WIN32) || ZLIB_VERNUM >= 0x1240 | |
| if (decompressor->header_data != NULL && | |
| decompressor->header_data->gzheader.done == 1) | |
| { | |
| HeaderData *data = decompressor->header_data; | |
| /* So we don't notify again */ | |
| data->gzheader.done = 2; | |
| data->file_info = g_file_info_new (); | |
| g_file_info_set_attribute_uint64 (data->file_info, | |
| G_FILE_ATTRIBUTE_TIME_MODIFIED, | |
| data->gzheader.time); | |
| g_file_info_set_attribute_uint32 (data->file_info, | |
| G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, | |
| 0); | |
| g_file_info_set_attribute_uint32 (data->file_info, | |
| G_FILE_ATTRIBUTE_TIME_MODIFIED_NSEC, | |
| 0); | |
| if (data->filename[0] != '\0') | |
| g_file_info_set_attribute_byte_string (data->file_info, | |
| G_FILE_ATTRIBUTE_STANDARD_NAME, | |
| data->filename); | |
| g_object_notify (G_OBJECT (decompressor), "file-info"); | |
| } | |
| #endif /* !G_OS_WIN32 || ZLIB >= 1.2.4 */ | |
| if (res == Z_STREAM_END) | |
| return G_CONVERTER_FINISHED; | |
| return G_CONVERTER_CONVERTED; | |
| } | |
| static void | |
| g_zlib_decompressor_iface_init (GConverterIface *iface) | |
| { | |
| iface->convert = g_zlib_decompressor_convert; | |
| iface->reset = g_zlib_decompressor_reset; | |
| } |