LCOV - code coverage report
Current view: top level - libreoffice/package/source/manifest - ManifestExport.cxx (source / functions) Hit Total Coverage
Test: libreoffice_filtered.info Lines: 206 228 90.4 %
Date: 2012-12-17 Functions: 1 1 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
       2             : /*
       3             :  * This file is part of the LibreOffice project.
       4             :  *
       5             :  * This Source Code Form is subject to the terms of the Mozilla Public
       6             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       7             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       8             :  *
       9             :  * This file incorporates work covered by the following license notice:
      10             :  *
      11             :  *   Licensed to the Apache Software Foundation (ASF) under one or more
      12             :  *   contributor license agreements. See the NOTICE file distributed
      13             :  *   with this work for additional information regarding copyright
      14             :  *   ownership. The ASF licenses this file to you under the Apache
      15             :  *   License, Version 2.0 (the "License"); you may not use this file
      16             :  *   except in compliance with the License. You may obtain a copy of
      17             :  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
      18             :  */
      19             : 
      20             : 
      21             : #include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp>
      22             : #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
      23             : #include <com/sun/star/xml/sax/XAttributeList.hpp>
      24             : #include <com/sun/star/xml/crypto/DigestID.hpp>
      25             : #include <com/sun/star/xml/crypto/CipherID.hpp>
      26             : #include <com/sun/star/beans/PropertyValue.hpp>
      27             : #include <com/sun/star/uno/RuntimeException.hpp>
      28             : 
      29             : #include <ManifestDefines.hxx>
      30             : #include <ManifestExport.hxx>
      31             : #include <sax/tools/converter.hxx>
      32             : 
      33             : #include <rtl/ustrbuf.hxx>
      34             : #include <comphelper/documentconstants.hxx>
      35             : #include <comphelper/attributelist.hxx>
      36             : 
      37             : using namespace ::com::sun::star;
      38             : 
      39             : using ::rtl::OUString;
      40             : using ::rtl::OUStringBuffer;
      41             : 
      42          20 : ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > xHandler,  const uno::Sequence< uno::Sequence < beans::PropertyValue > >& rManList )
      43             : {
      44          20 :     const OUString sFileEntryElement     ( ELEMENT_FILE_ENTRY );
      45          20 :     const OUString sManifestElement      ( ELEMENT_MANIFEST );
      46          20 :     const OUString sEncryptionDataElement( ELEMENT_ENCRYPTION_DATA );
      47          20 :     const OUString sAlgorithmElement     ( ELEMENT_ALGORITHM );
      48          20 :     const OUString sStartKeyGenerationElement ( ELEMENT_START_KEY_GENERATION );
      49          20 :     const OUString sKeyDerivationElement ( ELEMENT_KEY_DERIVATION );
      50             : 
      51          20 :     const OUString sCdataAttribute       ( ATTRIBUTE_CDATA );
      52          20 :     const OUString sMediaTypeAttribute   ( ATTRIBUTE_MEDIA_TYPE );
      53          20 :     const OUString sVersionAttribute     ( ATTRIBUTE_VERSION );
      54          20 :     const OUString sFullPathAttribute    ( ATTRIBUTE_FULL_PATH );
      55          20 :     const OUString sSizeAttribute        ( ATTRIBUTE_SIZE );
      56          20 :     const OUString sKeySizeAttribute     ( ATTRIBUTE_KEY_SIZE );
      57          20 :     const OUString sSaltAttribute        ( ATTRIBUTE_SALT );
      58          20 :     const OUString sInitialisationVectorAttribute ( ATTRIBUTE_INITIALISATION_VECTOR );
      59          20 :     const OUString sIterationCountAttribute  ( ATTRIBUTE_ITERATION_COUNT );
      60          20 :     const OUString sAlgorithmNameAttribute   ( ATTRIBUTE_ALGORITHM_NAME );
      61          20 :     const OUString sStartKeyGenerationNameAttribute ( ATTRIBUTE_START_KEY_GENERATION_NAME );
      62          20 :     const OUString sKeyDerivationNameAttribute   ( ATTRIBUTE_KEY_DERIVATION_NAME );
      63          20 :     const OUString sChecksumTypeAttribute    ( ATTRIBUTE_CHECKSUM_TYPE );
      64          20 :     const OUString sChecksumAttribute    ( ATTRIBUTE_CHECKSUM);
      65             : 
      66          20 :     const OUString sFullPathProperty     ( "FullPath" );
      67          20 :     const OUString sVersionProperty  ( "Version" );
      68          20 :     const OUString sMediaTypeProperty    ( "MediaType" );
      69          20 :     const OUString sIterationCountProperty   ( "IterationCount" );
      70          20 :     const OUString  sDerivedKeySizeProperty  ( "DerivedKeySize" );
      71          20 :     const OUString sSaltProperty         ( "Salt" );
      72          20 :     const OUString sInitialisationVectorProperty( "InitialisationVector" );
      73          20 :     const OUString sSizeProperty         ( "Size" );
      74          20 :     const OUString sDigestProperty       ( "Digest" );
      75          20 :     const OUString sEncryptionAlgProperty    ( "EncryptionAlgorithm" );
      76          20 :     const OUString sStartKeyAlgProperty  ( "StartKeyAlgorithm" );
      77          20 :     const OUString sDigestAlgProperty    ( "DigestAlgorithm" );
      78             : 
      79          20 :     const OUString sWhiteSpace           ( " " );
      80             : 
      81          20 :     const OUString sSHA256_URL           ( SHA256_URL );
      82          20 :     const OUString  sSHA1_Name           ( SHA1_NAME );
      83             : 
      84          20 :     const OUString  sSHA1_1k_Name        ( SHA1_1K_NAME );
      85          20 :     const OUString  sSHA256_1k_URL       ( SHA256_1K_URL );
      86             : 
      87          20 :     const OUString  sBlowfish_Name       ( BLOWFISH_NAME );
      88          20 :     const OUString  sAES256_URL          ( AES256_URL );
      89             : 
      90          20 :     const OUString  sPBKDF2_Name         ( PBKDF2_NAME );
      91             : 
      92          20 :     ::comphelper::AttributeList * pRootAttrList = new ::comphelper::AttributeList;
      93          20 :     const uno::Sequence < beans::PropertyValue > *pSequence = rManList.getConstArray();
      94          20 :     const sal_uInt32 nManLength = rManList.getLength();
      95             : 
      96             :     // find the mediatype of the document if any
      97          20 :     ::rtl::OUString aDocMediaType;
      98          20 :     ::rtl::OUString aDocVersion;
      99          20 :     for (sal_uInt32 nInd = 0; nInd < nManLength ; nInd++ )
     100             :     {
     101          20 :         ::rtl::OUString aMediaType;
     102          20 :         ::rtl::OUString aPath;
     103          20 :         ::rtl::OUString aVersion;
     104             : 
     105          20 :         const beans::PropertyValue *pValue = pSequence[nInd].getConstArray();
     106          60 :         for (sal_uInt32 j = 0, nNum = pSequence[nInd].getLength(); j < nNum; j++, pValue++)
     107             :         {
     108          60 :             if (pValue->Name.equals (sMediaTypeProperty) )
     109             :             {
     110          20 :                 pValue->Value >>= aMediaType;
     111             :             }
     112          40 :             else if (pValue->Name.equals (sFullPathProperty) )
     113             :             {
     114          20 :                 pValue->Value >>= aPath;
     115             :             }
     116          20 :             else if (pValue->Name.equals (sVersionProperty) )
     117             :             {
     118          20 :                 pValue->Value >>= aVersion;
     119             :             }
     120             : 
     121          60 :             if ( !aPath.isEmpty() && !aMediaType.isEmpty() && !aVersion.isEmpty() )
     122          20 :                 break;
     123             :         }
     124             : 
     125          20 :         if ( aPath == "/" )
     126             :         {
     127          20 :             aDocMediaType = aMediaType;
     128          20 :             aDocVersion = aVersion;
     129             :             break;
     130             :         }
     131          20 :     }
     132             : 
     133          20 :     sal_Bool bProvideDTD = sal_False;
     134          20 :     sal_Bool bAcceptNonemptyVersion = sal_False;
     135          20 :     sal_Bool bStoreStartKeyGeneration = sal_False;
     136          20 :     if ( !aDocMediaType.isEmpty() )
     137             :     {
     138         108 :         if ( aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_ASCII
     139          16 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_WEB_ASCII
     140          16 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_GLOBAL_ASCII
     141          16 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_ASCII
     142          16 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_ASCII
     143          16 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_ASCII
     144           2 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_CHART_ASCII
     145           2 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_DATABASE_ASCII
     146           2 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_ASCII
     147           2 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_TEMPLATE_ASCII
     148           0 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_TEMPLATE_ASCII
     149           0 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_TEMPLATE_ASCII
     150           0 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_TEMPLATE_ASCII
     151           0 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_CHART_TEMPLATE_ASCII
     152           0 :           || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_TEMPLATE_ASCII )
     153             : 
     154             :         {
     155             :             // oasis format
     156             :             pRootAttrList->AddAttribute ( ATTRIBUTE_XMLNS,
     157             :                                         sCdataAttribute,
     158          20 :                                         MANIFEST_OASIS_NAMESPACE );
     159          20 :             bAcceptNonemptyVersion = sal_True;
     160          20 :             if ( aDocVersion.compareTo( ODFVER_012_TEXT ) >= 0 )
     161             :             {
     162             :                 // this is ODF12 generation, let encrypted streams contain start-key-generation entry
     163          20 :                 bStoreStartKeyGeneration = sal_True;
     164          20 :                 pRootAttrList->AddAttribute ( sVersionAttribute, sCdataAttribute, aDocVersion );
     165             :             }
     166             :         }
     167             :         else
     168             :         {
     169             :             // even if it is no SO6 format the namespace must be specified
     170             :             // thus SO6 format is used as default one
     171             :             pRootAttrList->AddAttribute ( ATTRIBUTE_XMLNS,
     172             :                                         sCdataAttribute,
     173           0 :                                         MANIFEST_NAMESPACE );
     174             : 
     175           0 :             bProvideDTD = sal_True;
     176             :         }
     177             :     }
     178             : 
     179          20 :     uno::Reference < xml::sax::XAttributeList > xRootAttrList (pRootAttrList);
     180             : 
     181          20 :     xHandler->startDocument();
     182          20 :     uno::Reference < xml::sax::XExtendedDocumentHandler > xExtHandler ( xHandler, uno::UNO_QUERY );
     183          20 :     if ( xExtHandler.is() && bProvideDTD )
     184             :     {
     185           0 :         OUString aDocType ( MANIFEST_DOCTYPE );
     186           0 :         xExtHandler->unknown ( aDocType );
     187           0 :         xHandler->ignorableWhitespace ( sWhiteSpace );
     188             :     }
     189          20 :     xHandler->startElement( sManifestElement, xRootAttrList );
     190             : 
     191         116 :     for (sal_uInt32 i = 0 ; i < nManLength ; i++)
     192             :     {
     193          96 :         ::comphelper::AttributeList *pAttrList = new ::comphelper::AttributeList;
     194          96 :         const beans::PropertyValue *pValue = pSequence[i].getConstArray();
     195          96 :         ::rtl::OUString aString;
     196          96 :         const uno::Any *pVector = NULL, *pSalt = NULL, *pIterationCount = NULL, *pDigest = NULL, *pDigestAlg = NULL, *pEncryptAlg = NULL, *pStartKeyAlg = NULL, *pDerivedKeySize = NULL;
     197         456 :         for (sal_uInt32 j = 0, nNum = pSequence[i].getLength(); j < nNum; j++, pValue++)
     198             :         {
     199         360 :             if (pValue->Name.equals (sMediaTypeProperty) )
     200             :             {
     201          96 :                 pValue->Value >>= aString;
     202          96 :                 pAttrList->AddAttribute ( sMediaTypeAttribute, sCdataAttribute, aString );
     203             :             }
     204         264 :             else if (pValue->Name.equals (sVersionProperty) )
     205             :             {
     206          96 :                 pValue->Value >>= aString;
     207             :                 // the version is stored only if it is not empty
     208          96 :                 if ( bAcceptNonemptyVersion && !aString.isEmpty() )
     209          20 :                     pAttrList->AddAttribute ( sVersionAttribute, sCdataAttribute, aString );
     210             :             }
     211         168 :             else if (pValue->Name.equals (sFullPathProperty) )
     212             :             {
     213          96 :                 pValue->Value >>= aString;
     214          96 :                 pAttrList->AddAttribute ( sFullPathAttribute, sCdataAttribute, aString );
     215             :             }
     216          72 :             else if (pValue->Name.equals (sSizeProperty) )
     217             :             {
     218           8 :                 sal_Int64 nSize = 0;
     219           8 :                 pValue->Value >>= nSize;
     220           8 :                 ::rtl::OUStringBuffer aBuffer;
     221           8 :                 aBuffer.append ( nSize );
     222           8 :                 pAttrList->AddAttribute ( sSizeAttribute, sCdataAttribute, aBuffer.makeStringAndClear() );
     223             :             }
     224          64 :             else if (pValue->Name.equals (sInitialisationVectorProperty) )
     225           8 :                 pVector = &pValue->Value;
     226          56 :             else if (pValue->Name.equals (sSaltProperty) )
     227           8 :                 pSalt = &pValue->Value;
     228          48 :             else if (pValue->Name.equals (sIterationCountProperty) )
     229           8 :                 pIterationCount = &pValue->Value;
     230          40 :             else if (pValue->Name.equals ( sDigestProperty ) )
     231           8 :                 pDigest = &pValue->Value;
     232          32 :             else if (pValue->Name.equals ( sDigestAlgProperty ) )
     233           8 :                 pDigestAlg = &pValue->Value;
     234          24 :             else if (pValue->Name.equals ( sEncryptionAlgProperty ) )
     235           8 :                 pEncryptAlg = &pValue->Value;
     236          16 :             else if (pValue->Name.equals ( sStartKeyAlgProperty ) )
     237           8 :                 pStartKeyAlg = &pValue->Value;
     238           8 :             else if (pValue->Name.equals ( sDerivedKeySizeProperty ) )
     239           8 :                 pDerivedKeySize = &pValue->Value;
     240             :         }
     241             : 
     242          96 :         xHandler->ignorableWhitespace ( sWhiteSpace );
     243          96 :         uno::Reference < xml::sax::XAttributeList > xAttrList ( pAttrList );
     244          96 :         xHandler->startElement( sFileEntryElement , xAttrList);
     245          96 :         if ( pVector && pSalt && pIterationCount && pDigest && pDigestAlg && pEncryptAlg && pStartKeyAlg && pDerivedKeySize )
     246             :         {
     247             :             // ==== Encryption Data
     248           8 :             ::comphelper::AttributeList * pNewAttrList = new ::comphelper::AttributeList;
     249           8 :             uno::Reference < xml::sax::XAttributeList > xNewAttrList (pNewAttrList);
     250           8 :             ::rtl::OUStringBuffer aBuffer;
     251           8 :             uno::Sequence < sal_Int8 > aSequence;
     252             : 
     253           8 :             xHandler->ignorableWhitespace ( sWhiteSpace );
     254             : 
     255             :             // ==== Digest
     256           8 :             ::rtl::OUString sChecksumType;
     257           8 :             sal_Int32 nDigestAlgID = 0;
     258           8 :             *pDigestAlg >>= nDigestAlgID;
     259           8 :             if ( nDigestAlgID == xml::crypto::DigestID::SHA256_1K )
     260           8 :                 sChecksumType = sSHA256_1k_URL;
     261           0 :             else if ( nDigestAlgID == xml::crypto::DigestID::SHA1_1K )
     262           0 :                 sChecksumType = sSHA1_1k_Name;
     263             :             else
     264           0 :                 throw uno::RuntimeException( OSL_LOG_PREFIX "Unexpected digest algorithm is provided!", uno::Reference< uno::XInterface >() );
     265             : 
     266           8 :             pNewAttrList->AddAttribute ( sChecksumTypeAttribute, sCdataAttribute, sChecksumType );
     267           8 :             *pDigest >>= aSequence;
     268           8 :             ::sax::Converter::encodeBase64(aBuffer, aSequence);
     269           8 :             pNewAttrList->AddAttribute ( sChecksumAttribute, sCdataAttribute, aBuffer.makeStringAndClear() );
     270             : 
     271           8 :             xHandler->startElement( sEncryptionDataElement , xNewAttrList);
     272             : 
     273             :             // ==== Algorithm
     274           8 :             pNewAttrList = new ::comphelper::AttributeList;
     275           8 :             xNewAttrList = pNewAttrList;
     276             : 
     277           8 :             sal_Int32 nEncAlgID = 0;
     278           8 :             sal_Int32 nDerivedKeySize = 0;
     279           8 :             *pEncryptAlg >>= nEncAlgID;
     280           8 :             *pDerivedKeySize >>= nDerivedKeySize;
     281             : 
     282           8 :             ::rtl::OUString sEncAlgName;
     283           8 :             if ( nEncAlgID == xml::crypto::CipherID::AES_CBC_W3C_PADDING )
     284             :             {
     285             :                 OSL_ENSURE( nDerivedKeySize, "Unexpected key size is provided!" );
     286           8 :                 if ( nDerivedKeySize != 32 )
     287           0 :                     throw uno::RuntimeException( OSL_LOG_PREFIX "Unexpected key size is provided!", uno::Reference< uno::XInterface >() );
     288             : 
     289           8 :                 sEncAlgName = sAES256_URL;
     290             :             }
     291           0 :             else if ( nEncAlgID == xml::crypto::CipherID::BLOWFISH_CFB_8 )
     292             :             {
     293           0 :                 sEncAlgName = sBlowfish_Name;
     294             :             }
     295             :             else
     296           0 :                 throw uno::RuntimeException( OSL_LOG_PREFIX "Unexpected encryption algorithm is provided!", uno::Reference< uno::XInterface >() );
     297             : 
     298           8 :             pNewAttrList->AddAttribute ( sAlgorithmNameAttribute, sCdataAttribute, sEncAlgName );
     299             : 
     300           8 :             *pVector >>= aSequence;
     301           8 :             ::sax::Converter::encodeBase64(aBuffer, aSequence);
     302           8 :             pNewAttrList->AddAttribute ( sInitialisationVectorAttribute, sCdataAttribute, aBuffer.makeStringAndClear() );
     303             : 
     304           8 :             xHandler->ignorableWhitespace ( sWhiteSpace );
     305           8 :             xHandler->startElement( sAlgorithmElement , xNewAttrList);
     306           8 :             xHandler->ignorableWhitespace ( sWhiteSpace );
     307           8 :             xHandler->endElement( sAlgorithmElement );
     308             : 
     309             :             // ==== Key Derivation
     310           8 :             pNewAttrList = new ::comphelper::AttributeList;
     311           8 :             xNewAttrList = pNewAttrList;
     312             : 
     313           8 :             pNewAttrList->AddAttribute ( sKeyDerivationNameAttribute, sCdataAttribute, sPBKDF2_Name );
     314             : 
     315           8 :             if ( bStoreStartKeyGeneration )
     316             :             {
     317           8 :                 aBuffer.append( nDerivedKeySize );
     318           8 :                 pNewAttrList->AddAttribute ( sKeySizeAttribute, sCdataAttribute, aBuffer.makeStringAndClear() );
     319             :             }
     320             : 
     321           8 :             sal_Int32 nCount = 0;
     322           8 :             *pIterationCount >>= nCount;
     323           8 :             aBuffer.append (nCount);
     324           8 :             pNewAttrList->AddAttribute ( sIterationCountAttribute, sCdataAttribute, aBuffer.makeStringAndClear() );
     325             : 
     326           8 :             *pSalt >>= aSequence;
     327           8 :             ::sax::Converter::encodeBase64(aBuffer, aSequence);
     328           8 :             pNewAttrList->AddAttribute ( sSaltAttribute, sCdataAttribute, aBuffer.makeStringAndClear() );
     329             : 
     330           8 :             xHandler->ignorableWhitespace ( sWhiteSpace );
     331           8 :             xHandler->startElement( sKeyDerivationElement , xNewAttrList);
     332           8 :             xHandler->ignorableWhitespace ( sWhiteSpace );
     333           8 :             xHandler->endElement( sKeyDerivationElement );
     334             : 
     335             :             // we have to store start-key-generation element as the last one to workaround the parsing problem
     336             :             // in OOo3.1 and older versions
     337           8 :             if ( bStoreStartKeyGeneration )
     338             :             {
     339             :                 // ==== Start Key Generation
     340           8 :                 pNewAttrList = new ::comphelper::AttributeList;
     341           8 :                 xNewAttrList = pNewAttrList;
     342             : 
     343           8 :                 ::rtl::OUString sStartKeyAlg;
     344           8 :                 ::rtl::OUString sStartKeySize;
     345           8 :                 sal_Int32 nStartKeyAlgID = 0;
     346           8 :                 *pStartKeyAlg >>= nStartKeyAlgID;
     347           8 :                 if ( nStartKeyAlgID == xml::crypto::DigestID::SHA256 )
     348             :                 {
     349           8 :                     sStartKeyAlg = sSHA256_URL;
     350           8 :                     aBuffer.append( (sal_Int32)32 );
     351           8 :                     sStartKeySize = aBuffer.makeStringAndClear();
     352             :                 }
     353           0 :                 else if ( nStartKeyAlgID == xml::crypto::DigestID::SHA1 )
     354             :                 {
     355           0 :                     sStartKeyAlg = sSHA1_Name;
     356           0 :                     aBuffer.append( (sal_Int32)20 );
     357           0 :                     sStartKeySize = aBuffer.makeStringAndClear();
     358             :                 }
     359             :                 else
     360           0 :                     throw uno::RuntimeException( OSL_LOG_PREFIX "Unexpected start key algorithm is provided!", uno::Reference< uno::XInterface >() );
     361             : 
     362           8 :                 pNewAttrList->AddAttribute ( sStartKeyGenerationNameAttribute, sCdataAttribute, sStartKeyAlg );
     363           8 :                 pNewAttrList->AddAttribute ( sKeySizeAttribute, sCdataAttribute, sStartKeySize );
     364             : 
     365           8 :                 xHandler->ignorableWhitespace ( sWhiteSpace );
     366           8 :                 xHandler->startElement( sStartKeyGenerationElement , xNewAttrList);
     367           8 :                 xHandler->ignorableWhitespace ( sWhiteSpace );
     368           8 :                 xHandler->endElement( sStartKeyGenerationElement );
     369             :             }
     370             : 
     371           8 :             xHandler->ignorableWhitespace ( sWhiteSpace );
     372           8 :             xHandler->endElement( sEncryptionDataElement );
     373             :         }
     374          96 :         xHandler->ignorableWhitespace ( sWhiteSpace );
     375          96 :         xHandler->endElement( sFileEntryElement );
     376          96 :     }
     377          20 :     xHandler->ignorableWhitespace ( sWhiteSpace );
     378          20 :     xHandler->endElement( sManifestElement );
     379          20 :     xHandler->endDocument();
     380          20 : }
     381             : 
     382             : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Generated by: LCOV version 1.10