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("~$" + 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
);
96 // Write out the user name's length as a single byte integer
97 // The maximum length is 52 in MSO, so we'll need to truncate the user name if it's longer
98 OUString aUserName
= aEntry
[LockFileComponent::OOOUSERNAME
];
100 aData
[nIndex
] = static_cast<sal_Int8
>(
101 std::min(aUserName
.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH
)));
103 if (aUserName
.getLength() > MSO_USERNAME_MAX_LENGTH
)
104 aUserName
= aUserName
.copy(0, MSO_USERNAME_MAX_LENGTH
);
106 // From the second position write out the user name using one byte characters.
108 for (int nChar
= 0; nChar
< aUserName
.getLength(); ++nChar
)
110 aData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
]);
114 // Fill up the remaining bytes with dummy data
118 while (nIndex
< MSO_USERNAME_MAX_LENGTH
+ 2)
120 aData
[nIndex
] = static_cast<sal_Int8
>(0);
124 case AppType::PowerPoint
:
125 aData
[nIndex
] = static_cast<sal_Int8
>(0);
129 while (nIndex
< MSO_USERNAME_MAX_LENGTH
+ 3)
131 aData
[nIndex
] = static_cast<sal_Int8
>(0x20);
137 // At the next position we have the user name's length again, but now as a 2 byte integer
138 aData
[nIndex
] = static_cast<sal_Int8
>(
139 std::min(aUserName
.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH
)));
144 // And the user name again with unicode characters
145 for (int nChar
= 0; nChar
< aUserName
.getLength(); ++nChar
)
147 aData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
] & 0xff);
149 aData
[nIndex
] = static_cast<sal_Int8
>(aUserName
[nChar
] >> 8);
153 // Fill the remaining part with dummy bits
157 while (nIndex
< nLockFileSize
)
159 aData
[nIndex
] = static_cast<sal_Int8
>(0);
164 case AppType::PowerPoint
:
165 while (nIndex
< nLockFileSize
)
167 aData
[nIndex
] = static_cast<sal_Int8
>(0x20);
169 if (nIndex
< nLockFileSize
)
171 aData
[nIndex
] = static_cast<sal_Int8
>(0);
178 xOutput
->writeBytes(aData
);
181 css::uno::Reference
<css::io::XInputStream
> MSODocumentLockFile::OpenStream()
183 ::osl::MutexGuard
aGuard(m_aMutex
);
185 css::uno::Reference
<css::ucb::XCommandEnvironment
> xEnv
;
186 ::ucbhelper::Content
aSourceContent(GetURL(), xEnv
, comphelper::getProcessComponentContext());
188 // the file can be opened readonly, no locking will be done
189 return aSourceContent
.openStreamNoLock();
192 LockFileEntry
MSODocumentLockFile::GetLockData()
194 ::osl::MutexGuard
aGuard(m_aMutex
);
196 LockFileEntry aResult
;
197 css::uno::Reference
<css::io::XInputStream
> xInput
= OpenStream();
199 throw css::uno::RuntimeException();
201 const sal_Int32 nBufLen
= 256;
202 css::uno::Sequence
<sal_Int8
> aBuf(nBufLen
);
203 const sal_Int32 nRead
= xInput
->readBytes(aBuf
, nBufLen
);
204 xInput
->closeInput();
207 // Reverse engineering of MS Office Owner Files format (MS Office 2016 tested).
208 // It starts with a single byte with name length, after which characters of username go
209 // in current Windows 8-bit codepage.
210 // For Word lockfiles, the name is followed by zero bytes up to position 54.
211 // For PowerPoint lockfiles, the name is followed by a single zero byte, and then 0x20
212 // bytes up to position 55.
213 // For Excel lockfiles, the name is followed by 0x20 bytes up to position 55.
214 // At those positions in each type of lockfile, a name length 2-byte word goes, followed
215 // by UTF-16-LE-encoded copy of username. Spaces or some garbage follow up to the end of
216 // the lockfile (total 162 bytes for Word, 165 bytes for Excel/PowerPoint).
217 // Apparently MS Office does not allow username to be longer than 52 characters (trying
218 // to enter more in its options dialog results in error messages stating this limit).
219 const int nACPLen
= aBuf
[0];
220 if (nACPLen
> 0 && nACPLen
<= 52) // skip wrong format
222 const sal_Int8
* pBuf
= aBuf
.getConstArray() + 54;
223 int nUTF16Len
= *pBuf
; // try Word position
224 // If UTF-16 length is 0x20, then ACP length is also less than maximal, which means
225 // that in Word lockfile case, at least two preceding bytes would be zero. Both
226 // Excel and PowerPoint lockfiles would have at least one of those bytes non-zero.
227 if (nUTF16Len
== 0x20 && (*(pBuf
- 1) != 0 || *(pBuf
- 2) != 0))
228 nUTF16Len
= *++pBuf
; // use Excel/PowerPoint position
230 if (nUTF16Len
> 0 && nUTF16Len
<= 52) // skip wrong format
232 OUStringBuffer
str(nUTF16Len
);
233 sal_uInt8
const* p
= reinterpret_cast<sal_uInt8
const*>(pBuf
+ 2);
234 for (int i
= 0; i
!= nUTF16Len
; ++i
)
236 str
.append(sal_Unicode(p
[0] | (sal_uInt32(p
[1]) << 8)));
239 aResult
[LockFileComponent::OOOUSERNAME
] = str
.makeStringAndClear();
246 void MSODocumentLockFile::RemoveFile()
248 ::osl::MutexGuard
aGuard(m_aMutex
);
250 // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic?
251 LockFileEntry aNewEntry
= GenerateOwnEntry();
252 LockFileEntry aFileData
= GetLockData();
254 if (aFileData
[LockFileComponent::OOOUSERNAME
] != aNewEntry
[LockFileComponent::OOOUSERNAME
])
255 throw css::io::IOException(); // not the owner, access denied
257 RemoveFileDirectly();
260 bool MSODocumentLockFile::IsMSOSupportedFileFormat(const OUString
& aURL
)
262 INetURLObject aDocURL
= LockFileCommon::ResolveLinks(INetURLObject(aURL
));
263 const OUString sExt
= aDocURL
.GetFileExtension();
265 return isWordFormat(sExt
) || isExcelFormat(sExt
) || isPowerPointFormat(sExt
);
270 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */