Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/package/source/zippackage/ZipPackageFolder.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <ZipPackageFolder.hxx>
21
#include <ZipOutputStream.hxx>
22
#include <ZipPackageStream.hxx>
23
#include <PackageConstants.hxx>
24
#include "ZipPackageFolderEnumeration.hxx"
25
#include <com/sun/star/io/IOException.hpp>
26
#include <com/sun/star/packages/zip/ZipConstants.hpp>
27
#include <com/sun/star/packages/zip/ZipException.hpp>
28
#include <com/sun/star/embed/StorageFormats.hpp>
29
#include <comphelper/sequence.hxx>
30
#include <comphelper/servicehelper.hxx>
31
#include <cppuhelper/supportsservice.hxx>
32
#include <sal/log.hxx>
33
#include <com/sun/star/beans/PropertyValue.hpp>
34
35
using namespace com::sun::star;
36
using namespace com::sun::star::packages::zip::ZipConstants;
37
using namespace com::sun::star::packages::zip;
38
using namespace com::sun::star::packages;
39
using namespace com::sun::star::container;
40
using namespace com::sun::star::beans;
41
using namespace com::sun::star::lang;
42
using namespace com::sun::star::io;
43
using namespace cppu;
44
45
ZipPackageFolder::ZipPackageFolder( const css::uno::Reference < css::uno::XComponentContext >& xContext,
46
                                    sal_Int32 nFormat,
47
                                    bool bAllowRemoveOnInsert )
48
254k
{
49
254k
    m_xContext = xContext;
50
254k
    m_nFormat = nFormat;
51
254k
    mbAllowRemoveOnInsert = bAllowRemoveOnInsert;
52
254k
    SetFolder ( true );
53
254k
    aEntry.nVersion     = -1;
54
254k
    aEntry.nFlag        = 0;
55
254k
    aEntry.nMethod      = STORED;
56
254k
    aEntry.nTime        = -1;
57
254k
    aEntry.nCrc         = 0;
58
254k
    aEntry.nCompressedSize = 0;
59
254k
    aEntry.nSize        = 0;
60
254k
    aEntry.nOffset      = -1;
61
254k
}
62
63
ZipPackageFolder::~ZipPackageFolder()
64
254k
{
65
254k
}
66
67
bool ZipPackageFolder::LookForUnexpectedODF12Streams(
68
        std::u16string_view const aPath, bool const isWholesomeEncryption)
69
0
{
70
0
    bool bHasUnexpected = false;
71
72
0
    for (const auto& [rShortName, rInfo] : maContents)
73
0
    {
74
0
        if ( rInfo.bFolder )
75
0
        {
76
0
            if ( aPath == u"META-INF/" )
77
0
            {
78
                // META-INF is not allowed to contain subfolders
79
0
                bHasUnexpected = true;
80
0
            }
81
0
            else if (isWholesomeEncryption && rShortName != u"META-INF")
82
0
            {
83
0
                bHasUnexpected = true;
84
0
            }
85
0
            else
86
0
            {
87
0
                OUString sOwnPath = aPath + rShortName + "/";
88
0
                bHasUnexpected = rInfo.pFolder->LookForUnexpectedODF12Streams(sOwnPath, isWholesomeEncryption);
89
0
            }
90
0
        }
91
0
        else
92
0
        {
93
0
            if ( aPath == u"META-INF/" )
94
0
            {
95
0
                if ( rShortName != "manifest.xml"
96
0
                  && rShortName.indexOf( "signatures" ) == -1
97
0
                  && !rInfo.pStream->IsFromManifest() )
98
0
                {
99
                    // a stream from META-INF with unexpected name
100
0
                    bHasUnexpected = true;
101
0
                }
102
103
                // streams from META-INF with expected names (manifest.xml, or containing
104
                // "signatures", e.g. documentsignatures.xml) are allowed not to be registered
105
                // in manifest.xml; any other stream in META-INF is allowed as long as it is
106
                // explicitly registered in manifest.xml, matching the ODF Extended Package
107
                // class (ODF 1.3 Part 2, 2.2.2), which permits additional META-INF members
108
                // beyond those in the ordinary Package class (2.2.1(E))
109
0
            }
110
0
            else if (isWholesomeEncryption && rShortName != "mimetype" && rShortName != "encrypted-package")
111
0
            {
112
0
                bHasUnexpected = true;
113
0
            }
114
0
            else if ( !rInfo.pStream->IsFromManifest() )
115
0
            {
116
                // the stream is not in META-INF and is not registered in manifest.xml,
117
                // check whether it is an internal part of the package format
118
0
                if ( !aPath.empty() || rShortName != "mimetype" )
119
0
                {
120
                    // if it is not "mimetype" from the root it is not a part of the package
121
0
                    bHasUnexpected = true;
122
0
                }
123
0
            }
124
0
        }
125
126
0
        if (bHasUnexpected)
127
0
            break;
128
0
    }
129
130
0
    return bHasUnexpected;
131
0
}
132
133
void ZipPackageFolder::setChildStreamsTypeByExtension( const beans::StringPair& aPair )
134
132k
{
135
132k
    OUString aExt;
136
132k
    if ( aPair.First.toChar() == '.' )
137
0
        aExt = aPair.First;
138
132k
    else
139
132k
        aExt = "." + aPair.First;
140
141
132k
    for (const auto& [rShortName, rInfo] : maContents)
142
395k
    {
143
395k
        if ( rInfo.bFolder )
144
119k
            rInfo.pFolder->setChildStreamsTypeByExtension( aPair );
145
276k
        else
146
276k
        {
147
276k
            sal_Int32 nPathLength = rShortName.getLength();
148
276k
            sal_Int32 nExtLength = aExt.getLength();
149
276k
            if ( nPathLength >= nExtLength && rShortName.match( aExt, nPathLength - nExtLength ) )
150
82.7k
                rInfo.pStream->SetMediaType( aPair.Second );
151
276k
        }
152
395k
    }
153
132k
}
154
155
    // XNameContainer
156
void SAL_CALL ZipPackageFolder::insertByName( const OUString& aName, const uno::Any& aElement )
157
0
{
158
0
    if (hasByName(aName))
159
0
        throw ElementExistException();
160
161
0
    uno::Reference < XInterface > xRef;
162
0
    if ( !(aElement >>= xRef) )
163
0
        throw IllegalArgumentException(u""_ustr, uno::Reference< uno::XInterface >(), 0 );
164
165
0
    ZipPackageEntry* pEntry = dynamic_cast<ZipPackageFolder*>(xRef.get());
166
0
    if (!pEntry)
167
0
        pEntry = dynamic_cast<ZipPackageStream*>(xRef.get());
168
0
    if (!pEntry)
169
0
       throw IllegalArgumentException(u""_ustr, uno::Reference< uno::XInterface >(), 0 );
170
171
0
    if (pEntry->getName() != aName )
172
0
        pEntry->setName (aName);
173
0
    doInsertByName ( pEntry, true );
174
0
}
175
176
void SAL_CALL ZipPackageFolder::removeByName( const OUString& Name )
177
0
{
178
0
    return removeByName(std::u16string_view(Name));
179
0
}
180
181
void ZipPackageFolder::removeByName( std::u16string_view aName )
182
12.2k
{
183
12.2k
    ContentHash::iterator aIter = maContents.find ( aName );
184
12.2k
    if ( aIter == maContents.end() )
185
0
        throw NoSuchElementException();
186
12.2k
    maContents.erase( aIter );
187
12.2k
}
188
    // XEnumerationAccess
189
uno::Reference< XEnumeration > SAL_CALL ZipPackageFolder::createEnumeration(  )
190
178k
{
191
178k
    return uno::Reference < XEnumeration> (new ZipPackageFolderEnumeration(maContents));
192
178k
}
193
    // XElementAccess
194
uno::Type SAL_CALL ZipPackageFolder::getElementType(  )
195
0
{
196
0
    return cppu::UnoType<XInterface>::get();
197
0
}
198
sal_Bool SAL_CALL ZipPackageFolder::hasElements(  )
199
0
{
200
0
    return !maContents.empty();
201
0
}
202
    // XNameAccess
203
ZipContentInfo& ZipPackageFolder::doGetByName( std::u16string_view aName )
204
475k
{
205
475k
    ContentHash::iterator aIter = maContents.find ( aName );
206
475k
    if ( aIter == maContents.end())
207
0
        throw NoSuchElementException();
208
475k
    return aIter->second;
209
475k
}
210
211
uno::Any SAL_CALL ZipPackageFolder::getByName( const OUString& aName )
212
237k
{
213
237k
    return getByName(std::u16string_view(aName));
214
237k
}
215
uno::Any ZipPackageFolder::getByName( std::u16string_view aName )
216
359k
{
217
359k
    return uno::Any ( uno::Reference(cppu::getXWeak(doGetByName ( aName ).xPackageEntry.get())) );
218
359k
}
219
uno::Sequence< OUString > SAL_CALL ZipPackageFolder::getElementNames(  )
220
0
{
221
0
    return comphelper::mapKeysToSequence(maContents);
222
0
}
223
sal_Bool SAL_CALL ZipPackageFolder::hasByName( const OUString& aName )
224
515k
{
225
515k
    return hasByName( std::u16string_view( aName ));
226
515k
}
227
bool ZipPackageFolder::hasByName( std::u16string_view aName )
228
1.38M
{
229
1.38M
    return maContents.find ( aName ) != maContents.end ();
230
1.38M
}
231
    // XNameReplace
