Bump version to 5.0-14
[LibreOffice.git] / package / source / zippackage / ZipPackage.cxx
blob5013e93a2e5f645a24bd194eaee21aeedfdc9927
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
67 #include <osl/time.h>
68 #include <osl/diagnose.h>
69 #include "com/sun/star/io/XAsyncOutputMonitor.hpp"
71 #include <cstring>
72 #include <boost/scoped_ptr.hpp>
73 #include <vector>
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>
84 using namespace std;
85 using namespace osl;
86 using namespace cppu;
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
104 #else
105 #define THROW_WHERE ""
106 #endif
108 class ActiveDataStreamer : public ::cppu::WeakImplHelper1< XActiveDataStreamer >
110 uno::Reference< XStream > mStream;
111 public:
113 virtual uno::Reference< XStream > SAL_CALL getStream()
114 throw( RuntimeException, std::exception ) SAL_OVERRIDE
115 { return mStream; }
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
126 { return 0; }
128 virtual sal_Int32 SAL_CALL readSomeBytes( uno::Sequence< sal_Int8 >&, sal_Int32 )
129 throw ( NotConnectedException, BufferSizeExceededException, IOException, RuntimeException, std::exception ) SAL_OVERRIDE
130 { return 0; }
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
138 { return 0; }
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 )
160 , m_pZipFile( NULL )
162 m_xRootFolder = m_pRootFolder = new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
165 ZipPackage::~ZipPackage()
167 delete m_pZipFile;
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(
181 m_xContext ) );
184 aSystemPath = getSystemPathFromFileURL( xUcb, m_aURL );
186 catch ( Exception& )
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");
202 try {
203 uno::Reference< XUnoTunnel > xTunnel;
204 Any aAny = m_xRootFolder->getByName( sMeta );
205 aAny >>= xTunnel;
206 uno::Reference< XNameContainer > xMetaInfFolder( xTunnel, UNO_QUERY );
207 if ( xMetaInfFolder.is() && xMetaInfFolder->hasByName( sManifest ) )
209 aAny = xMetaInfFolder->getByName( sManifest );
210 aAny >>= xTunnel;
211 uno::Reference < XActiveDataSink > xSink ( xTunnel, UNO_QUERY );
212 if ( xSink.is() )
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;
272 aAny >>= xUnoTunnel;
273 sal_Int64 nTest=0;
274 if ( (nTest = xUnoTunnel->getSomething( ZipPackageFolder::static_getImplementationId() )) != 0 )
276 pFolder = reinterpret_cast < ZipPackageFolder* > ( nTest );
277 pFolder->SetMediaType ( sMediaType );
278 pFolder->SetVersion ( sVersion );
280 else
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;
289 sal_Int64 nSize = 0;
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 );
301 *pCount >>= nCount;
302 pStream->setIterationCount ( nCount );
304 *pSize >>= nSize;
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 );
320 if ( pStartKeyAlg )
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;
335 else
336 m_bHasNonEncryptedEntries = true;
341 bManifestParsed = true;
344 // now hide the manifest.xml file from user
345 xMetaInfFolder->removeByName( sManifest );
348 catch( Exception& )
350 if ( !m_bForceRecovery )
351 throw;
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();
378 if ( nRead )
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(
398 THROW_WHERE
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");
443 try {
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 );
450 aAny >>= xTunnel;
451 uno::Reference < io::XActiveDataSink > xSink( xTunnel, UNO_QUERY );
452 if ( xSink.is() )
454 uno::Reference< io::XInputStream > xInStream = xSink->getInputStream();
455 if ( xInStream.is() )
457 sal_Int32 nInd = 0;
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++ )
472 OUString aPath;
473 if ( aContentTypeInfo[1][nInd].First.toChar() == ( sal_Unicode )'/' )
474 aPath = aContentTypeInfo[1][nInd].First.copy( 1 );
475 else
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() );
484 if ( nTest != 0 )
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 )
500 throw;
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 )
543 break;
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;
551 else
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 )
560 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 )
571 parseManifest();
572 else if ( m_nFormat == embed::StorageFormats::OFOPXML )
573 parseContentType();
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++ )
588 OUString aParamUrl;
589 if ( ( aArguments[ind] >>= aParamUrl ))
591 m_eMode = e_IMode_URL;
594 sal_Int32 nParam = aParamUrl.indexOf( '?' );
595 if ( nParam >= 0 )
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;
607 break;
609 else if ( aCommand == "purezip" )
611 m_nFormat = embed::StorageFormats::ZIP;
612 m_pRootFolder->setPackageFormat_Impl( m_nFormat );
613 break;
615 else if ( aCommand == "ofopxml" )
617 m_nFormat = embed::StorageFormats::OFOPXML;
618 m_pRootFolder->setPackageFormat_Impl( m_nFormat );
619 break;
622 while ( nIndex >= 0 );
624 else
625 m_aURL = aParamUrl;
627 Content aContent(
628 m_aURL, uno::Reference< XCommandEnvironment >(),
629 m_xContext );
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();
641 else
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;
673 if ( !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;
690 else
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;
702 else
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" )
716 else
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;
738 else
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;
748 if ( bHaveZipFile )
750 bool bBadZipFile = false;
751 OUString message;
754 m_pZipFile = new ZipFile ( m_xContentStream, m_xContext, true, m_bForceRecovery, xProgressHandler );
755 getZipFileContents();
757 catch ( IOException & e )
759 bBadZipFile = true;
760 message = "IOException: " + e.Message;
762 catch ( ZipException & e )
764 bBadZipFile = true;
765 message = "ZipException: " + e.Message;
767 catch ( Exception & )
769 if( m_pZipFile ) { delete m_pZipFile; m_pZipFile = NULL; }
770 throw;
773 if ( bBadZipFile )
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 ) );
795 else
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() )
805 if ( bFolder )
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 ) );
811 else
812 m_aRecent.erase ( aIter );
814 else
816 sTemp = aName.copy ( nStreamIndex + 1 );
817 if ( ( *aIter ).second->hasByName( sTemp ) )
818 return ( *aIter ).second->getByName( sTemp );
819 else
820 m_aRecent.erase( aIter );
824 else
826 if ( m_pRootFolder->hasByName ( aName ) )
827 return m_pRootFolder->getByName ( aName );
829 nOldIndex = 0;
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 )
836 break;
837 if ( pCurrent->hasByName( sTemp ) )
839 pPrevious = pCurrent;
840 pCurrent = pCurrent->doGetByName( sTemp ).pFolder;
842 else
843 throw NoSuchElementException(THROW_WHERE );
844 nOldIndex = nIndex+1;
846 if ( bFolder )
848 if ( nStreamIndex != -1 )
849 m_aRecent[sDirName] = pPrevious;
850 return makeAny ( uno::Reference < XUnoTunnel > ( pCurrent ) );
852 else
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 );
861 else
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() == '/' )
875 return sal_True;
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() )
887 if ( bFolder )
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() )
892 return sal_True;
893 else
894 m_aRecent.erase ( aIter );
896 else
898 sTemp = aName.copy ( nStreamIndex + 1 );
899 if ( ( *aIter ).second->hasByName( sTemp ) )
900 return sal_True;
901 else
902 m_aRecent.erase( aIter );
906 else
908 if ( m_pRootFolder->hasByName ( aName ) )
909 return sal_True;
911 ZipPackageFolder * pCurrent = m_pRootFolder;
912 ZipPackageFolder * pPrevious = NULL;
913 nOldIndex = 0;
914 while ( ( nIndex = aName.indexOf( '/', nOldIndex )) != -1 )
916 sTemp = aName.copy ( nOldIndex, nIndex - nOldIndex );
917 if ( nIndex == nOldIndex )
918 break;
919 if ( pCurrent->hasByName( sTemp ) )
921 pPrevious = pCurrent;
922 pCurrent = pCurrent->doGetByName( sTemp ).pFolder;
924 else
925 return sal_False;
926 nOldIndex = nIndex+1;
928 if ( bFolder )
930 m_aRecent[sDirName] = pPrevious;
931 return sal_True;
933 else
935 sTemp = aName.copy( nOldIndex, aName.getLength() - nOldIndex );
937 if ( pCurrent->hasByName( sTemp ) )
939 m_aRecent[sDirName] = pCurrent;
940 return sal_True;
944 catch (const uno::RuntimeException &)
946 throw;
948 catch (const uno::Exception&)
950 uno::Any e(::cppu::getCaughtException());
951 throw lang::WrappedTargetRuntimeException(
952 OUString("ZipPackage::hasByHierarchicalName"),
953 0, e);
955 return sal_False;
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 ) );
962 return xRef;
965 uno::Reference< XInterface > SAL_CALL ZipPackage::createInstanceWithArguments( const uno::Sequence< Any >& aArguments )
966 throw( Exception, RuntimeException, std::exception )
968 bool bArg = false;
969 uno::Reference < XInterface > xRef;
970 if ( aArguments.getLength() )
971 aArguments[0] >>= bArg;
972 if ( bArg )
973 xRef = *new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
974 else
975 xRef = *new ZipPackageStream( *this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert );
977 return xRef;
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()),
990 nBufferLength );
992 pEntry->sPath = sMime;
993 pEntry->nMethod = STORED;
994 pEntry->nSize = pEntry->nCompressedSize = nBufferLength;
995 pEntry->nTime = ZipOutputStream::getCurrentDosTime();
997 CRC32 aCRC32;
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 ),
1013 makeAny( r ) );
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;
1027 pEntry->nCrc = -1;
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() );
1033 sal_Int32 nInd = 0;
1034 for ( vector < uno::Sequence < PropertyValue > >::const_iterator aIter = aManList.begin(), aEnd = aManList.end();
1035 aIter != aEnd;
1036 ++aIter, ++nInd )
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;
1062 pEntry->nCrc = -1;
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();
1073 aIter != aEnd;
1074 ++aIter)
1076 OUString aPath;
1077 OUString aType;
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
1084 nSeqLength++;
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 );
1114 if ( m_pZipFile )
1115 m_pZipFile->setInputStream( m_xContentStream );
1116 else
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();
1137 if( xSink.is() )
1139 uno::Reference< io::XStream > xStr = xSink->getStream();
1140 if( xStr.is() )
1142 xTempOut = xStr->getOutputStream();
1143 if( xTempOut.is() )
1144 bUseTemp = false;
1148 else if ( m_eMode == e_IMode_XStream && !m_pZipFile )
1150 // write directly to an empty stream
1151 xTempOut = m_xStream->getOutputStream();
1152 if( xTempOut.is() )
1153 bUseTemp = false;
1156 if( bUseTemp )
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 );
1181 aAny >>= xTunnel;
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
1224 TimeValue aTime;
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 );
1245 aZipOut.finish();
1247 if( bUseTemp )
1248 xResult = xTempIn;
1250 // Update our References to point to the new temp file
1251 if( !bUseTemp )
1253 // the case when the original contents were written directly
1254 xTempOut->flush();
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& )
1275 if( bUseTemp )
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 )
1283 throw aException;
1285 throw WrappedTargetException(
1286 THROW_WHERE "Problem writing the original content!",
1287 static_cast < OWeakObject * > ( this ),
1288 aCaught );
1290 else
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 ) );
1302 return xResult;
1305 uno::Reference< XActiveDataStreamer > ZipPackage::openOriginalForOutput()
1307 // open and truncate the original file
1308 Content aOriginalContent(
1309 m_aURL, uno::Reference< XCommandEnvironment >(),
1310 m_xContext );
1311 uno::Reference< XActiveDataStreamer > xSink = new ActiveDataStreamer;
1313 if ( m_eMode == e_IMode_URL )
1317 bool bTruncSuccess = false;
1321 Exception aDetect;
1322 sal_Int64 aSize = 0;
1323 Any aAny = aOriginalContent.setPropertyValue("Size", makeAny( aSize ) );
1324 if( !( aAny >>= aDetect ) )
1325 bTruncSuccess = true;
1327 catch( Exception& )
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
1343 aArg.Sink = xSink;
1344 aArg.Properties = uno::Sequence< Property >( 0 ); // unused
1346 aOriginalContent.executeCommand("open", makeAny( aArg ) );
1348 catch( Exception& )
1350 // seems to be nonlocal file
1351 // temporary file mechanics should be used
1355 return xSink;
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;
1453 if( isLocalFile() )
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& )
1482 try {
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 > ( '/' ) ) );
1503 Content aContent(
1504 sTargetFolder, uno::Reference< XCommandEnvironment >(),
1505 m_xContext );
1507 OUString sTempURL;
1508 Any aAny = xPropSet->getPropertyValue ("Uri");
1509 aAny >>= sTempURL;
1511 TransferInfo aInfo;
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 );
1518 aAny <<= aInfo;
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 ),
1531 makeAny ( r ) );
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;
1546 else
1547 m_eMode = e_IMode_XInputStream;
1549 OUString aTempURL;
1550 try {
1551 uno::Reference< beans::XPropertySet > xTempFile( xTempStream, uno::UNO_QUERY_THROW );
1552 uno::Any aUrl = xTempFile->getPropertyValue("Uri");
1553 aUrl >>= aTempURL;
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;
1580 else
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!" );
1592 else
1593 aResult = m_aEncryptionKey;
1595 return aResult;
1598 sal_Bool SAL_CALL ZipPackage::hasPendingChanges()
1599 throw( RuntimeException, std::exception )
1601 return sal_False;
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";
1628 return aNames;
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 );
1671 return 0;
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 )
1715 bHasSHA256 = true;
1716 if ( aKeys[nInd].Name == PACKAGE_ENCRYPTIONDATA_SHA1UTF8 )
1717 bHasSHA1 = true;
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" )
1740 sal_Int32 nID = 0;
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" )
1749 sal_Int32 nID = 0;
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" )
1758 sal_Int32 nID = 0;
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;
1765 else
1767 OSL_ENSURE( false, "Unexpected encryption algorithm is provided!" );
1768 throw IllegalArgumentException(THROW_WHERE "unexpected algorithms list is provided.", uno::Reference< uno::XInterface >(), 2 );
1772 else
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 );
1783 Any aAny;
1784 if ( PropertyName == ENCRYPTION_KEY_PROPERTY )
1786 aAny <<= m_aEncryptionKey;
1787 return aAny;
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();
1796 return aAny;
1798 if ( PropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY )
1800 aAny <<= m_aStorageEncryptionKeys;
1801 return aAny;
1803 else if ( PropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY )
1805 aAny <<= m_bHasEncryptedEntries;
1806 return aAny;
1808 else if ( PropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY )
1810 aAny <<= m_bHasNonEncryptedEntries;
1811 return aAny;
1813 else if ( PropertyName == IS_INCONSISTENT_PROPERTY )
1815 aAny <<= m_bInconsistent;
1816 return aAny;
1818 else if ( PropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY )
1820 aAny <<= m_bMediaTypeFallbackUsed;
1821 return aAny;
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: */