1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <ZipPackage.hxx>
21 #include <ZipPackageSink.hxx>
22 #include <ZipEnumeration.hxx>
23 #include <ZipPackageStream.hxx>
24 #include <ZipPackageFolder.hxx>
25 #include <ZipOutputEntry.hxx>
26 #include <ZipOutputStream.hxx>
27 #include <ZipPackageBuffer.hxx>
28 #include <ZipFile.hxx>
29 #include <PackageConstants.hxx>
30 #include <com/sun/star/beans/PropertyValue.hpp>
31 #include <com/sun/star/packages/zip/ZipConstants.hpp>
32 #include <com/sun/star/packages/manifest/ManifestReader.hpp>
33 #include <com/sun/star/packages/manifest/ManifestWriter.hpp>
34 #include <com/sun/star/io/TempFile.hpp>
35 #include <com/sun/star/io/XStream.hpp>
36 #include <com/sun/star/io/XInputStream.hpp>
37 #include <com/sun/star/io/XOutputStream.hpp>
38 #include <com/sun/star/io/XTruncate.hpp>
39 #include <com/sun/star/io/XSeekable.hpp>
40 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
41 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
42 #include <com/sun/star/container/XNameContainer.hpp>
43 #include <com/sun/star/ucb/IOErrorCode.hpp>
44 #include <ucbhelper/content.hxx>
45 #include <cppuhelper/factory.hxx>
46 #include <cppuhelper/exc_hlp.hxx>
47 #include <com/sun/star/ucb/TransferInfo.hpp>
48 #include <com/sun/star/ucb/NameClash.hpp>
49 #include <com/sun/star/ucb/OpenCommandArgument2.hpp>
50 #include <com/sun/star/ucb/OpenMode.hpp>
51 #include <com/sun/star/ucb/XProgressHandler.hpp>
52 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
53 #include <com/sun/star/ucb/UniversalContentBroker.hpp>
54 #include <com/sun/star/io/XActiveDataStreamer.hpp>
55 #include <com/sun/star/embed/XTransactedObject.hpp>
56 #include <com/sun/star/embed/UseBackupException.hpp>
57 #include <com/sun/star/embed/StorageFormats.hpp>
58 #include <com/sun/star/beans/NamedValue.hpp>
59 #include <com/sun/star/xml/crypto/DigestID.hpp>
60 #include <com/sun/star/xml/crypto/CipherID.hpp>
61 #include <cppuhelper/implbase1.hxx>
62 #include <ContentInfo.hxx>
63 #include <cppuhelper/typeprovider.hxx>
64 #include <rtl/uri.hxx>
65 #include <rtl/random.h>
66 #include <rtl/instance.hxx>
68 #include <osl/diagnose.h>
69 #include "com/sun/star/io/XAsyncOutputMonitor.hpp"
72 #include <boost/scoped_ptr.hpp>
75 #include <ucbhelper/fileidentifierconverter.hxx>
76 #include <comphelper/processfactory.hxx>
77 #include <comphelper/seekableinput.hxx>
78 #include <comphelper/storagehelper.hxx>
79 #include <comphelper/ofopxmlhelper.hxx>
80 #include <comphelper/documentconstants.hxx>
81 #include <comphelper/sequenceashashmap.hxx>
82 #include <cppuhelper/supportsservice.hxx>
87 using namespace ucbhelper
;
88 using namespace com::sun::star
;
89 using namespace com::sun::star::io
;
90 using namespace com::sun::star::uno
;
91 using namespace com::sun::star::ucb
;
92 using namespace com::sun::star::util
;
93 using namespace com::sun::star::lang
;
94 using namespace com::sun::star::task
;
95 using namespace com::sun::star::beans
;
96 using namespace com::sun::star::packages
;
97 using namespace com::sun::star::container
;
98 using namespace com::sun::star::packages::zip
;
99 using namespace com::sun::star::packages::manifest
;
100 using namespace com::sun::star::packages::zip::ZipConstants
;
102 #if OSL_DEBUG_LEVEL > 0
103 #define THROW_WHERE SAL_WHERE
105 #define THROW_WHERE ""
108 class ActiveDataStreamer
: public ::cppu::WeakImplHelper1
< XActiveDataStreamer
>
110 uno::Reference
< XStream
> mStream
;
113 virtual uno::Reference
< XStream
> SAL_CALL
getStream()
114 throw( RuntimeException
, std::exception
) SAL_OVERRIDE
117 virtual void SAL_CALL
setStream( const uno::Reference
< XStream
>& stream
)
118 throw( RuntimeException
, std::exception
) SAL_OVERRIDE
119 { mStream
= stream
; }
122 class DummyInputStream
: public ::cppu::WeakImplHelper1
< XInputStream
>
124 virtual sal_Int32 SAL_CALL
readBytes( uno::Sequence
< sal_Int8
>&, sal_Int32
)
125 throw ( NotConnectedException
, BufferSizeExceededException
, IOException
, RuntimeException
, std::exception
) SAL_OVERRIDE
128 virtual sal_Int32 SAL_CALL
readSomeBytes( uno::Sequence
< sal_Int8
>&, sal_Int32
)
129 throw ( NotConnectedException
, BufferSizeExceededException
, IOException
, RuntimeException
, std::exception
) SAL_OVERRIDE
132 virtual void SAL_CALL
skipBytes( sal_Int32
)
133 throw ( NotConnectedException
, BufferSizeExceededException
, IOException
, RuntimeException
, std::exception
) SAL_OVERRIDE
136 virtual sal_Int32 SAL_CALL
available()
137 throw ( NotConnectedException
, BufferSizeExceededException
, IOException
, RuntimeException
, std::exception
) SAL_OVERRIDE
140 virtual void SAL_CALL
closeInput()
141 throw ( NotConnectedException
, BufferSizeExceededException
, IOException
, RuntimeException
, std::exception
) SAL_OVERRIDE
145 ZipPackage::ZipPackage ( const uno::Reference
< XComponentContext
> &xContext
)
146 : m_aMutexHolder( new SotMutexHolder
)
147 , m_nStartKeyGenerationID( xml::crypto::DigestID::SHA1
)
148 , m_nChecksumDigestID( xml::crypto::DigestID::SHA1_1K
)
149 , m_nCommonEncryptionID( xml::crypto::CipherID::BLOWFISH_CFB_8
)
150 , m_bHasEncryptedEntries ( false )
151 , m_bHasNonEncryptedEntries ( false )
152 , m_bInconsistent ( false )
153 , m_bForceRecovery ( false )
154 , m_bMediaTypeFallbackUsed ( false )
155 , m_nFormat( embed::StorageFormats::PACKAGE
) // package is the default format
156 , m_bAllowRemoveOnInsert( true )
157 , m_eMode ( e_IMode_None
)
158 , m_xContext( xContext
)
159 , m_pRootFolder( NULL
)
162 m_xRootFolder
= m_pRootFolder
= new ZipPackageFolder( m_xContext
, m_nFormat
, m_bAllowRemoveOnInsert
);
165 ZipPackage::~ZipPackage()
169 // All folders and streams contain pointers to their parents, when a parent diappeares
170 // it should disconnect all the children from itself during destruction automatically.
171 // So there is no need in explicit m_pRootFolder->releaseUpwardRef() call here any more
172 // since m_pRootFolder has no parent and cleaning of its children will be done automatically
173 // during m_pRootFolder dying by refcount.
176 bool ZipPackage::isLocalFile() const
178 OUString aSystemPath
;
179 uno::Reference
< XUniversalContentBroker
> xUcb(
180 UniversalContentBroker::create(
184 aSystemPath
= getSystemPathFromFileURL( xUcb
, m_aURL
);
189 return !aSystemPath
.isEmpty();
192 void ZipPackage::parseManifest()
194 if ( m_nFormat
== embed::StorageFormats::PACKAGE
)
196 bool bManifestParsed
= false;
197 const OUString
sMeta ("META-INF");
198 if ( m_xRootFolder
->hasByName( sMeta
) )
200 const OUString
sManifest ("manifest.xml");
203 uno::Reference
< XUnoTunnel
> xTunnel
;
204 Any aAny
= m_xRootFolder
->getByName( sMeta
);
206 uno::Reference
< XNameContainer
> xMetaInfFolder( xTunnel
, UNO_QUERY
);
207 if ( xMetaInfFolder
.is() && xMetaInfFolder
->hasByName( sManifest
) )
209 aAny
= xMetaInfFolder
->getByName( sManifest
);
211 uno::Reference
< XActiveDataSink
> xSink ( xTunnel
, UNO_QUERY
);
214 uno::Reference
< XManifestReader
> xReader
= ManifestReader::create( m_xContext
);
216 const OUString
sPropFullPath ("FullPath");
217 const OUString
sPropVersion ("Version");
218 const OUString
sPropMediaType ("MediaType");
219 const OUString
sPropInitialisationVector ("InitialisationVector");
220 const OUString
sPropSalt ("Salt");
221 const OUString
sPropIterationCount ("IterationCount");
222 const OUString
sPropSize ("Size");
223 const OUString
sPropDigest ("Digest");
224 const OUString
sPropDerivedKeySize ("DerivedKeySize");
225 const OUString
sPropDigestAlgorithm ("DigestAlgorithm");
226 const OUString
sPropEncryptionAlgorithm ("EncryptionAlgorithm");
227 const OUString
sPropStartKeyAlgorithm ("StartKeyAlgorithm");
229 uno::Sequence
< uno::Sequence
< PropertyValue
> > aManifestSequence
= xReader
->readManifestSequence ( xSink
->getInputStream() );
230 sal_Int32 nLength
= aManifestSequence
.getLength();
231 const uno::Sequence
< PropertyValue
> *pSequence
= aManifestSequence
.getConstArray();
232 ZipPackageStream
*pStream
= NULL
;
233 ZipPackageFolder
*pFolder
= NULL
;
235 for ( sal_Int32 i
= 0; i
< nLength
; i
++, pSequence
++ )
237 OUString sPath
, sMediaType
, sVersion
;
238 const PropertyValue
*pValue
= pSequence
->getConstArray();
239 const Any
*pSalt
= NULL
, *pVector
= NULL
, *pCount
= NULL
, *pSize
= NULL
, *pDigest
= NULL
, *pDigestAlg
= NULL
, *pEncryptionAlg
= NULL
, *pStartKeyAlg
= NULL
, *pDerivedKeySize
= NULL
;
240 for ( sal_Int32 j
= 0, nNum
= pSequence
->getLength(); j
< nNum
; j
++ )
242 if ( pValue
[j
].Name
.equals( sPropFullPath
) )
243 pValue
[j
].Value
>>= sPath
;
244 else if ( pValue
[j
].Name
.equals( sPropVersion
) )
245 pValue
[j
].Value
>>= sVersion
;
246 else if ( pValue
[j
].Name
.equals( sPropMediaType
) )
247 pValue
[j
].Value
>>= sMediaType
;
248 else if ( pValue
[j
].Name
.equals( sPropSalt
) )
249 pSalt
= &( pValue
[j
].Value
);
250 else if ( pValue
[j
].Name
.equals( sPropInitialisationVector
) )
251 pVector
= &( pValue
[j
].Value
);
252 else if ( pValue
[j
].Name
.equals( sPropIterationCount
) )
253 pCount
= &( pValue
[j
].Value
);
254 else if ( pValue
[j
].Name
.equals( sPropSize
) )
255 pSize
= &( pValue
[j
].Value
);
256 else if ( pValue
[j
].Name
.equals( sPropDigest
) )
257 pDigest
= &( pValue
[j
].Value
);
258 else if ( pValue
[j
].Name
.equals( sPropDigestAlgorithm
) )
259 pDigestAlg
= &( pValue
[j
].Value
);
260 else if ( pValue
[j
].Name
.equals( sPropEncryptionAlgorithm
) )
261 pEncryptionAlg
= &( pValue
[j
].Value
);
262 else if ( pValue
[j
].Name
.equals( sPropStartKeyAlgorithm
) )
263 pStartKeyAlg
= &( pValue
[j
].Value
);
264 else if ( pValue
[j
].Name
.equals( sPropDerivedKeySize
) )
265 pDerivedKeySize
= &( pValue
[j
].Value
);
268 if ( !sPath
.isEmpty() && hasByHierarchicalName ( sPath
) )
270 aAny
= getByHierarchicalName( sPath
);
271 uno::Reference
< XUnoTunnel
> xUnoTunnel
;
274 if ( (nTest
= xUnoTunnel
->getSomething( ZipPackageFolder::static_getImplementationId() )) != 0 )
276 pFolder
= reinterpret_cast < ZipPackageFolder
* > ( nTest
);
277 pFolder
->SetMediaType ( sMediaType
);
278 pFolder
->SetVersion ( sVersion
);
282 pStream
= reinterpret_cast < ZipPackageStream
* > ( xUnoTunnel
->getSomething( ZipPackageStream::static_getImplementationId() ));
283 pStream
->SetMediaType ( sMediaType
);
284 pStream
->SetFromManifest( true );
286 if ( pSalt
&& pVector
&& pCount
&& pSize
&& pDigest
&& pDigestAlg
&& pEncryptionAlg
)
288 uno::Sequence
< sal_Int8
> aSequence
;
290 sal_Int32 nCount
= 0, nDigestAlg
= 0, nEncryptionAlg
= 0;
291 sal_Int32 nDerivedKeySize
= 16, nStartKeyAlg
= xml::crypto::DigestID::SHA1
;
293 pStream
->SetToBeEncrypted ( true );
295 *pSalt
>>= aSequence
;
296 pStream
->setSalt ( aSequence
);
298 *pVector
>>= aSequence
;
299 pStream
->setInitialisationVector ( aSequence
);
302 pStream
->setIterationCount ( nCount
);
305 pStream
->setSize ( nSize
);
307 *pDigest
>>= aSequence
;
308 pStream
->setDigest ( aSequence
);
310 *pDigestAlg
>>= nDigestAlg
;
311 pStream
->SetImportedChecksumAlgorithm( nDigestAlg
);
313 *pEncryptionAlg
>>= nEncryptionAlg
;
314 pStream
->SetImportedEncryptionAlgorithm( nEncryptionAlg
);
316 if ( pDerivedKeySize
)
317 *pDerivedKeySize
>>= nDerivedKeySize
;
318 pStream
->SetImportedDerivedKeySize( nDerivedKeySize
);
321 *pStartKeyAlg
>>= nStartKeyAlg
;
322 pStream
->SetImportedStartKeyAlgorithm( nStartKeyAlg
);
324 pStream
->SetToBeCompressed ( true );
325 pStream
->SetToBeEncrypted ( true );
326 pStream
->SetIsEncrypted ( true );
327 if ( !m_bHasEncryptedEntries
&& pStream
->getName() == "content.xml" )
329 m_bHasEncryptedEntries
= true;
330 m_nStartKeyGenerationID
= nStartKeyAlg
;
331 m_nChecksumDigestID
= nDigestAlg
;
332 m_nCommonEncryptionID
= nEncryptionAlg
;
336 m_bHasNonEncryptedEntries
= true;
341 bManifestParsed
= true;
344 // now hide the manifest.xml file from user
345 xMetaInfFolder
->removeByName( sManifest
);
350 if ( !m_bForceRecovery
)
355 if ( !bManifestParsed
&& !m_bForceRecovery
)
356 throw ZipIOException(
357 THROW_WHERE
"Could not parse manifest.xml\n" );
359 const OUString
sMimetype ("mimetype");
360 if ( m_xRootFolder
->hasByName( sMimetype
) )
362 // get mediatype from the "mimetype" stream
363 OUString aPackageMediatype
;
364 uno::Reference
< lang::XUnoTunnel
> xMimeTypeTunnel
;
365 m_xRootFolder
->getByName( sMimetype
) >>= xMimeTypeTunnel
;
366 uno::Reference
< io::XActiveDataSink
> xMimeSink( xMimeTypeTunnel
, UNO_QUERY
);
367 if ( xMimeSink
.is() )
369 uno::Reference
< io::XInputStream
> xMimeInStream
= xMimeSink
->getInputStream();
370 if ( xMimeInStream
.is() )
372 // Mediatypes longer than 1024 symbols should not appear here
373 uno::Sequence
< sal_Int8
> aData( 1024 );
374 sal_Int32 nRead
= xMimeInStream
->readBytes( aData
, 1024 );
375 if ( nRead
> aData
.getLength() )
376 nRead
= aData
.getLength();
379 aPackageMediatype
= OUString( reinterpret_cast<char const *>(aData
.getConstArray()), nRead
, RTL_TEXTENCODING_ASCII_US
);
383 if ( !bManifestParsed
)
385 // the manifest.xml could not be successfully parsed, this is an inconsistent package
386 if ( aPackageMediatype
.startsWith("application/vnd.") )
388 // accept only types that look similar to own mediatypes
389 m_pRootFolder
->SetMediaType( aPackageMediatype
);
390 m_bMediaTypeFallbackUsed
= true;
393 else if ( !m_bForceRecovery
)
395 // the mimetype stream should contain the information from manifest.xml
396 if ( !m_pRootFolder
->GetMediaType().equals( aPackageMediatype
) )
397 throw ZipIOException(
399 "mimetype conflicts with manifest.xml, \""
400 + m_pRootFolder
->GetMediaType() + "\" vs. \""
401 + aPackageMediatype
+ "\"" );
404 m_xRootFolder
->removeByName( sMimetype
);
407 m_bInconsistent
= m_pRootFolder
->LookForUnexpectedODF12Streams( OUString() );
409 bool bODF12AndNewer
= ( m_pRootFolder
->GetVersion().compareTo( ODFVER_012_TEXT
) >= 0 );
410 if ( !m_bForceRecovery
&& bODF12AndNewer
)
412 bool bDifferentStartKeyAlgorithm
= false;
414 if ( m_bInconsistent
)
416 // this is an ODF1.2 document that contains streams not referred in the manifest.xml;
417 // in case of ODF1.2 documents without version in manifest.xml the property IsInconsistent
418 // should be checked later
419 throw ZipIOException(
420 THROW_WHERE
"there are streams not referred in manifest.xml" );
422 else if ( bDifferentStartKeyAlgorithm
)
424 // all the streams should be encrypted with the same StartKey in ODF1.2
425 // TODO/LATER: in future the exception should be thrown
426 OSL_ENSURE( false, "ODF1.2 contains different StartKey Algorithms" );
427 // throw ZipIOException( THROW_WHERE "More than one Start Key Generation algorithm is specified!" );
431 // in case it is a correct ODF1.2 document, the version must be set
432 // and the META-INF folder is reserved for package format
433 if ( bODF12AndNewer
)
434 m_xRootFolder
->removeByName( sMeta
);
438 void ZipPackage::parseContentType()
440 if ( m_nFormat
== embed::StorageFormats::OFOPXML
)
442 const OUString
aContentTypes("[Content_Types].xml");
444 // the content type must exist in OFOPXML format!
445 if ( !m_xRootFolder
->hasByName( aContentTypes
) )
446 throw io::IOException(THROW_WHERE
"Wrong format!" );
448 uno::Reference
< lang::XUnoTunnel
> xTunnel
;
449 uno::Any aAny
= m_xRootFolder
->getByName( aContentTypes
);
451 uno::Reference
< io::XActiveDataSink
> xSink( xTunnel
, UNO_QUERY
);
454 uno::Reference
< io::XInputStream
> xInStream
= xSink
->getInputStream();
455 if ( xInStream
.is() )
458 // here aContentTypeInfo[0] - Defaults, and aContentTypeInfo[1] - Overrides
459 uno::Sequence
< uno::Sequence
< beans::StringPair
> > aContentTypeInfo
=
460 ::comphelper::OFOPXMLHelper::ReadContentTypeSequence( xInStream
, m_xContext
);
462 if ( aContentTypeInfo
.getLength() != 2 )
463 throw io::IOException(THROW_WHERE
);
465 // set the implicit types fist
466 for ( nInd
= 0; nInd
< aContentTypeInfo
[0].getLength(); nInd
++ )
467 m_pRootFolder
->setChildStreamsTypeByExtension( aContentTypeInfo
[0][nInd
] );
469 // now set the explicit types
470 for ( nInd
= 0; nInd
< aContentTypeInfo
[1].getLength(); nInd
++ )
473 if ( aContentTypeInfo
[1][nInd
].First
.toChar() == ( sal_Unicode
)'/' )
474 aPath
= aContentTypeInfo
[1][nInd
].First
.copy( 1 );
476 aPath
= aContentTypeInfo
[1][nInd
].First
;
478 if ( !aPath
.isEmpty() && hasByHierarchicalName( aPath
) )
480 uno::Any aIterAny
= getByHierarchicalName( aPath
);
481 uno::Reference
< lang::XUnoTunnel
> xIterTunnel
;
482 aIterAny
>>= xIterTunnel
;
483 sal_Int64 nTest
= xIterTunnel
->getSomething( ZipPackageStream::static_getImplementationId() );
486 // this is a package stream, in OFOPXML format only streams can have mediatype
487 ZipPackageStream
*pStream
= reinterpret_cast < ZipPackageStream
* > ( nTest
);
488 pStream
->SetMediaType( aContentTypeInfo
[1][nInd
].Second
);
495 m_xRootFolder
->removeByName( aContentTypes
);
497 catch( uno::Exception
& )
499 if ( !m_bForceRecovery
)
505 void ZipPackage::getZipFileContents()
507 boost::scoped_ptr
< ZipEnumeration
> pEnum ( m_pZipFile
->entries() );
508 ZipPackageStream
*pPkgStream
;
509 ZipPackageFolder
*pPkgFolder
, *pCurrent
;
510 OUString sTemp
, sDirName
;
511 sal_Int32 nOldIndex
, nIndex
, nStreamIndex
;
512 FolderHash::iterator aIter
;
514 while ( pEnum
->hasMoreElements() )
516 nIndex
= nOldIndex
= 0;
517 pCurrent
= m_pRootFolder
;
518 const ZipEntry
& rEntry
= *pEnum
->nextElement();
519 OUString rName
= rEntry
.sPath
;
521 if ( m_bForceRecovery
)
523 // the PKZIP Application note version 6.2 does not allows to use '\' as separator
524 // unfortunately it is used by some implementations, so we have to support it in recovery mode
525 rName
= rName
.replace( '\\', '/' );
528 nStreamIndex
= rName
.lastIndexOf ( '/' );
529 if ( nStreamIndex
!= -1 )
531 sDirName
= rName
.copy ( 0, nStreamIndex
);
532 aIter
= m_aRecent
.find ( sDirName
);
533 if ( aIter
!= m_aRecent
.end() )
534 pCurrent
= ( *aIter
).second
;
537 if ( pCurrent
== m_pRootFolder
)
539 while ( ( nIndex
= rName
.indexOf( '/', nOldIndex
) ) != -1 )
541 sTemp
= rName
.copy ( nOldIndex
, nIndex
- nOldIndex
);
542 if ( nIndex
== nOldIndex
)
544 if ( !pCurrent
->hasByName( sTemp
) )
546 pPkgFolder
= new ZipPackageFolder( m_xContext
, m_nFormat
, m_bAllowRemoveOnInsert
);
547 pPkgFolder
->setName( sTemp
);
548 pPkgFolder
->doSetParent( pCurrent
, true );
549 pCurrent
= pPkgFolder
;
552 pCurrent
= pCurrent
->doGetByName( sTemp
).pFolder
;
553 nOldIndex
= nIndex
+1;
555 if ( nStreamIndex
!= -1 && !sDirName
.isEmpty() )
556 m_aRecent
[ sDirName
] = pCurrent
;
558 if ( rName
.getLength() -1 != nStreamIndex
)
561 sTemp
= rName
.copy( nStreamIndex
, rName
.getLength() - nStreamIndex
);
562 pPkgStream
= new ZipPackageStream( *this, m_xContext
, m_nFormat
, m_bAllowRemoveOnInsert
);
563 pPkgStream
->SetPackageMember( true );
564 pPkgStream
->setZipEntryOnLoading( rEntry
);
565 pPkgStream
->setName( sTemp
);
566 pPkgStream
->doSetParent( pCurrent
, true );
570 if ( m_nFormat
== embed::StorageFormats::PACKAGE
)
572 else if ( m_nFormat
== embed::StorageFormats::OFOPXML
)
576 void SAL_CALL
ZipPackage::initialize( const uno::Sequence
< Any
>& aArguments
)
577 throw( Exception
, RuntimeException
, std::exception
)
579 uno::Reference
< XProgressHandler
> xProgressHandler
;
580 beans::NamedValue aNamedValue
;
582 if ( aArguments
.getLength() )
584 bool bHaveZipFile
= true;
586 for( int ind
= 0; ind
< aArguments
.getLength(); ind
++ )
589 if ( ( aArguments
[ind
] >>= aParamUrl
))
591 m_eMode
= e_IMode_URL
;
594 sal_Int32 nParam
= aParamUrl
.indexOf( '?' );
597 m_aURL
= aParamUrl
.copy( 0, nParam
);
598 OUString aParam
= aParamUrl
.copy( nParam
+ 1 );
600 sal_Int32 nIndex
= 0;
603 OUString aCommand
= aParam
.getToken( 0, '&', nIndex
);
604 if ( aCommand
== "repairpackage" )
606 m_bForceRecovery
= true;
609 else if ( aCommand
== "purezip" )
611 m_nFormat
= embed::StorageFormats::ZIP
;
612 m_pRootFolder
->setPackageFormat_Impl( m_nFormat
);
615 else if ( aCommand
== "ofopxml" )
617 m_nFormat
= embed::StorageFormats::OFOPXML
;
618 m_pRootFolder
->setPackageFormat_Impl( m_nFormat
);
622 while ( nIndex
>= 0 );
628 m_aURL
, uno::Reference
< XCommandEnvironment
>(),
630 Any aAny
= aContent
.getPropertyValue("Size");
631 sal_uInt64 aSize
= 0;
632 // kind of optimization: treat empty files as nonexistent files
633 // and write to such files directly. Note that "Size" property is optional.
634 bool bHasSizeProperty
= aAny
>>= aSize
;
635 if( !bHasSizeProperty
|| ( bHasSizeProperty
&& aSize
) )
637 uno::Reference
< XActiveDataSink
> xSink
= new ZipPackageSink
;
638 if ( aContent
.openStream ( xSink
) )
639 m_xContentStream
= xSink
->getInputStream();
642 bHaveZipFile
= false;
644 catch ( com::sun::star::uno::Exception
& )
646 // Exception derived from uno::Exception thrown. This probably
647 // means the file doesn't exist...we'll create it at
648 // commitChanges time
649 bHaveZipFile
= false;
652 else if ( ( aArguments
[ind
] >>= m_xStream
) )
654 // a writable stream can implement both XStream & XInputStream
655 m_eMode
= e_IMode_XStream
;
656 m_xContentStream
= m_xStream
->getInputStream();
658 else if ( ( aArguments
[ind
] >>= m_xContentStream
) )
660 m_eMode
= e_IMode_XInputStream
;
662 else if ( ( aArguments
[ind
] >>= aNamedValue
) )
664 if ( aNamedValue
.Name
== "RepairPackage" )
665 aNamedValue
.Value
>>= m_bForceRecovery
;
666 else if ( aNamedValue
.Name
== "PackageFormat" )
668 // setting this argument to true means Package format
669 // setting it to false means plain Zip format
671 bool bPackFormat
= true;
672 aNamedValue
.Value
>>= bPackFormat
;
674 m_nFormat
= embed::StorageFormats::ZIP
;
676 m_pRootFolder
->setPackageFormat_Impl( m_nFormat
);
678 else if ( aNamedValue
.Name
== "StorageFormat" )
680 OUString aFormatName
;
681 sal_Int32 nFormatID
= 0;
682 if ( aNamedValue
.Value
>>= aFormatName
)
684 if ( aFormatName
== PACKAGE_STORAGE_FORMAT_STRING
)
685 m_nFormat
= embed::StorageFormats::PACKAGE
;
686 else if ( aFormatName
== ZIP_STORAGE_FORMAT_STRING
)
687 m_nFormat
= embed::StorageFormats::ZIP
;
688 else if ( aFormatName
== OFOPXML_STORAGE_FORMAT_STRING
)
689 m_nFormat
= embed::StorageFormats::OFOPXML
;
691 throw lang::IllegalArgumentException(THROW_WHERE
, uno::Reference
< uno::XInterface
>(), 1 );
693 else if ( aNamedValue
.Value
>>= nFormatID
)
695 if ( nFormatID
!= embed::StorageFormats::PACKAGE
696 && nFormatID
!= embed::StorageFormats::ZIP
697 && nFormatID
!= embed::StorageFormats::OFOPXML
)
698 throw lang::IllegalArgumentException(THROW_WHERE
, uno::Reference
< uno::XInterface
>(), 1 );
700 m_nFormat
= nFormatID
;
703 throw lang::IllegalArgumentException(THROW_WHERE
, uno::Reference
< uno::XInterface
>(), 1 );
705 m_pRootFolder
->setPackageFormat_Impl( m_nFormat
);
707 else if ( aNamedValue
.Name
== "AllowRemoveOnInsert" )
709 aNamedValue
.Value
>>= m_bAllowRemoveOnInsert
;
710 m_pRootFolder
->setRemoveOnInsertMode_Impl( m_bAllowRemoveOnInsert
);
713 // for now the progress handler is not used, probably it will never be
714 // if ( aNamedValue.Name == "ProgressHandler" )
718 // The URL is not acceptable
719 throw com::sun::star::uno::Exception (THROW_WHERE
"Bad arguments.",
720 static_cast < ::cppu::OWeakObject
* > ( this ) );
726 if ( m_xContentStream
.is() )
728 // the stream must be seekable, if it is not it will be wrapped
729 m_xContentStream
= ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( m_xContentStream
, m_xContext
);
730 m_xContentSeek
= uno::Reference
< XSeekable
> ( m_xContentStream
, UNO_QUERY
);
731 if ( ! m_xContentSeek
.is() )
732 throw com::sun::star::uno::Exception (THROW_WHERE
"The package component _requires_ an XSeekable interface!",
733 static_cast < ::cppu::OWeakObject
* > ( this ) );
735 if ( !m_xContentSeek
->getLength() )
736 bHaveZipFile
= false;
739 bHaveZipFile
= false;
741 catch ( com::sun::star::uno::Exception
& )
743 // Exception derived from uno::Exception thrown. This probably
744 // means the file doesn't exist...we'll create it at
745 // commitChanges time
746 bHaveZipFile
= false;
750 bool bBadZipFile
= false;
754 m_pZipFile
= new ZipFile ( m_xContentStream
, m_xContext
, true, m_bForceRecovery
, xProgressHandler
);
755 getZipFileContents();
757 catch ( IOException
& e
)
760 message
= "IOException: " + e
.Message
;
762 catch ( ZipException
& e
)
765 message
= "ZipException: " + e
.Message
;
767 catch ( Exception
& )
769 if( m_pZipFile
) { delete m_pZipFile
; m_pZipFile
= NULL
; }
775 // clean up the memory, and tell the UCB about the error
776 if( m_pZipFile
) { delete m_pZipFile
; m_pZipFile
= NULL
; }
778 throw com::sun::star::packages::zip::ZipIOException (
779 THROW_WHERE
"Bad Zip File, " + message
,
780 static_cast < ::cppu::OWeakObject
* > ( this ) );
786 Any SAL_CALL
ZipPackage::getByHierarchicalName( const OUString
& aName
)
787 throw( NoSuchElementException
, RuntimeException
, std::exception
)
789 OUString sTemp
, sDirName
;
790 sal_Int32 nOldIndex
, nIndex
, nStreamIndex
;
791 FolderHash::iterator aIter
;
793 if ( ( nIndex
= aName
.getLength() ) == 1 && *aName
.getStr() == '/' )
794 return makeAny ( uno::Reference
< XUnoTunnel
> ( m_pRootFolder
) );
797 nStreamIndex
= aName
.lastIndexOf ( '/' );
798 bool bFolder
= nStreamIndex
== nIndex
-1;
799 if ( nStreamIndex
!= -1 )
801 sDirName
= aName
.copy ( 0, nStreamIndex
);
802 aIter
= m_aRecent
.find ( sDirName
);
803 if ( aIter
!= m_aRecent
.end() )
807 sal_Int32 nDirIndex
= aName
.lastIndexOf ( '/', nStreamIndex
);
808 sTemp
= aName
.copy ( nDirIndex
== -1 ? 0 : nDirIndex
+1, nStreamIndex
-nDirIndex
-1 );
809 if ( sTemp
== ( *aIter
).second
->getName() )
810 return makeAny ( uno::Reference
< XUnoTunnel
> ( ( *aIter
).second
) );
812 m_aRecent
.erase ( aIter
);
816 sTemp
= aName
.copy ( nStreamIndex
+ 1 );
817 if ( ( *aIter
).second
->hasByName( sTemp
) )
818 return ( *aIter
).second
->getByName( sTemp
);
820 m_aRecent
.erase( aIter
);
826 if ( m_pRootFolder
->hasByName ( aName
) )
827 return m_pRootFolder
->getByName ( aName
);
830 ZipPackageFolder
* pCurrent
= m_pRootFolder
;
831 ZipPackageFolder
* pPrevious
= NULL
;
832 while ( ( nIndex
= aName
.indexOf( '/', nOldIndex
)) != -1 )
834 sTemp
= aName
.copy ( nOldIndex
, nIndex
- nOldIndex
);
835 if ( nIndex
== nOldIndex
)
837 if ( pCurrent
->hasByName( sTemp
) )
839 pPrevious
= pCurrent
;
840 pCurrent
= pCurrent
->doGetByName( sTemp
).pFolder
;
843 throw NoSuchElementException(THROW_WHERE
);
844 nOldIndex
= nIndex
+1;
848 if ( nStreamIndex
!= -1 )
849 m_aRecent
[sDirName
] = pPrevious
;
850 return makeAny ( uno::Reference
< XUnoTunnel
> ( pCurrent
) );
854 sTemp
= aName
.copy( nOldIndex
, aName
.getLength() - nOldIndex
);
855 if ( pCurrent
->hasByName ( sTemp
) )
857 if ( nStreamIndex
!= -1 )
858 m_aRecent
[sDirName
] = pCurrent
;
859 return pCurrent
->getByName( sTemp
);
862 throw NoSuchElementException(THROW_WHERE
);
867 sal_Bool SAL_CALL
ZipPackage::hasByHierarchicalName( const OUString
& aName
)
868 throw( RuntimeException
, std::exception
)
870 OUString sTemp
, sDirName
;
871 sal_Int32 nOldIndex
, nIndex
, nStreamIndex
;
872 FolderHash::iterator aIter
;
874 if ( ( nIndex
= aName
.getLength() ) == 1 && *aName
.getStr() == '/' )
879 nStreamIndex
= aName
.lastIndexOf ( '/' );
880 bool bFolder
= nStreamIndex
== nIndex
-1;
881 if ( nStreamIndex
!= -1 )
883 sDirName
= aName
.copy ( 0, nStreamIndex
);
884 aIter
= m_aRecent
.find ( sDirName
);
885 if ( aIter
!= m_aRecent
.end() )
889 sal_Int32 nDirIndex
= aName
.lastIndexOf ( '/', nStreamIndex
);
890 sTemp
= aName
.copy ( nDirIndex
== -1 ? 0 : nDirIndex
+1, nStreamIndex
-nDirIndex
-1 );
891 if ( sTemp
== ( *aIter
).second
->getName() )
894 m_aRecent
.erase ( aIter
);
898 sTemp
= aName
.copy ( nStreamIndex
+ 1 );
899 if ( ( *aIter
).second
->hasByName( sTemp
) )
902 m_aRecent
.erase( aIter
);
908 if ( m_pRootFolder
->hasByName ( aName
) )
911 ZipPackageFolder
* pCurrent
= m_pRootFolder
;
912 ZipPackageFolder
* pPrevious
= NULL
;
914 while ( ( nIndex
= aName
.indexOf( '/', nOldIndex
)) != -1 )
916 sTemp
= aName
.copy ( nOldIndex
, nIndex
- nOldIndex
);
917 if ( nIndex
== nOldIndex
)
919 if ( pCurrent
->hasByName( sTemp
) )
921 pPrevious
= pCurrent
;
922 pCurrent
= pCurrent
->doGetByName( sTemp
).pFolder
;
926 nOldIndex
= nIndex
+1;
930 m_aRecent
[sDirName
] = pPrevious
;
935 sTemp
= aName
.copy( nOldIndex
, aName
.getLength() - nOldIndex
);
937 if ( pCurrent
->hasByName( sTemp
) )
939 m_aRecent
[sDirName
] = pCurrent
;
944 catch (const uno::RuntimeException
&)
948 catch (const uno::Exception
&)
950 uno::Any
e(::cppu::getCaughtException());
951 throw lang::WrappedTargetRuntimeException(
952 OUString("ZipPackage::hasByHierarchicalName"),
958 uno::Reference
< XInterface
> SAL_CALL
ZipPackage::createInstance()
959 throw( Exception
, RuntimeException
, std::exception
)
961 uno::Reference
< XInterface
> xRef
= *( new ZipPackageStream( *this, m_xContext
, m_nFormat
, m_bAllowRemoveOnInsert
) );
965 uno::Reference
< XInterface
> SAL_CALL
ZipPackage::createInstanceWithArguments( const uno::Sequence
< Any
>& aArguments
)
966 throw( Exception
, RuntimeException
, std::exception
)
969 uno::Reference
< XInterface
> xRef
;
970 if ( aArguments
.getLength() )
971 aArguments
[0] >>= bArg
;
973 xRef
= *new ZipPackageFolder( m_xContext
, m_nFormat
, m_bAllowRemoveOnInsert
);
975 xRef
= *new ZipPackageStream( *this, m_xContext
, m_nFormat
, m_bAllowRemoveOnInsert
);
980 void ZipPackage::WriteMimetypeMagicFile( ZipOutputStream
& aZipOut
)
982 const OUString
sMime ("mimetype");
983 if ( m_xRootFolder
->hasByName( sMime
) )
984 m_xRootFolder
->removeByName( sMime
);
986 ZipEntry
* pEntry
= new ZipEntry
;
987 sal_Int32 nBufferLength
= m_pRootFolder
->GetMediaType().getLength();
988 OString sMediaType
= OUStringToOString( m_pRootFolder
->GetMediaType(), RTL_TEXTENCODING_ASCII_US
);
989 const uno::Sequence
< sal_Int8
> aType( reinterpret_cast<sal_Int8
const *>(sMediaType
.getStr()),
992 pEntry
->sPath
= sMime
;
993 pEntry
->nMethod
= STORED
;
994 pEntry
->nSize
= pEntry
->nCompressedSize
= nBufferLength
;
995 pEntry
->nTime
= ZipOutputStream::getCurrentDosTime();
998 aCRC32
.update( aType
);
999 pEntry
->nCrc
= aCRC32
.getValue();
1003 ZipOutputStream::setEntry(pEntry
);
1004 aZipOut
.writeLOC(pEntry
);
1005 aZipOut
.rawWrite(aType
);
1006 aZipOut
.rawCloseEntry();
1008 catch ( const ::com::sun::star::io::IOException
& r
)
1010 throw WrappedTargetException(
1011 THROW_WHERE
"Error adding mimetype to the ZipOutputStream!",
1012 static_cast < OWeakObject
* > ( this ),
1017 void ZipPackage::WriteManifest( ZipOutputStream
& aZipOut
, const vector
< uno::Sequence
< PropertyValue
> >& aManList
)
1019 // Write the manifest
1020 uno::Reference
< XManifestWriter
> xWriter
= ManifestWriter::create( m_xContext
);
1021 ZipEntry
* pEntry
= new ZipEntry
;
1022 ZipPackageBuffer
*pBuffer
= new ZipPackageBuffer( n_ConstBufferSize
);
1023 uno::Reference
< XOutputStream
> xManOutStream( *pBuffer
, UNO_QUERY
);
1025 pEntry
->sPath
= "META-INF/manifest.xml";
1026 pEntry
->nMethod
= DEFLATED
;
1028 pEntry
->nSize
= pEntry
->nCompressedSize
= -1;
1029 pEntry
->nTime
= ZipOutputStream::getCurrentDosTime();
1031 // Convert vector into a uno::Sequence
1032 uno::Sequence
< uno::Sequence
< PropertyValue
> > aManifestSequence ( aManList
.size() );
1034 for ( vector
< uno::Sequence
< PropertyValue
> >::const_iterator aIter
= aManList
.begin(), aEnd
= aManList
.end();
1038 aManifestSequence
[nInd
] = ( *aIter
);
1040 xWriter
->writeManifestSequence ( xManOutStream
, aManifestSequence
);
1042 sal_Int32 nBufferLength
= static_cast < sal_Int32
> ( pBuffer
->getPosition() );
1043 pBuffer
->realloc( nBufferLength
);
1045 // the manifest.xml is never encrypted - so pass an empty reference
1046 ZipOutputStream::setEntry(pEntry
);
1047 aZipOut
.writeLOC(pEntry
);
1048 ZipOutputEntry
aZipEntry(aZipOut
.getStream(), m_xContext
, *pEntry
, NULL
);
1049 aZipEntry
.write(pBuffer
->getSequence());
1050 aZipEntry
.closeEntry();
1051 aZipOut
.rawCloseEntry();
1054 void ZipPackage::WriteContentTypes( ZipOutputStream
& aZipOut
, const vector
< uno::Sequence
< PropertyValue
> >& aManList
)
1056 ZipEntry
* pEntry
= new ZipEntry
;
1057 ZipPackageBuffer
*pBuffer
= new ZipPackageBuffer( n_ConstBufferSize
);
1058 uno::Reference
< io::XOutputStream
> xConTypeOutStream( *pBuffer
, UNO_QUERY
);
1060 pEntry
->sPath
= "[Content_Types].xml";
1061 pEntry
->nMethod
= DEFLATED
;
1063 pEntry
->nSize
= pEntry
->nCompressedSize
= -1;
1064 pEntry
->nTime
= ZipOutputStream::getCurrentDosTime();
1066 // Convert vector into a uno::Sequence
1067 // TODO/LATER: use Defaulst entries in future
1068 uno::Sequence
< beans::StringPair
> aDefaultsSequence
;
1069 uno::Sequence
< beans::StringPair
> aOverridesSequence( aManList
.size() );
1070 sal_Int32 nSeqLength
= 0;
1071 for ( vector
< uno::Sequence
< beans::PropertyValue
> >::const_iterator aIter
= aManList
.begin(),
1072 aEnd
= aManList
.end();
1078 OSL_ENSURE( ( *aIter
)[PKG_MNFST_MEDIATYPE
].Name
== "MediaType" && ( *aIter
)[PKG_MNFST_FULLPATH
].Name
== "FullPath",
1079 "The mediatype sequence format is wrong!\n" );
1080 ( *aIter
)[PKG_MNFST_MEDIATYPE
].Value
>>= aType
;
1081 if ( !aType
.isEmpty() )
1083 // only nonempty type makes sense here
1085 ( *aIter
)[PKG_MNFST_FULLPATH
].Value
>>= aPath
;
1086 aOverridesSequence
[nSeqLength
-1].First
= "/" + aPath
;
1087 aOverridesSequence
[nSeqLength
-1].Second
= aType
;
1090 aOverridesSequence
.realloc( nSeqLength
);
1092 ::comphelper::OFOPXMLHelper::WriteContentSequence(
1093 xConTypeOutStream
, aDefaultsSequence
, aOverridesSequence
, m_xContext
);
1095 sal_Int32 nBufferLength
= static_cast < sal_Int32
> ( pBuffer
->getPosition() );
1096 pBuffer
->realloc( nBufferLength
);
1098 // there is no encryption in this format currently
1099 ZipOutputStream::setEntry(pEntry
);
1100 aZipOut
.writeLOC(pEntry
);
1101 ZipOutputEntry
aZipEntry(aZipOut
.getStream(), m_xContext
, *pEntry
, NULL
);
1102 aZipEntry
.write(pBuffer
->getSequence());
1103 aZipEntry
.closeEntry();
1104 aZipOut
.rawCloseEntry();
1107 void ZipPackage::ConnectTo( const uno::Reference
< io::XInputStream
>& xInStream
)
1109 m_xContentSeek
.set( xInStream
, uno::UNO_QUERY_THROW
);
1110 m_xContentStream
= xInStream
;
1112 // seek back to the beginning of the temp file so we can read segments from it
1113 m_xContentSeek
->seek( 0 );
1115 m_pZipFile
->setInputStream( m_xContentStream
);
1117 m_pZipFile
= new ZipFile ( m_xContentStream
, m_xContext
, false );
1120 uno::Reference
< io::XInputStream
> ZipPackage::writeTempFile()
1122 // In case the target local file does not exist or empty
1123 // write directly to it otherwize create a temporary file to write to.
1124 // If a temporary file is created it is returned back by the method.
1125 // If the data written directly, xComponentStream will be switched here
1127 bool bUseTemp
= true;
1128 uno::Reference
< io::XInputStream
> xResult
;
1129 uno::Reference
< io::XInputStream
> xTempIn
;
1131 uno::Reference
< io::XOutputStream
> xTempOut
;
1132 uno::Reference
< io::XActiveDataStreamer
> xSink
;
1134 if ( m_eMode
== e_IMode_URL
&& !m_pZipFile
&& isLocalFile() )
1136 xSink
= openOriginalForOutput();
1139 uno::Reference
< io::XStream
> xStr
= xSink
->getStream();
1142 xTempOut
= xStr
->getOutputStream();
1148 else if ( m_eMode
== e_IMode_XStream
&& !m_pZipFile
)
1150 // write directly to an empty stream
1151 xTempOut
= m_xStream
->getOutputStream();
1158 // create temporary file
1159 uno::Reference
< io::XTempFile
> xTempFile( io::TempFile::create(m_xContext
) );
1160 xTempOut
.set( xTempFile
->getOutputStream(), UNO_SET_THROW
);
1161 xTempIn
.set( xTempFile
->getInputStream(), UNO_SET_THROW
);
1164 // Hand it to the ZipOutputStream:
1165 ZipOutputStream
aZipOut( xTempOut
);
1168 if ( m_nFormat
== embed::StorageFormats::PACKAGE
)
1170 // Remove the old manifest.xml file as the
1171 // manifest will be re-generated and the
1172 // META-INF directory implicitly created if does not exist
1173 const OUString
sMeta ("META-INF");
1175 if ( m_xRootFolder
->hasByName( sMeta
) )
1177 const OUString
sManifest ("manifest.xml");
1179 uno::Reference
< XUnoTunnel
> xTunnel
;
1180 Any aAny
= m_xRootFolder
->getByName( sMeta
);
1182 uno::Reference
< XNameContainer
> xMetaInfFolder( xTunnel
, UNO_QUERY
);
1183 if ( xMetaInfFolder
.is() && xMetaInfFolder
->hasByName( sManifest
) )
1184 xMetaInfFolder
->removeByName( sManifest
);
1187 // Write a magic file with mimetype
1188 WriteMimetypeMagicFile( aZipOut
);
1190 else if ( m_nFormat
== embed::StorageFormats::OFOPXML
)
1192 // Remove the old [Content_Types].xml file as the
1193 // file will be re-generated
1195 const OUString
aContentTypes("[Content_Types].xml");
1197 if ( m_xRootFolder
->hasByName( aContentTypes
) )
1198 m_xRootFolder
->removeByName( aContentTypes
);
1201 // Create a vector to store data for the manifest.xml file
1202 vector
< uno::Sequence
< PropertyValue
> > aManList
;
1204 const OUString
sMediaType ("MediaType");
1205 const OUString
sVersion ("Version");
1206 const OUString
sFullPath ("FullPath");
1208 if ( m_nFormat
== embed::StorageFormats::PACKAGE
)
1210 uno::Sequence
< PropertyValue
> aPropSeq( PKG_SIZE_NOENCR_MNFST
);
1211 aPropSeq
[PKG_MNFST_MEDIATYPE
].Name
= sMediaType
;
1212 aPropSeq
[PKG_MNFST_MEDIATYPE
].Value
<<= m_pRootFolder
->GetMediaType();
1213 aPropSeq
[PKG_MNFST_VERSION
].Name
= sVersion
;
1214 aPropSeq
[PKG_MNFST_VERSION
].Value
<<= m_pRootFolder
->GetVersion();
1215 aPropSeq
[PKG_MNFST_FULLPATH
].Name
= sFullPath
;
1216 aPropSeq
[PKG_MNFST_FULLPATH
].Value
<<= OUString("/");
1218 aManList
.push_back( aPropSeq
);
1221 // Get a random number generator and seed it with current timestamp
1222 // This will be used to generate random salt and initialisation vectors
1223 // for encrypted streams
1225 osl_getSystemTime( &aTime
);
1226 rtlRandomPool aRandomPool
= rtl_random_createPool ();
1227 rtl_random_addBytes ( aRandomPool
, &aTime
, 8 );
1229 // call saveContents ( it will recursively save sub-directories
1230 OUString aEmptyString
;
1231 m_pRootFolder
->saveContents( aEmptyString
, aManList
, aZipOut
, GetEncryptionKey(), aRandomPool
);
1233 // Clean up random pool memory
1234 rtl_random_destroyPool ( aRandomPool
);
1236 if( m_nFormat
== embed::StorageFormats::PACKAGE
)
1238 WriteManifest( aZipOut
, aManList
);
1240 else if( m_nFormat
== embed::StorageFormats::OFOPXML
)
1242 WriteContentTypes( aZipOut
, aManList
);
1250 // Update our References to point to the new temp file
1253 // the case when the original contents were written directly
1256 // in case the stream is based on a file it will implement the following interface
1257 // the call should be used to be sure that the contents are written to the file system
1258 uno::Reference
< io::XAsyncOutputMonitor
> asyncOutputMonitor( xTempOut
, uno::UNO_QUERY
);
1259 if ( asyncOutputMonitor
.is() )
1260 asyncOutputMonitor
->waitForCompletion();
1262 // no need to postpone switching to the new stream since the target was written directly
1263 uno::Reference
< io::XInputStream
> xNewStream
;
1264 if ( m_eMode
== e_IMode_URL
)
1265 xNewStream
= xSink
->getStream()->getInputStream();
1266 else if ( m_eMode
== e_IMode_XStream
&& m_xStream
.is() )
1267 xNewStream
= m_xStream
->getInputStream();
1269 if ( xNewStream
.is() )
1270 ConnectTo( xNewStream
);
1273 catch ( uno::Exception
& )
1277 // no information loss appears, thus no special handling is required
1278 uno::Any
aCaught( ::cppu::getCaughtException() );
1280 // it is allowed to throw WrappedTargetException
1281 WrappedTargetException aException
;
1282 if ( aCaught
>>= aException
)
1285 throw WrappedTargetException(
1286 THROW_WHERE
"Problem writing the original content!",
1287 static_cast < OWeakObject
* > ( this ),
1292 // the document is written directly, although it was empty it is important to notify that the writing has failed
1293 // TODO/LATER: let the package be able to recover in this situation
1294 OUString
aErrTxt(THROW_WHERE
"This package is unusable!");
1295 embed::UseBackupException
aException( aErrTxt
, uno::Reference
< uno::XInterface
>(), OUString() );
1296 throw WrappedTargetException( aErrTxt
,
1297 static_cast < OWeakObject
* > ( this ),
1298 makeAny ( aException
) );
1305 uno::Reference
< XActiveDataStreamer
> ZipPackage::openOriginalForOutput()
1307 // open and truncate the original file
1308 Content
aOriginalContent(
1309 m_aURL
, uno::Reference
< XCommandEnvironment
>(),
1311 uno::Reference
< XActiveDataStreamer
> xSink
= new ActiveDataStreamer
;
1313 if ( m_eMode
== e_IMode_URL
)
1317 bool bTruncSuccess
= false;
1322 sal_Int64 aSize
= 0;
1323 Any aAny
= aOriginalContent
.setPropertyValue("Size", makeAny( aSize
) );
1324 if( !( aAny
>>= aDetect
) )
1325 bTruncSuccess
= true;
1331 if( !bTruncSuccess
)
1333 // the file is not accessible
1334 // just try to write an empty stream to it
1336 uno::Reference
< XInputStream
> xTempIn
= new DummyInputStream
; //uno::Reference< XInputStream >( xTempOut, UNO_QUERY );
1337 aOriginalContent
.writeStream( xTempIn
, true );
1340 OpenCommandArgument2 aArg
;
1341 aArg
.Mode
= OpenMode::DOCUMENT
;
1342 aArg
.Priority
= 0; // unused
1344 aArg
.Properties
= uno::Sequence
< Property
>( 0 ); // unused
1346 aOriginalContent
.executeCommand("open", makeAny( aArg
) );
1350 // seems to be nonlocal file
1351 // temporary file mechanics should be used
1358 void SAL_CALL
ZipPackage::commitChanges()
1359 throw( WrappedTargetException
, RuntimeException
, std::exception
)
1361 // lock the component for the time of committing
1362 ::osl::MutexGuard
aGuard( m_aMutexHolder
->GetMutex() );
1364 if ( m_eMode
== e_IMode_XInputStream
)
1366 IOException aException
;
1367 throw WrappedTargetException(THROW_WHERE
"This package is read only!",
1368 static_cast < OWeakObject
* > ( this ), makeAny ( aException
) );
1370 // first the writeTempFile is called, if it returns a stream the stream should be written to the target
1371 // if no stream was returned, the file was written directly, nothing should be done
1372 uno::Reference
< io::XInputStream
> xTempInStream
;
1375 xTempInStream
= writeTempFile();
1377 catch (const ucb::ContentCreationException
& r
)
1379 throw WrappedTargetException(THROW_WHERE
"Temporary file should be createable!",
1380 static_cast < OWeakObject
* > ( this ), makeAny ( r
) );
1382 if ( xTempInStream
.is() )
1384 uno::Reference
< io::XSeekable
> xTempSeek( xTempInStream
, uno::UNO_QUERY_THROW
);
1388 xTempSeek
->seek( 0 );
1390 catch( const uno::Exception
& r
)
1392 throw WrappedTargetException(THROW_WHERE
"Temporary file should be seekable!",
1393 static_cast < OWeakObject
* > ( this ), makeAny ( r
) );
1398 // connect to the temporary stream
1399 ConnectTo( xTempInStream
);
1401 catch( const io::IOException
& r
)
1403 throw WrappedTargetException(THROW_WHERE
"Temporary file should be connectable!",
1404 static_cast < OWeakObject
* > ( this ), makeAny ( r
) );
1407 if ( m_eMode
== e_IMode_XStream
)
1409 // First truncate our output stream
1410 uno::Reference
< XOutputStream
> xOutputStream
;
1412 // preparation for copy step
1415 xOutputStream
= m_xStream
->getOutputStream();
1416 uno::Reference
< XTruncate
> xTruncate ( xOutputStream
, UNO_QUERY
);
1417 if ( !xTruncate
.is() )
1418 throw uno::RuntimeException(THROW_WHERE
);
1420 // after successful truncation the original file contents are already lost
1421 xTruncate
->truncate();
1423 catch( const uno::Exception
& r
)
1425 throw WrappedTargetException(THROW_WHERE
"This package is read only!",
1426 static_cast < OWeakObject
* > ( this ), makeAny ( r
) );
1431 // then copy the contents of the tempfile to our output stream
1432 ::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream
, xOutputStream
);
1433 xOutputStream
->flush();
1434 uno::Reference
< io::XAsyncOutputMonitor
> asyncOutputMonitor(
1435 xOutputStream
, uno::UNO_QUERY
);
1436 if ( asyncOutputMonitor
.is() ) {
1437 asyncOutputMonitor
->waitForCompletion();
1440 catch( uno::Exception
& )
1442 // if anything goes wrong in this block the target file becomes corrupted
1443 // so an exception should be thrown as a notification about it
1444 // and the package must disconnect from the stream
1445 DisconnectFromTargetAndThrowException_Impl( xTempInStream
);
1448 else if ( m_eMode
== e_IMode_URL
)
1450 uno::Reference
< XOutputStream
> aOrigFileStream
;
1451 bool bCanBeCorrupted
= false;
1455 // write directly in case of local file
1456 uno::Reference
< ::com::sun::star::ucb::XSimpleFileAccess3
> xSimpleAccess(
1457 SimpleFileAccess::create( m_xContext
) );
1458 OSL_ENSURE( xSimpleAccess
.is(), "Can't instatiate SimpleFileAccess service!\n" );
1459 uno::Reference
< io::XTruncate
> xOrigTruncate
;
1460 if ( xSimpleAccess
.is() )
1464 aOrigFileStream
= xSimpleAccess
->openFileWrite( m_aURL
);
1465 xOrigTruncate
= uno::Reference
< io::XTruncate
>( aOrigFileStream
, uno::UNO_QUERY_THROW
);
1466 // after successful truncation the file is already corrupted
1467 xOrigTruncate
->truncate();
1469 catch( uno::Exception
& )
1473 if( xOrigTruncate
.is() )
1477 ::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream
, aOrigFileStream
);
1478 aOrigFileStream
->closeOutput();
1480 catch( uno::Exception
& )
1483 aOrigFileStream
->closeOutput();
1484 } catch ( uno::Exception
& ) {}
1486 aOrigFileStream
= uno::Reference
< XOutputStream
>();
1487 // the original file can already be corrupted
1488 bCanBeCorrupted
= true;
1493 if( !aOrigFileStream
.is() )
1497 uno::Reference
< XPropertySet
> xPropSet ( xTempInStream
, UNO_QUERY
);
1498 OSL_ENSURE( xPropSet
.is(), "This is a temporary file that must implement XPropertySet!\n" );
1499 if ( !xPropSet
.is() )
1500 throw uno::RuntimeException(THROW_WHERE
);
1502 OUString sTargetFolder
= m_aURL
.copy ( 0, m_aURL
.lastIndexOf ( static_cast < sal_Unicode
> ( '/' ) ) );
1504 sTargetFolder
, uno::Reference
< XCommandEnvironment
>(),
1508 Any aAny
= xPropSet
->getPropertyValue ("Uri");
1512 aInfo
.NameClash
= NameClash::OVERWRITE
;
1513 aInfo
.MoveData
= sal_False
;
1514 aInfo
.SourceURL
= sTempURL
;
1515 aInfo
.NewTitle
= rtl::Uri::decode ( m_aURL
.copy ( 1 + m_aURL
.lastIndexOf ( static_cast < sal_Unicode
> ( '/' ) ) ),
1516 rtl_UriDecodeWithCharset
,
1517 RTL_TEXTENCODING_UTF8
);
1520 // if the file is still not corrupted, it can become after the next step
1521 aContent
.executeCommand ("transfer", aAny
);
1523 catch ( const ::com::sun::star::uno::Exception
& r
)
1525 if ( bCanBeCorrupted
)
1526 DisconnectFromTargetAndThrowException_Impl( xTempInStream
);
1528 throw WrappedTargetException(
1529 THROW_WHERE
"This package may be read only!",
1530 static_cast < OWeakObject
* > ( this ),
1537 // after successful storing it can be set to false
1538 m_bMediaTypeFallbackUsed
= false;
1541 void ZipPackage::DisconnectFromTargetAndThrowException_Impl( const uno::Reference
< io::XInputStream
>& xTempStream
)
1543 m_xStream
= uno::Reference
< io::XStream
>( xTempStream
, uno::UNO_QUERY
);
1544 if ( m_xStream
.is() )
1545 m_eMode
= e_IMode_XStream
;
1547 m_eMode
= e_IMode_XInputStream
;
1551 uno::Reference
< beans::XPropertySet
> xTempFile( xTempStream
, uno::UNO_QUERY_THROW
);
1552 uno::Any aUrl
= xTempFile
->getPropertyValue("Uri");
1554 xTempFile
->setPropertyValue("RemoveFile",
1555 uno::makeAny( sal_False
) );
1557 catch ( uno::Exception
& )
1559 OSL_FAIL( "These calls are pretty simple, they should not fail!\n" );
1562 OUString
aErrTxt(THROW_WHERE
"This package is read only!");
1563 embed::UseBackupException
aException( aErrTxt
, uno::Reference
< uno::XInterface
>(), aTempURL
);
1564 throw WrappedTargetException( aErrTxt
,
1565 static_cast < OWeakObject
* > ( this ),
1566 makeAny ( aException
) );
1569 const uno::Sequence
< sal_Int8
> ZipPackage::GetEncryptionKey()
1571 uno::Sequence
< sal_Int8
> aResult
;
1573 if ( m_aStorageEncryptionKeys
.getLength() )
1575 OUString aNameToFind
;
1576 if ( m_nStartKeyGenerationID
== xml::crypto::DigestID::SHA256
)
1577 aNameToFind
= PACKAGE_ENCRYPTIONDATA_SHA256UTF8
;
1578 else if ( m_nStartKeyGenerationID
== xml::crypto::DigestID::SHA1
)
1579 aNameToFind
= PACKAGE_ENCRYPTIONDATA_SHA1UTF8
;
1581 throw uno::RuntimeException(THROW_WHERE
"No expected key is provided!" );
1583 for ( sal_Int32 nInd
= 0; nInd
< m_aStorageEncryptionKeys
.getLength(); nInd
++ )
1584 if ( m_aStorageEncryptionKeys
[nInd
].Name
.equals( aNameToFind
) )
1585 m_aStorageEncryptionKeys
[nInd
].Value
>>= aResult
;
1587 // empty keys are not allowed here
1588 // so it is not important whether there is no key, or the key is empty, it is an error
1589 if ( !aResult
.getLength() )
1590 throw uno::RuntimeException(THROW_WHERE
"No expected key is provided!" );
1593 aResult
= m_aEncryptionKey
;
1598 sal_Bool SAL_CALL
ZipPackage::hasPendingChanges()
1599 throw( RuntimeException
, std::exception
)
1603 Sequence
< ElementChange
> SAL_CALL
ZipPackage::getPendingChanges()
1604 throw( RuntimeException
, std::exception
)
1606 return uno::Sequence
< ElementChange
> ();
1610 * Function to create a new component instance; is needed by factory helper implementation.
1611 * @param xMgr service manager to if the components needs other component instances
1613 uno::Reference
< XInterface
>SAL_CALL
ZipPackage_createInstance(
1614 const uno::Reference
< XMultiServiceFactory
> & xMgr
)
1616 return uno::Reference
< XInterface
>( *new ZipPackage( comphelper::getComponentContext(xMgr
) ) );
1619 OUString
ZipPackage::static_getImplementationName()
1621 return OUString("com.sun.star.packages.comp.ZipPackage");
1624 Sequence
< OUString
> ZipPackage::static_getSupportedServiceNames()
1626 uno::Sequence
< OUString
> aNames( 1 );
1627 aNames
[0] = "com.sun.star.packages.Package";
1631 OUString
ZipPackage::getImplementationName()
1632 throw ( RuntimeException
, std::exception
)
1634 return static_getImplementationName();
1637 Sequence
< OUString
> ZipPackage::getSupportedServiceNames()
1638 throw ( RuntimeException
, std::exception
)
1640 return static_getSupportedServiceNames();
1643 sal_Bool SAL_CALL
ZipPackage::supportsService( OUString
const & rServiceName
)
1644 throw ( RuntimeException
, std::exception
)
1646 return cppu::supportsService(this, rServiceName
);
1649 uno::Reference
< XSingleServiceFactory
> ZipPackage::createServiceFactory( uno::Reference
< XMultiServiceFactory
> const & rServiceFactory
)
1651 return cppu::createSingleFactory ( rServiceFactory
,
1652 static_getImplementationName(),
1653 ZipPackage_createInstance
,
1654 static_getSupportedServiceNames() );
1657 namespace { struct lcl_ImplId
: public rtl::Static
< ::cppu::OImplementationId
, lcl_ImplId
> {}; }
1659 Sequence
< sal_Int8
> ZipPackage::getUnoTunnelImplementationId()
1660 throw ( RuntimeException
)
1662 ::cppu::OImplementationId
&rId
= lcl_ImplId::get();
1663 return rId
.getImplementationId();
1666 sal_Int64 SAL_CALL
ZipPackage::getSomething( const uno::Sequence
< sal_Int8
>& aIdentifier
)
1667 throw( RuntimeException
, std::exception
)
1669 if ( aIdentifier
.getLength() == 16 && 0 == memcmp( getUnoTunnelImplementationId().getConstArray(), aIdentifier
.getConstArray(), 16 ) )
1670 return reinterpret_cast < sal_Int64
> ( this );
1674 uno::Reference
< XPropertySetInfo
> SAL_CALL
ZipPackage::getPropertySetInfo()
1675 throw( RuntimeException
, std::exception
)
1677 return uno::Reference
< XPropertySetInfo
> ();
1680 void SAL_CALL
ZipPackage::setPropertyValue( const OUString
& aPropertyName
, const Any
& aValue
)
1681 throw( UnknownPropertyException
, PropertyVetoException
, IllegalArgumentException
, WrappedTargetException
, RuntimeException
, std::exception
)
1683 if ( m_nFormat
!= embed::StorageFormats::PACKAGE
)
1684 throw UnknownPropertyException(THROW_WHERE
);
1686 if (aPropertyName
== HAS_ENCRYPTED_ENTRIES_PROPERTY
1687 ||aPropertyName
== HAS_NONENCRYPTED_ENTRIES_PROPERTY
1688 ||aPropertyName
== IS_INCONSISTENT_PROPERTY
1689 ||aPropertyName
== MEDIATYPE_FALLBACK_USED_PROPERTY
)
1690 throw PropertyVetoException(THROW_WHERE
);
1691 else if ( aPropertyName
== ENCRYPTION_KEY_PROPERTY
)
1693 if ( !( aValue
>>= m_aEncryptionKey
) )
1694 throw IllegalArgumentException(THROW_WHERE
, uno::Reference
< uno::XInterface
>(), 2 );
1696 m_aStorageEncryptionKeys
.realloc( 0 );
1698 else if ( aPropertyName
== STORAGE_ENCRYPTION_KEYS_PROPERTY
)
1700 // this property is only necessary to support raw passwords in storage API;
1701 // because of this support the storage has to operate with more than one key dependent on storage generation algorithm;
1702 // when this support is removed, the storage will get only one key from outside
1703 // TODO/LATER: Get rid of this property as well as of support of raw passwords in storages
1704 uno::Sequence
< beans::NamedValue
> aKeys
;
1705 if ( !( aValue
>>= aKeys
) || ( aKeys
.getLength() && aKeys
.getLength() < 2 ) )
1706 throw IllegalArgumentException(THROW_WHERE
, uno::Reference
< uno::XInterface
>(), 2 );
1708 if ( aKeys
.getLength() )
1710 bool bHasSHA256
= false;
1711 bool bHasSHA1
= false;
1712 for ( sal_Int32 nInd
= 0; nInd
< aKeys
.getLength(); nInd
++ )
1714 if ( aKeys
[nInd
].Name
== PACKAGE_ENCRYPTIONDATA_SHA256UTF8
)
1716 if ( aKeys
[nInd
].Name
== PACKAGE_ENCRYPTIONDATA_SHA1UTF8
)
1720 if ( !bHasSHA256
|| !bHasSHA1
)
1721 throw IllegalArgumentException(THROW_WHERE
"Expected keys are not provided!", uno::Reference
< uno::XInterface
>(), 2 );
1724 m_aStorageEncryptionKeys
= aKeys
;
1725 m_aEncryptionKey
.realloc( 0 );
1727 else if ( aPropertyName
== ENCRYPTION_ALGORITHMS_PROPERTY
)
1729 uno::Sequence
< beans::NamedValue
> aAlgorithms
;
1730 if ( m_pZipFile
|| !( aValue
>>= aAlgorithms
) || aAlgorithms
.getLength() == 0 )
1732 // the algorithms can not be changed if the file has a persistence based on the algorithms ( m_pZipFile )
1733 throw IllegalArgumentException(THROW_WHERE
"unexpected algorithms list is provided.", uno::Reference
< uno::XInterface
>(), 2 );
1736 for ( sal_Int32 nInd
= 0; nInd
< aAlgorithms
.getLength(); nInd
++ )
1738 if ( aAlgorithms
[nInd
].Name
== "StartKeyGenerationAlgorithm" )
1741 if ( !( aAlgorithms
[nInd
].Value
>>= nID
)
1742 || ( nID
!= xml::crypto::DigestID::SHA256
&& nID
!= xml::crypto::DigestID::SHA1
) )
1743 throw IllegalArgumentException(THROW_WHERE
"Unexpected start key generation algorithm is provided!", uno::Reference
< uno::XInterface
>(), 2 );
1745 m_nStartKeyGenerationID
= nID
;
1747 else if ( aAlgorithms
[nInd
].Name
== "EncryptionAlgorithm" )
1750 if ( !( aAlgorithms
[nInd
].Value
>>= nID
)
1751 || ( nID
!= xml::crypto::CipherID::AES_CBC_W3C_PADDING
&& nID
!= xml::crypto::CipherID::BLOWFISH_CFB_8
) )
1752 throw IllegalArgumentException(THROW_WHERE
"Unexpected start key generation algorithm is provided!", uno::Reference
< uno::XInterface
>(), 2 );
1754 m_nCommonEncryptionID
= nID
;
1756 else if ( aAlgorithms
[nInd
].Name
== "ChecksumAlgorithm" )
1759 if ( !( aAlgorithms
[nInd
].Value
>>= nID
)
1760 || ( nID
!= xml::crypto::DigestID::SHA1_1K
&& nID
!= xml::crypto::DigestID::SHA256_1K
) )
1761 throw IllegalArgumentException(THROW_WHERE
"Unexpected start key generation algorithm is provided!", uno::Reference
< uno::XInterface
>(), 2 );
1763 m_nChecksumDigestID
= nID
;
1767 OSL_ENSURE( false, "Unexpected encryption algorithm is provided!" );
1768 throw IllegalArgumentException(THROW_WHERE
"unexpected algorithms list is provided.", uno::Reference
< uno::XInterface
>(), 2 );
1773 throw UnknownPropertyException(THROW_WHERE
);
1776 Any SAL_CALL
ZipPackage::getPropertyValue( const OUString
& PropertyName
)
1777 throw( UnknownPropertyException
, WrappedTargetException
, RuntimeException
, std::exception
)
1779 // TODO/LATER: Activate the check when zip-ucp is ready
1780 // if ( m_nFormat != embed::StorageFormats::PACKAGE )
1781 // throw UnknownPropertyException(THROW_WHERE );
1784 if ( PropertyName
== ENCRYPTION_KEY_PROPERTY
)
1786 aAny
<<= m_aEncryptionKey
;
1789 else if ( PropertyName
== ENCRYPTION_ALGORITHMS_PROPERTY
)
1791 ::comphelper::SequenceAsHashMap aAlgorithms
;
1792 aAlgorithms
["StartKeyGenerationAlgorithm"] <<= m_nStartKeyGenerationID
;
1793 aAlgorithms
["EncryptionAlgorithm"] <<= m_nCommonEncryptionID
;
1794 aAlgorithms
["ChecksumAlgorithm"] <<= m_nChecksumDigestID
;
1795 aAny
<<= aAlgorithms
.getAsConstNamedValueList();
1798 if ( PropertyName
== STORAGE_ENCRYPTION_KEYS_PROPERTY
)
1800 aAny
<<= m_aStorageEncryptionKeys
;
1803 else if ( PropertyName
== HAS_ENCRYPTED_ENTRIES_PROPERTY
)
1805 aAny
<<= m_bHasEncryptedEntries
;
1808 else if ( PropertyName
== HAS_NONENCRYPTED_ENTRIES_PROPERTY
)
1810 aAny
<<= m_bHasNonEncryptedEntries
;
1813 else if ( PropertyName
== IS_INCONSISTENT_PROPERTY
)
1815 aAny
<<= m_bInconsistent
;
1818 else if ( PropertyName
== MEDIATYPE_FALLBACK_USED_PROPERTY
)
1820 aAny
<<= m_bMediaTypeFallbackUsed
;
1823 throw UnknownPropertyException(THROW_WHERE
);
1825 void SAL_CALL
ZipPackage::addPropertyChangeListener( const OUString
& /*aPropertyName*/, const uno::Reference
< XPropertyChangeListener
>& /*xListener*/ )
1826 throw( UnknownPropertyException
, WrappedTargetException
, RuntimeException
, std::exception
)
1829 void SAL_CALL
ZipPackage::removePropertyChangeListener( const OUString
& /*aPropertyName*/, const uno::Reference
< XPropertyChangeListener
>& /*aListener*/ )
1830 throw( UnknownPropertyException
, WrappedTargetException
, RuntimeException
, std::exception
)
1833 void SAL_CALL
ZipPackage::addVetoableChangeListener( const OUString
& /*PropertyName*/, const uno::Reference
< XVetoableChangeListener
>& /*aListener*/ )
1834 throw( UnknownPropertyException
, WrappedTargetException
, RuntimeException
, std::exception
)
1837 void SAL_CALL
ZipPackage::removeVetoableChangeListener( const OUString
& /*PropertyName*/, const uno::Reference
< XVetoableChangeListener
>& /*aListener*/ )
1838 throw( UnknownPropertyException
, WrappedTargetException
, RuntimeException
, std::exception
)
1842 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */