Coverage Report

Created: 2026-05-16 09:25

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
311k
{
49
311k
    m_xContext = xContext;
50
311k
    m_nFormat = nFormat;
51
311k
    mbAllowRemoveOnInsert = bAllowRemoveOnInsert;
52
311k
    SetFolder ( true );
53
311k
    aEntry.nVersion     = -1;
54
311k
    aEntry.nFlag        = 0;
55
311k
    aEntry.nMethod      = STORED;
56
311k
    aEntry.nTime        = -1;
57
311k
    aEntry.nCrc         = 0;
58
311k
    aEntry.nCompressedSize = 0;
59
311k
    aEntry.nSize        = 0;
60
311k
    aEntry.nOffset      = -1;
61
311k
}
62
63
ZipPackageFolder::~ZipPackageFolder()
64
310k
{
65
310k
}
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
                {
98
                    // a stream from META-INF with unexpected name
99
0
                    bHasUnexpected = true;
100
0
                }
101
102
                // streams from META-INF with expected names are allowed not to be registered in manifest.xml
103
0
            }
104
0
            else if (isWholesomeEncryption && rShortName != "mimetype" && rShortName != "encrypted-package")
105
0
            {
106
0
                bHasUnexpected = true;
107
0
            }
108
0
            else if ( !rInfo.pStream->IsFromManifest() )
109
0
            {
110
                // the stream is not in META-INF and is not registered in manifest.xml,
111
                // check whether it is an internal part of the package format
112
0
                if ( !aPath.empty() || rShortName != "mimetype" )
113
0
                {
114
                    // if it is not "mimetype" from the root it is not a part of the package
115
0
                    bHasUnexpected = true;
116
0
                }
117
0
            }
118
0
        }
119
120
0
        if (bHasUnexpected)
121
0
            break;
122
0
    }
123
124
0
    return bHasUnexpected;
125
0
}
126
127
void ZipPackageFolder::setChildStreamsTypeByExtension( const beans::StringPair& aPair )
128
126k
{
129
126k
    OUString aExt;
130
126k
    if ( aPair.First.toChar() == '.' )
131
0
        aExt = aPair.First;
132
126k
    else
133
126k
        aExt = "." + aPair.First;
134
135
126k
    for (const auto& [rShortName, rInfo] : maContents)
136
381k
    {
137
381k
        if ( rInfo.bFolder )
138
114k
            rInfo.pFolder->setChildStreamsTypeByExtension( aPair );
139
267k
        else
140
267k
        {
141
267k
            sal_Int32 nPathLength = rShortName.getLength();
142
267k
            sal_Int32 nExtLength = aExt.getLength();
143
267k
            if ( nPathLength >= nExtLength && rShortName.match( aExt, nPathLength - nExtLength ) )
144
81.1k
                rInfo.pStream->SetMediaType( aPair.Second );
145
267k
        }
146
381k
    }
147
126k
}
148
149
    // XNameContainer
150
void SAL_CALL ZipPackageFolder::insertByName( const OUString& aName, const uno::Any& aElement )
151
0
{
152
0
    if (hasByName(aName))
153
0
        throw ElementExistException();
154
155
0
    uno::Reference < XInterface > xRef;
156
0
    if ( !(aElement >>= xRef) )
157
0
        throw IllegalArgumentException(u""_ustr, uno::Reference< uno::XInterface >(), 0 );
158
159
0
    ZipPackageEntry* pEntry = dynamic_cast<ZipPackageFolder*>(xRef.get());
160
0
    if (!pEntry)
161
0
        pEntry = dynamic_cast<ZipPackageStream*>(xRef.get());
162
0
    if (!pEntry)
163
0
       throw IllegalArgumentException(u""_ustr, uno::Reference< uno::XInterface >(), 0 );
164
165
0
    if (pEntry->getName() != aName )
166
0
        pEntry->setName (aName);
167
0
    doInsertByName ( pEntry, true );
168
0
}
169
170
void SAL_CALL ZipPackageFolder::removeByName( const OUString& Name )
171
0
{
172
0
    return removeByName(std::u16string_view(Name));
173
0
}
174
175
void ZipPackageFolder::removeByName( std::u16string_view aName )
176
11.0k
{
177
11.0k
    ContentHash::iterator aIter = maContents.find ( aName );
178
11.0k
    if ( aIter == maContents.end() )
179
0
        throw NoSuchElementException();
180
11.0k
    maContents.erase( aIter );
181
11.0k
}
182
    // XEnumerationAccess
183
uno::Reference< XEnumeration > SAL_CALL ZipPackageFolder::createEnumeration(  )
184
240k
{
185
240k
    return uno::Reference < XEnumeration> (new ZipPackageFolderEnumeration(maContents));
186
240k
}
187
    // XElementAccess
188
uno::Type SAL_CALL ZipPackageFolder::getElementType(  )
189
0
{
190
0
    return cppu::UnoType<XInterface>::get();
191
0
}
192
sal_Bool SAL_CALL ZipPackageFolder::hasElements(  )
193
0
{
194
0
    return !maContents.empty();
195
0
}
196
    // XNameAccess
197
ZipContentInfo& ZipPackageFolder::doGetByName( std::u16string_view aName )
198
446k
{
199
446k
    ContentHash::iterator aIter = maContents.find ( aName );
200
446k
    if ( aIter == maContents.end())
201
0
        throw NoSuchElementException();
202
446k
    return aIter->second;
203
446k
}
204
205
uno::Any SAL_CALL ZipPackageFolder::getByName( const OUString& aName )
206
221k
{
207
221k
    return getByName(std::u16string_view(aName));
208
221k
}
209
uno::Any ZipPackageFolder::getByName( std::u16string_view aName )
210
335k
{
211
335k
    return uno::Any ( uno::Reference(cppu::getXWeak(doGetByName ( aName ).xPackageEntry.get())) );
212
335k
}
213
uno::Sequence< OUString > SAL_CALL ZipPackageFolder::getElementNames(  )
214
0
{
215
0
    return comphelper::mapKeysToSequence(maContents);
216
0
}
217
sal_Bool SAL_CALL ZipPackageFolder::hasByName( const OUString& aName )
218
481k
{
219
481k
    return hasByName( std::u16string_view( aName ));
220
481k
}
221
bool ZipPackageFolder::hasByName( std::u16string_view aName )
222
1.29M
{
223
1.29M
    return maContents.find ( aName ) != maContents.end ();
224
1.29M
}
225
    // XNameReplace
226
void SAL_CALL ZipPackageFolder::replaceByName( const OUString& aName, const uno::Any& aElement )
227
0
{
228
0
    if ( !hasByName( aName ) )
229
0
        throw NoSuchElementException();
230
231
0
    removeByName( aName );
232
0
    insertByName(aName, aElement);
233
0
}
234
235
bool ZipPackageFolder::saveChild(
236
        const OUString &rPath,
237
        std::vector < uno::Sequence < PropertyValue > > &rManList,
238
        ZipOutputStream & rZipOut,
239
        const uno::Sequence < sal_Int8 >& rEncryptionKey,
240
        ::std::optional<sal_Int32> const oPBKDF2IterationCount,
241
        ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args)
242
0
{
243
0
    uno::Sequence < PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST);
244
0
    OUString sTempName = rPath + "/";
245
246
0
    if ( !GetMediaType().isEmpty() )
247
0
    {
248
0
        auto pPropSet = aPropSet.getArray();
249
0
        pPropSet[PKG_MNFST_MEDIATYPE].Name = "MediaType";
250
0
        pPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType();
251
0
        pPropSet[PKG_MNFST_VERSION].Name = "Version";
252
0
        pPropSet[PKG_MNFST_VERSION].Value <<= GetVersion();
253
0
        pPropSet[PKG_MNFST_FULLPATH].Name = "FullPath";
254
0
        pPropSet[PKG_MNFST_FULLPATH].Value <<= sTempName;
255
0
    }
256
0
    else
257
0
        aPropSet.realloc( 0 );
258
259
0
    saveContents(sTempName, rManList, rZipOut, rEncryptionKey, oPBKDF2IterationCount, oArgon2Args);
260
261
    // folder can have a mediatype only in package format
262
0
    if ( aPropSet.hasElements() && ( m_nFormat == embed::StorageFormats::PACKAGE ) )
263
0
        rManList.push_back( aPropSet );
264
265
0
    return true;
266
0
}
267
268
void ZipPackageFolder::saveContents(
269
        const OUString &rPath,
270
        std::vector < uno::Sequence < PropertyValue > > &rManList,
271
        ZipOutputStream & rZipOut,
272
        const uno::Sequence < sal_Int8 >& rEncryptionKey,
273
        ::std::optional<sal_Int32> const oPBKDF2IterationCount,
274
        ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args) const
275
0
{
276
0
    if ( maContents.empty() && !rPath.isEmpty() && m_nFormat != embed::StorageFormats::OFOPXML )
277
0
    {
278
        // it is an empty subfolder, use workaround to store it
279
0
        auto pTempEntry = std::make_unique<ZipEntry>(aEntry);
280
0
        pTempEntry->nPathLen = static_cast<sal_Int16>( OUStringToOString( rPath, RTL_TEXTENCODING_UTF8 ).getLength() );
281
0
        pTempEntry->nExtraLen = -1;
282
0
        pTempEntry->sPath = rPath;
283
284
0
        try
285
0
        {
286
0
            ZipOutputStream::setEntry(*pTempEntry);
287
0
            rZipOut.writeLOC(std::move(pTempEntry));
288
0
            rZipOut.rawCloseEntry();
289
0
        }
290
0
        catch ( ZipException& )
291
0
        {
292
0
            throw uno::RuntimeException();
293
0
        }
294
0
        catch ( IOException& )
295
0
        {
296
0
            throw uno::RuntimeException();
297
0
        }
298
0
    }
299
300
0
    bool bMimeTypeStreamStored = false;
301
0
    OUString aMimeTypeStreamName(u"mimetype"_ustr);
302
0
    if ( m_nFormat == embed::StorageFormats::ZIP && rPath.isEmpty() )
303
0
    {
304
        // let the "mimetype" stream in root folder be stored as the first stream if it is zip format
305
0
        ContentHash::const_iterator aIter = maContents.find ( aMimeTypeStreamName );
306
0
        if ( aIter != maContents.end() && !(*aIter).second.bFolder )
307
0
        {
308
0
            bMimeTypeStreamStored = true;
309
0
            if (!aIter->second.pStream->saveChild(rPath + aIter->first, rManList, rZipOut,
310
0
                    rEncryptionKey, oPBKDF2IterationCount, oArgon2Args))
311
0
            {
312
0
                throw uno::RuntimeException();
313
0
            }
314
0
        }
315
0
    }
316
317
0
    for (const auto& [rShortName, rInfo] : maContents)
318
0
    {
319
0
        if ( !bMimeTypeStreamStored || rShortName != aMimeTypeStreamName )
320
0
        {
321
0
            if (rInfo.bFolder)
322
0
            {
323
0
                if (!rInfo.pFolder->saveChild(rPath + rShortName, rManList, rZipOut,
324
0
                        rEncryptionKey, oPBKDF2IterationCount, oArgon2Args))
325
0
                {
326
0
                    throw uno::RuntimeException();
327
0
                }
328
0
            }
329
0
            else
330
0
            {
331
0
                if (!rInfo.pStream->saveChild(rPath + rShortName, rManList, rZipOut,
332
0
                        rEncryptionKey, oPBKDF2IterationCount, oArgon2Args))
333
0
                {
334
0
                    throw uno::RuntimeException();
335
0
                }
336
0
            }
337
0
        }
338
0
    }
339
0
}
340
341
void SAL_CALL ZipPackageFolder::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
342
0
{
343
0
    if ( aPropertyName == "MediaType" )
344
0
    {
345
        // TODO/LATER: activate when zip ucp is ready
346
        // if ( m_nFormat != embed::StorageFormats::PACKAGE )
347
        //  throw UnknownPropertyException();
348
349
0
        aValue >>= msMediaType;
350
0
    }
351
0
    else if ( aPropertyName == "Version" )
352
0
        aValue >>= m_sVersion;
353
0
    else if ( aPropertyName == "Size" )
354
0
        aValue >>= aEntry.nSize;
355
0
    else
356
0
        throw UnknownPropertyException(aPropertyName);
357
0
}
358
uno::Any SAL_CALL ZipPackageFolder::getPropertyValue( const OUString& PropertyName )
359
53.8k
{
360
53.8k
    if ( PropertyName == "MediaType" )
361
26.9k
    {
362
        // TODO/LATER: activate when zip ucp is ready
363
        // if ( m_nFormat != embed::StorageFormats::PACKAGE )
364
        //  throw UnknownPropertyException();
365
366
26.9k
        return uno::Any ( msMediaType );
367
26.9k
    }
368
26.9k
    else if ( PropertyName == "Version" )
369
26.9k
        return uno::Any( m_sVersion );
370
0
    else if ( PropertyName == "Size" )
371
0
        return uno::Any ( aEntry.nSize );
372
0
    else
373
0
        throw UnknownPropertyException(PropertyName);
374
53.8k
}
375
376
void ZipPackageFolder::doInsertByName ( ZipPackageEntry *pEntry, bool bSetParent )
377
481k
{
378
481k
    if ( pEntry->IsFolder() )
379
143k
        maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageFolder*>(pEntry)));
