/* -*- 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 <utility>
#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 <comphelper/configuration.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 <docsh.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 <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.getType() )
    {
        // #i54261# hyperlinks in string cells
        case CELLTYPE_STRING:
        case CELLTYPE_EDIT:
        {
            ScInterpreterContext& rContext = rDoc.getDoc().GetNonThreadedContext();
            sal_uInt32 nNumFmt = rDoc.getDoc().GetNumberFormat(rContext, aScPos);
            const Color* pColor;
            OUString aDisplText = ScCellFormat::GetString(aCell, nNumFmt, &pColor, &rContext, rDoc.getDoc());
            if (aDisplText.isEmpty())
                aDisplText = rUrl;
 
            ScEditEngineDefaulter& rEE = rRoot.GetEditEngine();
            SvxURLField aUrlField( rUrl, aDisplText, SvxURLFormat::AppDefault );
 
            if( aCell.getType() == CELLTYPE_EDIT )
            {
                const EditTextObject* pEditObj = aCell.getEditText();
                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 let's 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(OUStringChar(c) + OUStringChar(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 (comphelper::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_uInt32 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( ScRangeList aRanges, const ScValidationData& rValidData ) :
    maRanges(std::move(aRanges)), 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()
{
    const bool bFuzzing = comphelper::IsFuzzing();
    size_t nPatterns = 0;
 
    ScDocument& rDoc = GetRoot().GetDoc();
    for (const auto& rxDVItem : maDVItems)
    {
        DVItem& rItem = *rxDVItem;
        // set the handle ID
        sal_uInt32 nHandle = rDoc.AddValidationEntry( rItem.maValidData );
        ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
        aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALIDDATA, nHandle ) );
 
        // apply all ranges
        for ( size_t i = 0, nRanges = rItem.maRanges.size(); i < nRanges; ++i, ++nPatterns )
        {
            const ScRange & rScRange = rItem.maRanges[ i ];
            rDoc.ApplyPatternAreaTab( rScRange.aStart.Col(), rScRange.aStart.Row(),
                rScRange.aEnd.Col(), rScRange.aEnd.Row(), rScRange.aStart.Tab(), aPattern );
            if (bFuzzing && nPatterns >= 128)
            {
                SAL_WARN("sc.filter", "for fuzzing performance, abandoned pattern after " << nPatterns << " insertions");
                break;
            }
        }
    }
    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 constexpr 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: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V1048 The 'eCondMode' variable was assigned the same value.