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 <string.h>
21 
22 #include <ZipPackageFolder.hxx>
23 #include <ZipFile.hxx>
24 #include <ZipOutputStream.hxx>
25 #include <ZipPackageStream.hxx>
26 #include <PackageConstants.hxx>
27 #include "ZipPackageFolderEnumeration.hxx"
28 #include <com/sun/star/packages/zip/ZipConstants.hpp>
29 #include <com/sun/star/embed/StorageFormats.hpp>
30 #include <comphelper/sequence.hxx>
31 #include <comphelper/servicehelper.hxx>
32 #include <cppuhelper/supportsservice.hxx>
33 #include <cppuhelper/typeprovider.hxx>
34 #include <osl/diagnose.h>
35 #include <sal/log.hxx>
36 #include <rtl/digest.h>
37 #include "ContentInfo.hxx"
38 #include <com/sun/star/beans/PropertyValue.hpp>
39 #include <EncryptedDataHeader.hxx>
40 #include <rtl/instance.hxx>
41 
42 using namespace com::sun::star;
43 using namespace com::sun::star::packages::zip::ZipConstants;
44 using namespace com::sun::star::packages::zip;
45 using namespace com::sun::star::packages;
46 using namespace com::sun::star::container;
47 using namespace com::sun::star::beans;
48 using namespace com::sun::star::lang;
49 using namespace com::sun::star::io;
50 using namespace cppu;
51 
52 #if OSL_DEBUG_LEVEL > 0
53 #define THROW_WHERE SAL_WHERE
54 #else
55 #define THROW_WHERE ""
56 #endif
57 
58 namespace { struct lcl_CachedImplId : public rtl::Static< cppu::OImplementationId, lcl_CachedImplId > {}; }
59 
ZipPackageFolder(const css::uno::Reference<css::uno::XComponentContext> & xContext,sal_Int32 nFormat,bool bAllowRemoveOnInsert)60 ZipPackageFolder::ZipPackageFolder( const css::uno::Reference < css::uno::XComponentContext >& xContext,
61                                     sal_Int32 nFormat,
62                                     bool bAllowRemoveOnInsert )
63 {
64     m_xContext = xContext;
65     m_nFormat = nFormat;
66     mbAllowRemoveOnInsert = bAllowRemoveOnInsert;
67     SetFolder ( true );
68     aEntry.nVersion     = -1;
69     aEntry.nFlag        = 0;
70     aEntry.nMethod      = STORED;
71     aEntry.nTime        = -1;
72     aEntry.nCrc         = 0;
73     aEntry.nCompressedSize = 0;
74     aEntry.nSize        = 0;
75     aEntry.nOffset      = -1;
76 }
77 
~ZipPackageFolder()78 ZipPackageFolder::~ZipPackageFolder()
79 {
80 }
81 
LookForUnexpectedODF12Streams(const OUString & aPath)82 bool ZipPackageFolder::LookForUnexpectedODF12Streams( const OUString& aPath )
83 {
84     bool bHasUnexpected = false;
85 
86     for (const auto& [rShortName, rxInfo] : maContents)
87     {
88         const ZipContentInfo &rInfo = *rxInfo;
89 
90         if ( rInfo.bFolder )
91         {
92             if ( aPath == "META-INF/" )
93             {
94                 // META-INF is not allowed to contain subfolders
95                 bHasUnexpected = true;
96             }
97             else
98             {
99                 OUString sOwnPath = aPath + rShortName + "/";
100                 bHasUnexpected = rInfo.pFolder->LookForUnexpectedODF12Streams( sOwnPath );
101             }
102         }
103         else
104         {
105             if ( aPath == "META-INF/" )
106             {
107                 if ( rShortName != "manifest.xml"
108                   && rShortName.indexOf( "signatures" ) == -1 )
109                 {
110                     // a stream from META-INF with unexpected name
111                     bHasUnexpected = true;
112                 }
113 
114                 // streams from META-INF with expected names are allowed not to be registered in manifest.xml
115             }
116             else if ( !rInfo.pStream->IsFromManifest() )
117             {
118                 // the stream is not in META-INF and is not registered in manifest.xml,
119                 // check whether it is an internal part of the package format
120                 if ( !aPath.isEmpty() || rShortName != "mimetype" )
121                 {
122                     // if it is not "mimetype" from the root it is not a part of the package
123                     bHasUnexpected = true;
124                 }
125             }
126         }
127 
128         if (bHasUnexpected)
129             break;
130     }
131 
132     return bHasUnexpected;
133 }
134 
setChildStreamsTypeByExtension(const beans::StringPair & aPair)135 void ZipPackageFolder::setChildStreamsTypeByExtension( const beans::StringPair& aPair )
136 {
137     OUString aExt;
138     if ( aPair.First.toChar() == '.' )
139         aExt = aPair.First;
140     else
141         aExt = "." + aPair.First;
142 
143     for (const auto& [rShortName, rxInfo] : maContents)
144     {
145         const ZipContentInfo &rInfo = *rxInfo;
146 
147         if ( rInfo.bFolder )
148             rInfo.pFolder->setChildStreamsTypeByExtension( aPair );
149         else
150         {
151             sal_Int32 nPathLength = rShortName.getLength();
152             sal_Int32 nExtLength = aExt.getLength();
153             if ( nPathLength >= nExtLength && rShortName.match( aExt, nPathLength - nExtLength ) )
154                 rInfo.pStream->SetMediaType( aPair.Second );
155         }
156     }
157 }
158 
getUnoTunnelId()159 css::uno::Sequence < sal_Int8 > ZipPackageFolder::getUnoTunnelId()
160 {
161     return lcl_CachedImplId::get().getImplementationId();
162 }
163 
164     // XNameContainer
insertByName(const OUString & aName,const uno::Any & aElement)165 void SAL_CALL ZipPackageFolder::insertByName( const OUString& aName, const uno::Any& aElement )
166 {
167     if (hasByName(aName))
168         throw ElementExistException(THROW_WHERE );
169 
170     uno::Reference < XUnoTunnel > xRef;
171     aElement >>= xRef;
172     if ( !(aElement >>= xRef) )
173         throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 0 );
174 
175     sal_Int64 nTest;
176     ZipPackageEntry *pEntry;
177     if ( ( nTest = xRef->getSomething ( ZipPackageFolder::getUnoTunnelId() ) ) != 0 )
178     {
179         ZipPackageFolder *pFolder = reinterpret_cast < ZipPackageFolder * > ( nTest );
180         pEntry = static_cast < ZipPackageEntry * > ( pFolder );
181     }
182     else if ( ( nTest = xRef->getSomething ( ZipPackageStream::getUnoTunnelId() ) ) != 0 )
183     {
184         ZipPackageStream *pStream = reinterpret_cast < ZipPackageStream * > ( nTest );
185         pEntry = static_cast < ZipPackageEntry * > ( pStream );
186     }
187     else
188        throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 0 );
189 
190     if (pEntry->getName() != aName )
191         pEntry->setName (aName);
192     doInsertByName ( pEntry, true );
193 }
194 
removeByName(const OUString & Name)195 void SAL_CALL ZipPackageFolder::removeByName( const OUString& Name )
196 {
197     ContentHash::iterator aIter = maContents.find ( Name );
198     if ( aIter == maContents.end() )
199         throw NoSuchElementException(THROW_WHERE );
200     maContents.erase( aIter );
201 }
202     // XEnumerationAccess
createEnumeration()203 uno::Reference< XEnumeration > SAL_CALL ZipPackageFolder::createEnumeration(  )
204 {
205     return uno::Reference < XEnumeration> (new ZipPackageFolderEnumeration(maContents));
206 }
207     // XElementAccess
getElementType()208 uno::Type SAL_CALL ZipPackageFolder::getElementType(  )
209 {
210     return cppu::UnoType<XUnoTunnel>::get();
211 }
hasElements()212 sal_Bool SAL_CALL ZipPackageFolder::hasElements(  )
213 {
214     return !maContents.empty();
215 }
216     // XNameAccess
doGetByName(const OUString & aName)217 ZipContentInfo& ZipPackageFolder::doGetByName( const OUString& aName )
218 {
219     ContentHash::iterator aIter = maContents.find ( aName );
220     if ( aIter == maContents.end())
221         throw NoSuchElementException(THROW_WHERE );
222     return *aIter->second;
223 }
224 
getByName(const OUString & aName)225 uno::Any SAL_CALL ZipPackageFolder::getByName( const OUString& aName )
226 {
227     return uno::makeAny ( doGetByName ( aName ).xTunnel );
228 }
getElementNames()229 uno::Sequence< OUString > SAL_CALL ZipPackageFolder::getElementNames(  )
230 {
231     return comphelper::mapKeysToSequence(maContents);
232 }
hasByName(const OUString & aName)233 sal_Bool SAL_CALL ZipPackageFolder::hasByName( const OUString& aName )
234 {
235     return maContents.find ( aName ) != maContents.end ();
236 }
237     // XNameReplace
replaceByName(const OUString & aName,const uno::Any & aElement)238 void SAL_CALL ZipPackageFolder::replaceByName( const OUString& aName, const uno::Any& aElement )
239 {
240     if ( !hasByName( aName ) )
241         throw NoSuchElementException(THROW_WHERE );
242 
243     removeByName( aName );
244     insertByName(aName, aElement);
245 }
246 
saveChild(const OUString & rPath,std::vector<uno::Sequence<PropertyValue>> & rManList,ZipOutputStream & rZipOut,const uno::Sequence<sal_Int8> & rEncryptionKey,sal_Int32 nPBKDF2IterationCount,const rtlRandomPool & rRandomPool)247 bool ZipPackageFolder::saveChild(
248         const OUString &rPath,
249         std::vector < uno::Sequence < PropertyValue > > &rManList,
250         ZipOutputStream & rZipOut,
251         const uno::Sequence < sal_Int8 >& rEncryptionKey,
252         sal_Int32 nPBKDF2IterationCount,
253         const rtlRandomPool &rRandomPool)
254 {
255     const OUString sMediaTypeProperty ("MediaType");
256     const OUString sVersionProperty ("Version");
257     const OUString sFullPathProperty ("FullPath");
258 
259     uno::Sequence < PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST);
260     OUString sTempName = rPath + "/";
261 
262     if ( !GetMediaType().isEmpty() )
263     {
264         aPropSet[PKG_MNFST_MEDIATYPE].Name = sMediaTypeProperty;
265         aPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType();
266         aPropSet[PKG_MNFST_VERSION].Name = sVersionProperty;
267         aPropSet[PKG_MNFST_VERSION].Value <<= GetVersion();
268         aPropSet[PKG_MNFST_FULLPATH].Name = sFullPathProperty;
269         aPropSet[PKG_MNFST_FULLPATH].Value <<= sTempName;
270     }
271     else
272         aPropSet.realloc( 0 );
273 
274     saveContents( sTempName, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool);
275 
276     // folder can have a mediatype only in package format
277     if ( aPropSet.hasElements() && ( m_nFormat == embed::StorageFormats::PACKAGE ) )
278         rManList.push_back( aPropSet );
279 
280     return true;
281 }
282 
saveContents(const OUString & rPath,std::vector<uno::Sequence<PropertyValue>> & rManList,ZipOutputStream & rZipOut,const uno::Sequence<sal_Int8> & rEncryptionKey,sal_Int32 nPBKDF2IterationCount,const rtlRandomPool & rRandomPool) const283 void ZipPackageFolder::saveContents(
284         const OUString &rPath,
285         std::vector < uno::Sequence < PropertyValue > > &rManList,
286         ZipOutputStream & rZipOut,
287         const uno::Sequence < sal_Int8 >& rEncryptionKey,
288         sal_Int32 nPBKDF2IterationCount,
289         const rtlRandomPool &rRandomPool ) const
290 {
291     if ( maContents.empty() && !rPath.isEmpty() && m_nFormat != embed::StorageFormats::OFOPXML )
292     {
293         // it is an empty subfolder, use workaround to store it
294         ZipEntry* pTempEntry = new ZipEntry(aEntry);
295         pTempEntry->nPathLen = static_cast<sal_Int16>( OUStringToOString( rPath, RTL_TEXTENCODING_UTF8 ).getLength() );
296         pTempEntry->nExtraLen = -1;
297         pTempEntry->sPath = rPath;
298 
299         try
300         {
301             ZipOutputStream::setEntry(pTempEntry);
302             rZipOut.writeLOC(pTempEntry);
303             rZipOut.rawCloseEntry();
304         }
305         catch ( ZipException& )
306         {
307             throw uno::RuntimeException( THROW_WHERE );
308         }
309         catch ( IOException& )
310         {
311             throw uno::RuntimeException( THROW_WHERE );
312         }
313     }
314 
315     bool bMimeTypeStreamStored = false;
316     OUString aMimeTypeStreamName("mimetype");
317     if ( m_nFormat == embed::StorageFormats::ZIP && rPath.isEmpty() )
318     {
319         // let the "mimetype" stream in root folder be stored as the first stream if it is zip format
320         ContentHash::const_iterator aIter = maContents.find ( aMimeTypeStreamName );
321         if ( aIter != maContents.end() && !(*aIter).second->bFolder )
322         {
323             bMimeTypeStreamStored = true;
324             if( !aIter->second->pStream->saveChild(
325                 rPath + aIter->first, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool ))
326             {
327                 throw uno::RuntimeException( THROW_WHERE );
328             }
329         }
330     }
331 
332     for (const auto& [rShortName, rxInfo] : maContents)
333     {
334         const ZipContentInfo &rInfo = *rxInfo;
335 
336         if ( !bMimeTypeStreamStored || rShortName != aMimeTypeStreamName )
337         {
338             if (rInfo.bFolder)
339             {
340                 if( !rInfo.pFolder->saveChild(
341                     rPath + rShortName, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool ))
342                 {
343                     throw uno::RuntimeException( THROW_WHERE );
344                 }
345             }
346             else
347             {
348                 if( !rInfo.pStream->saveChild(
349                     rPath + rShortName, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool ))
350                 {
351                     throw uno::RuntimeException( THROW_WHERE );
352                 }
353             }
354         }
355     }
356 }
357 
getSomething(const uno::Sequence<sal_Int8> & aIdentifier)358 sal_Int64 SAL_CALL ZipPackageFolder::getSomething( const uno::Sequence< sal_Int8 >& aIdentifier )
359 {
360     sal_Int64 nMe = 0;
361     if ( isUnoTunnelId<ZipPackageFolder>(aIdentifier) )
362         nMe = reinterpret_cast < sal_Int64 > ( this );
363     return nMe;
364 }
setPropertyValue(const OUString & aPropertyName,const uno::Any & aValue)365 void SAL_CALL ZipPackageFolder::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
366 {
367     if ( aPropertyName == "MediaType" )
368     {
369         // TODO/LATER: activate when zip ucp is ready
370         // if ( m_nFormat != embed::StorageFormats::PACKAGE )
371         //  throw UnknownPropertyException(THROW_WHERE );
372 
373         aValue >>= msMediaType;
374     }
375     else if ( aPropertyName == "Version" )
376         aValue >>= m_sVersion;
377     else if ( aPropertyName == "Size" )
378         aValue >>= aEntry.nSize;
379     else
380         throw UnknownPropertyException(aPropertyName);
381 }
getPropertyValue(const OUString & PropertyName)382 uno::Any SAL_CALL ZipPackageFolder::getPropertyValue( const OUString& PropertyName )
383 {
384     if ( PropertyName == "MediaType" )
385     {
386         // TODO/LATER: activate when zip ucp is ready
387         // if ( m_nFormat != embed::StorageFormats::PACKAGE )
388         //  throw UnknownPropertyException(THROW_WHERE );
389 
390         return uno::makeAny ( msMediaType );
391     }
392     else if ( PropertyName == "Version" )
393         return uno::makeAny( m_sVersion );
394     else if ( PropertyName == "Size" )
395         return uno::makeAny ( aEntry.nSize );
396     else
397         throw UnknownPropertyException(PropertyName);
398 }
399 
doInsertByName(ZipPackageEntry * pEntry,bool bSetParent)400 void ZipPackageFolder::doInsertByName ( ZipPackageEntry *pEntry, bool bSetParent )
401 {
402     if ( pEntry->IsFolder() )
403         maContents[pEntry->getName()] = std::make_unique<ZipContentInfo>(static_cast<ZipPackageFolder*>(pEntry));
404     else
405         maContents[pEntry->getName()] = std::make_unique<ZipContentInfo>(static_cast<ZipPackageStream*>(pEntry));
406     if ( bSetParent )
407         pEntry->setParent ( *this );
408 }
409 
getImplementationName()410 OUString ZipPackageFolder::getImplementationName()
411 {
412     return "ZipPackageFolder";
413 }
414 
getSupportedServiceNames()415 uno::Sequence< OUString > ZipPackageFolder::getSupportedServiceNames()
416 {
417     uno::Sequence< OUString > aNames { "com.sun.star.packages.PackageFolder" };
418     return aNames;
419 }
420 
supportsService(OUString const & rServiceName)421 sal_Bool SAL_CALL ZipPackageFolder::supportsService( OUString const & rServiceName )
422 {
423     return cppu::supportsService(this, rServiceName);
424 }
425 
426 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
427