From 267c6f2ac71f92999e969232431ba04678e7437e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 07:54:39 +0200 Subject: Adding upstream version 4:24.2.0. Signed-off-by: Daniel Baumann --- package/source/zippackage/ZipPackage.cxx | 1961 ++++++++++++++++++++ package/source/zippackage/ZipPackageBuffer.cxx | 128 ++ package/source/zippackage/ZipPackageEntry.cxx | 125 ++ package/source/zippackage/ZipPackageFolder.cxx | 425 +++++ .../zippackage/ZipPackageFolderEnumeration.cxx | 70 + .../zippackage/ZipPackageFolderEnumeration.hxx | 49 + package/source/zippackage/ZipPackageSink.cxx | 37 + package/source/zippackage/ZipPackageSink.hxx | 38 + package/source/zippackage/ZipPackageStream.cxx | 1359 ++++++++++++++ package/source/zippackage/wrapstreamforshare.cxx | 152 ++ package/source/zippackage/wrapstreamforshare.hxx | 59 + package/source/zippackage/zipfileaccess.cxx | 479 +++++ 12 files changed, 4882 insertions(+) create mode 100644 package/source/zippackage/ZipPackage.cxx create mode 100644 package/source/zippackage/ZipPackageBuffer.cxx create mode 100644 package/source/zippackage/ZipPackageEntry.cxx create mode 100644 package/source/zippackage/ZipPackageFolder.cxx create mode 100644 package/source/zippackage/ZipPackageFolderEnumeration.cxx create mode 100644 package/source/zippackage/ZipPackageFolderEnumeration.hxx create mode 100644 package/source/zippackage/ZipPackageSink.cxx create mode 100644 package/source/zippackage/ZipPackageSink.hxx create mode 100644 package/source/zippackage/ZipPackageStream.cxx create mode 100644 package/source/zippackage/wrapstreamforshare.cxx create mode 100644 package/source/zippackage/wrapstreamforshare.hxx create mode 100644 package/source/zippackage/zipfileaccess.cxx (limited to 'package/source/zippackage') diff --git a/package/source/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx new file mode 100644 index 0000000000..02f7cf71e8 --- /dev/null +++ b/package/source/zippackage/ZipPackage.cxx @@ -0,0 +1,1961 @@ +/* -*- 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 +#include "ZipPackageSink.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + {} +}; + +} + +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); +} + +void ZipPackage::parseManifest() +{ + if ( m_nFormat != embed::StorageFormats::PACKAGE ) + return; + + bool bManifestParsed = false; + 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 ); + + static constexpr OUStringLiteral sPropFullPath (u"FullPath"); + static constexpr OUStringLiteral sPropVersion (u"Version"); + static constexpr OUStringLiteral sPropMediaType (u"MediaType"); + static constexpr OUStringLiteral sPropInitialisationVector (u"InitialisationVector"); + static constexpr OUStringLiteral sPropSalt (u"Salt"); + static constexpr OUStringLiteral sPropIterationCount (u"IterationCount"); + static constexpr OUStringLiteral sPropSize (u"Size"); + static constexpr OUStringLiteral sPropDigest (u"Digest"); + static constexpr OUStringLiteral sPropDerivedKeySize (u"DerivedKeySize"); + static constexpr OUStringLiteral sPropDigestAlgorithm (u"DigestAlgorithm"); + static constexpr OUStringLiteral sPropEncryptionAlgorithm (u"EncryptionAlgorithm"); + static constexpr OUStringLiteral sPropStartKeyAlgorithm (u"StartKeyAlgorithm"); + static constexpr OUStringLiteral sKeyInfo (u"KeyInfo"); + + const uno::Sequence < uno::Sequence < PropertyValue > > aManifestSequence = xReader->readManifestSequence ( xSink->getInputStream() ); + const Any *pKeyInfo = nullptr; + + for ( const uno::Sequence& 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 == sPropFullPath ) + rValue.Value >>= sPath; + else if ( rValue.Name == sPropVersion ) + rValue.Value >>= sVersion; + else if ( rValue.Name == sPropMediaType ) + rValue.Value >>= sMediaType; + else if ( rValue.Name == sPropSalt ) + pSalt = &( rValue.Value ); + else if ( rValue.Name == sPropInitialisationVector ) + pVector = &( rValue.Value ); + else if ( rValue.Name == sPropIterationCount ) + pCount = &( rValue.Value ); + else if ( rValue.Name == sPropSize ) + pSize = &( rValue.Value ); + else if ( rValue.Name == sPropDigest ) + pDigest = &( rValue.Value ); + else if ( rValue.Name == sPropDigestAlgorithm ) + pDigestAlg = &( rValue.Value ); + else if ( rValue.Name == sPropEncryptionAlgorithm ) + pEncryptionAlg = &( rValue.Value ); + else if ( rValue.Name == sPropStartKeyAlgorithm ) + pStartKeyAlg = &( rValue.Value ); + else if ( rValue.Name == sPropDerivedKeySize ) + pDerivedKeySize = &( rValue.Value ); + else if ( rValue.Name == sKeyInfo ) + pKeyInfo = &( rValue.Value ); + else if (rValue.Name == "KeyDerivationFunction") { + pKDF = &rValue.Value; + } else if (rValue.Name == "Argon2Args") { + pArgon2Args = &rValue.Value; + } + } + + if ( !sPath.isEmpty() && hasByHierarchicalName ( sPath ) ) + { + aAny = getByHierarchicalName( sPath ); + uno::Reference < XInterface > xTmp; + aAny >>= xTmp; + if (auto pFolder = dynamic_cast(xTmp.get())) + { + pFolder->SetMediaType ( sMediaType ); + pFolder->SetVersion ( sVersion ); + } + else if (auto pStream = dynamic_cast(xTmp.get())) + { + pStream->SetMediaType ( sMediaType ); + pStream->SetFromManifest( true ); + + if (pKeyInfo + && pVector && pSize && pEncryptionAlg + && pKDF && pKDF->has() && pKDF->get() == xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P + && ((pEncryptionAlg->has() + && pEncryptionAlg->get() == xml::crypto::CipherID::AES_GCM_W3C) + || (pDigest && pDigestAlg))) + { + uno::Sequence < sal_Int8 > aSequence; + sal_Int64 nSize = 0; + ::std::optional 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()); + oDigestAlg.emplace(pDigestAlg->get()); + pStream->SetImportedChecksumAlgorithm(oDigestAlg); + } + + *pEncryptionAlg >>= nEncryptionAlg; + pStream->SetImportedEncryptionAlgorithm( nEncryptionAlg ); + + *pKeyInfo >>= m_aGpgProps; + + pStream->SetToBeCompressed ( true ); + pStream->SetToBeEncrypted ( true ); + pStream->SetIsEncrypted ( true ); + pStream->setIterationCount(::std::optional()); + pStream->setArgon2Args(::std::optional<::std::tuple>()); + + // 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->SetImportedStartKeyAlgorithm( nStartKeyAlg ); + + 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() + && ((pKDF->get() == xml::crypto::KDFID::PBKDF2 && pCount) + || (pKDF->get() == xml::crypto::KDFID::Argon2id && pArgon2Args)) + && ((pEncryptionAlg->has() + && pEncryptionAlg->get() == xml::crypto::CipherID::AES_GCM_W3C) + || (pDigest && pDigestAlg))) + + { + uno::Sequence < sal_Int8 > aSequence; + sal_Int64 nSize = 0; + ::std::optional 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(nCount)); + } + + if (pArgon2Args) + { + uno::Sequence args; + *pArgon2Args >>= args; + assert(args.getLength() == 3); + ::std::optional<::std::tuple> 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()); + oDigestAlg.emplace(pDigestAlg->get()); + pStream->SetImportedChecksumAlgorithm(oDigestAlg); + } + + *pEncryptionAlg >>= nEncryptionAlg; + pStream->SetImportedEncryptionAlgorithm( nEncryptionAlg ); + + if ( pDerivedKeySize ) + *pDerivedKeySize >>= nDerivedKeySize; + pStream->SetImportedDerivedKeySize( nDerivedKeySize ); + + if ( pStartKeyAlg ) + *pStartKeyAlg >>= nStartKeyAlg; + pStream->SetImportedStartKeyAlgorithm( nStartKeyAlg ); + + 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; + } + + // 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(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 ); + // 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("encrypted-package")) + { + m_bMediaTypeFallbackUsed = true; + } + } + } + else if ( !m_bForceRecovery ) + { + // the mimetype stream should contain the same information as manifest.xml + OUString const mediaTypeXML(m_xRootFolder->hasByName("encrypted-package") + ? m_xRootFolder->doGetByName("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("encrypted-package")); + + 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 OUString aContentTypes(u"[Content_Types].xml"_ustr); + // 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(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 sTemp, sDirName; + sal_Int32 nOldIndex, nStreamIndex; + FolderHash::iterator aIter; + + while (aEnum.hasMoreElements()) + { + nOldIndex = 0; + 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( '\\', '/' ); + } + + nStreamIndex = rName.lastIndexOf ( '/' ); + if ( nStreamIndex != -1 ) + { + sDirName = rName.copy ( 0, nStreamIndex ); + aIter = m_aRecent.find ( sDirName ); + if ( aIter != m_aRecent.end() ) + pCurrent = ( *aIter ).second; + } + + if ( pCurrent == m_xRootFolder.get() ) + { + sal_Int32 nIndex; + while ( ( nIndex = rName.indexOf( '/', nOldIndex ) ) != -1 ) + { + sTemp = rName.copy ( nOldIndex, nIndex - nOldIndex ); + if ( nIndex == nOldIndex ) + break; + if ( !pCurrent->hasByName( sTemp ) ) + { + rtl::Reference pPkgFolder = new ZipPackageFolder(m_xContext, m_nFormat, m_bAllowRemoveOnInsert); + pPkgFolder->setName( sTemp ); + pPkgFolder->doSetParent( pCurrent ); + pCurrent = pPkgFolder.get(); + } + else + { + ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp); + if (!rInfo.bFolder) + throw css::packages::zip::ZipIOException("Bad Zip File, stream as folder"); + pCurrent = rInfo.pFolder; + } + nOldIndex = nIndex+1; + } + if ( nStreamIndex != -1 && !sDirName.isEmpty() ) + m_aRecent [ sDirName ] = pCurrent; + } + if ( rName.getLength() -1 != nStreamIndex ) + { + nStreamIndex++; + sTemp = rName.copy( nStreamIndex ); + + if (!pCurrent->hasByName(sTemp)) + { + rtl::Reference pPkgStream = new ZipPackageStream(*this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert); + pPkgStream->SetPackageMember(true); + pPkgStream->setZipEntryOnLoading(rEntry); + pPkgStream->setName(sTemp); + pPkgStream->doSetParent(pCurrent); + } + } + } + + if ( m_nFormat == embed::StorageFormats::PACKAGE ) + parseManifest(); + else if ( m_nFormat == embed::StorageFormats::OFOPXML ) + parseContentType(); +} + +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("Size"); + 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); + 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 sTemp, sDirName; + sal_Int32 nOldIndex, nStreamIndex; + FolderHash::iterator aIter; + + sal_Int32 nIndex = aName.getLength(); + + if (aName == "/") + // root directory. + return Any ( uno::Reference( cppu::getXWeak(m_xRootFolder.get()) ) ); + + nStreamIndex = aName.lastIndexOf ( '/' ); + bool bFolder = nStreamIndex == nIndex-1; // last character is '/'. + + if ( nStreamIndex != -1 ) + { + // The name contains '/'. + sDirName = aName.copy ( 0, nStreamIndex ); + 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 ); + sTemp = aName.copy ( 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. + sTemp = aName.copy ( 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 ) + { + sTemp = aName.copy ( 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("Bad Zip File, stream as folder"); + pCurrent = rInfo.pFolder; + nOldIndex = nIndex+1; + } + + if ( bFolder ) + { + if ( nStreamIndex != -1 ) + m_aRecent[sDirName] = pPrevious; // cache it. + return Any ( uno::Reference( cppu::getXWeak(pCurrent) ) ); + } + + sTemp = aName.copy( 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 ) +{ + OUString sTemp; + sal_Int32 nOldIndex; + FolderHash::iterator aIter; + + sal_Int32 nIndex = aName.getLength(); + + if (aName == "/") + // root directory + return true; + + try + { + OUString sDirName; + sal_Int32 nStreamIndex; + nStreamIndex = aName.lastIndexOf ( '/' ); + bool bFolder = nStreamIndex == nIndex-1; + if ( nStreamIndex != -1 ) + { + sDirName = aName.copy ( 0, nStreamIndex ); + aIter = m_aRecent.find ( sDirName ); + if ( aIter != m_aRecent.end() ) + { + if ( bFolder ) + { + sal_Int32 nDirIndex = aName.lastIndexOf ( '/', nStreamIndex ); + sTemp = aName.copy ( nDirIndex == -1 ? 0 : nDirIndex+1, nStreamIndex-nDirIndex-1 ); + if ( sTemp == ( *aIter ).second->getName() ) + return true; + else + m_aRecent.erase ( aIter ); + } + else + { + sTemp = aName.copy ( 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; + nOldIndex = 0; + while ( ( nIndex = aName.indexOf( '/', nOldIndex )) != -1 ) + { + sTemp = aName.copy ( nOldIndex, nIndex - nOldIndex ); + if ( nIndex == nOldIndex ) + break; + if ( pCurrent->hasByName( sTemp ) ) + { + pPrevious = pCurrent; + ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp); + if (!rInfo.bFolder) + throw css::packages::zip::ZipIOException("Bad Zip File, stream as folder"); + pCurrent = rInfo.pFolder; + } + else + return false; + nOldIndex = nIndex+1; + } + if ( bFolder ) + { + m_aRecent[sDirName] = pPrevious; + return true; + } + else + { + sTemp = aName.copy( 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("ZipPackage::hasByHierarchicalName", 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 OUString sMime (u"mimetype"_ustr); + if ( m_xRootFolder->hasByName( sMime ) ) + m_xRootFolder->removeByName( sMime ); + + ZipEntry * pEntry = new 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(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(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 ); + ZipEntry * pEntry = new ZipEntry; + rtl::Reference pBuffer = new ZipPackageBuffer; + + pEntry->sPath = "META-INF/manifest.xml"; + pEntry->nMethod = DEFLATED; + pEntry->nCrc = -1; + pEntry->nSize = 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 ); + + // the manifest.xml is never encrypted - so pass an empty reference + ZipOutputStream::setEntry(pEntry); + aZipOut.writeLOC(pEntry); + ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, *pEntry, nullptr, /*bEncrypt*/false); + aZipEntry.write(pBuffer->getSequence()); + aZipEntry.closeEntry(); + aZipOut.rawCloseEntry(); +} + +void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const std::vector< uno::Sequence < PropertyValue > >& aManList ) +{ + ZipEntry* pEntry = new ZipEntry; + rtl::Reference pBuffer = new ZipPackageBuffer; + + pEntry->sPath = "[Content_Types].xml"; + pEntry->nMethod = DEFLATED; + pEntry->nCrc = -1; + pEntry->nSize = pEntry->nCompressedSize = -1; + pEntry->nTime = ZipOutputStream::getCurrentDosTime(); + + // Add default entries, the count must be updated manually when appending. + // Add at least the standard default entries. + uno::Sequence< beans::StringPair > aDefaultsSequence + { + { "xml", "application/xml" }, + { "rels", "application/vnd.openxmlformats-package.relationships+xml" }, + { "png", "image/png" }, + { "jpeg", "image/jpeg" } + }; + + uno::Sequence< beans::StringPair > aOverridesSequence(aManList.size()); + auto aOverridesSequenceRange = asNonConstRange(aOverridesSequence); + sal_Int32 nOverSeqLength = 0; + for (const auto& rMan : aManList) + { + OUString aType; + OSL_ENSURE( rMan[PKG_MNFST_MEDIATYPE].Name == "MediaType" && rMan[PKG_MNFST_FULLPATH].Name == "FullPath", + "The mediatype sequence format is wrong!" ); + rMan[PKG_MNFST_MEDIATYPE].Value >>= aType; + if ( !aType.isEmpty() ) + { + OUString aPath; + // only nonempty type makes sense here + rMan[PKG_MNFST_FULLPATH].Value >>= aPath; + //FIXME: For now we have no way of differentiating defaults from others. + 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 ); + + // there is no encryption in this format currently + ZipOutputStream::setEntry(pEntry); + aZipOut.writeLOC(pEntry); + ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, *pEntry, nullptr, /*bEncrypt*/false); + aZipEntry.write(pBuffer->getSequence()); + aZipEntry.closeEntry(); + aZipOut.rawCloseEntry(); +} + +void ZipPackage::ConnectTo( const uno::Reference< io::XInputStream >& xInStream ) +{ + 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); +} + +namespace +{ + class RandomPool + { + private: + rtlRandomPool m_aRandomPool; + public: + RandomPool() : m_aRandomPool(rtl_random_createPool ()) + { + } + rtlRandomPool get() + { + return m_aRandomPool; + } + ~RandomPool() + { + // Clean up random pool memory + rtl_random_destroyPool(m_aRandomPool); + } + }; +} + +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 OUString sMeta (u"META-INF"_ustr); + + 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 OUString aContentTypes(u"[Content_Types].xml"_ustr); + + 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 <<= OUString("/"); + + if( bIsGpgEncrypt ) + { + pPropSeq[PKG_SIZE_NOENCR_MNFST].Name = "KeyInfo"; + pPropSeq[PKG_SIZE_NOENCR_MNFST].Value <<= m_aGpgProps; + } + aManList.push_back( aPropSeq ); + } + + { + // This will be used to generate random salt and initialisation vectors + // for encrypted streams + RandomPool aRandomPool; + + ::std::optional oPBKDF2IterationCount; + ::std::optional<::std::tuple> oArgon2Args; + + if (!bIsGpgEncrypt) + { + if (m_nKeyDerivationFunctionID == xml::crypto::KDFID::PBKDF2) + { // if there is only one KDF invocation, increase the safety margin + oPBKDF2IterationCount.emplace(officecfg::Office::Common::Misc::ExperimentalMode::get() ? 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("", aManList, aZipOut, GetEncryptionKey(), + oPBKDF2IterationCount, oArgon2Args, aRandomPool.get()); + } + + if( m_nFormat == embed::StorageFormats::PACKAGE ) + { + WriteManifest( aZipOut, aManList ); + } + else if( m_nFormat == embed::StorageFormats::OFOPXML ) + { + WriteContentTypes( aZipOut, aManList ); + } + + aZipOut.finish(); + + if( bUseTemp ) + xResult = 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("Size", 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("open", 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 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 ("Uri"); + 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 ("transfer", 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("Uri"); + aUrl >>= aTempURL; + xTempFile->setPropertyValue("RemoveFile", + 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 : std::as_const(m_aStorageEncryptionKeys) ) + if ( rKey.Name == aNameToFind ) + rKey.Value >>= aResult; + } + 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 "com.sun.star.packages.comp.ZipPackage"; +} + +Sequence< OUString > ZipPackage::getSupportedServiceNames() +{ + return { "com.sun.star.packages.Package" }; +} + +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 : std::as_const(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(), 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(), 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(), 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(), 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 = 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["StartKeyGenerationAlgorithm"] <<= m_nStartKeyGenerationID; + aAlgorithms["KeyDerivationFunction"] <<= m_nKeyDerivationFunctionID; + aAlgorithms["EncryptionAlgorithm"] <<= m_nCommonEncryptionID; + if (m_oChecksumDigestID) + { + aAlgorithms["ChecksumAlgorithm"] <<= *m_oChecksumDigestID; + } + else + { + aAlgorithms["ChecksumAlgorithm"]; + } + 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 const&) +{ + return cppu::acquire(new ZipPackage(context)); +} + +extern "C" bool TestImportZip(SvStream& rStream) +{ + // explicitly tests the "RepairPackage" recovery mode + rtl::Reference xPackage(new ZipPackage(comphelper::getProcessComponentContext())); + css::uno::Reference xStream(new utl::OInputStreamWrapper(rStream)); + css::uno::Sequence aArgs{ Any(xStream), Any(NamedValue("RepairPackage", Any(true))) }; + xPackage->initialize(aArgs); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageBuffer.cxx b/package/source/zippackage/ZipPackageBuffer.cxx new file mode 100644 index 0000000000..810daa737a --- /dev/null +++ b/package/source/zippackage/ZipPackageBuffer.cxx @@ -0,0 +1,128 @@ +/* -*- 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 +#include +#include +#include + +#include +#include + +using namespace ::com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::io; +using com::sun::star::lang::IllegalArgumentException; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageBuffer::ZipPackageBuffer() +: m_nBufferSize (n_ConstBufferSize) +, m_nEnd(0) +, m_nCurrent(0) +, m_bMustInitBuffer ( true ) +{ +} +ZipPackageBuffer::~ZipPackageBuffer() +{ +} + +sal_Int32 SAL_CALL ZipPackageBuffer::readBytes( Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + if (nBytesToRead < 0) + throw BufferSizeExceededException(THROW_WHERE, *this ); + + if (nBytesToRead + m_nCurrent > m_nEnd) + nBytesToRead = static_cast < sal_Int32 > (m_nEnd - m_nCurrent); + + aData.realloc ( nBytesToRead ); + memcpy(aData.getArray(), m_aBuffer.getConstArray() + m_nCurrent, nBytesToRead); + m_nCurrent +=nBytesToRead; + return nBytesToRead; +} + +sal_Int32 SAL_CALL ZipPackageBuffer::readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return readBytes(aData, nMaxBytesToRead); +} +void SAL_CALL ZipPackageBuffer::skipBytes( sal_Int32 nBytesToSkip ) +{ + if (nBytesToSkip < 0) + throw BufferSizeExceededException(THROW_WHERE, *this ); + + if (nBytesToSkip + m_nCurrent > m_nEnd) + nBytesToSkip = static_cast < sal_Int32 > (m_nEnd - m_nCurrent); + + m_nCurrent+=nBytesToSkip; +} +sal_Int32 SAL_CALL ZipPackageBuffer::available( ) +{ + return std::min(SAL_MAX_INT32, m_nEnd - m_nCurrent); +} +void SAL_CALL ZipPackageBuffer::closeInput( ) +{ +} +void SAL_CALL ZipPackageBuffer::writeBytes( const Sequence< sal_Int8 >& aData ) +{ + sal_Int64 nDataLen = aData.getLength(), nCombined = m_nEnd + nDataLen; + + if ( nCombined > m_nBufferSize) + { + do + m_nBufferSize *=2; + while (nCombined > m_nBufferSize); + m_aBuffer.realloc(static_cast < sal_Int32 > (m_nBufferSize)); + m_bMustInitBuffer = false; + } + else if (m_bMustInitBuffer) + { + m_aBuffer.realloc ( static_cast < sal_Int32 > ( m_nBufferSize ) ); + m_bMustInitBuffer = false; + } + memcpy( m_aBuffer.getArray() + m_nCurrent, aData.getConstArray(), static_cast < sal_Int32 > (nDataLen)); + m_nCurrent+=nDataLen; + if (m_nCurrent>m_nEnd) + m_nEnd = m_nCurrent; +} +void SAL_CALL ZipPackageBuffer::flush( ) +{ +} +void SAL_CALL ZipPackageBuffer::closeOutput( ) +{ +} +void SAL_CALL ZipPackageBuffer::seek( sal_Int64 location ) +{ + if ( location > m_nEnd || location < 0 ) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + m_nCurrent = location; +} +sal_Int64 SAL_CALL ZipPackageBuffer::getPosition( ) +{ + return m_nCurrent; +} +sal_Int64 SAL_CALL ZipPackageBuffer::getLength( ) +{ + return m_nEnd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageEntry.cxx b/package/source/zippackage/ZipPackageEntry.cxx new file mode 100644 index 0000000000..56337109ec --- /dev/null +++ b/package/source/zippackage/ZipPackageEntry.cxx @@ -0,0 +1,125 @@ +/* -*- 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 +#include +#include +#include +#include + +#include + +#include +#include + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::packages::zip::ZipConstants; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageEntry::ZipPackageEntry() +: mbIsFolder( false ) +, mbAllowRemoveOnInsert(false) +, mpParent ( nullptr ) +, m_nFormat(0) +{ +} + +ZipPackageEntry::~ZipPackageEntry() +{ + // When the entry is destroyed it must be already disconnected from the parent + OSL_ENSURE( !mpParent, "The parent must be disconnected already! Memory corruption is possible!" ); +} + +// XChild +OUString SAL_CALL ZipPackageEntry::getName( ) +{ + return msName; +} +void SAL_CALL ZipPackageEntry::setName( const OUString& aName ) +{ + if ( mpParent && !msName.isEmpty() && mpParent->hasByName ( msName ) ) + mpParent->removeByName ( msName ); + + // unfortunately no other exception than RuntimeException can be thrown here + // usually the package is used through storage implementation, the problem should be detected there + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( aName, true ) ) + throw RuntimeException(THROW_WHERE "Unexpected character is used in file name." ); + + msName = aName; + + if ( mpParent ) + mpParent->doInsertByName ( this, false ); +} +uno::Reference< XInterface > SAL_CALL ZipPackageEntry::getParent( ) +{ + // return uno::Reference< XInterface >( xParent, UNO_QUERY ); + return cppu::getXWeak( mpParent ); +} + +void ZipPackageEntry::doSetParent ( ZipPackageFolder * pNewParent ) +{ + // xParent = mpParent = pNewParent; + mpParent = pNewParent; + if ( !msName.isEmpty() && !pNewParent->hasByName ( msName ) ) + pNewParent->doInsertByName ( this, false ); +} + +void SAL_CALL ZipPackageEntry::setParent( const uno::Reference< XInterface >& xNewParent ) +{ + if ( !xNewParent.is() ) + throw NoSupportException(THROW_WHERE ); + ZipPackageFolder* pNewParent = dynamic_cast(xNewParent.get()); + if (!pNewParent) + throw NoSupportException(THROW_WHERE ); + + if ( pNewParent != mpParent ) + { + if ( mpParent && !msName.isEmpty() && mpParent->hasByName ( msName ) && mbAllowRemoveOnInsert ) + mpParent->removeByName( msName ); + doSetParent ( pNewParent ); + } +} + //XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL ZipPackageEntry::getPropertySetInfo( ) +{ + return uno::Reference < beans::XPropertySetInfo > (); +} +void SAL_CALL ZipPackageEntry::addPropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ +} +void SAL_CALL ZipPackageEntry::removePropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ ) +{ +} +void SAL_CALL ZipPackageEntry::addVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} +void SAL_CALL ZipPackageEntry::removeVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageFolder.cxx b/package/source/zippackage/ZipPackageFolder.cxx new file mode 100644 index 0000000000..bca4e46e1b --- /dev/null +++ b/package/source/zippackage/ZipPackageFolder.cxx @@ -0,0 +1,425 @@ +/* -*- 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 +#include +#include +#include +#include "ZipPackageFolderEnumeration.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::packages; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; +using namespace com::sun::star::io; +using namespace cppu; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageFolder::ZipPackageFolder( const css::uno::Reference < css::uno::XComponentContext >& xContext, + sal_Int32 nFormat, + bool bAllowRemoveOnInsert ) +{ + m_xContext = xContext; + m_nFormat = nFormat; + mbAllowRemoveOnInsert = bAllowRemoveOnInsert; + SetFolder ( true ); + aEntry.nVersion = -1; + aEntry.nFlag = 0; + aEntry.nMethod = STORED; + aEntry.nTime = -1; + aEntry.nCrc = 0; + aEntry.nCompressedSize = 0; + aEntry.nSize = 0; + aEntry.nOffset = -1; +} + +ZipPackageFolder::~ZipPackageFolder() +{ +} + +bool ZipPackageFolder::LookForUnexpectedODF12Streams( + std::u16string_view const aPath, bool const isWholesomeEncryption) +{ + bool bHasUnexpected = false; + + for (const auto& [rShortName, rInfo] : maContents) + { + if ( rInfo.bFolder ) + { + if ( aPath == u"META-INF/" ) + { + // META-INF is not allowed to contain subfolders + bHasUnexpected = true; + } + else if (isWholesomeEncryption && rShortName != u"META-INF") + { + bHasUnexpected = true; + } + else + { + OUString sOwnPath = aPath + rShortName + "/"; + bHasUnexpected = rInfo.pFolder->LookForUnexpectedODF12Streams(sOwnPath, isWholesomeEncryption); + } + } + else + { + if ( aPath == u"META-INF/" ) + { + if ( rShortName != "manifest.xml" + && rShortName.indexOf( "signatures" ) == -1 ) + { + // a stream from META-INF with unexpected name + bHasUnexpected = true; + } + + // streams from META-INF with expected names are allowed not to be registered in manifest.xml + } + else if (isWholesomeEncryption && rShortName != "mimetype" && rShortName != "encrypted-package") + { + bHasUnexpected = true; + } + else if ( !rInfo.pStream->IsFromManifest() ) + { + // the stream is not in META-INF and is not registered in manifest.xml, + // check whether it is an internal part of the package format + if ( !aPath.empty() || rShortName != "mimetype" ) + { + // if it is not "mimetype" from the root it is not a part of the package + bHasUnexpected = true; + } + } + } + + if (bHasUnexpected) + break; + } + + return bHasUnexpected; +} + +void ZipPackageFolder::setChildStreamsTypeByExtension( const beans::StringPair& aPair ) +{ + OUString aExt; + if ( aPair.First.toChar() == '.' ) + aExt = aPair.First; + else + aExt = "." + aPair.First; + + for (const auto& [rShortName, rInfo] : maContents) + { + if ( rInfo.bFolder ) + rInfo.pFolder->setChildStreamsTypeByExtension( aPair ); + else + { + sal_Int32 nPathLength = rShortName.getLength(); + sal_Int32 nExtLength = aExt.getLength(); + if ( nPathLength >= nExtLength && rShortName.match( aExt, nPathLength - nExtLength ) ) + rInfo.pStream->SetMediaType( aPair.Second ); + } + } +} + + // XNameContainer +void SAL_CALL ZipPackageFolder::insertByName( const OUString& aName, const uno::Any& aElement ) +{ + if (hasByName(aName)) + throw ElementExistException(THROW_WHERE ); + + uno::Reference < XInterface > xRef; + aElement >>= xRef; + if ( !(aElement >>= xRef) ) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + ZipPackageEntry* pEntry = dynamic_cast(xRef.get()); + if (!pEntry) + pEntry = dynamic_cast(xRef.get()); + if (!pEntry) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + if (pEntry->getName() != aName ) + pEntry->setName (aName); + doInsertByName ( pEntry, true ); +} + +void SAL_CALL ZipPackageFolder::removeByName( const OUString& Name ) +{ + ContentHash::iterator aIter = maContents.find ( Name ); + if ( aIter == maContents.end() ) + throw NoSuchElementException(THROW_WHERE ); + maContents.erase( aIter ); +} + // XEnumerationAccess +uno::Reference< XEnumeration > SAL_CALL ZipPackageFolder::createEnumeration( ) +{ + return uno::Reference < XEnumeration> (new ZipPackageFolderEnumeration(maContents)); +} + // XElementAccess +uno::Type SAL_CALL ZipPackageFolder::getElementType( ) +{ + return cppu::UnoType::get(); +} +sal_Bool SAL_CALL ZipPackageFolder::hasElements( ) +{ + return !maContents.empty(); +} + // XNameAccess +ZipContentInfo& ZipPackageFolder::doGetByName( const OUString& aName ) +{ + ContentHash::iterator aIter = maContents.find ( aName ); + if ( aIter == maContents.end()) + throw NoSuchElementException(THROW_WHERE ); + return aIter->second; +} + +uno::Any SAL_CALL ZipPackageFolder::getByName( const OUString& aName ) +{ + return uno::Any ( uno::Reference(cppu::getXWeak(doGetByName ( aName ).xPackageEntry.get())) ); +} +uno::Sequence< OUString > SAL_CALL ZipPackageFolder::getElementNames( ) +{ + return comphelper::mapKeysToSequence(maContents); +} +sal_Bool SAL_CALL ZipPackageFolder::hasByName( const OUString& aName ) +{ + return maContents.find ( aName ) != maContents.end (); +} + // XNameReplace +void SAL_CALL ZipPackageFolder::replaceByName( const OUString& aName, const uno::Any& aElement ) +{ + if ( !hasByName( aName ) ) + throw NoSuchElementException(THROW_WHERE ); + + removeByName( aName ); + insertByName(aName, aElement); +} + +bool ZipPackageFolder::saveChild( + const OUString &rPath, + std::vector < uno::Sequence < PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional const oPBKDF2IterationCount, + ::std::optional<::std::tuple> const oArgon2Args, + const rtlRandomPool &rRandomPool) +{ + uno::Sequence < PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST); + OUString sTempName = rPath + "/"; + + if ( !GetMediaType().isEmpty() ) + { + auto pPropSet = aPropSet.getArray(); + pPropSet[PKG_MNFST_MEDIATYPE].Name = "MediaType"; + pPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType(); + pPropSet[PKG_MNFST_VERSION].Name = "Version"; + pPropSet[PKG_MNFST_VERSION].Value <<= GetVersion(); + pPropSet[PKG_MNFST_FULLPATH].Name = "FullPath"; + pPropSet[PKG_MNFST_FULLPATH].Value <<= sTempName; + } + else + aPropSet.realloc( 0 ); + + saveContents(sTempName, rManList, rZipOut, rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool); + + // folder can have a mediatype only in package format + if ( aPropSet.hasElements() && ( m_nFormat == embed::StorageFormats::PACKAGE ) ) + rManList.push_back( aPropSet ); + + return true; +} + +void ZipPackageFolder::saveContents( + const OUString &rPath, + std::vector < uno::Sequence < PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional const oPBKDF2IterationCount, + ::std::optional<::std::tuple> const oArgon2Args, + const rtlRandomPool &rRandomPool ) const +{ + if ( maContents.empty() && !rPath.isEmpty() && m_nFormat != embed::StorageFormats::OFOPXML ) + { + // it is an empty subfolder, use workaround to store it + ZipEntry* pTempEntry = new ZipEntry(aEntry); + pTempEntry->nPathLen = static_cast( OUStringToOString( rPath, RTL_TEXTENCODING_UTF8 ).getLength() ); + pTempEntry->nExtraLen = -1; + pTempEntry->sPath = rPath; + + try + { + ZipOutputStream::setEntry(pTempEntry); + rZipOut.writeLOC(pTempEntry); + rZipOut.rawCloseEntry(); + } + catch ( ZipException& ) + { + throw uno::RuntimeException( THROW_WHERE ); + } + catch ( IOException& ) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + + bool bMimeTypeStreamStored = false; + OUString aMimeTypeStreamName("mimetype"); + if ( m_nFormat == embed::StorageFormats::ZIP && rPath.isEmpty() ) + { + // let the "mimetype" stream in root folder be stored as the first stream if it is zip format + ContentHash::const_iterator aIter = maContents.find ( aMimeTypeStreamName ); + if ( aIter != maContents.end() && !(*aIter).second.bFolder ) + { + bMimeTypeStreamStored = true; + if (!aIter->second.pStream->saveChild(rPath + aIter->first, rManList, rZipOut, + rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool)) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + } + + for (const auto& [rShortName, rInfo] : maContents) + { + if ( !bMimeTypeStreamStored || rShortName != aMimeTypeStreamName ) + { + if (rInfo.bFolder) + { + if (!rInfo.pFolder->saveChild(rPath + rShortName, rManList, rZipOut, + rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool)) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + else + { + if (!rInfo.pStream->saveChild(rPath + rShortName, rManList, rZipOut, + rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool)) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + } + } +} + +void SAL_CALL ZipPackageFolder::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + if ( aPropertyName == "MediaType" ) + { + // TODO/LATER: activate when zip ucp is ready + // if ( m_nFormat != embed::StorageFormats::PACKAGE ) + // throw UnknownPropertyException(THROW_WHERE ); + + aValue >>= msMediaType; + } + else if ( aPropertyName == "Version" ) + aValue >>= m_sVersion; + else if ( aPropertyName == "Size" ) + aValue >>= aEntry.nSize; + else + throw UnknownPropertyException(aPropertyName); +} +uno::Any SAL_CALL ZipPackageFolder::getPropertyValue( const OUString& PropertyName ) +{ + if ( PropertyName == "MediaType" ) + { + // TODO/LATER: activate when zip ucp is ready + // if ( m_nFormat != embed::StorageFormats::PACKAGE ) + // throw UnknownPropertyException(THROW_WHERE ); + + return uno::Any ( msMediaType ); + } + else if ( PropertyName == "Version" ) + return uno::Any( m_sVersion ); + else if ( PropertyName == "Size" ) + return uno::Any ( aEntry.nSize ); + else + throw UnknownPropertyException(PropertyName); +} + +void ZipPackageFolder::doInsertByName ( ZipPackageEntry *pEntry, bool bSetParent ) +{ + if ( pEntry->IsFolder() ) + maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast(pEntry))); + else + maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast(pEntry))); + if ( bSetParent ) + pEntry->setParent ( *this ); +} + +OUString ZipPackageFolder::getImplementationName() +{ + return "ZipPackageFolder"; +} + +uno::Sequence< OUString > ZipPackageFolder::getSupportedServiceNames() +{ + return { "com.sun.star.packages.PackageFolder" }; +} + +sal_Bool SAL_CALL ZipPackageFolder::supportsService( OUString const & rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + + +ZipContentInfo::ZipContentInfo ( ZipPackageStream * pNewStream ) +: xPackageEntry ( pNewStream ) +, bFolder ( false ) +, pStream ( pNewStream ) +{ +} + +ZipContentInfo::ZipContentInfo ( ZipPackageFolder * pNewFolder ) +: xPackageEntry ( pNewFolder ) +, bFolder ( true ) +, pFolder ( pNewFolder ) +{ +} + +ZipContentInfo::ZipContentInfo( const ZipContentInfo& ) = default; +ZipContentInfo::ZipContentInfo( ZipContentInfo&& ) = default; +ZipContentInfo& ZipContentInfo::operator=( const ZipContentInfo& ) = default; +ZipContentInfo& ZipContentInfo::operator=( ZipContentInfo&& ) = default; + +ZipContentInfo::~ZipContentInfo() +{ + if ( bFolder ) + pFolder->clearParent(); + else + pStream->clearParent(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageFolderEnumeration.cxx b/package/source/zippackage/ZipPackageFolderEnumeration.cxx new file mode 100644 index 0000000000..080592c95f --- /dev/null +++ b/package/source/zippackage/ZipPackageFolderEnumeration.cxx @@ -0,0 +1,70 @@ +/* -*- 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 "ZipPackageFolderEnumeration.hxx" +#include +#include + +using namespace com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageFolderEnumeration::ZipPackageFolderEnumeration(ContentHash& rInput) + : rContents(rInput) + , aIterator(rContents.begin()) +{ +} + +ZipPackageFolderEnumeration::~ZipPackageFolderEnumeration() {} + +sal_Bool SAL_CALL ZipPackageFolderEnumeration::hasMoreElements() +{ + return (aIterator != rContents.end()); +} +uno::Any SAL_CALL ZipPackageFolderEnumeration::nextElement() +{ + uno::Any aAny; + if (aIterator == rContents.end()) + throw container::NoSuchElementException(THROW_WHERE); + aAny <<= uno::Reference(cppu::getXWeak((*aIterator).second.xPackageEntry.get())); + ++aIterator; + return aAny; +} + +OUString ZipPackageFolderEnumeration::getImplementationName() +{ + return "ZipPackageFolderEnumeration"; +} + +uno::Sequence ZipPackageFolderEnumeration::getSupportedServiceNames() +{ + uno::Sequence aNames{ "com.sun.star.packages.PackageFolderEnumeration" }; + return aNames; +} + +sal_Bool SAL_CALL ZipPackageFolderEnumeration::supportsService(OUString const& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageFolderEnumeration.hxx b/package/source/zippackage/ZipPackageFolderEnumeration.hxx new file mode 100644 index 0000000000..f3b805b2aa --- /dev/null +++ b/package/source/zippackage/ZipPackageFolderEnumeration.hxx @@ -0,0 +1,49 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGEFOLDERENUMERATION_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGEFOLDERENUMERATION_HXX + +#include +#include +#include +#include + +class ZipPackageFolderEnumeration final + : public cppu::WeakImplHelper +{ + ContentHash& rContents; + ContentHash::const_iterator aIterator; + +public: + //ZipPackageFolderEnumeration (unordered_map < OUString, css::uno::Reference < css::container::XNamed >, hashFunc, eqFunc > &rInput); + ZipPackageFolderEnumeration(ContentHash& rInput); + virtual ~ZipPackageFolderEnumeration() override; + + // XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual css::uno::Any SAL_CALL nextElement() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence SAL_CALL getSupportedServiceNames() override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageSink.cxx b/package/source/zippackage/ZipPackageSink.cxx new file mode 100644 index 0000000000..103af9d6ed --- /dev/null +++ b/package/source/zippackage/ZipPackageSink.cxx @@ -0,0 +1,37 @@ +/* -*- 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 "ZipPackageSink.hxx" + +ZipPackageSink::ZipPackageSink() + : xStream(css::uno::Reference(nullptr)) +{ +} +ZipPackageSink::~ZipPackageSink() {} +void SAL_CALL +ZipPackageSink::setInputStream(const css::uno::Reference& aStream) +{ + xStream = aStream; +} +css::uno::Reference SAL_CALL ZipPackageSink::getInputStream() +{ + return xStream; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageSink.hxx b/package/source/zippackage/ZipPackageSink.hxx new file mode 100644 index 0000000000..9bc406c89e --- /dev/null +++ b/package/source/zippackage/ZipPackageSink.hxx @@ -0,0 +1,38 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGESINK_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGESINK_HXX + +#include +#include + +class ZipPackageSink final : public ::cppu::WeakImplHelper +{ + css::uno::Reference xStream; + +public: + ZipPackageSink(); + virtual ~ZipPackageSink() override; + virtual void SAL_CALL + setInputStream(const css::uno::Reference& aStream) override; + virtual css::uno::Reference SAL_CALL getInputStream() override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageStream.cxx b/package/source/zippackage/ZipPackageStream.cxx new file mode 100644 index 0000000000..d3068a6665 --- /dev/null +++ b/package/source/zippackage/ZipPackageStream.cxx @@ -0,0 +1,1359 @@ +/* -*- 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "wrapstreamforshare.hxx" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star; +using namespace cppu; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageStream::ZipPackageStream ( ZipPackage & rNewPackage, + const uno::Reference< XComponentContext >& xContext, + sal_Int32 nFormat, + bool bAllowRemoveOnInsert ) +: m_rZipPackage( rNewPackage ) +, m_bToBeCompressed ( true ) +, m_bToBeEncrypted ( false ) +, m_bHaveOwnKey ( false ) +, m_bIsEncrypted ( false ) +, m_nImportedStartKeyAlgorithm( 0 ) +, m_nImportedEncryptionAlgorithm( 0 ) +, m_nImportedDerivedKeySize( 0 ) +, m_nStreamMode( PACKAGE_STREAM_NOTSET ) +, m_nMagicalHackPos( 0 ) +, m_nMagicalHackSize( 0 ) +, m_nOwnStreamOrigSize( 0 ) +, m_bHasSeekable( false ) +, m_bCompressedIsSetFromOutside( false ) +, m_bFromManifest( false ) +, m_bUseWinEncoding( false ) +, m_bRawStream( false ) +{ + m_xContext = xContext; + m_nFormat = nFormat; + mbAllowRemoveOnInsert = bAllowRemoveOnInsert; + SetFolder ( false ); + aEntry.nVersion = -1; + aEntry.nFlag = 0; + aEntry.nMethod = -1; + aEntry.nTime = -1; + aEntry.nCrc = -1; + aEntry.nCompressedSize = -1; + aEntry.nSize = -1; + aEntry.nOffset = -1; + aEntry.nPathLen = -1; + aEntry.nExtraLen = -1; +} + +ZipPackageStream::~ZipPackageStream() +{ +} + +void ZipPackageStream::setZipEntryOnLoading( const ZipEntry &rInEntry ) +{ + aEntry.nVersion = rInEntry.nVersion; + aEntry.nFlag = rInEntry.nFlag; + aEntry.nMethod = rInEntry.nMethod; + aEntry.nTime = rInEntry.nTime; + aEntry.nCrc = rInEntry.nCrc; + aEntry.nCompressedSize = rInEntry.nCompressedSize; + aEntry.nSize = rInEntry.nSize; + aEntry.nOffset = rInEntry.nOffset; + aEntry.sPath = rInEntry.sPath; + aEntry.nPathLen = rInEntry.nPathLen; + aEntry.nExtraLen = rInEntry.nExtraLen; + + if ( aEntry.nMethod == STORED ) + m_bToBeCompressed = false; +} + +uno::Reference< io::XInputStream > const & ZipPackageStream::GetOwnSeekStream() +{ + if ( !m_bHasSeekable && m_xStream.is() ) + { + // The package component requires that every stream either be FROM a package or it must support XSeekable! + // The only exception is a nonseekable stream that is provided only for storing, if such a stream + // is accessed before commit it MUST be wrapped. + // Wrap the stream in case it is not seekable + m_xStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( m_xStream, m_xContext ); + uno::Reference< io::XSeekable > xSeek( m_xStream, UNO_QUERY_THROW ); + + m_bHasSeekable = true; + } + + return m_xStream; +} + +uno::Reference< io::XInputStream > ZipPackageStream::GetRawEncrStreamNoHeaderCopy() +{ + if ( m_nStreamMode != PACKAGE_STREAM_RAW || !GetOwnSeekStream().is() ) + throw io::IOException(THROW_WHERE ); + + if ( m_xBaseEncryptionData.is() ) + throw ZipIOException(THROW_WHERE "Encrypted stream without encryption data!" ); + + uno::Reference< io::XSeekable > xSeek( GetOwnSeekStream(), UNO_QUERY ); + if ( !xSeek.is() ) + throw ZipIOException(THROW_WHERE "The stream must be seekable!" ); + + // skip header + xSeek->seek( n_ConstHeaderSize + m_xBaseEncryptionData->m_aInitVector.getLength() + + m_xBaseEncryptionData->m_aSalt.getLength() + m_xBaseEncryptionData->m_aDigest.getLength() ); + + // create temporary stream + rtl::Reference < utl::TempFileFastService > xTempFile = new utl::TempFileFastService; + uno::Reference < io::XInputStream > xTempIn = xTempFile->getInputStream(); + + // copy the raw stream to the temporary file starting from the current position + ::comphelper::OStorageHelper::CopyInputToOutput( GetOwnSeekStream(), xTempFile ); + xTempFile->closeOutput(); + xTempFile->seek( 0 ); + + return xTempIn; +} + +sal_Int32 ZipPackageStream::GetEncryptionAlgorithm() const +{ + return m_nImportedEncryptionAlgorithm ? m_nImportedEncryptionAlgorithm : m_rZipPackage.GetEncAlgID(); +} + +sal_Int32 ZipPackageStream::GetIVSize() const +{ + switch (GetEncryptionAlgorithm()) + { + case css::xml::crypto::CipherID::BLOWFISH_CFB_8: + return 8; + case css::xml::crypto::CipherID::AES_CBC_W3C_PADDING: + return 16; + case css::xml::crypto::CipherID::AES_GCM_W3C: + return 12; + default: + O3TL_UNREACHABLE; + } +} + +::rtl::Reference ZipPackageStream::GetEncryptionData(Bugs const bugs) +{ + ::rtl::Reference< EncryptionData > xResult; + if ( m_xBaseEncryptionData.is() ) + xResult = new EncryptionData( + *m_xBaseEncryptionData, + GetEncryptionKey(bugs), + GetEncryptionAlgorithm(), + m_oImportedChecksumAlgorithm ? m_oImportedChecksumAlgorithm : m_rZipPackage.GetChecksumAlgID(), + m_nImportedDerivedKeySize ? m_nImportedDerivedKeySize : m_rZipPackage.GetDefaultDerivedKeySize(), + GetStartKeyGenID(), + bugs != Bugs::None); + + return xResult; +} + +uno::Sequence ZipPackageStream::GetEncryptionKey(Bugs const bugs) +{ + uno::Sequence< sal_Int8 > aResult; + sal_Int32 nKeyGenID = GetStartKeyGenID(); + bool const bUseWinEncoding = (bugs == Bugs::WinEncodingWrongSHA1 || m_bUseWinEncoding); + + if ( m_bHaveOwnKey && m_aStorageEncryptionKeys.hasElements() ) + { + OUString aNameToFind; + if ( nKeyGenID == xml::crypto::DigestID::SHA256 ) + aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA256UTF8; + else if ( nKeyGenID == xml::crypto::DigestID::SHA1 ) + { + aNameToFind = bUseWinEncoding + ? PACKAGE_ENCRYPTIONDATA_SHA1MS1252 + : (bugs == Bugs::WrongSHA1) + ? PACKAGE_ENCRYPTIONDATA_SHA1UTF8 + : PACKAGE_ENCRYPTIONDATA_SHA1CORRECT; + } + else + throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" ); + + for ( const auto& rKey : std::as_const(m_aStorageEncryptionKeys) ) + if ( rKey.Name == aNameToFind ) + rKey.Value >>= aResult; + + // empty keys are not allowed here + // so it is not important whether there is no key, or the key is empty, it is an error + if ( !aResult.hasElements() ) + throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" ); + } + else + aResult = m_aEncryptionKey; + + if ( !aResult.hasElements() || !m_bHaveOwnKey ) + aResult = m_rZipPackage.GetEncryptionKey(); + + return aResult; +} + +sal_Int32 ZipPackageStream::GetStartKeyGenID() const +{ + // generally should all the streams use the same Start Key + // but if raw copy without password takes place, we should preserve the imported algorithm + return m_nImportedStartKeyAlgorithm ? m_nImportedStartKeyAlgorithm : m_rZipPackage.GetStartKeyGenID(); +} + +uno::Reference< io::XInputStream > ZipPackageStream::TryToGetRawFromDataStream( bool bAddHeaderForEncr ) +{ + if ( m_nStreamMode != PACKAGE_STREAM_DATA || !GetOwnSeekStream().is() || ( bAddHeaderForEncr && !m_bToBeEncrypted ) ) + throw packages::NoEncryptionException(THROW_WHERE ); + + Sequence< sal_Int8 > aKey; + + if ( m_bToBeEncrypted ) + { + aKey = GetEncryptionKey(); + if ( !aKey.hasElements() ) + throw packages::NoEncryptionException(THROW_WHERE ); + } + + try + { + // create temporary file + uno::Reference < io::XStream > xTempStream(new utl::TempFileFastService); + + // create a package based on it + rtl::Reference pPackage = new ZipPackage( m_xContext ); + + Sequence< Any > aArgs{ Any(xTempStream) }; + pPackage->initialize( aArgs ); + + // create a new package stream + uno::Reference< XDataSinkEncrSupport > xNewPackStream( pPackage->createInstance(), UNO_QUERY_THROW ); + xNewPackStream->setDataStream( + new WrapStreamForShare(GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef())); + + uno::Reference< XPropertySet > xNewPSProps( xNewPackStream, UNO_QUERY_THROW ); + + // copy all the properties of this stream to the new stream + xNewPSProps->setPropertyValue("MediaType", Any( msMediaType ) ); + xNewPSProps->setPropertyValue("Compressed", Any( m_bToBeCompressed ) ); + if ( m_bToBeEncrypted ) + { + xNewPSProps->setPropertyValue(ENCRYPTION_KEY_PROPERTY, Any( aKey ) ); + xNewPSProps->setPropertyValue("Encrypted", Any( true ) ); + } + + // insert a new stream in the package + uno::Reference< XInterface > xTmp; + Any aRoot = pPackage->getByHierarchicalName("/"); + aRoot >>= xTmp; + uno::Reference< container::XNameContainer > xRootNameContainer( xTmp, UNO_QUERY_THROW ); + + uno::Reference< XInterface > xNPSDummy( xNewPackStream, UNO_QUERY ); + xRootNameContainer->insertByName("dummy", Any( xNPSDummy ) ); + + // commit the temporary package + pPackage->commitChanges(); + + // get raw stream from the temporary package + uno::Reference< io::XInputStream > xInRaw; + if ( bAddHeaderForEncr ) + xInRaw = xNewPackStream->getRawStream(); + else + xInRaw = xNewPackStream->getPlainRawStream(); + + // create another temporary file + rtl::Reference < utl::TempFileFastService > xTempOut = new utl::TempFileFastService; + uno::Reference < io::XInputStream > xTempIn( xTempOut ); + + // copy the raw stream to the temporary file + ::comphelper::OStorageHelper::CopyInputToOutput( xInRaw, xTempOut ); + xTempOut->closeOutput(); + xTempOut->seek( 0 ); + + // close raw stream, package stream and folder + xInRaw.clear(); + xNewPSProps.clear(); + xNPSDummy.clear(); + xNewPackStream.clear(); + xTmp.clear(); + xRootNameContainer.clear(); + + // return the stream representing the first temporary file + return xTempIn; + } + catch ( RuntimeException& ) + { + throw; + } + catch ( Exception& ) + { + } + + throw io::IOException(THROW_WHERE ); +} + +// presumably the purpose of this is to transfer encrypted streams between +// storages, needed for password-protected macros in documents, which is +// tragically a feature that exists +bool ZipPackageStream::ParsePackageRawStream() +{ + OSL_ENSURE( GetOwnSeekStream().is(), "A stream must be provided!" ); + + if ( !GetOwnSeekStream().is() ) + return false; + + bool bOk = false; + + ::rtl::Reference< BaseEncryptionData > xTempEncrData; + Sequence < sal_Int8 > aHeader ( 4 ); + + try + { + if ( GetOwnSeekStream()->readBytes ( aHeader, 4 ) == 4 ) + { + const sal_Int8 *pHeader = aHeader.getConstArray(); + sal_uInt32 nHeader = ( pHeader [0] & 0xFF ) | + ( pHeader [1] & 0xFF ) << 8 | + ( pHeader [2] & 0xFF ) << 16 | + ( pHeader [3] & 0xFF ) << 24; + if ( nHeader == n_ConstHeader ) + { + // this is one of our god-awful, but extremely devious hacks, everyone cheer + xTempEncrData = new BaseEncryptionData; + + OUString aMediaType; + sal_Int32 nEncAlgorithm = 0; + sal_Int32 nChecksumAlgorithm = 0; + sal_Int32 nDerivedKeySize = 0; + sal_Int32 nStartKeyGenID = 0; + sal_Int32 nMagHackSize = 0; + if ( ZipFile::StaticFillData( xTempEncrData, nEncAlgorithm, nChecksumAlgorithm, nDerivedKeySize, nStartKeyGenID, nMagHackSize, aMediaType, GetOwnSeekStream() ) ) + { + // We'll want to skip the data we've just read, so calculate how much we just read + // and remember it + m_nMagicalHackPos = n_ConstHeaderSize + xTempEncrData->m_aSalt.getLength() + + xTempEncrData->m_aInitVector.getLength() + + xTempEncrData->m_aDigest.getLength() + + aMediaType.getLength() * sizeof( sal_Unicode ); + m_nImportedEncryptionAlgorithm = nEncAlgorithm; + if (nChecksumAlgorithm == 0) + { + m_oImportedChecksumAlgorithm.reset(); + } + else + { + m_oImportedChecksumAlgorithm.emplace(nChecksumAlgorithm); + } + m_nImportedDerivedKeySize = nDerivedKeySize; + m_nImportedStartKeyAlgorithm = nStartKeyGenID; + m_nMagicalHackSize = nMagHackSize; + msMediaType = aMediaType; + + bOk = true; + } + } + } + } + catch( Exception& ) + { + } + + if ( !bOk ) + { + // the provided stream is not a raw stream + return false; + } + + m_xBaseEncryptionData = xTempEncrData; + SetIsEncrypted ( true ); + // it's already compressed and encrypted + m_bToBeEncrypted = m_bToBeCompressed = false; + + return true; +} + +static void ImplSetStoredData( ZipEntry & rEntry, uno::Reference< io::XInputStream> const & rStream ) +{ + // It's very annoying that we have to do this, but lots of zip packages + // don't allow data descriptors for STORED streams, meaning we have to + // know the size and CRC32 of uncompressed streams before we actually + // write them ! + CRC32 aCRC32; + rEntry.nMethod = STORED; + rEntry.nCompressedSize = rEntry.nSize = aCRC32.updateStream ( rStream ); + rEntry.nCrc = aCRC32.getValue(); +} + +bool ZipPackageStream::saveChild( + const OUString &rPath, + std::vector < uno::Sequence < beans::PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional const oPBKDF2IterationCount, + ::std::optional<::std::tuple> const oArgon2Args, + const rtlRandomPool &rRandomPool) +{ + bool bSuccess = true; + + static constexpr OUString sDigestProperty (u"Digest"_ustr); + static constexpr OUString sEncryptionAlgProperty (u"EncryptionAlgorithm"_ustr); + static constexpr OUString sStartKeyAlgProperty (u"StartKeyAlgorithm"_ustr); + static constexpr OUString sDigestAlgProperty (u"DigestAlgorithm"_ustr); + static constexpr OUString sDerivedKeySizeProperty (u"DerivedKeySize"_ustr); + + uno::Sequence < beans::PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST); + + // In case the entry we are reading is also the entry we are writing, we will + // store the ZipEntry data in pTempEntry + + // if pTempEntry is necessary, it will be released and passed to the ZipOutputStream + // and be deleted in the ZipOutputStream destructor + std::unique_ptr < ZipEntry > pAutoTempEntry ( new ZipEntry(aEntry) ); + ZipEntry* pTempEntry = pAutoTempEntry.get(); + + pTempEntry->sPath = rPath; + pTempEntry->nPathLen = static_cast( OUStringToOString( pTempEntry->sPath, RTL_TEXTENCODING_UTF8 ).getLength() ); + + const bool bToBeEncrypted = m_bToBeEncrypted && (rEncryptionKey.hasElements() || m_bHaveOwnKey); + const bool bToBeCompressed = bToBeEncrypted || m_bToBeCompressed; + + auto pPropSet = aPropSet.getArray(); + pPropSet[PKG_MNFST_MEDIATYPE].Name = "MediaType"; + pPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType( ); + pPropSet[PKG_MNFST_VERSION].Name = "Version"; + pPropSet[PKG_MNFST_VERSION].Value <<= OUString(); // no version is stored for streams currently + pPropSet[PKG_MNFST_FULLPATH].Name = "FullPath"; + pPropSet[PKG_MNFST_FULLPATH].Value <<= pTempEntry->sPath; + + OSL_ENSURE( m_nStreamMode != PACKAGE_STREAM_NOTSET, "Unacceptable ZipPackageStream mode!" ); + + m_bRawStream = false; + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + m_bRawStream = ParsePackageRawStream(); + else if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + m_bRawStream = true; + + bool bBackgroundThreadDeflate = false; + bool bTransportOwnEncrStreamAsRaw = false; + // During the storing the original size of the stream can be changed + // TODO/LATER: get rid of this hack + m_nOwnStreamOrigSize = m_bRawStream ? m_nMagicalHackSize : aEntry.nSize; + + bool bUseNonSeekableAccess = false; + uno::Reference < io::XInputStream > xStream; + if ( !IsPackageMember() && !m_bRawStream && !bToBeEncrypted && bToBeCompressed ) + { + // the stream is not a package member, not a raw stream, + // it should not be encrypted and it should be compressed, + // in this case nonseekable access can be used + + xStream = m_xStream; + uno::Reference < io::XSeekable > xSeek ( xStream, uno::UNO_QUERY ); + + bUseNonSeekableAccess = ( xStream.is() && !xSeek.is() ); + } + + if ( !bUseNonSeekableAccess ) + { + xStream = getRawData(); + + if ( !xStream.is() ) + { + OSL_FAIL( "ZipPackageStream didn't have a stream associated with it, skipping!" ); + bSuccess = false; + return bSuccess; + } + + uno::Reference < io::XSeekable > xSeek ( xStream, uno::UNO_QUERY ); + try + { + if ( xSeek.is() ) + { + // If the stream is a raw one, then we should be positioned + // at the beginning of the actual data + if ( !bToBeCompressed || m_bRawStream ) + { + // The raw stream can neither be encrypted nor connected + OSL_ENSURE( !m_bRawStream || !(bToBeCompressed || bToBeEncrypted), "The stream is already encrypted!" ); + xSeek->seek ( m_bRawStream ? m_nMagicalHackPos : 0 ); + ImplSetStoredData ( *pTempEntry, xStream ); + + // TODO/LATER: Get rid of hacks related to switching of Flag Method and Size properties! + } + else if ( bToBeEncrypted ) + { + // this is the correct original size + pTempEntry->nSize = xSeek->getLength(); + m_nOwnStreamOrigSize = pTempEntry->nSize; + } + + xSeek->seek ( 0 ); + } + else + { + // Okay, we don't have an xSeekable stream. This is possibly bad. + // check if it's one of our own streams, if it is then we know that + // each time we ask for it we'll get a new stream that will be + // at position zero...otherwise, assert and skip this stream... + if ( IsPackageMember() ) + { + // if the password has been changed then the stream should not be package member any more + if ( m_bIsEncrypted && m_bToBeEncrypted ) + { + // Should be handled close to the raw stream handling + bTransportOwnEncrStreamAsRaw = true; + pTempEntry->nMethod = STORED; + + // TODO/LATER: get rid of this situation + // this size should be different from the one that will be stored in manifest.xml + // it is used in storing algorithms and after storing the correct size will be set + pTempEntry->nSize = pTempEntry->nCompressedSize; + } + } + else + { + bSuccess = false; + return bSuccess; + } + } + } + catch ( uno::Exception& ) + { + bSuccess = false; + return bSuccess; + } + + if ( bToBeEncrypted || m_bRawStream || bTransportOwnEncrStreamAsRaw ) + { + if ( bToBeEncrypted && !bTransportOwnEncrStreamAsRaw ) + { + uno::Sequence aSalt(16); + // note: for GCM it's particularly important that IV is unique + uno::Sequence aVector(GetIVSize()); + rtl_random_getBytes ( rRandomPool, aSalt.getArray(), 16 ); + rtl_random_getBytes ( rRandomPool, aVector.getArray(), aVector.getLength() ); + if ( !m_bHaveOwnKey ) + { + m_aEncryptionKey = rEncryptionKey; + m_aStorageEncryptionKeys.realloc( 0 ); + } + + setInitialisationVector ( aVector ); + setSalt ( aSalt ); + setIterationCount(oPBKDF2IterationCount); + setArgon2Args(oArgon2Args); + } + + // last property is digest, which is inserted later if we didn't have + // a magic header + aPropSet.realloc(PKG_SIZE_ENCR_MNFST); + pPropSet = aPropSet.getArray(); + pPropSet[PKG_MNFST_INIVECTOR].Name = "InitialisationVector"; + pPropSet[PKG_MNFST_INIVECTOR].Value <<= m_xBaseEncryptionData->m_aInitVector; + pPropSet[PKG_MNFST_SALT].Name = "Salt"; + pPropSet[PKG_MNFST_SALT].Value <<= m_xBaseEncryptionData->m_aSalt; + if (m_xBaseEncryptionData->m_oArgon2Args) + { + pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::Argon2id; + pPropSet[PKG_MNFST_ARGON2ARGS].Name = "Argon2Args"; + uno::Sequence const args{ + ::std::get<0>(*m_xBaseEncryptionData->m_oArgon2Args), + ::std::get<1>(*m_xBaseEncryptionData->m_oArgon2Args), + ::std::get<2>(*m_xBaseEncryptionData->m_oArgon2Args) }; + pPropSet[PKG_MNFST_ARGON2ARGS].Value <<= args; + } + else if (m_xBaseEncryptionData->m_oPBKDFIterationCount) + { + pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PBKDF2; + pPropSet[PKG_MNFST_ITERATION].Name = "IterationCount"; + pPropSet[PKG_MNFST_ITERATION].Value <<= *m_xBaseEncryptionData->m_oPBKDFIterationCount; + } + else + { + pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P; + } + + // Need to store the uncompressed size in the manifest + OSL_ENSURE( m_nOwnStreamOrigSize >= 0, "The stream size was not correctly initialized!" ); + pPropSet[PKG_MNFST_UCOMPSIZE].Name = "Size"; + pPropSet[PKG_MNFST_UCOMPSIZE].Value <<= m_nOwnStreamOrigSize; + + if ( m_bRawStream || bTransportOwnEncrStreamAsRaw ) + { + ::rtl::Reference< EncryptionData > xEncData = GetEncryptionData(); + if ( !xEncData.is() ) + throw uno::RuntimeException(); + + pPropSet[PKG_MNFST_ENCALG].Name = sEncryptionAlgProperty; + pPropSet[PKG_MNFST_ENCALG].Value <<= xEncData->m_nEncAlg; + pPropSet[PKG_MNFST_STARTALG].Name = sStartKeyAlgProperty; + pPropSet[PKG_MNFST_STARTALG].Value <<= xEncData->m_nStartKeyGenID; + if (xEncData->m_oCheckAlg) + { + assert(xEncData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C); + pPropSet[PKG_MNFST_DIGEST].Name = sDigestProperty; + pPropSet[PKG_MNFST_DIGEST].Value <<= m_xBaseEncryptionData->m_aDigest; + pPropSet[PKG_MNFST_DIGESTALG].Name = sDigestAlgProperty; + pPropSet[PKG_MNFST_DIGESTALG].Value <<= *xEncData->m_oCheckAlg; + } + pPropSet[PKG_MNFST_DERKEYSIZE].Name = sDerivedKeySizeProperty; + pPropSet[PKG_MNFST_DERKEYSIZE].Value <<= xEncData->m_nDerivedKeySize; + } + } + } + + // If the entry is already stored in the zip file in the format we + // want for this write...copy it raw + if ( !bUseNonSeekableAccess + && ( m_bRawStream || bTransportOwnEncrStreamAsRaw + || ( IsPackageMember() && !bToBeEncrypted + && ( ( aEntry.nMethod == DEFLATED && bToBeCompressed ) + || ( aEntry.nMethod == STORED && !bToBeCompressed ) ) ) ) ) + { + // If it's a PackageMember, then it's an unbuffered stream and we need + // to get a new version of it as we can't seek backwards. + if ( IsPackageMember() ) + { + xStream = getRawData(); + if ( !xStream.is() ) + { + // Make sure that we actually _got_ a new one ! + bSuccess = false; + return bSuccess; + } + } + + try + { + if ( m_bRawStream ) + xStream->skipBytes( m_nMagicalHackPos ); + + ZipOutputStream::setEntry(pTempEntry); + rZipOut.writeLOC(pTempEntry); + // coverity[leaked_storage] - the entry is provided to the ZipOutputStream that will delete it + pAutoTempEntry.release(); + + uno::Sequence < sal_Int8 > aSeq ( n_ConstBufferSize ); + sal_Int32 nLength; + + do + { + nLength = xStream->readBytes( aSeq, n_ConstBufferSize ); + if (nLength != n_ConstBufferSize) + aSeq.realloc(nLength); + + rZipOut.rawWrite(aSeq); + } + while ( nLength == n_ConstBufferSize ); + + rZipOut.rawCloseEntry(); + } + catch ( ZipException& ) + { + bSuccess = false; + } + catch ( io::IOException& ) + { + bSuccess = false; + } + } + else + { + // This stream is definitely not a raw stream + + // If nonseekable access is used the stream should be at the beginning and + // is useless after the storing. Thus if the storing fails the package should + // be thrown away ( as actually it is done currently )! + // To allow to reuse the package after the error, the optimization must be removed! + + // If it's a PackageMember, then our previous reference held a 'raw' stream + // so we need to re-get it, unencrypted, uncompressed and positioned at the + // beginning of the stream + if ( IsPackageMember() ) + { + xStream = getInputStream(); + if ( !xStream.is() ) + { + // Make sure that we actually _got_ a new one ! + bSuccess = false; + return bSuccess; + } + } + + if ( bToBeCompressed ) + { + pTempEntry->nMethod = DEFLATED; + pTempEntry->nCrc = -1; + pTempEntry->nCompressedSize = pTempEntry->nSize = -1; + } + + uno::Reference< io::XSeekable > xSeek(xStream, uno::UNO_QUERY); + // It's not worth to deflate jpegs to save ~1% in a slow process + // Unfortunately, does not work for streams protected by password + if (xSeek.is() && msMediaType.endsWith("/jpeg") && !m_bToBeEncrypted && !m_bToBeCompressed) + { + ImplSetStoredData(*pTempEntry, xStream); + xSeek->seek(0); + } + + try + { + ZipOutputStream::setEntry(pTempEntry); + // the entry is provided to the ZipOutputStream that will delete it + pAutoTempEntry.release(); + + if (pTempEntry->nMethod == STORED) + { + sal_Int32 nLength; + uno::Sequence< sal_Int8 > aSeq(n_ConstBufferSize); + rZipOut.writeLOC(pTempEntry, bToBeEncrypted); + do + { + nLength = xStream->readBytes(aSeq, n_ConstBufferSize); + if (nLength != n_ConstBufferSize) + aSeq.realloc(nLength); + + rZipOut.rawWrite(aSeq); + } + while ( nLength == n_ConstBufferSize ); + rZipOut.rawCloseEntry(bToBeEncrypted); + } + else + { + // tdf#89236 Encrypting in a background thread does not work + bBackgroundThreadDeflate = !bToBeEncrypted; + // Do not deflate small streams using threads. XSeekable's getLength() + // gives the full size, XInputStream's available() may not be + // the full size, but it appears that at this point it usually is. + sal_Int64 estimatedSize = xSeek.is() ? xSeek->getLength() : xStream->available(); + + if (estimatedSize > 1000000) + { + // Use ThreadDeflater which will split the stream into blocks and compress + // them in threads, but not in background (i.e. writeStream() will block). + // This is suitable for large data. + bBackgroundThreadDeflate = false; + rZipOut.writeLOC(pTempEntry, bToBeEncrypted); + ZipOutputEntryParallel aZipEntry(rZipOut.getStream(), m_xContext, *pTempEntry, this, bToBeEncrypted); + aZipEntry.writeStream(xStream); + rZipOut.rawCloseEntry(bToBeEncrypted); + } + else if (bBackgroundThreadDeflate && estimatedSize > 100000) + { + // tdf#93553 limit to a useful amount of pending tasks. Having way too many + // tasks pending may use a lot of memory. Take number of available + // cores and allow 4-times the amount for having the queue well filled. The + // 2nd parameter is the time to wait between cleanups in 10th of a second. + // Both values may be added to the configuration settings if needed. + static std::size_t nAllowedTasks(comphelper::ThreadPool::getPreferredConcurrency() * 4); //TODO: overflow + rZipOut.reduceScheduledThreadTasksToGivenNumberOrLess(nAllowedTasks); + + // Start a new thread task deflating this zip entry + ZipOutputEntryInThread *pZipEntry = new ZipOutputEntryInThread( + m_xContext, *pTempEntry, this, bToBeEncrypted); + rZipOut.addDeflatingThreadTask( pZipEntry, + pZipEntry->createTask( rZipOut.getThreadTaskTag(), xStream) ); + } + else + { + bBackgroundThreadDeflate = false; + rZipOut.writeLOC(pTempEntry, bToBeEncrypted); + ZipOutputEntry aZipEntry(rZipOut.getStream(), m_xContext, *pTempEntry, this, bToBeEncrypted); + aZipEntry.writeStream(xStream); + rZipOut.rawCloseEntry(bToBeEncrypted); + } + } + } + catch ( ZipException& ) + { + bSuccess = false; + } + catch ( io::IOException& ) + { + bSuccess = false; + } + + if ( bToBeEncrypted ) + { + ::rtl::Reference< EncryptionData > xEncData = GetEncryptionData(); + if ( !xEncData.is() ) + throw uno::RuntimeException(); + + // very confusing: half the encryption properties are + // unconditionally added above and the other half conditionally; + // assert that we have the expected group and not duplicates + assert(std::any_of(aPropSet.begin(), aPropSet.end(), [](auto const& it){ return it.Name == "Salt"; })); + assert(!std::any_of(aPropSet.begin(), aPropSet.end(), [](auto const& it){ return it.Name == sEncryptionAlgProperty; })); + + pPropSet[PKG_MNFST_ENCALG].Name = sEncryptionAlgProperty; + pPropSet[PKG_MNFST_ENCALG].Value <<= xEncData->m_nEncAlg; + pPropSet[PKG_MNFST_STARTALG].Name = sStartKeyAlgProperty; + pPropSet[PKG_MNFST_STARTALG].Value <<= xEncData->m_nStartKeyGenID; + if (xEncData->m_oCheckAlg) + { + assert(xEncData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C); + pPropSet[PKG_MNFST_DIGEST].Name = sDigestProperty; + pPropSet[PKG_MNFST_DIGEST].Value <<= m_xBaseEncryptionData->m_aDigest; + pPropSet[PKG_MNFST_DIGESTALG].Name = sDigestAlgProperty; + pPropSet[PKG_MNFST_DIGESTALG].Value <<= *xEncData->m_oCheckAlg; + } + pPropSet[PKG_MNFST_DERKEYSIZE].Name = sDerivedKeySizeProperty; + pPropSet[PKG_MNFST_DERKEYSIZE].Value <<= xEncData->m_nDerivedKeySize; + + SetIsEncrypted ( true ); + } + } + + if (bSuccess && !bBackgroundThreadDeflate) + successfullyWritten(pTempEntry); + + if ( aPropSet.hasElements() + && ( m_nFormat == embed::StorageFormats::PACKAGE || m_nFormat == embed::StorageFormats::OFOPXML ) ) + rManList.push_back( aPropSet ); + + return bSuccess; +} + +void ZipPackageStream::successfullyWritten( ZipEntry const *pEntry ) +{ + if ( !IsPackageMember() ) + { + if ( m_xStream.is() ) + { + m_xStream->closeInput(); + m_xStream.clear(); + m_bHasSeekable = false; + } + SetPackageMember ( true ); + } + + if ( m_bRawStream ) + { + // the raw stream was integrated and now behaves + // as usual encrypted stream + SetToBeEncrypted( true ); + } + + // Then copy it back afterwards... + aEntry = *pEntry; + + // TODO/LATER: get rid of this hack ( the encrypted stream size property is changed during saving ) + if ( m_bIsEncrypted ) + setSize( m_nOwnStreamOrigSize ); + + aEntry.nOffset *= -1; +} + +void ZipPackageStream::SetPackageMember( bool bNewValue ) +{ + if ( bNewValue ) + { + m_nStreamMode = PACKAGE_STREAM_PACKAGEMEMBER; + m_nMagicalHackPos = 0; + m_nMagicalHackSize = 0; + } + else if ( m_nStreamMode == PACKAGE_STREAM_PACKAGEMEMBER ) + m_nStreamMode = PACKAGE_STREAM_NOTSET; // must be reset +} + +// XActiveDataSink +void SAL_CALL ZipPackageStream::setInputStream( const uno::Reference< io::XInputStream >& aStream ) +{ + // if seekable access is required the wrapping will be done on demand + m_xStream = aStream; + m_nImportedEncryptionAlgorithm = 0; + m_bHasSeekable = false; + SetPackageMember ( false ); + aEntry.nTime = -1; + m_nStreamMode = PACKAGE_STREAM_DETECT; +} + +uno::Reference< io::XInputStream > ZipPackageStream::getRawData() +{ + try + { + if ( IsPackageMember() ) + { + return m_rZipPackage.getZipFile().getRawData( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef(), false/*bUseBufferedStream*/ ); + } + else if ( GetOwnSeekStream().is() ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else + return uno::Reference < io::XInputStream > (); + } + catch ( ZipException & )//rException ) + { + TOOLS_WARN_EXCEPTION( "package", "" ); + return uno::Reference < io::XInputStream > (); + } + catch ( Exception & ) + { + TOOLS_WARN_EXCEPTION( "package", "Exception is thrown during stream wrapping!" ); + return uno::Reference < io::XInputStream > (); + } +} + +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getInputStream() +{ + try + { + if ( IsPackageMember() ) + { + return m_rZipPackage.getZipFile().getInputStream( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + } + else if ( GetOwnSeekStream().is() ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else + return uno::Reference < io::XInputStream > (); + } + catch ( ZipException & )//rException ) + { + TOOLS_WARN_EXCEPTION( "package", "" ); + return uno::Reference < io::XInputStream > (); + } + catch ( const Exception & ) + { + TOOLS_WARN_EXCEPTION( "package", "Exception is thrown during stream wrapping!"); + return uno::Reference < io::XInputStream > (); + } +} + +// XDataSinkEncrSupport +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getDataStream() +{ + // There is no stream attached to this object + if ( m_nStreamMode == PACKAGE_STREAM_NOTSET ) + return uno::Reference< io::XInputStream >(); + + // this method can not be used together with old approach + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + throw packages::zip::ZipIOException(THROW_WHERE ); + + if ( IsPackageMember() ) + { + uno::Reference< io::XInputStream > xResult; + try + { + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(Bugs::None), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + } + catch( const packages::WrongPasswordException& ) + { + // note: due to SHA1 check this fallback is only done for + // * ODF 1.2 files written by OOo < 3.4beta / LO < 3.5 + // * ODF 1.1/OOoXML files written by any version + if ( m_rZipPackage.GetStartKeyGenID() == xml::crypto::DigestID::SHA1 ) + { + SAL_WARN("package", "ZipPackageStream::getDataStream(): SHA1 mismatch, trying fallbacks..."); + try + { // tdf#114939 try with legacy StarOffice SHA1 bug + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(Bugs::WrongSHA1), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + return xResult; + } + catch (const packages::WrongPasswordException&) + { + /* ignore and try next... */ + } + + try + { + // rhbz#1013844 / fdo#47482 workaround for the encrypted + // OpenOffice.org 1.0 documents generated by Libreoffice <= + // 3.6 with the new encryption format and using SHA256, but + // missing a specified startkey of SHA256 + + // force SHA256 and see if that works + m_nImportedStartKeyAlgorithm = xml::crypto::DigestID::SHA256; + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + return xResult; + } + catch (const packages::WrongPasswordException&) + { + // if that didn't work, restore to SHA1 and trundle through the *other* earlier + // bug fix + m_nImportedStartKeyAlgorithm = xml::crypto::DigestID::SHA1; + } + + // workaround for the encrypted documents generated with the old OOo1.x bug. + if ( !m_bUseWinEncoding ) + { + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(Bugs::WinEncodingWrongSHA1), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + m_bUseWinEncoding = true; + } + else + throw; + } + else + throw; + } + return xResult; + } + else if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + return ZipFile::StaticGetDataFromRawStream( m_rZipPackage.GetSharedMutexRef(), m_xContext, GetOwnSeekStream(), GetEncryptionData() ); + else if ( GetOwnSeekStream().is() ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else + return uno::Reference< io::XInputStream >(); +} + +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getRawStream() +{ + // There is no stream attached to this object + if ( m_nStreamMode == PACKAGE_STREAM_NOTSET ) + return uno::Reference< io::XInputStream >(); + + // this method can not be used together with old approach + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + throw packages::zip::ZipIOException(THROW_WHERE ); + + if ( IsPackageMember() ) + { + if ( !m_bIsEncrypted || !GetEncryptionData().is() ) + throw packages::NoEncryptionException(THROW_WHERE ); + + return m_rZipPackage.getZipFile().getWrappedRawStream( aEntry, GetEncryptionData(), msMediaType, m_rZipPackage.GetSharedMutexRef() ); + } + else if ( GetOwnSeekStream().is() ) + { + if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else if ( m_nStreamMode == PACKAGE_STREAM_DATA && m_bToBeEncrypted ) + return TryToGetRawFromDataStream( true ); + } + + throw packages::NoEncryptionException(THROW_WHERE ); +} + +void SAL_CALL ZipPackageStream::setDataStream( const uno::Reference< io::XInputStream >& aStream ) +{ + setInputStream( aStream ); + m_nStreamMode = PACKAGE_STREAM_DATA; +} + +void SAL_CALL ZipPackageStream::setRawStream( const uno::Reference< io::XInputStream >& aStream ) +{ + // wrap the stream in case it is not seekable + uno::Reference< io::XInputStream > xNewStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( aStream, m_xContext ); + uno::Reference< io::XSeekable > xSeek( xNewStream, UNO_QUERY_THROW ); + xSeek->seek( 0 ); + uno::Reference< io::XInputStream > xOldStream = m_xStream; + m_xStream = xNewStream; + if ( !ParsePackageRawStream() ) + { + m_xStream = xOldStream; + throw packages::NoRawFormatException(THROW_WHERE ); + } + + // the raw stream MUST have seekable access + m_bHasSeekable = true; + + SetPackageMember ( false ); + aEntry.nTime = -1; + m_nStreamMode = PACKAGE_STREAM_RAW; +} + +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getPlainRawStream() +{ + // There is no stream attached to this object + if ( m_nStreamMode == PACKAGE_STREAM_NOTSET ) + return uno::Reference< io::XInputStream >(); + + // this method can not be used together with old approach + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + throw packages::zip::ZipIOException(THROW_WHERE ); + + if ( IsPackageMember() ) + { + return m_rZipPackage.getZipFile().getRawData( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + } + else if ( GetOwnSeekStream().is() ) + { + if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + { + // the header should not be returned here + return GetRawEncrStreamNoHeaderCopy(); + } + else if ( m_nStreamMode == PACKAGE_STREAM_DATA ) + return TryToGetRawFromDataStream( false ); + } + + return uno::Reference< io::XInputStream >(); +} + +// XPropertySet +void SAL_CALL ZipPackageStream::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) +{ + if ( aPropertyName == "MediaType" ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE && m_rZipPackage.getFormat() != embed::StorageFormats::OFOPXML ) + throw beans::PropertyVetoException(THROW_WHERE ); + + if ( !(aValue >>= msMediaType) ) + throw IllegalArgumentException(THROW_WHERE "MediaType must be a string!", + uno::Reference< XInterface >(), + 2 ); + + if ( !msMediaType.isEmpty() ) + { + if ( msMediaType.indexOf ( "text" ) != -1 + || msMediaType == "application/vnd.sun.star.oleobject" ) + m_bToBeCompressed = true; + else if ( !m_bCompressedIsSetFromOutside ) + m_bToBeCompressed = false; + } + } + else if ( aPropertyName == "Size" ) + { + if ( !( aValue >>= aEntry.nSize ) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for Size property!", + uno::Reference< XInterface >(), + 2 ); + } + else if ( aPropertyName == "Encrypted" ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE ) + throw beans::PropertyVetoException(THROW_WHERE ); + + bool bEnc = false; + if ( !(aValue >>= bEnc) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for Encrypted property!", + uno::Reference< XInterface >(), + 2 ); + + // In case of new raw stream, the stream must not be encrypted on storing + if ( bEnc && m_nStreamMode == PACKAGE_STREAM_RAW ) + throw IllegalArgumentException(THROW_WHERE "Raw stream can not be encrypted on storing", + uno::Reference< XInterface >(), + 2 ); + + m_bToBeEncrypted = bEnc; + if ( m_bToBeEncrypted && !m_xBaseEncryptionData.is() ) + m_xBaseEncryptionData = new BaseEncryptionData; + + } + else if ( aPropertyName == ENCRYPTION_KEY_PROPERTY ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE ) + throw beans::PropertyVetoException(THROW_WHERE ); + + uno::Sequence< sal_Int8 > aNewKey; + + if ( !( aValue >>= aNewKey ) ) + { + OUString sTempString; + if ( !(aValue >>= sTempString) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for EncryptionKey property!", + uno::Reference< XInterface >(), + 2 ); + + sal_Int32 nPathLength = sTempString.getLength(); + Sequence < sal_Int8 > aSequence ( nPathLength ); + sal_Int8 *pArray = aSequence.getArray(); + const sal_Unicode *pChar = sTempString.getStr(); + for ( sal_Int32 i = 0; i < nPathLength; i++ ) + pArray[i] = static_cast < sal_Int8 > ( pChar[i] ); + aNewKey = aSequence; + } + + if ( aNewKey.hasElements() ) + { + if ( !m_xBaseEncryptionData.is() ) + m_xBaseEncryptionData = new BaseEncryptionData; + + m_aEncryptionKey = aNewKey; + // In case of new raw stream, the stream must not be encrypted on storing + m_bHaveOwnKey = true; + if ( m_nStreamMode != PACKAGE_STREAM_RAW ) + m_bToBeEncrypted = true; + } + else + { + m_bHaveOwnKey = false; + m_aEncryptionKey.realloc( 0 ); + } + + m_aStorageEncryptionKeys.realloc( 0 ); + } + else if ( aPropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE ) + throw beans::PropertyVetoException(THROW_WHERE ); + + uno::Sequence< beans::NamedValue > aKeys; + if ( !( aValue >>= aKeys ) ) + { + throw IllegalArgumentException(THROW_WHERE "Wrong type for StorageEncryptionKeys property!", + uno::Reference< XInterface >(), + 2 ); + } + + if ( aKeys.hasElements() ) + { + if ( !m_xBaseEncryptionData.is() ) + m_xBaseEncryptionData = new BaseEncryptionData; + + m_aStorageEncryptionKeys = aKeys; + + // In case of new raw stream, the stream must not be encrypted on storing + m_bHaveOwnKey = true; + if ( m_nStreamMode != PACKAGE_STREAM_RAW ) + m_bToBeEncrypted = true; + } + else + { + m_bHaveOwnKey = false; + m_aStorageEncryptionKeys.realloc( 0 ); + } + + m_aEncryptionKey.realloc( 0 ); + } + else if ( aPropertyName == "Compressed" ) + { + bool bCompr = false; + + if ( !(aValue >>= bCompr) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for Compressed property!", + uno::Reference< XInterface >(), + 2 ); + + // In case of new raw stream, the stream must not be encrypted on storing + if ( bCompr && m_nStreamMode == PACKAGE_STREAM_RAW ) + throw IllegalArgumentException(THROW_WHERE "Raw stream can not be encrypted on storing", + uno::Reference< XInterface >(), + 2 ); + + m_bToBeCompressed = bCompr; + m_bCompressedIsSetFromOutside = true; + } + else + throw beans::UnknownPropertyException(aPropertyName); +} + +Any SAL_CALL ZipPackageStream::getPropertyValue( const OUString& PropertyName ) +{ + if ( PropertyName == "MediaType" ) + { + return Any(msMediaType); + } + else if ( PropertyName == "Size" ) + { + return Any(aEntry.nSize); + } + else if ( PropertyName == "Encrypted" ) + { + return Any((m_nStreamMode == PACKAGE_STREAM_RAW) || m_bToBeEncrypted); + } + else if ( PropertyName == "WasEncrypted" ) + { + return Any(m_bIsEncrypted); + } + else if ( PropertyName == "Compressed" ) + { + return Any(m_bToBeCompressed); + } + else if ( PropertyName == ENCRYPTION_KEY_PROPERTY ) + { + return Any(m_aEncryptionKey); + } + else if ( PropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY ) + { + return Any(m_aStorageEncryptionKeys); + } + else + throw beans::UnknownPropertyException(PropertyName); +} + +void ZipPackageStream::setSize ( const sal_Int64 nNewSize ) +{ + if ( aEntry.nCompressedSize != nNewSize ) + aEntry.nMethod = DEFLATED; + aEntry.nSize = nNewSize; +} +OUString ZipPackageStream::getImplementationName() +{ + return "ZipPackageStream"; +} + +Sequence< OUString > ZipPackageStream::getSupportedServiceNames() +{ + return { "com.sun.star.packages.PackageStream" }; +} + +sal_Bool SAL_CALL ZipPackageStream::supportsService( OUString const & rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/wrapstreamforshare.cxx b/package/source/zippackage/wrapstreamforshare.cxx new file mode 100644 index 0000000000..250ccb4ba5 --- /dev/null +++ b/package/source/zippackage/wrapstreamforshare.cxx @@ -0,0 +1,152 @@ +/* -*- 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 +#include + +#include +#include +#include + +#include "wrapstreamforshare.hxx" + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +WrapStreamForShare::WrapStreamForShare( uno::Reference< io::XInputStream > xInStream, + rtl::Reference< comphelper::RefCountedMutex > xMutexRef ) +: m_xMutex(std::move( xMutexRef )) +, m_xInStream(std::move( xInStream )) +, m_nCurPos( 0 ) +{ + if ( !m_xMutex.is() || !m_xInStream.is() ) + { + OSL_FAIL( "Wrong initialization of wrapping stream!" ); + throw uno::RuntimeException(THROW_WHERE ); + } + m_xSeekable.set( m_xInStream, uno::UNO_QUERY_THROW ); +} + +WrapStreamForShare::~WrapStreamForShare() +{ +} + +// XInputStream +sal_Int32 SAL_CALL WrapStreamForShare::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + m_xSeekable->seek( m_nCurPos ); + + sal_Int32 nRead = m_xInStream->readBytes( aData, nBytesToRead ); + m_nCurPos += nRead; + + return nRead; +} + +sal_Int32 SAL_CALL WrapStreamForShare::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + m_xSeekable->seek( m_nCurPos ); + + sal_Int32 nRead = m_xInStream->readSomeBytes( aData, nMaxBytesToRead ); + m_nCurPos += nRead; + + return nRead; +} + +void SAL_CALL WrapStreamForShare::skipBytes( sal_Int32 nBytesToSkip ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + m_xSeekable->seek( m_nCurPos ); + + m_xInStream->skipBytes( nBytesToSkip ); + m_nCurPos = m_xSeekable->getPosition(); +} + +sal_Int32 SAL_CALL WrapStreamForShare::available() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + return m_xInStream->available(); +} + +void SAL_CALL WrapStreamForShare::closeInput() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + // the package is the owner so it will close the stream + // m_xInStream->closeInput(); + m_xInStream.clear(); + m_xSeekable.clear(); +} + +// XSeekable +void SAL_CALL WrapStreamForShare::seek( sal_Int64 location ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + // let stream implementation do all the checking + m_xSeekable->seek( location ); + + m_nCurPos = m_xSeekable->getPosition(); +} + +sal_Int64 SAL_CALL WrapStreamForShare::getPosition() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + return m_nCurPos; +} + +sal_Int64 SAL_CALL WrapStreamForShare::getLength() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + return m_xSeekable->getLength(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/wrapstreamforshare.hxx b/package/source/zippackage/wrapstreamforshare.hxx new file mode 100644 index 0000000000..ac8de25884 --- /dev/null +++ b/package/source/zippackage/wrapstreamforshare.hxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_WRAPSTREAMFORSHARE_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_WRAPSTREAMFORSHARE_HXX + +#include +#include +#include +#include +#include + +class WrapStreamForShare final : public cppu::WeakImplHelper < css::io::XInputStream + , css::io::XSeekable > +{ + rtl::Reference< comphelper::RefCountedMutex > m_xMutex; + css::uno::Reference < css::io::XInputStream > m_xInStream; + css::uno::Reference < css::io::XSeekable > m_xSeekable; + + sal_Int64 m_nCurPos; + +public: + WrapStreamForShare( css::uno::Reference< css::io::XInputStream > xInStream, + rtl::Reference< comphelper::RefCountedMutex > xMutexRef ); + virtual ~WrapStreamForShare() override; + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; + + //XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition() override; + virtual sal_Int64 SAL_CALL getLength() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/zipfileaccess.cxx b/package/source/zippackage/zipfileaccess.cxx new file mode 100644 index 0000000000..d693a51cec --- /dev/null +++ b/package/source/zippackage/zipfileaccess.cxx @@ -0,0 +1,479 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ZipPackageSink.hxx" +#include + +#include +#include +#include +#include + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +OZipFileAccess::OZipFileAccess( const uno::Reference< uno::XComponentContext >& rxContext ) +: m_aMutexHolder( new comphelper::RefCountedMutex ) +, m_xContext( rxContext ) +, m_bDisposed( false ) +, m_bOwnContent( false ) +{ + if ( !rxContext.is() ) + throw uno::RuntimeException(THROW_WHERE ); +} + +OZipFileAccess::~OZipFileAccess() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + if ( !m_bDisposed ) + { + try { + // dispose will use refcounting so the further destruction must be avoided + osl_atomic_increment(&m_refCount); + dispose(); + } catch( uno::Exception& ) + {} + } +} + +uno::Sequence< OUString > OZipFileAccess::GetPatternsFromString_Impl( const OUString& aString ) +{ + if ( aString.isEmpty() ) + return uno::Sequence< OUString >(); + + uno::Sequence< OUString > aPattern( 1 ); + auto pPattern = aPattern.getArray(); + sal_Int32 nInd = 0; + + const sal_Unicode* pString = aString.getStr(); + while( *pString ) + { + if ( *pString == '\\' ) + { + pString++; + + if ( *pString == '\\' ) + { + pPattern[nInd] += "\\"; + pString++; + } + else if ( *pString == '*' ) + { + pPattern[nInd] += "*"; + pString++; + } + else + { + OSL_FAIL( "The backslash is not guarded!" ); + pPattern[nInd] += "\\"; + } + } + else if ( *pString == '*' ) + { + aPattern.realloc( ( ++nInd ) + 1 ); + pPattern = aPattern.getArray(); + pString++; + } + else + { + pPattern[nInd] += OUStringChar( *pString ); + pString++; + } + } + + return aPattern; +} + +bool OZipFileAccess::StringGoodForPattern_Impl( std::u16string_view aString, + const uno::Sequence< OUString >& aPattern ) +{ + sal_Int32 nInd = aPattern.getLength() - 1; + if ( nInd < 0 ) + return false; + + if ( nInd == 0 ) + { + if ( aPattern[0].isEmpty() ) + return true; + + return aString == aPattern[0]; + } + + sal_Int32 nBeginInd = aPattern[0].getLength(); + sal_Int32 nEndInd = aString.size() - aPattern[nInd].getLength(); + if ( nEndInd < nBeginInd + || ( nEndInd != sal_Int32(aString.size()) && aString.substr( nEndInd ) != aPattern[nInd] ) + || ( nBeginInd != 0 && aString.substr( 0, nBeginInd ) != aPattern[0] ) ) + return false; + + for ( sal_Int32 nCurInd = aPattern.getLength() - 2; nCurInd > 0; nCurInd-- ) + { + if ( aPattern[nCurInd].isEmpty() ) + continue; + + if ( nEndInd == nBeginInd ) + return false; + + // check that search does not use nEndInd position + size_t nLastInd = aString.substr(0, nEndInd - 1).rfind( aPattern[nCurInd] ); + + if ( nLastInd == std::u16string_view::npos ) + return false; + + if ( sal_Int32(nLastInd) < nBeginInd ) + return false; + + nEndInd = nLastInd; + } + + return true; +} + +// XInitialization +void SAL_CALL OZipFileAccess::initialize( const uno::Sequence< uno::Any >& aArguments ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE ); // initialization is allowed only one time + + if ( !aArguments.hasElements() ) + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + OSL_ENSURE( aArguments.getLength() == 1, "Too many arguments are provided, only the first one will be used!" ); + + OUString aParamURL; + uno::Reference< io::XStream > xStream; + uno::Reference< io::XSeekable > xSeekable; + uno::Sequence aArgs; + + auto openInputStream = [&]() + { + ::ucbhelper::Content aContent( + aParamURL, + uno::Reference< css::ucb::XCommandEnvironment >(), + m_xContext ); + uno::Reference < io::XActiveDataSink > xSink = new ZipPackageSink; + if ( aContent.openStream ( xSink ) ) + { + m_xContentStream = xSink->getInputStream(); + m_bOwnContent = true; + xSeekable.set( m_xContentStream, uno::UNO_QUERY ); + } + }; + + if ( aArguments[0] >>= aParamURL ) + { + openInputStream(); + } + else if ( aArguments[0] >>= xStream ) + { + // a writable stream can implement both XStream & XInputStream + m_xContentStream = xStream->getInputStream(); + xSeekable.set( xStream, uno::UNO_QUERY ); + } + else if ( aArguments[0] >>= m_xContentStream ) + { + xSeekable.set( m_xContentStream, uno::UNO_QUERY ); + } + else if (aArguments[0] >>= aArgs) + { + for (const beans::NamedValue& rArg : std::as_const(aArgs)) + { + if (rArg.Name == "URL") + rArg.Value >>= aParamURL; + } + + if (aParamURL.isEmpty()) + throw lang::IllegalArgumentException( + THROW_WHERE"required argument 'URL' is not given or invalid.", + uno::Reference(), 1); + + openInputStream(); + } + else + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + if ( !m_xContentStream.is() ) + throw io::IOException(THROW_WHERE ); + + if ( !xSeekable.is() ) + { + // TODO: after fwkbugfix02 is integrated a helper class can be used to make the stream seekable + throw io::IOException(THROW_WHERE ); + } + + // TODO: in case xSeekable is implemented on separated XStream implementation a wrapper is required + m_pZipFile.emplace( + m_aMutexHolder, + m_xContentStream, + m_xContext, + true ); +} + +// XNameAccess +uno::Any SAL_CALL OZipFileAccess::getByName( const OUString& aName ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + EntryHash::iterator aIter = m_pZipFile->GetEntryHash().find( aName ); + if ( aIter == m_pZipFile->GetEntryHash().end() ) + throw container::NoSuchElementException(THROW_WHERE ); + + uno::Reference< io::XInputStream > xEntryStream; + try + { + xEntryStream = m_pZipFile->getDataStream((*aIter).second, + ::rtl::Reference< EncryptionData >(), + false, + m_aMutexHolder); + } + catch (const container::NoSuchElementException&) + { + throw; + } + catch (const lang::WrappedTargetException&) + { + throw; + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetException( "This package is unusable!", + getXWeak(), anyEx); + } + + if ( !xEntryStream.is() ) + throw uno::RuntimeException(THROW_WHERE ); + + return uno::Any ( xEntryStream ); +} + +uno::Sequence< OUString > SAL_CALL OZipFileAccess::getElementNames() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + uno::Sequence< OUString > aNames( m_pZipFile->GetEntryHash().size() ); + auto pNames = aNames.getArray(); + sal_Int32 nLen = 0; + + for ( const auto& rEntry : m_pZipFile->GetEntryHash() ) + { + if ( aNames.getLength() < ++nLen ) + { + OSL_FAIL( "The size must be the same!" ); + aNames.realloc( nLen ); + pNames = aNames.getArray(); + } + + pNames[nLen-1] = rEntry.second.sPath; + } + + if ( aNames.getLength() != nLen ) + { + OSL_FAIL( "The size must be the same!" ); + aNames.realloc( nLen ); + } + + return aNames; +} + +sal_Bool SAL_CALL OZipFileAccess::hasByName( const OUString& aName ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + EntryHash::iterator aIter = m_pZipFile->GetEntryHash().find( aName ); + + return ( aIter != m_pZipFile->GetEntryHash().end() ); +} + +uno::Type SAL_CALL OZipFileAccess::getElementType() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + return cppu::UnoType::get(); +} + +sal_Bool SAL_CALL OZipFileAccess::hasElements() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + return ( !m_pZipFile->GetEntryHash().empty() ); +} + +// XZipFileAccess +uno::Reference< io::XInputStream > SAL_CALL OZipFileAccess::getStreamByPattern( const OUString& aPatternString ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw io::NotConnectedException(THROW_WHERE ); + + // Code to compare strings by patterns + uno::Sequence< OUString > aPattern = GetPatternsFromString_Impl( aPatternString ); + + auto aIter = std::find_if(m_pZipFile->GetEntryHash().begin(), m_pZipFile->GetEntryHash().end(), + [&aPattern](const EntryHash::value_type& rEntry) { return StringGoodForPattern_Impl(rEntry.second.sPath, aPattern); }); + if (aIter != m_pZipFile->GetEntryHash().end()) + { + uno::Reference< io::XInputStream > xEntryStream( m_pZipFile->getDataStream( (*aIter).second, + ::rtl::Reference< EncryptionData >(), + false, + m_aMutexHolder ) ); + + if ( !xEntryStream.is() ) + throw uno::RuntimeException(THROW_WHERE ); + return xEntryStream; + } + + throw container::NoSuchElementException(THROW_WHERE ); +} + +// XComponent +void SAL_CALL OZipFileAccess::dispose() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( m_pListenersContainer ) + { + lang::EventObject aSource( getXWeak() ); + m_pListenersContainer->disposeAndClear( aSource ); + m_pListenersContainer.reset(); + } + + m_pZipFile.reset(); + + if ( m_xContentStream.is() && m_bOwnContent ) + try { + m_xContentStream->closeInput(); + } catch( uno::Exception& ) + {} + + m_bDisposed = true; +} + +void SAL_CALL OZipFileAccess::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pListenersContainer ) + m_pListenersContainer.reset( new ::comphelper::OInterfaceContainerHelper3( m_aMutexHolder->GetMutex() ) ); + m_pListenersContainer->addInterface( xListener ); +} + +void SAL_CALL OZipFileAccess::removeEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( m_pListenersContainer ) + m_pListenersContainer->removeInterface( xListener ); +} + +OUString SAL_CALL OZipFileAccess::getImplementationName() +{ + return "com.sun.star.comp.package.zip.ZipFileAccess"; +} + +sal_Bool SAL_CALL OZipFileAccess::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL OZipFileAccess::getSupportedServiceNames() +{ + return { "com.sun.star.packages.zip.ZipFileAccess", + "com.sun.star.comp.packages.zip.ZipFileAccess" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +package_OZipFileAccess_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence const&) +{ + return cppu::acquire(new OZipFileAccess(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3