/* zip.c -- Backwards compatible zip interface
   part of the minizip-ng project

   Copyright (C) Nathan Moinvaziri
     https://github.com/zlib-ng/minizip-ng
   Copyright (C) 1998-2010 Gilles Vollant
     https://www.winimage.com/zLibDll/minizip.html

   This program is distributed under the terms of the same license as zlib.
   See the accompanying LICENSE file for the full text of the license.
*/

#include "mz.h"
#include "mz_os.h"
#include "mz_strm.h"
#include "mz_strm_os.h"
#include "mz_zip.h"

#include "zip.h"

/***************************************************************************/

typedef struct mz_zip_compat_s {
    void *stream;
    void *handle;
} mz_zip_compat;

/***************************************************************************/

static int32_t zipConvertAppendToStreamMode(int append) {
    int32_t mode = MZ_OPEN_MODE_WRITE;
    switch (append) {
    case APPEND_STATUS_CREATE:
        mode |= MZ_OPEN_MODE_CREATE;
        break;
    case APPEND_STATUS_CREATEAFTER:
        mode |= MZ_OPEN_MODE_CREATE | MZ_OPEN_MODE_APPEND;
        break;
    case APPEND_STATUS_ADDINZIP:
        mode |= MZ_OPEN_MODE_READ | MZ_OPEN_MODE_APPEND;
        break;
    }
    return mode;
}

zipFile zipOpen(const char *path, int append) {
    return zipOpen2(path, append, NULL, NULL);
}

zipFile zipOpen64(const void *path, int append) {
    return zipOpen2(path, append, NULL, NULL);
}

zipFile zipOpen2(const char *path, int append, zipcharpc *globalcomment, zlib_filefunc_def *pzlib_filefunc_def) {
    zipFile zip = NULL;
    int32_t mode = zipConvertAppendToStreamMode(append);
    void *stream = NULL;

    if (pzlib_filefunc_def) {
        if (pzlib_filefunc_def->zopen_file) {
            stream = mz_stream_ioapi_create();
            if (!stream)
                return NULL;
            mz_stream_ioapi_set_filefunc(stream, pzlib_filefunc_def);
        } else if (pzlib_filefunc_def->opaque) {
            stream = mz_stream_create((mz_stream_vtbl *)pzlib_filefunc_def->opaque);
            if (!stream)
                return NULL;
        }
    }

    if (!stream) {
        stream = mz_stream_os_create();
        if (!stream)
            return NULL;
    }

    if (mz_stream_open(stream, path, mode) != MZ_OK) {
        mz_stream_delete(&stream);
        return NULL;
    }

    zip = zipOpen_MZ(stream, append, globalcomment);

    if (!zip) {
        mz_stream_delete(&stream);
        return NULL;
    }

    return zip;
}

zipFile zipOpen2_64(const void *path, int append, zipcharpc *globalcomment, zlib_filefunc64_def *pzlib_filefunc_def) {
    zipFile zip = NULL;
    int32_t mode = zipConvertAppendToStreamMode(append);
    void *stream = NULL;

    if (pzlib_filefunc_def) {
        if (pzlib_filefunc_def->zopen64_file) {
            stream = mz_stream_ioapi_create();
            if (!stream)
                return NULL;
            mz_stream_ioapi_set_filefunc64(stream, pzlib_filefunc_def);
        } else if (pzlib_filefunc_def->opaque) {
            stream = mz_stream_create((mz_stream_vtbl *)pzlib_filefunc_def->opaque);
            if (!stream)
                return NULL;
        }
    }

    if (!stream) {
        stream = mz_stream_os_create();
        if (!stream)
            return NULL;
    }

    if (mz_stream_open(stream, path, mode) != MZ_OK) {
        mz_stream_delete(&stream);
        return NULL;
    }

    zip = zipOpen_MZ(stream, append, globalcomment);

    if (!zip) {
        mz_stream_delete(&stream);
        return NULL;
    }

    return zip;
}

zipFile zipOpen_MZ(void *stream, int append, zipcharpc *globalcomment) {
    mz_zip_compat *compat = NULL;
    int32_t err = MZ_OK;
    int32_t mode = zipConvertAppendToStreamMode(append);
    void *handle = NULL;

    handle = mz_zip_create();
    if (!handle)
        return NULL;

    err = mz_zip_open(handle, stream, mode);

    if (err != MZ_OK) {
        mz_zip_delete(&handle);
        return NULL;
    }

    if (globalcomment)
        mz_zip_get_comment(handle, globalcomment);

    compat = (mz_zip_compat *)calloc(1, sizeof(mz_zip_compat));
    if (compat) {
        compat->handle = handle;
        compat->stream = stream;
    } else {
        mz_zip_delete(&handle);
    }

    return (zipFile)compat;
}

void *zipGetHandle_MZ(zipFile file) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    if (!compat)
        return NULL;
    return compat->handle;
}

void *zipGetStream_MZ(zipFile file) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    if (!compat)
        return NULL;
    return (void *)compat->stream;
}

static time_t zipConvertZipDateToTime(tm_zip tmz_date) {
    struct tm tm_date;
    memset(&tm_date, 0, sizeof(struct tm));
    memcpy(&tm_date, &tmz_date, sizeof(tm_zip));
    return mz_zip_dosdate_to_time_t(mz_zip_tm_to_dosdate(&tm_date));
}

int zipOpenNewFileInZip5(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                         uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global,
                         const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel,
                         int strategy, const char *password, unsigned long crc_for_crypting,
                         unsigned long version_madeby, unsigned long flag_base, int zip64) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    mz_zip_file file_info;

    MZ_UNUSED(strategy);
    MZ_UNUSED(memLevel);
    MZ_UNUSED(windowBits);
    MZ_UNUSED(size_extrafield_local);
    MZ_UNUSED(extrafield_local);
    MZ_UNUSED(crc_for_crypting);

    if (!compat)
        return ZIP_PARAMERROR;

    /* The filename and comment length must fit in 16 bits. */
    if (filename && strlen(filename) > 0xffff)
        return ZIP_PARAMERROR;
    if (comment && strlen(comment) > 0xffff)
        return ZIP_PARAMERROR;

    memset(&file_info, 0, sizeof(file_info));

    if (zipfi) {
        if (zipfi->mz_dos_date != 0)
            file_info.modified_date = mz_zip_dosdate_to_time_t(zipfi->mz_dos_date);
        else
            file_info.modified_date = zipConvertZipDateToTime(zipfi->tmz_date);

        file_info.external_fa = (uint32_t)zipfi->external_fa;
        file_info.internal_fa = (uint16_t)zipfi->internal_fa;
    }

    if (!filename)
        filename = "-";

    file_info.compression_method = (uint16_t)compression_method;
    file_info.filename = filename;
    /* file_info.extrafield_local = extrafield_local; */
    /* file_info.extrafield_local_size = size_extrafield_local; */
    file_info.extrafield = extrafield_global;
    file_info.extrafield_size = size_extrafield_global;
    file_info.version_madeby = (uint16_t)version_madeby;
    file_info.comment = comment;
    if (file_info.comment)
        file_info.comment_size = (uint16_t)strlen(file_info.comment);
    file_info.flag = (uint16_t)flag_base;
    if (zip64)
        file_info.zip64 = MZ_ZIP64_FORCE;
    else
        file_info.zip64 = MZ_ZIP64_DISABLE;
#ifdef HAVE_WZAES
    if (password || (raw && (file_info.flag & MZ_ZIP_FLAG_ENCRYPTED)))
        file_info.aes_version = MZ_AES_VERSION;
#endif

    return mz_zip_entry_write_open(compat->handle, &file_info, (int16_t)level, (uint8_t)raw, password);
}

int zipOpenNewFileInZip4_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                            uint16_t size_extrafield_local, const void *extrafield_global,
                            uint16_t size_extrafield_global, const char *comment, int compression_method, int level,
                            int raw, int windowBits, int memLevel, int strategy, const char *password,
                            unsigned long crc_for_crypting, unsigned long version_madeby, unsigned long flag_base,
                            int zip64) {
    return zipOpenNewFileInZip5(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                size_extrafield_global, comment, compression_method, level, raw, windowBits, memLevel,
                                strategy, password, crc_for_crypting, version_madeby, flag_base, zip64);
}

int zipOpenNewFileInZip4(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                         uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global,
                         const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel,
                         int strategy, const char *password, unsigned long crc_for_crypting,
                         unsigned long version_madeby, unsigned long flag_base) {
    return zipOpenNewFileInZip4_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                   size_extrafield_global, comment, compression_method, level, raw, windowBits,
                                   memLevel, strategy, password, crc_for_crypting, version_madeby, flag_base, 0);
}

int zipOpenNewFileInZip3(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                         uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global,
                         const char *comment, int compression_method, int level, int raw, int windowBits, int memLevel,
                         int strategy, const char *password, unsigned long crc_for_crypting) {
    return zipOpenNewFileInZip3_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                   size_extrafield_global, comment, compression_method, level, raw, windowBits,
                                   memLevel, strategy, password, crc_for_crypting, 0);
}

int zipOpenNewFileInZip3_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                            uint16_t size_extrafield_local, const void *extrafield_global,
                            uint16_t size_extrafield_global, const char *comment, int compression_method, int level,
                            int raw, int windowBits, int memLevel, int strategy, const char *password,
                            unsigned long crc_for_crypting, int zip64) {
    return zipOpenNewFileInZip4_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                   size_extrafield_global, comment, compression_method, level, raw, windowBits,
                                   memLevel, strategy, password, crc_for_crypting, MZ_VERSION_MADEBY, 0, zip64);
}

int zipOpenNewFileInZip2(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                         uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global,
                         const char *comment, int compression_method, int level, int raw) {
    return zipOpenNewFileInZip3_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                   size_extrafield_global, comment, compression_method, level, raw, 0, 0, 0, NULL, 0,
                                   0);
}

int zipOpenNewFileInZip2_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                            uint16_t size_extrafield_local, const void *extrafield_global,
                            uint16_t size_extrafield_global, const char *comment, int compression_method, int level,
                            int raw, int zip64) {
    return zipOpenNewFileInZip3_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                   size_extrafield_global, comment, compression_method, level, raw, 0, 0, 0, NULL, 0,
                                   zip64);
}

int zipOpenNewFileInZip(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                        uint16_t size_extrafield_local, const void *extrafield_global, uint16_t size_extrafield_global,
                        const char *comment, int compression_method, int level) {
    return zipOpenNewFileInZip_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                  size_extrafield_global, comment, compression_method, level, 0);
}

int zipOpenNewFileInZip64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                          uint16_t size_extrafield_local, const void *extrafield_global,
                          uint16_t size_extrafield_global, const char *comment, int compression_method, int level,
                          int zip64) {
    return zipOpenNewFileInZip2_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                   size_extrafield_global, comment, compression_method, level, 0, zip64);
}

int zipOpenNewFileInZip_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, const void *extrafield_local,
                           uint16_t size_extrafield_local, const void *extrafield_global,
                           uint16_t size_extrafield_global, const char *comment, int compression_method, int level,
                           int zip64) {
    return zipOpenNewFileInZip2_64(file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global,
                                   size_extrafield_global, comment, compression_method, level, 0, zip64);
}

int zipWriteInFileInZip(zipFile file, const void *buf, uint32_t len) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    int32_t written = 0;
    if (!compat || len >= INT32_MAX)
        return ZIP_PARAMERROR;
    written = mz_zip_entry_write(compat->handle, buf, (int32_t)len);
    if ((written < 0) || ((uint32_t)written != len))
        return ZIP_ERRNO;
    return ZIP_OK;
}

int zipCloseFileInZipRaw(zipFile file, unsigned long uncompressed_size, unsigned long crc32) {
    return zipCloseFileInZipRaw64(file, uncompressed_size, crc32);
}

int zipCloseFileInZipRaw64(zipFile file, uint64_t uncompressed_size, unsigned long crc32) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    if (!compat)
        return ZIP_PARAMERROR;
    return mz_zip_entry_close_raw(compat->handle, (int64_t)uncompressed_size, (uint32_t)crc32);
}

int zipCloseFileInZip(zipFile file) {
    return zipCloseFileInZip64(file);
}

int zipCloseFileInZip64(zipFile file) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    if (!compat)
        return ZIP_PARAMERROR;
    return mz_zip_entry_close(compat->handle);
}

int zipClose(zipFile file, const char *global_comment) {
    return zipClose_64(file, global_comment);
}

int zipClose_64(zipFile file, const char *global_comment) {
    return zipClose2_64(file, global_comment, MZ_VERSION_MADEBY);
}

int zipClose2_64(zipFile file, const char *global_comment, uint16_t version_madeby) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    int32_t err = MZ_OK;

    if (compat->handle)
        err = zipClose2_MZ(file, global_comment, version_madeby);

    if (compat->stream) {
        mz_stream_close(compat->stream);
        mz_stream_delete(&compat->stream);
    }

    free(compat);

    return err;
}

/* Only closes the zip handle, does not close the stream */
int zipClose_MZ(zipFile file, const char *global_comment) {
    return zipClose2_MZ(file, global_comment, MZ_VERSION_MADEBY);
}

/* Only closes the zip handle, does not close the stream */
int zipClose2_MZ(zipFile file, const char *global_comment, uint16_t version_madeby) {
    mz_zip_compat *compat = (mz_zip_compat *)file;
    int32_t err = MZ_OK;

    if (!compat)
        return ZIP_PARAMERROR;
    if (!compat->handle)
        return err;

    if (global_comment)
        mz_zip_set_comment(compat->handle, global_comment);

    mz_zip_set_version_madeby(compat->handle, version_madeby);
    err = mz_zip_close(compat->handle);
    mz_zip_delete(&compat->handle);

    return err;
}
