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>
15 #include <com/sun/star/io/IOException.hpp>
16 #include <com/sun/star/io/XOutputStream.hpp>
17 #include <com/sun/star/io/XInputStream.hpp>
18 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
24 bool isWordFormat(const OUString
& sExt
)
26 return sExt
.equalsIgnoreAsciiCase("DOC") || sExt
.equalsIgnoreAsciiCase("DOCX")
27 || sExt
.equalsIgnoreAsciiCase("RTF") || sExt
.equalsIgnoreAsciiCase("ODT");
30 bool isExcelFormat(const OUString
& sExt
)
32 return //sExt.equalsIgnoreAsciiCase("XLS") || // MSO does not create lockfile for XLS
33 sExt
.equalsIgnoreAsciiCase("XLSX") || sExt
.equalsIgnoreAsciiCase("ODS");
36 bool isPowerPointFormat(const OUString
& sExt
)
38 return sExt
.equalsIgnoreAsciiCase("PPTX") || sExt
.equalsIgnoreAsciiCase("PPT")
39 || sExt
.equalsIgnoreAsciiCase("ODP");
42 // Need to generate different lock file name for MSO.
43 OUString
GenerateMSOLockFileURL(const OUString
& aOrigURL
)
45 INetURLObject aURL
= LockFileCommon::ResolveLinks(INetURLObject(aOrigURL
));
47 // For text documents MSO Word cuts some of the first characters of the file name
48 OUString sFileName
= aURL
.GetLastName();
49 const OUString sExt
= aURL
.GetFileExtension();
51 if (isWordFormat(sExt
))
53 const sal_Int32 nFileNameLength
= sFileName
.getLength() - sExt
.getLength() - 1;
54 if (nFileNameLength
>= 8)
55 sFileName
= sFileName
.copy(2);
56 else if (nFileNameLength
== 7)
57 sFileName
= sFileName
.copy(1);
59 aURL
.setName(OUStringConcatenation("~$" + sFileName
));
60 return aURL
.GetMainURL(INetURLObject::DecodeMechanism::NONE
);
65 MSODocumentLockFile::AppType
MSODocumentLockFile::getAppType(const OUString
& sOrigURL
)
67 AppType eResult
= AppType::PowerPoint
;
68 INetURLObject aDocURL
= LockFileCommon::ResolveLinks(INetURLObject(sOrigURL
));
69 const OUString sExt
= aDocURL
.GetFileExtension();
70 if (isWordFormat(sExt
))
71 eResult
= AppType::Word
;
72 else if (isExcelFormat(sExt
))
73 eResult
= AppType::Excel
;
78 MSODocumentLockFile::MSODocumentLockFile(const OUString
& aOrigURL
)
79 : GenDocumentLockFile(GenerateMSOLockFileURL(aOrigURL
))
80 , m_eAppType(getAppType(aOrigURL
))
84 MSODocumentLockFile::~MSODocumentLockFile() {}
86 void MSODocumentLockFile::WriteEntryToStream(
87 const LockFileEntry
& aEntry
, const css::uno::Reference
<css::io::XOutputStream
>& xOutput
)
89 ::osl::MutexGuard
aGuard(m_aMutex
);
91 // Reallocate the date with the right size, different lock file size for different components
92 int nLockFileSize
= m_eAppType
== AppType::Word
? MSO_WORD_LOCKFILE_SIZE
93 : MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE
;
94 css::uno::Sequence
<sal_Int8
> aData(nLockFileSize
);
95 auto pData
= aData
.getArray();
97 // Write out the user name's length as a single byte integer
98 // The maximum length is 52 in MSO, so we'll need to truncate the user name if it's longer
99 OUString aUserName
= aEntry
[LockFileComponent::OOOUSERNAME
];
101 pData
[nIndex
] = static_cast<sal_Int8
>(
102 std::min(aUserName
.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH
)));
104 if (aUserName
.getLength() > MSO_USERNAME_MAX_LENGTH
)
105 aUserName
= aUserName
.copy(0, MSO_USERNAME_MAX_LENGTH
);
107 // From the second position write out the user name using one byte characters.
109 for (int nChar
= 0; nChar
< aUserName
.getLength(); ++nChar
)
111 pData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
]);
115 // Fill up the remaining bytes with dummy data
119 while (nIndex
< MSO_USERNAME_MAX_LENGTH
+ 2)
121 pData
[nIndex
] = static_cast<sal_Int8
>(0);
125 case AppType::PowerPoint
:
126 pData
[nIndex
] = static_cast<sal_Int8
>(0);
130 while (nIndex
< MSO_USERNAME_MAX_LENGTH
+ 3)
132 pData
[nIndex
] = static_cast<sal_Int8
>(0x20);
138 // At the next position we have the user name's length again, but now as a 2 byte integer
139 pData
[nIndex
] = static_cast<sal_Int8
>(
140 std::min(aUserName
.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH
)));
145 // And the user name again with unicode characters
146 for (int nChar
= 0; nChar
< aUserName
.getLength(); ++nChar
)
148 pData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
] & 0xff);
150 pData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
] >> 8);
154 // Fill the remaining part with dummy bits
158 while (nIndex
< nLockFileSize
)
160 pData
[nIndex
] = static_cast<sal_Int8
>(0);
165 case AppType::PowerPoint
:
166 while (nIndex
< nLockFileSize
)
168 pData
[nIndex
] = static_cast<sal_Int8
>(0x20);
170 if (nIndex
< nLockFileSize
)
172 pData
[nIndex
] = static_cast<sal_Int8
>(0);
179 xOutput
->writeBytes(aData
);
182 css::uno::Reference
<css::io::XInputStream
> MSODocumentLockFile::OpenStream()
184 ::osl::MutexGuard
aGuard(m_aMutex
);
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::GetLockData()
195 ::osl::MutexGuard
aGuard(m_aMutex
);
197 LockFileEntry aResult
;
198 css::uno::Reference
<css::io::XInputStream
> xInput
= OpenStream();
200 throw css::uno::RuntimeException();
202 const sal_Int32 nBufLen
= 256;
203 css::uno::Sequence
<sal_Int8
> aBuf(nBufLen
);
204 const sal_Int32 nRead
= xInput
->readBytes(aBuf
, nBufLen
);
205 xInput
->closeInput();
208 // Reverse engineering of MS Office Owner Files format (MS Office 2016 tested).
209 // It starts with a single byte with name length, after which characters of username go
210 // in current Windows 8-bit codepage.
211 // For Word lockfiles, the name is followed by zero bytes up to position 54.
212 // For PowerPoint lockfiles, the name is followed by a single zero byte, and then 0x20
213 // bytes up to position 55.
214 // For Excel lockfiles, the name is followed by 0x20 bytes up to position 55.
215 // At those positions in each type of lockfile, a name length 2-byte word goes, followed
216 // by UTF-16-LE-encoded copy of username. Spaces or some garbage follow up to the end of
217 // the lockfile (total 162 bytes for Word, 165 bytes for Excel/PowerPoint).
218 // Apparently MS Office does not allow username to be longer than 52 characters (trying
219 // to enter more in its options dialog results in error messages stating this limit).
220 const int nACPLen
= aBuf
[0];
221 if (nACPLen
> 0 && nACPLen
<= 52) // skip wrong format
223 const sal_Int8
* pBuf
= aBuf
.getConstArray() + 54;
224 int nUTF16Len
= *pBuf
; // try Word position
225 // If UTF-16 length is 0x20, then ACP length is also less than maximal, which means
226 // that in Word lockfile case, at least two preceding bytes would be zero. Both
227 // Excel and PowerPoint lockfiles would have at least one of those bytes non-zero.
228 if (nUTF16Len
== 0x20 && (*(pBuf
- 1) != 0 || *(pBuf
- 2) != 0))
229 nUTF16Len
= *++pBuf
; // use Excel/PowerPoint position
231 if (nUTF16Len
> 0 && nUTF16Len
<= 52) // skip wrong format
233 OUStringBuffer
str(nUTF16Len
);
234 sal_uInt8
const* p
= reinterpret_cast<sal_uInt8
const*>(pBuf
+ 2);
235 for (int i
= 0; i
!= nUTF16Len
; ++i
)
237 str
.append(sal_Unicode(p
[0] | (sal_uInt32(p
[1]) << 8)));
240 aResult
[LockFileComponent::OOOUSERNAME
] = str
.makeStringAndClear();
247 void MSODocumentLockFile::RemoveFile()
249 ::osl::MutexGuard
aGuard(m_aMutex
);
251 // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic?
252 LockFileEntry aNewEntry
= GenerateOwnEntry();
253 LockFileEntry aFileData
= GetLockData();
255 if (aFileData
[LockFileComponent::OOOUSERNAME
] != aNewEntry
[LockFileComponent::OOOUSERNAME
])
256 throw css::io::IOException(); // not the owner, access denied
258 RemoveFileDirectly();
261 bool MSODocumentLockFile::IsMSOSupportedFileFormat(const OUString
& aURL
)
263 INetURLObject aDocURL
= LockFileCommon::ResolveLinks(INetURLObject(aURL
));
264 const OUString sExt
= aDocURL
.GetFileExtension();
266 return isWordFormat(sExt
) || isExcelFormat(sExt
) || isPowerPointFormat(sExt
);
271 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */