/* $Id: pkzip.cpp $ */
/** @file
 * IPRT - PKZIP archive I/O.
 */

/*
 * Copyright (C) 2014 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/******************************************************************************
 *   Header Files                                                             *
 ******************************************************************************/
#include <iprt/zip.h>
#include <iprt/file.h>
#include <iprt/fs.h>
#include <iprt/mem.h>
#include <iprt/string.h>
#include <iprt/vfs.h>
#include <iprt/vfslowlevel.h>


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * Memory stream private data.
 */
typedef struct MEMIOSTREAM
{
    /** Size of the memory buffer. */
    size_t      cbBuf;
    /** Pointer to the memory buffer. */
    uint8_t     *pu8Buf;
    /** Current offset. */
    size_t      off;
} MEMIOSTREAM;
typedef MEMIOSTREAM *PMEMIOSTREAM;


/**
 * @interface_method_impl{RTVFSOBJOPS,pfnClose}
 */
static DECLCALLBACK(int) memFssIos_Close(void *pvThis)
{
    NOREF(pvThis);
    return VINF_SUCCESS;
}

/**
 * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
 */
static DECLCALLBACK(int) memFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
{
    PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
    switch (enmAddAttr)
    {
        case RTFSOBJATTRADD_NOTHING:
        case RTFSOBJATTRADD_UNIX:
            RT_ZERO(*pObjInfo);
            pObjInfo->cbObject = pThis->cbBuf;
            break;
        default:
            return VERR_NOT_SUPPORTED;
    }
    return VINF_SUCCESS;
}

/**
 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
 */
static DECLCALLBACK(int) memFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
{
    PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
    Assert(pSgBuf->cSegs == 1);

    if (off < 0)
        off = pThis->off;
    if (off >= (RTFOFF)pThis->cbBuf)
        return pcbRead ? VINF_EOF : VERR_EOF;

    size_t cbLeft = pThis->cbBuf - off;
    size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
    if (cbToRead > cbLeft)
    {
        if (!pcbRead)
            return VERR_EOF;
        cbToRead = (size_t)cbLeft;
    }

    memcpy(pSgBuf->paSegs[0].pvSeg, pThis->pu8Buf + off, cbToRead);
    pThis->off = off + cbToRead;
    if (pcbRead)
        *pcbRead = cbToRead;

    return VINF_SUCCESS;
}

/**
 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
 */
static DECLCALLBACK(int) memFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
{
    return VERR_NOT_IMPLEMENTED;
}

/**
 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
 */
static DECLCALLBACK(int) memFssIos_Flush(void *pvThis)
{
    return VERR_NOT_IMPLEMENTED;
}

/**
 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
 */
static DECLCALLBACK(int) memFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents)
{
    return VERR_NOT_IMPLEMENTED;
}

/**
 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
 */
static DECLCALLBACK(int) memFssIos_Tell(void *pvThis, PRTFOFF poffActual)
{
    PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
    *poffActual = pThis->off;
    return VINF_SUCCESS;
}

/**
 * Memory I/O object stream operations.
 */
static const RTVFSIOSTREAMOPS g_memFssIosOps =
{
    { /* Obj */
        RTVFSOBJOPS_VERSION,
        RTVFSOBJTYPE_IO_STREAM,
        "MemFsStream::IoStream",
        memFssIos_Close,
        memFssIos_QueryInfo,
        RTVFSOBJOPS_VERSION
    },
    RTVFSIOSTREAMOPS_VERSION,
    RTVFSIOSTREAMOPS_FEAT_NO_SG,
    memFssIos_Read,
    memFssIos_Write,
    memFssIos_Flush,
    memFssIos_PollOne,
    memFssIos_Tell,
    NULL /*Skip*/,
    NULL /*ZeroFill*/,
    RTVFSIOSTREAMOPS_VERSION
};

RTDECL(int) RTZipPkzipMemDecompress(void **ppvDst, size_t *pcbDst, const void *pvSrc, size_t cbSrc, const char *pszObject)
{
    PMEMIOSTREAM pIosData;
    RTVFSIOSTREAM hVfsIos;
    int rc = RTVfsNewIoStream(&g_memFssIosOps,
                              sizeof(*pIosData),
                              RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
                              NIL_RTVFS,
                              NIL_RTVFSLOCK,
                              &hVfsIos,
                              (void **)&pIosData);
    if (RT_SUCCESS(rc))
    {
        pIosData->pu8Buf = (uint8_t*)pvSrc;
        pIosData->cbBuf  = cbSrc;
        pIosData->off    = 0;
        RTVFSFSSTREAM hVfsFss;
        rc = RTZipPkzipFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, &hVfsFss);
        RTVfsIoStrmRelease(hVfsIos);
        if (RT_SUCCESS(rc))
        {
            /*
             * Loop through all objects. Actually this wouldn't be required
             * for .zip files but we opened it as I/O stream.
             */
            for (bool fFound = false; !fFound;)
            {
                char        *pszName;
                RTVFSOBJ    hVfsObj;
                rc = RTVfsFsStrmNext(hVfsFss, &pszName, NULL /*penmType*/, &hVfsObj);
                if (RT_FAILURE(rc))
                    break;
                fFound = !strcmp(pszName, pszObject);
                if (fFound)
                {
                    RTFSOBJINFO UnixInfo;
                    rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
                    if (RT_SUCCESS(rc))
                    {
                        size_t cb = UnixInfo.cbObject;
                        void *pv = RTMemAlloc(cb);
                        if (pv)
                        {
                            RTVFSIOSTREAM hVfsIosObj = RTVfsObjToIoStream(hVfsObj);
                            if (hVfsIos)
                            {
                                rc = RTVfsIoStrmRead(hVfsIosObj, pv, cb, true /*fBlocking*/, NULL);
                                if (RT_SUCCESS(rc))
                                {
                                    *ppvDst = pv;
                                    *pcbDst = cb;
                                }
                                else
                                    RTMemFree(pv);
                            }
                        }
                    }
                }
                RTVfsObjRelease(hVfsObj);
                RTStrFree(pszName);
            }
            RTVfsFsStrmRelease(hVfsFss);
        }
    }
    return rc;
}
