tdf#105844 package: remove root document from manifest ...

... for ODF wholesome encryption.

  4.3  <manifest:file-entry>
  For directories, the manifest file should contain a <manifest:file-entry> element only if a directory contains a document or a sub document.

Because the "encrypted-package" is not a document but a package, we
should probably omit the file-entry for the root document.

ZipPackage::writeTempFile() always generates the root document becuase
it's needed for GPG properties, and ManifestExport filters it out.

A bit tricky to implement, because there isn't a clean distinction
between the package and the root document/storage in the package module,
in particular there's no other place than the root storage to store the
MediaType property.

Change-Id: Id7e72a64e2faa074dce80cd5fefb2fa189e2e3ee
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160717
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
diff --git a/package/source/manifest/ManifestExport.cxx b/package/source/manifest/ManifestExport.cxx
index 950e47f..cf60614 100644
--- a/package/source/manifest/ManifestExport.cxx
+++ b/package/source/manifest/ManifestExport.cxx
@@ -81,6 +81,7 @@ ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > con
    // find the mediatype of the document if any
    OUString aDocMediaType;
    OUString aDocVersion;
    bool isWholesomeEncryption(false);
    const uno::Sequence<beans::PropertyValue>* pRootFolderPropSeq = nullptr;
    for (const uno::Sequence < beans::PropertyValue >& rSequence : rManList)
    {
@@ -109,12 +110,27 @@ ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > con

        if ( aPath == "/" )
        {
            assert(aDocMediaType.isEmpty());
            // unfortunately no aMediaType in some cases where non-documents
            // are stored as StorageFormats::PACKAGE instead of sensible
            // StorageFormats::ZIP, such as SvxXMLXTableExportComponent and
            // SwXMLTextBlocks, which results in an empty "mimetype" etc but
            // can't be easily fixed; try to exclude these cases by checking
            // for aVersion, but of course then forgetting to set both version
            // and type on an actual document can't be found :(
            assert(!aMediaType.isEmpty() || aVersion.isEmpty());
            aDocMediaType = aMediaType;
            aDocVersion = aVersion;
            pRootFolderPropSeq = &rSequence;
            break;
        }

        if (aPath == "encrypted-package")
        {
            isWholesomeEncryption = true;
            assert(aDocMediaType.isEmpty() || aDocMediaType == aMediaType);
        }
    }
    assert(pRootFolderPropSeq);

    bool bProvideDTD = false;
    bool bAcceptNonemptyVersion = false;
@@ -293,7 +309,12 @@ ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > con
    // now write individual file entries
    for (const uno::Sequence<beans::PropertyValue>& rSequence : rManList)
    {
        if (&rSequence == pRootFolderPropSeq && isWholesomeEncryption)
        {
            continue; // no root document, but embedded package => omit
        }
        rtl::Reference<::comphelper::AttributeList> pAttrList = new ::comphelper::AttributeList;
        OUString fullPath;
        OUString aString;
        const uno::Any *pVector = nullptr, *pSalt = nullptr, *pIterationCount = nullptr, *pDigest = nullptr, *pDigestAlg = nullptr, *pEncryptAlg = nullptr, *pStartKeyAlg = nullptr, *pDerivedKeySize = nullptr;
        for (const beans::PropertyValue& rValue : rSequence)
@@ -312,8 +333,8 @@ ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > con
            }
            else if (rValue.Name == sFullPathProperty )
            {
                rValue.Value >>= aString;
                pAttrList->AddAttribute ( ATTRIBUTE_FULL_PATH, aString );
                rValue.Value >>= fullPath;
                pAttrList->AddAttribute(ATTRIBUTE_FULL_PATH, fullPath);
            }
            else if (rValue.Name == sSizeProperty )
            {
@@ -338,6 +359,11 @@ ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > con
            else if (rValue.Name == sDerivedKeySizeProperty )
                pDerivedKeySize = &rValue.Value;
        }
        assert(!fullPath.isEmpty());
        if (isWholesomeEncryption)
        {   // there may be signatures in META-INF too
            assert(fullPath == "encrypted-package" || fullPath.startsWith("META-INF/"));
        }

        xHandler->ignorableWhitespace ( sWhiteSpace );
        xHandler->startElement( ELEMENT_FILE_ENTRY , pAttrList);
@@ -390,6 +416,7 @@ ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > con
            }
            else if (nEncAlgID == xml::crypto::CipherID::AES_GCM_W3C)
            {
                assert(bStoreStartKeyGeneration || pKeyInfoProperty);
                SAL_WARN_IF(nDerivedKeySize != 32, "package.manifest", "Unexpected key size is provided!");
                if (nDerivedKeySize != 32)
                {
diff --git a/package/source/zipapi/ZipFile.cxx b/package/source/zipapi/ZipFile.cxx
index 664cfa7..bdcd861 100644
--- a/package/source/zipapi/ZipFile.cxx
+++ b/package/source/zipapi/ZipFile.cxx
@@ -647,7 +647,8 @@ uno::Reference< XInputStream > ZipFile::createStreamForZipEntry(
#ifndef EMSCRIPTEN
    static const sal_Int32 nThreadingThreshold = 10000;

    if( xSrcStream->available() > nThreadingThreshold )
    // "encrypted-package" is the only data stream, no point in threading it
    if (rEntry.sPath != "encrypted-package" && nThreadingThreshold < xSrcStream->available())
        xBufStream = new XBufferedThreadedStream(xSrcStream, xSrcStream->getSize());
    else
#endif
diff --git a/package/source/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx
index 1bae902..f95731f 100644
--- a/package/source/zippackage/ZipPackage.cxx
+++ b/package/source/zippackage/ZipPackage.cxx
@@ -417,25 +417,36 @@ void ZipPackage::parseManifest()
            }
        }

        if ( !bManifestParsed )
        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 );
                m_bMediaTypeFallbackUsed = true;
                // 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 information from manifest.xml
            if ( m_xRootFolder->GetMediaType() != aPackageMediatype )
            // 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, \""
                    + m_xRootFolder->GetMediaType() + "\" vs. \""
                    + mediaTypeXML + "\" vs. \""
                    + aPackageMediatype + "\"" );
            }
        }

        m_xRootFolder->removeByName( sMimetype );
@@ -1269,6 +1280,8 @@ uno::Reference< io::XInputStream > ZipPackage::writeTempFile()
        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(
diff --git a/svx/source/xml/xmlxtexp.cxx b/svx/source/xml/xmlxtexp.cxx
index bd03967..214d976 100644
--- a/svx/source/xml/xmlxtexp.cxx
+++ b/svx/source/xml/xmlxtexp.cxx
@@ -227,7 +227,13 @@ bool SvxXMLXTableExportComponent::save(
        if( !bToStorage || !xStorage.is() )
        { // local URL -> SfxMedium route
            if( bSaveAsStorage )
            {
                // ideally this should use a ZIP_STORAGE_FORMAT_STRING storage
                // but changing it to that could cause problems loading the
                // file with an old version of LO that expects to find in the
                // user profile a PACKAGE_STORAGE_FORMAT_STRING storage
                xSubStorage = ::comphelper::OStorageHelper::GetStorageFromURL( rURL, eCreate );
            }
            else
            {
                pMedium.reset(new SfxMedium( rURL, StreamMode::WRITE | StreamMode::TRUNC ));