diff options
author | Tamás Zolnai <tamas.zolnai@collabora.com> | 2019-03-23 15:53:27 +0100 |
---|---|---|
committer | Tamás Zolnai <tamas.zolnai@collabora.com> | 2019-06-20 14:23:20 +0200 |
commit | f5946cd499f7d7de40b03f453f5b1c52e31a29fc (patch) | |
tree | 3fc6c6383fd6c6ce1751ba55223fde333e707dc6 /svl | |
parent | d645f8fd9ca055f583dfdad15aad23ddd9dfca1b (diff) |
Introduce new lockfile handler for MSO like lockfiles
* Implement writing of MSO lockfiles
* Grab the already implemented parsing code (tryMSOwnerFile method)
and put it together into one class
* Add tests about the generated URL for lockfiles and the lockfile content
* MSO lockfiles are not written yet by LO, next step is to integrate
this code into the locking mechanism.
Reviewed-on: https://gerrit.libreoffice.org/69582
Tested-by: Jenkins
Reviewed-by: Tamás Zolnai <tamas.zolnai@collabora.com>
(cherry picked from commit 5db1e20b8b0942dac2d50f3cd34532bb61147020)
Change-Id: I3b0ed1975cd57dfd006d4e1890b23c307890de5c
Reviewed-on: https://gerrit.libreoffice.org/69842
Reviewed-by: Andras Timar <andras.timar@collabora.com>
Tested-by: Andras Timar <andras.timar@collabora.com>
Diffstat (limited to 'svl')
-rw-r--r-- | svl/CppunitTest_svl_lockfiles.mk | 52 | ||||
-rw-r--r-- | svl/Library_svl.mk | 3 | ||||
-rw-r--r-- | svl/Module_svl.mk | 1 | ||||
-rw-r--r-- | svl/qa/unit/lockfiles/test_lockfiles.cxx | 732 | ||||
-rw-r--r-- | svl/source/misc/documentlockfile.cxx | 140 | ||||
-rw-r--r-- | svl/source/misc/lockfilecommon.cxx | 35 | ||||
-rw-r--r-- | svl/source/misc/msodocumentlockfile.cxx | 244 | ||||
-rw-r--r-- | svl/source/misc/sharecontrolfile.cxx | 6 |
8 files changed, 1138 insertions, 75 deletions
diff --git a/svl/CppunitTest_svl_lockfiles.mk b/svl/CppunitTest_svl_lockfiles.mk new file mode 100644 index 000000000000..c93e78b45b8d --- /dev/null +++ b/svl/CppunitTest_svl_lockfiles.mk @@ -0,0 +1,52 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,svl_lockfiles)) + +$(eval $(call gb_CppunitTest_use_sdk_api,svl_lockfiles)) + +$(eval $(call gb_CppunitTest_use_api,svl_lockfiles,\ + udkapi \ + offapi \ + oovbaapi \ +)) + +$(eval $(call gb_CppunitTest_use_ure,svl_lockfiles)) + +$(eval $(call gb_CppunitTest_use_vcl,svl_lockfiles)) + + +$(eval $(call gb_CppunitTest_add_exception_objects,svl_lockfiles, \ + svl/qa/unit/lockfiles/test_lockfiles \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,svl_lockfiles, \ + comphelper \ + cppu \ + cppuhelper \ + tl \ + sal \ + svl \ + svt \ + sw \ + test \ + unotest \ + utl \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_use_rdb,svl_lockfiles,services)) + +$(eval $(call gb_CppunitTest_use_custom_headers,svl_lockfiles,\ + officecfg/registry \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,svl_lockfiles)) + +# vim: set noet sw=4 ts=4: diff --git a/svl/Library_svl.mk b/svl/Library_svl.mk index 2103a1483a45..4dad7be2a929 100644 --- a/svl/Library_svl.mk +++ b/svl/Library_svl.mk @@ -163,7 +163,8 @@ $(eval $(call gb_Library_add_exception_objects,svl,\ svl/source/misc/PasswordHelper \ svl/source/misc/adrparse \ $(if $(filter DESKTOP,$(BUILD_TYPE)),\ - svl/source/misc/documentlockfile) \ + svl/source/misc/documentlockfile \ + svl/source/misc/msodocumentlockfile) \ svl/source/misc/filenotation \ svl/source/misc/fstathelper \ svl/source/misc/getstringresource \ diff --git a/svl/Module_svl.mk b/svl/Module_svl.mk index d14e184b65a9..2569edb05b64 100644 --- a/svl/Module_svl.mk +++ b/svl/Module_svl.mk @@ -34,6 +34,7 @@ $(eval $(call gb_Module_add_check_targets,svl,\ CppunitTest_svl_itempool \ CppunitTest_svl_items \ CppunitTest_svl_lngmisc \ + CppunitTest_svl_lockfiles \ CppunitTest_svl_notify \ CppunitTest_svl_qa_cppunit \ CppunitTest_svl_urihelper \ diff --git a/svl/qa/unit/lockfiles/test_lockfiles.cxx b/svl/qa/unit/lockfiles/test_lockfiles.cxx new file mode 100644 index 000000000000..8083c143f461 --- /dev/null +++ b/svl/qa/unit/lockfiles/test_lockfiles.cxx @@ -0,0 +1,732 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <test/bootstrapfixture.hxx> + +#include <unotest/directories.hxx> +#include <svl/lockfilecommon.hxx> +#include <svl/documentlockfile.hxx> +#include <svl/msodocumentlockfile.hxx> +#include <unotools/useroptions.hxx> +#include <comphelper/sequence.hxx> +#include <tools/stream.hxx> +#include <rtl/strbuf.hxx> +#include <osl/security.hxx> +#include <osl/socket.hxx> +#include <unotools/bootstrap.hxx> + +namespace +{ +class LockfileTest : public test::BootstrapFixture +{ +public: + void testLOLockFileURL(); + void testLOLockFileContent(); + void testLOLockFileRT(); + void testLOLockFileUnicodeUsername(); + void testLOLockFileOverwrite(); + void testWordLockFileURL(); + void testExcelLockFileURL(); + void testPowerPointLockFileURL(); + void testWordLockFileContent(); + void testExcelLockFileContent(); + void testPowerPointLockFileContent(); + void testWordLockFileRT(); + void testExcelLockFileRT(); + void testPowerPointLockFileRT(); + void testMSOLockFileLongUserName(); + void testMSOLockFileUnicodeUsername(); + void testMSOLockFileOverwrite(); + +private: + CPPUNIT_TEST_SUITE(LockfileTest); + CPPUNIT_TEST(testLOLockFileURL); + CPPUNIT_TEST(testLOLockFileContent); + CPPUNIT_TEST(testLOLockFileRT); + CPPUNIT_TEST(testLOLockFileUnicodeUsername); + CPPUNIT_TEST(testLOLockFileOverwrite); + CPPUNIT_TEST(testWordLockFileURL); + CPPUNIT_TEST(testExcelLockFileURL); + CPPUNIT_TEST(testPowerPointLockFileURL); + CPPUNIT_TEST(testWordLockFileContent); + CPPUNIT_TEST(testExcelLockFileContent); + CPPUNIT_TEST(testPowerPointLockFileContent); + CPPUNIT_TEST(testWordLockFileRT); + CPPUNIT_TEST(testExcelLockFileRT); + CPPUNIT_TEST(testPowerPointLockFileRT); + CPPUNIT_TEST(testMSOLockFileLongUserName); + CPPUNIT_TEST(testMSOLockFileUnicodeUsername); + CPPUNIT_TEST(testMSOLockFileOverwrite); + CPPUNIT_TEST_SUITE_END(); +}; + +OUString readLockFile(const OUString& aSource) +{ + SvFileStream aFileStream(aSource, StreamMode::READ); + std::size_t nSize = aFileStream.remainingSize(); + std::unique_ptr<sal_Int8[]> pBuffer(new sal_Int8[nSize]); + aFileStream.ReadBytes(pBuffer.get(), nSize); + + css::uno::Sequence<sal_Int8> aData(pBuffer.get(), nSize); + OStringBuffer aResult; + for (sal_Int8 nByte : aData) + { + aResult.append(static_cast<sal_Char>(nByte)); + } + return OStringToOUString(aResult.makeStringAndClear(), RTL_TEXTENCODING_UTF8); +} + +OUString GetLockFileName(const svt::GenDocumentLockFile& rLockFile) +{ + INetURLObject aDocURL = rLockFile.ResolveLinks(INetURLObject(rLockFile.GetURL())); + return aDocURL.GetName(); +} + +void LockfileTest::testLOLockFileURL() +{ + // Test the generated file name for LibreOffice lock files + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testLOLockFileURL.odt"); + + svt::DocumentLockFile aLockFile(aTestODT); + CPPUNIT_ASSERT_EQUAL(OUString(".~lock.testLOLockFileURL.odt%23"), GetLockFileName(aLockFile)); +} + +void LockfileTest::testLOLockFileContent() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testLOLockFileContent.odt"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::DocumentLockFile aLockFile(aTestODT); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // User name + sal_Int32 nFirstChar = 0; + sal_Int32 nNextComma = sLockFileContent.indexOf(',', nFirstChar); + OUString sUserName; + sUserName += aUserOpt.GetFirstName() + " "; + sUserName += aUserOpt.GetLastName(); + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(nFirstChar, nNextComma - nFirstChar)); + + // System user name + nFirstChar = nNextComma + 1; + nNextComma = sLockFileContent.indexOf(',', nFirstChar); + ::osl::Security aSecurity; + OUString sSysUserName; + aSecurity.getUserName(sSysUserName); + CPPUNIT_ASSERT_EQUAL(sSysUserName, sLockFileContent.copy(nFirstChar, nNextComma - nFirstChar)); + + // Local host + nFirstChar = nNextComma + 1; + nNextComma = sLockFileContent.indexOf(',', nFirstChar); + CPPUNIT_ASSERT_EQUAL(::osl::SocketAddr::getLocalHostname(), + sLockFileContent.copy(nFirstChar, nNextComma - nFirstChar)); + + // Skip date and time because it changes after the lock file was created + nFirstChar = nNextComma + 1; + nNextComma = sLockFileContent.indexOf(',', nFirstChar); + + // user url + nFirstChar = nNextComma + 1; + OUString aUserInstDir; + ::utl::Bootstrap::locateUserInstallation(aUserInstDir); + CPPUNIT_ASSERT_EQUAL( + aUserInstDir, + sLockFileContent.copy(nFirstChar, sLockFileContent.getLength() - nFirstChar - 1)); +} + +void LockfileTest::testLOLockFileRT() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testLOLockFileRT.odt"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::SYSUSERNAME], + aRTEntry[LockFileComponent::SYSUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::LOCALHOST], + aRTEntry[LockFileComponent::LOCALHOST]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::USERURL], + aRTEntry[LockFileComponent::USERURL]); + // LockFileComponent::EDITTIME can change + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testLOLockFileUnicodeUsername() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT = m_directories.getURLFromSrc( + "/svl/qa/unit/lockfiles/data/testLOLockFileUnicodeUsername.odt"); + + // Set user name + SvtUserOptions aUserOpt; + sal_Unicode vFirstName[] = { 2351, 2676, 3117, 5279 }; + aUserOpt.SetToken(UserOptToken::FirstName, OUString(vFirstName, 4)); + sal_Unicode vLastName[] = { 671, 1245, 1422, 1822 }; + aUserOpt.SetToken(UserOptToken::LastName, OUString(vLastName, 4)); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(OUString(aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName()), + aOrigEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testLOLockFileOverwrite() +{ + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testLOLockFileOverwrite.odt"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + aLockFile.CreateOwnLockFile(); + + // Change user name + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile2"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Overwrite lockfile + svt::DocumentLockFile aLockFile2(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile2.OverwriteOwnLockFile(); + + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::SYSUSERNAME], + aRTEntry[LockFileComponent::SYSUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::LOCALHOST], + aRTEntry[LockFileComponent::LOCALHOST]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::USERURL], + aRTEntry[LockFileComponent::USERURL]); + + aLockFile2.RemoveFileDirectly(); +} + +void LockfileTest::testWordLockFileURL() +{ + // Test the generated file name for Word lock files + + // Word specific file format + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testWordLockFileURL.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$stWordLockFileURL.docx"), GetLockFileName(aLockFile)); + } + + // Eight character file name (cuts two characters) + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.docx"), GetLockFileName(aLockFile)); + } + + // Seven character file name (cuts one character) + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/1234567.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$234567.docx"), GetLockFileName(aLockFile)); + } + + // Six character file name (cuts no character) + { + OUString aTestFile = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/123456.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$123456.docx"), GetLockFileName(aLockFile)); + } + + // One character file name + { + OUString aTestFile = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/1.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$1.docx"), GetLockFileName(aLockFile)); + } + + // Test for ODT format + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.odt"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.odt"), GetLockFileName(aLockFile)); + } + + // Test for DOC format + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.doc"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.doc"), GetLockFileName(aLockFile)); + } + + // Test for RTF format + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.rtf"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.rtf"), GetLockFileName(aLockFile)); + } +} + +void LockfileTest::testExcelLockFileURL() +{ + // Test the generated file name for Excel lock files + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testExcelLockFileURL.xlsx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$testExcelLockFileURL.xlsx"), GetLockFileName(aLockFile)); + } + + // Eight character file name + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.xlsx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.xlsx"), GetLockFileName(aLockFile)); + } + + // One character file name + { + OUString aTestFile = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/1.xlsx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$1.xlsx"), GetLockFileName(aLockFile)); + } + + // Test for ODS format + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.ods"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.ods"), GetLockFileName(aLockFile)); + } +} + +void LockfileTest::testPowerPointLockFileURL() +{ + // Test the generated file name for PowerPoint lock files + { + OUString aTestFile = m_directories.getURLFromSrc( + "/svl/qa/unit/lockfiles/data/testPowerPointLockFileURL.pptx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$testPowerPointLockFileURL.pptx"), + GetLockFileName(aLockFile)); + } + + // Eight character file name + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.pptx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.pptx"), GetLockFileName(aLockFile)); + } + + // One character file name + { + OUString aTestFile = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/1.pptx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$1.pptx"), GetLockFileName(aLockFile)); + } + + // Test for PPT format + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.ppt"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.ppt"), GetLockFileName(aLockFile)); + } + + // Test for ODP format + { + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/12345678.odp"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.odp"), GetLockFileName(aLockFile)); + } +} + +void LockfileTest::testWordLockFileContent() +{ + // Test the lockfile generated for the specified DOCX document + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testWordLockFileContent.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::MSODocumentLockFile aLockFile(aTestFile); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // First character is the size of the user name + OUString sUserName; + sUserName += aUserOpt.GetFirstName() + " "; + sUserName += aUserOpt.GetLastName(); + int nIndex = 0; + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(1, sUserName.getLength())); + + // We have some filling 0 bytes after the user name + for (nIndex = sUserName.getLength() + 1; nIndex < MSO_USERNAME_MAX_LENGTH + 2; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // Then we have the user name's length again + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name again with 16 bit coding + for (int i = 0; i < sUserName.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL( + sUserName[i], + static_cast<sal_Unicode>(static_cast<sal_Int16>(sLockFileContent[55 + i * 2]) + + static_cast<sal_Int16>(sLockFileContent[55 + i * 2 + 1]))); + } + + // We have some filling 0 bytes after the user name + for (nIndex += sUserName.getLength() * 2 + 1; nIndex < MSO_WORD_LOCKFILE_SIZE; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // We have a fixed size lock file + CPPUNIT_ASSERT_EQUAL(sal_Int32(MSO_WORD_LOCKFILE_SIZE), sLockFileContent.getLength()); +} + +void LockfileTest::testExcelLockFileContent() +{ + // Test the lockfile generated for the specified XLSX document + OUString aTestFile + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testExcelLockFileContent.xlsx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::MSODocumentLockFile aLockFile(aTestFile); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // First character is the size of the user name + OUString sUserName; + sUserName += aUserOpt.GetFirstName() + " "; + sUserName += aUserOpt.GetLastName(); + int nIndex = 0; + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(1, sUserName.getLength())); + + // We have some filling 0x20 bytes after the user name + for (nIndex = sUserName.getLength() + 1; nIndex < MSO_USERNAME_MAX_LENGTH + 3; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // Then we have the user name's length again + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name again with 16 bit coding + for (int i = 0; i < sUserName.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL( + sUserName[i], + static_cast<sal_Unicode>(static_cast<sal_Int16>(sLockFileContent[56 + i * 2]) + + static_cast<sal_Int16>(sLockFileContent[56 + i * 2 + 1]))); + } + + // We have some filling 0 and 0x20 bytes after the user name + for (nIndex += sUserName.getLength() * 2 + 2; nIndex < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE; + nIndex += 2) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + if (nIndex + 1 < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE) + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), + static_cast<sal_Int32>(sLockFileContent[nIndex + 1])); + } + + // We have a fixed size lock file + CPPUNIT_ASSERT_EQUAL(sal_Int32(MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE), + sLockFileContent.getLength()); +} + +void LockfileTest::testPowerPointLockFileContent() +{ + // Test the lockfile generated for the specified PPTX document + OUString aTestFile = m_directories.getURLFromSrc( + "/svl/qa/unit/lockfiles/data/testPowerPointLockFileContent.pptx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::MSODocumentLockFile aLockFile(aTestFile); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // First character is the size of the user name + OUString sUserName; + sUserName += aUserOpt.GetFirstName() + " "; + sUserName += aUserOpt.GetLastName(); + int nIndex = 0; + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(1, sUserName.getLength())); + + // We have some filling bytes after the user name + nIndex = sUserName.getLength() + 1; + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), static_cast<sal_Int32>(sLockFileContent[nIndex])); + for (nIndex += 1; nIndex < MSO_USERNAME_MAX_LENGTH + 3; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // Then we have the user name's length again + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name again with 16 bit coding + for (int i = 0; i < sUserName.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL( + sUserName[i], + static_cast<sal_Unicode>(static_cast<sal_Int16>(sLockFileContent[56 + i * 2]) + + static_cast<sal_Int16>(sLockFileContent[56 + i * 2 + 1]))); + } + + // We have some filling 0 and 0x20 bytes after the user name + for (nIndex += sUserName.getLength() * 2 + 2; nIndex < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE; + nIndex += 2) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + if (nIndex + 1 < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE) + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), + static_cast<sal_Int32>(sLockFileContent[nIndex + 1])); + } + + // We have a fixed size lock file + CPPUNIT_ASSERT_EQUAL(sal_Int32(MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE), + sLockFileContent.getLength()); +} + +void LockfileTest::testWordLockFileRT() +{ + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testWordLockFileRT.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + aLockFile.RemoveFileDirectly(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); +} + +void LockfileTest::testExcelLockFileRT() +{ + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testExcelLockFileRT.xlsx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + aLockFile.RemoveFileDirectly(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); +} + +void LockfileTest::testPowerPointLockFileRT() +{ + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testPowerPointLockFileRT.pptx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + aLockFile.RemoveFileDirectly(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); +} + +void LockfileTest::testMSOLockFileLongUserName() +{ + OUString aTestODT = m_directories.getURLFromSrc( + "/svl/qa/unit/lockfiles/data/testMSOLockFileLongUserName.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + aUserOpt.SetToken(UserOptToken::LastName, + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the user name was cut to the maximum length + CPPUNIT_ASSERT_EQUAL( + aOrigEntry[LockFileComponent::OOOUSERNAME].copy(0, MSO_USERNAME_MAX_LENGTH), + aRTEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testMSOLockFileUnicodeUsername() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT = m_directories.getURLFromSrc( + "/svl/qa/unit/lockfiles/data/testMSOLockFileUnicodeUsername.docx"); + + // Set user name + SvtUserOptions aUserOpt; + sal_Unicode vFirstName[] = { 2351, 2676, 3117, 5279 }; + aUserOpt.SetToken(UserOptToken::FirstName, OUString(vFirstName, 4)); + sal_Unicode vLastName[] = { 671, 1245, 1422, 1822 }; + aUserOpt.SetToken(UserOptToken::LastName, OUString(vLastName, 4)); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the user name is the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(OUString(aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName()), + aOrigEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testMSOLockFileOverwrite() +{ + OUString aTestODT + = m_directories.getURLFromSrc("/svl/qa/unit/lockfiles/data/testMSOLockFileOverwrite.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + aLockFile.CreateOwnLockFile(); + + // Change user name + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile2"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Overwrite lockfile + svt::MSODocumentLockFile aLockFile2(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile2.OverwriteOwnLockFile(); + + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile2.RemoveFileDirectly(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(LockfileTest); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svl/source/misc/documentlockfile.cxx b/svl/source/misc/documentlockfile.cxx index 837005bd6f85..1bdf7ce5f386 100644 --- a/svl/source/misc/documentlockfile.cxx +++ b/svl/source/misc/documentlockfile.cxx @@ -54,42 +54,34 @@ using namespace ::com::sun::star; namespace svt { -bool DocumentLockFile::m_bAllowInteraction = true; +GenDocumentLockFile::GenDocumentLockFile( const OUString& aURL ) +: LockFileCommon( aURL ) +{ +} -DocumentLockFile::DocumentLockFile( const OUString& aOrigURL ) -: LockFileCommon( aOrigURL, ".~lock." ) +GenDocumentLockFile::GenDocumentLockFile( const OUString& aOrigURL, const OUString& aPrefix ) +: LockFileCommon( aOrigURL, aPrefix ) { } -DocumentLockFile::~DocumentLockFile() +GenDocumentLockFile::~GenDocumentLockFile() { } - -void DocumentLockFile::WriteEntryToStream( const LockFileEntry& aEntry, const uno::Reference< io::XOutputStream >& xOutput ) +uno::Reference< io::XInputStream > GenDocumentLockFile::OpenStream() { ::osl::MutexGuard aGuard( m_aMutex ); - OUStringBuffer aBuffer; - - for ( LockFileComponent lft : o3tl::enumrange<LockFileComponent>() ) - { - aBuffer.append( EscapeCharacters( aEntry[lft] ) ); - if ( lft < LockFileComponent::LAST ) - aBuffer.append( ',' ); - else - aBuffer.append( ';' ); - } + uno::Reference < css::ucb::XCommandEnvironment > xEnv; + ::ucbhelper::Content aSourceContent( GetURL(), xEnv, comphelper::getProcessComponentContext() ); - OString aStringData( OUStringToOString( aBuffer.makeStringAndClear(), RTL_TEXTENCODING_UTF8 ) ); - uno::Sequence< sal_Int8 > aData( reinterpret_cast<sal_Int8 const *>(aStringData.getStr()), aStringData.getLength() ); - xOutput->writeBytes( aData ); + // the file can be opened readonly, no locking will be done + return aSourceContent.openStream(); } - -bool DocumentLockFile::CreateOwnLockFile() +bool GenDocumentLockFile::CreateOwnLockFile() { ::osl::MutexGuard aGuard( m_aMutex ); @@ -113,7 +105,7 @@ bool DocumentLockFile::CreateOwnLockFile() xSeekable->seek( 0 ); uno::Reference < css::ucb::XCommandEnvironment > xEnv; - ::ucbhelper::Content aTargetContent( m_aURL, xEnv, comphelper::getProcessComponentContext() ); + ::ucbhelper::Content aTargetContent( GetURL(), xEnv, comphelper::getProcessComponentContext() ); ucb::InsertCommandArgument aInsertArg; aInsertArg.Data = xInput; @@ -135,50 +127,13 @@ bool DocumentLockFile::CreateOwnLockFile() return true; } - -LockFileEntry DocumentLockFile::GetLockData() -{ - ::osl::MutexGuard aGuard( m_aMutex ); - - uno::Reference< io::XInputStream > xInput = OpenStream(); - if ( !xInput.is() ) - throw uno::RuntimeException(); - - const sal_Int32 nBufLen = 32000; - uno::Sequence< sal_Int8 > aBuffer( nBufLen ); - - sal_Int32 nRead = 0; - - nRead = xInput->readBytes( aBuffer, nBufLen ); - xInput->closeInput(); - - if ( nRead == nBufLen ) - throw io::WrongFormatException(); - - sal_Int32 nCurPos = 0; - return ParseEntry( aBuffer, nCurPos ); -} - - -uno::Reference< io::XInputStream > DocumentLockFile::OpenStream() -{ - ::osl::MutexGuard aGuard( m_aMutex ); - - uno::Reference < css::ucb::XCommandEnvironment > xEnv; - ::ucbhelper::Content aSourceContent( m_aURL, xEnv, comphelper::getProcessComponentContext() ); - - // the file can be opened readonly, no locking will be done - return aSourceContent.openStream(); -} - - -bool DocumentLockFile::OverwriteOwnLockFile() +bool GenDocumentLockFile::OverwriteOwnLockFile() { // allows to overwrite the lock file with the current data try { uno::Reference < css::ucb::XCommandEnvironment > xEnv; - ::ucbhelper::Content aTargetContent( m_aURL, xEnv, comphelper::getProcessComponentContext() ); + ::ucbhelper::Content aTargetContent( GetURL(), xEnv, comphelper::getProcessComponentContext() ); LockFileEntry aNewEntry = GenerateOwnEntry(); @@ -198,8 +153,7 @@ bool DocumentLockFile::OverwriteOwnLockFile() return true; } - -void DocumentLockFile::RemoveFile() +void GenDocumentLockFile::RemoveFile() { ::osl::MutexGuard aGuard( m_aMutex ); @@ -215,15 +169,71 @@ void DocumentLockFile::RemoveFile() RemoveFileDirectly(); } -void DocumentLockFile::RemoveFileDirectly() +void GenDocumentLockFile::RemoveFileDirectly() { uno::Reference < css::ucb::XCommandEnvironment > xEnv; - ::ucbhelper::Content aCnt(m_aURL, xEnv, comphelper::getProcessComponentContext()); + ::ucbhelper::Content aCnt(GetURL(), xEnv, comphelper::getProcessComponentContext()); aCnt.executeCommand("delete", uno::makeAny(true)); } +DocumentLockFile::DocumentLockFile( const OUString& aOrigURL ) +: GenDocumentLockFile( aOrigURL, ".~lock." ) +{ +} + + +DocumentLockFile::~DocumentLockFile() +{ +} + + +void DocumentLockFile::WriteEntryToStream( const LockFileEntry& aEntry, const uno::Reference< io::XOutputStream >& xOutput ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUStringBuffer aBuffer; + + for ( LockFileComponent lft : o3tl::enumrange<LockFileComponent>() ) + { + aBuffer.append( EscapeCharacters( aEntry[lft] ) ); + if ( lft < LockFileComponent::LAST ) + aBuffer.append( ',' ); + else + aBuffer.append( ';' ); + } + + OString aStringData( OUStringToOString( aBuffer.makeStringAndClear(), RTL_TEXTENCODING_UTF8 ) ); + uno::Sequence< sal_Int8 > aData( reinterpret_cast<sal_Int8 const *>(aStringData.getStr()), aStringData.getLength() ); + xOutput->writeBytes( aData ); +} + +LockFileEntry DocumentLockFile::GetLockData() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + uno::Reference< io::XInputStream > xInput = OpenStream(); + if ( !xInput.is() ) + throw uno::RuntimeException(); + + const sal_Int32 nBufLen = 32000; + uno::Sequence< sal_Int8 > aBuffer( nBufLen ); + + sal_Int32 nRead = 0; + + nRead = xInput->readBytes( aBuffer, nBufLen ); + xInput->closeInput(); + + if ( nRead == nBufLen ) + throw io::WrongFormatException(); + + sal_Int32 nCurPos = 0; + return ParseEntry( aBuffer, nCurPos ); +} + + + } // namespace svt diff --git a/svl/source/misc/lockfilecommon.cxx b/svl/source/misc/lockfilecommon.cxx index 88850e7687f9..30b53745c620 100644 --- a/svl/source/misc/lockfilecommon.cxx +++ b/svl/source/misc/lockfilecommon.cxx @@ -54,20 +54,43 @@ using namespace ::com::sun::star; namespace svt { +LockFileCommon::LockFileCommon( const OUString& aURL ) +{ + m_aURL = aURL; +} + LockFileCommon::LockFileCommon( const OUString& aOrigURL, const OUString& aPrefix ) { + m_aURL = GenerateURL(aOrigURL, aPrefix); +} + + +LockFileCommon::~LockFileCommon() +{ +} + + +const OUString& LockFileCommon::GetURL() const +{ + return m_aURL; +} + + +void LockFileCommon::SetURL(const OUString& aURL) +{ + m_aURL = aURL; +} + + +OUString LockFileCommon::GenerateURL( const OUString& aOrigURL, const OUString& aPrefix ) +{ INetURLObject aDocURL = ResolveLinks( INetURLObject( aOrigURL ) ); OUString aShareURLString = aDocURL.GetPartBeforeLastName(); aShareURLString += aPrefix; aShareURLString += aDocURL.GetName(); aShareURLString += "%23"; // '#' - m_aURL = INetURLObject( aShareURLString ).GetMainURL( INetURLObject::DecodeMechanism::NONE ); -} - - -LockFileCommon::~LockFileCommon() -{ + return INetURLObject( aShareURLString ).GetMainURL( INetURLObject::DecodeMechanism::NONE ); } diff --git a/svl/source/misc/msodocumentlockfile.cxx b/svl/source/misc/msodocumentlockfile.cxx new file mode 100644 index 000000000000..938b36d5cd26 --- /dev/null +++ b/svl/source/misc/msodocumentlockfile.cxx @@ -0,0 +1,244 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <svl/msodocumentlockfile.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <algorithm> + +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XInputStream.hpp> + +namespace svt +{ +bool MSODocumentLockFile::isWordFormat(const OUString& aOrigURL) const +{ + INetURLObject aDocURL = ResolveLinks(INetURLObject(aOrigURL)); + + return aDocURL.GetFileExtension().compareToIgnoreAsciiCase("DOC") == 0 + || aDocURL.GetFileExtension().compareToIgnoreAsciiCase("DOCX") == 0 + || aDocURL.GetFileExtension().compareToIgnoreAsciiCase("RTF") == 0 + || aDocURL.GetFileExtension().compareToIgnoreAsciiCase("ODT") == 0; +} + +bool MSODocumentLockFile::isExcelFormat(const OUString& aOrigURL) const +{ + INetURLObject aDocURL = ResolveLinks(INetURLObject(aOrigURL)); + + return //aDocURL.GetFileExtension().compareToIgnoreAsciiCase("XLS") || // MSO does not create lockfile for XLS + aDocURL.GetFileExtension().compareToIgnoreAsciiCase("XLSX") == 0 + || aDocURL.GetFileExtension().compareToIgnoreAsciiCase("ODS") == 0; +} + +bool MSODocumentLockFile::isPowerPointFormat(const OUString& aOrigURL) const +{ + INetURLObject aDocURL = ResolveLinks(INetURLObject(aOrigURL)); + + return aDocURL.GetFileExtension().compareToIgnoreAsciiCase("PPTX") == 0 + || aDocURL.GetFileExtension().compareToIgnoreAsciiCase("PPT") == 0 + || aDocURL.GetFileExtension().compareToIgnoreAsciiCase("ODP") == 0; +} + +MSODocumentLockFile::MSODocumentLockFile(const OUString& aOrigURL) + : GenDocumentLockFile(GenerateURL(aOrigURL, "~$")) + , m_sOrigURL(aOrigURL) +{ +} + +MSODocumentLockFile::~MSODocumentLockFile() {} + +OUString MSODocumentLockFile::GenerateURL(const OUString& aOrigURL, const OUString& aPrefix) +{ + INetURLObject aDocURL = ResolveLinks(INetURLObject(aOrigURL)); + OUString aURL = aDocURL.GetPartBeforeLastName(); + aURL += aPrefix; + + // For text documents MSO Word cuts some of the first characters of the file name + OUString sFileName = aDocURL.GetName(); + if (isWordFormat(aOrigURL)) + { + sal_Int32 nFileNameLength + = aDocURL.GetName().getLength() - aDocURL.GetFileExtension().getLength() - 1; + if (nFileNameLength >= 8) + aURL += sFileName.copy(2); + else if (nFileNameLength == 7) + aURL += sFileName.copy(1); + else + aURL += sFileName; + } + else + { + aURL += sFileName; + } + return INetURLObject(aURL).GetMainURL(INetURLObject::DecodeMechanism::NONE); +} + +void MSODocumentLockFile::WriteEntryToStream( + const LockFileEntry& aEntry, const css::uno::Reference<css::io::XOutputStream>& xOutput) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + // Reallocate the date with the right size, different lock file size for different components + int nLockFileSize = isWordFormat(m_sOrigURL) ? MSO_WORD_LOCKFILE_SIZE + : MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE; + css::uno::Sequence<sal_Int8> aData(nLockFileSize); + + // Write out the user name's length as a single byte integer + // The maximum length is 52 in MSO, so we'll need to truncate the user name if it's longer + OUString aUserName = aEntry[LockFileComponent::OOOUSERNAME]; + int nIndex = 0; + aData[nIndex] = static_cast<sal_Int8>( + std::min(aUserName.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH))); + + if (aUserName.getLength() > MSO_USERNAME_MAX_LENGTH) + aUserName = aUserName.copy(0, MSO_USERNAME_MAX_LENGTH); + + // From the second position write out the user name using one byte characters. + nIndex = 1; + for (int nChar = 0; nChar < aUserName.getLength(); ++nChar) + { + aData[nIndex] = static_cast<sal_Int8>(aUserName[nChar]); + ++nIndex; + } + + // Fill up the remaining bytes with dummy data + if (isWordFormat(m_sOrigURL)) + { + while (nIndex < MSO_USERNAME_MAX_LENGTH + 2) + { + aData[nIndex] = static_cast<sal_Int8>(0); + ++nIndex; + } + } + else if (isExcelFormat(m_sOrigURL)) + { + while (nIndex < MSO_USERNAME_MAX_LENGTH + 3) + { + aData[nIndex] = static_cast<sal_Int8>(0x20); + ++nIndex; + } + } + else + { + aData[nIndex] = static_cast<sal_Int8>(0); + ++nIndex; + + while (nIndex < MSO_USERNAME_MAX_LENGTH + 3) + { + aData[nIndex] = static_cast<sal_Int8>(0x20); + ++nIndex; + } + } + + // At the next position we have the user name's length again, but now as a 2 byte integer + aData[nIndex] = static_cast<sal_Int8>( + std::min(aUserName.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH))); + ++nIndex; + aData[nIndex] = 0; + ++nIndex; + + // And the user name again with unicode characters + for (int nChar = 0; nChar < aUserName.getLength(); ++nChar) + { + aData[nIndex] = static_cast<sal_Int8>(aUserName[nChar] & 0xff); + ++nIndex; + aData[nIndex] = static_cast<sal_Int8>(aUserName[nChar] >> 8); + ++nIndex; + } + + // Fill the remaining part with dummy bits + if (isWordFormat(m_sOrigURL)) + { + while (nIndex < nLockFileSize) + { + aData[nIndex] = static_cast<sal_Int8>(0); + ++nIndex; + } + } + else + { + while (nIndex < nLockFileSize) + { + aData[nIndex] = static_cast<sal_Int8>(0x20); + ++nIndex; + if (nIndex < nLockFileSize) + { + aData[nIndex] = static_cast<sal_Int8>(0); + ++nIndex; + } + } + } + + xOutput->writeBytes(aData); +} + +LockFileEntry MSODocumentLockFile::GetLockData() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + LockFileEntry aResult; + css::uno::Reference<css::io::XInputStream> xInput = OpenStream(); + if (!xInput.is()) + throw css::uno::RuntimeException(); + + const sal_Int32 nBufLen = 256; + css::uno::Sequence<sal_Int8> aBuf(nBufLen); + const sal_Int32 nRead = xInput->readBytes(aBuf, nBufLen); + xInput->closeInput(); + if (nRead >= 162) + { + // Reverse engineering of MS Office Owner Files format (MS Office 2016 tested). + // It starts with a single byte with name length, after which characters of username go + // in current Windows 8-bit codepage. + // For Word lockfiles, the name is followed by zero bytes up to position 54. + // For PowerPoint lockfiles, the name is followed by a single zero byte, and then 0x20 + // bytes up to position 55. + // For Excel lockfiles, the name is followed by 0x20 bytes up to position 55. + // At those positions in each type of lockfile, a name length 2-byte word goes, followed + // by UTF-16-LE-encoded copy of username. Spaces or some garbage follow up to the end of + // the lockfile (total 162 bytes for Word, 165 bytes for Excel/PowerPoint). + // Apparently MS Office does not allow username to be longer than 52 characters (trying + // to enter more in its options dialog results in error messages stating this limit). + const int nACPLen = aBuf[0]; + if (nACPLen > 0 && nACPLen <= 52) // skip wrong format + { + const sal_Int8* pBuf = aBuf.getConstArray() + 54; + int nUTF16Len = *pBuf; // try Word position + // If UTF-16 length is 0x20, then ACP length is also less than maximal, which means + // that in Word lockfile case, at least two preceeding bytes would be zero. Both + // Excel and PowerPoint lockfiles would have at least one of those bytes non-zero. + if (nUTF16Len == 0x20 && (*(pBuf - 1) != 0 || *(pBuf - 2) != 0)) + nUTF16Len = *++pBuf; // use Excel/PowerPoint position + + if (nUTF16Len > 0 && nUTF16Len <= 52) // skip wrong format + aResult[LockFileComponent::OOOUSERNAME] + = OUString(reinterpret_cast<const sal_Unicode*>(pBuf + 2), nUTF16Len); + } + } + return aResult; +} + +void MSODocumentLockFile::RemoveFile() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic? + LockFileEntry aNewEntry = GenerateOwnEntry(); + LockFileEntry aFileData = GetLockData(); + + if (aFileData[LockFileComponent::OOOUSERNAME] != aNewEntry[LockFileComponent::OOOUSERNAME]) + throw css::io::IOException(); // not the owner, access denied + + RemoveFileDirectly(); +} + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svl/source/misc/sharecontrolfile.cxx b/svl/source/misc/sharecontrolfile.cxx index d9ef69f9844e..26e8015b6d06 100644 --- a/svl/source/misc/sharecontrolfile.cxx +++ b/svl/source/misc/sharecontrolfile.cxx @@ -55,10 +55,10 @@ namespace svt { ShareControlFile::ShareControlFile( const OUString& aOrigURL ) : LockFileCommon( aOrigURL, ".~sharing." ) { - if ( !m_xStream.is() && !m_aURL.isEmpty() ) + if ( !m_xStream.is() && !GetURL().isEmpty() ) { uno::Reference< ucb::XCommandEnvironment > xDummyEnv; - ::ucbhelper::Content aContent = ::ucbhelper::Content( m_aURL, xDummyEnv, comphelper::getProcessComponentContext() ); + ::ucbhelper::Content aContent = ::ucbhelper::Content( GetURL(), xDummyEnv, comphelper::getProcessComponentContext() ); uno::Reference< ucb::XContentIdentifier > xContId( aContent.get().is() ? aContent.get()->getIdentifier() : nullptr ); if ( !xContId.is() || xContId->getContentProviderScheme() != "file" ) @@ -329,7 +329,7 @@ void ShareControlFile::RemoveFile() Close(); uno::Reference<ucb::XSimpleFileAccess3> xSimpleFileAccess(ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext())); - xSimpleFileAccess->kill( m_aURL ); + xSimpleFileAccess->kill( GetURL() ); } } // namespace svt |