diff options
Diffstat (limited to 'sc/source/filter/excel/xicontent.cxx')
-rw-r--r-- | sc/source/filter/excel/xicontent.cxx | 1442 |
1 files changed, 1442 insertions, 0 deletions
diff --git a/sc/source/filter/excel/xicontent.cxx b/sc/source/filter/excel/xicontent.cxx new file mode 100644 index 000000000..872632a1c --- /dev/null +++ b/sc/source/filter/excel/xicontent.cxx @@ -0,0 +1,1442 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/sheet/TableValidationVisibility.hpp> +#include <xicontent.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docfile.hxx> +#include <tools/urlobj.hxx> +#include <sfx2/linkmgr.hxx> +#include <svl/itemset.hxx> +#include <scitems.hxx> +#include <editeng/eeitem.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/editobj.hxx> +#include <unotools/charclass.hxx> +#include <unotools/configmgr.hxx> +#include <stringutil.hxx> +#include <cellform.hxx> +#include <cellvalue.hxx> +#include <document.hxx> +#include <editutil.hxx> +#include <validat.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <rangenam.hxx> +#include <arealink.hxx> +#include <stlsheet.hxx> +#include <xlcontent.hxx> +#include <xlformula.hxx> +#include <xltracer.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xistyle.hxx> +#include <xiescher.hxx> +#include <xiname.hxx> + +#include <excform.hxx> +#include <tabprotection.hxx> +#include <documentimport.hxx> + +#include <memory> +#include <utility> +#include <oox/helper/helper.hxx> +#include <sal/log.hxx> + +using ::com::sun::star::uno::Sequence; +using ::std::unique_ptr; + +// Shared string table ======================================================== + +XclImpSst::XclImpSst( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpSst::ReadSst( XclImpStream& rStrm ) +{ + rStrm.Ignore( 4 ); + sal_uInt32 nStrCount = rStrm.ReaduInt32(); + auto nBytesAvailable = rStrm.GetRecLeft(); + if (nStrCount > nBytesAvailable) + { + SAL_WARN("sc.filter", "xls claimed to have " << nStrCount << " strings, but only " << nBytesAvailable << " bytes available, truncating"); + nStrCount = nBytesAvailable; + } + maStrings.clear(); + maStrings.reserve(nStrCount); + while( (nStrCount > 0) && rStrm.IsValid() ) + { + XclImpString aString; + aString.Read( rStrm ); + maStrings.push_back( aString ); + --nStrCount; + } +} + +const XclImpString* XclImpSst::GetString( sal_uInt32 nSstIndex ) const +{ + return (nSstIndex < maStrings.size()) ? &maStrings[ nSstIndex ] : nullptr; +} + +// Hyperlinks ================================================================= + +namespace { + +/** Reads character array and stores it into rString. + @param nChars Number of following characters (not byte count!). + @param b16Bit true = 16-bit characters, false = 8-bit characters. */ +void lclAppendString32( OUString& rString, XclImpStream& rStrm, sal_uInt32 nChars, bool b16Bit ) +{ + sal_uInt16 nReadChars = ulimit_cast< sal_uInt16 >( nChars ); + rString += rStrm.ReadRawUniString( nReadChars, b16Bit ); + // ignore remaining chars + std::size_t nIgnore = nChars - nReadChars; + if( b16Bit ) + nIgnore *= 2; + rStrm.Ignore( nIgnore ); +} + +/** Reads 32-bit string length and the character array and stores it into rString. + @param b16Bit true = 16-bit characters, false = 8-bit characters. */ +void lclAppendString32( OUString& rString, XclImpStream& rStrm, bool b16Bit ) +{ + lclAppendString32( rString, rStrm, rStrm.ReaduInt32(), b16Bit ); +} + +/** Reads 32-bit string length and ignores following 16-bit character array. */ +void lclIgnoreString32( XclImpStream& rStrm ) +{ + sal_uInt32 nChars = rStrm.ReaduInt32(); + nChars *= 2; + rStrm.Ignore( nChars ); +} + +/** Converts a path to an absolute path. + @param rPath The source path. The resulting path is returned here. + @param nLevel Number of parent directories to add in front of the path. */ +void lclGetAbsPath( OUString& rPath, sal_uInt16 nLevel, const SfxObjectShell* pDocShell ) +{ + OUStringBuffer aTmpStr; + while( nLevel ) + { + aTmpStr.append( "../" ); + --nLevel; + } + aTmpStr.append( rPath ); + + if( pDocShell ) + { + bool bWasAbs = false; + rPath = pDocShell->GetMedium()->GetURLObject().smartRel2Abs( aTmpStr.makeStringAndClear(), bWasAbs ).GetMainURL( INetURLObject::DecodeMechanism::NONE ); + // full path as stored in SvxURLField must be encoded + } + else + rPath = aTmpStr.makeStringAndClear(); +} + +/** Inserts the URL into a text cell. Does not modify value or formula cells. */ +void lclInsertUrl( XclImpRoot& rRoot, const OUString& rUrl, SCCOL nScCol, SCROW nScRow, SCTAB nScTab ) +{ + ScDocumentImport& rDoc = rRoot.GetDocImport(); + ScAddress aScPos( nScCol, nScRow, nScTab ); + ScRefCellValue aCell(rDoc.getDoc(), aScPos); + switch( aCell.meType ) + { + // #i54261# hyperlinks in string cells + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + { + sal_uInt32 nNumFmt = rDoc.getDoc().GetNumberFormat(rDoc.getDoc().GetNonThreadedContext(), aScPos); + SvNumberFormatter* pFormatter = rDoc.getDoc().GetFormatTable(); + const Color* pColor; + OUString aDisplText = ScCellFormat::GetString(aCell, nNumFmt, &pColor, *pFormatter, rDoc.getDoc()); + if (aDisplText.isEmpty()) + aDisplText = rUrl; + + ScEditEngineDefaulter& rEE = rRoot.GetEditEngine(); + SvxURLField aUrlField( rUrl, aDisplText, SvxURLFormat::AppDefault ); + + if( aCell.meType == CELLTYPE_EDIT ) + { + const EditTextObject* pEditObj = aCell.mpEditText; + rEE.SetTextCurrentDefaults( *pEditObj ); + rEE.QuickInsertField( SvxFieldItem( aUrlField, EE_FEATURE_FIELD ), ESelection( 0, 0, EE_PARA_ALL, 0 ) ); + } + else + { + rEE.SetTextCurrentDefaults( OUString() ); + rEE.QuickInsertField( SvxFieldItem( aUrlField, EE_FEATURE_FIELD ), ESelection() ); + if( const ScPatternAttr* pPattern = rDoc.getDoc().GetPattern( aScPos.Col(), aScPos.Row(), nScTab ) ) + { + SfxItemSet aItemSet( rEE.GetEmptyItemSet() ); + pPattern->FillEditItemSet( &aItemSet ); + rEE.QuickSetAttribs( aItemSet, ESelection( 0, 0, EE_PARA_ALL, 0 ) ); + } + } + + // The cell will own the text object instance. + rDoc.setEditCell(aScPos, rEE.CreateTextObject()); + } + break; + + default: + // Handle other cell types e.g. formulas ( and ? ) that have associated + // hyperlinks. + // Ideally all hyperlinks should be treated as below. For the moment, + // given the current absence of ods support lets just handle what we + // previously didn't handle the new way. + // Unfortunately we won't be able to preserve such hyperlinks when + // saving to ods. Note: when we are able to save such hyperlinks to ods + // we should handle *all* imported hyperlinks as below ( e.g. as cell + // attribute ) for better interoperability. + { + SfxStringItem aItem( ATTR_HYPERLINK, rUrl ); + rDoc.getDoc().ApplyAttr(nScCol, nScRow, nScTab, aItem); + break; + } + } +} + +} // namespace + +void XclImpHyperlink::ReadHlink( XclImpStream& rStrm ) +{ + XclRange aXclRange( ScAddress::UNINITIALIZED ); + rStrm >> aXclRange; + // #i80006# Excel silently ignores invalid hi-byte of column index (TODO: everywhere?) + aXclRange.maFirst.mnCol &= 0xFF; + aXclRange.maLast.mnCol &= 0xFF; + OUString aString = ReadEmbeddedData( rStrm ); + if ( !aString.isEmpty() ) + rStrm.GetRoot().GetXFRangeBuffer().SetHyperlink( aXclRange, aString ); +} + +OUString XclImpHyperlink::ReadEmbeddedData( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + SfxObjectShell* pDocShell = rRoot.GetDocShell(); + + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + XclGuid aGuid; + rStrm >> aGuid; + rStrm.Ignore( 4 ); + sal_uInt32 nFlags = rStrm.ReaduInt32(); + + OSL_ENSURE( aGuid == XclTools::maGuidStdLink, "XclImpHyperlink::ReadEmbeddedData - unknown header GUID" ); + + ::std::unique_ptr< OUString > xLongName; // link / file name + ::std::unique_ptr< OUString > xShortName; // 8.3-representation of file name + ::std::unique_ptr< OUString > xTextMark; // text mark + + // description (ignore) + if( ::get_flag( nFlags, EXC_HLINK_DESCR ) ) + lclIgnoreString32( rStrm ); + // target frame (ignore) !! DESCR/FRAME - is this the right order? (never seen them together) + if( ::get_flag( nFlags, EXC_HLINK_FRAME ) ) + lclIgnoreString32( rStrm ); + + // URL fields are zero-terminated - do not let the stream replace them + // in the lclAppendString32() with the '?' character. + rStrm.SetNulSubstChar( '\0' ); + + // UNC path + if( ::get_flag( nFlags, EXC_HLINK_UNC ) ) + { + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, true ); + lclGetAbsPath( *xLongName, 0, pDocShell ); + } + // file link or URL + else if( ::get_flag( nFlags, EXC_HLINK_BODY ) ) + { + rStrm >> aGuid; + + if( aGuid == XclTools::maGuidFileMoniker ) + { + sal_uInt16 nLevel = rStrm.ReaduInt16(); // counter for level to climb down in path + xShortName.reset( new OUString ); + lclAppendString32( *xShortName, rStrm, false ); + rStrm.Ignore( 24 ); + + sal_uInt32 nStrLen = rStrm.ReaduInt32(); + if( nStrLen ) + { + nStrLen = rStrm.ReaduInt32(); + nStrLen /= 2; // it's byte count here... + rStrm.Ignore( 2 ); + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, nStrLen, true ); + lclGetAbsPath( *xLongName, nLevel, pDocShell ); + } + else + lclGetAbsPath( *xShortName, nLevel, pDocShell ); + } + else if( aGuid == XclTools::maGuidUrlMoniker ) + { + sal_uInt32 nStrLen = rStrm.ReaduInt32(); + nStrLen /= 2; // it's byte count here... + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, nStrLen, true ); + if( !::get_flag( nFlags, EXC_HLINK_ABS ) ) + lclGetAbsPath( *xLongName, 0, pDocShell ); + } + else + { + OSL_FAIL( "XclImpHyperlink::ReadEmbeddedData - unknown content GUID" ); + } + } + + // text mark + if( ::get_flag( nFlags, EXC_HLINK_MARK ) ) + { + xTextMark.reset( new OUString ); + lclAppendString32( *xTextMark, rStrm, true ); + } + + rStrm.SetNulSubstChar(); // back to default + + OSL_ENSURE( rStrm.GetRecLeft() == 0, "XclImpHyperlink::ReadEmbeddedData - record size mismatch" ); + + if (!xLongName && xShortName) + xLongName = std::move(xShortName); + else if (!xLongName && xTextMark) + xLongName.reset( new OUString ); + + if (xLongName) + { + if (xTextMark) + { + if( xLongName->isEmpty() ) + { + sal_Int32 nSepPos = xTextMark->lastIndexOf( '!' ); + if( nSepPos > 0 ) + { + // Do not attempt to blindly convert '#SheetName!A1' to + // '#SheetName.A1', it can be #SheetName!R1C1 as well. + // Hyperlink handler has to handle all, but prefer + // '#SheetName.A1' if possible. + if (nSepPos < xTextMark->getLength() - 1) + { + ScDocument& rDoc = rRoot.GetDoc(); + ScRange aRange; + if ((aRange.ParseAny( xTextMark->copy( nSepPos + 1 ), rDoc, formula::FormulaGrammar::CONV_XL_R1C1) + & ScRefFlags::VALID) == ScRefFlags::ZERO) + xTextMark.reset( new OUString( xTextMark->replaceAt( nSepPos, 1, rtl::OUStringChar( '.' )))); + } + } + } + xLongName.reset( new OUString( *xLongName + "#" + *xTextMark ) ); + } + return( *xLongName ); + } + return( OUString() ); +} + +void XclImpHyperlink::ConvertToValidTabName(OUString& rUrl) +{ + sal_Int32 n = rUrl.getLength(); + if (n < 4) + // Needs at least 4 characters. + return; + + if (rUrl[0] != '#') + // the 1st character must be '#'. + return; + + OUStringBuffer aNewUrl("#"); + OUStringBuffer aTabName; + + bool bInQuote = false; + bool bQuoteTabName = false; + for( sal_Int32 i = 1; i < n; ++i ) + { + sal_Unicode c = rUrl[i]; + if (c == '\'') + { + if (bInQuote && i+1 < n && rUrl[i+1] == '\'') + { + // Two consecutive single quotes ('') signify a single literal + // quite. When this occurs, the whole table name needs to be + // quoted. + bQuoteTabName = true; + aTabName.append(c).append(c); + ++i; + continue; + } + + bInQuote = !bInQuote; + if (!bInQuote && !aTabName.isEmpty()) + { + if (bQuoteTabName) + aNewUrl.append("'"); + aNewUrl.append(aTabName); + if (bQuoteTabName) + aNewUrl.append("'"); + } + } + else if (bInQuote) + aTabName.append(c); + else + aNewUrl.append(c); + } + + if (bInQuote) + // It should be outside the quotes! + return; + + // All is good. Pass the new URL. + rUrl = aNewUrl.makeStringAndClear(); +} + +void XclImpHyperlink::InsertUrl( XclImpRoot& rRoot, const XclRange& rXclRange, const OUString& rUrl ) +{ + OUString aUrl(rUrl); + ConvertToValidTabName(aUrl); + + SCTAB nScTab = rRoot.GetCurrScTab(); + ScRange aScRange( ScAddress::UNINITIALIZED ); + if( rRoot.GetAddressConverter().ConvertRange( aScRange, rXclRange, nScTab, nScTab, true ) ) + { + SCCOL nScCol1, nScCol2; + SCROW nScRow1, nScRow2; + aScRange.GetVars( nScCol1, nScRow1, nScTab, nScCol2, nScRow2, nScTab ); + + if (utl::ConfigManager::IsFuzzing()) + { + SCROW nRows = nScRow2 - nScRow1; + if (nRows > 1024) + { + SAL_WARN("sc.filter", "for fuzzing performance, clamped hyperlink apply range end row from " << nScRow2 << " to " << nScRow1 + 1024); + nScRow2 = nScRow1 + 1024; + } + } + + for( SCCOL nScCol = nScCol1; nScCol <= nScCol2; ++nScCol ) + for( SCROW nScRow = nScRow1; nScRow <= nScRow2; ++nScRow ) + lclInsertUrl( rRoot, aUrl, nScCol, nScRow, nScTab ); + } +} + +// Label ranges =============================================================== + +void XclImpLabelranges::ReadLabelranges( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + ScDocument& rDoc = rRoot.GetDoc(); + SCTAB nScTab = rRoot.GetCurrScTab(); + XclImpAddressConverter& rAddrConv = rRoot.GetAddressConverter(); + ScRangePairListRef xLabelRangesRef; + + XclRangeList aRowXclRanges, aColXclRanges; + rStrm >> aRowXclRanges >> aColXclRanges; + + // row label ranges + ScRangeList aRowScRanges; + rAddrConv.ConvertRangeList( aRowScRanges, aRowXclRanges, nScTab, false ); + xLabelRangesRef = rDoc.GetRowNameRangesRef(); + for ( size_t i = 0, nRanges = aRowScRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = aRowScRanges[ i ]; + ScRange aDataRange( rScRange ); + if( aDataRange.aEnd.Col() < rDoc.MaxCol() ) + { + aDataRange.aStart.SetCol( aDataRange.aEnd.Col() + 1 ); + aDataRange.aEnd.SetCol( rDoc.MaxCol() ); + } + else if( aDataRange.aStart.Col() > 0 ) + { + aDataRange.aEnd.SetCol( aDataRange.aStart.Col() - 1 ); + aDataRange.aStart.SetCol( 0 ); + } + xLabelRangesRef->Append( ScRangePair( rScRange, aDataRange ) ); + } + + // column label ranges + ScRangeList aColScRanges; + rAddrConv.ConvertRangeList( aColScRanges, aColXclRanges, nScTab, false ); + xLabelRangesRef = rDoc.GetColNameRangesRef(); + + for ( size_t i = 0, nRanges = aColScRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = aColScRanges[ i ]; + ScRange aDataRange( rScRange ); + if( aDataRange.aEnd.Row() < rDoc.MaxRow() ) + { + aDataRange.aStart.SetRow( aDataRange.aEnd.Row() + 1 ); + aDataRange.aEnd.SetRow( rDoc.MaxRow() ); + } + else if( aDataRange.aStart.Row() > 0 ) + { + aDataRange.aEnd.SetRow( aDataRange.aStart.Row() - 1 ); + aDataRange.aStart.SetRow( 0 ); + } + xLabelRangesRef->Append( ScRangePair( rScRange, aDataRange ) ); + } +} + +// Conditional formatting ===================================================== + +XclImpCondFormat::XclImpCondFormat( const XclImpRoot& rRoot, sal_uInt32 nFormatIndex ) : + XclImpRoot( rRoot ), + mnFormatIndex( nFormatIndex ), + mnCondCount( 0 ), + mnCondIndex( 0 ) +{ +} + +XclImpCondFormat::~XclImpCondFormat() +{ +} + +void XclImpCondFormat::ReadCondfmt( XclImpStream& rStrm ) +{ + OSL_ENSURE( !mnCondCount, "XclImpCondFormat::ReadCondfmt - already initialized" ); + XclRangeList aXclRanges; + mnCondCount = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + rStrm >> aXclRanges; + GetAddressConverter().ConvertRangeList( maRanges, aXclRanges, GetCurrScTab(), true ); +} + +void XclImpCondFormat::ReadCF( XclImpStream& rStrm ) +{ + if( mnCondIndex >= mnCondCount ) + { + OSL_FAIL( "XclImpCondFormat::ReadCF - CF without leading CONDFMT" ); + return; + } + + // entire conditional format outside of valid range? + if( maRanges.empty() ) + return; + + sal_uInt8 nType = rStrm.ReaduInt8(); + sal_uInt8 nOperator = rStrm.ReaduInt8(); + sal_uInt16 nFmlaSize1 = rStrm.ReaduInt16(); + sal_uInt16 nFmlaSize2 = rStrm.ReaduInt16(); + sal_uInt32 nFlags = rStrm.ReaduInt32(); + rStrm.Ignore( 2 ); //nFlagsExtended + + // *** mode and comparison operator *** + + ScConditionMode eMode = ScConditionMode::NONE; + switch( nType ) + { + case EXC_CF_TYPE_CELL: + { + switch( nOperator ) + { + case EXC_CF_CMP_BETWEEN: eMode = ScConditionMode::Between; break; + case EXC_CF_CMP_NOT_BETWEEN: eMode = ScConditionMode::NotBetween; break; + case EXC_CF_CMP_EQUAL: eMode = ScConditionMode::Equal; break; + case EXC_CF_CMP_NOT_EQUAL: eMode = ScConditionMode::NotEqual; break; + case EXC_CF_CMP_GREATER: eMode = ScConditionMode::Greater; break; + case EXC_CF_CMP_LESS: eMode = ScConditionMode::Less; break; + case EXC_CF_CMP_GREATER_EQUAL: eMode = ScConditionMode::EqGreater; break; + case EXC_CF_CMP_LESS_EQUAL: eMode = ScConditionMode::EqLess; break; + default: + SAL_INFO( + "sc.filter", "unknown CF comparison " << nOperator); + } + } + break; + + case EXC_CF_TYPE_FMLA: + eMode = ScConditionMode::Direct; + break; + + default: + SAL_INFO("sc.filter", "unknown CF mode " << nType); + return; + } + + // *** create style sheet *** + + OUString aStyleName( XclTools::GetCondFormatStyleName( GetCurrScTab(), mnFormatIndex, mnCondIndex ) ); + SfxItemSet& rStyleItemSet = ScfTools::MakeCellStyleSheet( GetStyleSheetPool(), aStyleName, true ).GetItemSet(); + + const XclImpPalette& rPalette = GetPalette(); + + // number format + + if( get_flag( nFlags, EXC_CF_BLOCK_NUMFMT ) ) + { + XclImpNumFmtBuffer& rNumFmtBuffer = GetRoot().GetNumFmtBuffer(); + bool bIFmt = get_flag( nFlags, EXC_CF_IFMT_USER ); + sal_uInt16 nFormat = rNumFmtBuffer.ReadCFFormat( rStrm, bIFmt ); + rNumFmtBuffer.FillToItemSet( rStyleItemSet, nFormat ); + } + + // *** font block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_FONT ) ) + { + XclImpFont aFont( GetRoot() ); + aFont.ReadCFFontBlock( rStrm ); + aFont.FillToItemSet( rStyleItemSet, XclFontItemType::Cell ); + } + + // alignment + if( get_flag( nFlags, EXC_CF_BLOCK_ALIGNMENT ) ) + { + XclImpCellAlign aAlign; + sal_uInt16 nAlign(0); + sal_uInt16 nAlignMisc(0); + nAlign = rStrm.ReaduInt16(); + nAlignMisc = rStrm.ReaduInt16(); + aAlign.FillFromCF( nAlign, nAlignMisc ); + aAlign.FillToItemSet( rStyleItemSet, nullptr ); + rStrm.Ignore(4); + } + + // *** border block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_BORDER ) ) + { + sal_uInt16 nLineStyle(0); + sal_uInt32 nLineColor(0); + nLineStyle = rStrm.ReaduInt16(); + nLineColor = rStrm.ReaduInt32(); + rStrm.Ignore( 2 ); + + XclImpCellBorder aBorder; + aBorder.FillFromCF8( nLineStyle, nLineColor, nFlags ); + aBorder.FillToItemSet( rStyleItemSet, rPalette ); + } + + // *** pattern block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_AREA ) ) + { + sal_uInt16 nPattern(0), nColor(0); + nPattern = rStrm.ReaduInt16(); + nColor = rStrm.ReaduInt16(); + + XclImpCellArea aArea; + aArea.FillFromCF8( nPattern, nColor, nFlags ); + aArea.FillToItemSet( rStyleItemSet, rPalette ); + } + + if( get_flag( nFlags, EXC_CF_BLOCK_PROTECTION ) ) + { + sal_uInt16 nCellProt; + nCellProt = rStrm.ReaduInt16(); + XclImpCellProt aCellProt; + aCellProt.FillFromXF3(nCellProt); + aCellProt.FillToItemSet( rStyleItemSet ); + } + + // *** formulas *** + + const ScAddress& rPos = maRanges.front().aStart; // assured above that maRanges is not empty + ExcelToSc& rFmlaConv = GetOldFmlaConverter(); + + ::std::unique_ptr< ScTokenArray > xTokArr1; + if( nFmlaSize1 > 0 ) + { + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset( rPos ); + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize1, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + { + xTokArr1 = std::move( pTokArr ); + GetDoc().CheckLinkFormulaNeedingCheck( *xTokArr1); + } + } + + ::std::unique_ptr< ScTokenArray > xTokArr2; + if( nFmlaSize2 > 0 ) + { + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset( rPos ); + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize2, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + { + xTokArr2 = std::move( pTokArr ); + GetDoc().CheckLinkFormulaNeedingCheck( *xTokArr2); + } + } + + // *** create the Calc conditional formatting *** + + const ScAddress aPos(rPos); //in case maRanges.Join invalidates it + + if( !mxScCondFmt ) + { + mxScCondFmt.reset( new ScConditionalFormat( 0/*nKey*/, &GetDoc() ) ); + if(maRanges.size() > 1) + maRanges.Join(maRanges[0], true); + mxScCondFmt->SetRange(maRanges); + } + + ScCondFormatEntry* pEntry = new ScCondFormatEntry(eMode, xTokArr1.get(), xTokArr2.get(), GetDoc(), aPos, aStyleName); + mxScCondFmt->AddEntry( pEntry ); + ++mnCondIndex; +} + +void XclImpCondFormat::Apply() +{ + if( mxScCondFmt ) + { + ScDocument& rDoc = GetDoc(); + + SCTAB nTab = maRanges.front().aStart.Tab(); + sal_uLong nKey = rDoc.AddCondFormat( mxScCondFmt->Clone(), nTab ); + + rDoc.AddCondFormatData( maRanges, nTab, nKey ); + } +} + +XclImpCondFormatManager::XclImpCondFormatManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpCondFormatManager::ReadCondfmt( XclImpStream& rStrm ) +{ + XclImpCondFormat* pFmt = new XclImpCondFormat( GetRoot(), maCondFmtList.size() ); + pFmt->ReadCondfmt( rStrm ); + maCondFmtList.push_back( std::unique_ptr<XclImpCondFormat>(pFmt) ); +} + +void XclImpCondFormatManager::ReadCF( XclImpStream& rStrm ) +{ + OSL_ENSURE( !maCondFmtList.empty(), "XclImpCondFormatManager::ReadCF - CF without leading CONDFMT" ); + if( !maCondFmtList.empty() ) + maCondFmtList.back()->ReadCF( rStrm ); +} + +void XclImpCondFormatManager::Apply() +{ + for( auto& rxFmt : maCondFmtList ) + rxFmt->Apply(); + maCondFmtList.clear(); +} + +// Data Validation ============================================================ + +XclImpValidationManager::DVItem::DVItem( const ScRangeList& rRanges, const ScValidationData& rValidData ) : + maRanges(rRanges), maValidData(rValidData) {} + +XclImpValidationManager::XclImpValidationManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpValidationManager::ReadDval( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + sal_uInt32 nObjId(0); + rStrm.Ignore( 10 ); + nObjId = rStrm.ReaduInt32(); + if( nObjId != EXC_DVAL_NOOBJ ) + { + OSL_ENSURE( nObjId <= 0xFFFF, "XclImpValidation::ReadDval - invalid object ID" ); + rRoot.GetCurrSheetDrawing().SetSkipObj( static_cast< sal_uInt16 >( nObjId ) ); + } +} + +void XclImpValidationManager::ReadDV( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + ScDocument& rDoc = rRoot.GetDoc(); + SCTAB nScTab = rRoot.GetCurrScTab(); + ExcelToSc& rFmlaConv = rRoot.GetOldFmlaConverter(); + + // flags + sal_uInt32 nFlags = rStrm.ReaduInt32(); + + // message strings + /* Empty strings are single NUL characters in Excel (string length is 1). + -> Do not let the stream replace them with '?' characters. */ + rStrm.SetNulSubstChar( '\0' ); + OUString aPromptTitle( rStrm.ReadUniString() ); + OUString aErrorTitle( rStrm.ReadUniString() ); + OUString aPromptMessage( rStrm.ReadUniString() ); + OUString aErrorMessage( rStrm.ReadUniString() ); + rStrm.SetNulSubstChar(); // back to default + + // formula(s) + if ( rStrm.GetRecLeft() <= 8 ) + // Not enough bytes left in the record. Bail out. + return; + + // first formula + // string list is single tStr token with NUL separators -> replace them with LF + rStrm.SetNulSubstChar( '\n' ); + ::std::unique_ptr< ScTokenArray > xTokArr1; + + // We can't import the formula directly because we need the range + sal_uInt16 nLenFormula1 = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + XclImpStreamPos aPosFormula1; + rStrm.StorePosition(aPosFormula1); + rStrm.Ignore(nLenFormula1); + + // second formula + ::std::unique_ptr< ScTokenArray > xTokArr2; + + sal_uInt16 nLenFormula2 = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + XclImpStreamPos aPosFormula2; + rStrm.StorePosition(aPosFormula2); + rStrm.Ignore(nLenFormula2); + + // read all cell ranges + XclRangeList aXclRanges; + rStrm >> aXclRanges; + + // convert to Calc range list + ScRangeList aScRanges; + rRoot.GetAddressConverter().ConvertRangeList( aScRanges, aXclRanges, nScTab, true ); + + // only continue if there are valid ranges + if ( aScRanges.empty() ) + return; + + ScRange aCombinedRange = aScRanges.Combine(); + + XclImpStreamPos aCurrentPos; + rStrm.StorePosition(aCurrentPos); + rStrm.RestorePosition(aPosFormula1); + if( nLenFormula1 > 0 ) + { + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset(aCombinedRange.aStart); + rFmlaConv.Convert( pTokArr, rStrm, nLenFormula1, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + xTokArr1 = std::move( pTokArr ); + } + rStrm.SetNulSubstChar(); // back to default + if (nLenFormula2 > 0) + { + rStrm.RestorePosition(aPosFormula2); + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset(aCombinedRange.aStart); + rFmlaConv.Convert( pTokArr, rStrm, nLenFormula2, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + xTokArr2 = std::move( pTokArr ); + } + + rStrm.RestorePosition(aCurrentPos); + + bool bIsValid = true; // valid settings in flags field + + ScValidationMode eValMode = SC_VALID_ANY; + switch( nFlags & EXC_DV_MODE_MASK ) + { + case EXC_DV_MODE_ANY: eValMode = SC_VALID_ANY; break; + case EXC_DV_MODE_WHOLE: eValMode = SC_VALID_WHOLE; break; + case EXC_DV_MODE_DECIMAL: eValMode = SC_VALID_DECIMAL; break; + case EXC_DV_MODE_LIST: eValMode = SC_VALID_LIST; break; + case EXC_DV_MODE_DATE: eValMode = SC_VALID_DATE; break; + case EXC_DV_MODE_TIME: eValMode = SC_VALID_TIME; break; + case EXC_DV_MODE_TEXTLEN: eValMode = SC_VALID_TEXTLEN; break; + case EXC_DV_MODE_CUSTOM: eValMode = SC_VALID_CUSTOM; break; + default: bIsValid = false; + } + rRoot.GetTracer().TraceDVType(eValMode == SC_VALID_CUSTOM); + + ScConditionMode eCondMode = ScConditionMode::Between; + switch( nFlags & EXC_DV_COND_MASK ) + { + case EXC_DV_COND_BETWEEN: eCondMode = ScConditionMode::Between; break; + case EXC_DV_COND_NOTBETWEEN:eCondMode = ScConditionMode::NotBetween; break; + case EXC_DV_COND_EQUAL: eCondMode = ScConditionMode::Equal; break; + case EXC_DV_COND_NOTEQUAL: eCondMode = ScConditionMode::NotEqual; break; + case EXC_DV_COND_GREATER: eCondMode = ScConditionMode::Greater; break; + case EXC_DV_COND_LESS: eCondMode = ScConditionMode::Less; break; + case EXC_DV_COND_EQGREATER: eCondMode = ScConditionMode::EqGreater; break; + case EXC_DV_COND_EQLESS: eCondMode = ScConditionMode::EqLess; break; + default: bIsValid = false; + } + + if ( !bIsValid ) + // No valid validation found. Bail out. + return; + + // The default value for comparison is _BETWEEN. However, custom + // rules are a formula, and thus the comparator should be ignored + // and only a true or false from the formula is evaluated. In Calc, + // formulas use comparison SC_COND_DIRECT. + if( eValMode == SC_VALID_CUSTOM ) + { + eCondMode = ScConditionMode::Direct; + } + + // first range for base address for relative references + const ScRange& rScRange = aScRanges.front(); // aScRanges is not empty + + // process string list of a list validity (convert to list of string tokens) + if( xTokArr1 && (eValMode == SC_VALID_LIST) && ::get_flag( nFlags, EXC_DV_STRINGLIST ) ) + XclTokenArrayHelper::ConvertStringToList(*xTokArr1, rDoc.GetSharedStringPool(), '\n'); + + maDVItems.push_back( + std::make_unique<DVItem>(aScRanges, ScValidationData(eValMode, eCondMode, xTokArr1.get(), xTokArr2.get(), rDoc, rScRange.aStart))); + DVItem& rItem = *maDVItems.back(); + + rItem.maValidData.SetIgnoreBlank( ::get_flag( nFlags, EXC_DV_IGNOREBLANK ) ); + rItem.maValidData.SetListType( ::get_flagvalue( nFlags, EXC_DV_SUPPRESSDROPDOWN, css::sheet::TableValidationVisibility::INVISIBLE, css::sheet::TableValidationVisibility::UNSORTED ) ); + + // *** prompt box *** + if( !aPromptTitle.isEmpty() || !aPromptMessage.isEmpty() ) + { + // set any text stored in the record + rItem.maValidData.SetInput( aPromptTitle, aPromptMessage ); + if( !::get_flag( nFlags, EXC_DV_SHOWPROMPT ) ) + rItem.maValidData.ResetInput(); + } + + // *** error box *** + ScValidErrorStyle eErrStyle = SC_VALERR_STOP; + switch( nFlags & EXC_DV_ERROR_MASK ) + { + case EXC_DV_ERROR_WARNING: eErrStyle = SC_VALERR_WARNING; break; + case EXC_DV_ERROR_INFO: eErrStyle = SC_VALERR_INFO; break; + } + // set texts and error style + rItem.maValidData.SetError( aErrorTitle, aErrorMessage, eErrStyle ); + if( !::get_flag( nFlags, EXC_DV_SHOWERROR ) ) + rItem.maValidData.ResetError(); +} + +void XclImpValidationManager::Apply() +{ + ScDocument& rDoc = GetRoot().GetDoc(); + for (const auto& rxDVItem : maDVItems) + { + DVItem& rItem = *rxDVItem; + // set the handle ID + sal_uLong nHandle = rDoc.AddValidationEntry( rItem.maValidData ); + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALIDDATA, nHandle ) ); + + // apply all ranges + for ( size_t i = 0, nRanges = rItem.maRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = rItem.maRanges[ i ]; + rDoc.ApplyPatternAreaTab( rScRange.aStart.Col(), rScRange.aStart.Row(), + rScRange.aEnd.Col(), rScRange.aEnd.Row(), rScRange.aStart.Tab(), aPattern ); + } + } + maDVItems.clear(); +} + +// Web queries ================================================================ + +XclImpWebQuery::XclImpWebQuery( const ScRange& rDestRange ) : + maDestRange( rDestRange ), + meMode( xlWQUnknown ), + mnRefresh( 0 ) +{ +} + +void XclImpWebQuery::ReadParamqry( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags = rStrm.ReaduInt16(); + sal_uInt16 nType = ::extract_value< sal_uInt16 >( nFlags, 0, 3 ); + if( !((nType == EXC_PQRYTYPE_WEBQUERY) && ::get_flag( nFlags, EXC_PQRY_WEBQUERY )) ) + return; + + if( ::get_flag( nFlags, EXC_PQRY_TABLES ) ) + { + meMode = xlWQAllTables; + maTables = ScfTools::GetHTMLTablesName(); + } + else + { + meMode = xlWQDocument; + maTables = ScfTools::GetHTMLDocName(); + } +} + +void XclImpWebQuery::ReadWqstring( XclImpStream& rStrm ) +{ + maURL = rStrm.ReadUniString(); +} + +void XclImpWebQuery::ReadWqsettings( XclImpStream& rStrm ) +{ + rStrm.Ignore( 10 ); + sal_uInt16 nFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + mnRefresh = rStrm.ReaduInt16(); + + if( ::get_flag( nFlags, EXC_WQSETT_SPECTABLES ) && (meMode == xlWQAllTables) ) + meMode = xlWQSpecTables; +} + +void XclImpWebQuery::ReadWqtables( XclImpStream& rStrm ) +{ + if( meMode != xlWQSpecTables ) + return; + + rStrm.Ignore( 4 ); + OUString aTables( rStrm.ReadUniString() ); + + const sal_Unicode cSep = ';'; + static const OUStringLiteral aQuotedPairs( u"\"\"" ); + maTables.clear(); + for ( sal_Int32 nStringIx {aTables.isEmpty() ? -1 : 0}; nStringIx>=0; ) + { + OUString aToken( ScStringUtil::GetQuotedToken( aTables, 0, aQuotedPairs, ',', nStringIx ) ); + sal_Int32 nTabNum = CharClass::isAsciiNumeric( aToken ) ? aToken.toInt32() : 0; + if( nTabNum > 0 ) + maTables = ScGlobal::addToken( maTables, ScfTools::GetNameFromHTMLIndex( static_cast< sal_uInt32 >( nTabNum ) ), cSep ); + else + { + ScGlobal::EraseQuotes( aToken, '"', false ); + if( !aToken.isEmpty() ) + maTables = ScGlobal::addToken( maTables, ScfTools::GetNameFromHTMLName( aToken ), cSep ); + } + } +} + +void XclImpWebQuery::Apply( ScDocument& rDoc, const OUString& rFilterName ) +{ + if( !maURL.isEmpty() && (meMode != xlWQUnknown) && rDoc.GetDocumentShell() ) + { + ScAreaLink* pLink = new ScAreaLink( rDoc.GetDocumentShell(), + maURL, rFilterName, OUString(), maTables, maDestRange, mnRefresh * 60UL ); + rDoc.GetLinkManager()->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, + maURL, &rFilterName, &maTables ); + } +} + +XclImpWebQueryBuffer::XclImpWebQueryBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpWebQueryBuffer::ReadQsi( XclImpStream& rStrm ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + rStrm.Ignore( 10 ); + OUString aXclName( rStrm.ReadUniString() ); + + // #i64794# Excel replaces spaces with underscores + aXclName = aXclName.replaceAll( " ", "_" ); + + // find the defined name used in Calc + if( const XclImpName* pName = GetNameManager().FindName( aXclName, GetCurrScTab() ) ) + { + if( const ScRangeData* pRangeData = pName->GetScRangeData() ) + { + ScRange aRange; + if( pRangeData->IsReference( aRange ) ) + maWQList.emplace_back( aRange ); + } + } + } + else + { + DBG_ERROR_BIFF(); + } +} + +void XclImpWebQueryBuffer::ReadParamqry( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadParamqry( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqstring( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqstring( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqsettings( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqsettings( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqtables( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqtables( rStrm ); +} + +void XclImpWebQueryBuffer::Apply() +{ + ScDocument& rDoc = GetDoc(); + for( auto& rQuery : maWQList ) + rQuery.Apply( rDoc, EXC_WEBQRY_FILTER ); +} + +// Decryption ================================================================= + +namespace { + +XclImpDecrypterRef lclReadFilepass5( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + OSL_ENSURE( rStrm.GetRecLeft() == 4, "lclReadFilepass5 - wrong record size" ); + if( rStrm.GetRecLeft() == 4 ) + { + sal_uInt16 nKey(0), nHash(0); + nKey = rStrm.ReaduInt16(); + nHash = rStrm.ReaduInt16(); + xDecr = std::make_shared<XclImpBiff5Decrypter>( nKey, nHash ); + } + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8_Standard( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + OSL_ENSURE( rStrm.GetRecLeft() == 48, "lclReadFilepass8 - wrong record size" ); + if( rStrm.GetRecLeft() == 48 ) + { + std::vector<sal_uInt8> aSalt(16); + std::vector<sal_uInt8> aVerifier(16); + std::vector<sal_uInt8> aVerifierHash(16); + rStrm.Read(aSalt.data(), 16); + rStrm.Read(aVerifier.data(), 16); + rStrm.Read(aVerifierHash.data(), 16); + xDecr = std::make_shared<XclImpBiff8StdDecrypter>(std::move(aSalt), std::move(aVerifier), std::move(aVerifierHash)); + } + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8_Strong(XclImpStream& rStream) +{ + //It is possible there are other variants in existence but these + //are the defaults I get with Excel 2013 + XclImpDecrypterRef xDecr; + + msfilter::RC4EncryptionInfo info; + + info.header.flags = rStream.ReaduInt32(); + if (oox::getFlag( info.header.flags, msfilter::ENCRYPTINFO_EXTERNAL)) + return xDecr; + + sal_uInt32 nHeaderSize = rStream.ReaduInt32(); + sal_uInt32 actualHeaderSize = sizeof(info.header); + + if( nHeaderSize < actualHeaderSize ) + return xDecr; + + info.header.flags = rStream.ReaduInt32(); + info.header.sizeExtra = rStream.ReaduInt32(); + info.header.algId = rStream.ReaduInt32(); + info.header.algIdHash = rStream.ReaduInt32(); + info.header.keyBits = rStream.ReaduInt32(); + info.header.providedType = rStream.ReaduInt32(); + info.header.reserved1 = rStream.ReaduInt32(); + info.header.reserved2 = rStream.ReaduInt32(); + + rStream.Ignore(nHeaderSize - actualHeaderSize); + + info.verifier.saltSize = rStream.ReaduInt32(); + if (info.verifier.saltSize != msfilter::SALT_LENGTH) + return xDecr; + rStream.Read(&info.verifier.salt, sizeof(info.verifier.salt)); + rStream.Read(&info.verifier.encryptedVerifier, sizeof(info.verifier.encryptedVerifier)); + + info.verifier.encryptedVerifierHashSize = rStream.ReaduInt32(); + if (info.verifier.encryptedVerifierHashSize != RTL_DIGEST_LENGTH_SHA1) + return xDecr; + rStream.Read(&info.verifier.encryptedVerifierHash, info.verifier.encryptedVerifierHashSize); + + // check flags and algorithm IDs, required are AES128 and SHA-1 + if (!oox::getFlag(info.header.flags, msfilter::ENCRYPTINFO_CRYPTOAPI)) + return xDecr; + + if (oox::getFlag(info.header.flags, msfilter::ENCRYPTINFO_AES)) + return xDecr; + + if (info.header.algId != msfilter::ENCRYPT_ALGO_RC4) + return xDecr; + + // hash algorithm ID 0 defaults to SHA-1 too + if (info.header.algIdHash != 0 && info.header.algIdHash != msfilter::ENCRYPT_HASH_SHA1) + return xDecr; + + xDecr = std::make_shared<XclImpBiff8CryptoAPIDecrypter>( + std::vector<sal_uInt8>(info.verifier.salt, + info.verifier.salt + SAL_N_ELEMENTS(info.verifier.salt)), + std::vector<sal_uInt8>(info.verifier.encryptedVerifier, + info.verifier.encryptedVerifier + SAL_N_ELEMENTS(info.verifier.encryptedVerifier)), + std::vector<sal_uInt8>(info.verifier.encryptedVerifierHash, + info.verifier.encryptedVerifierHash + SAL_N_ELEMENTS(info.verifier.encryptedVerifierHash))); + + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + + sal_uInt16 nMode = rStrm.ReaduInt16(); + switch( nMode ) + { + case EXC_FILEPASS_BIFF5: + xDecr = lclReadFilepass5( rStrm ); + break; + + case EXC_FILEPASS_BIFF8: + { + sal_uInt32 nVersion = rStrm.ReaduInt32(); + if (nVersion == msfilter::VERSION_INFO_1997_FORMAT) + { + //A Version structure where Version.vMajor MUST be 0x0001, + //and Version.vMinor MUST be 0x0001. + xDecr = lclReadFilepass8_Standard(rStrm); + } + else if (nVersion == msfilter::VERSION_INFO_2007_FORMAT || + nVersion == msfilter::VERSION_INFO_2007_FORMAT_SP2) + { + //Version.vMajor MUST be 0x0002, 0x0003 or 0x0004 and + //Version.vMinor MUST be 0x0002. + xDecr = lclReadFilepass8_Strong(rStrm); + } + else + OSL_FAIL("lclReadFilepass8 - unknown BIFF8 encryption sub mode"); + } + break; + + default: + OSL_FAIL( "lclReadFilepass8 - unknown encryption mode" ); + } + + return xDecr; +} + +} // namespace + +const ErrCode& XclImpDecryptHelper::ReadFilepass( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + rStrm.DisableDecryption(); + + // read the FILEPASS record and create a new decrypter object + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: xDecr = lclReadFilepass5( rStrm ); break; + case EXC_BIFF8: xDecr = lclReadFilepass8( rStrm ); break; + default: DBG_ERROR_BIFF(); + }; + + // set decrypter at import stream + rStrm.SetDecrypter( xDecr ); + + // request and verify a password (decrypter implements IDocPasswordVerifier) + if( xDecr ) + rStrm.GetRoot().RequestEncryptionData( *xDecr ); + + // return error code (success, wrong password, etc.) + return xDecr ? xDecr->GetError() : EXC_ENCR_ERROR_UNSUPP_CRYPT; +} + +// Document protection ======================================================== + +XclImpDocProtectBuffer::XclImpDocProtectBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mnPassHash(0x0000), + mbDocProtect(false), + mbWinProtect(false) +{ +} + +void XclImpDocProtectBuffer::ReadDocProtect( XclImpStream& rStrm ) +{ + mbDocProtect = rStrm.ReaduInt16() != 0; +} + +void XclImpDocProtectBuffer::ReadWinProtect( XclImpStream& rStrm ) +{ + mbWinProtect = rStrm.ReaduInt16() != 0; +} + +void XclImpDocProtectBuffer::ReadPasswordHash( XclImpStream& rStrm ) +{ + rStrm.EnableDecryption(); + mnPassHash = rStrm.ReaduInt16(); +} + +void XclImpDocProtectBuffer::Apply() const +{ + if (!mbDocProtect && !mbWinProtect) + // Excel requires either the structure or windows protection is set. + // If neither is set then the document is not protected at all. + return; + + unique_ptr<ScDocProtection> pProtect(new ScDocProtection); + pProtect->setProtected(true); + + if (mnPassHash) + { + // 16-bit password hash. + Sequence<sal_Int8> aPass{sal_Int8(mnPassHash >> 8), sal_Int8(mnPassHash & 0xFF)}; + pProtect->setPasswordHash(aPass, PASSHASH_XL); + } + + // document protection options + pProtect->setOption(ScDocProtection::STRUCTURE, mbDocProtect); + pProtect->setOption(ScDocProtection::WINDOWS, mbWinProtect); + + GetDoc().SetDocProtection(pProtect.get()); +} + +// Sheet Protection =========================================================== + +XclImpSheetProtectBuffer::Sheet::Sheet() : + mbProtected(false), + mnPasswordHash(0x0000), + mnOptions(0x4400) +{ +} + +XclImpSheetProtectBuffer::Sheet::Sheet(const Sheet& r) : + mbProtected(r.mbProtected), + mnPasswordHash(r.mnPasswordHash), + mnOptions(r.mnOptions) +{ +} + +XclImpSheetProtectBuffer::XclImpSheetProtectBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpSheetProtectBuffer::ReadProtect( XclImpStream& rStrm, SCTAB nTab ) +{ + if ( rStrm.ReaduInt16() ) + { + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mbProtected = true; + } +} + +void XclImpSheetProtectBuffer::ReadOptions( XclImpStream& rStrm, SCTAB nTab ) +{ + // The flag size specifies the size of bytes that follows that stores + // feature data. If -1 it depends on the feature type imported earlier. + // For enhanced protection data, the size is always 4. For the most xls + // documents out there this value is almost always -1. + sal_Int32 nFlagSize = rStrm.ReadInt32(); + if (nFlagSize != -1) + return; + + // There are actually 4 bytes to read, but the upper 2 bytes currently + // don't store any bits. + sal_uInt16 nOptions = rStrm.ReaduInt16(); + + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mnOptions = nOptions; +} + +void XclImpSheetProtectBuffer::AppendEnhancedProtection( const ScEnhancedProtection & rProt, SCTAB nTab ) +{ + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->maEnhancedProtections.push_back( rProt); +} + +void XclImpSheetProtectBuffer::ReadPasswordHash( XclImpStream& rStrm, SCTAB nTab ) +{ + sal_uInt16 nHash = rStrm.ReaduInt16(); + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mnPasswordHash = nHash; +} + +void XclImpSheetProtectBuffer::Apply() const +{ + for (const auto& [rTab, rSheet] : maProtectedSheets) + { + if (!rSheet.mbProtected) + // This sheet is (for whatever reason) not protected. + continue; + + unique_ptr<ScTableProtection> pProtect(new ScTableProtection); + pProtect->setProtected(true); + + // 16-bit hash password + const sal_uInt16 nHash = rSheet.mnPasswordHash; + if (nHash) + { + Sequence<sal_Int8> aPass{sal_Int8(nHash >> 8), sal_Int8(nHash & 0xFF)}; + pProtect->setPasswordHash(aPass, PASSHASH_XL); + } + + // sheet protection options + const sal_uInt16 nOptions = rSheet.mnOptions; + pProtect->setOption( ScTableProtection::OBJECTS, (nOptions & 0x0001) ); + pProtect->setOption( ScTableProtection::SCENARIOS, (nOptions & 0x0002) ); + pProtect->setOption( ScTableProtection::FORMAT_CELLS, (nOptions & 0x0004) ); + pProtect->setOption( ScTableProtection::FORMAT_COLUMNS, (nOptions & 0x0008) ); + pProtect->setOption( ScTableProtection::FORMAT_ROWS, (nOptions & 0x0010) ); + pProtect->setOption( ScTableProtection::INSERT_COLUMNS, (nOptions & 0x0020) ); + pProtect->setOption( ScTableProtection::INSERT_ROWS, (nOptions & 0x0040) ); + pProtect->setOption( ScTableProtection::INSERT_HYPERLINKS, (nOptions & 0x0080) ); + pProtect->setOption( ScTableProtection::DELETE_COLUMNS, (nOptions & 0x0100) ); + pProtect->setOption( ScTableProtection::DELETE_ROWS, (nOptions & 0x0200) ); + pProtect->setOption( ScTableProtection::SELECT_LOCKED_CELLS, (nOptions & 0x0400) ); + pProtect->setOption( ScTableProtection::SORT, (nOptions & 0x0800) ); + pProtect->setOption( ScTableProtection::AUTOFILTER, (nOptions & 0x1000) ); + pProtect->setOption( ScTableProtection::PIVOT_TABLES, (nOptions & 0x2000) ); + pProtect->setOption( ScTableProtection::SELECT_UNLOCKED_CELLS, (nOptions & 0x4000) ); + + // Enhanced protection containing editable ranges and permissions. + pProtect->setEnhancedProtection( std::vector(rSheet.maEnhancedProtections) ); + + // all done. now commit. + GetDoc().SetTabProtection(rTab, pProtect.get()); + } +} + +XclImpSheetProtectBuffer::Sheet* XclImpSheetProtectBuffer::GetSheetItem( SCTAB nTab ) +{ + ProtectedSheetMap::iterator itr = maProtectedSheets.find(nTab); + if (itr == maProtectedSheets.end()) + { + // new sheet + if ( !maProtectedSheets.emplace( nTab, Sheet() ).second ) + return nullptr; + + itr = maProtectedSheets.find(nTab); + } + + return &itr->second; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |