| /* 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; |
| } |