380
338k
    else
381
338k
        maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageStream*>(pEntry)));
382
481k
    if ( bSetParent )
383
0
        pEntry->setParent ( *this );
384
481k
}
385
386
OUString ZipPackageFolder::getImplementationName()
387
0
{
388
0
    return u"ZipPackageFolder"_ustr;
389
0
}
390
391
uno::Sequence< OUString > ZipPackageFolder::getSupportedServiceNames()
392
0
{
393
0
    return { u"com.sun.star.packages.PackageFolder"_ustr };
394
0
}
395
396
sal_Bool SAL_CALL ZipPackageFolder::supportsService( OUString const & rServiceName )
397
0
{
398
0
    return cppu::supportsService(this, rServiceName);
399
0
}
400
401
402
ZipContentInfo::ZipContentInfo ( ZipPackageStream * pNewStream )
403
338k
: xPackageEntry ( pNewStream )
404
338k
, bFolder ( false )
405
338k
, pStream ( pNewStream )
406
338k
{
407
338k
}
408
409
ZipContentInfo::ZipContentInfo ( ZipPackageFolder * pNewFolder )
410
143k
: xPackageEntry ( pNewFolder )
411
143k
, bFolder ( true )
412
143k
, pFolder ( pNewFolder )
413
143k
{
414
143k
}
415
416
0
ZipContentInfo::ZipContentInfo( const ZipContentInfo& ) = default;
417
481k
ZipContentInfo::ZipContentInfo( ZipContentInfo&& ) = default;
418
0
ZipContentInfo& ZipContentInfo::operator=( const ZipContentInfo& ) = default;
419
0
ZipContentInfo& ZipContentInfo::operator=( ZipContentInfo&& ) = default;
420
421
ZipContentInfo::~ZipContentInfo()
422
963k
{
423
963k
    if ( bFolder )
424
286k
        pFolder->clearParent();
425
677k
    else
426
677k
        pStream->clearParent();
427
963k
}
428
429
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */