Coverage Report

Created: 2025-12-31 10:39

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
#if OSL_DEBUG_LEVEL > 0
46
#define THROW_WHERE SAL_WHERE
47
#else
48
0
#define THROW_WHERE ""
49
#endif
50
51
ZipPackageFolder::ZipPackageFolder( const css::uno::Reference < css::uno::XComponentContext >& xContext,
52
                                    sal_Int32 nFormat,
53
                                    bool bAllowRemoveOnInsert )
54
375k
{
55
375k
    m_xContext = xContext;
56
375k
    m_nFormat = nFormat;
57
375k
    mbAllowRemoveOnInsert = bAllowRemoveOnInsert;
58
375k
    SetFolder ( true );
59
375k
    aEntry.nVersion     = -1;
60
375k
    aEntry.nFlag        = 0;
61
375k
    aEntry.nMethod      = STORED;
62
375k
    aEntry.nTime        = -1;
63
375k
    aEntry.nCrc         = 0;
64
375k
    aEntry.nCompressedSize = 0;
65
375k
    aEntry.nSize        = 0;
66
375k
    aEntry.nOffset      = -1;
67
375k
}
68
69
ZipPackageFolder::~ZipPackageFolder()
70
375k
{
71
375k
}
72
73
bool ZipPackageFolder::LookForUnexpectedODF12Streams(
74
        std::u16string_view const aPath, bool const isWholesomeEncryption)
75
0
{
76
0
    bool bHasUnexpected = false;
77
78
0
    for (const auto& [rShortName, rInfo] : maContents)
79
0
    {
80
0
        if ( rInfo.bFolder )
81
0
        {
82
0
            if ( aPath == u"META-INF/" )
83
0
            {
84
                // META-INF is not allowed to contain subfolders
85
0
                bHasUnexpected = true;
86
0
            }
87
0
            else if (isWholesomeEncryption && rShortName != u"META-INF")
88
0
            {
89
0
                bHasUnexpected = true;
90
0
            }
91
0
            else
92
0
            {
93
0
                OUString sOwnPath = aPath + rShortName + "/";
94
0
                bHasUnexpected = rInfo.pFolder->LookForUnexpectedODF12Streams(sOwnPath, isWholesomeEncryption);
95
0
            }
96
0
        }
97
0
        else
98
0
        {
99
0
            if ( aPath == u"META-INF/" )
100
0
            {
101
0
                if ( rShortName != "manifest.xml"
102
0
                  && rShortName.indexOf( "signatures" ) == -1 )
103
0
                {
104
                    // a stream from META-INF with unexpected name
105
0
                    bHasUnexpected = true;
106
0
                }
107
108
                // streams from META-INF with expected names are allowed not to be registered in manifest.xml
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
150k
{
135
150k
    OUString aExt;
136
150k
    if ( aPair.First.toChar() == '.' )
137
0
        aExt = aPair.First;
138
150k
    else
139
150k
        aExt = "." + aPair.First;
140
141
150k
    for (const auto& [rShortName, rInfo] : maContents)
142
454k
    {
143
454k
        if ( rInfo.bFolder )
144
135k
            rInfo.pFolder->setChildStreamsTypeByExtension( aPair );
145
319k
        else
146
319k
        {
147
319k
            sal_Int32 nPathLength = rShortName.getLength();
148
319k
            sal_Int32 nExtLength = aExt.getLength();
149
319k
            if ( nPathLength >= nExtLength && rShortName.match( aExt, nPathLength - nExtLength ) )
150
96.2k
                rInfo.pStream->SetMediaType( aPair.Second );
151
319k
        }
152
454k
    }
153
150k
}
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(THROW_WHERE );
160
161
0
    uno::Reference < XInterface > xRef;
162
0
    if ( !(aElement >>= xRef) )
163
0
        throw IllegalArgumentException(THROW_WHERE, 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(THROW_WHERE, 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
14.1k
{
183
14.1k
    ContentHash::iterator aIter = maContents.find ( aName );
184
14.1k
    if ( aIter == maContents.end() )
185
0
        throw NoSuchElementException(THROW_WHERE );
186
14.1k
    maContents.erase( aIter );
187
14.1k
}
188
    // XEnumerationAccess
189
uno::Reference< XEnumeration > SAL_CALL ZipPackageFolder::createEnumeration(  )
190
287k
{
191
287k
    return uno::Reference < XEnumeration> (new ZipPackageFolderEnumeration(maContents));
192
287k
}
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
547k
{
205
547k
    ContentHash::iterator aIter = maContents.find ( aName );
206
547k
    if ( aIter == maContents.end())
207
0
        throw NoSuchElementException(THROW_WHERE );
208
547k
    return aIter->second;
209
547k
}
210
211
uno::Any SAL_CALL ZipPackageFolder::getByName( const OUString& aName )
212
272k
{
213
272k
    return getByName(std::u16string_view(aName));
214
272k
}
215
uno::Any ZipPackageFolder::getByName( std::u16string_view aName )
216
413k
{
217
413k
    return uno::Any ( uno::Reference(cppu::getXWeak(doGetByName ( aName ).xPackageEntry.get())) );
218
413k
}
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
611k
{
225
611k
    return hasByName( std::u16string_view( aName ));
226
611k
}
227
bool ZipPackageFolder::hasByName( std::u16string_view aName )
228
1.62M
{
229
1.62M
    return maContents.find ( aName ) != maContents.end ();
230
1.62M
}
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(THROW_WHERE );
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( THROW_WHERE );
299
0
        }
300
0
        catch ( IOException& )
301
0
        {
302
0
            throw uno::RuntimeException( THROW_WHERE );
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( THROW_WHERE );
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( THROW_WHERE );
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( THROW_WHERE );
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(THROW_WHERE );
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
62.8k
{
366
62.8k
    if ( PropertyName == "MediaType" )
367
31.4k
    {
368
        // TODO/LATER: activate when zip ucp is ready
369
        // if ( m_nFormat != embed::StorageFormats::PACKAGE )
370
        //  throw UnknownPropertyException(THROW_WHERE );
371
372
31.4k
        return uno::Any ( msMediaType );
373
31.4k
    }
374
31.4k
    else if ( PropertyName == "Version" )
375
31.4k
        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
62.8k
}
381
382
void ZipPackageFolder::doInsertByName ( ZipPackageEntry *pEntry, bool bSetParent )
383
611k
{
384
611k
    if ( pEntry->IsFolder() )
385
178k
        maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageFolder*>(pEntry)));
386
433k
    else
387
433k
        maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageStream*>(pEntry)));
388
611k
    if ( bSetParent )
389
0
        pEntry->setParent ( *this );
390
611k
}
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
433k
: xPackageEntry ( pNewStream )
410
433k
, bFolder ( false )
411
433k
, pStream ( pNewStream )
412
433k
{
413
433k
}
414
415
ZipContentInfo::ZipContentInfo ( ZipPackageFolder * pNewFolder )
416
178k
: xPackageEntry ( pNewFolder )
417
178k
, bFolder ( true )
418
178k
, pFolder ( pNewFolder )
419
178k
{
420
178k
}
421
422
0
ZipContentInfo::ZipContentInfo( const ZipContentInfo& ) = default;
423
611k
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.22M
{
429
1.22M
    if ( bFolder )
430
356k
        pFolder->clearParent();
431
867k
    else
432
867k
        pStream->clearParent();
433
1.22M
}
434
435
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */