1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <svl/msodocumentlockfile.hxx>
12 #include <ucbhelper/content.hxx>
13 #include <comphelper/processfactory.hxx>
14 #include <o3tl/string_view.hxx>
16 #include <com/sun/star/io/IOException.hpp>
17 #include <com/sun/star/io/XOutputStream.hpp>
18 #include <com/sun/star/io/XInputStream.hpp>
19 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
25 bool isWordFormat(std::u16string_view sExt
)
27 return o3tl::equalsIgnoreAsciiCase(sExt
, u
"DOC") || o3tl::equalsIgnoreAsciiCase(sExt
, u
"DOCX")
28 || o3tl::equalsIgnoreAsciiCase(sExt
, u
"RTF")
29 || o3tl::equalsIgnoreAsciiCase(sExt
, u
"ODT");
32 bool isExcelFormat(std::u16string_view sExt
)
34 return //sExt.equalsIgnoreAsciiCase("XLS") || // MSO does not create lockfile for XLS
35 o3tl::equalsIgnoreAsciiCase(sExt
, u
"XLSX") || o3tl::equalsIgnoreAsciiCase(sExt
, u
"ODS");
38 bool isPowerPointFormat(std::u16string_view sExt
)
40 return o3tl::equalsIgnoreAsciiCase(sExt
, u
"PPTX") || o3tl::equalsIgnoreAsciiCase(sExt
, u
"PPT")
41 || o3tl::equalsIgnoreAsciiCase(sExt
, u
"ODP");
44 // Need to generate different lock file name for MSO.
45 OUString
GenerateMSOLockFileURL(std::u16string_view aOrigURL
)
47 INetURLObject aURL
= LockFileCommon::ResolveLinks(INetURLObject(aOrigURL
));
49 // For text documents MSO Word cuts some of the first characters of the file name
50 OUString sFileName
= aURL
.GetLastName();
51 const OUString sExt
= aURL
.GetFileExtension();
53 if (isWordFormat(sExt
))
55 const sal_Int32 nFileNameLength
= sFileName
.getLength() - sExt
.getLength() - 1;
56 if (nFileNameLength
>= 8)
57 sFileName
= sFileName
.copy(2);
58 else if (nFileNameLength
== 7)
59 sFileName
= sFileName
.copy(1);
61 aURL
.setName(Concat2View("~$" + sFileName
));
62 return aURL
.GetMainURL(INetURLObject::DecodeMechanism::NONE
);
67 MSODocumentLockFile::AppType
MSODocumentLockFile::getAppType(std::u16string_view sOrigURL
)
69 AppType eResult
= AppType::PowerPoint
;
70 INetURLObject aDocURL
= LockFileCommon::ResolveLinks(INetURLObject(sOrigURL
));
71 const OUString sExt
= aDocURL
.GetFileExtension();
72 if (isWordFormat(sExt
))
73 eResult
= AppType::Word
;
74 else if (isExcelFormat(sExt
))
75 eResult
= AppType::Excel
;
80 MSODocumentLockFile::MSODocumentLockFile(std::u16string_view aOrigURL
)
81 : GenDocumentLockFile(GenerateMSOLockFileURL(aOrigURL
))
82 , m_eAppType(getAppType(aOrigURL
))
86 MSODocumentLockFile::~MSODocumentLockFile() {}
88 void MSODocumentLockFile::WriteEntryToStream(
89 std::unique_lock
<std::mutex
>& /*rGuard*/, const LockFileEntry
& aEntry
,
90 const css::uno::Reference
<css::io::XOutputStream
>& xOutput
)
92 // Reallocate the date with the right size, different lock file size for different components
93 int nLockFileSize
= m_eAppType
== AppType::Word
? MSO_WORD_LOCKFILE_SIZE
94 : MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE
;
95 css::uno::Sequence
<sal_Int8
> aData(nLockFileSize
);
96 auto pData
= aData
.getArray();
98 // Write out the user name's length as a single byte integer
99 // The maximum length is 52 in MSO, so we'll need to truncate the user name if it's longer
100 OUString aUserName
= aEntry
[LockFileComponent::OOOUSERNAME
];
102 pData
[nIndex
] = static_cast<sal_Int8
>(
103 std::min(aUserName
.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH
)));
105 if (aUserName
.getLength() > MSO_USERNAME_MAX_LENGTH
)
106 aUserName
= aUserName
.copy(0, MSO_USERNAME_MAX_LENGTH
);
108 // From the second position write out the user name using one byte characters.
110 for (int nChar
= 0; nChar
< aUserName
.getLength(); ++nChar
)
112 pData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
]);
116 // Fill up the remaining bytes with dummy data
120 while (nIndex
< MSO_USERNAME_MAX_LENGTH
+ 2)
122 pData
[nIndex
] = static_cast<sal_Int8
>(0);
126 case AppType::PowerPoint
:
127 pData
[nIndex
] = static_cast<sal_Int8
>(0);
131 while (nIndex
< MSO_USERNAME_MAX_LENGTH
+ 3)
133 pData
[nIndex
] = static_cast<sal_Int8
>(0x20);
139 // At the next position we have the user name's length again, but now as a 2 byte integer
140 pData
[nIndex
] = static_cast<sal_Int8
>(
141 std::min(aUserName
.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH
)));
146 // And the user name again with unicode characters
147 for (int nChar
= 0; nChar
< aUserName
.getLength(); ++nChar
)
149 pData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
] & 0xff);
151 pData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
] >> 8);
155 // Fill the remaining part with dummy bits
159 while (nIndex
< nLockFileSize
)
161 pData
[nIndex
] = static_cast<sal_Int8
>(0);
166 case AppType::PowerPoint
:
167 while (nIndex
< nLockFileSize
)
169 pData
[nIndex
] = static_cast<sal_Int8
>(0x20);
171 if (nIndex
< nLockFileSize
)
173 pData
[nIndex
] = static_cast<sal_Int8
>(0);
180 xOutput
->writeBytes(aData
);
183 css::uno::Reference
<css::io::XInputStream
>
184 MSODocumentLockFile::OpenStream(std::unique_lock
<std::mutex
>& /*rGuard*/)
186 css::uno::Reference
<css::ucb::XCommandEnvironment
> xEnv
;
187 ::ucbhelper::Content
aSourceContent(GetURL(), xEnv
, comphelper::getProcessComponentContext());
189 // the file can be opened readonly, no locking will be done
190 return aSourceContent
.openStreamNoLock();
193 LockFileEntry
MSODocumentLockFile::GetLockDataImpl(std::unique_lock
<std::mutex
>& rGuard
)
195 LockFileEntry aResult
;
196 css::uno::Reference
<css::io::XInputStream
> xInput
= OpenStream(rGuard
);
198 throw css::uno::RuntimeException();
200 const sal_Int32 nBufLen
= 256;
201 css::uno::Sequence
<sal_Int8
> aBuf(nBufLen
);
202 const sal_Int32 nRead
= xInput
->readBytes(aBuf
, nBufLen
);
203 xInput
->closeInput();
206 // Reverse engineering of MS Office Owner Files format (MS Office 2016 tested).
207 // It starts with a single byte with name length, after which characters of username go
208 // in current Windows 8-bit codepage.
209 // For Word lockfiles, the name is followed by zero bytes up to position 54.
210 // For PowerPoint lockfiles, the name is followed by a single zero byte, and then 0x20
211 // bytes up to position 55.
212 // For Excel lockfiles, the name is followed by 0x20 bytes up to position 55.
213 // At those positions in each type of lockfile, a name length 2-byte word goes, followed
214 // by UTF-16-LE-encoded copy of username. Spaces or some garbage follow up to the end of
215 // the lockfile (total 162 bytes for Word, 165 bytes for Excel/PowerPoint).
216 // Apparently MS Office does not allow username to be longer than 52 characters (trying
217 // to enter more in its options dialog results in error messages stating this limit).
218 const int nACPLen
= aBuf
[0];
219 if (nACPLen
> 0 && nACPLen
<= 52) // skip wrong format
221 const sal_Int8
* pBuf
= aBuf
.getConstArray() + 54;
222 int nUTF16Len
= *pBuf
; // try Word position
223 // If UTF-16 length is 0x20, then ACP length is also less than maximal, which means
224 // that in Word lockfile case, at least two preceding bytes would be zero. Both
225 // Excel and PowerPoint lockfiles would have at least one of those bytes non-zero.
226 if (nUTF16Len
== 0x20 && (*(pBuf
- 1) != 0 || *(pBuf
- 2) != 0))
227 nUTF16Len
= *++pBuf
; // use Excel/PowerPoint position
229 if (nUTF16Len
> 0 && nUTF16Len
<= 52) // skip wrong format
231 OUStringBuffer
str(nUTF16Len
);
232 sal_uInt8
const* p
= reinterpret_cast<sal_uInt8
const*>(pBuf
+ 2);
233 for (int i
= 0; i
!= nUTF16Len
; ++i
)
235 str
.append(sal_Unicode(p
[0] | (sal_uInt32(p
[1]) << 8)));
238 aResult
[LockFileComponent::OOOUSERNAME
] = str
.makeStringAndClear();
245 void MSODocumentLockFile::RemoveFile()
247 std::unique_lock
aGuard(m_aMutex
);
249 // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic?
250 LockFileEntry aNewEntry
= GenerateOwnEntry();
251 LockFileEntry aFileData
= GetLockDataImpl(aGuard
);
253 if (aFileData
[LockFileComponent::OOOUSERNAME
] != aNewEntry
[LockFileComponent::OOOUSERNAME
])
254 throw css::io::IOException(); // not the owner, access denied
256 RemoveFileDirectly();
259 bool MSODocumentLockFile::IsMSOSupportedFileFormat(std::u16string_view aURL
)
261 INetURLObject aDocURL
= LockFileCommon::ResolveLinks(INetURLObject(aURL
));
262 const OUString sExt
= aDocURL
.GetFileExtension();
264 return isWordFormat(sExt
) || isExcelFormat(sExt
) || isPowerPointFormat(sExt
);
269 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */