2011 lines
78 KiB
C++
2011 lines
78 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <ZipPackage.hxx>
|
|
#include "ZipPackageSink.hxx"
|
|
#include <ZipEnumeration.hxx>
|
|
#include <ZipPackageStream.hxx>
|
|
#include <ZipPackageFolder.hxx>
|
|
#include <ZipOutputEntry.hxx>
|
|
#include <ZipOutputStream.hxx>
|
|
#include <ZipPackageBuffer.hxx>
|
|
#include <ZipFile.hxx>
|
|
#include <PackageConstants.hxx>
|
|
#include <com/sun/star/beans/PropertyValue.hpp>
|
|
#include <com/sun/star/packages/zip/ZipConstants.hpp>
|
|
#include <com/sun/star/packages/zip/ZipException.hpp>
|
|
#include <com/sun/star/packages/zip/ZipIOException.hpp>
|
|
#include <com/sun/star/packages/manifest/ManifestReader.hpp>
|
|
#include <com/sun/star/packages/manifest/ManifestWriter.hpp>
|
|
#include <com/sun/star/io/TempFile.hpp>
|
|
#include <com/sun/star/io/XStream.hpp>
|
|
#include <com/sun/star/io/XInputStream.hpp>
|
|
#include <com/sun/star/io/XOutputStream.hpp>
|
|
#include <com/sun/star/io/XTruncate.hpp>
|
|
#include <com/sun/star/io/XSeekable.hpp>
|
|
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
|
|
#include <com/sun/star/container/XNameContainer.hpp>
|
|
#include <officecfg/Office/Common.hxx>
|
|
#include <comphelper/fileurl.hxx>
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <ucbhelper/content.hxx>
|
|
#include <cppuhelper/exc_hlp.hxx>
|
|
#include <com/sun/star/ucb/ContentCreationException.hpp>
|
|
#include <com/sun/star/ucb/TransferInfo.hpp>
|
|
#include <com/sun/star/ucb/NameClash.hpp>
|
|
#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
|
|
#include <com/sun/star/ucb/OpenMode.hpp>
|
|
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
|
|
#include <com/sun/star/io/XActiveDataStreamer.hpp>
|
|
#include <com/sun/star/embed/UseBackupException.hpp>
|
|
#include <com/sun/star/embed/StorageFormats.hpp>
|
|
#include <com/sun/star/beans/NamedValue.hpp>
|
|
#include <com/sun/star/xml/crypto/DigestID.hpp>
|
|
#include <com/sun/star/xml/crypto/KDFID.hpp>
|
|
#include <cppuhelper/implbase.hxx>
|
|
#include <rtl/uri.hxx>
|
|
#include <rtl/random.h>
|
|
#include <o3tl/string_view.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include <sal/log.hxx>
|
|
#include <unotools/streamwrap.hxx>
|
|
#include <unotools/tempfile.hxx>
|
|
#include <com/sun/star/io/XAsyncOutputMonitor.hpp>
|
|
|
|
#include <string_view>
|
|
|
|
#include <comphelper/seekableinput.hxx>
|
|
#include <comphelper/storagehelper.hxx>
|
|
#include <comphelper/ofopxmlhelper.hxx>
|
|
#include <comphelper/documentconstants.hxx>
|
|
#include <comphelper/sequenceashashmap.hxx>
|
|
#include <cppuhelper/supportsservice.hxx>
|
|
#include <comphelper/sequence.hxx>
|
|
#include <comphelper/servicehelper.hxx>
|
|
#include <utility>
|
|
|
|
using namespace osl;
|
|
using namespace cppu;
|
|
using namespace ucbhelper;
|
|
using namespace com::sun::star;
|
|
using namespace com::sun::star::io;
|
|
using namespace com::sun::star::uno;
|
|
using namespace com::sun::star::ucb;
|
|
using namespace com::sun::star::util;
|
|
using namespace com::sun::star::lang;
|
|
using namespace com::sun::star::task;
|
|
using namespace com::sun::star::beans;
|
|
using namespace com::sun::star::packages;
|
|
using namespace com::sun::star::container;
|
|
using namespace com::sun::star::packages::zip;
|
|
using namespace com::sun::star::packages::manifest;
|
|
using namespace com::sun::star::packages::zip::ZipConstants;
|
|
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
#define THROW_WHERE SAL_WHERE
|
|
#else
|
|
#define THROW_WHERE ""
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
class ActiveDataStreamer : public ::cppu::WeakImplHelper< XActiveDataStreamer >
|
|
{
|
|
uno::Reference< XStream > mStream;
|
|
public:
|
|
|
|
virtual uno::Reference< XStream > SAL_CALL getStream() override
|
|
{ return mStream; }
|
|
|
|
virtual void SAL_CALL setStream( const uno::Reference< XStream >& stream ) override
|
|
{ mStream = stream; }
|
|
};
|
|
|
|
class DummyInputStream : public ::cppu::WeakImplHelper< XInputStream >
|
|
{
|
|
virtual sal_Int32 SAL_CALL readBytes( uno::Sequence< sal_Int8 >&, sal_Int32 ) override
|
|
{ return 0; }
|
|
|
|
virtual sal_Int32 SAL_CALL readSomeBytes( uno::Sequence< sal_Int8 >&, sal_Int32 ) override
|
|
{ return 0; }
|
|
|
|
virtual void SAL_CALL skipBytes( sal_Int32 ) override
|
|
{}
|
|
|
|
virtual sal_Int32 SAL_CALL available() override
|
|
{ return 0; }
|
|
|
|
virtual void SAL_CALL closeInput() override
|
|
{}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
sal_Int32 GetDefaultDerivedKeySize(sal_Int32 const nCipherID)
|
|
{
|
|
switch (nCipherID)
|
|
{
|
|
case css::xml::crypto::CipherID::BLOWFISH_CFB_8:
|
|
return 16;
|
|
case css::xml::crypto::CipherID::AES_CBC_W3C_PADDING:
|
|
case css::xml::crypto::CipherID::AES_GCM_W3C:
|
|
return 32;
|
|
default:
|
|
O3TL_UNREACHABLE;
|
|
}
|
|
}
|
|
|
|
ZipPackage::ZipPackage ( uno::Reference < XComponentContext > xContext )
|
|
: m_aMutexHolder( new comphelper::RefCountedMutex )
|
|
, m_nStartKeyGenerationID( xml::crypto::DigestID::SHA1 )
|
|
, m_oChecksumDigestID( xml::crypto::DigestID::SHA1_1K )
|
|
, m_nKeyDerivationFunctionID(xml::crypto::KDFID::PBKDF2)
|
|
, m_nCommonEncryptionID( xml::crypto::CipherID::BLOWFISH_CFB_8 )
|
|
, m_bHasEncryptedEntries ( false )
|
|
, m_bHasNonEncryptedEntries ( false )
|
|
, m_bInconsistent ( false )
|
|
, m_bForceRecovery ( false )
|
|
, m_bMediaTypeFallbackUsed ( false )
|
|
, m_nFormat( embed::StorageFormats::PACKAGE ) // package is the default format
|
|
, m_bAllowRemoveOnInsert( true )
|
|
, m_eMode ( e_IMode_None )
|
|
, m_xContext(std::move( xContext ))
|
|
{
|
|
m_xRootFolder = new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
|
|
}
|
|
|
|
ZipPackage::~ZipPackage()
|
|
{
|
|
}
|
|
|
|
bool ZipPackage::isLocalFile() const
|
|
{
|
|
return comphelper::isFileUrl(m_aURL);
|
|
}
|
|
|
|
// note: don't check for StorageFormats::ZIP, it breaks signing!
|
|
void ZipPackage::checkZipEntriesWithDD()
|
|
{
|
|
if (!m_bForceRecovery)
|
|
{
|
|
ZipEnumeration entries{m_pZipFile->entries()};
|
|
while (entries.hasMoreElements())
|
|
{
|
|
ZipEntry const& rEntry{*entries.nextElement()};
|
|
if ((rEntry.nFlag & 0x08) != 0 && rEntry.nMethod == STORED)
|
|
{
|
|
uno::Reference<XPropertySet> xStream;
|
|
getByHierarchicalName(rEntry.sPath) >>= xStream;
|
|
uno::Reference<XServiceInfo> const xStreamSI{xStream, uno::UNO_QUERY_THROW};
|
|
if (!xStreamSI->supportsService("com.sun.star.packages.PackageStream"))
|
|
{
|
|
SAL_INFO("package", "entry STORED with data descriptor is folder: \"" << rEntry.sPath << "\"");
|
|
throw ZipIOException(
|
|
THROW_WHERE
|
|
"entry STORED with data descriptor is folder");
|
|
}
|
|
if (!xStream->getPropertyValue("WasEncrypted").get<bool>())
|
|
{
|
|
SAL_INFO("package", "entry STORED with data descriptor but not encrypted: \"" << rEntry.sPath << "\"");
|
|
throw ZipIOException(
|
|
THROW_WHERE
|
|
"entry STORED with data descriptor but not encrypted");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ZipPackage::parseManifest()
|
|
{
|
|
if ( m_nFormat != embed::StorageFormats::PACKAGE )
|
|
return;
|
|
|
|
bool bManifestParsed = false;
|
|
::std::optional<OUString> oFirstVersion;
|
|
static constexpr OUString sMeta (u"META-INF"_ustr);
|
|
if ( m_xRootFolder->hasByName( sMeta ) )
|
|
{
|
|
try {
|
|
static constexpr OUString sManifest (u"manifest.xml"_ustr);
|
|
Any aAny = m_xRootFolder->getByName( sMeta );
|
|
uno::Reference< XNameContainer > xMetaInfFolder;
|
|
aAny >>= xMetaInfFolder;
|
|
if ( xMetaInfFolder.is() && xMetaInfFolder->hasByName( sManifest ) )
|
|
{
|
|
uno::Reference < XActiveDataSink > xSink;
|
|
aAny = xMetaInfFolder->getByName( sManifest );
|
|
aAny >>= xSink;
|
|
if ( xSink.is() )
|
|
{
|
|
uno::Reference < XManifestReader > xReader = ManifestReader::create( m_xContext );
|
|
|
|
const uno::Sequence < uno::Sequence < PropertyValue > > aManifestSequence = xReader->readManifestSequence ( xSink->getInputStream() );
|
|
const Any *pKeyInfo = nullptr;
|
|
|
|
for ( const uno::Sequence<PropertyValue>& rSequence : aManifestSequence )
|
|
{
|
|
OUString sPath, sMediaType, sVersion;
|
|
const Any *pSalt = nullptr, *pVector = nullptr, *pCount = nullptr, *pSize = nullptr, *pDigest = nullptr, *pDigestAlg = nullptr, *pEncryptionAlg = nullptr, *pStartKeyAlg = nullptr, *pDerivedKeySize = nullptr;
|
|
uno::Any const* pKDF = nullptr;
|
|
uno::Any const* pArgon2Args = nullptr;
|
|
for ( const PropertyValue& rValue : rSequence )
|
|
{
|
|
if ( rValue.Name == u"FullPath" )
|
|
rValue.Value >>= sPath;
|
|
else if ( rValue.Name == u"Version" )
|
|
{
|
|
rValue.Value >>= sVersion;
|
|
if (!oFirstVersion)
|
|
{
|
|
oFirstVersion.emplace(sVersion);
|
|
}
|
|
}
|
|
else if ( rValue.Name == u"MediaType" )
|
|
rValue.Value >>= sMediaType;
|
|
else if ( rValue.Name == u"Salt" )
|
|
pSalt = &( rValue.Value );
|
|
else if ( rValue.Name == u"InitialisationVector" )
|
|
pVector = &( rValue.Value );
|
|
else if ( rValue.Name == u"IterationCount" )
|
|
pCount = &( rValue.Value );
|
|
else if ( rValue.Name == u"Size" )
|
|
pSize = &( rValue.Value );
|
|
else if ( rValue.Name == u"Digest" )
|
|
pDigest = &( rValue.Value );
|
|
else if ( rValue.Name == u"DigestAlgorithm" )
|
|
pDigestAlg = &( rValue.Value );
|
|
else if ( rValue.Name == u"EncryptionAlgorithm" )
|
|
pEncryptionAlg = &( rValue.Value );
|
|
else if ( rValue.Name == u"StartKeyAlgorithm" )
|
|
pStartKeyAlg = &( rValue.Value );
|
|
else if ( rValue.Name == u"DerivedKeySize" )
|
|
pDerivedKeySize = &( rValue.Value );
|
|
else if ( rValue.Name == u"KeyInfo" )
|
|
pKeyInfo = &( rValue.Value );
|
|
else if (rValue.Name == u"KeyDerivationFunction") {
|
|
pKDF = &rValue.Value;
|
|
} else if (rValue.Name == u"Argon2Args") {
|
|
pArgon2Args = &rValue.Value;
|
|
}
|
|
}
|
|
|
|
if ( !sPath.isEmpty() && hasByHierarchicalName ( sPath ) )
|
|
{
|
|
aAny = getByHierarchicalName( sPath );
|
|
uno::Reference < XInterface > xTmp;
|
|
aAny >>= xTmp;
|
|
if (auto pFolder = dynamic_cast<ZipPackageFolder*>(xTmp.get()))
|
|
{
|
|
pFolder->SetMediaType ( sMediaType );
|
|
pFolder->SetVersion ( sVersion );
|
|
}
|
|
else if (auto pStream = dynamic_cast<ZipPackageStream*>(xTmp.get()))
|
|
{
|
|
pStream->SetMediaType ( sMediaType );
|
|
pStream->SetFromManifest( true );
|
|
|
|
if (pKeyInfo
|
|
&& pVector && pSize && pEncryptionAlg
|
|
&& pKDF && pKDF->has<sal_Int32>() && pKDF->get<sal_Int32>() == xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P
|
|
&& ((pEncryptionAlg->has<sal_Int32>()
|
|
&& pEncryptionAlg->get<sal_Int32>() == xml::crypto::CipherID::AES_GCM_W3C)
|
|
|| (pDigest && pDigestAlg)))
|
|
{
|
|
uno::Sequence < sal_Int8 > aSequence;
|
|
sal_Int64 nSize = 0;
|
|
::std::optional<sal_Int32> oDigestAlg;
|
|
sal_Int32 nEncryptionAlg = 0;
|
|
|
|
pStream->SetToBeEncrypted ( true );
|
|
|
|
*pVector >>= aSequence;
|
|
pStream->setInitialisationVector ( aSequence );
|
|
|
|
*pSize >>= nSize;
|
|
pStream->setSize ( nSize );
|
|
|
|
if (pDigest && pDigestAlg)
|
|
{
|
|
*pDigest >>= aSequence;
|
|
pStream->setDigest(aSequence);
|
|
|
|
assert(pDigestAlg->has<sal_Int32>());
|
|
oDigestAlg.emplace(pDigestAlg->get<sal_Int32>());
|
|
}
|
|
|
|
*pEncryptionAlg >>= nEncryptionAlg;
|
|
|
|
*pKeyInfo >>= m_aGpgProps;
|
|
|
|
pStream->SetToBeCompressed ( true );
|
|
pStream->SetToBeEncrypted ( true );
|
|
pStream->SetIsEncrypted ( true );
|
|
pStream->setIterationCount(::std::optional<sal_Int32>());
|
|
pStream->setArgon2Args(::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>>());
|
|
|
|
// clamp to default SHA256 start key magic value,
|
|
// c.f. ZipPackageStream::GetEncryptionKey()
|
|
// trying to get key value from properties
|
|
const sal_Int32 nStartKeyAlg = xml::crypto::DigestID::SHA256;
|
|
|
|
pStream->SetImportedAlgorithms({
|
|
.nImportedStartKeyAlgorithm = nStartKeyAlg,
|
|
.nImportedEncryptionAlgorithm = nEncryptionAlg,
|
|
.oImportedChecksumAlgorithm = oDigestAlg,
|
|
// note m_nCommonEncryptionID is not inited yet here
|
|
.nImportedDerivedKeySize = ::GetDefaultDerivedKeySize(nEncryptionAlg),
|
|
});
|
|
|
|
if (!m_bHasEncryptedEntries
|
|
&& (pStream->getName() == "content.xml"
|
|
|| pStream->getName() == "encrypted-package"))
|
|
{
|
|
m_bHasEncryptedEntries = true;
|
|
m_oChecksumDigestID = oDigestAlg;
|
|
m_nKeyDerivationFunctionID = xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P;
|
|
m_nCommonEncryptionID = nEncryptionAlg;
|
|
m_nStartKeyGenerationID = nStartKeyAlg;
|
|
}
|
|
}
|
|
else if (pSalt
|
|
&& pVector && pSize && pEncryptionAlg
|
|
&& pKDF && pKDF->has<sal_Int32>()
|
|
&& ((pKDF->get<sal_Int32>() == xml::crypto::KDFID::PBKDF2 && pCount)
|
|
|| (pKDF->get<sal_Int32>() == xml::crypto::KDFID::Argon2id && pArgon2Args))
|
|
&& ((pEncryptionAlg->has<sal_Int32>()
|
|
&& pEncryptionAlg->get<sal_Int32>() == xml::crypto::CipherID::AES_GCM_W3C)
|
|
|| (pDigest && pDigestAlg)))
|
|
|
|
{
|
|
uno::Sequence < sal_Int8 > aSequence;
|
|
sal_Int64 nSize = 0;
|
|
::std::optional<sal_Int32> oDigestAlg;
|
|
sal_Int32 nKDF = 0;
|
|
sal_Int32 nEncryptionAlg = 0;
|
|
sal_Int32 nCount = 0;
|
|
sal_Int32 nDerivedKeySize = 16, nStartKeyAlg = xml::crypto::DigestID::SHA1;
|
|
|
|
pStream->SetToBeEncrypted ( true );
|
|
|
|
*pSalt >>= aSequence;
|
|
pStream->setSalt ( aSequence );
|
|
|
|
*pVector >>= aSequence;
|
|
pStream->setInitialisationVector ( aSequence );
|
|
|
|
*pKDF >>= nKDF;
|
|
|
|
if (pCount)
|
|
{
|
|
*pCount >>= nCount;
|
|
pStream->setIterationCount(::std::optional<sal_Int32>(nCount));
|
|
}
|
|
|
|
if (pArgon2Args)
|
|
{
|
|
uno::Sequence<sal_Int32> args;
|
|
*pArgon2Args >>= args;
|
|
assert(args.getLength() == 3);
|
|
::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgs;
|
|
oArgs.emplace(args[0], args[1], args[2]);
|
|
pStream->setArgon2Args(oArgs);
|
|
}
|
|
|
|
*pSize >>= nSize;
|
|
pStream->setSize ( nSize );
|
|
|
|
if (pDigest && pDigestAlg)
|
|
{
|
|
*pDigest >>= aSequence;
|
|
pStream->setDigest(aSequence);
|
|
|
|
assert(pDigestAlg->has<sal_Int32>());
|
|
oDigestAlg.emplace(pDigestAlg->get<sal_Int32>());
|
|
}
|
|
|
|
*pEncryptionAlg >>= nEncryptionAlg;
|
|
|
|
if ( pDerivedKeySize )
|
|
*pDerivedKeySize >>= nDerivedKeySize;
|
|
|
|
if ( pStartKeyAlg )
|
|
*pStartKeyAlg >>= nStartKeyAlg;
|
|
|
|
pStream->SetImportedAlgorithms({
|
|
.nImportedStartKeyAlgorithm = nStartKeyAlg,
|
|
.nImportedEncryptionAlgorithm = nEncryptionAlg,
|
|
.oImportedChecksumAlgorithm = oDigestAlg,
|
|
.nImportedDerivedKeySize = nDerivedKeySize,
|
|
});
|
|
|
|
pStream->SetToBeCompressed ( true );
|
|
pStream->SetToBeEncrypted ( true );
|
|
pStream->SetIsEncrypted ( true );
|
|
if (!m_bHasEncryptedEntries
|
|
&& (pStream->getName() == "content.xml"
|
|
|| pStream->getName() == "encrypted-package"))
|
|
{
|
|
m_bHasEncryptedEntries = true;
|
|
m_nStartKeyGenerationID = nStartKeyAlg;
|
|
m_nKeyDerivationFunctionID = nKDF;
|
|
m_oChecksumDigestID = oDigestAlg;
|
|
m_nCommonEncryptionID = nEncryptionAlg;
|
|
}
|
|
}
|
|
else
|
|
m_bHasNonEncryptedEntries = true;
|
|
}
|
|
else
|
|
throw ZipIOException(THROW_WHERE "Wrong content");
|
|
}
|
|
}
|
|
|
|
bManifestParsed = true;
|
|
}
|
|
|
|
checkZipEntriesWithDD(); // check before removing entries!
|
|
|
|
// now hide the manifest.xml file from user
|
|
xMetaInfFolder->removeByName( sManifest );
|
|
}
|
|
}
|
|
catch( Exception& )
|
|
{
|
|
if ( !m_bForceRecovery )
|
|
throw;
|
|
}
|
|
}
|
|
|
|
if ( !bManifestParsed && !m_bForceRecovery )
|
|
throw ZipIOException(
|
|
THROW_WHERE "Could not parse manifest.xml" );
|
|
|
|
static constexpr OUString sMimetype (u"mimetype"_ustr);
|
|
if ( m_xRootFolder->hasByName( sMimetype ) )
|
|
{
|
|
// get mediatype from the "mimetype" stream
|
|
OUString aPackageMediatype;
|
|
uno::Reference < io::XActiveDataSink > xMimeSink;
|
|
m_xRootFolder->getByName( sMimetype ) >>= xMimeSink;
|
|
if ( xMimeSink.is() )
|
|
{
|
|
uno::Reference< io::XInputStream > xMimeInStream = xMimeSink->getInputStream();
|
|
if ( xMimeInStream.is() )
|
|
{
|
|
// Mediatypes longer than 1024 symbols should not appear here
|
|
uno::Sequence< sal_Int8 > aData( 1024 );
|
|
sal_Int32 nRead = xMimeInStream->readBytes( aData, 1024 );
|
|
if ( nRead > aData.getLength() )
|
|
nRead = aData.getLength();
|
|
|
|
if ( nRead )
|
|
aPackageMediatype = OUString( reinterpret_cast<char const *>(aData.getConstArray()), nRead, RTL_TEXTENCODING_ASCII_US );
|
|
}
|
|
}
|
|
|
|
if (!bManifestParsed || m_xRootFolder->GetMediaType().isEmpty())
|
|
{
|
|
// the manifest.xml could not be successfully parsed, this is an inconsistent package
|
|
if ( aPackageMediatype.startsWith("application/vnd.") )
|
|
{
|
|
// accept only types that look similar to own mediatypes
|
|
m_xRootFolder->SetMediaType( aPackageMediatype );
|
|
// also set version explicitly
|
|
if (oFirstVersion && m_xRootFolder->GetVersion().isEmpty())
|
|
{
|
|
m_xRootFolder->SetVersion(*oFirstVersion);
|
|
}
|
|
// if there is an encrypted inner package, there is no root
|
|
// document, because instead there is a package, and it is not
|
|
// an error
|
|
if (!m_xRootFolder->hasByName(u"encrypted-package"_ustr))
|
|
{
|
|
m_bMediaTypeFallbackUsed = true;
|
|
}
|
|
}
|
|
}
|
|
else if ( !m_bForceRecovery )
|
|
{
|
|
// the mimetype stream should contain the same information as manifest.xml
|
|
OUString const mediaTypeXML(m_xRootFolder->hasByName(u"encrypted-package"_ustr)
|
|
? m_xRootFolder->doGetByName(u"encrypted-package"_ustr).xPackageEntry->GetMediaType()
|
|
: m_xRootFolder->GetMediaType());
|
|
if (mediaTypeXML != aPackageMediatype)
|
|
{
|
|
throw ZipIOException(
|
|
THROW_WHERE
|
|
"mimetype conflicts with manifest.xml, \""
|
|
+ mediaTypeXML + "\" vs. \""
|
|
+ aPackageMediatype + "\"" );
|
|
}
|
|
}
|
|
|
|
m_xRootFolder->removeByName( sMimetype );
|
|
}
|
|
|
|
m_bInconsistent = m_xRootFolder->LookForUnexpectedODF12Streams(
|
|
std::u16string_view(), m_xRootFolder->hasByName(u"encrypted-package"_ustr));
|
|
|
|
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<ZipPackageStream*>(xIterTmp.get()))
|
|
{
|
|
// this is a package stream, in OFOPXML format only streams can have mediatype
|
|
pStream->SetMediaType( rPair.Second );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_xRootFolder->removeByName( aContentTypes );
|
|
}
|
|
catch( uno::Exception& )
|
|
{
|
|
if ( !m_bForceRecovery )
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void ZipPackage::getZipFileContents()
|
|
{
|
|
ZipEnumeration aEnum = m_pZipFile->entries();
|
|
OUString sDirName;
|
|
|
|
while (aEnum.hasMoreElements())
|
|
{
|
|
ZipPackageFolder* pCurrent = m_xRootFolder.get();
|
|
const ZipEntry & rEntry = *aEnum.nextElement();
|
|
OUString rName = rEntry.sPath;
|
|
|
|
if ( m_bForceRecovery )
|
|
{
|
|
// the PKZIP Application note version 6.2 does not allows to use '\' as separator
|
|
// unfortunately it is used by some implementations, so we have to support it in recovery mode
|
|
rName = rName.replace( '\\', '/' );
|
|
}
|
|
|
|
sal_Int32 nStreamIndex = rName.lastIndexOf ( '/' );
|
|
if ( nStreamIndex != -1 )
|
|
{
|
|
sDirName = rName.copy ( 0, nStreamIndex );
|
|
FolderHash::iterator aIter = m_aRecent.find ( sDirName );
|
|
if ( aIter != m_aRecent.end() )
|
|
pCurrent = ( *aIter ).second;
|
|
}
|
|
|
|
if ( pCurrent == m_xRootFolder.get() )
|
|
{
|
|
sal_Int32 nIndex;
|
|
sal_Int32 nOldIndex = 0;
|
|
while ( ( nIndex = rName.indexOf( '/', nOldIndex ) ) != -1 )
|
|
{
|
|
OUString sTemp = rName.copy ( nOldIndex, nIndex - nOldIndex );
|
|
if ( nIndex == nOldIndex )
|
|
break;
|
|
if ( !pCurrent->hasByName( sTemp ) )
|
|
{
|
|
rtl::Reference<ZipPackageFolder> pPkgFolder = new ZipPackageFolder(m_xContext, m_nFormat, m_bAllowRemoveOnInsert);
|
|
try {
|
|
pPkgFolder->setName( sTemp );
|
|
} catch (uno::RuntimeException const& e) {
|
|
throw css::packages::zip::ZipIOException(e.Message);
|
|
}
|
|
pPkgFolder->doSetParent( pCurrent );
|
|
pCurrent = pPkgFolder.get();
|
|
}
|
|
else
|
|
{
|
|
ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp);
|
|
if (!rInfo.bFolder)
|
|
throw css::packages::zip::ZipIOException(u"Bad Zip File, stream as folder"_ustr);
|
|
pCurrent = rInfo.pFolder;
|
|
}
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
if ( nStreamIndex != -1 && !sDirName.isEmpty() )
|
|
m_aRecent [ sDirName ] = pCurrent;
|
|
}
|
|
if ( rName.getLength() -1 != nStreamIndex )
|
|
{
|
|
nStreamIndex++;
|
|
OUString sTemp = rName.copy( nStreamIndex );
|
|
|
|
if (!pCurrent->hasByName(sTemp))
|
|
{
|
|
rtl::Reference<ZipPackageStream> pPkgStream = new ZipPackageStream(*this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert);
|
|
pPkgStream->SetPackageMember(true);
|
|
pPkgStream->setZipEntryOnLoading(rEntry);
|
|
pPkgStream->setName(sTemp);
|
|
pPkgStream->doSetParent(pCurrent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_nFormat == embed::StorageFormats::PACKAGE )
|
|
parseManifest();
|
|
else if ( m_nFormat == embed::StorageFormats::OFOPXML )
|
|
{
|
|
parseContentType();
|
|
checkZipEntriesWithDD();
|
|
}
|
|
}
|
|
|
|
void SAL_CALL ZipPackage::initialize( const uno::Sequence< Any >& aArguments )
|
|
{
|
|
beans::NamedValue aNamedValue;
|
|
|
|
if ( !aArguments.hasElements() )
|
|
return;
|
|
|
|
bool bHaveZipFile = true;
|
|
|
|
for( const auto& rArgument : aArguments )
|
|
{
|
|
OUString aParamUrl;
|
|
if ( rArgument >>= aParamUrl )
|
|
{
|
|
m_eMode = e_IMode_URL;
|
|
try
|
|
{
|
|
sal_Int32 nParam = aParamUrl.indexOf( '?' );
|
|
if ( nParam >= 0 )
|
|
{
|
|
m_aURL = aParamUrl.copy( 0, nParam );
|
|
std::u16string_view aParam = aParamUrl.subView( nParam + 1 );
|
|
|
|
sal_Int32 nIndex = 0;
|
|
do
|
|
{
|
|
std::u16string_view aCommand = o3tl::getToken(aParam, 0, '&', nIndex );
|
|
if ( aCommand == u"repairpackage" )
|
|
{
|
|
m_bForceRecovery = true;
|
|
break;
|
|
}
|
|
else if ( aCommand == u"purezip" )
|
|
{
|
|
m_nFormat = embed::StorageFormats::ZIP;
|
|
m_xRootFolder->setPackageFormat_Impl( m_nFormat );
|
|
break;
|
|
}
|
|
else if ( aCommand == u"ofopxml" )
|
|
{
|
|
m_nFormat = embed::StorageFormats::OFOPXML;
|
|
m_xRootFolder->setPackageFormat_Impl( m_nFormat );
|
|
break;
|
|
}
|
|
}
|
|
while ( nIndex >= 0 );
|
|
}
|
|
else
|
|
m_aURL = aParamUrl;
|
|
|
|
Content aContent(
|
|
m_aURL, uno::Reference< XCommandEnvironment >(),
|
|
m_xContext );
|
|
Any aAny = aContent.getPropertyValue(u"Size"_ustr);
|
|
sal_uInt64 aSize = 0;
|
|
// kind of optimization: treat empty files as nonexistent files
|
|
// and write to such files directly. Note that "Size" property is optional.
|
|
bool bHasSizeProperty = aAny >>= aSize;
|
|
if( !bHasSizeProperty || aSize )
|
|
{
|
|
uno::Reference < XActiveDataSink > xSink = new ZipPackageSink;
|
|
if ( aContent.openStream ( xSink ) )
|
|
m_xContentStream = xSink->getInputStream();
|
|
}
|
|
else
|
|
bHaveZipFile = false;
|
|
}
|
|
catch ( css::uno::Exception& )
|
|
{
|
|
// Exception derived from uno::Exception thrown. This probably
|
|
// means the file doesn't exist...we'll create it at
|
|
// commitChanges time
|
|
bHaveZipFile = false;
|
|
}
|
|
}
|
|
else if ( rArgument >>= m_xStream )
|
|
{
|
|
// a writable stream can implement both XStream & XInputStream
|
|
m_eMode = e_IMode_XStream;
|
|
m_xContentStream = m_xStream->getInputStream();
|
|
}
|
|
else if ( rArgument >>= m_xContentStream )
|
|
{
|
|
m_eMode = e_IMode_XInputStream;
|
|
}
|
|
else if ( rArgument >>= aNamedValue )
|
|
{
|
|
if ( aNamedValue.Name == "RepairPackage" )
|
|
aNamedValue.Value >>= m_bForceRecovery;
|
|
else if ( aNamedValue.Name == "PackageFormat" )
|
|
{
|
|
// setting this argument to true means Package format
|
|
// setting it to false means plain Zip format
|
|
|
|
bool bPackFormat = true;
|
|
aNamedValue.Value >>= bPackFormat;
|
|
if ( !bPackFormat )
|
|
m_nFormat = embed::StorageFormats::ZIP;
|
|
|
|
m_xRootFolder->setPackageFormat_Impl( m_nFormat );
|
|
}
|
|
else if ( aNamedValue.Name == "StorageFormat" )
|
|
{
|
|
OUString aFormatName;
|
|
sal_Int32 nFormatID = 0;
|
|
if ( aNamedValue.Value >>= aFormatName )
|
|
{
|
|
if ( aFormatName == PACKAGE_STORAGE_FORMAT_STRING )
|
|
m_nFormat = embed::StorageFormats::PACKAGE;
|
|
else if ( aFormatName == ZIP_STORAGE_FORMAT_STRING )
|
|
m_nFormat = embed::StorageFormats::ZIP;
|
|
else if ( aFormatName == OFOPXML_STORAGE_FORMAT_STRING )
|
|
m_nFormat = embed::StorageFormats::OFOPXML;
|
|
else
|
|
throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 );
|
|
}
|
|
else if ( aNamedValue.Value >>= nFormatID )
|
|
{
|
|
if (nFormatID != embed::StorageFormats::PACKAGE
|
|
&& nFormatID != embed::StorageFormats::ZIP
|
|
&& nFormatID != embed::StorageFormats::OFOPXML)
|
|
throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 );
|
|
|
|
m_nFormat = nFormatID;
|
|
}
|
|
else
|
|
throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 );
|
|
|
|
m_xRootFolder->setPackageFormat_Impl( m_nFormat );
|
|
}
|
|
else if ( aNamedValue.Name == "AllowRemoveOnInsert" )
|
|
{
|
|
aNamedValue.Value >>= m_bAllowRemoveOnInsert;
|
|
m_xRootFolder->setRemoveOnInsertMode_Impl( m_bAllowRemoveOnInsert );
|
|
}
|
|
else if (aNamedValue.Name == "NoFileSync")
|
|
aNamedValue.Value >>= m_bDisableFileSync;
|
|
|
|
// for now the progress handler is not used, probably it will never be
|
|
// if ( aNamedValue.Name == "ProgressHandler" )
|
|
}
|
|
else
|
|
{
|
|
// The URL is not acceptable
|
|
throw css::uno::Exception (THROW_WHERE "Bad arguments.",
|
|
getXWeak() );
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if ( m_xContentStream.is() )
|
|
{
|
|
// the stream must be seekable, if it is not it will be wrapped
|
|
m_xContentStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( m_xContentStream, m_xContext );
|
|
m_xContentSeek.set( m_xContentStream, UNO_QUERY_THROW );
|
|
if ( !m_xContentSeek->getLength() )
|
|
bHaveZipFile = false;
|
|
}
|
|
else
|
|
bHaveZipFile = false;
|
|
}
|
|
catch ( css::uno::Exception& )
|
|
{
|
|
// Exception derived from uno::Exception thrown. This probably
|
|
// means the file doesn't exist...we'll create it at
|
|
// commitChanges time
|
|
bHaveZipFile = false;
|
|
}
|
|
if ( !bHaveZipFile )
|
|
return;
|
|
|
|
bool bBadZipFile = false;
|
|
OUString message;
|
|
try
|
|
{
|
|
m_pZipFile.emplace(m_aMutexHolder, m_xContentStream, m_xContext, true,
|
|
m_bForceRecovery,
|
|
m_nFormat == embed::StorageFormats::ZIP
|
|
? ZipFile::Checks::Default
|
|
: m_nFormat == embed::StorageFormats::OFOPXML
|
|
? ZipFile::Checks::CheckInsensitive
|
|
: ZipFile::Checks::TryCheckInsensitive);
|
|
getZipFileContents();
|
|
}
|
|
catch ( IOException & e )
|
|
{
|
|
bBadZipFile = true;
|
|
message = "IOException: " + e.Message;
|
|
}
|
|
catch ( ZipException & e )
|
|
{
|
|
bBadZipFile = true;
|
|
message = "ZipException: " + e.Message;
|
|
}
|
|
catch ( Exception & )
|
|
{
|
|
m_pZipFile.reset();
|
|
throw;
|
|
}
|
|
|
|
if ( bBadZipFile )
|
|
{
|
|
// clean up the memory, and tell the UCB about the error
|
|
m_pZipFile.reset();
|
|
|
|
throw css::packages::zip::ZipIOException (
|
|
THROW_WHERE "Bad Zip File, " + message,
|
|
getXWeak() );
|
|
}
|
|
}
|
|
|
|
Any SAL_CALL ZipPackage::getByHierarchicalName( const OUString& aName )
|
|
{
|
|
OUString sDirName;
|
|
sal_Int32 nOldIndex, nStreamIndex;
|
|
|
|
if (aName == "/")
|
|
// root directory.
|
|
return Any ( uno::Reference( cppu::getXWeak(m_xRootFolder.get()) ) );
|
|
|
|
sal_Int32 nIndex = aName.getLength();
|
|
|
|
nStreamIndex = aName.lastIndexOf ( '/' );
|
|
bool bFolder = nStreamIndex == nIndex-1; // last character is '/'.
|
|
|
|
if ( nStreamIndex != -1 )
|
|
{
|
|
// The name contains '/'.
|
|
sDirName = aName.copy ( 0, nStreamIndex );
|
|
FolderHash::iterator aIter = m_aRecent.find ( sDirName );
|
|
if ( aIter != m_aRecent.end() )
|
|
{
|
|
// There is a cached entry for this name.
|
|
|
|
ZipPackageFolder* pFolder = aIter->second;
|
|
|
|
if ( bFolder )
|
|
{
|
|
// Determine the directory name.
|
|
sal_Int32 nDirIndex = aName.lastIndexOf ( '/', nStreamIndex );
|
|
std::u16string_view sTemp = aName.subView ( nDirIndex == -1 ? 0 : nDirIndex+1, nStreamIndex-nDirIndex-1 );
|
|
|
|
if (pFolder && sTemp == pFolder->getName())
|
|
return Any(uno::Reference(cppu::getXWeak(pFolder)));
|
|
}
|
|
else
|
|
{
|
|
// Determine the file name.
|
|
OUString 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 )
|
|
{
|
|
OUString 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(u"Bad Zip File, stream as folder"_ustr);
|
|
pCurrent = rInfo.pFolder;
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
|
|
if ( bFolder )
|
|
{
|
|
if ( nStreamIndex != -1 )
|
|
m_aRecent[sDirName] = pPrevious; // cache it.
|
|
return Any ( uno::Reference( cppu::getXWeak(pCurrent) ) );
|
|
}
|
|
|
|
OUString 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 )
|
|
{
|
|
if (aName == "/")
|
|
// root directory
|
|
return true;
|
|
|
|
try
|
|
{
|
|
OUString sDirName;
|
|
sal_Int32 nStreamIndex;
|
|
nStreamIndex = aName.lastIndexOf ( '/' );
|
|
bool bFolder = nStreamIndex == aName.getLength()-1;
|
|
if ( nStreamIndex != -1 )
|
|
{
|
|
sDirName = aName.copy ( 0, nStreamIndex );
|
|
FolderHash::iterator aIter = m_aRecent.find ( sDirName );
|
|
if ( aIter != m_aRecent.end() )
|
|
{
|
|
if ( bFolder )
|
|
{
|
|
sal_Int32 nDirIndex = aName.lastIndexOf ( '/', nStreamIndex );
|
|
std::u16string_view sTemp = aName.subView ( nDirIndex == -1 ? 0 : nDirIndex+1, nStreamIndex-nDirIndex-1 );
|
|
if ( sTemp == ( *aIter ).second->getName() )
|
|
return true;
|
|
else
|
|
m_aRecent.erase ( aIter );
|
|
}
|
|
else
|
|
{
|
|
OUString 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;
|
|
sal_Int32 nOldIndex = 0;
|
|
sal_Int32 nIndex;
|
|
while ( ( nIndex = aName.indexOf( '/', nOldIndex )) != -1 )
|
|
{
|
|
if ( nIndex == nOldIndex )
|
|
break;
|
|
|
|
OUString sTemp = aName.copy ( nOldIndex, nIndex - nOldIndex );
|
|
|
|
if ( pCurrent->hasByName( sTemp ) )
|
|
{
|
|
pPrevious = pCurrent;
|
|
ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp);
|
|
if (!rInfo.bFolder)
|
|
throw css::packages::zip::ZipIOException(u"Bad Zip File, stream as folder"_ustr);
|
|
pCurrent = rInfo.pFolder;
|
|
}
|
|
else
|
|
return false;
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
if ( bFolder )
|
|
{
|
|
m_aRecent[sDirName] = pPrevious;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
OUString 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(u"ZipPackage::hasByHierarchicalName"_ustr, nullptr, e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uno::Reference< XInterface > SAL_CALL ZipPackage::createInstance()
|
|
{
|
|
uno::Reference < XInterface > xRef = *( new ZipPackageStream( *this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert ) );
|
|
return xRef;
|
|
}
|
|
|
|
uno::Reference< XInterface > SAL_CALL ZipPackage::createInstanceWithArguments( const uno::Sequence< Any >& aArguments )
|
|
{
|
|
bool bArg = false;
|
|
uno::Reference < XInterface > xRef;
|
|
if ( aArguments.hasElements() )
|
|
aArguments[0] >>= bArg;
|
|
if ( bArg )
|
|
xRef = *new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
|
|
else
|
|
xRef = *new ZipPackageStream( *this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
|
|
|
|
return xRef;
|
|
}
|
|
|
|
void ZipPackage::WriteMimetypeMagicFile( ZipOutputStream& aZipOut )
|
|
{
|
|
static constexpr OUString sMime (u"mimetype"_ustr);
|
|
if ( m_xRootFolder->hasByName( sMime ) )
|
|
m_xRootFolder->removeByName( sMime );
|
|
|
|
auto pEntry = std::make_unique<ZipEntry>();
|
|
sal_Int32 nBufferLength = m_xRootFolder->GetMediaType().getLength();
|
|
OString sMediaType = OUStringToOString( m_xRootFolder->GetMediaType(), RTL_TEXTENCODING_ASCII_US );
|
|
const uno::Sequence< sal_Int8 > aType( reinterpret_cast<sal_Int8 const *>(sMediaType.getStr()),
|
|
nBufferLength );
|
|
|
|
pEntry->sPath = sMime;
|
|
pEntry->nMethod = STORED;
|
|
pEntry->nSize = pEntry->nCompressedSize = nBufferLength;
|
|
pEntry->nTime = ZipOutputStream::getCurrentDosTime();
|
|
|
|
CRC32 aCRC32;
|
|
aCRC32.update( aType );
|
|
pEntry->nCrc = aCRC32.getValue();
|
|
|
|
try
|
|
{
|
|
ZipOutputStream::setEntry(*pEntry);
|
|
aZipOut.writeLOC(std::move(pEntry));
|
|
aZipOut.rawWrite(aType);
|
|
aZipOut.rawCloseEntry();
|
|
}
|
|
catch ( const css::io::IOException & )
|
|
{
|
|
css::uno::Any anyEx = cppu::getCaughtException();
|
|
throw WrappedTargetException(
|
|
THROW_WHERE "Error adding mimetype to the ZipOutputStream!",
|
|
getXWeak(),
|
|
anyEx );
|
|
}
|
|
}
|
|
|
|
void ZipPackage::WriteManifest( ZipOutputStream& aZipOut, const std::vector< uno::Sequence < PropertyValue > >& aManList )
|
|
{
|
|
// Write the manifest
|
|
uno::Reference < XManifestWriter > xWriter = ManifestWriter::create( m_xContext );
|
|
auto pEntry = std::make_unique<ZipEntry>();
|
|
rtl::Reference<ZipPackageBuffer> pBuffer = new ZipPackageBuffer;
|
|
|
|
pEntry->sPath = "META-INF/manifest.xml";
|
|
pEntry->nMethod = DEFLATED;
|
|
pEntry->nCrc = -1;
|
|
pEntry->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);
|
|
auto p = pEntry.get();
|
|
aZipOut.writeLOC(std::move(pEntry));
|
|
ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, p, nullptr, /*bEncrypt*/false);
|
|
aZipEntry.write(pBuffer->getSequence());
|
|
aZipEntry.closeEntry();
|
|
aZipOut.rawCloseEntry();
|
|
}
|
|
|
|
void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const std::vector< uno::Sequence < PropertyValue > >& aManList )
|
|
{
|
|
auto pEntry = std::make_unique<ZipEntry>();
|
|
rtl::Reference<ZipPackageBuffer> pBuffer = new ZipPackageBuffer;
|
|
|
|
pEntry->sPath = "[Content_Types].xml";
|
|
pEntry->nMethod = DEFLATED;
|
|
pEntry->nCrc = -1;
|
|
pEntry->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
|
|
{
|
|
{ u"xml"_ustr, u"application/xml"_ustr },
|
|
{ u"rels"_ustr, u"application/vnd.openxmlformats-package.relationships+xml"_ustr },
|
|
{ u"png"_ustr, u"image/png"_ustr },
|
|
{ u"jpeg"_ustr, u"image/jpeg"_ustr }
|
|
};
|
|
|
|
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);
|
|
auto p = pEntry.get();
|
|
aZipOut.writeLOC(std::move(pEntry));
|
|
ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, p, nullptr, /*bEncrypt*/false);
|
|
aZipEntry.write(pBuffer->getSequence());
|
|
aZipEntry.closeEntry();
|
|
aZipOut.rawCloseEntry();
|
|
}
|
|
|
|
void ZipPackage::ConnectTo( const uno::Reference< io::XInputStream >& xInStream )
|
|
{
|
|
assert(dynamic_cast<comphelper::ByteReader*>(xInStream.get()));
|
|
m_xContentSeek.set( xInStream, uno::UNO_QUERY_THROW );
|
|
m_xContentStream = xInStream;
|
|
|
|
// seek back to the beginning of the temp file so we can read segments from it
|
|
m_xContentSeek->seek( 0 );
|
|
if ( m_pZipFile )
|
|
m_pZipFile->setInputStream( m_xContentStream );
|
|
else
|
|
m_pZipFile.emplace(m_aMutexHolder, m_xContentStream, m_xContext, false,
|
|
false,
|
|
m_nFormat == embed::StorageFormats::ZIP
|
|
? ZipFile::Checks::Default
|
|
: m_nFormat == embed::StorageFormats::OFOPXML
|
|
? ZipFile::Checks::CheckInsensitive
|
|
: ZipFile::Checks::TryCheckInsensitive);
|
|
}
|
|
|
|
uno::Reference< io::XInputStream > ZipPackage::writeTempFile()
|
|
{
|
|
// In case the target local file does not exist or empty
|
|
// write directly to it otherwise create a temporary file to write to.
|
|
// If a temporary file is created it is returned back by the method.
|
|
// If the data written directly, xComponentStream will be switched here
|
|
|
|
bool bUseTemp = true;
|
|
uno::Reference < io::XInputStream > xResult;
|
|
uno::Reference < io::XInputStream > xTempIn;
|
|
|
|
uno::Reference < io::XOutputStream > xTempOut;
|
|
uno::Reference< io::XActiveDataStreamer > xSink;
|
|
|
|
if ( m_eMode == e_IMode_URL && !m_pZipFile && isLocalFile() )
|
|
{
|
|
xSink = openOriginalForOutput();
|
|
if( xSink.is() )
|
|
{
|
|
uno::Reference< io::XStream > xStr = xSink->getStream();
|
|
if( xStr.is() )
|
|
{
|
|
xTempOut = xStr->getOutputStream();
|
|
if( xTempOut.is() )
|
|
bUseTemp = false;
|
|
}
|
|
}
|
|
}
|
|
else if ( m_eMode == e_IMode_XStream && !m_pZipFile )
|
|
{
|
|
// write directly to an empty stream
|
|
xTempOut = m_xStream->getOutputStream();
|
|
if( xTempOut.is() )
|
|
bUseTemp = false;
|
|
}
|
|
|
|
if( bUseTemp )
|
|
{
|
|
// create temporary file
|
|
rtl::Reference < utl::TempFileFastService > xTempFile( new utl::TempFileFastService );
|
|
xTempOut.set( xTempFile );
|
|
xTempIn.set( xTempFile );
|
|
}
|
|
|
|
// Hand it to the ZipOutputStream:
|
|
ZipOutputStream aZipOut( xTempOut );
|
|
try
|
|
{
|
|
if ( m_nFormat == embed::StorageFormats::PACKAGE )
|
|
{
|
|
// Remove the old manifest.xml file as the
|
|
// manifest will be re-generated and the
|
|
// META-INF directory implicitly created if does not exist
|
|
static constexpr 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 <<= u"/"_ustr;
|
|
|
|
if( bIsGpgEncrypt )
|
|
{
|
|
pPropSeq[PKG_SIZE_NOENCR_MNFST].Name = "KeyInfo";
|
|
pPropSeq[PKG_SIZE_NOENCR_MNFST].Value <<= m_aGpgProps;
|
|
}
|
|
aManList.push_back( aPropSeq );
|
|
}
|
|
|
|
{
|
|
::std::optional<sal_Int32> oPBKDF2IterationCount;
|
|
::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgon2Args;
|
|
|
|
if (!bIsGpgEncrypt)
|
|
{
|
|
if (m_nKeyDerivationFunctionID == xml::crypto::KDFID::PBKDF2)
|
|
{ // if there is only one KDF invocation, increase the safety margin
|
|
oPBKDF2IterationCount.emplace(m_xRootFolder->hasByName(u"encrypted-package"_ustr) ? 600000 : 100000);
|
|
}
|
|
else
|
|
{
|
|
assert(m_nKeyDerivationFunctionID == xml::crypto::KDFID::Argon2id);
|
|
oArgon2Args.emplace(3, (1<<16), 4);
|
|
}
|
|
}
|
|
|
|
// call saveContents - it will recursively save sub-directories
|
|
m_xRootFolder->saveContents(u""_ustr, aManList, aZipOut, GetEncryptionKey(),
|
|
oPBKDF2IterationCount, oArgon2Args);
|
|
}
|
|
|
|
if( m_nFormat == embed::StorageFormats::PACKAGE )
|
|
{
|
|
WriteManifest( aZipOut, aManList );
|
|
}
|
|
else if( m_nFormat == embed::StorageFormats::OFOPXML )
|
|
{
|
|
WriteContentTypes( aZipOut, aManList );
|
|
}
|
|
|
|
aZipOut.finish();
|
|
|
|
if( bUseTemp )
|
|
xResult = std::move(xTempIn);
|
|
|
|
// Update our References to point to the new temp file
|
|
if( !bUseTemp )
|
|
{
|
|
// the case when the original contents were written directly
|
|
xTempOut->flush();
|
|
|
|
// in case the stream is based on a file it will implement the following interface
|
|
// the call should be used to be sure that the contents are written to the file system
|
|
uno::Reference< io::XAsyncOutputMonitor > asyncOutputMonitor( xTempOut, uno::UNO_QUERY );
|
|
if (asyncOutputMonitor.is() && !m_bDisableFileSync)
|
|
asyncOutputMonitor->waitForCompletion();
|
|
|
|
// no need to postpone switching to the new stream since the target was written directly
|
|
uno::Reference< io::XInputStream > xNewStream;
|
|
if ( m_eMode == e_IMode_URL )
|
|
xNewStream = xSink->getStream()->getInputStream();
|
|
else if ( m_eMode == e_IMode_XStream && m_xStream.is() )
|
|
xNewStream = m_xStream->getInputStream();
|
|
|
|
if ( xNewStream.is() )
|
|
ConnectTo( xNewStream );
|
|
}
|
|
}
|
|
catch ( uno::Exception& )
|
|
{
|
|
if( bUseTemp )
|
|
{
|
|
// no information loss appears, thus no special handling is required
|
|
uno::Any aCaught( ::cppu::getCaughtException() );
|
|
|
|
// it is allowed to throw WrappedTargetException
|
|
WrappedTargetException aException;
|
|
if ( aCaught >>= aException )
|
|
throw aException;
|
|
|
|
throw WrappedTargetException(
|
|
THROW_WHERE "Problem writing the original content!",
|
|
getXWeak(),
|
|
aCaught );
|
|
}
|
|
else
|
|
{
|
|
// the document is written directly, although it was empty it is important to notify that the writing has failed
|
|
// TODO/LATER: let the package be able to recover in this situation
|
|
OUString aErrTxt(THROW_WHERE "This package is unusable!");
|
|
embed::UseBackupException aException( aErrTxt, uno::Reference< uno::XInterface >(), OUString() );
|
|
throw WrappedTargetException( aErrTxt,
|
|
getXWeak(),
|
|
Any ( aException ) );
|
|
}
|
|
}
|
|
|
|
return xResult;
|
|
}
|
|
|
|
uno::Reference< XActiveDataStreamer > ZipPackage::openOriginalForOutput()
|
|
{
|
|
// open and truncate the original file
|
|
Content aOriginalContent(
|
|
m_aURL, uno::Reference< XCommandEnvironment >(),
|
|
m_xContext );
|
|
uno::Reference< XActiveDataStreamer > xSink = new ActiveDataStreamer;
|
|
|
|
if ( m_eMode == e_IMode_URL )
|
|
{
|
|
try
|
|
{
|
|
bool bTruncSuccess = false;
|
|
|
|
try
|
|
{
|
|
Exception aDetect;
|
|
Any aAny = aOriginalContent.setPropertyValue(u"Size"_ustr, Any( sal_Int64(0) ) );
|
|
if( !( aAny >>= aDetect ) )
|
|
bTruncSuccess = true;
|
|
}
|
|
catch( Exception& )
|
|
{
|
|
}
|
|
|
|
if( !bTruncSuccess )
|
|
{
|
|
// the file is not accessible
|
|
// just try to write an empty stream to it
|
|
|
|
uno::Reference< XInputStream > xTempIn = new DummyInputStream; //uno::Reference< XInputStream >( xTempOut, UNO_QUERY );
|
|
aOriginalContent.writeStream( xTempIn , true );
|
|
}
|
|
|
|
OpenCommandArgument2 aArg;
|
|
aArg.Mode = OpenMode::DOCUMENT;
|
|
aArg.Priority = 0; // unused
|
|
aArg.Sink = xSink;
|
|
aArg.Properties = uno::Sequence< Property >( 0 ); // unused
|
|
|
|
aOriginalContent.executeCommand(u"open"_ustr, Any( aArg ) );
|
|
}
|
|
catch( Exception& )
|
|
{
|
|
// seems to be nonlocal file
|
|
// temporary file mechanics should be used
|
|
}
|
|
}
|
|
|
|
return xSink;
|
|
}
|
|
|
|
void SAL_CALL ZipPackage::commitChanges()
|
|
{
|
|
// lock the component for the time of committing
|
|
::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() );
|
|
|
|
if ( m_eMode == e_IMode_XInputStream )
|
|
{
|
|
IOException aException;
|
|
throw WrappedTargetException(THROW_WHERE "This package is read only!",
|
|
getXWeak(), Any ( aException ) );
|
|
}
|
|
// first the writeTempFile is called, if it returns a stream the stream should be written to the target
|
|
// if no stream was returned, the file was written directly, nothing should be done
|
|
uno::Reference< io::XInputStream > xTempInStream;
|
|
try
|
|
{
|
|
xTempInStream = writeTempFile();
|
|
}
|
|
catch (const ucb::ContentCreationException&)
|
|
{
|
|
css::uno::Any anyEx = cppu::getCaughtException();
|
|
throw WrappedTargetException(THROW_WHERE "Temporary file should be creatable!",
|
|
getXWeak(), anyEx );
|
|
}
|
|
if ( xTempInStream.is() )
|
|
{
|
|
uno::Reference< io::XSeekable > xTempSeek( xTempInStream, uno::UNO_QUERY_THROW );
|
|
|
|
try
|
|
{
|
|
xTempSeek->seek( 0 );
|
|
}
|
|
catch( const uno::Exception& )
|
|
{
|
|
css::uno::Any anyEx = cppu::getCaughtException();
|
|
throw WrappedTargetException(THROW_WHERE "Temporary file should be seekable!",
|
|
getXWeak(), anyEx );
|
|
}
|
|
|
|
try
|
|
{
|
|
// connect to the temporary stream
|
|
ConnectTo( xTempInStream );
|
|
}
|
|
catch( const io::IOException& )
|
|
{
|
|
css::uno::Any anyEx = cppu::getCaughtException();
|
|
throw WrappedTargetException(THROW_WHERE "Temporary file should be connectable!",
|
|
getXWeak(), anyEx );
|
|
}
|
|
|
|
if ( m_eMode == e_IMode_XStream )
|
|
{
|
|
// First truncate our output stream
|
|
uno::Reference < XOutputStream > xOutputStream;
|
|
|
|
// preparation for copy step
|
|
try
|
|
{
|
|
xOutputStream = m_xStream->getOutputStream();
|
|
|
|
// Make sure we avoid a situation where the current position is
|
|
// not zero, but the underlying file is truncated in the
|
|
// meantime.
|
|
uno::Reference<io::XSeekable> xSeekable(xOutputStream, uno::UNO_QUERY);
|
|
if (xSeekable.is())
|
|
xSeekable->seek(0);
|
|
|
|
uno::Reference < XTruncate > xTruncate ( xOutputStream, UNO_QUERY_THROW );
|
|
|
|
// after successful truncation the original file contents are already lost
|
|
xTruncate->truncate();
|
|
}
|
|
catch( const uno::Exception& )
|
|
{
|
|
css::uno::Any anyEx = cppu::getCaughtException();
|
|
throw WrappedTargetException(THROW_WHERE "This package is read only!",
|
|
getXWeak(), anyEx );
|
|
}
|
|
|
|
try
|
|
{
|
|
// then copy the contents of the tempfile to our output stream
|
|
::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream, xOutputStream );
|
|
xOutputStream->flush();
|
|
uno::Reference< io::XAsyncOutputMonitor > asyncOutputMonitor(
|
|
xOutputStream, uno::UNO_QUERY );
|
|
if ( asyncOutputMonitor.is() ) {
|
|
asyncOutputMonitor->waitForCompletion();
|
|
}
|
|
}
|
|
catch( uno::Exception& )
|
|
{
|
|
// if anything goes wrong in this block the target file becomes corrupted
|
|
// so an exception should be thrown as a notification about it
|
|
// and the package must disconnect from the stream
|
|
DisconnectFromTargetAndThrowException_Impl( xTempInStream );
|
|
}
|
|
}
|
|
else if ( m_eMode == e_IMode_URL )
|
|
{
|
|
uno::Reference< XOutputStream > aOrigFileStream;
|
|
bool bCanBeCorrupted = false;
|
|
|
|
if( isLocalFile() )
|
|
{
|
|
// write directly in case of local file
|
|
uno::Reference< css::ucb::XSimpleFileAccess3 > xSimpleAccess(
|
|
SimpleFileAccess::create( m_xContext ) );
|
|
OSL_ENSURE( xSimpleAccess.is(), "Can't instantiate SimpleFileAccess service!" );
|
|
uno::Reference< io::XTruncate > xOrigTruncate;
|
|
if ( xSimpleAccess.is() )
|
|
{
|
|
try
|
|
{
|
|
aOrigFileStream = xSimpleAccess->openFileWrite( m_aURL );
|
|
xOrigTruncate.set( aOrigFileStream, uno::UNO_QUERY_THROW );
|
|
// after successful truncation the file is already corrupted
|
|
xOrigTruncate->truncate();
|
|
}
|
|
catch( uno::Exception& )
|
|
{}
|
|
}
|
|
|
|
if( xOrigTruncate.is() )
|
|
{
|
|
try
|
|
{
|
|
::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream, aOrigFileStream );
|
|
aOrigFileStream->closeOutput();
|
|
}
|
|
catch( uno::Exception& )
|
|
{
|
|
try {
|
|
aOrigFileStream->closeOutput();
|
|
} catch ( uno::Exception& ) {}
|
|
|
|
aOrigFileStream.clear();
|
|
// the original file can already be corrupted
|
|
bCanBeCorrupted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !aOrigFileStream.is() )
|
|
{
|
|
try
|
|
{
|
|
uno::Reference < XPropertySet > xPropSet ( xTempInStream, UNO_QUERY_THROW );
|
|
|
|
OUString sTargetFolder = m_aURL.copy ( 0, m_aURL.lastIndexOf ( u'/' ) );
|
|
Content aContent(
|
|
sTargetFolder, uno::Reference< XCommandEnvironment >(),
|
|
m_xContext );
|
|
|
|
OUString sTempURL;
|
|
Any aAny = xPropSet->getPropertyValue (u"Uri"_ustr);
|
|
aAny >>= sTempURL;
|
|
|
|
TransferInfo aInfo;
|
|
aInfo.NameClash = NameClash::OVERWRITE;
|
|
aInfo.MoveData = false;
|
|
aInfo.SourceURL = sTempURL;
|
|
aInfo.NewTitle = rtl::Uri::decode ( m_aURL.copy ( 1 + m_aURL.lastIndexOf ( u'/' ) ),
|
|
rtl_UriDecodeWithCharset,
|
|
RTL_TEXTENCODING_UTF8 );
|
|
// if the file is still not corrupted, it can become after the next step
|
|
aContent.executeCommand (u"transfer"_ustr, Any(aInfo) );
|
|
}
|
|
catch ( const css::uno::Exception& )
|
|
{
|
|
if ( bCanBeCorrupted )
|
|
DisconnectFromTargetAndThrowException_Impl( xTempInStream );
|
|
|
|
css::uno::Any anyEx = cppu::getCaughtException();
|
|
throw WrappedTargetException(
|
|
THROW_WHERE "This package may be read only!",
|
|
getXWeak(),
|
|
anyEx );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// after successful storing it can be set to false
|
|
m_bMediaTypeFallbackUsed = false;
|
|
}
|
|
|
|
void ZipPackage::DisconnectFromTargetAndThrowException_Impl( const uno::Reference< io::XInputStream >& xTempStream )
|
|
{
|
|
m_xStream.set( xTempStream, uno::UNO_QUERY );
|
|
if ( m_xStream.is() )
|
|
m_eMode = e_IMode_XStream;
|
|
else
|
|
m_eMode = e_IMode_XInputStream;
|
|
|
|
OUString aTempURL;
|
|
try {
|
|
uno::Reference< beans::XPropertySet > xTempFile( xTempStream, uno::UNO_QUERY_THROW );
|
|
uno::Any aUrl = xTempFile->getPropertyValue(u"Uri"_ustr);
|
|
aUrl >>= aTempURL;
|
|
xTempFile->setPropertyValue(u"RemoveFile"_ustr,
|
|
uno::Any( false ) );
|
|
}
|
|
catch ( uno::Exception& )
|
|
{
|
|
OSL_FAIL( "These calls are pretty simple, they should not fail!" );
|
|
}
|
|
|
|
OUString aErrTxt(THROW_WHERE "This package is read only!");
|
|
embed::UseBackupException aException( aErrTxt, uno::Reference< uno::XInterface >(), aTempURL );
|
|
throw WrappedTargetException( aErrTxt,
|
|
getXWeak(),
|
|
Any ( aException ) );
|
|
}
|
|
|
|
uno::Sequence< sal_Int8 > ZipPackage::GetEncryptionKey()
|
|
{
|
|
uno::Sequence< sal_Int8 > aResult;
|
|
|
|
if ( m_aStorageEncryptionKeys.hasElements() )
|
|
{
|
|
OUString aNameToFind;
|
|
if ( m_nStartKeyGenerationID == xml::crypto::DigestID::SHA256 )
|
|
aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA256UTF8;
|
|
else if ( m_nStartKeyGenerationID == xml::crypto::DigestID::SHA1 )
|
|
aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA1CORRECT;
|
|
else
|
|
throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" );
|
|
|
|
for (const auto& rKey : m_aStorageEncryptionKeys)
|
|
if ( rKey.Name == aNameToFind )
|
|
rKey.Value >>= aResult;
|
|
|
|
if (!aResult.hasElements() && m_aStorageEncryptionKeys.hasElements())
|
|
{ // tdf#159519 sanity check
|
|
throw uno::RuntimeException(THROW_WHERE "Expected key is missing!");
|
|
}
|
|
}
|
|
else
|
|
aResult = m_aEncryptionKey;
|
|
|
|
return aResult;
|
|
}
|
|
|
|
sal_Bool SAL_CALL ZipPackage::hasPendingChanges()
|
|
{
|
|
return false;
|
|
}
|
|
Sequence< ElementChange > SAL_CALL ZipPackage::getPendingChanges()
|
|
{
|
|
return uno::Sequence < ElementChange > ();
|
|
}
|
|
|
|
|
|
OUString ZipPackage::getImplementationName()
|
|
{
|
|
return u"com.sun.star.packages.comp.ZipPackage"_ustr;
|
|
}
|
|
|
|
Sequence< OUString > ZipPackage::getSupportedServiceNames()
|
|
{
|
|
return { u"com.sun.star.packages.Package"_ustr };
|
|
}
|
|
|
|
sal_Bool SAL_CALL ZipPackage::supportsService( OUString const & rServiceName )
|
|
{
|
|
return cppu::supportsService(this, rServiceName);
|
|
}
|
|
|
|
uno::Reference< XPropertySetInfo > SAL_CALL ZipPackage::getPropertySetInfo()
|
|
{
|
|
return uno::Reference < XPropertySetInfo > ();
|
|
}
|
|
|
|
void SAL_CALL ZipPackage::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
|
|
{
|
|
if ( m_nFormat != embed::StorageFormats::PACKAGE )
|
|
throw UnknownPropertyException(aPropertyName);
|
|
|
|
if (aPropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY
|
|
||aPropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY
|
|
||aPropertyName == IS_INCONSISTENT_PROPERTY
|
|
||aPropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY)
|
|
throw PropertyVetoException(THROW_WHERE );
|
|
else if ( aPropertyName == ENCRYPTION_KEY_PROPERTY )
|
|
{
|
|
if ( !( aValue >>= m_aEncryptionKey ) )
|
|
throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 2 );
|
|
|
|
m_aStorageEncryptionKeys.realloc( 0 );
|
|
}
|
|
else if ( aPropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY )
|
|
{
|
|
// this property is only necessary to support raw passwords in storage API;
|
|
// because of this support the storage has to operate with more than one key dependent on storage generation algorithm;
|
|
// when this support is removed, the storage will get only one key from outside
|
|
if ( !( aValue >>= m_aStorageEncryptionKeys ) )
|
|
throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 2 );
|
|
|
|
m_aEncryptionKey.realloc( 0 );
|
|
}
|
|
else if ( aPropertyName == ENCRYPTION_ALGORITHMS_PROPERTY )
|
|
{
|
|
uno::Sequence< beans::NamedValue > aAlgorithms;
|
|
if ( m_pZipFile || !( aValue >>= aAlgorithms ) || !aAlgorithms.hasElements() )
|
|
{
|
|
// the algorithms can not be changed if the file has a persistence based on the algorithms ( m_pZipFile )
|
|
throw IllegalArgumentException(THROW_WHERE "unexpected algorithms list is provided.", uno::Reference< uno::XInterface >(), 2 );
|
|
}
|
|
|
|
for (const auto& rAlgorithm : aAlgorithms)
|
|
{
|
|
if ( rAlgorithm.Name == "StartKeyGenerationAlgorithm" )
|
|
{
|
|
sal_Int32 nID = 0;
|
|
if ( !( rAlgorithm.Value >>= nID )
|
|
|| ( nID != xml::crypto::DigestID::SHA256 && nID != xml::crypto::DigestID::SHA1 ) )
|
|
{
|
|
throw IllegalArgumentException(THROW_WHERE "Unexpected start key generation algorithm is provided!", uno::Reference<uno::XInterface>(), 2);
|
|
}
|
|
|
|
m_nStartKeyGenerationID = nID;
|
|
}
|
|
else if (rAlgorithm.Name == "KeyDerivationFunction")
|
|
{
|
|
sal_Int32 nID = 0;
|
|
if (!(rAlgorithm.Value >>= nID)
|
|
|| (nID != xml::crypto::KDFID::PBKDF2
|
|
&& nID != xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P
|
|
&& nID != xml::crypto::KDFID::Argon2id))
|
|
{
|
|
throw IllegalArgumentException(THROW_WHERE "Unexpected key derivation function provided!", uno::Reference<uno::XInterface>(), 2);
|
|
}
|
|
m_nKeyDerivationFunctionID = nID;
|
|
}
|
|
else if ( rAlgorithm.Name == "EncryptionAlgorithm" )
|
|
{
|
|
sal_Int32 nID = 0;
|
|
if ( !( rAlgorithm.Value >>= nID )
|
|
|| (nID != xml::crypto::CipherID::AES_GCM_W3C
|
|
&& nID != xml::crypto::CipherID::AES_CBC_W3C_PADDING
|
|
&& nID != xml::crypto::CipherID::BLOWFISH_CFB_8))
|
|
{
|
|
throw IllegalArgumentException(THROW_WHERE "Unexpected encryption algorithm is provided!", uno::Reference<uno::XInterface>(), 2);
|
|
}
|
|
|
|
m_nCommonEncryptionID = nID;
|
|
}
|
|
else if ( rAlgorithm.Name == "ChecksumAlgorithm" )
|
|
{
|
|
sal_Int32 nID = 0;
|
|
if (!rAlgorithm.Value.hasValue())
|
|
{
|
|
m_oChecksumDigestID.reset();
|
|
continue;
|
|
}
|
|
if ( !( rAlgorithm.Value >>= nID )
|
|
|| ( nID != xml::crypto::DigestID::SHA1_1K && nID != xml::crypto::DigestID::SHA256_1K ) )
|
|
{
|
|
throw IllegalArgumentException(THROW_WHERE "Unexpected checksum algorithm is provided!", uno::Reference<uno::XInterface>(), 2);
|
|
}
|
|
|
|
m_oChecksumDigestID.emplace(nID);
|
|
}
|
|
else
|
|
{
|
|
OSL_ENSURE( false, "Unexpected encryption algorithm is provided!" );
|
|
throw IllegalArgumentException(THROW_WHERE "unexpected algorithms list is provided.", uno::Reference< uno::XInterface >(), 2 );
|
|
}
|
|
}
|
|
}
|
|
else if ( aPropertyName == ENCRYPTION_GPG_PROPERTIES )
|
|
{
|
|
uno::Sequence< uno::Sequence< beans::NamedValue > > aGpgProps;
|
|
if ( !( aValue >>= aGpgProps ) || !aGpgProps.hasElements() )
|
|
{
|
|
throw IllegalArgumentException(THROW_WHERE "unexpected Gpg properties are provided.", uno::Reference< uno::XInterface >(), 2 );
|
|
}
|
|
|
|
m_aGpgProps = std::move(aGpgProps);
|
|
|
|
// override algorithm defaults (which are some legacy ODF
|
|
// defaults) with reasonable values
|
|
// note: these should be overridden by SfxObjectShell::SetupStorage()
|
|
m_nStartKeyGenerationID = 0; // this is unused for PGP
|
|
m_nKeyDerivationFunctionID = xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P;
|
|
m_nCommonEncryptionID = xml::crypto::CipherID::AES_CBC_W3C_PADDING;
|
|
m_oChecksumDigestID.emplace(xml::crypto::DigestID::SHA512_1K);
|
|
}
|
|
else
|
|
throw UnknownPropertyException(aPropertyName);
|
|
}
|
|
|
|
Any SAL_CALL ZipPackage::getPropertyValue( const OUString& PropertyName )
|
|
{
|
|
// TODO/LATER: Activate the check when zip-ucp is ready
|
|
// if ( m_nFormat != embed::StorageFormats::PACKAGE )
|
|
// throw UnknownPropertyException(THROW_WHERE );
|
|
|
|
if ( PropertyName == ENCRYPTION_KEY_PROPERTY )
|
|
{
|
|
return Any(m_aEncryptionKey);
|
|
}
|
|
else if ( PropertyName == ENCRYPTION_ALGORITHMS_PROPERTY )
|
|
{
|
|
::comphelper::SequenceAsHashMap aAlgorithms;
|
|
aAlgorithms[u"StartKeyGenerationAlgorithm"_ustr] <<= m_nStartKeyGenerationID;
|
|
aAlgorithms[u"KeyDerivationFunction"_ustr] <<= m_nKeyDerivationFunctionID;
|
|
aAlgorithms[u"EncryptionAlgorithm"_ustr] <<= m_nCommonEncryptionID;
|
|
if (m_oChecksumDigestID)
|
|
{
|
|
aAlgorithms[u"ChecksumAlgorithm"_ustr] <<= *m_oChecksumDigestID;
|
|
}
|
|
else
|
|
{
|
|
aAlgorithms[u"ChecksumAlgorithm"_ustr];
|
|
}
|
|
return Any(aAlgorithms.getAsConstNamedValueList());
|
|
}
|
|
if ( PropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY )
|
|
{
|
|
return Any(m_aStorageEncryptionKeys);
|
|
}
|
|
else if ( PropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY )
|
|
{
|
|
return Any(m_bHasEncryptedEntries);
|
|
}
|
|
else if ( PropertyName == ENCRYPTION_GPG_PROPERTIES )
|
|
{
|
|
return Any(m_aGpgProps);
|
|
}
|
|
else if ( PropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY )
|
|
{
|
|
return Any(m_bHasNonEncryptedEntries);
|
|
}
|
|
else if ( PropertyName == IS_INCONSISTENT_PROPERTY )
|
|
{
|
|
return Any(m_bInconsistent);
|
|
}
|
|
else if ( PropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY )
|
|
{
|
|
return Any(m_bMediaTypeFallbackUsed);
|
|
}
|
|
else if (PropertyName == "HasElements")
|
|
{
|
|
return Any(m_pZipFile && m_pZipFile->entries().hasMoreElements());
|
|
}
|
|
throw UnknownPropertyException(PropertyName);
|
|
}
|
|
void SAL_CALL ZipPackage::addPropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ )
|
|
{
|
|
}
|
|
void SAL_CALL ZipPackage::removePropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*aListener*/ )
|
|
{
|
|
}
|
|
void SAL_CALL ZipPackage::addVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< XVetoableChangeListener >& /*aListener*/ )
|
|
{
|
|
}
|
|
void SAL_CALL ZipPackage::removeVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< XVetoableChangeListener >& /*aListener*/ )
|
|
{
|
|
}
|
|
|
|
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
|
|
package_ZipPackage_get_implementation(
|
|
css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
|
|
{
|
|
return cppu::acquire(new ZipPackage(context));
|
|
}
|
|
|
|
extern "C" bool TestImportZip(SvStream& rStream)
|
|
{
|
|
// explicitly tests the "RepairPackage" recovery mode
|
|
rtl::Reference<ZipPackage> xPackage(new ZipPackage(comphelper::getProcessComponentContext()));
|
|
css::uno::Reference<css::io::XInputStream> xStream(new utl::OInputStreamWrapper(rStream));
|
|
css::uno::Sequence<Any> aArgs{ Any(xStream), Any(NamedValue(u"RepairPackage"_ustr, Any(true))) };
|
|
xPackage->initialize(aArgs);
|
|
return true;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|