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

Generated by: LCOV version 1.11