/*
 *  $Id: gwyzip-minizip1.h 29594 2026-03-09 13:49:26Z yeti-dn $
 *  Copyright (C) 2015-2024 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Do not include this file directly, let gwyzip.h include it as needed! */

#ifndef __GWY_FILE_MINIZIP1_H__
#define __GWY_FILE_MINIZIP1_H__

#include <unzip.h>

// XXX: Minizip1 in fact has memory buffer API, but the library is obsolete so it does not seem worth it.
#undef GWYZIP_HAS_MEMORY_FILES

struct _GwyZipFile {
    unzFile *unzfile;
    gchar *tmpfilename;
    guint index;
};

#ifdef G_OS_WIN32
G_GNUC_UNUSED
static voidpf
gwyzip_minizip_open_file_func(G_GNUC_UNUSED voidpf opaque,
                              const char* filename,
                              G_GNUC_UNUSED int mode)
{
    /* Don't implement other modes.  We never write ZIP files with minizip. */
    return (voidpf)gwy_fopen(filename, "rb");
}

G_GNUC_UNUSED
static uLong
gwyzip_minizip_read_file_func(G_GNUC_UNUSED voidpf opaque,
                              voidpf stream,
                              void* buf,
                              uLong size)
{
    return fread(buf, 1, size, (FILE*)stream);
}

G_GNUC_UNUSED
static uLong
gwyzip_minizip_write_file_func(G_GNUC_UNUSED voidpf opaque,
                               G_GNUC_UNUSED voidpf stream,
                               G_GNUC_UNUSED const void* buf,
                               G_GNUC_UNUSED uLong size)
{
    /* Don't implement writing.  We never write ZIP files with minizip. */
    errno = ENOSYS;
    return 0;
}

G_GNUC_UNUSED
static int
gwyzip_minizip_close_file_func(G_GNUC_UNUSED voidpf opaque,
                               voidpf stream)
{
    return fclose((FILE*)stream);
}

G_GNUC_UNUSED
static int
gwyzip_minizip_testerror_file_func(G_GNUC_UNUSED voidpf opaque,
                                   voidpf stream)
{
    return ferror((FILE*)stream);
}

G_GNUC_UNUSED
static long
gwyzip_minizip_tell_file_func(G_GNUC_UNUSED voidpf opaque,
                              voidpf stream)
{
    return ftell((FILE*)stream);
}

G_GNUC_UNUSED
static long
gwyzip_minizip_seek_file_func(G_GNUC_UNUSED voidpf opaque,
                              voidpf stream,
                              uLong offset,
                              int origin)
{
    return fseek((FILE*)stream, offset, origin);
}

G_GNUC_UNUSED
static GwyZipFile
gwyzip_open(const gchar *path, GError **error)
{
    static zlib_filefunc_def ffdef = {
        gwyzip_minizip_open_file_func,
        gwyzip_minizip_read_file_func,
        gwyzip_minizip_write_file_func,
        gwyzip_minizip_tell_file_func,
        gwyzip_minizip_seek_file_func,
        gwyzip_minizip_close_file_func,
        gwyzip_minizip_testerror_file_func,
        NULL,
    };
    struct _GwyZipFile *zipfile;
    unzFile *unzfile;

    if (!(unzfile = unzOpen2(path, &ffdef))) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_SPECIFIC,
                    _("%s cannot open the file as a ZIP file."), "Minizip");
        return NULL;
    }

    zipfile = g_new0(struct _GwyZipFile, 1);
    zipfile->unzfile = unzfile;
    return zipfile;
}
#else
G_GNUC_UNUSED
static GwyZipFile
gwyzip_open(const gchar *path, GError **error)
{
    struct _GwyZipFile *zipfile;
    unzFile *unzfile;

    if (!(unzfile = unzOpen(path))) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_SPECIFIC,
                    _("%s cannot open the file as a ZIP file."), "Minizip");
        return NULL;
    }

    zipfile = g_new0(struct _GwyZipFile, 1);
    zipfile->unzfile = unzfile;
    return zipfile;
}
#endif

G_GNUC_UNUSED
static gboolean
err_MINIZIP(gint status, GError **error)
{
    const gchar *errstr = _("Unknown error");

    if (status == UNZ_ERRNO)
        errstr = g_strerror(errno);
    else if (status == UNZ_EOF)
        errstr = _("End of file");
    else if (status == UNZ_END_OF_LIST_OF_FILE)
        errstr = _("End of list of files");
    else if (status == UNZ_PARAMERROR)
        errstr = _("Parameter error");
    else if (status == UNZ_BADZIPFILE)
        errstr = _("Bad zip file");
    else if (status == UNZ_INTERNALERROR)
        errstr = _("Internal error");
    else if (status == UNZ_CRCERROR)
        errstr = _("CRC error");

    g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                _("%s error while reading the zip file: %s (%d)."), "Minizip", errstr, status);
    return FALSE;
}

G_GNUC_UNUSED
static void
gwyzip_close(GwyZipFile zipfile)
{
    unzClose(zipfile->unzfile);
    if (zipfile->tmpfilename) {
        g_unlink(zipfile->tmpfilename);
        g_free(zipfile->tmpfilename);
    }
    g_free(zipfile);
}

G_GNUC_UNUSED
static gboolean
gwyzip_next_file(GwyZipFile zipfile, GError **error)
{
    gint status;
    if ((status = unzGoToNextFile(zipfile->unzfile)) == UNZ_OK) {
        zipfile->index++;
        return TRUE;
    }
    err_MINIZIP(status, error);
    return FALSE;
}

G_GNUC_UNUSED
static gboolean
gwyzip_first_file(GwyZipFile zipfile, GError **error)
{
    gint status;
    if ((status = unzGoToFirstFile(zipfile->unzfile)) == UNZ_OK) {
        zipfile->index = 0;
        return TRUE;
    }
    err_MINIZIP(status, error);
    return FALSE;
}

G_GNUC_UNUSED
static gboolean
gwyzip_get_current_filename(GwyZipFile zipfile, gchar **filename, GError **error)
{
    unz_file_info fileinfo;
    gint status;
    gchar *filename_buf;
    guint size = 256;

    /* unzGetCurrentFileInfo() nul-terminates the string if we pass longer buffer than actual file name length - which
     * is only uint16. */
    filename_buf = g_new(gchar, size+1);
    status = unzGetCurrentFileInfo(zipfile->unzfile, &fileinfo, filename_buf, size, NULL, 0, NULL, 0);
    if (status == UNZ_OK && fileinfo.size_filename > size) {
        g_free(filename_buf);
        size = fileinfo.size_filename;
        filename_buf = g_new(gchar, size+1);
        status = unzGetCurrentFileInfo(zipfile->unzfile, &fileinfo, filename_buf, size, NULL, 0, NULL, 0);
    }
    if (status != UNZ_OK) {
        g_free(filename_buf);
        *filename = NULL;
        err_MINIZIP(status, error);
        return FALSE;
    }
    filename_buf[size] = '\0';
    *filename = filename_buf;
    return TRUE;
}

/* Minizip from zlib v1.3 has the following unzLocateFile prototype with a simple flag:
int unzLocateFile(unzFile file, const char *szFileName, int iCaseSensitivity);

But standalone MiniZip (v1.2) has the version with unzFileNameComparer:
int unzLocateFile(unzFile file, const char *filename, unzFileNameComparer filename_compare_func);

So the APIs are not compatible. It is not clear how to tell them apart. One can get either in older systems, both
called minizip. MiniZip defines UNZ_BADPASSWORD error which zlib does not have. Using it to decide is probably better
than nothing (since minizip1 is obsolete, it is probably OK to assume the distinction stays). */

#ifdef UNZ_BADPASSWORD
static gint
gwyzip_minizip_icasesens_compare(G_GNUC_UNUSED unzFile unzfile,
                                 const char *filename1, const char *filename2)
{
    return g_ascii_strcasecmp(filename1, filename2);
}

G_GNUC_UNUSED
static gboolean
gwyzip_locate_file(GwyZipFile zipfile, const gchar *filename, gint casesens, GError **error)
{
    unzFileNameComparer comparer = NULL;
    if (!casesens)
        comparer = gwyzip_minizip_icasesens_compare;

    gwy_debug("calling unzLocateFile() to find %s", filename);
    if (unzLocateFile(zipfile->unzfile, filename, comparer) != UNZ_OK) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                    _("File %s is missing in the zip file."), filename);
        return FALSE;
    }
    return TRUE;
}
#else
G_GNUC_UNUSED
static gboolean
gwyzip_locate_file(GwyZipFile zipfile, const gchar *filename, gint casesens, GError **error)
{
    gwy_debug("calling unzLocateFile() to find %s", filename);
    if (unzLocateFile(zipfile->unzfile, filename, casesens) != UNZ_OK) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_IO,
                    _("File %s is missing in the zip file."), filename);
        return FALSE;
    }
    return TRUE;
}
#endif

G_GNUC_UNUSED
static guchar*
gwyzip_get_file_content(GwyZipFile zipfile, gsize *contentsize, GError **error)
{
    unz_file_info fileinfo;
    guchar *buffer;
    gulong size;
    glong readbytes;
    gint status;

    gwy_debug("calling unzGetCurrentFileInfo() to figure out buffer size");
    status = unzGetCurrentFileInfo(zipfile->unzfile, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
    if (status != UNZ_OK) {
        err_MINIZIP(status, error);
        return NULL;
    }

    gwy_debug("calling unzGetCurrentFileInfo()");
    status = unzOpenCurrentFile(zipfile->unzfile);
    if (status != UNZ_OK) {
        err_MINIZIP(status, error);
        return NULL;
    }

    size = fileinfo.uncompressed_size;
    gwy_debug("uncompressed_size: %lu", size);
    buffer = g_new(guchar, size + 1);
    gwy_debug("calling unzReadCurrentFile()");
    readbytes = unzReadCurrentFile(zipfile->unzfile, buffer, size);
    if (readbytes != size) {
        err_MINIZIP(status, error);
        unzCloseCurrentFile(zipfile->unzfile);
        g_free(buffer);
        return NULL;
    }
    gwy_debug("calling unzCloseCurrentFile()");
    unzCloseCurrentFile(zipfile->unzfile);

    buffer[size] = '\0';
    if (contentsize)
        *contentsize = size;
    return buffer;
}

#endif

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