232
void SAL_CALL ZipPackageFolder::replaceByName( const OUString& aName, const uno::Any& aElement )
233
0
{
234
0
    if ( !hasByName( aName ) )
235
0
        throw NoSuchElementException();
236
237
0
    removeByName( aName );
238
0
    insertByName(aName, aElement);
239
0
}
240
241
bool ZipPackageFolder::saveChild(
242
        const OUString &rPath,
243
        std::vector < uno::Sequence < PropertyValue > > &rManList,
244
        ZipOutputStream & rZipOut,
245
        const uno::Sequence < sal_Int8 >& rEncryptionKey,
246
        ::std::optional<sal_Int32> const oPBKDF2IterationCount,
247
        ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args)
248
0
{
249
0
    uno::Sequence < PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST);
250
0
    OUString sTempName = rPath + "/";
251
252
0
    if ( !GetMediaType().isEmpty() )
253
0
    {
254
0
        auto pPropSet = aPropSet.getArray();
255
0
        pPropSet[PKG_MNFST_MEDIATYPE].Name = "MediaType";
256
0
        pPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType();
257
0
        pPropSet[PKG_MNFST_VERSION].Name = "Version";
258
0
        pPropSet[PKG_MNFST_VERSION].Value <<= GetVersion();
259
0
        pPropSet[PKG_MNFST_FULLPATH].Name = "FullPath";
260
0
        pPropSet[PKG_MNFST_FULLPATH].Value <<= sTempName;
261
0
    }
262
0
    else
263
0
        aPropSet.realloc( 0 );
264
265
0
    saveContents(sTempName, rManList, rZipOut, rEncryptionKey, oPBKDF2IterationCount, oArgon2Args);
266
267
    // folder can have a mediatype only in package format
268
0
    if ( aPropSet.hasElements() && ( m_nFormat == embed::StorageFormats::PACKAGE ) )
269
0
        rManList.push_back( aPropSet );
270
271
0
    return true;
272
0
}
273
274
void ZipPackageFolder::saveContents(
275
        const OUString &rPath,
276
        std::vector < uno::Sequence < PropertyValue > > &rManList,
277
        ZipOutputStream & rZipOut,
278
        const uno::Sequence < sal_Int8 >& rEncryptionKey,
279
        ::std::optional<sal_Int32> const oPBKDF2IterationCount,
280
        ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args) const
281
0
{
282
0
    if ( maContents.empty() && !rPath.isEmpty() && m_nFormat != embed::StorageFormats::OFOPXML )
283
0
    {
284
        // it is an empty subfolder, use workaround to store it
285
0
        auto pTempEntry = std::make_unique<ZipEntry>(aEntry);
286
0
        pTempEntry->nPathLen = static_cast<sal_Int16>( OUStringToOString( rPath, RTL_TEXTENCODING_UTF8 ).getLength() );
287
0
        pTempEntry->nExtraLen = -1;
288
0
        pTempEntry->sPath = rPath;
289
290
0
        try
291
0
        {
292
0
            ZipOutputStream::setEntry(*pTempEntry);
293
0
            rZipOut.writeLOC(std::move(pTempEntry));
294
0
            rZipOut.rawCloseEntry();
295
0
        }
296
0
        catch ( ZipException& )
297
0
        {
298
0
            throw uno::RuntimeException();
299
0
        }
300
0
        catch ( IOException& )
301
0
        {
302
0
            throw uno::RuntimeException();
303
0
        }
304
0
    }
305
306
0
    bool bMimeTypeStreamStored = false;
307
0
    OUString aMimeTypeStreamName(u"mimetype"_ustr);
308
0
    if ( m_nFormat == embed::StorageFormats::ZIP && rPath.isEmpty() )
309
0
    {
310
        // let the "mimetype" stream in root folder be stored as the first stream if it is zip format
311
0
        ContentHash::const_iterator aIter = maContents.find ( aMimeTypeStreamName );
312
0
        if ( aIter != maContents.end() && !(*aIter).second.bFolder )
313
0
        {
314
0
            bMimeTypeStreamStored = true;
315
0
            if (!aIter->second.pStream->saveChild(rPath + aIter->first, rManList, rZipOut,
316
0
                    rEncryptionKey, oPBKDF2IterationCount, oArgon2Args))
317
0
            {
318
0
                throw uno::RuntimeException();
319
0
            }
320
0
        }
321
0
    }
322
323
0
    for (const auto& [rShortName, rInfo] : maContents)
324
0
    {
325
0
        if ( !bMimeTypeStreamStored || rShortName != aMimeTypeStreamName )
326
0
        {
327
0
            if (rInfo.bFolder)
328
0
            {
329
0
                if (!rInfo.pFolder->saveChild(rPath + rShortName, rManList, rZipOut,
330
0
                        rEncryptionKey, oPBKDF2IterationCount, oArgon2Args))
331
0
                {
332
0
                    throw uno::RuntimeException();
333
0
                }
334
0
            }
335
0
            else
336
0
            {
337
0
                if (!rInfo.pStream->saveChild(rPath + rShortName, rManList, rZipOut,
338
0
                        rEncryptionKey, oPBKDF2IterationCount, oArgon2Args))
339
0
                {
340
0
                    throw uno::RuntimeException();
341
0
                }
342
0
            }
343
0
        }
344
0
    }
345
0
}
346
347
void SAL_CALL ZipPackageFolder::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
348
0
{
349
0
    if ( aPropertyName == "MediaType" )
350
0
    {
351
        // TODO/LATER: activate when zip ucp is ready
352
        // if ( m_nFormat != embed::StorageFormats::PACKAGE )
353
        //  throw UnknownPropertyException();
354
355
0
        aValue >>= msMediaType;
356
0
    }
357
0
    else if ( aPropertyName == "Version" )
358
0
        aValue >>= m_sVersion;
359
0
    else if ( aPropertyName == "Size" )
360
0
        aValue >>= aEntry.nSize;
361
0
    else
362
0
        throw UnknownPropertyException(aPropertyName);
363
0
}
364
uno::Any SAL_CALL ZipPackageFolder::getPropertyValue( const OUString& PropertyName )
365
65.5k
{
366
65.5k
    if ( PropertyName == "MediaType" )
367
32.7k
    {
368
        // TODO/LATER: activate when zip ucp is ready
369
        // if ( m_nFormat != embed::StorageFormats::PACKAGE )
370
        //  throw UnknownPropertyException();
371
372
32.7k
        return uno::Any ( msMediaType );
373
32.7k
    }
374
32.7k
    else if ( PropertyName == "Version" )
375
32.7k
        return uno::Any( m_sVersion );
376
0
    else if ( PropertyName == "Size" )
377
0
        return uno::Any ( aEntry.nSize );
378
0
    else
379
0
        throw UnknownPropertyException(PropertyName);
380
65.5k
}
381
382
void ZipPackageFolder::doInsertByName ( ZipPackageEntry *pEntry, bool bSetParent )
383
515k
{
384
515k
    if ( pEntry->IsFolder() )
385
153k
        maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageFolder*>(pEntry)));
386
362k
    else
387
362k
        maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageStream*>(pEntry)));
388
515k
    if ( bSetParent )
389
0
        pEntry->setParent ( *this );
390
515k
}
391
392
OUString ZipPackageFolder::getImplementationName()
393
0
{
394
0
    return u"ZipPackageFolder"_ustr;
395
0
}
396
397
uno::Sequence< OUString > ZipPackageFolder::getSupportedServiceNames()
398
0
{
399
0
    return { u"com.sun.star.packages.PackageFolder"_ustr };
400
0
}
401
402
sal_Bool SAL_CALL ZipPackageFolder::supportsService( OUString const & rServiceName )
403
0
{
404
0
    return cppu::supportsService(this, rServiceName);
405
0
}
406
407
408
ZipContentInfo::ZipContentInfo ( ZipPackageStream * pNewStream )
409
362k
: xPackageEntry ( pNewStream )
410
362k
, bFolder ( false )
411
362k
, pStream ( pNewStream )
412
362k
{
413
362k
}
414
415
ZipContentInfo::ZipContentInfo ( ZipPackageFolder * pNewFolder )
416
153k
: xPackageEntry ( pNewFolder )
417
153k
, bFolder ( true )
418
153k
, pFolder ( pNewFolder )
419
153k
{
420
153k
}
421
422
0
ZipContentInfo::ZipContentInfo( const ZipContentInfo& ) = default;
423
515k
ZipContentInfo::ZipContentInfo( ZipContentInfo&& ) = default;
424
0
ZipContentInfo& ZipContentInfo::operator=( const ZipContentInfo& ) = default;
425
0
ZipContentInfo& ZipContentInfo::operator=( ZipContentInfo&& ) = default;
426
427
ZipContentInfo::~ZipContentInfo()
428
1.03M
{
429
1.03M
    if ( bFolder )
430
306k
        pFolder->clearParent();
431
724k
    else
432
724k
        pStream->clearParent();
433
1.03M
}
434
435
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */