diff options
Diffstat (limited to 'sc/source/core/data/tabprotection.cxx')
-rw-r--r-- | sc/source/core/data/tabprotection.cxx | 724 |
1 files changed, 724 insertions, 0 deletions
diff --git a/sc/source/core/data/tabprotection.cxx b/sc/source/core/data/tabprotection.cxx new file mode 100644 index 000000000..12bfff06b --- /dev/null +++ b/sc/source/core/data/tabprotection.cxx @@ -0,0 +1,724 @@ +/* -*- 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 OUStringLiteral URI_SHA1 = u"http://www.w3.org/2000/09/xmldsig#sha1"; +constexpr OUStringLiteral URI_SHA256_ODF12 = u"http://www.w3.org/2000/09/xmldsig#sha256"; +constexpr OUStringLiteral URI_SHA256_W3C = u"http://www.w3.org/2001/04/xmlenc#sha256"; +constexpr OUStringLiteral URI_XLS_LEGACY = u"http://docs.oasis-open.org/office/ns/table/legacy-hash-excel"; + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Sequence; +using ::std::vector; + +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; +} + +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 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( const OUString& 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(const OUString& 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<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(const OUString& aPassText) const +{ +#if DEBUG_TAB_PROTECTION + fprintf(stdout, "ScTableProtectionImpl::verifyPassword: input = '%s'\n", + OUStringToOString(aPassText, RTL_TEXTENCODING_UTF8).getStr()); +#endif + + if (mbEmptyPass) + return aPassText.isEmpty(); + + 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(), 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(const OUString& 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(const OUString& 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: */ |