xref: /trunk/main/oox/source/core/filterdetect.cxx (revision 604463aa)
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