/* -*- 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 <tabprotection.hxx>
#include <svl/PasswordHelper.hxx>
#include <comphelper/docpasswordhelper.hxx>
#include <comphelper/hash.hxx>
#include <comphelper/sequence.hxx>
#include <osl/diagnose.h>
#include <document.hxx>

#include <vector>

#define DEBUG_TAB_PROTECTION 0

constexpr OUString URI_SHA1 = u"http://www.w3.org/2000/09/xmldsig#sha1"_ustr;
constexpr OUString URI_SHA256_ODF12 = u"http://www.w3.org/2000/09/xmldsig#sha256"_ustr;
constexpr OUStringLiteral URI_SHA256_W3C = u"http://www.w3.org/2001/04/xmlenc#sha256";
constexpr OUString URI_XLS_LEGACY = u"http://docs.oasis-open.org/office/ns/table/legacy-hash-excel"_ustr;

using namespace ::com::sun::star;
using ::com::sun::star::uno::Sequence;

bool ScPassHashHelper::needsPassHashRegen(const ScDocument& rDoc, ScPasswordHash eHash1, ScPasswordHash eHash2)
{
    if (rDoc.IsDocProtected())
    {
        const ScDocProtection* p = rDoc.GetDocProtection();
        if (!p->isPasswordEmpty() && !p->hasPasswordHash(eHash1, eHash2))
            return true;
    }

    SCTAB nTabCount = rDoc.GetTableCount();
    for (SCTAB i = 0; i < nTabCount; ++i)
    {
        const ScTableProtection* p = rDoc.GetTabProtection(i);
        if (!p || !p->isProtected())
            // Sheet not protected.  Skip it.
            continue;

        if (!p->isPasswordEmpty() && !p->hasPasswordHash(eHash1, eHash2))
            return true;
    }

    return false;
}

const OUString & ScPassHashHelper::getHashURI(ScPasswordHash eHash)
{
    switch (eHash)
    {
        case PASSHASH_SHA256:
            return URI_SHA256_ODF12;
        case PASSHASH_SHA1:
            return URI_SHA1;
        case PASSHASH_XL:
            return URI_XLS_LEGACY;
        case PASSHASH_UNSPECIFIED:
        default:
            ;
    }
    return EMPTY_OUSTRING;
}

ScPasswordHash ScPassHashHelper::getHashTypeFromURI(std::u16string_view rURI)
{
    if (rURI == URI_SHA256_ODF12 || rURI == URI_SHA256_W3C)
        return PASSHASH_SHA256;
    if ( rURI == URI_SHA1 )
        return PASSHASH_SHA1;
    else if ( rURI == URI_XLS_LEGACY )
        return PASSHASH_XL;
    return PASSHASH_UNSPECIFIED;
}

bool ScOoxPasswordHash::verifyPassword( std::u16string_view aPassText ) const
{
    if (!hasPassword())
        return false;

    const OUString aHash( comphelper::DocPasswordHelper::GetOoxHashAsBase64(
                aPassText, maSaltValue, mnSpinCount, comphelper::Hash::IterCount::APPEND, maAlgorithmName));
    if (aHash.isEmpty())
        // unsupported algorithm
        return false;

    return aHash == maHashValue;
}

ScPassHashProtectable::~ScPassHashProtectable()
{
}

class ScTableProtectionImpl
{
public:
    static Sequence<sal_Int8> hashPassword(std::u16string_view aPassText, ScPasswordHash eHash);
    static Sequence<sal_Int8> hashPassword(const Sequence<sal_Int8>& rPassHash, ScPasswordHash eHash);

    explicit ScTableProtectionImpl(SCSIZE nOptSize);
    explicit ScTableProtectionImpl(const ScTableProtectionImpl& r);

    bool isProtected() const { return mbProtected;}
    bool isProtectedWithPass() const;
    void setProtected(bool bProtected);

    bool isPasswordEmpty() const { return mbEmptyPass;}
    bool hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const;
    void setPassword(const OUString& aPassText);
    css::uno::Sequence<sal_Int8> getPasswordHash(
        ScPasswordHash eHash, ScPasswordHash eHash2) const;
    const ScOoxPasswordHash& getPasswordHash() const;
    void setPasswordHash(
        const css::uno::Sequence<sal_Int8>& aPassword,
        ScPasswordHash eHash, ScPasswordHash eHash2);
    void setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
            const OUString& rSaltValue, sal_uInt32 nSpinCount );
    bool verifyPassword(std::u16string_view aPassText) const;

    bool isOptionEnabled(SCSIZE nOptId) const;
    void setOption(SCSIZE nOptId, bool bEnabled);

    void setEnhancedProtection(std::vector< ScEnhancedProtection > && rProt );
    const std::vector< ScEnhancedProtection > & getEnhancedProtection() const { return maEnhancedProtection;}
    bool updateReference( UpdateRefMode, const ScDocument&, const ScRange& rWhere, SCCOL nDx, SCROW nDy, SCTAB nDz );
    bool isBlockEditable( const ScRange& rRange ) const;
    bool isSelectionEditable( const ScRangeList& rRangeList ) const;

private:
    OUString maPassText;
    css::uno::Sequence<sal_Int8>   maPassHash;
    std::vector<bool> maOptions;
    bool mbEmptyPass;
    bool mbProtected;
    ScPasswordHash meHash1;
    ScPasswordHash meHash2;
    ScOoxPasswordHash maPasswordHash;
    std::vector< ScEnhancedProtection > maEnhancedProtection;
};

Sequence<sal_Int8> ScTableProtectionImpl::hashPassword(std::u16string_view aPassText, ScPasswordHash eHash)
{
    Sequence<sal_Int8> aHash;
    switch (eHash)
    {
        case PASSHASH_XL:
            aHash = ::comphelper::DocPasswordHelper::GetXLHashAsSequence( aPassText );
        break;
        case PASSHASH_SHA1:
            SvPasswordHelper::GetHashPassword(aHash, aPassText);
        break;
        case PASSHASH_SHA1_UTF8:
            SvPasswordHelper::GetHashPasswordSHA1UTF8(aHash, aPassText);
        break;
        case PASSHASH_SHA256:
            SvPasswordHelper::GetHashPasswordSHA256(aHash, aPassText);
        break;
        default:
            ;
    }
    return aHash;
}

Sequence<sal_Int8> ScTableProtectionImpl::hashPassword(
    const Sequence<sal_Int8>& rPassHash, ScPasswordHash eHash)
{
    if (!rPassHash.hasElements() || eHash == PASSHASH_UNSPECIFIED)
        return rPassHash;

    // TODO: Right now, we only support double-hash by SHA1.
    if (eHash == PASSHASH_SHA1)
    {
        auto aChars = comphelper::sequenceToContainer<std::vector<char>>(rPassHash);

        Sequence<sal_Int8> aNewHash;
        SvPasswordHelper::GetHashPassword(aNewHash, aChars.data(), aChars.size());
        return aNewHash;
    }

    return rPassHash;
}

ScTableProtectionImpl::ScTableProtectionImpl(SCSIZE nOptSize) :
    maOptions(nOptSize),
    mbEmptyPass(true),
    mbProtected(false),
    meHash1(PASSHASH_SHA1),
    meHash2(PASSHASH_UNSPECIFIED)
{
}

ScTableProtectionImpl::ScTableProtectionImpl(const ScTableProtectionImpl& r) :
    maPassText(r.maPassText),
    maPassHash(r.maPassHash),
    maOptions(r.maOptions),
    mbEmptyPass(r.mbEmptyPass),
    mbProtected(r.mbProtected),
    meHash1(r.meHash1),
    meHash2(r.meHash2),
    maPasswordHash(r.maPasswordHash),
    maEnhancedProtection(r.maEnhancedProtection)
{
}

bool ScTableProtectionImpl::isProtectedWithPass() const
{
    if (!mbProtected)
        return false;

    return !maPassText.isEmpty() || maPassHash.hasElements() || maPasswordHash.hasPassword();
}

void ScTableProtectionImpl::setProtected(bool bProtected)
{
    mbProtected = bProtected;
    // We need to keep the old password even when the protection is off.  So,
    // don't erase the password data here.
}

void ScTableProtectionImpl::setPassword(const OUString& aPassText)
{
    // We can't hash it here because we don't know whether this document will
    // get saved to Excel or ODF, depending on which we will need to use a
    // different hashing algorithm.  One alternative is to hash it using all
    // hash algorithms that we support, and store them all.

    maPassText = aPassText;
    mbEmptyPass = aPassText.isEmpty();
    if (mbEmptyPass)
    {
        maPassHash = Sequence<sal_Int8>();
    }
    maPasswordHash.clear();
}

bool ScTableProtectionImpl::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
{
    if (mbEmptyPass)
        return true;

    if (!maPassText.isEmpty())
        return true;

    if (meHash1 == eHash)
    {
        if (meHash2 == PASSHASH_UNSPECIFIED)
            // single hash.
            return true;

        return meHash2 == eHash2;
    }

    return false;
}

Sequence<sal_Int8> ScTableProtectionImpl::getPasswordHash(
    ScPasswordHash eHash, ScPasswordHash eHash2) const
{
    Sequence<sal_Int8> aPassHash;

    if (mbEmptyPass)
        // Flagged as empty.
        return aPassHash;

    if (!maPassText.isEmpty())
    {
        // Cleartext password exists.  Hash it.
        aPassHash = hashPassword(maPassText, eHash);
        if (eHash2 != PASSHASH_UNSPECIFIED)
            // Double-hash it.
            aPassHash = hashPassword(aPassHash, eHash2);

        return aPassHash;
    }
    else
    {
        // No clear text password.  Check if we have a hash value of the right hash type.
        if (meHash1 == eHash)
        {
            aPassHash = maPassHash;

            if (meHash2 == eHash2)
                // Matching double-hash requested.
                return aPassHash;
            else if (meHash2 == PASSHASH_UNSPECIFIED)
                // primary hashing type match.  Double hash it by the requested
                // double-hash type.
                return hashPassword(aPassHash, eHash2);
        }
    }

    // failed.
    return Sequence<sal_Int8>();
}

const ScOoxPasswordHash& ScTableProtectionImpl::getPasswordHash() const
{
    return maPasswordHash;
}

void ScTableProtectionImpl::setPasswordHash(
    const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
{
    sal_Int32 nLen = aPassword.getLength();
    mbEmptyPass = nLen <= 0;
    meHash1 = eHash;
    meHash2 = eHash2;
    maPassHash = aPassword;

#if DEBUG_TAB_PROTECTION
    for (sal_Int8 n : aPassword)
        printf("%2.2X ", static_cast<sal_uInt8>(n));
    printf("\n");
#endif
}

void ScTableProtectionImpl::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
        const OUString& rSaltValue, sal_uInt32 nSpinCount )
{
    if (!rHashValue.isEmpty())
    {
        // Invalidate the other hashes.
        setPasswordHash( uno::Sequence<sal_Int8>(), PASSHASH_UNSPECIFIED, PASSHASH_UNSPECIFIED);

        // We don't know whether this is an empty password (or would
        // unnecessarily have to try to verify an empty password), assume it is
        // not. A later verifyPassword() with an empty password will determine.
        // If this was not set to false then a verifyPassword() with an empty
        // password would unlock even if this hash here wasn't for an empty
        // password. Ugly stuff.
        mbEmptyPass = false;
    }

    maPasswordHash.maAlgorithmName  = rAlgorithmName;
    maPasswordHash.maHashValue      = rHashValue;
    maPasswordHash.maSaltValue      = rSaltValue;
    maPasswordHash.mnSpinCount      = nSpinCount;
}

bool ScTableProtectionImpl::verifyPassword(std::u16string_view aPassText) const
{
#if DEBUG_TAB_PROTECTION
    fprintf(stdout, "ScTableProtectionImpl::verifyPassword: input = '%s'\n",
            OUStringToOString(aPassText, RTL_TEXTENCODING_UTF8).getStr());
#endif

    if (mbEmptyPass)
        return aPassText.empty();

    if (!maPassText.isEmpty())
        // Clear text password exists, and this one takes precedence.
        return aPassText == maPassText;

    // For PASSHASH_UNSPECIFIED also maPassHash is empty and any aPassText
    // would yield an empty hash as well and thus compare true. Don't.
    if (meHash1 != PASSHASH_UNSPECIFIED)
    {
        Sequence<sal_Int8> aHash = hashPassword(aPassText, meHash1);
        aHash = hashPassword(aHash, meHash2);

#if DEBUG_TAB_PROTECTION
        fprintf(stdout, "ScTableProtectionImpl::verifyPassword: hash = ");
        for (sal_Int32 i = 0; i < aHash.getLength(); ++i)
            printf("%2.2X ", static_cast<sal_uInt8>(aHash[i]));
        printf("\n");
#endif

        if (aHash == maPassHash)
        {
            return true;
        }
    }

    // tdf#115483 compat hack for ODF 1.2; for now UTF8-SHA1 passwords are only
    // verified, not generated
    if (meHash1 == PASSHASH_SHA1 && meHash2 == PASSHASH_UNSPECIFIED)
    {
        Sequence<sal_Int8> const aHash2 = hashPassword(aPassText, PASSHASH_SHA1_UTF8);
        return aHash2 == maPassHash;
    }

    // Not yet generated or tracked with meHash1 or meHash2, but can be read
    // from OOXML.
    return maPasswordHash.verifyPassword( aPassText);
}

bool ScTableProtectionImpl::isOptionEnabled(SCSIZE nOptId) const
{
    if ( maOptions.size() <= static_cast<size_t>(nOptId) )
    {
        OSL_FAIL("ScTableProtectionImpl::isOptionEnabled: wrong size");
        return false;
    }

    return maOptions[nOptId];
}

void ScTableProtectionImpl::setOption(SCSIZE nOptId, bool bEnabled)
{
    if ( maOptions.size() <= static_cast<size_t>(nOptId) )
    {
        OSL_FAIL("ScTableProtectionImpl::setOption: wrong size");
        return;
    }

    maOptions[nOptId] = bEnabled;
}

void ScTableProtectionImpl::setEnhancedProtection(std::vector< ScEnhancedProtection > && rProt )
{
    maEnhancedProtection = std::move(rProt);
}

bool ScTableProtectionImpl::updateReference( UpdateRefMode eMode, const ScDocument& rDoc,
        const ScRange& rWhere, SCCOL nDx, SCROW nDy, SCTAB nDz )
{
    bool bChanged = false;
    for (auto& rEnhancedProtection : maEnhancedProtection)
    {
        if (rEnhancedProtection.maRangeList.is())
            bChanged |= rEnhancedProtection.maRangeList->UpdateReference( eMode, rDoc, rWhere, nDx, nDy, nDz);
    }
    return bChanged;
}

bool ScTableProtectionImpl::isBlockEditable( const ScRange& rRange ) const
{
    /* TODO: ask for password (and remember) if a password was set for
     * a matching range and no matching range without password was encountered.
     * Would need another return type than boolean to reflect
     * "password required for a specific protection". */

    // No protection exception or overriding permission to edit if empty.
    if (maEnhancedProtection.empty())
        return false;

    // No security descriptor in an enhanced protection means the ranges of
    // that protection are editable. If there is any security descriptor
    // present we assume the permission to edit is not granted. Until we
    // actually can evaluate the descriptors...

    {
        auto lIsEditable = [rRange](const ScEnhancedProtection& rEnhancedProtection) {
            return !rEnhancedProtection.hasSecurityDescriptor()
                && rEnhancedProtection.maRangeList.is() && rEnhancedProtection.maRangeList->Contains( rRange)
                && !rEnhancedProtection.hasPassword(); // Range is editable if no password is assigned.
        };
        if (std::any_of(maEnhancedProtection.begin(), maEnhancedProtection.end(), std::move(lIsEditable)))
            return true;
    }

    // For a single address, a simple check with single ranges was sufficient.
    if (rRange.aStart == rRange.aEnd)
        return false;

    // Test also for cases where rRange is encompassed by a union of two or
    // more ranges of the list. The original ranges are not necessarily joined.
    for (const auto& rEnhancedProtection : maEnhancedProtection)
    {
        if (!rEnhancedProtection.hasSecurityDescriptor() && rEnhancedProtection.maRangeList.is())
        {
            ScRangeList aList( rEnhancedProtection.maRangeList->GetIntersectedRange( rRange));
            if (aList.size() == 1 && aList[0] == rRange)
            {
                // Range is editable if no password is assigned.
                if (!rEnhancedProtection.hasPassword())
                    return true;
            }
        }
    }

    // Ranges may even be distributed over different protection records, for
    // example if they are assigned different names, and can have different
    // passwords. Combine the ones that can be edited.
    /* TODO: once we handle passwords, remember a successful unlock at
     * ScEnhancedProtection so we can use that here. */
    ScRangeList aRangeList;
    for (const auto& rEnhancedProtection : maEnhancedProtection)
    {
        if (!rEnhancedProtection.hasSecurityDescriptor() && rEnhancedProtection.maRangeList.is())
        {
            // Ranges are editable if no password is assigned.
            if (!rEnhancedProtection.hasPassword())
            {
                const ScRangeList& rRanges = *rEnhancedProtection.maRangeList;
                size_t nRanges = rRanges.size();
                for (size_t i=0; i < nRanges; ++i)
                {
                    aRangeList.push_back( rRanges[i]);
                }
            }
        }
    }
    ScRangeList aResultList( aRangeList.GetIntersectedRange( rRange));
    return aResultList.size() == 1 && aResultList[0] == rRange;
}

bool ScTableProtectionImpl::isSelectionEditable( const ScRangeList& rRangeList ) const
{
    if (rRangeList.empty())
        return false;

    for (size_t i=0, nRanges = rRangeList.size(); i < nRanges; ++i)
    {
        if (!isBlockEditable( rRangeList[i]))
            return false;
    }
    return true;
}

ScDocProtection::ScDocProtection() :
    mpImpl(new ScTableProtectionImpl(static_cast<SCSIZE>(ScDocProtection::NONE)))
{
}

ScDocProtection::ScDocProtection(const ScDocProtection& r) :
    ScPassHashProtectable(),
    mpImpl(new ScTableProtectionImpl(*r.mpImpl))
{
}

ScDocProtection::~ScDocProtection()
{
}

bool ScDocProtection::isProtected() const
{
    return mpImpl->isProtected();
}

bool ScDocProtection::isProtectedWithPass() const
{
    return mpImpl->isProtectedWithPass();
}

void ScDocProtection::setProtected(bool bProtected)
{
    mpImpl->setProtected(bProtected);

    // Currently Calc doesn't support document protection options.  So, let's
    // assume that when the document is protected, its structure is protected.
    // We need to do this for Excel export.
    mpImpl->setOption(ScDocProtection::STRUCTURE, bProtected);
}

bool ScDocProtection::isPasswordEmpty() const
{
    return mpImpl->isPasswordEmpty();
}

bool ScDocProtection::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
{
    return mpImpl->hasPasswordHash(eHash, eHash2);
}

void ScDocProtection::setPassword(const OUString& aPassText)
{
    mpImpl->setPassword(aPassText);
}

uno::Sequence<sal_Int8> ScDocProtection::getPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
{
    return mpImpl->getPasswordHash(eHash, eHash2);
}

const ScOoxPasswordHash& ScDocProtection::getPasswordHash() const
{
    return mpImpl->getPasswordHash();
}

void ScDocProtection::setPasswordHash(
    const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
{
    mpImpl->setPasswordHash(aPassword, eHash, eHash2);
}

void ScDocProtection::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
        const OUString& rSaltValue, sal_uInt32 nSpinCount )
{
    mpImpl->setPasswordHash( rAlgorithmName, rHashValue, rSaltValue, nSpinCount);
}

bool ScDocProtection::verifyPassword(std::u16string_view aPassText) const
{
    return mpImpl->verifyPassword(aPassText);
}

bool ScDocProtection::isOptionEnabled(Option eOption) const
{
    return mpImpl->isOptionEnabled(eOption);
}

void ScDocProtection::setOption(Option eOption, bool bEnabled)
{
    mpImpl->setOption(eOption, bEnabled);
}

ScTableProtection::ScTableProtection() :
    mpImpl(new ScTableProtectionImpl(static_cast<SCSIZE>(ScTableProtection::NONE)))
{
    // Set default values for the options.
    mpImpl->setOption(SELECT_LOCKED_CELLS,   true);
    mpImpl->setOption(SELECT_UNLOCKED_CELLS, true);
}

ScTableProtection::ScTableProtection(const ScTableProtection& r) :
    ScPassHashProtectable(),
    mpImpl(new ScTableProtectionImpl(*r.mpImpl))
{
}

ScTableProtection::~ScTableProtection()
{
}

bool ScTableProtection::isProtected() const
{
    return mpImpl->isProtected();
}

bool ScTableProtection::isProtectedWithPass() const
{
    return mpImpl->isProtectedWithPass();
}

void ScTableProtection::setProtected(bool bProtected)
{
    mpImpl->setProtected(bProtected);
}

bool ScTableProtection::isPasswordEmpty() const
{
    return mpImpl->isPasswordEmpty();
}

bool ScTableProtection::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
{
    return mpImpl->hasPasswordHash(eHash, eHash2);
}

void ScTableProtection::setPassword(const OUString& aPassText)
{
    mpImpl->setPassword(aPassText);
}

Sequence<sal_Int8> ScTableProtection::getPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
{
    return mpImpl->getPasswordHash(eHash, eHash2);
}

const ScOoxPasswordHash& ScTableProtection::getPasswordHash() const
{
    return mpImpl->getPasswordHash();
}

void ScTableProtection::setPasswordHash(
    const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
{
    mpImpl->setPasswordHash(aPassword, eHash, eHash2);
}

void ScTableProtection::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
        const OUString& rSaltValue, sal_uInt32 nSpinCount )
{
    mpImpl->setPasswordHash( rAlgorithmName, rHashValue, rSaltValue, nSpinCount);
}

bool ScTableProtection::verifyPassword(std::u16string_view aPassText) const
{
    return mpImpl->verifyPassword(aPassText);
}

bool ScTableProtection::isOptionEnabled(Option eOption) const
{
    return mpImpl->isOptionEnabled(eOption);
}

void ScTableProtection::setOption(Option eOption, bool bEnabled)
{
    mpImpl->setOption(eOption, bEnabled);
}

void ScTableProtection::setEnhancedProtection(std::vector< ScEnhancedProtection > && rProt )
{
    mpImpl->setEnhancedProtection(std::move(rProt));
}

const std::vector< ScEnhancedProtection > & ScTableProtection::getEnhancedProtection() const
{
    return mpImpl->getEnhancedProtection();
}

bool ScTableProtection::updateReference( UpdateRefMode eMode, const ScDocument& rDoc, const ScRange& rWhere,
        SCCOL nDx, SCROW nDy, SCTAB nDz )
{
    return mpImpl->updateReference( eMode, rDoc, rWhere, nDx, nDy, nDz);
}

bool ScTableProtection::isBlockEditable( const ScRange& rRange ) const
{
    return mpImpl->isBlockEditable( rRange);
}

bool ScTableProtection::isSelectionEditable( const ScRangeList& rRangeList ) const
{
    return mpImpl->isSelectionEditable( rRangeList);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
