/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <ZipPackage.hxx>
#include "ZipPackageSink.hxx"
#include <ZipEnumeration.hxx>
#include <ZipPackageStream.hxx>
#include <ZipPackageFolder.hxx>
#include <ZipOutputEntry.hxx>
#include <ZipOutputStream.hxx>
#include <ZipPackageBuffer.hxx>
#include <ZipFile.hxx>
#include <PackageConstants.hxx>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/packages/zip/ZipConstants.hpp>
#include <com/sun/star/packages/zip/ZipException.hpp>
#include <com/sun/star/packages/zip/ZipIOException.hpp>
#include <com/sun/star/packages/manifest/ManifestReader.hpp>
#include <com/sun/star/packages/manifest/ManifestWriter.hpp>
#include <com/sun/star/io/TempFile.hpp>
#include <com/sun/star/io/XStream.hpp>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/io/XTruncate.hpp>
#include <com/sun/star/io/XSeekable.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/container/XNameContainer.hpp>
#include <officecfg/Office/Common.hxx>
#include <comphelper/DirectoryHelper.hxx>
#include <comphelper/fileurl.hxx>
#include <comphelper/processfactory.hxx>
#include <ucbhelper/content.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <com/sun/star/ucb/ContentCreationException.hpp>
#include <com/sun/star/ucb/TransferInfo.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
#include <com/sun/star/ucb/OpenMode.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <com/sun/star/io/XActiveDataStreamer.hpp>
#include <com/sun/star/embed/UseBackupException.hpp>
#include <com/sun/star/embed/StorageFormats.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/xml/crypto/DigestID.hpp>
#include <com/sun/star/xml/crypto/KDFID.hpp>
#include <cppuhelper/implbase.hxx>
#include <rtl/uri.hxx>
#include <rtl/random.h>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <unotools/streamwrap.hxx>
#include <unotools/tempfile.hxx>
#include <com/sun/star/io/XAsyncOutputMonitor.hpp>

#include <string_view>

#include <comphelper/seekableinput.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/ofopxmlhelper.hxx>
#include <comphelper/documentconstants.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/servicehelper.hxx>
#include <utility>

using namespace std::literals;

using namespace osl;
using namespace cppu;
using namespace ucbhelper;
using namespace com::sun::star;
using namespace com::sun::star::io;
using namespace com::sun::star::uno;
using namespace com::sun::star::ucb;
using namespace com::sun::star::util;
using namespace com::sun::star::lang;
using namespace com::sun::star::task;
using namespace com::sun::star::beans;
using namespace com::sun::star::packages;
using namespace com::sun::star::container;
using namespace com::sun::star::packages::zip;
using namespace com::sun::star::packages::manifest;
using namespace com::sun::star::packages::zip::ZipConstants;

#if OSL_DEBUG_LEVEL > 0
#define THROW_WHERE SAL_WHERE
#else
#define THROW_WHERE ""
#endif

namespace {

class ActiveDataStreamer : public ::cppu::WeakImplHelper< XActiveDataStreamer >
{
    uno::Reference< XStream > mStream;
public:

    virtual uno::Reference< XStream > SAL_CALL getStream() override
            { return mStream; }

    virtual void SAL_CALL setStream( const uno::Reference< XStream >& stream ) override
            { mStream = stream; }
};

class DummyInputStream : public ::cppu::WeakImplHelper< XInputStream >
{
    virtual sal_Int32 SAL_CALL readBytes( uno::Sequence< sal_Int8 >&, sal_Int32 ) override
        { return 0; }

    virtual sal_Int32 SAL_CALL readSomeBytes( uno::Sequence< sal_Int8 >&, sal_Int32 ) override
        { return 0; }

    virtual void SAL_CALL skipBytes( sal_Int32 ) override
        {}

    virtual sal_Int32 SAL_CALL available() override
        { return 0; }

    virtual void SAL_CALL closeInput() override
        {}
};

} // namespace

sal_Int32 GetDefaultDerivedKeySize(sal_Int32 const nCipherID)
{
    switch (nCipherID)
    {
        case css::xml::crypto::CipherID::BLOWFISH_CFB_8:
            return 16;
        case css::xml::crypto::CipherID::AES_CBC_W3C_PADDING:
        case css::xml::crypto::CipherID::AES_GCM_W3C:
            return 32;
        default:
            O3TL_UNREACHABLE;
    }
}

ZipPackage::ZipPackage ( uno::Reference < XComponentContext > xContext )
: m_aMutexHolder( new comphelper::RefCountedMutex )
, m_nStartKeyGenerationID( xml::crypto::DigestID::SHA1 )
, m_oChecksumDigestID( xml::crypto::DigestID::SHA1_1K )
, m_nKeyDerivationFunctionID(xml::crypto::KDFID::PBKDF2)
, m_nCommonEncryptionID( xml::crypto::CipherID::BLOWFISH_CFB_8 )
, m_bHasEncryptedEntries ( false )
, m_bHasNonEncryptedEntries ( false )
, m_bInconsistent ( false )
, m_bForceRecovery ( false )
, m_bMediaTypeFallbackUsed ( false )
, m_nFormat( embed::StorageFormats::PACKAGE ) // package is the default format
, m_bAllowRemoveOnInsert( true )
, m_eMode ( e_IMode_None )
, m_xContext(std::move( xContext ))
{
    m_xRootFolder = new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
}

ZipPackage::~ZipPackage()
{
}

bool ZipPackage::isLocalFile() const
{
    return comphelper::isFileUrl(m_aURL);
}

// note: don't check for StorageFormats::ZIP, it breaks signing!
void ZipPackage::checkZipEntriesWithDD()
{
    if (!m_bForceRecovery)
    {
        ZipEnumeration entries{m_pZipFile->entries()};
        while (entries.hasMoreElements())
        {
            ZipEntry const& rEntry{*entries.nextElement()};
            if ((rEntry.nFlag & 0x08) != 0 && rEntry.nMethod == STORED)
            {
                uno::Reference<XPropertySet> xStream;
                getByHierarchicalName(rEntry.sPath) >>= xStream;
                uno::Reference<XServiceInfo> const xStreamSI{xStream, uno::UNO_QUERY_THROW};
                if (!xStreamSI->supportsService("com.sun.star.packages.PackageStream"))
                {
                    SAL_INFO("package", "entry STORED with data descriptor is folder: \"" << rEntry.sPath << "\"");
                    throw ZipIOException(
                        THROW_WHERE
                        "entry STORED with data descriptor is folder");
                }
                if (!xStream->getPropertyValue("WasEncrypted").get<bool>())
                {
                    SAL_INFO("package", "entry STORED with data descriptor but not encrypted: \"" << rEntry.sPath << "\"");
                    throw ZipIOException(
                        THROW_WHERE
                        "entry STORED with data descriptor but not encrypted");
                }
            }
        }
    }
}

void ZipPackage::parseManifest()
{
    if ( m_nFormat != embed::StorageFormats::PACKAGE )
        return;

    bool bManifestParsed = false;
    ::std::optional<OUString> oFirstVersion;
    static constexpr OUString sMeta (u"META-INF"_ustr);
    if ( m_xRootFolder->hasByName( sMeta ) )
    {
        try {
            static constexpr OUString sManifest (u"manifest.xml"_ustr);
            Any aAny = m_xRootFolder->getByName( sMeta );
            uno::Reference< XNameContainer > xMetaInfFolder;
            aAny >>= xMetaInfFolder;
            if ( xMetaInfFolder.is() && xMetaInfFolder->hasByName( sManifest ) )
            {
                uno::Reference < XActiveDataSink > xSink;
                aAny = xMetaInfFolder->getByName( sManifest );
                aAny >>= xSink;
                if ( xSink.is() )
                {
                    uno::Reference < XManifestReader > xReader = ManifestReader::create( m_xContext );

                    const uno::Sequence < uno::Sequence < PropertyValue > > aManifestSequence = xReader->readManifestSequence ( xSink->getInputStream() );
                    const Any *pKeyInfo = nullptr;

                    for ( const uno::Sequence<PropertyValue>& rSequence : aManifestSequence )
                    {
                        OUString sPath, sMediaType, sVersion;
                        const Any *pSalt = nullptr, *pVector = nullptr, *pCount = nullptr, *pSize = nullptr, *pDigest = nullptr, *pDigestAlg = nullptr, *pEncryptionAlg = nullptr, *pStartKeyAlg = nullptr, *pDerivedKeySize = nullptr;
                        uno::Any const* pKDF = nullptr;
                        uno::Any const* pArgon2Args = nullptr;
                        for ( const PropertyValue& rValue : rSequence )
                        {
                            if ( rValue.Name == u"FullPath" )
                                rValue.Value >>= sPath;
                            else if ( rValue.Name == u"Version" )
                            {
                                rValue.Value >>= sVersion;
                                if (!oFirstVersion)
                                {
                                    oFirstVersion.emplace(sVersion);
                                }
                            }
                            else if ( rValue.Name == u"MediaType" )
                                rValue.Value >>= sMediaType;
                            else if ( rValue.Name == u"Salt" )
                                pSalt = &( rValue.Value );
                            else if ( rValue.Name == u"InitialisationVector" )
                                pVector = &( rValue.Value );
                            else if ( rValue.Name == u"IterationCount" )
                                pCount = &( rValue.Value );
                            else if ( rValue.Name == u"Size" )
                                pSize = &( rValue.Value );
                            else if ( rValue.Name == u"Digest" )
                                pDigest = &( rValue.Value );
                            else if ( rValue.Name == u"DigestAlgorithm" )
                                pDigestAlg = &( rValue.Value );
                            else if ( rValue.Name == u"EncryptionAlgorithm" )
                                pEncryptionAlg = &( rValue.Value );
                            else if ( rValue.Name == u"StartKeyAlgorithm" )
                                pStartKeyAlg = &( rValue.Value );
                            else if ( rValue.Name == u"DerivedKeySize" )
                                pDerivedKeySize = &( rValue.Value );
                            else if ( rValue.Name == u"KeyInfo" )
                                pKeyInfo = &( rValue.Value );
                            else if (rValue.Name == u"KeyDerivationFunction") {
                                pKDF = &rValue.Value;
                            } else if (rValue.Name == u"Argon2Args") {
                                pArgon2Args = &rValue.Value;
                            }
                        }

                        if ( !sPath.isEmpty() && hasByHierarchicalName ( sPath ) )
                        {
                            aAny = getByHierarchicalName( sPath );
                            uno::Reference < XInterface > xTmp;
                            aAny >>= xTmp;
                            if (auto pFolder = dynamic_cast<ZipPackageFolder*>(xTmp.get()))
                            {
                                pFolder->SetMediaType ( sMediaType );
                                pFolder->SetVersion ( sVersion );
                            }
                            else if (auto pStream = dynamic_cast<ZipPackageStream*>(xTmp.get()))
                            {
                                pStream->SetMediaType ( sMediaType );
                                pStream->SetFromManifest( true );

                                if (pKeyInfo
                                    && pVector && pSize && pEncryptionAlg
                                    && pKDF && pKDF->has<sal_Int32>() && pKDF->get<sal_Int32>() == xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P
                                    && ((pEncryptionAlg->has<sal_Int32>()
                                            && pEncryptionAlg->get<sal_Int32>() == xml::crypto::CipherID::AES_GCM_W3C)
                                        || (pDigest && pDigestAlg)))
                                {
                                    uno::Sequence < sal_Int8 > aSequence;
                                    sal_Int64 nSize = 0;
                                    ::std::optional<sal_Int32> oDigestAlg;
                                    sal_Int32 nEncryptionAlg = 0;

                                    pStream->SetToBeEncrypted ( true );

                                    *pVector >>= aSequence;
                                    pStream->setInitialisationVector ( aSequence );

                                    *pSize >>= nSize;
                                    pStream->setSize ( nSize );

                                    if (pDigest && pDigestAlg)
                                    {
                                        *pDigest >>= aSequence;
                                        pStream->setDigest(aSequence);

                                        assert(pDigestAlg->has<sal_Int32>());
                                        oDigestAlg.emplace(pDigestAlg->get<sal_Int32>());
                                    }

                                    *pEncryptionAlg >>= nEncryptionAlg;

                                    *pKeyInfo >>= m_aGpgProps;

                                    pStream->SetToBeCompressed ( true );
                                    pStream->SetToBeEncrypted ( true );
                                    pStream->SetIsEncrypted ( true );
                                    pStream->setIterationCount(::std::optional<sal_Int32>());
                                    pStream->setArgon2Args(::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>>());

                                    // clamp to default SHA256 start key magic value,
                                    // c.f. ZipPackageStream::GetEncryptionKey()
                                    // trying to get key value from properties
                                    const sal_Int32 nStartKeyAlg = xml::crypto::DigestID::SHA256;

                                    pStream->SetImportedAlgorithms({
                                        .nImportedStartKeyAlgorithm = nStartKeyAlg,
                                        .nImportedEncryptionAlgorithm = nEncryptionAlg,
                                        .oImportedChecksumAlgorithm = oDigestAlg,
                                        // note m_nCommonEncryptionID is not inited yet here
                                        .nImportedDerivedKeySize = ::GetDefaultDerivedKeySize(nEncryptionAlg),
                                        });

                                    if (!m_bHasEncryptedEntries
                                        && (pStream->getName() == "content.xml"
                                            || pStream->getName() == "encrypted-package"))
                                    {
                                        m_bHasEncryptedEntries = true;
                                        m_oChecksumDigestID = oDigestAlg;
                                        m_nKeyDerivationFunctionID = xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P;
                                        m_nCommonEncryptionID = nEncryptionAlg;
                                        m_nStartKeyGenerationID = nStartKeyAlg;
                                    }
                                }
                                else if (pSalt
                                    && pVector && pSize && pEncryptionAlg
                                    && pKDF && pKDF->has<sal_Int32>()
                                    && ((pKDF->get<sal_Int32>() == xml::crypto::KDFID::PBKDF2 && pCount)
                                        || (pKDF->get<sal_Int32>() == xml::crypto::KDFID::Argon2id && pArgon2Args))
                                    && ((pEncryptionAlg->has<sal_Int32>()
                                            && pEncryptionAlg->get<sal_Int32>() == xml::crypto::CipherID::AES_GCM_W3C)
                                        || (pDigest && pDigestAlg)))

                                {
                                    uno::Sequence < sal_Int8 > aSequence;
                                    sal_Int64 nSize = 0;
                                    ::std::optional<sal_Int32> oDigestAlg;
                                    sal_Int32 nKDF = 0;
                                    sal_Int32 nEncryptionAlg = 0;
                                    sal_Int32 nCount = 0;
                                    sal_Int32 nDerivedKeySize = 16, nStartKeyAlg = xml::crypto::DigestID::SHA1;

                                    pStream->SetToBeEncrypted ( true );

                                    *pSalt >>= aSequence;
                                    pStream->setSalt ( aSequence );

                                    *pVector >>= aSequence;
                                    pStream->setInitialisationVector ( aSequence );

                                    *pKDF >>= nKDF;

                                    if (pCount)
                                    {
                                        *pCount >>= nCount;
                                        pStream->setIterationCount(::std::optional<sal_Int32>(nCount));
                                    }

                                    if (pArgon2Args)
                                    {
                                        uno::Sequence<sal_Int32> args;
                                        *pArgon2Args >>= args;
                                        assert(args.getLength() == 3);
                                        ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgs;
                                        oArgs.emplace(args[0], args[1], args[2]);
                                        pStream->setArgon2Args(oArgs);
                                    }

                                    *pSize >>= nSize;
                                    pStream->setSize ( nSize );

                                    if (pDigest && pDigestAlg)
                                    {
                                        *pDigest >>= aSequence;
                                        pStream->setDigest(aSequence);

                                        assert(pDigestAlg->has<sal_Int32>());
                                        oDigestAlg.emplace(pDigestAlg->get<sal_Int32>());
                                    }

                                    *pEncryptionAlg >>= nEncryptionAlg;

                                    if ( pDerivedKeySize )
                                        *pDerivedKeySize >>= nDerivedKeySize;

                                    if ( pStartKeyAlg )
                                        *pStartKeyAlg >>= nStartKeyAlg;

                                    pStream->SetImportedAlgorithms({
                                        .nImportedStartKeyAlgorithm = nStartKeyAlg,
                                        .nImportedEncryptionAlgorithm = nEncryptionAlg,
                                        .oImportedChecksumAlgorithm = oDigestAlg,
                                        .nImportedDerivedKeySize = nDerivedKeySize,
                                        });

                                    pStream->SetToBeCompressed ( true );
                                    pStream->SetToBeEncrypted ( true );
                                    pStream->SetIsEncrypted ( true );
                                    if (!m_bHasEncryptedEntries
                                        && (pStream->getName() == "content.xml"
                                            || pStream->getName() == "encrypted-package"))
                                    {
                                        m_bHasEncryptedEntries = true;
                                        m_nStartKeyGenerationID = nStartKeyAlg;
                                        m_nKeyDerivationFunctionID = nKDF;
                                        m_oChecksumDigestID = oDigestAlg;
                                        m_nCommonEncryptionID = nEncryptionAlg;
                                    }
                                }
                                else
                                    m_bHasNonEncryptedEntries = true;
                            }
                            else
                                throw ZipIOException(THROW_WHERE "Wrong content");
                        }
                    }

                    bManifestParsed = true;
                }

                checkZipEntriesWithDD(); // check before removing entries!

                // now hide the manifest.xml file from user
                xMetaInfFolder->removeByName( sManifest );
            }
        }
        catch( Exception& )
        {
            if ( !m_bForceRecovery )
                throw;
        }
    }

    if ( !bManifestParsed && !m_bForceRecovery )
        throw ZipIOException(
            THROW_WHERE "Could not parse manifest.xml" );

    static constexpr OUString sMimetype (u"mimetype"_ustr);
    if ( m_xRootFolder->hasByName( sMimetype ) )
    {
        // get mediatype from the "mimetype" stream
        OUString aPackageMediatype;
        uno::Reference < io::XActiveDataSink > xMimeSink;
        m_xRootFolder->getByName( sMimetype ) >>= xMimeSink;
        if ( xMimeSink.is() )
        {
            uno::Reference< io::XInputStream > xMimeInStream = xMimeSink->getInputStream();
            if ( xMimeInStream.is() )
            {
                // Mediatypes longer than 1024 symbols should not appear here
                uno::Sequence< sal_Int8 > aData( 1024 );
                sal_Int32 nRead = xMimeInStream->readBytes( aData, 1024 );
                if ( nRead > aData.getLength() )
                    nRead = aData.getLength();

                if ( nRead )
                    aPackageMediatype = OUString( reinterpret_cast<char const *>(aData.getConstArray()), nRead, RTL_TEXTENCODING_ASCII_US );
            }
        }

        if (!bManifestParsed || m_xRootFolder->GetMediaType().isEmpty())
        {
            // the manifest.xml could not be successfully parsed, this is an inconsistent package
            if ( aPackageMediatype.startsWith("application/vnd.") )
            {
                // accept only types that look similar to own mediatypes
                m_xRootFolder->SetMediaType( aPackageMediatype );
                // also set version explicitly
                if (oFirstVersion && m_xRootFolder->GetVersion().isEmpty())
                {
                    m_xRootFolder->SetVersion(*oFirstVersion);
                }
                // if there is an encrypted inner package, there is no root
                // document, because instead there is a package, and it is not
                // an error
                if (!m_xRootFolder->hasByName(u"encrypted-package"sv))
                {
                    m_bMediaTypeFallbackUsed = true;
                }
            }
        }
        else if ( !m_bForceRecovery )
        {
            // the mimetype stream should contain the same information as manifest.xml
            OUString const mediaTypeXML(m_xRootFolder->hasByName(u"encrypted-package"sv)
                ? m_xRootFolder->doGetByName(u"encrypted-package").xPackageEntry->GetMediaType()
                : m_xRootFolder->GetMediaType());
            if (mediaTypeXML != aPackageMediatype)
            {
                throw ZipIOException(
                    THROW_WHERE
                    "mimetype conflicts with manifest.xml, \""
                    + mediaTypeXML + "\" vs. \""
                    + aPackageMediatype + "\"" );
            }
        }

        m_xRootFolder->removeByName( sMimetype );
    }

    m_bInconsistent = m_xRootFolder->LookForUnexpectedODF12Streams(
        std::u16string_view(), m_xRootFolder->hasByName(u"encrypted-package"sv));

    bool bODF12AndNewer = ( m_xRootFolder->GetVersion().compareTo( ODFVER_012_TEXT ) >= 0 );
    if ( !m_bForceRecovery && bODF12AndNewer )
    {
        if ( m_bInconsistent )
        {
            // this is an ODF1.2 document that contains streams not referred in the manifest.xml;
            // in case of ODF1.2 documents without version in manifest.xml the property IsInconsistent
            // should be checked later
            throw ZipIOException(
                THROW_WHERE "there are streams not referred in manifest.xml" );
        }
        // all the streams should be encrypted with the same StartKey in ODF1.2
        // TODO/LATER: in future the exception should be thrown
        // throw ZipIOException( THROW_WHERE "More than one Start Key Generation algorithm is specified!" );
    }

    // in case it is a correct ODF1.2 document, the version must be set
    // and the META-INF folder is reserved for package format
    if ( bODF12AndNewer )
        m_xRootFolder->removeByName( sMeta );
}

void ZipPackage::parseContentType()
{
    if ( m_nFormat != embed::StorageFormats::OFOPXML )
        return;

    try {
        static constexpr std::u16string_view aContentTypes(u"[Content_Types].xml");
        // the content type must exist in OFOPXML format!
        if ( !m_xRootFolder->hasByName( aContentTypes ) )
            throw io::IOException(THROW_WHERE "Wrong format!" );

        uno::Reference < io::XActiveDataSink > xSink;
        uno::Any aAny = m_xRootFolder->getByName( aContentTypes );
        aAny >>= xSink;
        if ( xSink.is() )
        {
            uno::Reference< io::XInputStream > xInStream = xSink->getInputStream();
            if ( xInStream.is() )
            {
                // here aContentTypeInfo[0] - Defaults, and aContentTypeInfo[1] - Overrides
                const uno::Sequence< uno::Sequence< beans::StringPair > > aContentTypeInfo =
                    ::comphelper::OFOPXMLHelper::ReadContentTypeSequence( xInStream, m_xContext );

                if ( aContentTypeInfo.getLength() != 2 )
                    throw io::IOException(THROW_WHERE );

                // set the implicit types first
                for ( const auto& rPair : aContentTypeInfo[0] )
                    m_xRootFolder->setChildStreamsTypeByExtension( rPair );

                // now set the explicit types
                for ( const auto& rPair : aContentTypeInfo[1] )
                {
                    OUString aPath;
                    if ( rPair.First.toChar() == '/' )
                        aPath = rPair.First.copy( 1 );
                    else
                        aPath = rPair.First;

                    if ( !aPath.isEmpty() && hasByHierarchicalName( aPath ) )
                    {
                        uno::Any aIterAny = getByHierarchicalName( aPath );
                        uno::Reference < XInterface > xIterTmp;
                        aIterAny >>= xIterTmp;
                        if (auto pStream = dynamic_cast<ZipPackageStream*>(xIterTmp.get()))
                        {
                            // this is a package stream, in OFOPXML format only streams can have mediatype
                            pStream->SetMediaType( rPair.Second );
                        }
                    }
                }
            }
        }

        m_xRootFolder->removeByName( aContentTypes );
    }
    catch( uno::Exception& )
    {
        if ( !m_bForceRecovery )
            throw;
    }
}

void ZipPackage::getZipFileContents()
{
    ZipEnumeration aEnum = m_pZipFile->entries();
    OUString sDirName;

    while (aEnum.hasMoreElements())
    {
        ZipPackageFolder* pCurrent = m_xRootFolder.get();
        const ZipEntry & rEntry = *aEnum.nextElement();
        OUString rName = rEntry.sPath;

        if ( m_bForceRecovery )
        {
            // the PKZIP Application note version 6.2 does not allows to use '\' as separator
            // unfortunately it is used by some implementations, so we have to support it in recovery mode
            rName = rName.replace( '\\', '/' );
        }

        sal_Int32 nStreamIndex = rName.lastIndexOf ( '/' );
        if ( nStreamIndex != -1 )
        {
            sDirName = rName.copy ( 0, nStreamIndex );
            FolderHash::iterator aIter = m_aRecent.find ( sDirName );
            if ( aIter != m_aRecent.end() )
                pCurrent = ( *aIter ).second;
        }

        if ( pCurrent == m_xRootFolder.get() )
        {
            sal_Int32 nIndex;
            sal_Int32 nOldIndex = 0;
            while ( ( nIndex = rName.indexOf( '/', nOldIndex ) ) != -1 )
            {
                std::u16string_view sTemp = rName.subView( nOldIndex, nIndex - nOldIndex );
                if ( nIndex == nOldIndex )
                    break;
                if ( !pCurrent->hasByName( sTemp ) )
                {
                    rtl::Reference<ZipPackageFolder> pPkgFolder = new ZipPackageFolder(m_xContext, m_nFormat, m_bAllowRemoveOnInsert);
                    try {
                        pPkgFolder->setName( OUString(sTemp) );
                    } catch (uno::RuntimeException const& e) {
                        throw css::packages::zip::ZipIOException(e.Message);
                    }
                    pPkgFolder->doSetParent( pCurrent );
                    pCurrent = pPkgFolder.get();
                }
                else
                {
                    ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp);
                    if (!rInfo.bFolder)
                        throw css::packages::zip::ZipIOException(u"Bad Zip File, stream as folder"_ustr);
                    pCurrent = rInfo.pFolder;
                }
                nOldIndex = nIndex+1;
            }
            if ( nStreamIndex != -1 && !sDirName.isEmpty() )
                m_aRecent [ sDirName ] = pCurrent;
        }
        if ( rName.getLength() -1 != nStreamIndex )
        {
            nStreamIndex++;
            std::u16string_view sTemp = rName.subView( nStreamIndex );

            if (!pCurrent->hasByName(sTemp))
            {
                rtl::Reference<ZipPackageStream> pPkgStream = new ZipPackageStream(*this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert);
                pPkgStream->SetPackageMember(true);
                pPkgStream->setZipEntryOnLoading(rEntry);
                pPkgStream->setName(OUString(sTemp));
                pPkgStream->doSetParent(pCurrent);
            }
        }
    }

    if ( m_nFormat == embed::StorageFormats::PACKAGE )
        parseManifest();
    else if ( m_nFormat == embed::StorageFormats::OFOPXML )
    {
        parseContentType();
        checkZipEntriesWithDD();
    }
}

void SAL_CALL ZipPackage::initialize( const uno::Sequence< Any >& aArguments )
{
    beans::NamedValue aNamedValue;

    if ( !aArguments.hasElements() )
        return;

    bool bHaveZipFile = true;

    for( const auto& rArgument : aArguments )
    {
        OUString aParamUrl;
        if ( rArgument >>= aParamUrl )
        {
            m_eMode = e_IMode_URL;
            try
            {
                sal_Int32 nParam = aParamUrl.indexOf( '?' );
                if ( nParam >= 0 )
                {
                    m_aURL = aParamUrl.copy( 0, nParam );
                    std::u16string_view aParam = aParamUrl.subView( nParam + 1 );

                    sal_Int32 nIndex = 0;
                    do
                    {
                        std::u16string_view aCommand = o3tl::getToken(aParam, 0, '&', nIndex );
                        if ( aCommand == u"repairpackage" )
                        {
                            m_bForceRecovery = true;
                            break;
                        }
                        else if ( aCommand == u"purezip" )
                        {
                            m_nFormat = embed::StorageFormats::ZIP;
                            m_xRootFolder->setPackageFormat_Impl( m_nFormat );
                            break;
                        }
                        else if ( aCommand == u"ofopxml" )
                        {
                            m_nFormat = embed::StorageFormats::OFOPXML;
                            m_xRootFolder->setPackageFormat_Impl( m_nFormat );
                            break;
                        }
                    }
                    while ( nIndex >= 0 );
                }
                else
                    m_aURL = aParamUrl;

                Content aContent(
                    m_aURL, uno::Reference< XCommandEnvironment >(),
                    m_xContext );
                Any aAny = aContent.getPropertyValue(u"Size"_ustr);
                sal_uInt64 aSize = 0;
                // kind of optimization: treat empty files as nonexistent files
                // and write to such files directly. Note that "Size" property is optional.
                bool bHasSizeProperty = aAny >>= aSize;
                if( !bHasSizeProperty || aSize )
                {
                    uno::Reference < XActiveDataSink > xSink = new ZipPackageSink;
                    if ( aContent.openStream ( xSink ) )
                        m_xContentStream = xSink->getInputStream();
                }
                else
                    bHaveZipFile = false;
            }
            catch ( css::uno::Exception& )
            {
                // Exception derived from uno::Exception thrown. This probably
                // means the file doesn't exist...we'll create it at
                // commitChanges time
                bHaveZipFile = false;
            }
        }
        else if ( rArgument >>= m_xStream )
        {
            // a writable stream can implement both XStream & XInputStream
            m_eMode = e_IMode_XStream;
            m_xContentStream = m_xStream->getInputStream();
        }
        else if ( rArgument >>= m_xContentStream )
        {
            m_eMode = e_IMode_XInputStream;
        }
        else if ( rArgument >>= aNamedValue )
        {
            if ( aNamedValue.Name == "RepairPackage" )
                aNamedValue.Value >>= m_bForceRecovery;
            else if ( aNamedValue.Name == "PackageFormat" )
            {
                // setting this argument to true means Package format
                // setting it to false means plain Zip format

                bool bPackFormat = true;
                aNamedValue.Value >>= bPackFormat;
                if ( !bPackFormat )
                    m_nFormat = embed::StorageFormats::ZIP;

                m_xRootFolder->setPackageFormat_Impl( m_nFormat );
            }
            else if ( aNamedValue.Name == "StorageFormat" )
            {
                OUString aFormatName;
                sal_Int32 nFormatID = 0;
                if ( aNamedValue.Value >>= aFormatName )
                {
                    if ( aFormatName == PACKAGE_STORAGE_FORMAT_STRING )
                        m_nFormat = embed::StorageFormats::PACKAGE;
                    else if ( aFormatName == ZIP_STORAGE_FORMAT_STRING )
                        m_nFormat = embed::StorageFormats::ZIP;
                    else if ( aFormatName == OFOPXML_STORAGE_FORMAT_STRING )
                        m_nFormat = embed::StorageFormats::OFOPXML;
                    else
                        throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 );
                }
                else if ( aNamedValue.Value >>= nFormatID )
                {
                    if (nFormatID != embed::StorageFormats::PACKAGE
                        && nFormatID != embed::StorageFormats::ZIP
                        && nFormatID != embed::StorageFormats::OFOPXML)
                        throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 );

                    m_nFormat = nFormatID;
                }
                else
                    throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 );

                m_xRootFolder->setPackageFormat_Impl( m_nFormat );
            }
            else if ( aNamedValue.Name == "AllowRemoveOnInsert" )
            {
                aNamedValue.Value >>= m_bAllowRemoveOnInsert;
                m_xRootFolder->setRemoveOnInsertMode_Impl( m_bAllowRemoveOnInsert );
            }
            else if (aNamedValue.Name == "NoFileSync")
                aNamedValue.Value >>= m_bDisableFileSync;

            // for now the progress handler is not used, probably it will never be
            // if ( aNamedValue.Name == "ProgressHandler" )
        }
        else
        {
            // The URL is not acceptable
            throw css::uno::Exception (THROW_WHERE "Bad arguments.",
                getXWeak() );
        }
    }

    try
    {
        if ( m_xContentStream.is() )
        {
            // the stream must be seekable, if it is not it will be wrapped
            m_xContentStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( m_xContentStream, m_xContext );
            m_xContentSeek.set( m_xContentStream, UNO_QUERY_THROW );
            if ( !m_xContentSeek->getLength() )
                bHaveZipFile = false;
        }
        else
            bHaveZipFile = false;
    }
    catch ( css::uno::Exception& )
    {
        // Exception derived from uno::Exception thrown. This probably
        // means the file doesn't exist...we'll create it at
        // commitChanges time
        bHaveZipFile = false;
    }
    if ( !bHaveZipFile )
        return;

    bool bBadZipFile = false;
    OUString message;
    try
    {
        m_pZipFile.emplace(m_aMutexHolder, m_xContentStream, m_xContext, true,
            m_bForceRecovery,
            m_nFormat == embed::StorageFormats::ZIP
                ? ZipFile::Checks::Default
                : m_nFormat == embed::StorageFormats::OFOPXML
                    ? ZipFile::Checks::CheckInsensitive
                    : ZipFile::Checks::TryCheckInsensitive);
        getZipFileContents();
    }
    catch ( IOException & e )
    {
        bBadZipFile = true;
        message = "IOException: " + e.Message;
    }
    catch ( ZipException & e )
    {
        bBadZipFile = true;
        message = "ZipException: " + e.Message;
    }
    catch ( Exception & )
    {
        m_pZipFile.reset();
        throw;
    }

    if ( bBadZipFile )
    {
        // clean up the memory, and tell the UCB about the error
        m_pZipFile.reset();

        throw css::packages::zip::ZipIOException (
            THROW_WHERE "Bad Zip File, " + message,
            getXWeak() );
    }
}

Any SAL_CALL ZipPackage::getByHierarchicalName( const OUString& aName )
{
    OUString sDirName;
    sal_Int32 nOldIndex, nStreamIndex;

    if (aName == "/")
        // root directory.
        return Any ( uno::Reference( cppu::getXWeak(m_xRootFolder.get()) ) );

    sal_Int32 nIndex = aName.getLength();

    nStreamIndex = aName.lastIndexOf ( '/' );
    bool bFolder = nStreamIndex == nIndex-1; // last character is '/'.

    if ( nStreamIndex != -1 )
    {
        // The name contains '/'.
        sDirName = aName.copy ( 0, nStreamIndex );
        FolderHash::iterator aIter = m_aRecent.find ( sDirName );
        if ( aIter != m_aRecent.end() )
        {
            // There is a cached entry for this name.

            ZipPackageFolder* pFolder = aIter->second;

            if ( bFolder )
            {
                // Determine the directory name.
                sal_Int32 nDirIndex = aName.lastIndexOf ( '/', nStreamIndex );
                std::u16string_view sTemp = aName.subView ( nDirIndex == -1 ? 0 : nDirIndex+1, nStreamIndex-nDirIndex-1 );

                if (pFolder && sTemp == pFolder->getName())
                    return Any(uno::Reference(cppu::getXWeak(pFolder)));
            }
            else
            {
                // Determine the file name.
                std::u16string_view sTemp = aName.subView( nStreamIndex + 1 );

                if (pFolder && pFolder->hasByName(sTemp))
                    return pFolder->getByName(sTemp);
            }

            m_aRecent.erase( aIter );
        }
    }
    else if ( m_xRootFolder->hasByName ( aName ) )
        // top-level element.
        return m_xRootFolder->getByName ( aName );

    // Not in the cache. Search normally.

    nOldIndex = 0;
    ZipPackageFolder * pCurrent = m_xRootFolder.get();
    ZipPackageFolder * pPrevious = nullptr;

    // Find the right directory for the given path.

    while ( ( nIndex = aName.indexOf( '/', nOldIndex )) != -1 )
    {
        std::u16string_view sTemp = aName.subView ( nOldIndex, nIndex - nOldIndex );
        if ( nIndex == nOldIndex )
            break;
        if ( !pCurrent->hasByName( sTemp ) )
            throw NoSuchElementException(THROW_WHERE );

        pPrevious = pCurrent;
        ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp);
        if (!rInfo.bFolder)
            throw css::packages::zip::ZipIOException(u"Bad Zip File, stream as folder"_ustr);
        pCurrent = rInfo.pFolder;
        nOldIndex = nIndex+1;
    }

    if ( bFolder )
    {
        if ( nStreamIndex != -1 )
            m_aRecent[sDirName] = pPrevious; // cache it.
        return Any ( uno::Reference( cppu::getXWeak(pCurrent) ) );
    }

    std::u16string_view sTemp = aName.subView( nOldIndex );

    if ( pCurrent->hasByName ( sTemp ) )
    {
        if ( nStreamIndex != -1 )
            m_aRecent[sDirName] = pCurrent; // cache it.
        return pCurrent->getByName( sTemp );
    }

    throw NoSuchElementException(THROW_WHERE);
}

sal_Bool SAL_CALL ZipPackage::hasByHierarchicalName( const OUString& aName )
{
    if (aName == "/")
        // root directory
        return true;

    try
    {
        OUString sDirName;
        sal_Int32 nStreamIndex;
        nStreamIndex = aName.lastIndexOf ( '/' );
        bool bFolder = nStreamIndex == aName.getLength()-1;
        if ( nStreamIndex != -1 )
        {
            sDirName = aName.copy ( 0, nStreamIndex );
            FolderHash::iterator aIter = m_aRecent.find ( sDirName );
            if ( aIter != m_aRecent.end() )
            {
                if ( bFolder )
                {
                    sal_Int32 nDirIndex = aName.lastIndexOf ( '/', nStreamIndex );
                    std::u16string_view sTemp = aName.subView ( nDirIndex == -1 ? 0 : nDirIndex+1, nStreamIndex-nDirIndex-1 );
                    if ( sTemp == ( *aIter ).second->getName() )
                        return true;
                    else
                        m_aRecent.erase ( aIter );
                }
                else
                {
                    std::u16string_view sTemp = aName.subView( nStreamIndex + 1 );
                    if ( ( *aIter ).second->hasByName( sTemp ) )
                        return true;
                    else
                        m_aRecent.erase( aIter );
                }
            }
        }
        else
        {
            if ( m_xRootFolder->hasByName ( aName ) )
                return true;
        }
        ZipPackageFolder * pCurrent = m_xRootFolder.get();
        ZipPackageFolder * pPrevious = nullptr;
        sal_Int32 nOldIndex = 0;
        sal_Int32 nIndex;
        while ( ( nIndex = aName.indexOf( '/', nOldIndex )) != -1 )
        {
            if ( nIndex == nOldIndex )
                break;

            std::u16string_view sTemp = aName.subView ( nOldIndex, nIndex - nOldIndex );

            if ( pCurrent->hasByName( sTemp ) )
            {
                pPrevious = pCurrent;
                ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp);
                if (!rInfo.bFolder)
                    throw css::packages::zip::ZipIOException(u"Bad Zip File, stream as folder"_ustr);
                pCurrent = rInfo.pFolder;
            }
            else
                return false;
            nOldIndex = nIndex+1;
        }
        if ( bFolder )
        {
            m_aRecent[sDirName] = pPrevious;
            return true;
        }
        else
        {
            std::u16string_view sTemp = aName.subView( nOldIndex );

            if ( pCurrent->hasByName( sTemp ) )
            {
                m_aRecent[sDirName] = pCurrent;
                return true;
            }
        }
    }
    catch (const uno::RuntimeException &)
    {
        throw;
    }
    catch (const uno::Exception&)
    {
        uno::Any e(::cppu::getCaughtException());
        throw lang::WrappedTargetRuntimeException(u"ZipPackage::hasByHierarchicalName"_ustr, nullptr, e);
    }
    return false;
}

uno::Reference< XInterface > SAL_CALL ZipPackage::createInstance()
{
    uno::Reference < XInterface > xRef = *( new ZipPackageStream( *this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert ) );
    return xRef;
}

uno::Reference< XInterface > SAL_CALL ZipPackage::createInstanceWithArguments( const uno::Sequence< Any >& aArguments )
{
    bool bArg = false;
    uno::Reference < XInterface > xRef;
    if ( aArguments.hasElements() )
        aArguments[0] >>= bArg;
    if ( bArg )
        xRef = *new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
    else
        xRef = *new ZipPackageStream( *this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert );

    return xRef;
}

void ZipPackage::WriteMimetypeMagicFile( ZipOutputStream& aZipOut )
{
    static constexpr std::u16string_view sMime (u"mimetype" );
    if ( m_xRootFolder->hasByName( sMime ) )
        m_xRootFolder->removeByName( sMime );

    auto pEntry = std::make_unique<ZipEntry>();
    sal_Int32 nBufferLength = m_xRootFolder->GetMediaType().getLength();
    OString sMediaType = OUStringToOString( m_xRootFolder->GetMediaType(), RTL_TEXTENCODING_ASCII_US );
    const uno::Sequence< sal_Int8 > aType( reinterpret_cast<sal_Int8 const *>(sMediaType.getStr()),
                                     nBufferLength );

    pEntry->sPath = sMime;
    pEntry->nMethod = STORED;
    pEntry->nSize = pEntry->nCompressedSize = nBufferLength;
    pEntry->nTime = ZipOutputStream::getCurrentDosTime();

    CRC32 aCRC32;
    aCRC32.update( aType );
    pEntry->nCrc = aCRC32.getValue();

    try
    {
        ZipOutputStream::setEntry(*pEntry);
        aZipOut.writeLOC(std::move(pEntry));
        aZipOut.rawWrite(aType);
        aZipOut.rawCloseEntry();
    }
    catch ( const css::io::IOException & )
    {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw WrappedTargetException(
                THROW_WHERE "Error adding mimetype to the ZipOutputStream!",
                getXWeak(),
                anyEx );
    }
}

void ZipPackage::WriteManifest( ZipOutputStream& aZipOut, const std::vector< uno::Sequence < PropertyValue > >& aManList )
{
    // Write the manifest
    uno::Reference < XManifestWriter > xWriter = ManifestWriter::create( m_xContext );
    auto pEntry = std::make_unique<ZipEntry>();
    rtl::Reference<ZipPackageBuffer> pBuffer = new ZipPackageBuffer;

    pEntry->sPath = "META-INF/manifest.xml";
    pEntry->nMethod = DEFLATED;
    pEntry->nCrc = -1;
    pEntry->nCompressedSize = -1;
    pEntry->nTime = ZipOutputStream::getCurrentDosTime();

    xWriter->writeManifestSequence ( pBuffer,  comphelper::containerToSequence(aManList) );

    sal_Int32 nBufferLength = static_cast < sal_Int32 > ( pBuffer->getPosition() );
    pBuffer->realloc( nBufferLength );
    pEntry->nSize = nBufferLength;

    // the manifest.xml is never encrypted - so pass an empty reference
    ZipOutputStream::setEntry(*pEntry);
    auto p = pEntry.get();
    aZipOut.writeLOC(std::move(pEntry));
    ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, p, nullptr, /*bEncrypt*/false);
    aZipEntry.write(pBuffer->getSequence());
    aZipEntry.closeEntry();
    aZipOut.rawCloseEntry();
}

void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const std::vector< uno::Sequence < PropertyValue > >& aManList )
{
    auto pEntry = std::make_unique<ZipEntry>();
    rtl::Reference<ZipPackageBuffer> pBuffer = new ZipPackageBuffer;

    pEntry->sPath = "[Content_Types].xml";
    pEntry->nMethod = DEFLATED;
    pEntry->nCrc = -1;
    pEntry->nCompressedSize = -1;
    pEntry->nTime = ZipOutputStream::getCurrentDosTime();

    // Add at least the standard default entries.
    uno::Sequence< beans::StringPair > aDefaultsSequence
    {
        { u"fntdata"_ustr, u"application/x-fontdata"_ustr },
        { u"jpeg"_ustr, u"image/jpeg"_ustr },
        { u"png"_ustr, u"image/png"_ustr },
        { u"rels"_ustr, u"application/vnd.openxmlformats-package.relationships+xml"_ustr },
        { u"xml"_ustr, u"application/xml"_ustr }
    };

    uno::Sequence< beans::StringPair > aOverridesSequence(aManList.size());
    auto aOverridesSequenceRange = asNonConstRange(aOverridesSequence);
    sal_Int32 nOverSeqLength = 0;
    for (const auto& rMan : aManList)
    {
        OSL_ENSURE( rMan[PKG_MNFST_MEDIATYPE].Name == "MediaType" && rMan[PKG_MNFST_FULLPATH].Name == "FullPath",
                    "The mediatype sequence format is wrong!" );
        OUString aType;
        rMan[PKG_MNFST_MEDIATYPE].Value >>= aType;
        if (aType.isEmpty())
            continue;

        OUString aPath, aExtension;
        rMan[PKG_MNFST_FULLPATH].Value >>= aPath;
        (void)comphelper::DirectoryHelper::splitAtLastToken(aPath, '.', aExtension);

        // don't add override if same extension and content type exists as default
        const bool bTypeFound
            = !aExtension.isEmpty()
              && std::find_if(aDefaultsSequence.begin(), aDefaultsSequence.end(),
                              [&aExtension, &aType](const beans::StringPair& defPair)
                              { return defPair.First == aExtension && defPair.Second == aType; })
                     != aDefaultsSequence.end();
        if (bTypeFound)
            continue;

        aOverridesSequenceRange[nOverSeqLength].First = "/" + aPath;
        aOverridesSequenceRange[nOverSeqLength].Second = aType;
        ++nOverSeqLength;
    }
    aOverridesSequence.realloc(nOverSeqLength);

    ::comphelper::OFOPXMLHelper::WriteContentSequence(
            pBuffer, aDefaultsSequence, aOverridesSequence, m_xContext );

    sal_Int32 nBufferLength = static_cast < sal_Int32 > ( pBuffer->getPosition() );
    pBuffer->realloc( nBufferLength );
    pEntry->nSize = nBufferLength;

    // there is no encryption in this format currently
    ZipOutputStream::setEntry(*pEntry);
    auto p = pEntry.get();
    aZipOut.writeLOC(std::move(pEntry));
    ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, p, nullptr, /*bEncrypt*/false);
    aZipEntry.write(pBuffer->getSequence());
    aZipEntry.closeEntry();
    aZipOut.rawCloseEntry();
}

void ZipPackage::ConnectTo( const uno::Reference< io::XInputStream >& xInStream )
{
    assert(dynamic_cast<comphelper::ByteReader*>(xInStream.get()));
    m_xContentSeek.set( xInStream, uno::UNO_QUERY_THROW );
    m_xContentStream = xInStream;

    // seek back to the beginning of the temp file so we can read segments from it
    m_xContentSeek->seek( 0 );
    if ( m_pZipFile )
        m_pZipFile->setInputStream( m_xContentStream );
    else
        m_pZipFile.emplace(m_aMutexHolder, m_xContentStream, m_xContext, false,
            false,
            m_nFormat == embed::StorageFormats::ZIP
                ? ZipFile::Checks::Default
                : m_nFormat == embed::StorageFormats::OFOPXML
                    ? ZipFile::Checks::CheckInsensitive
                    : ZipFile::Checks::TryCheckInsensitive);
}

uno::Reference< io::XInputStream > ZipPackage::writeTempFile()
{
    // In case the target local file does not exist or empty
    // write directly to it otherwise create a temporary file to write to.
    // If a temporary file is created it is returned back by the method.
    // If the data written directly, xComponentStream will be switched here

    bool bUseTemp = true;
    uno::Reference < io::XInputStream > xResult;
    uno::Reference < io::XInputStream > xTempIn;

    uno::Reference < io::XOutputStream > xTempOut;
    uno::Reference< io::XActiveDataStreamer > xSink;

    if ( m_eMode == e_IMode_URL && !m_pZipFile && isLocalFile() )
    {
        xSink = openOriginalForOutput();
        if( xSink.is() )
        {
            uno::Reference< io::XStream > xStr = xSink->getStream();
            if( xStr.is() )
            {
                xTempOut = xStr->getOutputStream();
                if( xTempOut.is() )
                    bUseTemp = false;
            }
        }
    }
    else if ( m_eMode == e_IMode_XStream && !m_pZipFile )
    {
        // write directly to an empty stream
        xTempOut = m_xStream->getOutputStream();
        if( xTempOut.is() )
            bUseTemp = false;
    }

    if( bUseTemp )
    {
        // create temporary file
        rtl::Reference < utl::TempFileFastService > xTempFile( new utl::TempFileFastService );
        xTempOut.set( xTempFile );
        xTempIn.set( xTempFile );
    }

    // Hand it to the ZipOutputStream:
    ZipOutputStream aZipOut( xTempOut );
    try
    {
        if ( m_nFormat == embed::StorageFormats::PACKAGE )
        {
            // Remove the old manifest.xml file as the
            // manifest will be re-generated and the
            // META-INF directory implicitly created if does not exist
            static constexpr std::u16string_view sMeta (u"META-INF");

            if ( m_xRootFolder->hasByName( sMeta ) )
            {
                static constexpr OUString sManifest (u"manifest.xml"_ustr);

                uno::Reference< XNameContainer > xMetaInfFolder;
                Any aAny = m_xRootFolder->getByName( sMeta );
                aAny >>= xMetaInfFolder;
                if ( xMetaInfFolder.is() && xMetaInfFolder->hasByName( sManifest ) )
                    xMetaInfFolder->removeByName( sManifest );
            }

            // Write a magic file with mimetype
            WriteMimetypeMagicFile( aZipOut );
        }
        else if ( m_nFormat == embed::StorageFormats::OFOPXML )
        {
            // Remove the old [Content_Types].xml file as the
            // file will be re-generated

            static constexpr std::u16string_view aContentTypes(u"[Content_Types].xml");

            if ( m_xRootFolder->hasByName( aContentTypes ) )
                m_xRootFolder->removeByName( aContentTypes );
        }

        // Create a vector to store data for the manifest.xml file
        std::vector < uno::Sequence < PropertyValue > > aManList;

        static constexpr OUStringLiteral sMediaType(u"MediaType");
        static constexpr OUStringLiteral sVersion(u"Version");
        static constexpr OUStringLiteral sFullPath(u"FullPath");
        const bool bIsGpgEncrypt = m_aGpgProps.hasElements();

        // note: this is always created here (needed for GPG), possibly
        // filtered out later in ManifestExport
        if ( m_nFormat == embed::StorageFormats::PACKAGE )
        {
            uno::Sequence < PropertyValue > aPropSeq(
                bIsGpgEncrypt ? PKG_SIZE_NOENCR_MNFST+1 : PKG_SIZE_NOENCR_MNFST );
            auto pPropSeq = aPropSeq.getArray();
            pPropSeq [PKG_MNFST_MEDIATYPE].Name = sMediaType;
            pPropSeq [PKG_MNFST_MEDIATYPE].Value <<= m_xRootFolder->GetMediaType();
            pPropSeq [PKG_MNFST_VERSION].Name = sVersion;
            pPropSeq [PKG_MNFST_VERSION].Value <<= m_xRootFolder->GetVersion();
            pPropSeq [PKG_MNFST_FULLPATH].Name = sFullPath;
            pPropSeq [PKG_MNFST_FULLPATH].Value <<= u"/"_ustr;

            if( bIsGpgEncrypt )
            {
                pPropSeq[PKG_SIZE_NOENCR_MNFST].Name = "KeyInfo";
                pPropSeq[PKG_SIZE_NOENCR_MNFST].Value <<= m_aGpgProps;
            }
            aManList.push_back( aPropSeq );
        }

        {
            ::std::optional<sal_Int32> oPBKDF2IterationCount;
            ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgon2Args;

            if (!bIsGpgEncrypt)
            {
                if (m_nKeyDerivationFunctionID == xml::crypto::KDFID::PBKDF2)
                {   // if there is only one KDF invocation, increase the safety margin
                    oPBKDF2IterationCount.emplace(m_xRootFolder->hasByName(u"encrypted-package"sv) ? 600000 : 100000);
                }
                else
                {
                    assert(m_nKeyDerivationFunctionID == xml::crypto::KDFID::Argon2id);
                    oArgon2Args.emplace(3, (1<<16), 4);
                }
            }

            // call saveContents - it will recursively save sub-directories
            m_xRootFolder->saveContents(u""_ustr, aManList, aZipOut, GetEncryptionKey(),
                oPBKDF2IterationCount, oArgon2Args);
        }

        if( m_nFormat == embed::StorageFormats::PACKAGE )
        {
            WriteManifest( aZipOut, aManList );
        }
        else if( m_nFormat == embed::StorageFormats::OFOPXML )
        {
            WriteContentTypes( aZipOut, aManList );
        }

        aZipOut.finish();

        if( bUseTemp )
            xResult = std::move(xTempIn);

        // Update our References to point to the new temp file
        if( !bUseTemp )
        {
            // the case when the original contents were written directly
            xTempOut->flush();

            // in case the stream is based on a file it will implement the following interface
            // the call should be used to be sure that the contents are written to the file system
            uno::Reference< io::XAsyncOutputMonitor > asyncOutputMonitor( xTempOut, uno::UNO_QUERY );
            if (asyncOutputMonitor.is() && !m_bDisableFileSync)
                asyncOutputMonitor->waitForCompletion();

            // no need to postpone switching to the new stream since the target was written directly
            uno::Reference< io::XInputStream > xNewStream;
            if ( m_eMode == e_IMode_URL )
                xNewStream = xSink->getStream()->getInputStream();
            else if ( m_eMode == e_IMode_XStream && m_xStream.is() )
                xNewStream = m_xStream->getInputStream();

            if ( xNewStream.is() )
                ConnectTo( xNewStream );
        }
    }
    catch ( uno::Exception& )
    {
        if( bUseTemp )
        {
            // no information loss appears, thus no special handling is required
            uno::Any aCaught( ::cppu::getCaughtException() );

            // it is allowed to throw WrappedTargetException
            WrappedTargetException aException;
            if ( aCaught >>= aException )
                throw aException;

            throw WrappedTargetException(
                    THROW_WHERE "Problem writing the original content!",
                    getXWeak(),
                    aCaught );
        }
        else
        {
            // the document is written directly, although it was empty it is important to notify that the writing has failed
            // TODO/LATER: let the package be able to recover in this situation
            OUString aErrTxt(THROW_WHERE "This package is unusable!");
            embed::UseBackupException aException( aErrTxt, uno::Reference< uno::XInterface >(), OUString() );
            throw WrappedTargetException( aErrTxt,
                                            getXWeak(),
                                            Any ( aException ) );
        }
    }

    return xResult;
}

uno::Reference< XActiveDataStreamer > ZipPackage::openOriginalForOutput()
{
    // open and truncate the original file
    Content aOriginalContent(
        m_aURL, uno::Reference< XCommandEnvironment >(),
        m_xContext );
    uno::Reference< XActiveDataStreamer > xSink = new ActiveDataStreamer;

    if ( m_eMode == e_IMode_URL )
    {
        try
        {
            bool bTruncSuccess = false;

            try
            {
                Exception aDetect;
                Any aAny = aOriginalContent.setPropertyValue(u"Size"_ustr, Any( sal_Int64(0) ) );
                if( !( aAny >>= aDetect ) )
                    bTruncSuccess = true;
            }
            catch( Exception& )
            {
            }

            if( !bTruncSuccess )
            {
                // the file is not accessible
                // just try to write an empty stream to it

                uno::Reference< XInputStream > xTempIn = new DummyInputStream; //uno::Reference< XInputStream >( xTempOut, UNO_QUERY );
                aOriginalContent.writeStream( xTempIn , true );
            }

            OpenCommandArgument2 aArg;
            aArg.Mode        = OpenMode::DOCUMENT;
            aArg.Priority    = 0; // unused
            aArg.Sink       = xSink;
            aArg.Properties = uno::Sequence< Property >( 0 ); // unused

            aOriginalContent.executeCommand(u"open"_ustr, Any( aArg ) );
        }
        catch( Exception& )
        {
            // seems to be nonlocal file
            // temporary file mechanics should be used
        }
    }

    return xSink;
}

void SAL_CALL ZipPackage::commitChanges()
{
    // lock the component for the time of committing
    ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );

    if ( m_eMode == e_IMode_XInputStream )
    {
        IOException aException;
        throw WrappedTargetException(THROW_WHERE "This package is read only!",
                getXWeak(), Any ( aException ) );
    }
    // first the writeTempFile is called, if it returns a stream the stream should be written to the target
    // if no stream was returned, the file was written directly, nothing should be done
    uno::Reference< io::XInputStream > xTempInStream;
    try
    {
        xTempInStream = writeTempFile();
    }
    catch (const ucb::ContentCreationException&)
    {
        css::uno::Any anyEx = cppu::getCaughtException();
        throw WrappedTargetException(THROW_WHERE "Temporary file should be creatable!",
                    getXWeak(), anyEx );
    }
    if ( xTempInStream.is() )
    {
        uno::Reference< io::XSeekable > xTempSeek( xTempInStream, uno::UNO_QUERY_THROW );

        try
        {
            xTempSeek->seek( 0 );
        }
        catch( const uno::Exception& )
        {
            css::uno::Any anyEx = cppu::getCaughtException();
            throw WrappedTargetException(THROW_WHERE "Temporary file should be seekable!",
                    getXWeak(), anyEx );
        }

        try
        {
            // connect to the temporary stream
            ConnectTo( xTempInStream );
        }
        catch( const io::IOException& )
        {
            css::uno::Any anyEx = cppu::getCaughtException();
            throw WrappedTargetException(THROW_WHERE "Temporary file should be connectable!",
                    getXWeak(), anyEx );
        }

        if ( m_eMode == e_IMode_XStream )
        {
            // First truncate our output stream
            uno::Reference < XOutputStream > xOutputStream;

            // preparation for copy step
            try
            {
                xOutputStream = m_xStream->getOutputStream();

                // Make sure we avoid a situation where the current position is
                // not zero, but the underlying file is truncated in the
                // meantime.
                uno::Reference<io::XSeekable> xSeekable(xOutputStream, uno::UNO_QUERY);
                if (xSeekable.is())
                    xSeekable->seek(0);

                uno::Reference < XTruncate > xTruncate ( xOutputStream, UNO_QUERY_THROW );

                // after successful truncation the original file contents are already lost
                xTruncate->truncate();
            }
            catch( const uno::Exception& )
            {
                css::uno::Any anyEx = cppu::getCaughtException();
                throw WrappedTargetException(THROW_WHERE "This package is read only!",
                        getXWeak(), anyEx );
            }

            try
            {
                // then copy the contents of the tempfile to our output stream
                ::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream, xOutputStream );
                xOutputStream->flush();
                uno::Reference< io::XAsyncOutputMonitor > asyncOutputMonitor(
                    xOutputStream, uno::UNO_QUERY );
                if ( asyncOutputMonitor.is() ) {
                    asyncOutputMonitor->waitForCompletion();
                }
            }
            catch( uno::Exception& )
            {
                // if anything goes wrong in this block the target file becomes corrupted
                // so an exception should be thrown as a notification about it
                // and the package must disconnect from the stream
                DisconnectFromTargetAndThrowException_Impl( xTempInStream );
            }
        }
        else if ( m_eMode == e_IMode_URL )
        {
            uno::Reference< XOutputStream > aOrigFileStream;
            bool bCanBeCorrupted = false;

            if( isLocalFile() )
            {
                // write directly in case of local file
                uno::Reference< css::ucb::XSimpleFileAccess3 > xSimpleAccess(
                    SimpleFileAccess::create( m_xContext ) );
                OSL_ENSURE( xSimpleAccess.is(), "Can't instantiate SimpleFileAccess service!" );
                uno::Reference< io::XTruncate > xOrigTruncate;
                if ( xSimpleAccess.is() )
                {
                    try
                    {
                        aOrigFileStream = xSimpleAccess->openFileWrite( m_aURL );
                        xOrigTruncate.set( aOrigFileStream, uno::UNO_QUERY_THROW );
                        // after successful truncation the file is already corrupted
                        xOrigTruncate->truncate();
                    }
                    catch( uno::Exception& )
                    {}
                }

                if( xOrigTruncate.is() )
                {
                    try
                    {
                        ::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream, aOrigFileStream );
                        aOrigFileStream->closeOutput();
                    }
                    catch( uno::Exception& )
                    {
                        try {
                            aOrigFileStream->closeOutput();
                        } catch ( uno::Exception& ) {}

                        aOrigFileStream.clear();
                        // the original file can already be corrupted
                        bCanBeCorrupted = true;
                    }
                }
            }

            if( !aOrigFileStream.is() )
            {
                try
                {
                    uno::Reference < XPropertySet > xPropSet ( xTempInStream, UNO_QUERY_THROW );

                    OUString sTargetFolder = m_aURL.copy ( 0, m_aURL.lastIndexOf ( u'/' ) );
                    Content aContent(
                        sTargetFolder, uno::Reference< XCommandEnvironment >(),
                        m_xContext );

                    OUString sTempURL;
                    Any aAny = xPropSet->getPropertyValue (u"Uri"_ustr);
                    aAny >>= sTempURL;

                    TransferInfo aInfo;
                    aInfo.NameClash = NameClash::OVERWRITE;
                    aInfo.MoveData = false;
                    aInfo.SourceURL = sTempURL;
                    aInfo.NewTitle = rtl::Uri::decode ( m_aURL.copy ( 1 + m_aURL.lastIndexOf ( u'/' ) ),
                                                        rtl_UriDecodeWithCharset,
                                                        RTL_TEXTENCODING_UTF8 );
                    // if the file is still not corrupted, it can become after the next step
                    aContent.executeCommand (u"transfer"_ustr, Any(aInfo) );
                }
                catch ( const css::uno::Exception& )
                {
                    if ( bCanBeCorrupted )
                        DisconnectFromTargetAndThrowException_Impl( xTempInStream );

                    css::uno::Any anyEx = cppu::getCaughtException();
                    throw WrappedTargetException(
                                                THROW_WHERE "This package may be read only!",
                                                getXWeak(),
                                                anyEx );
                }
            }
        }
    }

    // after successful storing it can be set to false
    m_bMediaTypeFallbackUsed = false;
}

void ZipPackage::DisconnectFromTargetAndThrowException_Impl( const uno::Reference< io::XInputStream >& xTempStream )
{
    m_xStream.set( xTempStream, uno::UNO_QUERY );
    if ( m_xStream.is() )
        m_eMode = e_IMode_XStream;
    else
        m_eMode = e_IMode_XInputStream;

    OUString aTempURL;
    try {
        uno::Reference< beans::XPropertySet > xTempFile( xTempStream, uno::UNO_QUERY_THROW );
        uno::Any aUrl = xTempFile->getPropertyValue(u"Uri"_ustr);
        aUrl >>= aTempURL;
        xTempFile->setPropertyValue(u"RemoveFile"_ustr,
                                     uno::Any( false ) );
    }
    catch ( uno::Exception& )
    {
        OSL_FAIL( "These calls are pretty simple, they should not fail!" );
    }

    OUString aErrTxt(THROW_WHERE "This package is read only!");
    embed::UseBackupException aException( aErrTxt, uno::Reference< uno::XInterface >(), aTempURL );
    throw WrappedTargetException( aErrTxt,
                                    getXWeak(),
                                    Any ( aException ) );
}

uno::Sequence< sal_Int8 > ZipPackage::GetEncryptionKey()
{
    uno::Sequence< sal_Int8 > aResult;

    if ( m_aStorageEncryptionKeys.hasElements() )
    {
        OUString aNameToFind;
        if ( m_nStartKeyGenerationID == xml::crypto::DigestID::SHA256 )
            aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA256UTF8;
        else if ( m_nStartKeyGenerationID == xml::crypto::DigestID::SHA1 )
            aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA1CORRECT;
        else
            throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" );

        for (const auto& rKey : m_aStorageEncryptionKeys)
            if ( rKey.Name == aNameToFind )
                rKey.Value >>= aResult;

        if (!aResult.hasElements() && m_aStorageEncryptionKeys.hasElements())
        {   // tdf#159519 sanity check
            throw uno::RuntimeException(THROW_WHERE "Expected key is missing!");
        }
    }
    else
        aResult = m_aEncryptionKey;

    return aResult;
}

sal_Bool SAL_CALL ZipPackage::hasPendingChanges()
{
    return false;
}
Sequence< ElementChange > SAL_CALL ZipPackage::getPendingChanges()
{
    return uno::Sequence < ElementChange > ();
}


OUString ZipPackage::getImplementationName()
{
    return u"com.sun.star.packages.comp.ZipPackage"_ustr;
}

Sequence< OUString > ZipPackage::getSupportedServiceNames()
{
    return { u"com.sun.star.packages.Package"_ustr };
}

sal_Bool SAL_CALL ZipPackage::supportsService( OUString const & rServiceName )
{
    return cppu::supportsService(this, rServiceName);
}

uno::Reference< XPropertySetInfo > SAL_CALL ZipPackage::getPropertySetInfo()
{
    return uno::Reference < XPropertySetInfo > ();
}

void SAL_CALL ZipPackage::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
{
    if ( m_nFormat != embed::StorageFormats::PACKAGE )
        throw UnknownPropertyException(aPropertyName);

    if (aPropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY
      ||aPropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY
      ||aPropertyName == IS_INCONSISTENT_PROPERTY
      ||aPropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY)
        throw PropertyVetoException(THROW_WHERE );
    else if ( aPropertyName == ENCRYPTION_KEY_PROPERTY )
    {
        if ( !( aValue >>= m_aEncryptionKey ) )
            throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 2 );

        m_aStorageEncryptionKeys.realloc( 0 );
    }
    else if ( aPropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY )
    {
        // this property is only necessary to support raw passwords in storage API;
        // because of this support the storage has to operate with more than one key dependent on storage generation algorithm;
        // when this support is removed, the storage will get only one key from outside
        if ( !( aValue >>= m_aStorageEncryptionKeys ) )
            throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 2 );

        m_aEncryptionKey.realloc( 0 );
    }
    else if ( aPropertyName == ENCRYPTION_ALGORITHMS_PROPERTY )
    {
        uno::Sequence< beans::NamedValue > aAlgorithms;
        if ( m_pZipFile || !( aValue >>= aAlgorithms ) || !aAlgorithms.hasElements() )
        {
            // the algorithms can not be changed if the file has a persistence based on the algorithms ( m_pZipFile )
            throw IllegalArgumentException(THROW_WHERE "unexpected algorithms list is provided.", uno::Reference< uno::XInterface >(), 2 );
        }

        for (const auto& rAlgorithm : aAlgorithms)
        {
            if ( rAlgorithm.Name == "StartKeyGenerationAlgorithm" )
            {
                sal_Int32 nID = 0;
                if ( !( rAlgorithm.Value >>= nID )
                  || ( nID != xml::crypto::DigestID::SHA256 && nID != xml::crypto::DigestID::SHA1 ) )
                {
                    throw IllegalArgumentException(THROW_WHERE "Unexpected start key generation algorithm is provided!", uno::Reference<uno::XInterface>(), 2);
                }

                m_nStartKeyGenerationID = nID;
            }
            else if (rAlgorithm.Name == "KeyDerivationFunction")
            {
                sal_Int32 nID = 0;
                if (!(rAlgorithm.Value >>= nID)
                  || (nID != xml::crypto::KDFID::PBKDF2
                      && nID != xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P
                      && nID != xml::crypto::KDFID::Argon2id))
                {
                    throw IllegalArgumentException(THROW_WHERE "Unexpected key derivation function provided!", uno::Reference<uno::XInterface>(), 2);
                }
                m_nKeyDerivationFunctionID = nID;
            }
            else if ( rAlgorithm.Name == "EncryptionAlgorithm" )
            {
                sal_Int32 nID = 0;
                if ( !( rAlgorithm.Value >>= nID )
                  || (nID != xml::crypto::CipherID::AES_GCM_W3C
                      && nID != xml::crypto::CipherID::AES_CBC_W3C_PADDING
                      && nID != xml::crypto::CipherID::BLOWFISH_CFB_8))
                {
                    throw IllegalArgumentException(THROW_WHERE "Unexpected encryption algorithm is provided!", uno::Reference<uno::XInterface>(), 2);
                }

                m_nCommonEncryptionID = nID;
            }
            else if ( rAlgorithm.Name == "ChecksumAlgorithm" )
            {
                sal_Int32 nID = 0;
                if (!rAlgorithm.Value.hasValue())
                {
                    m_oChecksumDigestID.reset();
                    continue;
                }
                if ( !( rAlgorithm.Value >>= nID )
                  || ( nID != xml::crypto::DigestID::SHA1_1K && nID != xml::crypto::DigestID::SHA256_1K ) )
                {
                    throw IllegalArgumentException(THROW_WHERE "Unexpected checksum algorithm is provided!", uno::Reference<uno::XInterface>(), 2);
                }

                m_oChecksumDigestID.emplace(nID);
            }
            else
            {
                OSL_ENSURE( false, "Unexpected encryption algorithm is provided!" );
                throw IllegalArgumentException(THROW_WHERE "unexpected algorithms list is provided.", uno::Reference< uno::XInterface >(), 2 );
            }
        }
    }
    else if ( aPropertyName == ENCRYPTION_GPG_PROPERTIES )
    {
        uno::Sequence< uno::Sequence< beans::NamedValue > > aGpgProps;
        if ( !( aValue >>= aGpgProps ) || !aGpgProps.hasElements() )
        {
            throw IllegalArgumentException(THROW_WHERE "unexpected Gpg properties are provided.", uno::Reference< uno::XInterface >(), 2 );
        }

        m_aGpgProps = std::move(aGpgProps);

        // override algorithm defaults (which are some legacy ODF
        // defaults) with reasonable values
        // note: these should be overridden by SfxObjectShell::SetupStorage()
        m_nStartKeyGenerationID = 0; // this is unused for PGP
        m_nKeyDerivationFunctionID = xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P;
        m_nCommonEncryptionID = xml::crypto::CipherID::AES_CBC_W3C_PADDING;
        m_oChecksumDigestID.emplace(xml::crypto::DigestID::SHA512_1K);
    }
    else
        throw UnknownPropertyException(aPropertyName);
}

Any SAL_CALL ZipPackage::getPropertyValue( const OUString& PropertyName )
{
    // TODO/LATER: Activate the check when zip-ucp is ready
    // if ( m_nFormat != embed::StorageFormats::PACKAGE )
    //  throw UnknownPropertyException(THROW_WHERE );

    if ( PropertyName == ENCRYPTION_KEY_PROPERTY )
    {
        return Any(m_aEncryptionKey);
    }
    else if ( PropertyName == ENCRYPTION_ALGORITHMS_PROPERTY )
    {
        ::comphelper::SequenceAsHashMap aAlgorithms;
        aAlgorithms[u"StartKeyGenerationAlgorithm"_ustr] <<= m_nStartKeyGenerationID;
        aAlgorithms[u"KeyDerivationFunction"_ustr] <<= m_nKeyDerivationFunctionID;
        aAlgorithms[u"EncryptionAlgorithm"_ustr] <<= m_nCommonEncryptionID;
        if (m_oChecksumDigestID)
        {
            aAlgorithms[u"ChecksumAlgorithm"_ustr] <<= *m_oChecksumDigestID;
        }
        else
        {
            aAlgorithms[u"ChecksumAlgorithm"_ustr];
        }
        return Any(aAlgorithms.getAsConstNamedValueList());
    }
    if ( PropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY )
    {
        return Any(m_aStorageEncryptionKeys);
    }
    else if ( PropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY )
    {
        return Any(m_bHasEncryptedEntries);
    }
    else if ( PropertyName == ENCRYPTION_GPG_PROPERTIES )
    {
        return Any(m_aGpgProps);
    }
    else if ( PropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY )
    {
        return Any(m_bHasNonEncryptedEntries);
    }
    else if ( PropertyName == IS_INCONSISTENT_PROPERTY )
    {
        return Any(m_bInconsistent);
    }
    else if ( PropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY )
    {
        return Any(m_bMediaTypeFallbackUsed);
    }
    else if (PropertyName == "HasElements")
    {
        return Any(m_pZipFile && m_pZipFile->entries().hasMoreElements());
    }
    throw UnknownPropertyException(PropertyName);
}
void SAL_CALL ZipPackage::addPropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ )
{
}
void SAL_CALL ZipPackage::removePropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*aListener*/ )
{
}
void SAL_CALL ZipPackage::addVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< XVetoableChangeListener >& /*aListener*/ )
{
}
void SAL_CALL ZipPackage::removeVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< XVetoableChangeListener >& /*aListener*/ )
{
}

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
package_ZipPackage_get_implementation(
    css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
{
    return cppu::acquire(new ZipPackage(context));
}

extern "C" bool TestImportZip(SvStream& rStream)
{
    // explicitly tests the "RepairPackage" recovery mode
    rtl::Reference<ZipPackage> xPackage(new ZipPackage(comphelper::getProcessComponentContext()));
    css::uno::Reference<css::io::XInputStream> xStream(new utl::OInputStreamWrapper(rStream));
    css::uno::Sequence<Any> aArgs{ Any(xStream), Any(NamedValue(u"RepairPackage"_ustr, Any(true))) };
    xPackage->initialize(aArgs);
    return true;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
