/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 ::cpo::uno::Sequence;

// 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(std::move(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::All());
            }
            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::All());
                }
            }

            // 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 )
        {
            // If it's an external formula then skip the entry
            if (const formula::FormulaToken* pToken = pTokArr->FirstToken())
            {
                if (pToken->GetOpCode() == ocExternal || pToken->GetOpCode() == ocUDExternal)
                    return;
            }
            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 )
        {
            // If it's an external formula then skip the entry
            if (const formula::FormulaToken* pToken = pTokArr->FirstToken())
            {
                if (pToken->GetOpCode() == ocExternal || pToken->GetOpCode() == ocUDExternal)
                    return;
            }
            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.ItemSetPut(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 = ';';
    maTables.clear();
    for ( sal_Int32 nStringIx {aTables.isEmpty() ? -1 : 0}; nStringIx>=0; )
    {
        OUString aToken( ScStringUtil::GetQuotedToken( aTables, 0, u"\"\""_ustr, ',', 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;

    std::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;

        std::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: */
