diff options
author | Mike Kaganski <mike.kaganski@collabora.com> | 2018-12-04 10:00:51 +0300 |
---|---|---|
committer | Mike Kaganski <mike.kaganski@collabora.com> | 2019-06-18 13:58:13 +0200 |
commit | 8f1ecdc9e874de1e49f6a47789bb0cceeea1221b (patch) | |
tree | e34ee924991d89dbf14d87d0dba921df91724e6c | |
parent | 910c02e2b921ca82d268611022415f1c2bbb9df4 (diff) |
tdf#34171: check foreign lockfiles to tell who has locked document
MS Office (Word/Excel/PowerPoint) lockfiles are supported now.
Note that Excel does *not* create lockfiles for pre-OOXML files.
This changes osl_openFile implementation on Windows, to treat
osl_File_OpenFlag_NoLock to also include FILE_SHARE_DELETE flag
for CreateFileW. This is required to allow opening files created
with FILE_FLAG_DELETE_ON_CLOSE flag, such as Excel's owner files.
The shange should be consistent with the overall meaning of the
osl_File_OpenFlag_NoLock to not impose any locking constraints
to the file being opened.
Change-Id: I7b99012f4bd60ab3821fb91d5166a286031b7e93
Reviewed-on: https://gerrit.libreoffice.org/64496
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 607b80ca3cddc239a35580470944a438ce144fc8)
Reviewed-on: https://gerrit.libreoffice.org/74259
Tested-by: Mike Kaganski <mike.kaganski@collabora.com>
-rw-r--r-- | include/sfx2/docfile.hxx | 3 | ||||
-rw-r--r-- | sal/osl/w32/file.cxx | 2 | ||||
-rw-r--r-- | sfx2/source/doc/docfile.cxx | 108 |
3 files changed, 108 insertions, 5 deletions
diff --git a/include/sfx2/docfile.hxx b/include/sfx2/docfile.hxx index 7022d51743d9..2f7d9454c1e5 100644 --- a/include/sfx2/docfile.hxx +++ b/include/sfx2/docfile.hxx @@ -295,7 +295,8 @@ public: private: enum class ShowLockResult { NoLock, Succeeded, Try }; - ShowLockResult ShowLockedDocumentDialog(const LockFileEntry& aData, bool bIsLoading, bool bOwnLock, bool bHandleSysLocked); + ShowLockResult ShowLockedDocumentDialog(const OUString& aDocURL, const LockFileEntry& aData, + bool bIsLoading, bool bOwnLock, bool bHandleSysLocked); enum class MessageDlg { LockFileIgnore, LockFileCorrupt }; bool ShowLockFileProblemDialog(MessageDlg nWhichDlg); diff --git a/sal/osl/w32/file.cxx b/sal/osl/w32/file.cxx index 1bc898eba4e4..0c3f3d9621fe 100644 --- a/sal/osl/w32/file.cxx +++ b/sal/osl/w32/file.cxx @@ -653,7 +653,7 @@ oslFileError SAL_CALL osl_openFile( dwShare |= FILE_SHARE_WRITE; if (uFlags & osl_File_OpenFlag_NoLock) - dwShare |= FILE_SHARE_WRITE; + dwShare |= FILE_SHARE_WRITE | FILE_SHARE_DELETE; if (uFlags & osl_File_OpenFlag_Create) dwCreation |= CREATE_NEW; diff --git a/sfx2/source/doc/docfile.cxx b/sfx2/source/doc/docfile.cxx index 144fe50aa64c..dd1ff394894b 100644 --- a/sfx2/source/doc/docfile.cxx +++ b/sfx2/source/doc/docfile.cxx @@ -885,8 +885,104 @@ void SfxMedium::SetEncryptionDataToStorage_Impl() // that. Clearly the knowledge whether lock files should be used or // not for some URL scheme belongs in UCB, not here. +namespace +{ +OUString tryMSOwnerFile(const INetURLObject& aLockfileURL) +{ + try + { + static osl::Mutex aMutex; + osl::MutexGuard aGuard(aMutex); + css::uno::Reference<css::ucb::XCommandEnvironment> xEnv; + ucbhelper::Content aSourceContent( + aLockfileURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), xEnv, + comphelper::getProcessComponentContext()); + + // Excel creates Owner Files with FILE_FLAG_DELETE_ON_CLOSE, so we need to open it with + // FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE share mode + css::uno::Reference<css::io::XInputStream> xStream = aSourceContent.openStreamNoLock(); + if (!xStream) + return OUString(); + + const sal_Int32 nBufLen = 256; + css::uno::Sequence<sal_Int8> aBuf(nBufLen); + const sal_Int32 nRead = xStream->readBytes(aBuf, nBufLen); + xStream->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 + return OUString(reinterpret_cast<const sal_Unicode*>(pBuf + 2), nUTF16Len); + } + } + } + catch (...) {} // we don't ever need to care about any exceptions here + + return OUString(); +} -SfxMedium::ShowLockResult SfxMedium::ShowLockedDocumentDialog( const LockFileEntry& aData, bool bIsLoading, bool bOwnLock, bool bHandleSysLocked ) +OUString tryMSOwnerFiles(const OUString& sDocURL) +{ + INetURLObject aURL(sDocURL); + if (aURL.HasError()) + return OUString(); + const OUString sFileName = aURL.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + if (sFileName.isEmpty()) + return OUString(); + const OUString sFileExt = aURL.GetFileExtension(); + const sal_Int32 nFileNameLen + = sFileName.getLength() - sFileExt.getLength() - (sFileExt.isEmpty() ? 0 : 1); + // Word, Excel, PowerPoint all prepend the filename with "~$". + aURL.SetName("~$" + sFileName, INetURLObject::EncodeMechanism::All); + OUString sUserData = tryMSOwnerFile(aURL); + // Additionally, Word strips first chars of the filename: 1 for length 7, 2 for length >=8. + if (sUserData.isEmpty() && nFileNameLen > 6) + { + aURL.SetName("~$" + sFileName.copy((nFileNameLen == 7) ? 1 : 2), + INetURLObject::EncodeMechanism::All); + sUserData = tryMSOwnerFile(aURL); + } + + if (!sUserData.isEmpty()) + sUserData += " (MS Office)"; // Mention the used office suite + + return sUserData; +} + +OUString tryForeignLockfiles(const OUString& sDocURL) +{ + OUString sUserData = tryMSOwnerFiles(sDocURL); + // here we can test for empty result, and add other known applications' lockfile testing + return sUserData.trim(); +} +} + +SfxMedium::ShowLockResult SfxMedium::ShowLockedDocumentDialog(const OUString& aDocURL, + const LockFileEntry& aData, + bool bIsLoading, bool bOwnLock, + bool bHandleSysLocked) { ShowLockResult nResult = ShowLockResult::NoLock; @@ -924,6 +1020,10 @@ SfxMedium::ShowLockResult SfxMedium::ShowLockedDocumentDialog( const LockFileEnt else aInfo = aData[LockFileComponent::SYSUSERNAME]; + if (aInfo.isEmpty() && !aDocURL.isEmpty()) + // Try to get name of user who has locked the file using other applications + aInfo = tryForeignLockfiles(aDocURL); + if ( !aInfo.isEmpty() && !aData[LockFileComponent::EDITTIME].isEmpty() ) aInfo += " ( " + aData[LockFileComponent::EDITTIME] + " )"; @@ -1153,7 +1253,7 @@ SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand( bool bLoading, bool b if ( !bResult && !bNoUI ) { - bUIStatus = ShowLockedDocumentDialog( aLockData, bLoading, false , true ); + bUIStatus = ShowLockedDocumentDialog("", aLockData, bLoading, false , true ); } } catch( ucb::InteractiveNetworkWriteException& ) @@ -1375,7 +1475,9 @@ SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand( bool bLoading, bool b if ( !bResult && !bIoErr) { if (!bNoUI) - bUIStatus = ShowLockedDocumentDialog(aData, bLoading, bOwnLock, bHandleSysLocked); + bUIStatus = ShowLockedDocumentDialog( + pImpl->m_aLogicName, aData, bLoading, bOwnLock, + bHandleSysLocked); else if (bLoading && bTryIgnoreLockFile && !bHandleSysLocked) bUIStatus = ShowLockResult::Succeeded; |