From 2d92a84a6aac37e34d1699fdebe0270468b4f746 Mon Sep 17 00:00:00 2001 From: Matúš Kukan Date: Mon, 20 Oct 2014 21:13:50 +0200 Subject: package: Move most ZipOutputEntry's methods back to ZipOutputStream We want to use ZipOutputEntry only for deflating (and maybe rename it). ca13a9377e4a36436e4c82bb33648d0f3b6db6f5 was not a good idea because the data still needs to be written sequentially anyway. Otherwise it's hard to get offset positions of individual entries right. Since this commit rawCloseEntry needs to be called always; also when we use write&closeEntry because we don't call writeEXT in closeEntry anymore. Need to rename and add comments later. Change-Id: I03bd48ca6e108e6253a77a137746165909ca3c3d --- package/source/zipapi/ZipOutputEntry.cxx | 175 ++----------------------- package/source/zipapi/ZipOutputStream.cxx | 156 +++++++++++++++++++++- package/source/zippackage/ZipPackage.cxx | 21 +-- package/source/zippackage/ZipPackageFolder.cxx | 6 +- package/source/zippackage/ZipPackageStream.cxx | 12 +- 5 files changed, 184 insertions(+), 186 deletions(-) (limited to 'package/source') diff --git a/package/source/zipapi/ZipOutputEntry.cxx b/package/source/zipapi/ZipOutputEntry.cxx index a3e3cc8bb72f..4e2f6d45a25b 100644 --- a/package/source/zipapi/ZipOutputEntry.cxx +++ b/package/source/zipapi/ZipOutputEntry.cxx @@ -24,9 +24,11 @@ #include +#include #include #include #include +#include #include using namespace com::sun::star; @@ -37,43 +39,24 @@ using namespace com::sun::star::packages::zip::ZipConstants; /** This class is used to deflate Zip entries */ ZipOutputEntry::ZipOutputEntry( const uno::Reference< uno::XComponentContext >& rxContext, - ByteChucker& rChucker, + ZipOutputStream* pOutputStream, ZipEntry& rEntry, ZipPackageStream* pStream, bool bEncrypt) : m_aDeflateBuffer(n_ConstBufferSize) , m_aDeflater(DEFAULT_COMPRESSION, true) -, m_rChucker(rChucker) +, m_pZipOutputStream(pOutputStream) , m_pCurrentEntry(&rEntry) , m_nDigested(0) -, m_bEncryptCurrentEntry(false) +, m_bEncryptCurrentEntry(bEncrypt) , m_pCurrentStream(NULL) { - if (rEntry.nTime == -1) - rEntry.nTime = getCurrentDosTime(); - if (rEntry.nMethod == -1) - rEntry.nMethod = DEFLATED; - rEntry.nVersion = 20; - rEntry.nFlag = 1 << 11; - if (rEntry.nSize == -1 || rEntry.nCompressedSize == -1 || - rEntry.nCrc == -1) + if (m_bEncryptCurrentEntry) { - rEntry.nSize = rEntry.nCompressedSize = 0; - rEntry.nFlag |= 8; - } - - if (bEncrypt) - { - m_bEncryptCurrentEntry = true; - m_xCipherContext = ZipFile::StaticGetCipher( rxContext, pStream->GetEncryptionData(), true ); m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum( rxContext, pStream->GetEncryptionData() ); - m_nDigested = 0; - rEntry.nFlag |= 1 << 4; m_pCurrentStream = pStream; } - sal_Int32 nLOCLength = writeLOC(rEntry); - rEntry.nOffset = m_rChucker.GetPosition() - nLOCLength; } ZipOutputEntry::~ZipOutputEntry( void ) @@ -117,7 +100,6 @@ void SAL_CALL ZipOutputEntry::closeEntry( ) pEntry->nCompressedSize = m_aDeflater.getTotalOut(); } pEntry->nCrc = m_aCRC.getValue(); - writeEXT(*pEntry); } m_aDeflater.reset(); m_aCRC.reset(); @@ -170,27 +152,12 @@ void SAL_CALL ZipOutputEntry::write( const Sequence< sal_Int8 >& rBuffer, sal_In case STORED: { Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength ); - m_rChucker.WriteBytes( aTmpBuffer ); + m_pZipOutputStream->getChucker().WriteBytes( aTmpBuffer ); } break; } } -void SAL_CALL ZipOutputEntry::rawWrite( Sequence< sal_Int8 >& rBuffer, sal_Int32 /*nNewOffset*/, sal_Int32 nNewLength ) - throw(IOException, RuntimeException) -{ - Sequence < sal_Int8 > aTmpBuffer ( rBuffer.getConstArray(), nNewLength ); - m_rChucker.WriteBytes( aTmpBuffer ); -} - -void SAL_CALL ZipOutputEntry::rawCloseEntry( ) - throw(IOException, RuntimeException) -{ - if ( m_pCurrentEntry->nMethod == DEFLATED && ( m_pCurrentEntry->nFlag & 8 ) ) - writeEXT(*m_pCurrentEntry); - m_pCurrentEntry = NULL; -} - void ZipOutputEntry::doDeflate() { sal_Int32 nLength = m_aDeflater.doDeflateSegment(m_aDeflateBuffer, 0, m_aDeflateBuffer.getLength()); @@ -213,7 +180,7 @@ void ZipOutputEntry::doDeflate() // FIXME64: uno::Sequence not 64bit safe. uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer ); - m_rChucker.WriteBytes( aEncryptionBuffer ); + m_pZipOutputStream->getChucker().WriteBytes( aEncryptionBuffer ); // the sizes as well as checksum for encrypted streams is calculated here m_pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); @@ -222,7 +189,7 @@ void ZipOutputEntry::doDeflate() } else { - m_rChucker.WriteBytes ( aTmpBuffer ); + m_pZipOutputStream->getChucker().WriteBytes ( aTmpBuffer ); } } @@ -232,7 +199,7 @@ void ZipOutputEntry::doDeflate() uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose(); if ( aEncryptionBuffer.getLength() ) { - m_rChucker.WriteBytes( aEncryptionBuffer ); + m_pZipOutputStream->getChucker().WriteBytes( aEncryptionBuffer ); // the sizes as well as checksum for encrypted streams is calculated hier m_pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); @@ -242,126 +209,4 @@ void ZipOutputEntry::doDeflate() } } -static sal_uInt32 getTruncated( sal_Int64 nNum, bool *pIsTruncated ) -{ - if( nNum >= 0xffffffff ) - { - *pIsTruncated = true; - return 0xffffffff; - } - else - return static_cast< sal_uInt32 >( nNum ); -} - -void ZipOutputEntry::writeEXT( const ZipEntry &rEntry ) - throw(IOException, RuntimeException) -{ - bool bWrite64Header = false; - - m_rChucker << EXTSIG; - m_rChucker << static_cast < sal_uInt32> ( rEntry.nCrc ); - m_rChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header ); - m_rChucker << getTruncated( rEntry.nSize, &bWrite64Header ); - - if( bWrite64Header ) - { - // FIXME64: need to append a ZIP64 header instead of throwing - // We're about to silently lose people's data - which they are - // unlikely to appreciate so fail instead: - throw IOException( "File contains streams that are too large." ); - } -} - -sal_Int32 ZipOutputEntry::writeLOC( const ZipEntry &rEntry ) - throw(IOException, RuntimeException) -{ - if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, true ) ) - throw IOException("Unexpected character is used in file name." ); - - OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); - sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); - - m_rChucker << LOCSIG; - m_rChucker << rEntry.nVersion; - - if (rEntry.nFlag & (1 << 4) ) - { - // If it's an encrypted entry, we pretend its stored plain text - sal_Int16 nTmpFlag = rEntry.nFlag; - nTmpFlag &= ~(1 <<4 ); - m_rChucker << nTmpFlag; - m_rChucker << static_cast < sal_Int16 > ( STORED ); - } - else - { - m_rChucker << rEntry.nFlag; - m_rChucker << rEntry.nMethod; - } - - bool bWrite64Header = false; - - m_rChucker << static_cast < sal_uInt32 > (rEntry.nTime); - if ((rEntry.nFlag & 8) == 8 ) - { - m_rChucker << static_cast < sal_Int32 > (0); - m_rChucker << static_cast < sal_Int32 > (0); - m_rChucker << static_cast < sal_Int32 > (0); - } - else - { - m_rChucker << static_cast < sal_uInt32 > (rEntry.nCrc); - m_rChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header ); - m_rChucker << getTruncated( rEntry.nSize, &bWrite64Header ); - } - m_rChucker << nNameLength; - m_rChucker << static_cast < sal_Int16 > (0); - - if( bWrite64Header ) - { - // FIXME64: need to append a ZIP64 header instead of throwing - // We're about to silently lose people's data - which they are - // unlikely to appreciate so fail instead: - throw IOException( "File contains streams that are too large." ); - } - - Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() ); - m_rChucker.WriteBytes( aSequence ); - - return LOCHDR + nNameLength; -} -sal_uInt32 ZipOutputEntry::getCurrentDosTime( ) -{ - oslDateTime aDateTime; - TimeValue aTimeValue; - osl_getSystemTime ( &aTimeValue ); - osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime); - - // at year 2108, there is an overflow - // -> some decision needs to be made - // how to handle the ZIP file format (just overflow?) - - // if the current system time is before 1980, - // then the time traveller will have to make a decision - // how to handle the ZIP file format before it is invented - // (just underflow?) - - assert(aDateTime.Year > 1980 && aDateTime.Year < 2108); - - sal_uInt32 nYear = static_cast (aDateTime.Year); - - if (nYear>=1980) - nYear-=1980; - else if (nYear>=80) - { - nYear-=80; - } - sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) + - ( 32 * (aDateTime.Month)) + - ( 512 * nYear ) ) << 16) | - ( ( aDateTime.Seconds/2) + - ( 32 * aDateTime.Minutes) + - ( 2048 * static_cast (aDateTime.Hours) ) ) ); - return nResult; -} - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipOutputStream.cxx b/package/source/zipapi/ZipOutputStream.cxx index 7cd5acdb5c7b..29c19c42667a 100644 --- a/package/source/zipapi/ZipOutputStream.cxx +++ b/package/source/zipapi/ZipOutputStream.cxx @@ -23,8 +23,11 @@ #include #include +#include + #include #include +#include using namespace com::sun::star; using namespace com::sun::star::io; @@ -37,6 +40,7 @@ ZipOutputStream::ZipOutputStream( const uno::Reference < io::XOutputStream > &xO : m_xStream(xOStream) , m_aChucker(xOStream) , m_bFinished(false) +, m_pCurrentEntry(NULL) { } @@ -46,9 +50,45 @@ ZipOutputStream::~ZipOutputStream( void ) delete m_aZipList[i]; } -void ZipOutputStream::addEntry( ZipEntry *pZipEntry ) +void ZipOutputStream::putNextEntry( ZipEntry& rEntry, bool bEncrypt ) + throw(IOException, RuntimeException) +{ + assert(!m_pCurrentEntry && "Forgot to close an entry before putNextEntry()?"); + if (rEntry.nTime == -1) + rEntry.nTime = getCurrentDosTime(); + if (rEntry.nMethod == -1) + rEntry.nMethod = DEFLATED; + rEntry.nVersion = 20; + rEntry.nFlag = 1 << 11; + if (rEntry.nSize == -1 || rEntry.nCompressedSize == -1 || + rEntry.nCrc == -1) + { + rEntry.nSize = rEntry.nCompressedSize = 0; + rEntry.nFlag |= 8; + } + if (bEncrypt) + { + rEntry.nFlag |= 1 << 4; + } + + sal_Int32 nLOCLength = writeLOC(rEntry); + rEntry.nOffset = m_aChucker.GetPosition() - nLOCLength; + m_aZipList.push_back( &rEntry ); + m_pCurrentEntry = &rEntry; +} + +void ZipOutputStream::rawWrite( Sequence< sal_Int8 >& rBuffer, sal_Int32 /*nNewOffset*/, sal_Int32 nNewLength ) + throw(IOException, RuntimeException) +{ + m_aChucker.WriteBytes( Sequence< sal_Int8 >(rBuffer.getConstArray(), nNewLength) ); +} + +void ZipOutputStream::rawCloseEntry() + throw(IOException, RuntimeException) { - m_aZipList.push_back( pZipEntry ); + if ( m_pCurrentEntry->nMethod == DEFLATED && ( m_pCurrentEntry->nFlag & 8 ) ) + writeEXT(*m_pCurrentEntry); + m_pCurrentEntry = NULL; } void ZipOutputStream::finish( ) @@ -148,4 +188,116 @@ void ZipOutputStream::writeCEN( const ZipEntry &rEntry ) m_aChucker.WriteBytes( aSequence ); } +void ZipOutputStream::writeEXT( const ZipEntry &rEntry ) + throw(IOException, RuntimeException) +{ + bool bWrite64Header = false; + + m_aChucker << EXTSIG; + m_aChucker << static_cast < sal_uInt32> ( rEntry.nCrc ); + m_aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header ); + m_aChucker << getTruncated( rEntry.nSize, &bWrite64Header ); + + if( bWrite64Header ) + { + // FIXME64: need to append a ZIP64 header instead of throwing + // We're about to silently lose people's data - which they are + // unlikely to appreciate so fail instead: + throw IOException( "File contains streams that are too large." ); + } +} + +sal_Int32 ZipOutputStream::writeLOC( const ZipEntry &rEntry ) + throw(IOException, RuntimeException) +{ + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, true ) ) + throw IOException("Unexpected character is used in file name." ); + + OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); + sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); + + m_aChucker << LOCSIG; + m_aChucker << rEntry.nVersion; + + if (rEntry.nFlag & (1 << 4) ) + { + // If it's an encrypted entry, we pretend its stored plain text + sal_Int16 nTmpFlag = rEntry.nFlag; + nTmpFlag &= ~(1 <<4 ); + m_aChucker << nTmpFlag; + m_aChucker << static_cast < sal_Int16 > ( STORED ); + } + else + { + m_aChucker << rEntry.nFlag; + m_aChucker << rEntry.nMethod; + } + + bool bWrite64Header = false; + + m_aChucker << static_cast < sal_uInt32 > (rEntry.nTime); + if ((rEntry.nFlag & 8) == 8 ) + { + m_aChucker << static_cast < sal_Int32 > (0); + m_aChucker << static_cast < sal_Int32 > (0); + m_aChucker << static_cast < sal_Int32 > (0); + } + else + { + m_aChucker << static_cast < sal_uInt32 > (rEntry.nCrc); + m_aChucker << getTruncated( rEntry.nCompressedSize, &bWrite64Header ); + m_aChucker << getTruncated( rEntry.nSize, &bWrite64Header ); + } + m_aChucker << nNameLength; + m_aChucker << static_cast < sal_Int16 > (0); + + if( bWrite64Header ) + { + // FIXME64: need to append a ZIP64 header instead of throwing + // We're about to silently lose people's data - which they are + // unlikely to appreciate so fail instead: + throw IOException( "File contains streams that are too large." ); + } + + Sequence < sal_Int8 > aSequence( (sal_Int8*)sUTF8Name.getStr(), sUTF8Name.getLength() ); + m_aChucker.WriteBytes( aSequence ); + + return LOCHDR + nNameLength; +} + +sal_uInt32 ZipOutputStream::getCurrentDosTime() +{ + oslDateTime aDateTime; + TimeValue aTimeValue; + osl_getSystemTime ( &aTimeValue ); + osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime); + + // at year 2108, there is an overflow + // -> some decision needs to be made + // how to handle the ZIP file format (just overflow?) + + // if the current system time is before 1980, + // then the time traveller will have to make a decision + // how to handle the ZIP file format before it is invented + // (just underflow?) + + assert(aDateTime.Year > 1980 && aDateTime.Year < 2108); + + sal_uInt32 nYear = static_cast (aDateTime.Year); + + if (nYear>=1980) + nYear-=1980; + else if (nYear>=80) + { + nYear-=80; + } + sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) + + ( 32 * (aDateTime.Month)) + + ( 512 * nYear ) ) << 16) | + ( ( aDateTime.Seconds/2) + + ( 32 * aDateTime.Minutes) + + ( 2048 * static_cast (aDateTime.Hours) ) ) ); + return nResult; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx index a631cabb14ba..a05320a48951 100644 --- a/package/source/zippackage/ZipPackage.cxx +++ b/package/source/zippackage/ZipPackage.cxx @@ -989,7 +989,7 @@ void ZipPackage::WriteMimetypeMagicFile( ZipOutputStream& aZipOut ) pEntry->sPath = sMime; pEntry->nMethod = STORED; pEntry->nSize = pEntry->nCompressedSize = nBufferLength; - pEntry->nTime = ZipOutputEntry::getCurrentDosTime(); + pEntry->nTime = ZipOutputStream::getCurrentDosTime(); CRC32 aCRC32; aCRC32.update( aType ); @@ -997,10 +997,11 @@ void ZipPackage::WriteMimetypeMagicFile( ZipOutputStream& aZipOut ) try { - ZipOutputEntry aZipEntry(m_xContext, aZipOut.getChucker(), *pEntry, NULL); + aZipOut.putNextEntry(*pEntry); + ZipOutputEntry aZipEntry(m_xContext, &aZipOut, *pEntry, NULL); aZipEntry.write(aType, 0, nBufferLength); aZipEntry.closeEntry(); - aZipOut.addEntry(pEntry); + aZipOut.rawCloseEntry(); } catch ( const ::com::sun::star::io::IOException & r ) { @@ -1023,7 +1024,7 @@ void ZipPackage::WriteManifest( ZipOutputStream& aZipOut, const vector< uno::Seq pEntry->nMethod = DEFLATED; pEntry->nCrc = -1; pEntry->nSize = pEntry->nCompressedSize = -1; - pEntry->nTime = ZipOutputEntry::getCurrentDosTime(); + pEntry->nTime = ZipOutputStream::getCurrentDosTime(); // Convert vector into a uno::Sequence uno::Sequence < uno::Sequence < PropertyValue > > aManifestSequence ( aManList.size() ); @@ -1040,10 +1041,11 @@ void ZipPackage::WriteManifest( ZipOutputStream& aZipOut, const vector< uno::Seq pBuffer->realloc( nBufferLength ); // the manifest.xml is never encrypted - so pass an empty reference - ZipOutputEntry aZipEntry(m_xContext, aZipOut.getChucker(), *pEntry, NULL); + aZipOut.putNextEntry(*pEntry); + ZipOutputEntry aZipEntry(m_xContext, &aZipOut, *pEntry, NULL); aZipEntry.write(pBuffer->getSequence(), 0, nBufferLength); aZipEntry.closeEntry(); - aZipOut.addEntry(pEntry); + aZipOut.rawCloseEntry(); } void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const vector< uno::Sequence < PropertyValue > >& aManList ) @@ -1056,7 +1058,7 @@ void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const vector< uno: pEntry->nMethod = DEFLATED; pEntry->nCrc = -1; pEntry->nSize = pEntry->nCompressedSize = -1; - pEntry->nTime = ZipOutputEntry::getCurrentDosTime(); + pEntry->nTime = ZipOutputStream::getCurrentDosTime(); // Convert vector into a uno::Sequence // TODO/LATER: use Defaulst entries in future @@ -1091,10 +1093,11 @@ void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const vector< uno: pBuffer->realloc( nBufferLength ); // there is no encryption in this format currently - ZipOutputEntry aZipEntry(m_xContext, aZipOut.getChucker(), *pEntry, NULL); + aZipOut.putNextEntry(*pEntry); + ZipOutputEntry aZipEntry(m_xContext, &aZipOut, *pEntry, NULL); aZipEntry.write(pBuffer->getSequence(), 0, nBufferLength); aZipEntry.closeEntry(); - aZipOut.addEntry(pEntry); + aZipOut.rawCloseEntry(); } void ZipPackage::ConnectTo( const uno::Reference< io::XInputStream >& xInStream ) diff --git a/package/source/zippackage/ZipPackageFolder.cxx b/package/source/zippackage/ZipPackageFolder.cxx index c2e5a4fa2f9a..a6b2e5c139a7 100644 --- a/package/source/zippackage/ZipPackageFolder.cxx +++ b/package/source/zippackage/ZipPackageFolder.cxx @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -338,9 +337,8 @@ void ZipPackageFolder::saveContents( try { - ZipOutputEntry aZipEntry(m_xContext, rZipOut.getChucker(), *pTempEntry, NULL, false); - aZipEntry.rawCloseEntry(); - rZipOut.addEntry(pTempEntry); + rZipOut.putNextEntry( *pTempEntry ); + rZipOut.rawCloseEntry(); } catch ( ZipException& ) { diff --git a/package/source/zippackage/ZipPackageStream.cxx b/package/source/zippackage/ZipPackageStream.cxx index a4651d78b298..7547e9cf0dc5 100644 --- a/package/source/zippackage/ZipPackageStream.cxx +++ b/package/source/zippackage/ZipPackageStream.cxx @@ -674,7 +674,7 @@ bool ZipPackageStream::saveChild( if ( bRawStream ) xStream->skipBytes( m_nMagicalHackPos ); - ZipOutputEntry aZipEntry(m_xContext, rZipOut.getChucker(), *pTempEntry, this, false); + rZipOut.putNextEntry(*pTempEntry); // the entry is provided to the ZipOutputStream that will delete it pAutoTempEntry.release(); @@ -684,12 +684,11 @@ bool ZipPackageStream::saveChild( do { nLength = xStream->readBytes( aSeq, n_ConstBufferSize ); - aZipEntry.rawWrite(aSeq, 0, nLength); + rZipOut.rawWrite(aSeq, 0, nLength); } while ( nLength == n_ConstBufferSize ); - aZipEntry.rawCloseEntry(); - rZipOut.addEntry(pTempEntry); + rZipOut.rawCloseEntry(); } catch ( ZipException& ) { @@ -732,7 +731,8 @@ bool ZipPackageStream::saveChild( try { - ZipOutputEntry aZipEntry(m_xContext, rZipOut.getChucker(), *pTempEntry, this, bToBeEncrypted); + rZipOut.putNextEntry(*pTempEntry, bToBeEncrypted); + ZipOutputEntry aZipEntry(m_xContext, &rZipOut, *pTempEntry, this, bToBeEncrypted); // the entry is provided to the ZipOutputStream that will delete it pAutoTempEntry.release(); @@ -746,7 +746,7 @@ bool ZipPackageStream::saveChild( while ( nLength == n_ConstBufferSize ); aZipEntry.closeEntry(); - rZipOut.addEntry(pTempEntry); + rZipOut.rawCloseEntry(); } catch ( ZipException& ) { -- cgit