1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 #include "oox/core/filterdetect.hxx"
25 #include "oox/core/encryption.hxx"
26
27 #include <com/sun/star/io/XStream.hpp>
28 #include <com/sun/star/logging/LogLevel.hpp>
29 #include <comphelper/docpasswordhelper.hxx>
30 #include <comphelper/mediadescriptor.hxx>
31 #include "oox/core/fastparser.hxx"
32 #include "oox/core/relationshandler.hxx"
33 #include "oox/helper/attributelist.hxx"
34 #include "oox/helper/binaryinputstream.hxx"
35 #include "oox/helper/binaryoutputstream.hxx"
36 #include "oox/helper/zipstorage.hxx"
37 #include "oox/ole/olestorage.hxx"
38
39 namespace oox {
40 namespace core {
41
42 // ============================================================================
43
44 using namespace ::com::sun::star::beans;
45 using namespace ::com::sun::star::io;
46 using namespace ::com::sun::star::lang;
47 using namespace ::com::sun::star::logging;
48 using namespace ::com::sun::star::uno;
49 using namespace ::com::sun::star::xml::sax;
50
51 using ::comphelper::MediaDescriptor;
52 using ::comphelper::SequenceAsHashMap;
53 using ::rtl::OUString;
54
55 // ============================================================================
56
FilterDetectDocHandler(OUString & rFilterName)57 FilterDetectDocHandler::FilterDetectDocHandler( OUString& rFilterName ) :
58 mrFilterName( rFilterName )
59 {
60 maContextStack.reserve( 2 );
61 }
62
~FilterDetectDocHandler()63 FilterDetectDocHandler::~FilterDetectDocHandler()
64 {
65 }
66
startDocument()67 void SAL_CALL FilterDetectDocHandler::startDocument()
68 throw (SAXException, RuntimeException)
69 {
70 }
71
endDocument()72 void SAL_CALL FilterDetectDocHandler::endDocument()
73 throw (SAXException, RuntimeException)
74 {
75 }
76
setDocumentLocator(const Reference<XLocator> &)77 void SAL_CALL FilterDetectDocHandler::setDocumentLocator( const Reference<XLocator>& /*xLocator*/ )
78 throw (SAXException, RuntimeException)
79 {
80 }
81
startFastElement(sal_Int32 nElement,const Reference<XFastAttributeList> & rAttribs)82 void SAL_CALL FilterDetectDocHandler::startFastElement(
83 sal_Int32 nElement, const Reference< XFastAttributeList >& rAttribs )
84 throw (SAXException,RuntimeException)
85 {
86 AttributeList aAttribs( rAttribs );
87 switch ( nElement )
88 {
89 // cases for _rels/.rels
90 case PR_TOKEN( Relationships ):
91 break;
92 case PR_TOKEN( Relationship ):
93 if( !maContextStack.empty() && (maContextStack.back() == PR_TOKEN( Relationships )) )
94 parseRelationship( aAttribs );
95 break;
96
97 // cases for [Content_Types].xml
98 case PC_TOKEN( Types ):
99 break;
100 case PC_TOKEN( Default ):
101 if( !maContextStack.empty() && (maContextStack.back() == PC_TOKEN( Types )) )
102 parseContentTypesDefault( aAttribs );
103 break;
104 case PC_TOKEN( Override ):
105 if( !maContextStack.empty() && (maContextStack.back() == PC_TOKEN( Types )) )
106 parseContentTypesOverride( aAttribs );
107 break;
108 }
109 maContextStack.push_back( nElement );
110 }
111
startUnknownElement(const OUString &,const OUString &,const Reference<XFastAttributeList> &)112 void SAL_CALL FilterDetectDocHandler::startUnknownElement(
113 const OUString& /*Namespace*/, const OUString& /*Name*/, const Reference<XFastAttributeList>& /*Attribs*/ )
114 throw (SAXException, RuntimeException)
115 {
116 }
117
endFastElement(sal_Int32)118 void SAL_CALL FilterDetectDocHandler::endFastElement( sal_Int32 /*nElement*/ )
119 throw (SAXException, RuntimeException)
120 {
121 maContextStack.pop_back();
122 }
123
endUnknownElement(const OUString &,const OUString &)124 void SAL_CALL FilterDetectDocHandler::endUnknownElement(
125 const OUString& /*Namespace*/, const OUString& /*Name*/ ) throw (SAXException, RuntimeException)
126 {
127 }
128
createFastChildContext(sal_Int32,const Reference<XFastAttributeList> &)129 Reference<XFastContextHandler> SAL_CALL FilterDetectDocHandler::createFastChildContext(
130 sal_Int32 /*Element*/, const Reference<XFastAttributeList>& /*Attribs*/ )
131 throw (SAXException, RuntimeException)
132 {
133 return this;
134 }
135
createUnknownChildContext(const OUString &,const OUString &,const Reference<XFastAttributeList> &)136 Reference<XFastContextHandler> SAL_CALL FilterDetectDocHandler::createUnknownChildContext(
137 const OUString& /*Namespace*/, const OUString& /*Name*/, const Reference<XFastAttributeList>& /*Attribs*/)
138 throw (SAXException, RuntimeException)
139 {
140 return this;
141 }
142
characters(const OUString &)143 void SAL_CALL FilterDetectDocHandler::characters( const OUString& /*aChars*/ )
144 throw (SAXException, RuntimeException)
145 {
146 }
147
ignorableWhitespace(const OUString &)148 void SAL_CALL FilterDetectDocHandler::ignorableWhitespace( const OUString& /*aWhitespaces*/ )
149 throw (SAXException, RuntimeException)
150 {
151 }
152
processingInstruction(const OUString &,const OUString &)153 void SAL_CALL FilterDetectDocHandler::processingInstruction(
154 const OUString& /*aTarget*/, const OUString& /*aData*/ )
155 throw (SAXException, RuntimeException)
156 {
157 }
158
parseRelationship(const AttributeList & rAttribs)159 void FilterDetectDocHandler::parseRelationship( const AttributeList& rAttribs )
160 {
161 OUString aType = rAttribs.getString( XML_Type, OUString() );
162 if( aType.equalsAscii( "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" ) )
163 maTargetPath = RelationsFragment::removeDuplicateSlashes( OUString( sal_Unicode( '/' ) ) + rAttribs.getString( XML_Target, OUString() ) );
164 }
165
getFilterNameFromContentType(const OUString & rContentType) const166 OUString FilterDetectDocHandler::getFilterNameFromContentType( const OUString& rContentType ) const
167 {
168 if( rContentType.equalsAscii( "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" ) ||
169 rContentType.equalsAscii( "application/vnd.ms-word.document.macroEnabled.main+xml" ) )
170 return CREATE_OUSTRING( "writer_MS_Word_2007" );
171
172 if( rContentType.equalsAscii( "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml" ) ||
173 rContentType.equalsAscii( "application/vnd.ms-word.template.macroEnabledTemplate.main+xml" ) )
174 return CREATE_OUSTRING( "writer_MS_Word_2007_Template" );
175
176 if( rContentType.equalsAscii( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" ) ||
177 rContentType.equalsAscii( "application/vnd.ms-excel.sheet.macroEnabled.main+xml" ) )
178 return CREATE_OUSTRING( "MS Excel 2007 XML" );
179
180 if( rContentType.equalsAscii( "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" ) ||
181 rContentType.equalsAscii( "application/vnd.ms-excel.template.macroEnabled.main+xml" ) )
182 return CREATE_OUSTRING( "MS Excel 2007 XML Template" );
183
184 if( rContentType.equalsAscii( "application/vnd.ms-excel.sheet.binary.macroEnabled.main" ) )
185 return CREATE_OUSTRING( "MS Excel 2007 Binary" );
186
187 if( rContentType.equalsAscii( "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml" ) ||
188 rContentType.equalsAscii( "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml" ) )
189 return CREATE_OUSTRING( "MS PowerPoint 2007 XML" );
190
191 if( rContentType.equalsAscii( "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml" ) ||
192 rContentType.equalsAscii( "application/vnd.ms-powerpoint.template.macroEnabled.main+xml" ) )
193 return CREATE_OUSTRING( "MS PowerPoint 2007 XML Template" );
194
195 return OUString();
196 }
197
parseContentTypesDefault(const AttributeList & rAttribs)198 void FilterDetectDocHandler::parseContentTypesDefault( const AttributeList& rAttribs )
199 {
200 // only if no overridden part name found
201 if( mrFilterName.getLength() == 0 )
202 {
203 // check if target path ends with extension
204 OUString aExtension = rAttribs.getString( XML_Extension, OUString() );
205 sal_Int32 nExtPos = maTargetPath.getLength() - aExtension.getLength();
206 if( (nExtPos > 0) && (maTargetPath[ nExtPos - 1 ] == '.') && maTargetPath.match( aExtension, nExtPos ) )
207 mrFilterName = getFilterNameFromContentType( rAttribs.getString( XML_ContentType, OUString() ) );
208 }
209 }
210
parseContentTypesOverride(const AttributeList & rAttribs)211 void FilterDetectDocHandler::parseContentTypesOverride( const AttributeList& rAttribs )
212 {
213 if( rAttribs.getString( XML_PartName, OUString() ).equals( maTargetPath ) )
214 mrFilterName = getFilterNameFromContentType( rAttribs.getString( XML_ContentType, OUString() ) );
215 }
216
217 // ============================================================================
218
219 /* Helper for XServiceInfo */
FilterDetect_getSupportedServiceNames()220 Sequence< OUString > FilterDetect_getSupportedServiceNames()
221 {
222 Sequence< OUString > aServiceNames( 1 );
223 aServiceNames[ 0 ] = CREATE_OUSTRING( "com.sun.star.frame.ExtendedTypeDetection" );
224 return aServiceNames;
225 }
226
227 /* Helper for XServiceInfo */
FilterDetect_getImplementationName()228 OUString FilterDetect_getImplementationName()
229 {
230 return CREATE_OUSTRING( "com.sun.star.comp.oox.FormatDetector" );
231 }
232
233 /* Helper for registry */
FilterDetect_createInstance(const Reference<XComponentContext> & rxContext)234 Reference< XInterface > SAL_CALL FilterDetect_createInstance( const Reference< XComponentContext >& rxContext ) throw( Exception )
235 {
236 return static_cast< ::cppu::OWeakObject* >( new FilterDetect( rxContext ) );
237 }
238
239 // ----------------------------------------------------------------------------
240
FilterDetect(const Reference<XComponentContext> & rxContext)241 FilterDetect::FilterDetect( const Reference< XComponentContext >& rxContext ) throw( RuntimeException ) :
242 mxContext( rxContext, UNO_SET_THROW ),
243 logger( rxContext )
244 {
245 }
246
~FilterDetect()247 FilterDetect::~FilterDetect()
248 {
249 }
250
251 namespace {
252
253 // ----------------------------------------------------------------------------
254
lclIsZipPackage(const Reference<XComponentContext> & rxContext,const Reference<XInputStream> & rxInStrm)255 bool lclIsZipPackage( const Reference< XComponentContext >& rxContext, const Reference< XInputStream >& rxInStrm )
256 {
257 ZipStorage aZipStorage( rxContext, rxInStrm );
258 return aZipStorage.isStorage();
259 }
260
261 // the password verifier ------------------------------------------------------
262
263 class PasswordVerifier : public ::comphelper::IDocPasswordVerifier
264 {
265 public:
266 explicit PasswordVerifier( const ::boost::shared_ptr< EncryptionInfo >& rEncryptInfo, const ::comphelper::EventLogger& rLogger );
267
268 virtual ::comphelper::DocPasswordVerifierResult
269 verifyPassword( const OUString& rPassword, Sequence< NamedValue >& o_rEncryptionData );
270 virtual ::comphelper::DocPasswordVerifierResult
271 verifyEncryptionData( const Sequence< NamedValue >& rEncryptionData );
272
273 private:
274 const ::boost::shared_ptr< EncryptionInfo> encryptionInfo;
275 const ::comphelper::EventLogger logger;
276 };
277
PasswordVerifier(const::boost::shared_ptr<EncryptionInfo> & rEncryptInfo,const::comphelper::EventLogger & rLogger)278 PasswordVerifier::PasswordVerifier( const ::boost::shared_ptr< EncryptionInfo>& rEncryptInfo, const ::comphelper::EventLogger& rLogger ) :
279 encryptionInfo( rEncryptInfo ),
280 logger( rLogger )
281 {
282 }
283
verifyPassword(const OUString & rPassword,Sequence<NamedValue> & o_rEncryptionData)284 ::comphelper::DocPasswordVerifierResult PasswordVerifier::verifyPassword( const OUString& rPassword, Sequence< NamedValue >& o_rEncryptionData )
285 {
286 try
287 {
288 o_rEncryptionData = encryptionInfo->verifyPassword( rPassword );
289 if( o_rEncryptionData.hasElements() )
290 {
291 logger.log( LogLevel::FINE, OUString::createFromAscii( "Password is correct" ) );
292 return ::comphelper::DocPasswordVerifierResult_OK;
293 }
294 else
295 {
296 logger.log( LogLevel::WARNING, OUString::createFromAscii( "Password is incorrect" ) );
297 return ::comphelper::DocPasswordVerifierResult_WRONG_PASSWORD;
298 }
299 }
300 catch ( const Exception &e )
301 {
302 logger.log( LogLevel::WARNING, "Error verifying password: $1$", e.Message );
303 return ::comphelper::DocPasswordVerifierResult_ABORT;
304 }
305 }
306
verifyEncryptionData(const Sequence<NamedValue> & rEncryptionData)307 ::comphelper::DocPasswordVerifierResult PasswordVerifier::verifyEncryptionData( const Sequence< NamedValue >& rEncryptionData )
308 {
309 try
310 {
311 bool bResult = encryptionInfo->verifyEncryptionData( rEncryptionData );
312 if( bResult )
313 {
314 logger.log( LogLevel::FINE, OUString::createFromAscii( "EncryptionData is correct" ) );
315 return ::comphelper::DocPasswordVerifierResult_OK;
316 }
317 else
318 {
319 logger.log( LogLevel::WARNING, OUString::createFromAscii( "EncryptionData is incorrect" ) );
320 return ::comphelper::DocPasswordVerifierResult_WRONG_PASSWORD;
321 }
322 }
323 catch ( const Exception& e )
324 {
325 logger.log( LogLevel::WARNING, "Error verifying EncryptionData: $1$", e.Message );
326 return ::comphelper::DocPasswordVerifierResult_ABORT;
327 }
328 }
329
330 } // namespace
331
332 // ----------------------------------------------------------------------------
333
extractUnencryptedPackage(MediaDescriptor & rMediaDesc) const334 Reference< XInputStream > FilterDetect::extractUnencryptedPackage( MediaDescriptor& rMediaDesc ) const
335 {
336 // try the plain input stream
337 Reference< XInputStream > xInStrm( rMediaDesc[ MediaDescriptor::PROP_INPUTSTREAM() ], UNO_QUERY );
338 if( !xInStrm.is() || lclIsZipPackage( mxContext, xInStrm ) )
339 return xInStrm;
340
341 // check if a temporary file is passed in the 'ComponentData' property
342 Reference< XStream > xDecrypted( rMediaDesc.getComponentDataEntry( CREATE_OUSTRING( "DecryptedPackage" ) ), UNO_QUERY );
343 if( xDecrypted.is() )
344 {
345 Reference< XInputStream > xDecrInStrm = xDecrypted->getInputStream();
346 if( lclIsZipPackage( mxContext, xDecrInStrm ) )
347 return xDecrInStrm;
348 }
349
350 // try to decrypt an encrypted OLE package
351 ::oox::ole::OleStorage aOleStorage( mxContext, xInStrm, false );
352 if( aOleStorage.isStorage() ) try
353 {
354 // open the required input streams in the encrypted package
355 Reference< XInputStream > xEncryptionInfo( aOleStorage.openInputStream( CREATE_OUSTRING( "EncryptionInfo" ) ), UNO_SET_THROW );
356 Reference< XInputStream > xEncryptedPackage( aOleStorage.openInputStream( CREATE_OUSTRING( "EncryptedPackage" ) ), UNO_SET_THROW );
357
358 // read the encryption info stream
359 ::boost::shared_ptr< EncryptionInfo > encryptionInfo( EncryptionInfo::readEncryptionInfo( mxContext, xEncryptionInfo ) );
360
361 // check flags and algorithm IDs, required are AES128 and SHA-1
362 bool bImplemented = encryptionInfo->isImplemented();
363 if( bImplemented )
364 {
365 /* "VelvetSweatshop" is the built-in default encryption
366 password used by MS Excel for the "workbook protection"
367 feature with password. Try this first before prompting the
368 user for a password. */
369 ::std::vector< OUString > aDefaultPasswords;
370 aDefaultPasswords.push_back( CREATE_OUSTRING( "VelvetSweatshop" ) );
371
372 /* Use the comphelper password helper to request a password.
373 This helper returns either with the correct password
374 (according to the verifier), or with an empty string if
375 user has cancelled the password input dialog. */
376 PasswordVerifier aVerifier( encryptionInfo, logger );
377 Sequence< NamedValue > aEncryptionData = ::comphelper::DocPasswordHelper::requestAndVerifyDocPassword(
378 aVerifier, rMediaDesc, ::comphelper::DocPasswordRequestType_MS, &aDefaultPasswords );
379
380 if( aEncryptionData.getLength() == 0 )
381 {
382 rMediaDesc[ MediaDescriptor::PROP_ABORTED() ] <<= true;
383 }
384 else
385 {
386 // create temporary file for unencrypted package
387 Reference< XMultiComponentFactory > xFactory( mxContext->getServiceManager(), UNO_QUERY_THROW );
388 Reference< XStream > xTempFile( xFactory->createInstanceWithContext( CREATE_OUSTRING( "com.sun.star.io.TempFile" ), mxContext ), UNO_QUERY_THROW );
389 Reference< XOutputStream > xDecryptedPackage( xTempFile->getOutputStream(), UNO_SET_THROW );
390 BinaryXOutputStream aDecryptedPackage( xDecryptedPackage, true );
391 BinaryXInputStream aEncryptedPackage( xEncryptedPackage, true );
392
393 encryptionInfo->decryptStream( aEncryptedPackage, aDecryptedPackage );
394 aDecryptedPackage.seekToStart();
395
396 // store temp file in media descriptor to keep it alive
397 rMediaDesc.setComponentDataEntry( CREATE_OUSTRING( "DecryptedPackage" ), Any( xTempFile ) );
398
399 Reference< XInputStream > xDecrInStrm = xTempFile->getInputStream();
400 if( lclIsZipPackage( mxContext, xDecrInStrm ) )
401 return xDecrInStrm;
402 }
403 }
404 else
405 logger.log( LogLevel::WARNING, "Encryption type not implemented" );
406 }
407 catch( Exception& e )
408 {
409 logger.log( LogLevel::WARNING, "Error in ::oox::core::FilterDetect::extractUnencryptedPackage(): $1$", e.Message );
410 }
411
412 return Reference< XInputStream >();
413 }
414
415 // com.sun.star.lang.XServiceInfo interface -----------------------------------
416
getImplementationName()417 OUString SAL_CALL FilterDetect::getImplementationName() throw( RuntimeException )
418 {
419 return FilterDetect_getImplementationName();
420 }
421
supportsService(const OUString & rServiceName)422 sal_Bool SAL_CALL FilterDetect::supportsService( const OUString& rServiceName ) throw( RuntimeException )
423 {
424 const Sequence< OUString > aServices = FilterDetect_getSupportedServiceNames();
425 const OUString* pArray = aServices.getConstArray();
426 const OUString* pArrayEnd = pArray + aServices.getLength();
427 return ::std::find( pArray, pArrayEnd, rServiceName ) != pArrayEnd;
428 }
429
getSupportedServiceNames()430 Sequence< OUString > SAL_CALL FilterDetect::getSupportedServiceNames() throw( RuntimeException )
431 {
432 return FilterDetect_getSupportedServiceNames();
433 }
434
435 // com.sun.star.document.XExtendedFilterDetection interface -------------------
436
detect(Sequence<PropertyValue> & rMediaDescSeq)437 OUString SAL_CALL FilterDetect::detect( Sequence< PropertyValue >& rMediaDescSeq ) throw( RuntimeException )
438 {
439 OUString aFilterName;
440 MediaDescriptor aMediaDesc( rMediaDescSeq );
441
442 /* Check that the user has not chosen to abort detection, e.g. by hitting
443 'Cancel' in the password input dialog. This may happen because this
444 filter detection is used by different filters. */
445 bool bAborted = aMediaDesc.getUnpackedValueOrDefault( MediaDescriptor::PROP_ABORTED(), false );
446 if( !bAborted ) try
447 {
448 aMediaDesc.addInputStream();
449
450 /* Get the unencrypted input stream. This may include creation of a
451 temporary file that contains the decrypted package. This temporary
452 file will be stored in the 'ComponentData' property of the media
453 descriptor. */
454 Reference< XInputStream > xInStrm( extractUnencryptedPackage( aMediaDesc ), UNO_SET_THROW );
455
456 // stream must be a ZIP package
457 ZipStorage aZipStorage( mxContext, xInStrm );
458 if( aZipStorage.isStorage() )
459 {
460 // create the fast parser, register the XML namespaces, set document handler
461 FastParser aParser( mxContext );
462 aParser.registerNamespace( NMSP_packageRel );
463 aParser.registerNamespace( NMSP_officeRel );
464 aParser.registerNamespace( NMSP_packageContentTypes );
465 aParser.setDocumentHandler( new FilterDetectDocHandler( aFilterName ) );
466
467 /* Parse '_rels/.rels' to get the target path and '[Content_Types].xml'
468 to determine the content type of the part at the target path. */
469 aParser.parseStream( aZipStorage, CREATE_OUSTRING( "_rels/.rels" ) );
470 aParser.parseStream( aZipStorage, CREATE_OUSTRING( "[Content_Types].xml" ) );
471 }
472 }
473 catch( Exception& e )
474 {
475 logger.log( LogLevel::WARNING, "Error in ::oox::core::FilterDetect::detect(): $1$", e.Message );
476 }
477
478 // write back changed media descriptor members
479 aMediaDesc >> rMediaDescSeq;
480 return aFilterName;
481 }
482
483 // ============================================================================
484
485 } // namespace core
486 } // namespace oox
487