1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  */
9 
10 #include <sal/config.h>
11 #include <rtl/ustring.hxx>
12 #include <rtl/bootstrap.hxx>
13 #include <sal/log.hxx>
14 #include <osl/file.hxx>
15 #include <comphelper/backupfilehelper.hxx>
16 #include <comphelper/DirectoryHelper.hxx>
17 #include <rtl/crc.h>
18 #include <algorithm>
19 #include <deque>
20 #include <memory>
21 #include <string_view>
22 #include <vector>
23 #include <zlib.h>
24 
25 #include <comphelper/processfactory.hxx>
26 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
27 #include <com/sun/star/ucb/CommandAbortedException.hpp>
28 #include <com/sun/star/ucb/CommandFailedException.hpp>
29 #include <com/sun/star/uno/Sequence.hxx>
30 #include <com/sun/star/uno/Reference.hxx>
31 #include <com/sun/star/deployment/DeploymentException.hpp>
32 #include <com/sun/star/deployment/ExtensionManager.hpp>
33 #include <com/sun/star/xml/dom/XDocumentBuilder.hpp>
34 #include <com/sun/star/xml/dom/DocumentBuilder.hpp>
35 #include <com/sun/star/xml/dom/XElement.hpp>
36 #include <com/sun/star/xml/dom/XNodeList.hpp>
37 #include <com/sun/star/xml/dom/XText.hpp>
38 #include <com/sun/star/xml/sax/XSAXSerializable.hpp>
39 #include <com/sun/star/xml/sax/Writer.hpp>
40 #include <com/sun/star/xml/sax/XWriter.hpp>
41 #include <com/sun/star/io/XStream.hpp>
42 #include <com/sun/star/io/TempFile.hpp>
43 #include <com/sun/star/io/XOutputStream.hpp>
44 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
45 #include <com/sun/star/beans/XPropertySet.hpp>
46 #include <cppuhelper/exc_hlp.hxx>
47 
48 using namespace comphelper;
49 using namespace css;
50 using namespace css::xml::dom;
51 
52 const sal_uInt32 BACKUP_FILE_HELPER_BLOCK_SIZE = 16384;
53 
54 namespace
55 {
56     typedef std::shared_ptr< osl::File > FileSharedPtr;
57 
createCrc32(FileSharedPtr const & rCandidate,sal_uInt32 nOffset)58     sal_uInt32 createCrc32(FileSharedPtr const & rCandidate, sal_uInt32 nOffset)
59     {
60         sal_uInt32 nCrc32(0);
61 
62         if (rCandidate && osl::File::E_None == rCandidate->open(osl_File_OpenFlag_Read))
63         {
64             sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE];
65             sal_uInt64 nBytesTransfer(0);
66             sal_uInt64 nSize(0);
67 
68             rCandidate->getSize(nSize);
69 
70             // set offset in source file - should be zero due to crc32 should
71             // only be needed to be created for new entries, gets loaded with old
72             // ones
73             if (osl::File::E_None == rCandidate->setPos(osl_Pos_Absolut, sal_Int64(nOffset)))
74             {
75                 while (nSize != 0)
76                 {
77                     const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE)));
78 
79                     if (osl::File::E_None == rCandidate->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) && nBytesTransfer == nToTransfer)
80                     {
81                         // add to crc and reduce size
82                         nCrc32 = rtl_crc32(nCrc32, static_cast<void*>(aArray), static_cast<sal_uInt32>(nBytesTransfer));
83                         nSize -= nToTransfer;
84                     }
85                     else
86                     {
87                         // error - reset to zero again
88                         nSize = nCrc32 = 0;
89                     }
90                 }
91             }
92 
93             rCandidate->close();
94         }
95 
96         return nCrc32;
97     }
98 
read_sal_uInt32(FileSharedPtr const & rFile,sal_uInt32 & rTarget)99     bool read_sal_uInt32(FileSharedPtr const & rFile, sal_uInt32& rTarget)
100     {
101         sal_uInt8 aArray[4];
102         sal_uInt64 nBaseRead(0);
103 
104         // read rTarget
105         if (osl::File::E_None == rFile->read(static_cast<void*>(aArray), 4, nBaseRead) && 4 == nBaseRead)
106         {
107             rTarget = (sal_uInt32(aArray[0]) << 24) + (sal_uInt32(aArray[1]) << 16) + (sal_uInt32(aArray[2]) << 8) + sal_uInt32(aArray[3]);
108             return true;
109         }
110 
111         return false;
112     }
113 
write_sal_uInt32(oslFileHandle & rHandle,sal_uInt32 nSource)114     bool write_sal_uInt32(oslFileHandle& rHandle, sal_uInt32 nSource)
115     {
116         sal_uInt8 aArray[4];
117         sal_uInt64 nBaseWritten(0);
118 
119         // write nSource
120         aArray[0] = sal_uInt8((nSource & 0xff000000) >> 24);
121         aArray[1] = sal_uInt8((nSource & 0x00ff0000) >> 16);
122         aArray[2] = sal_uInt8((nSource & 0x0000ff00) >> 8);
123         aArray[3] = sal_uInt8(nSource & 0x000000ff);
124 
125         return osl_File_E_None == osl_writeFile(rHandle, static_cast<const void*>(aArray), 4, &nBaseWritten) && 4 == nBaseWritten;
126     }
127 
read_OString(FileSharedPtr const & rFile,OString & rTarget)128     bool read_OString(FileSharedPtr const & rFile, OString& rTarget)
129     {
130         sal_uInt32 nLength(0);
131 
132         if (!read_sal_uInt32(rFile, nLength))
133         {
134             return false;
135         }
136 
137         sal_uInt64 nPos;
138         if (osl::File::E_None != rFile->getPos(nPos))
139             return false;
140 
141         sal_uInt64 nSize;
142         if (osl::File::E_None != rFile->getSize(nSize))
143             return false;
144 
145         const auto nRemainingSize = nSize - nPos;
146         if (nLength > nRemainingSize)
147             return false;
148 
149         std::vector<char> aTarget(nLength);
150         sal_uInt64 nBaseRead(0);
151 
152         // read rTarget
153         if (osl::File::E_None == rFile->read(static_cast<void*>(aTarget.data()), nLength, nBaseRead) && nLength == nBaseRead)
154         {
155             rTarget = OString(aTarget.data(), static_cast<sal_Int32>(nBaseRead));
156             return true;
157         }
158 
159         return false;
160     }
161 
write_OString(oslFileHandle & rHandle,const OString & rSource)162     bool write_OString(oslFileHandle& rHandle, const OString& rSource)
163     {
164         const sal_uInt32 nLength(rSource.getLength());
165 
166         if (!write_sal_uInt32(rHandle, nLength))
167         {
168             return false;
169         }
170 
171         sal_uInt64 nBaseWritten(0);
172 
173         return osl_File_E_None == osl_writeFile(rHandle, static_cast<const void*>(rSource.getStr()), nLength, &nBaseWritten) && nLength == nBaseWritten;
174     }
175 
createFileURL(std::u16string_view rURL,std::u16string_view rName,std::u16string_view rExt)176     OUString createFileURL(
177         std::u16string_view rURL, std::u16string_view rName, std::u16string_view rExt)
178     {
179         OUString aRetval;
180 
181         if (!rURL.empty() && !rName.empty())
182         {
183             aRetval = OUString::Concat(rURL) + "/" + rName;
184 
185             if (!rExt.empty())
186             {
187                 aRetval += OUString::Concat(".") + rExt;
188             }
189         }
190 
191         return aRetval;
192     }
193 
createPackURL(std::u16string_view rURL,std::u16string_view rName)194     OUString createPackURL(std::u16string_view rURL, std::u16string_view rName)
195     {
196         OUString aRetval;
197 
198         if (!rURL.empty() && !rName.empty())
199         {
200             aRetval = OUString::Concat(rURL) + "/" + rName + ".pack";
201         }
202 
203         return aRetval;
204     }
205 }
206 
207 namespace
208 {
209     enum PackageRepository { USER, SHARED, BUNDLED };
210 
211     class ExtensionInfoEntry
212     {
213     private:
214         OString             maName;         // extension name
215         PackageRepository   maRepository;   // user|shared|bundled
216         bool                mbEnabled;      // state
217 
218     public:
ExtensionInfoEntry()219         ExtensionInfoEntry()
220         :   maName(),
221             maRepository(USER),
222             mbEnabled(false)
223         {
224         }
225 
ExtensionInfoEntry(const OString & rName,bool bEnabled)226         ExtensionInfoEntry(const OString& rName, bool bEnabled)
227         :   maName(rName),
228             maRepository(USER),
229             mbEnabled(bEnabled)
230         {
231         }
232 
ExtensionInfoEntry(const uno::Reference<deployment::XPackage> & rxPackage)233         ExtensionInfoEntry(const uno::Reference< deployment::XPackage >& rxPackage)
234         :   maName(OUStringToOString(rxPackage->getName(), RTL_TEXTENCODING_ASCII_US)),
235             maRepository(USER),
236             mbEnabled(false)
237         {
238             // check maRepository
239             const OString aRepName(OUStringToOString(rxPackage->getRepositoryName(), RTL_TEXTENCODING_ASCII_US));
240 
241             if (aRepName == "shared")
242             {
243                 maRepository = SHARED;
244             }
245             else if (aRepName == "bundled")
246             {
247                 maRepository = BUNDLED;
248             }
249 
250             // check mbEnabled
251             const beans::Optional< beans::Ambiguous< sal_Bool > > option(
252                 rxPackage->isRegistered(uno::Reference< task::XAbortChannel >(),
253                 uno::Reference< ucb::XCommandEnvironment >()));
254 
255             if (option.IsPresent)
256             {
257                 ::beans::Ambiguous< sal_Bool > const& reg = option.Value;
258 
259                 if (!reg.IsAmbiguous)
260                 {
261                     mbEnabled = reg.Value;
262                 }
263             }
264         }
265 
isSameExtension(const ExtensionInfoEntry & rComp) const266         bool isSameExtension(const ExtensionInfoEntry& rComp) const
267         {
268             return (maRepository == rComp.maRepository && maName == rComp.maName);
269         }
270 
operator <(const ExtensionInfoEntry & rComp) const271         bool operator<(const ExtensionInfoEntry& rComp) const
272         {
273             if (maRepository == rComp.maRepository)
274             {
275                 if (maName == rComp.maName)
276                 {
277                     return mbEnabled < rComp.mbEnabled;
278                 }
279                 else
280                 {
281                     return 0 > maName.compareTo(rComp.maName);
282                 }
283             }
284             else
285             {
286                 return maRepository < rComp.maRepository;
287             }
288         }
289 
read_entry(FileSharedPtr const & rFile)290         bool read_entry(FileSharedPtr const & rFile)
291         {
292             // read maName
293             if (!read_OString(rFile, maName))
294             {
295                 return false;
296             }
297 
298             // read maRepository
299             sal_uInt32 nState(0);
300 
301             if (read_sal_uInt32(rFile, nState))
302             {
303                 maRepository = static_cast< PackageRepository >(nState);
304             }
305             else
306             {
307                 return false;
308             }
309 
310             // read mbEnabled
311             if (read_sal_uInt32(rFile, nState))
312             {
313                 mbEnabled = static_cast< bool >(nState);
314             }
315             else
316             {
317                 return false;
318             }
319 
320             return true;
321         }
322 
write_entry(oslFileHandle & rHandle) const323         bool write_entry(oslFileHandle& rHandle) const
324         {
325             // write maName;
326             if (!write_OString(rHandle, maName))
327             {
328                 return false;
329             }
330 
331             // write maRepository
332             sal_uInt32 nState(maRepository);
333 
334             if (!write_sal_uInt32(rHandle, nState))
335             {
336                 return false;
337             }
338 
339             // write mbEnabled
340             nState = static_cast< sal_uInt32 >(mbEnabled);
341 
342             return write_sal_uInt32(rHandle, nState);
343         }
344 
getName() const345         const OString& getName() const
346         {
347             return maName;
348         }
349 
isEnabled() const350         bool isEnabled() const
351         {
352             return mbEnabled;
353         }
354     };
355 
356     typedef std::vector< ExtensionInfoEntry > ExtensionInfoEntryVector;
357 
358     constexpr OUStringLiteral gaRegPath { u"/registry/com.sun.star.comp.deployment.bundle.PackageRegistryBackend/backenddb.xml" };
359 
360     class ExtensionInfo
361     {
362     private:
363         ExtensionInfoEntryVector    maEntries;
364 
365     public:
ExtensionInfo()366         ExtensionInfo()
367             : maEntries()
368         {
369         }
370 
getExtensionInfoEntryVector() const371         const ExtensionInfoEntryVector& getExtensionInfoEntryVector() const
372         {
373             return maEntries;
374         }
375 
reset()376         void reset()
377         {
378             // clear all data
379             maEntries.clear();
380         }
381 
createUsingXExtensionManager()382         void createUsingXExtensionManager()
383         {
384             // clear all data
385             reset();
386 
387             // create content from current extension configuration
388             uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages;
389             uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
390             uno::Reference< deployment::XExtensionManager > m_xExtensionManager = deployment::ExtensionManager::get(xContext);
391 
392             try
393             {
394                 xAllPackages = m_xExtensionManager->getAllExtensions(uno::Reference< task::XAbortChannel >(),
395                     uno::Reference< ucb::XCommandEnvironment >());
396             }
397             catch (const deployment::DeploymentException &)
398             {
399                 return;
400             }
401             catch (const ucb::CommandFailedException &)
402             {
403                 return;
404             }
405             catch (const ucb::CommandAbortedException &)
406             {
407                 return;
408             }
409             catch (const lang::IllegalArgumentException & e)
410             {
411                 css::uno::Any anyEx = cppu::getCaughtException();
412                 throw css::lang::WrappedTargetRuntimeException( e.Message,
413                                 e.Context, anyEx );
414             }
415 
416             for (const uno::Sequence< uno::Reference< deployment::XPackage > > & xPackageList : std::as_const(xAllPackages))
417             {
418                 for (const uno::Reference< deployment::XPackage > & xPackage : xPackageList)
419                 {
420                     if (xPackage.is())
421                     {
422                         maEntries.emplace_back(xPackage);
423                     }
424                 }
425             }
426 
427             if (!maEntries.empty())
428             {
429                 // sort the list
430                 std::sort(maEntries.begin(), maEntries.end());
431             }
432         }
433 
434     private:
visitNodesXMLRead(const uno::Reference<xml::dom::XElement> & rElement)435         void visitNodesXMLRead(const uno::Reference< xml::dom::XElement >& rElement)
436         {
437             if (!rElement.is())
438                 return;
439 
440             const OUString aTagName(rElement->getTagName());
441 
442             if (aTagName == "extension")
443             {
444                 OUString aAttrUrl(rElement->getAttribute("url"));
445                 const OUString aAttrRevoked(rElement->getAttribute("revoked"));
446 
447                 if (!aAttrUrl.isEmpty())
448                 {
449                     const sal_Int32 nIndex(aAttrUrl.lastIndexOf('/'));
450 
451                     if (nIndex > 0 && aAttrUrl.getLength() > nIndex + 1)
452                     {
453                         aAttrUrl = aAttrUrl.copy(nIndex + 1);
454                     }
455 
456                     const bool bEnabled(aAttrRevoked.isEmpty() || !aAttrRevoked.toBoolean());
457                     maEntries.emplace_back(
458                             OUStringToOString(aAttrUrl, RTL_TEXTENCODING_ASCII_US),
459                             bEnabled);
460                 }
461             }
462             else
463             {
464                 uno::Reference< xml::dom::XNodeList > aList = rElement->getChildNodes();
465 
466                 if (aList.is())
467                 {
468                     const sal_Int32 nLength(aList->getLength());
469 
470                     for (sal_Int32 a(0); a < nLength; a++)
471                     {
472                         const uno::Reference< xml::dom::XElement > aChild(aList->item(a), uno::UNO_QUERY);
473 
474                         if (aChild.is())
475                         {
476                             visitNodesXMLRead(aChild);
477                         }
478                     }
479                 }
480             }
481         }
482 
483     public:
createUserExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL)484         void createUserExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL)
485         {
486             const OUString aPath(
487                 OUString::Concat(rUserConfigWorkURL) + "/uno_packages/cache" + gaRegPath);
488             createExtensionRegistryEntriesFromXML(aPath);
489         }
490 
createSharedExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL)491         void createSharedExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL)
492         {
493             const OUString aPath(
494                 OUString::Concat(rUserConfigWorkURL) + "/extensions/shared" + gaRegPath);
495             createExtensionRegistryEntriesFromXML(aPath);
496         }
497 
createBundledExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL)498         void createBundledExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL)
499         {
500             const OUString aPath(
501                 OUString::Concat(rUserConfigWorkURL) + "/extensions/bundled" + gaRegPath);
502             createExtensionRegistryEntriesFromXML(aPath);
503         }
504 
505 
createExtensionRegistryEntriesFromXML(const OUString & aPath)506         void createExtensionRegistryEntriesFromXML(const OUString& aPath)
507         {
508             if (DirectoryHelper::fileExists(aPath))
509             {
510                 uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
511                 uno::Reference< xml::dom::XDocumentBuilder > xBuilder(xml::dom::DocumentBuilder::create(xContext));
512                 uno::Reference< xml::dom::XDocument > aDocument = xBuilder->parseURI(aPath);
513 
514                 if (aDocument.is())
515                 {
516                     visitNodesXMLRead(aDocument->getDocumentElement());
517                 }
518             }
519 
520             if (!maEntries.empty())
521             {
522                 // sort the list
523                 std::sort(maEntries.begin(), maEntries.end());
524             }
525         }
526 
527     private:
visitNodesXMLChange(const OUString & rTagToSearch,const uno::Reference<xml::dom::XElement> & rElement,const ExtensionInfoEntryVector & rToBeEnabled,const ExtensionInfoEntryVector & rToBeDisabled)528         static bool visitNodesXMLChange(
529             const OUString& rTagToSearch,
530             const uno::Reference< xml::dom::XElement >& rElement,
531             const ExtensionInfoEntryVector& rToBeEnabled,
532             const ExtensionInfoEntryVector& rToBeDisabled)
533         {
534             bool bChanged(false);
535 
536             if (rElement.is())
537             {
538                 const OUString aTagName(rElement->getTagName());
539 
540                 if (aTagName == rTagToSearch)
541                 {
542                     const OString aAttrUrl(OUStringToOString(rElement->getAttribute("url"), RTL_TEXTENCODING_ASCII_US));
543                     const OUString aAttrRevoked(rElement->getAttribute("revoked"));
544                     const bool bEnabled(aAttrRevoked.isEmpty() || !aAttrRevoked.toBoolean());
545 
546                     if (!aAttrUrl.isEmpty())
547                     {
548                         for (const auto& enable : rToBeEnabled)
549                         {
550                             if (-1 != aAttrUrl.indexOf(enable.getName()))
551                             {
552                                 if (!bEnabled)
553                                 {
554                                     // needs to be enabled
555                                     rElement->removeAttribute("revoked");
556                                     bChanged = true;
557                                 }
558                             }
559                         }
560 
561                         for (const auto& disable : rToBeDisabled)
562                         {
563                             if (-1 != aAttrUrl.indexOf(disable.getName()))
564                             {
565                                 if (bEnabled)
566                                 {
567                                     // needs to be disabled
568                                     rElement->setAttribute("revoked", "true");
569                                     bChanged = true;
570                                 }
571                             }
572                         }
573                     }
574                 }
575                 else
576                 {
577                     uno::Reference< xml::dom::XNodeList > aList = rElement->getChildNodes();
578 
579                     if (aList.is())
580                     {
581                         const sal_Int32 nLength(aList->getLength());
582 
583                         for (sal_Int32 a(0); a < nLength; a++)
584                         {
585                             const uno::Reference< xml::dom::XElement > aChild(aList->item(a), uno::UNO_QUERY);
586 
587                             if (aChild.is())
588                             {
589                                 bChanged |= visitNodesXMLChange(
590                                     rTagToSearch,
591                                     aChild,
592                                     rToBeEnabled,
593                                     rToBeDisabled);
594                             }
595                         }
596                     }
597                 }
598             }
599 
600             return bChanged;
601         }
602 
visitNodesXMLChangeOneCase(const OUString & rUnoPackagReg,const OUString & rTagToSearch,const ExtensionInfoEntryVector & rToBeEnabled,const ExtensionInfoEntryVector & rToBeDisabled)603         static void visitNodesXMLChangeOneCase(
604             const OUString& rUnoPackagReg,
605             const OUString& rTagToSearch,
606             const ExtensionInfoEntryVector& rToBeEnabled,
607             const ExtensionInfoEntryVector& rToBeDisabled)
608         {
609             if (!DirectoryHelper::fileExists(rUnoPackagReg))
610                 return;
611 
612             uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
613             uno::Reference< xml::dom::XDocumentBuilder > xBuilder = xml::dom::DocumentBuilder::create(xContext);
614             uno::Reference< xml::dom::XDocument > aDocument = xBuilder->parseURI(rUnoPackagReg);
615 
616             if (!aDocument.is())
617                 return;
618 
619             if (!visitNodesXMLChange(
620                 rTagToSearch,
621                 aDocument->getDocumentElement(),
622                 rToBeEnabled,
623                 rToBeDisabled))
624                 return;
625 
626             // did change - write back
627             uno::Reference< xml::sax::XSAXSerializable > xSerializer(aDocument, uno::UNO_QUERY);
628 
629             if (!xSerializer.is())
630                 return;
631 
632             // create a SAXWriter
633             uno::Reference< xml::sax::XWriter > const xSaxWriter = xml::sax::Writer::create(xContext);
634             uno::Reference< io::XStream > xTempFile = io::TempFile::create(xContext);
635             uno::Reference< io::XOutputStream > xOutStrm = xTempFile->getOutputStream();
636 
637             // set output stream and do the serialization
638             xSaxWriter->setOutputStream(xOutStrm);
639             xSerializer->serialize(xSaxWriter, uno::Sequence< beans::StringPair >());
640 
641             // get URL from temp file
642             uno::Reference < beans::XPropertySet > xTempFileProps(xTempFile, uno::UNO_QUERY);
643             uno::Any aUrl = xTempFileProps->getPropertyValue("Uri");
644             OUString aTempURL;
645             aUrl >>= aTempURL;
646 
647             // copy back file
648             if (aTempURL.isEmpty() || !DirectoryHelper::fileExists(aTempURL))
649                 return;
650 
651             if (DirectoryHelper::fileExists(rUnoPackagReg))
652             {
653                 osl::File::remove(rUnoPackagReg);
654             }
655 
656 #if OSL_DEBUG_LEVEL > 1
657             SAL_WARN_IF(osl::FileBase::E_None != osl::File::move(aTempURL, rUnoPackagReg), "comphelper.backupfilehelper", "could not copy back modified Extension configuration file");
658 #else
659             osl::File::move(aTempURL, rUnoPackagReg);
660 #endif
661         }
662 
663     public:
changeEnableDisableStateInXML(std::u16string_view rUserConfigWorkURL,const ExtensionInfoEntryVector & rToBeEnabled,const ExtensionInfoEntryVector & rToBeDisabled)664         static void changeEnableDisableStateInXML(
665             std::u16string_view rUserConfigWorkURL,
666             const ExtensionInfoEntryVector& rToBeEnabled,
667             const ExtensionInfoEntryVector& rToBeDisabled)
668         {
669             static const OUStringLiteral aRegPathFront(u"/uno_packages/cache/registry/com.sun.star.comp.deployment.");
670             static const OUStringLiteral aRegPathBack(u".PackageRegistryBackend/backenddb.xml");
671             // first appearance to check
672             {
673                 const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "bundle" + aRegPathBack);
674 
675                 visitNodesXMLChangeOneCase(
676                     aUnoPackagReg,
677                     "extension",
678                     rToBeEnabled,
679                     rToBeDisabled);
680             }
681 
682             // second appearance to check
683             {
684                 const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "configuration" + aRegPathBack);
685 
686                 visitNodesXMLChangeOneCase(
687                     aUnoPackagReg,
688                     "configuration",
689                     rToBeEnabled,
690                     rToBeDisabled);
691             }
692 
693             // third appearance to check
694             {
695                 const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "script" + aRegPathBack);
696 
697                 visitNodesXMLChangeOneCase(
698                     aUnoPackagReg,
699                     "script",
700                     rToBeEnabled,
701                     rToBeDisabled);
702             }
703         }
704 
read_entries(FileSharedPtr const & rFile)705         bool read_entries(FileSharedPtr const & rFile)
706         {
707             // read NumExtensionEntries
708             sal_uInt32 nExtEntries(0);
709 
710             if (!read_sal_uInt32(rFile, nExtEntries))
711             {
712                 return false;
713             }
714 
715             // coverity#1373663 Untrusted loop bound, check file size
716             // isn't utterly broken
717             sal_uInt64 nFileSize(0);
718             rFile->getSize(nFileSize);
719             if (nFileSize < nExtEntries)
720                 return false;
721 
722             for (sal_uInt32 a(0); a < nExtEntries; a++)
723             {
724                 ExtensionInfoEntry aNewEntry;
725 
726                 if (aNewEntry.read_entry(rFile))
727                 {
728                     maEntries.push_back(aNewEntry);
729                 }
730                 else
731                 {
732                     return false;
733                 }
734             }
735 
736             return true;
737         }
738 
write_entries(oslFileHandle & rHandle) const739         bool write_entries(oslFileHandle& rHandle) const
740         {
741             const sal_uInt32 nExtEntries(maEntries.size());
742 
743             if (!write_sal_uInt32(rHandle, nExtEntries))
744             {
745                 return false;
746             }
747 
748             for (const auto& a : maEntries)
749             {
750                 if (!a.write_entry(rHandle))
751                 {
752                     return false;
753                 }
754             }
755 
756             return true;
757         }
758 
createTempFile(OUString & rTempFileName)759         bool createTempFile(OUString& rTempFileName)
760         {
761             oslFileHandle aHandle;
762             bool bRetval(false);
763 
764             // create current configuration
765             if (maEntries.empty())
766             {
767                 createUsingXExtensionManager();
768             }
769 
770             // open target temp file and write current configuration to it - it exists until deleted
771             if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &rTempFileName))
772             {
773                 bRetval = write_entries(aHandle);
774 
775                 // close temp file - it exists until deleted
776                 osl_closeFile(aHandle);
777             }
778 
779             return bRetval;
780         }
781 
areThereEnabledExtensions() const782         bool areThereEnabledExtensions() const
783         {
784             for (const auto& a : maEntries)
785             {
786                 if (a.isEnabled())
787                 {
788                     return true;
789                 }
790             }
791 
792             return false;
793         }
794     };
795 }
796 
797 namespace
798 {
799     class PackedFileEntry
800     {
801     private:
802         sal_uInt32          mnFullFileSize;     // size in bytes of unpacked original file
803         sal_uInt32          mnPackFileSize;     // size in bytes in file backup package (smaller if compressed, same if not)
804         sal_uInt32          mnOffset;           // offset in File (zero identifies new file)
805         sal_uInt32          mnCrc32;            // checksum
806         FileSharedPtr       maFile;             // file where to find the data (at offset)
807         bool const          mbDoCompress;       // flag if this file is scheduled to be compressed when written
808 
copy_content_straight(oslFileHandle & rTargetHandle)809         bool copy_content_straight(oslFileHandle& rTargetHandle)
810         {
811             if (maFile && osl::File::E_None == maFile->open(osl_File_OpenFlag_Read))
812             {
813                 sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE];
814                 sal_uInt64 nBytesTransfer(0);
815                 sal_uInt64 nSize(getPackFileSize());
816 
817                 // set offset in source file - when this is zero, a new file is to be added
818                 if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset())))
819                 {
820                     while (nSize != 0)
821                     {
822                         const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE)));
823 
824                         if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer)
825                         {
826                             break;
827                         }
828 
829                         if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aArray), nToTransfer, &nBytesTransfer) || nBytesTransfer != nToTransfer)
830                         {
831                             break;
832                         }
833 
834                         nSize -= nToTransfer;
835                     }
836                 }
837 
838                 maFile->close();
839                 return (0 == nSize);
840             }
841 
842             return false;
843         }
844 
copy_content_compress(oslFileHandle & rTargetHandle)845         bool copy_content_compress(oslFileHandle& rTargetHandle)
846         {
847             if (maFile && osl::File::E_None == maFile->open(osl_File_OpenFlag_Read))
848             {
849                 sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE];
850                 sal_uInt8 aBuffer[BACKUP_FILE_HELPER_BLOCK_SIZE];
851                 sal_uInt64 nBytesTransfer(0);
852                 sal_uInt64 nSize(getPackFileSize());
853                 z_stream zstream;
854                 memset(&zstream, 0, sizeof(zstream));
855 
856                 if (Z_OK == deflateInit(&zstream, Z_BEST_COMPRESSION))
857                 {
858                     // set offset in source file - when this is zero, a new file is to be added
859                     if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset())))
860                     {
861                         bool bOkay(true);
862 
863                         while (bOkay && nSize != 0)
864                         {
865                             const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE)));
866 
867                             if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer)
868                             {
869                                 break;
870                             }
871 
872                             zstream.avail_in = nToTransfer;
873                             zstream.next_in = reinterpret_cast<unsigned char*>(aArray);
874 
875                             do {
876                                 zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE;
877                                 zstream.next_out = reinterpret_cast<unsigned char*>(aBuffer);
878 #if !defined Z_PREFIX
879                                 const sal_Int64 nRetval(deflate(&zstream, nSize == nToTransfer ? Z_FINISH : Z_NO_FLUSH));
880 #else
881                                 const sal_Int64 nRetval(z_deflate(&zstream, nSize == nToTransfer ? Z_FINISH : Z_NO_FLUSH));
882 #endif
883                                 if (Z_STREAM_ERROR == nRetval)
884                                 {
885                                     bOkay = false;
886                                 }
887                                 else
888                                 {
889                                     const sal_uInt64 nAvailable(BACKUP_FILE_HELPER_BLOCK_SIZE - zstream.avail_out);
890 
891                                     if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aBuffer), nAvailable, &nBytesTransfer) || nBytesTransfer != nAvailable)
892                                     {
893                                         bOkay = false;
894                                     }
895                                 }
896                             } while (bOkay && 0 == zstream.avail_out);
897 
898                             if (!bOkay)
899                             {
900                                 break;
901                             }
902 
903                             nSize -= nToTransfer;
904                         }
905 
906 #if !defined Z_PREFIX
907                         deflateEnd(&zstream);
908 #else
909                         z_deflateEnd(&zstream);
910 #endif
911                     }
912                 }
913 
914                 maFile->close();
915 
916                 // get compressed size and add to entry
917                 if (mnFullFileSize == mnPackFileSize && mnFullFileSize == zstream.total_in)
918                 {
919                     mnPackFileSize = zstream.total_out;
920                 }
921 
922                 return (0 == nSize);
923             }
924 
925             return false;
926         }
927 
copy_content_uncompress(oslFileHandle & rTargetHandle)928         bool copy_content_uncompress(oslFileHandle& rTargetHandle)
929         {
930             if (maFile && osl::File::E_None == maFile->open(osl_File_OpenFlag_Read))
931             {
932                 sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE];
933                 sal_uInt8 aBuffer[BACKUP_FILE_HELPER_BLOCK_SIZE];
934                 sal_uInt64 nBytesTransfer(0);
935                 sal_uInt64 nSize(getPackFileSize());
936                 z_stream zstream;
937                 memset(&zstream, 0, sizeof(zstream));
938 
939                 if (Z_OK == inflateInit(&zstream))
940                 {
941                     // set offset in source file - when this is zero, a new file is to be added
942                     if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset())))
943                     {
944                         bool bOkay(true);
945 
946                         while (bOkay && nSize != 0)
947                         {
948                             const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE)));
949 
950                             if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer)
951                             {
952                                 break;
953                             }
954 
955                             zstream.avail_in = nToTransfer;
956                             zstream.next_in = reinterpret_cast<unsigned char*>(aArray);
957 
958                             do {
959                                 zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE;
960                                 zstream.next_out = reinterpret_cast<unsigned char*>(aBuffer);
961 #if !defined Z_PREFIX
962                                 const sal_Int64 nRetval(inflate(&zstream, Z_NO_FLUSH));
963 #else
964                                 const sal_Int64 nRetval(z_inflate(&zstream, Z_NO_FLUSH));
965 #endif
966                                 if (Z_STREAM_ERROR == nRetval)
967                                 {
968                                     bOkay = false;
969                                 }
970                                 else
971                                 {
972                                     const sal_uInt64 nAvailable(BACKUP_FILE_HELPER_BLOCK_SIZE - zstream.avail_out);
973 
974                                     if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aBuffer), nAvailable, &nBytesTransfer) || nBytesTransfer != nAvailable)
975                                     {
976                                         bOkay = false;
977                                     }
978                                 }
979                             } while (bOkay && 0 == zstream.avail_out);
980 
981                             if (!bOkay)
982                             {
983                                 break;
984                             }
985 
986                             nSize -= nToTransfer;
987                         }
988 
989 #if !defined Z_PREFIX
990                         deflateEnd(&zstream);
991 #else
992                         z_deflateEnd(&zstream);
993 #endif
994                     }
995                 }
996 
997                 maFile->close();
998                 return (0 == nSize);
999             }
1000 
1001             return false;
1002         }
1003 
1004 
1005     public:
1006         // create new, uncompressed entry
PackedFileEntry(sal_uInt32 nFullFileSize,sal_uInt32 nCrc32,FileSharedPtr const & rFile,bool bDoCompress)1007         PackedFileEntry(
1008             sal_uInt32 nFullFileSize,
1009             sal_uInt32 nCrc32,
1010             FileSharedPtr const & rFile,
1011             bool bDoCompress)
1012         :   mnFullFileSize(nFullFileSize),
1013             mnPackFileSize(nFullFileSize),
1014             mnOffset(0),
1015             mnCrc32(nCrc32),
1016             maFile(rFile),
1017             mbDoCompress(bDoCompress)
1018         {
1019         }
1020 
1021         // create entry to be loaded as header (read_header)
PackedFileEntry()1022         PackedFileEntry()
1023         :   mnFullFileSize(0),
1024             mnPackFileSize(0),
1025             mnOffset(0),
1026             mnCrc32(0),
1027             maFile(),
1028             mbDoCompress(false)
1029         {
1030         }
1031 
getFullFileSize() const1032         sal_uInt32 getFullFileSize() const
1033         {
1034             return  mnFullFileSize;
1035         }
1036 
getPackFileSize() const1037         sal_uInt32 getPackFileSize() const
1038         {
1039             return  mnPackFileSize;
1040         }
1041 
getOffset() const1042         sal_uInt32 getOffset() const
1043         {
1044             return mnOffset;
1045         }
1046 
setOffset(sal_uInt32 nOffset)1047         void setOffset(sal_uInt32 nOffset)
1048         {
1049             mnOffset = nOffset;
1050         }
1051 
getEntrySize()1052         static sal_uInt32 getEntrySize()
1053         {
1054             return 12;
1055         }
1056 
getCrc32() const1057         sal_uInt32 getCrc32() const
1058         {
1059             return mnCrc32;
1060         }
1061 
read_header(FileSharedPtr const & rFile)1062         bool read_header(FileSharedPtr const & rFile)
1063         {
1064             if (!rFile)
1065             {
1066                 return false;
1067             }
1068 
1069             maFile = rFile;
1070 
1071             // read and compute full file size
1072             if (!read_sal_uInt32(rFile, mnFullFileSize))
1073             {
1074                 return false;
1075             }
1076 
1077             // read and compute entry crc32
1078             if (!read_sal_uInt32(rFile, mnCrc32))
1079             {
1080                 return false;
1081             }
1082 
1083             // read and compute packed size
1084             if (!read_sal_uInt32(rFile, mnPackFileSize))
1085             {
1086                 return false;
1087             }
1088 
1089             return true;
1090         }
1091 
write_header(oslFileHandle & rHandle) const1092         bool write_header(oslFileHandle& rHandle) const
1093         {
1094             // write full file size
1095             if (!write_sal_uInt32(rHandle, mnFullFileSize))
1096             {
1097                 return false;
1098             }
1099 
1100             // write crc32
1101             if (!write_sal_uInt32(rHandle, mnCrc32))
1102             {
1103                 return false;
1104             }
1105 
1106             // write packed file size
1107             if (!write_sal_uInt32(rHandle, mnPackFileSize))
1108             {
1109                 return false;
1110             }
1111 
1112             return true;
1113         }
1114 
copy_content(oslFileHandle & rTargetHandle,bool bUncompress)1115         bool copy_content(oslFileHandle& rTargetHandle, bool bUncompress)
1116         {
1117             if (bUncompress)
1118             {
1119                 if (getFullFileSize() == getPackFileSize())
1120                 {
1121                     // not compressed, just copy
1122                     return copy_content_straight(rTargetHandle);
1123                 }
1124                 else
1125                 {
1126                     // compressed, need to uncompress on copy
1127                     return copy_content_uncompress(rTargetHandle);
1128                 }
1129             }
1130             else if (0 == getOffset())
1131             {
1132                 if (mbDoCompress)
1133                 {
1134                     // compressed wanted, need to compress on copy
1135                     return copy_content_compress(rTargetHandle);
1136                 }
1137                 else
1138                 {
1139                     // not compressed, straight copy
1140                     return copy_content_straight(rTargetHandle);
1141                 }
1142             }
1143             else
1144             {
1145                 return copy_content_straight(rTargetHandle);
1146             }
1147         }
1148     };
1149 }
1150 
1151 namespace
1152 {
1153     class PackedFile
1154     {
1155     private:
1156         const OUString          maURL;
1157         std::deque< PackedFileEntry >
1158                                 maPackedFileEntryVector;
1159         bool                    mbChanged;
1160 
1161     public:
PackedFile(const OUString & rURL)1162         PackedFile(const OUString& rURL)
1163         :   maURL(rURL),
1164             maPackedFileEntryVector(),
1165             mbChanged(false)
1166         {
1167             FileSharedPtr aSourceFile = std::make_shared<osl::File>(rURL);
1168 
1169             if (osl::File::E_None == aSourceFile->open(osl_File_OpenFlag_Read))
1170             {
1171                 sal_uInt64 nBaseLen(0);
1172                 aSourceFile->getSize(nBaseLen);
1173 
1174                 // we need at least File_ID and num entries -> 8byte
1175                 if (8 < nBaseLen)
1176                 {
1177                     sal_uInt8 aArray[4];
1178                     sal_uInt64 nBaseRead(0);
1179 
1180                     // read and check File_ID
1181                     if (osl::File::E_None == aSourceFile->read(static_cast< void* >(aArray), 4, nBaseRead) && 4 == nBaseRead)
1182                     {
1183                         if ('P' == aArray[0] && 'A' == aArray[1] && 'C' == aArray[2] && 'K' == aArray[3])
1184                         {
1185                             // read and compute num entries in this file
1186                             if (osl::File::E_None == aSourceFile->read(static_cast<void*>(aArray), 4, nBaseRead) && 4 == nBaseRead)
1187                             {
1188                                 sal_uInt32 nEntries((sal_uInt32(aArray[0]) << 24) + (sal_uInt32(aArray[1]) << 16) + (sal_uInt32(aArray[2]) << 8) + sal_uInt32(aArray[3]));
1189 
1190                                 // if there are entries (and less than max), read them
1191                                 if (nEntries >= 1 && nEntries <= 10)
1192                                 {
1193                                     for (sal_uInt32 a(0); a < nEntries; a++)
1194                                     {
1195                                         // create new entry, read header (size, crc and PackedSize),
1196                                         // set offset and source file
1197                                         PackedFileEntry aEntry;
1198 
1199                                         if (aEntry.read_header(aSourceFile))
1200                                         {
1201                                             // add to local data
1202                                             maPackedFileEntryVector.push_back(aEntry);
1203                                         }
1204                                         else
1205                                         {
1206                                             // error
1207                                             nEntries = 0;
1208                                         }
1209                                     }
1210 
1211                                     if (0 == nEntries)
1212                                     {
1213                                         // on read error clear local data
1214                                         maPackedFileEntryVector.clear();
1215                                     }
1216                                     else
1217                                     {
1218                                         // calculate and set offsets to file binary content
1219                                         sal_uInt32 nHeaderSize(8);
1220 
1221                                         nHeaderSize += maPackedFileEntryVector.size() * PackedFileEntry::getEntrySize();
1222 
1223                                         sal_uInt32 nOffset(nHeaderSize);
1224 
1225                                         for (auto& b : maPackedFileEntryVector)
1226                                         {
1227                                             b.setOffset(nOffset);
1228                                             nOffset += b.getPackFileSize();
1229                                         }
1230                                     }
1231                                 }
1232                             }
1233                         }
1234                     }
1235                 }
1236 
1237                 aSourceFile->close();
1238             }
1239 
1240             if (maPackedFileEntryVector.empty())
1241             {
1242                 // on error or no data get rid of pack file
1243                 osl::File::remove(maURL);
1244             }
1245         }
1246 
flush()1247         void flush()
1248         {
1249             bool bRetval(true);
1250 
1251             if (maPackedFileEntryVector.empty())
1252             {
1253                 // get rid of (now?) empty pack file
1254                 osl::File::remove(maURL);
1255             }
1256             else if (mbChanged)
1257             {
1258                 // need to create a new pack file, do this in a temp file to which data
1259                 // will be copied from local file (so keep it here until this is done)
1260                 oslFileHandle aHandle;
1261                 OUString aTempURL;
1262 
1263                 // open target temp file - it exists until deleted
1264                 if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL))
1265                 {
1266                     sal_uInt8 aArray[4];
1267                     sal_uInt64 nBaseWritten(0);
1268 
1269                     aArray[0] = 'P';
1270                     aArray[1] = 'A';
1271                     aArray[2] = 'C';
1272                     aArray[3] = 'K';
1273 
1274                     // write File_ID
1275                     if (osl_File_E_None == osl_writeFile(aHandle, static_cast<const void*>(aArray), 4, &nBaseWritten) && 4 == nBaseWritten)
1276                     {
1277                         const sal_uInt32 nSize(maPackedFileEntryVector.size());
1278 
1279                         // write number of entries
1280                         if (write_sal_uInt32(aHandle, nSize))
1281                         {
1282                             // write placeholder for headers. Due to the fact that
1283                             // PackFileSize for newly added files gets set during
1284                             // writing the content entry, write headers after content
1285                             // is written. To do so, write placeholders here
1286                             sal_uInt32 nWriteSize(0);
1287 
1288                             nWriteSize += maPackedFileEntryVector.size() * PackedFileEntry::getEntrySize();
1289 
1290                             aArray[0] = aArray[1] = aArray[2] = aArray[3] = 0;
1291 
1292                             for (sal_uInt32 a(0); bRetval && a < nWriteSize; a++)
1293                             {
1294                                 if (osl_File_E_None != osl_writeFile(aHandle, static_cast<const void*>(aArray), 1, &nBaseWritten) || 1 != nBaseWritten)
1295                                 {
1296                                     bRetval = false;
1297                                 }
1298                             }
1299 
1300                             if (bRetval)
1301                             {
1302                                 // write contents - this may adapt PackFileSize for new
1303                                 // files
1304                                 for (auto& candidate : maPackedFileEntryVector)
1305                                 {
1306                                     if (!candidate.copy_content(aHandle, false))
1307                                     {
1308                                         bRetval = false;
1309                                         break;
1310                                     }
1311                                 }
1312                             }
1313 
1314                             if (bRetval)
1315                             {
1316                                 // seek back to header start (at position 8)
1317                                 if (osl_File_E_None != osl_setFilePos(aHandle, osl_Pos_Absolut, sal_Int64(8)))
1318                                 {
1319                                     bRetval = false;
1320                                 }
1321                             }
1322 
1323                             if (bRetval)
1324                             {
1325                                 // write headers
1326                                 for (const auto& candidate : maPackedFileEntryVector)
1327                                 {
1328                                     if (!candidate.write_header(aHandle))
1329                                     {
1330                                         // error
1331                                         bRetval = false;
1332                                         break;
1333                                     }
1334                                 }
1335                             }
1336                         }
1337                     }
1338                 }
1339 
1340                 // close temp file (in all cases) - it exists until deleted
1341                 osl_closeFile(aHandle);
1342 
1343                 if (bRetval)
1344                 {
1345                     // copy over existing file by first deleting original
1346                     // and moving the temp file to old original
1347                     osl::File::remove(maURL);
1348                     osl::File::move(aTempURL, maURL);
1349                 }
1350 
1351                 // delete temp file (in all cases - it may be moved already)
1352                 osl::File::remove(aTempURL);
1353             }
1354         }
1355 
tryPush(FileSharedPtr const & rFileCandidate,bool bCompress)1356         bool tryPush(FileSharedPtr const & rFileCandidate, bool bCompress)
1357         {
1358             sal_uInt64 nFileSize(0);
1359 
1360             if (rFileCandidate && osl::File::E_None == rFileCandidate->open(osl_File_OpenFlag_Read))
1361             {
1362                 rFileCandidate->getSize(nFileSize);
1363                 rFileCandidate->close();
1364             }
1365 
1366             if (0 == nFileSize)
1367             {
1368                 // empty file offered
1369                 return false;
1370             }
1371 
1372             bool bNeedToAdd(false);
1373             sal_uInt32 nCrc32(0);
1374 
1375             if (maPackedFileEntryVector.empty())
1376             {
1377                 // no backup yet, add as 1st backup
1378                 bNeedToAdd = true;
1379             }
1380             else
1381             {
1382                 // already backups there, check if different from last entry
1383                 const PackedFileEntry& aLastEntry = maPackedFileEntryVector.back();
1384 
1385                 // check if file is different
1386                 if (aLastEntry.getFullFileSize() != static_cast<sal_uInt32>(nFileSize))
1387                 {
1388                     // different size, different file
1389                     bNeedToAdd = true;
1390                 }
1391                 else
1392                 {
1393                     // same size, check crc32
1394                     nCrc32 = createCrc32(rFileCandidate, 0);
1395 
1396                     if (nCrc32 != aLastEntry.getCrc32())
1397                     {
1398                         // different crc, different file
1399                         bNeedToAdd = true;
1400                     }
1401                 }
1402             }
1403 
1404             if (bNeedToAdd)
1405             {
1406                 // create crc32 if not yet done
1407                 if (0 == nCrc32)
1408                 {
1409                     nCrc32 = createCrc32(rFileCandidate, 0);
1410                 }
1411 
1412                 // create a file entry for a new file. Offset is set automatically
1413                 // to 0 to mark the entry as new file entry
1414                 maPackedFileEntryVector.emplace_back(
1415                         static_cast< sal_uInt32 >(nFileSize),
1416                         nCrc32,
1417                         rFileCandidate,
1418                         bCompress);
1419 
1420                 mbChanged = true;
1421             }
1422 
1423             return bNeedToAdd;
1424         }
1425 
tryPop(oslFileHandle & rHandle)1426         bool tryPop(oslFileHandle& rHandle)
1427         {
1428             if (!maPackedFileEntryVector.empty())
1429             {
1430                 // already backups there, check if different from last entry
1431                 PackedFileEntry& aLastEntry = maPackedFileEntryVector.back();
1432 
1433                 // here the uncompress flag has to be determined, true
1434                 // means to add the file compressed, false means to add it
1435                 // uncompressed
1436                 bool bRetval = aLastEntry.copy_content(rHandle, true);
1437 
1438                 if (bRetval)
1439                 {
1440                     maPackedFileEntryVector.pop_back();
1441                     mbChanged = true;
1442                 }
1443 
1444                 return bRetval;
1445             }
1446 
1447             return false;
1448         }
1449 
tryReduceToNumBackups(sal_uInt16 nNumBackups)1450         void tryReduceToNumBackups(sal_uInt16 nNumBackups)
1451         {
1452             while (maPackedFileEntryVector.size() > nNumBackups)
1453             {
1454                 maPackedFileEntryVector.pop_front();
1455                 mbChanged = true;
1456             }
1457         }
1458 
empty() const1459         bool empty() const
1460         {
1461             return maPackedFileEntryVector.empty();
1462         }
1463     };
1464 }
1465 
1466 namespace comphelper
1467 {
1468     sal_uInt16 BackupFileHelper::mnMaxAllowedBackups = 10;
1469     bool BackupFileHelper::mbExitWasCalled = false;
1470     bool BackupFileHelper::mbSafeModeDirExists = false;
1471     OUString BackupFileHelper::maInitialBaseURL;
1472     OUString BackupFileHelper::maUserConfigBaseURL;
1473     OUString BackupFileHelper::maUserConfigWorkURL;
1474     OUString BackupFileHelper::maRegModName;
1475     OUString BackupFileHelper::maExt;
1476 
getInitialBaseURL()1477     const OUString& BackupFileHelper::getInitialBaseURL()
1478     {
1479         if (maInitialBaseURL.isEmpty())
1480         {
1481             // try to access user layer configuration file URL, the one that
1482             // points to registrymodifications.xcu
1483             OUString conf("${CONFIGURATION_LAYERS}");
1484             rtl::Bootstrap::expandMacros(conf);
1485             static const OUStringLiteral aTokenUser(u"user:");
1486             sal_Int32 nStart(conf.indexOf(aTokenUser));
1487 
1488             if (-1 != nStart)
1489             {
1490                 nStart += aTokenUser.getLength();
1491                 sal_Int32 nEnd(conf.indexOf(' ', nStart));
1492 
1493                 if (-1 == nEnd)
1494                 {
1495                     nEnd = conf.getLength();
1496                 }
1497 
1498                 maInitialBaseURL = conf.copy(nStart, nEnd - nStart);
1499                 (void)maInitialBaseURL.startsWith("!", &maInitialBaseURL);
1500             }
1501 
1502             if (!maInitialBaseURL.isEmpty())
1503             {
1504                 // split URL at extension and at last path separator
1505                 maUserConfigBaseURL = DirectoryHelper::splitAtLastToken(
1506                     DirectoryHelper::splitAtLastToken(maInitialBaseURL, '.', maExt), '/',
1507                     maRegModName);
1508             }
1509 
1510             if (!maUserConfigBaseURL.isEmpty())
1511             {
1512                 // check if SafeModeDir exists
1513                 mbSafeModeDirExists = DirectoryHelper::dirExists(maUserConfigBaseURL + "/" + getSafeModeName());
1514             }
1515 
1516             maUserConfigWorkURL = maUserConfigBaseURL;
1517 
1518             if (mbSafeModeDirExists)
1519             {
1520                 // adapt work URL to do all repair op's in the correct directory
1521                 maUserConfigWorkURL += "/" + getSafeModeName();
1522             }
1523         }
1524 
1525         return maInitialBaseURL;
1526     }
1527 
getSafeModeName()1528     const OUString& BackupFileHelper::getSafeModeName()
1529     {
1530         static const OUString aSafeMode("SafeMode");
1531 
1532         return aSafeMode;
1533     }
1534 
BackupFileHelper()1535     BackupFileHelper::BackupFileHelper()
1536     :   maDirs(),
1537         maFiles(),
1538         mnNumBackups(2),
1539         mnMode(1),
1540         mbActive(false),
1541         mbExtensions(true),
1542         mbCompress(true)
1543     {
1544         OUString sTokenOut;
1545 
1546         // read configuration item 'SecureUserConfig' -> bool on/off
1547         if (rtl::Bootstrap::get("SecureUserConfig", sTokenOut))
1548         {
1549             mbActive = sTokenOut.toBoolean();
1550         }
1551 
1552         if (mbActive)
1553         {
1554             // ensure existence
1555             getInitialBaseURL();
1556 
1557             // if not found, we are out of business (maExt may be empty)
1558             mbActive = !maInitialBaseURL.isEmpty() && !maUserConfigBaseURL.isEmpty() && !maRegModName.isEmpty();
1559         }
1560 
1561         if (mbActive && rtl::Bootstrap::get("SecureUserConfigNumCopies", sTokenOut))
1562         {
1563             const sal_uInt16 nConfigNumCopies(static_cast<sal_uInt16>(sTokenOut.toUInt32()));
1564 
1565             // limit to range [1..mnMaxAllowedBackups]
1566             mnNumBackups = std::clamp(mnNumBackups, nConfigNumCopies, mnMaxAllowedBackups);
1567         }
1568 
1569         if (mbActive && rtl::Bootstrap::get("SecureUserConfigMode", sTokenOut))
1570         {
1571             const sal_uInt16 nMode(static_cast<sal_uInt16>(sTokenOut.toUInt32()));
1572 
1573             // limit to range [0..2]
1574             mnMode = std::min(nMode, sal_uInt16(2));
1575         }
1576 
1577         if (mbActive && rtl::Bootstrap::get("SecureUserConfigExtensions", sTokenOut))
1578         {
1579             mbExtensions = sTokenOut.toBoolean();
1580         }
1581 
1582         if (mbActive && rtl::Bootstrap::get("SecureUserConfigCompress", sTokenOut))
1583         {
1584             mbCompress = sTokenOut.toBoolean();
1585         }
1586     }
1587 
setExitWasCalled()1588     void BackupFileHelper::setExitWasCalled()
1589     {
1590         mbExitWasCalled = true;
1591     }
1592 
getExitWasCalled()1593     bool BackupFileHelper::getExitWasCalled()
1594     {
1595         return mbExitWasCalled;
1596     }
1597 
reactOnSafeMode(bool bSafeMode)1598     void BackupFileHelper::reactOnSafeMode(bool bSafeMode)
1599     {
1600         // ensure existence of needed paths
1601         getInitialBaseURL();
1602 
1603         if (maUserConfigBaseURL.isEmpty())
1604             return;
1605 
1606         if (bSafeMode)
1607         {
1608             if (!mbSafeModeDirExists)
1609             {
1610                 std::set< OUString > aExcludeList;
1611 
1612                 // do not move SafeMode directory itself
1613                 aExcludeList.insert(getSafeModeName());
1614 
1615                 // init SafeMode by creating the 'SafeMode' directory and moving
1616                 // all stuff there. All repairs will happen there. Both Dirs have to exist.
1617                 // extend maUserConfigWorkURL as needed
1618                 maUserConfigWorkURL = maUserConfigBaseURL + "/" + getSafeModeName();
1619 
1620                 osl::Directory::createPath(maUserConfigWorkURL);
1621                 DirectoryHelper::moveDirContent(maUserConfigBaseURL, maUserConfigWorkURL, aExcludeList);
1622 
1623                 // switch local flag, maUserConfigWorkURL is already reset
1624                 mbSafeModeDirExists = true;
1625             }
1626         }
1627         else
1628         {
1629             if (mbSafeModeDirExists)
1630             {
1631                 // SafeMode has ended, return to normal mode by moving all content
1632                 // from 'SafeMode' directory back to UserDirectory and deleting it.
1633                 // Both Dirs have to exist
1634                 std::set< OUString > aExcludeList;
1635 
1636                 DirectoryHelper::moveDirContent(maUserConfigWorkURL, maUserConfigBaseURL, aExcludeList);
1637                 osl::Directory::remove(maUserConfigWorkURL);
1638 
1639                 // switch local flag and reset maUserConfigWorkURL
1640                 mbSafeModeDirExists = false;
1641                 maUserConfigWorkURL = maUserConfigBaseURL;
1642             }
1643         }
1644     }
1645 
tryPush()1646     void BackupFileHelper::tryPush()
1647     {
1648         // no push when SafeModeDir exists, it may be Office's exit after SafeMode
1649         // where SafeMode flag is already deleted, but SafeModeDir cleanup is not
1650         // done yet (is done at next startup)
1651         if (!mbActive || mbSafeModeDirExists)
1652             return;
1653 
1654         const OUString aPackURL(getPackURL());
1655 
1656         // ensure dir and file vectors
1657         fillDirFileInfo();
1658 
1659         // process all files in question recursively
1660         if (!maDirs.empty() || !maFiles.empty())
1661         {
1662             tryPush_Files(
1663                 maDirs,
1664                 maFiles,
1665                 maUserConfigWorkURL,
1666                 aPackURL);
1667         }
1668     }
1669 
tryPushExtensionInfo()1670     void BackupFileHelper::tryPushExtensionInfo()
1671     {
1672         // no push when SafeModeDir exists, it may be Office's exit after SafeMode
1673         // where SafeMode flag is already deleted, but SafeModeDir cleanup is not
1674         // done yet (is done at next startup)
1675         if (mbActive && mbExtensions && !mbSafeModeDirExists)
1676         {
1677             const OUString aPackURL(getPackURL());
1678 
1679             tryPush_extensionInfo(aPackURL);
1680         }
1681     }
1682 
isPopPossible()1683     bool BackupFileHelper::isPopPossible()
1684     {
1685         bool bPopPossible(false);
1686 
1687         if (mbActive)
1688         {
1689             const OUString aPackURL(getPackURL());
1690 
1691             // ensure dir and file vectors
1692             fillDirFileInfo();
1693 
1694             // process all files in question recursively
1695             if (!maDirs.empty() || !maFiles.empty())
1696             {
1697                 bPopPossible = isPopPossible_files(
1698                     maDirs,
1699                     maFiles,
1700                     maUserConfigWorkURL,
1701                     aPackURL);
1702             }
1703         }
1704 
1705         return bPopPossible;
1706     }
1707 
tryPop()1708     void BackupFileHelper::tryPop()
1709     {
1710         if (!mbActive)
1711             return;
1712 
1713         bool bDidPop(false);
1714         const OUString aPackURL(getPackURL());
1715 
1716         // ensure dir and file vectors
1717         fillDirFileInfo();
1718 
1719         // process all files in question recursively
1720         if (!maDirs.empty() || !maFiles.empty())
1721         {
1722             bDidPop = tryPop_files(
1723                 maDirs,
1724                 maFiles,
1725                 maUserConfigWorkURL,
1726                 aPackURL);
1727         }
1728 
1729         if (bDidPop)
1730         {
1731             // try removal of evtl. empty directory
1732             osl::Directory::remove(aPackURL);
1733         }
1734     }
1735 
isPopPossibleExtensionInfo() const1736     bool BackupFileHelper::isPopPossibleExtensionInfo() const
1737     {
1738         bool bPopPossible(false);
1739 
1740         if (mbActive && mbExtensions)
1741         {
1742             const OUString aPackURL(getPackURL());
1743 
1744             bPopPossible = isPopPossible_extensionInfo(aPackURL);
1745         }
1746 
1747         return bPopPossible;
1748     }
1749 
tryPopExtensionInfo()1750     void BackupFileHelper::tryPopExtensionInfo()
1751     {
1752         if (!(mbActive && mbExtensions))
1753             return;
1754 
1755         bool bDidPop(false);
1756         const OUString aPackURL(getPackURL());
1757 
1758         bDidPop = tryPop_extensionInfo(aPackURL);
1759 
1760         if (bDidPop)
1761         {
1762             // try removal of evtl. empty directory
1763             osl::Directory::remove(aPackURL);
1764         }
1765     }
1766 
isTryDisableAllExtensionsPossible()1767     bool BackupFileHelper::isTryDisableAllExtensionsPossible()
1768     {
1769         // check if there are still enabled extension which can be disabled,
1770         // but as we are now in SafeMode, use XML infos for this since the
1771         // extensions are not loaded from XExtensionManager
1772         class ExtensionInfo aExtensionInfo;
1773 
1774         aExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL);
1775 
1776         return aExtensionInfo.areThereEnabledExtensions();
1777     }
1778 
tryDisableAllExtensions()1779     void BackupFileHelper::tryDisableAllExtensions()
1780     {
1781         // disable all still enabled extensions,
1782         // but as we are now in SafeMode, use XML infos for this since the
1783         // extensions are not loaded from XExtensionManager
1784         ExtensionInfo aCurrentExtensionInfo;
1785         const ExtensionInfoEntryVector aToBeEnabled{};
1786         ExtensionInfoEntryVector aToBeDisabled;
1787 
1788         aCurrentExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL);
1789 
1790         const ExtensionInfoEntryVector& rCurrentVector = aCurrentExtensionInfo.getExtensionInfoEntryVector();
1791 
1792         for (const auto& rCurrentInfo : rCurrentVector)
1793         {
1794             if (rCurrentInfo.isEnabled())
1795             {
1796                 aToBeDisabled.push_back(rCurrentInfo);
1797             }
1798         }
1799 
1800         ExtensionInfo::changeEnableDisableStateInXML(maUserConfigWorkURL, aToBeEnabled, aToBeDisabled);
1801     }
1802 
isTryDeinstallUserExtensionsPossible()1803     bool BackupFileHelper::isTryDeinstallUserExtensionsPossible()
1804     {
1805         // check if there are User Extensions installed.
1806         class ExtensionInfo aExtensionInfo;
1807 
1808         aExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL);
1809 
1810         return !aExtensionInfo.getExtensionInfoEntryVector().empty();
1811     }
1812 
tryDeinstallUserExtensions()1813     void BackupFileHelper::tryDeinstallUserExtensions()
1814     {
1815         // delete User Extension installs
1816         DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/uno_packages");
1817     }
1818 
isTryResetSharedExtensionsPossible()1819     bool BackupFileHelper::isTryResetSharedExtensionsPossible()
1820     {
1821         // check if there are shared Extensions installed
1822         class ExtensionInfo aExtensionInfo;
1823 
1824         aExtensionInfo.createSharedExtensionRegistryEntriesFromXML(maUserConfigWorkURL);
1825 
1826         return !aExtensionInfo.getExtensionInfoEntryVector().empty();
1827     }
1828 
tryResetSharedExtensions()1829     void BackupFileHelper::tryResetSharedExtensions()
1830     {
1831         // reset shared extension info
1832         DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/extensions/shared");
1833     }
1834 
isTryResetBundledExtensionsPossible()1835     bool BackupFileHelper::isTryResetBundledExtensionsPossible()
1836     {
1837         // check if there are shared Extensions installed
1838         class ExtensionInfo aExtensionInfo;
1839 
1840         aExtensionInfo.createBundledExtensionRegistryEntriesFromXML(maUserConfigWorkURL);
1841 
1842         return !aExtensionInfo.getExtensionInfoEntryVector().empty();
1843     }
1844 
tryResetBundledExtensions()1845     void BackupFileHelper::tryResetBundledExtensions()
1846     {
1847         // reset shared extension info
1848         DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/extensions/bundled");
1849     }
1850 
getCustomizationDirNames()1851     const std::vector< OUString >& BackupFileHelper::getCustomizationDirNames()
1852     {
1853         static std::vector< OUString > aDirNames =
1854         {
1855             "config",     // UI config stuff
1856             "registry",   // most of the registry stuff
1857             "psprint",    // not really needed, can be abandoned
1858             "store",      // not really needed, can be abandoned
1859             "temp",       // not really needed, can be abandoned
1860             "pack"       // own backup dir
1861         };
1862 
1863         return aDirNames;
1864     }
1865 
getCustomizationFileNames()1866     const std::vector< OUString >& BackupFileHelper::getCustomizationFileNames()
1867     {
1868         static std::vector< OUString > aFileNames =
1869         {
1870             "registrymodifications.xcu" // personal registry stuff
1871         };
1872 
1873         return aFileNames;
1874     }
1875 
1876     namespace {
lcl_getConfigElement(const uno::Reference<XDocument> & xDocument,const OUString & rPath,const OUString & rKey,const OUString & rValue)1877         uno::Reference<XElement> lcl_getConfigElement(const uno::Reference<XDocument>& xDocument, const OUString& rPath,
1878                                   const OUString& rKey, const OUString& rValue)
1879         {
1880             uno::Reference< XElement > itemElement = xDocument->createElement("item");
1881             itemElement->setAttribute("oor:path", rPath);
1882 
1883             uno::Reference< XElement > propElement = xDocument->createElement("prop");
1884             propElement->setAttribute("oor:name", rKey);
1885             propElement->setAttribute("oor:op", "replace"); // Replace any other options
1886 
1887             uno::Reference< XElement > valueElement = xDocument->createElement("value");
1888             uno::Reference< XText > textElement = xDocument->createTextNode(rValue);
1889 
1890             valueElement->appendChild(textElement);
1891             propElement->appendChild(valueElement);
1892             itemElement->appendChild(propElement);
1893 
1894             return itemElement;
1895         }
1896     }
1897 
tryDisableHWAcceleration()1898     void BackupFileHelper::tryDisableHWAcceleration()
1899     {
1900         const OUString aRegistryModifications(maUserConfigWorkURL + "/registrymodifications.xcu");
1901         if (!DirectoryHelper::fileExists(aRegistryModifications))
1902             return;
1903 
1904         uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
1905         uno::Reference< XDocumentBuilder > xBuilder = DocumentBuilder::create(xContext);
1906         uno::Reference< XDocument > xDocument = xBuilder->parseURI(aRegistryModifications);
1907         uno::Reference< XElement > xRootElement = xDocument->getDocumentElement();
1908 
1909         xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL",
1910                                                        "DisableOpenGL", "true"));
1911         xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/Misc",
1912                                                        "UseOpenCL", "false"));
1913         // Do not disable Skia entirely, just force its CPU-based raster mode.
1914         xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL",
1915                                                        "ForceSkia", "false"));
1916         xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL",
1917                                                        "ForceSkiaRaster", "true"));
1918 
1919         // write back
1920         uno::Reference< xml::sax::XSAXSerializable > xSerializer(xDocument, uno::UNO_QUERY);
1921 
1922         if (!xSerializer.is())
1923             return;
1924 
1925         // create a SAXWriter
1926         uno::Reference< xml::sax::XWriter > const xSaxWriter = xml::sax::Writer::create(xContext);
1927         uno::Reference< io::XStream > xTempFile = io::TempFile::create(xContext);
1928         uno::Reference< io::XOutputStream > xOutStrm = xTempFile->getOutputStream();
1929 
1930         // set output stream and do the serialization
1931         xSaxWriter->setOutputStream(xOutStrm);
1932         xSerializer->serialize(xSaxWriter, uno::Sequence< beans::StringPair >());
1933 
1934         // get URL from temp file
1935         uno::Reference < beans::XPropertySet > xTempFileProps(xTempFile, uno::UNO_QUERY);
1936         uno::Any aUrl = xTempFileProps->getPropertyValue("Uri");
1937         OUString aTempURL;
1938         aUrl >>= aTempURL;
1939 
1940         // copy back file
1941         if (aTempURL.isEmpty() || !DirectoryHelper::fileExists(aTempURL))
1942             return;
1943 
1944         if (DirectoryHelper::fileExists(aRegistryModifications))
1945         {
1946             osl::File::remove(aRegistryModifications);
1947         }
1948 
1949         int result = osl::File::move(aTempURL, aRegistryModifications);
1950         SAL_WARN_IF(result != osl::FileBase::E_None, "comphelper.backupfilehelper", "could not copy back modified Extension configuration file");
1951     }
1952 
isTryResetCustomizationsPossible()1953     bool BackupFileHelper::isTryResetCustomizationsPossible()
1954     {
1955         // return true if not all of the customization selection dirs or files are deleted
1956         const std::vector< OUString >& rDirs = getCustomizationDirNames();
1957 
1958         for (const auto& a : rDirs)
1959         {
1960             if (DirectoryHelper::dirExists(maUserConfigWorkURL + "/" + a))
1961             {
1962                 return true;
1963             }
1964         }
1965 
1966         const std::vector< OUString >& rFiles = getCustomizationFileNames();
1967 
1968         for (const auto& b : rFiles)
1969         {
1970             if (DirectoryHelper::fileExists(maUserConfigWorkURL + "/" + b))
1971             {
1972                 return true;
1973             }
1974         }
1975 
1976         return false;
1977     }
1978 
tryResetCustomizations()1979     void BackupFileHelper::tryResetCustomizations()
1980     {
1981         // delete all of the customization selection dirs
1982         const std::vector< OUString >& rDirs = getCustomizationDirNames();
1983 
1984         for (const auto& a : rDirs)
1985         {
1986             DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/" + a);
1987         }
1988 
1989         const std::vector< OUString >& rFiles = getCustomizationFileNames();
1990 
1991         for (const auto& b : rFiles)
1992         {
1993             osl::File::remove(maUserConfigWorkURL + "/" + b);
1994         }
1995     }
1996 
tryResetUserProfile()1997     void BackupFileHelper::tryResetUserProfile()
1998     {
1999         // completely delete the current UserProfile
2000         DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL);
2001     }
2002 
getUserProfileURL()2003     const OUString& BackupFileHelper::getUserProfileURL()
2004     {
2005         return maUserConfigBaseURL;
2006     }
2007 
getUserProfileWorkURL()2008     const OUString& BackupFileHelper::getUserProfileWorkURL()
2009     {
2010         return maUserConfigWorkURL;
2011     }
2012 
2013     /////////////////// helpers ///////////////////////
2014 
getPackURL()2015     OUString BackupFileHelper::getPackURL()
2016     {
2017         return OUString(maUserConfigWorkURL + "/pack");
2018     }
2019 
2020     /////////////////// file push helpers ///////////////////////
2021 
tryPush_Files(const std::set<OUString> & rDirs,const std::set<std::pair<OUString,OUString>> & rFiles,std::u16string_view rSourceURL,const OUString & rTargetURL)2022     bool BackupFileHelper::tryPush_Files(
2023         const std::set< OUString >& rDirs,
2024         const std::set< std::pair< OUString, OUString > >& rFiles,
2025         std::u16string_view rSourceURL, // source dir without trailing '/'
2026         const OUString& rTargetURL // target dir without trailing '/'
2027         )
2028     {
2029         bool bDidPush(false);
2030         osl::Directory::createPath(rTargetURL);
2031 
2032         // process files
2033         for (const auto& file : rFiles)
2034         {
2035             bDidPush |= tryPush_file(
2036                 rSourceURL,
2037                 rTargetURL,
2038                 file.first,
2039                 file.second);
2040         }
2041 
2042         // process dirs
2043         for (const auto& dir : rDirs)
2044         {
2045             OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir);
2046             OUString aNewTargetURL(rTargetURL + "/" + dir);
2047             std::set< OUString > aNewDirs;
2048             std::set< std::pair< OUString, OUString > > aNewFiles;
2049 
2050             DirectoryHelper::scanDirsAndFiles(
2051                 aNewSourceURL,
2052                 aNewDirs,
2053                 aNewFiles);
2054 
2055             if (!aNewDirs.empty() || !aNewFiles.empty())
2056             {
2057                 bDidPush |= tryPush_Files(
2058                     aNewDirs,
2059                     aNewFiles,
2060                     aNewSourceURL,
2061                     aNewTargetURL);
2062             }
2063         }
2064 
2065         if (!bDidPush)
2066         {
2067             // try removal of evtl. empty directory
2068             osl::Directory::remove(rTargetURL);
2069         }
2070 
2071         return bDidPush;
2072     }
2073 
tryPush_file(std::u16string_view rSourceURL,std::u16string_view rTargetURL,std::u16string_view rName,std::u16string_view rExt)2074     bool BackupFileHelper::tryPush_file(
2075         std::u16string_view rSourceURL, // source dir without trailing '/'
2076         std::u16string_view rTargetURL, // target dir without trailing '/'
2077         std::u16string_view rName,  // filename
2078         std::u16string_view rExt    // extension (or empty)
2079         )
2080     {
2081         const OUString aFileURL(createFileURL(rSourceURL, rName, rExt));
2082 
2083         if (DirectoryHelper::fileExists(aFileURL))
2084         {
2085             const OUString aPackURL(createPackURL(rTargetURL, rName));
2086             PackedFile aPackedFile(aPackURL);
2087             FileSharedPtr aBaseFile = std::make_shared<osl::File>(aFileURL);
2088 
2089             if (aPackedFile.tryPush(aBaseFile, mbCompress))
2090             {
2091                 // reduce to allowed number and flush
2092                 aPackedFile.tryReduceToNumBackups(mnNumBackups);
2093                 aPackedFile.flush();
2094 
2095                 return true;
2096             }
2097         }
2098 
2099         return false;
2100     }
2101 
2102     /////////////////// file pop possibilities helper ///////////////////////
2103 
isPopPossible_files(const std::set<OUString> & rDirs,const std::set<std::pair<OUString,OUString>> & rFiles,std::u16string_view rSourceURL,std::u16string_view rTargetURL)2104     bool BackupFileHelper::isPopPossible_files(
2105         const std::set< OUString >& rDirs,
2106         const std::set< std::pair< OUString, OUString > >& rFiles,
2107         std::u16string_view rSourceURL, // source dir without trailing '/'
2108         std::u16string_view rTargetURL // target dir without trailing '/'
2109         )
2110     {
2111         bool bPopPossible(false);
2112 
2113         // process files
2114         for (const auto& file : rFiles)
2115         {
2116             bPopPossible |= isPopPossible_file(
2117                 rSourceURL,
2118                 rTargetURL,
2119                 file.first,
2120                 file.second);
2121         }
2122 
2123         // process dirs
2124         for (const auto& dir : rDirs)
2125         {
2126             OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir);
2127             OUString aNewTargetURL(OUString::Concat(rTargetURL) + "/" + dir);
2128             std::set< OUString > aNewDirs;
2129             std::set< std::pair< OUString, OUString > > aNewFiles;
2130 
2131             DirectoryHelper::scanDirsAndFiles(
2132                 aNewSourceURL,
2133                 aNewDirs,
2134                 aNewFiles);
2135 
2136             if (!aNewDirs.empty() || !aNewFiles.empty())
2137             {
2138                 bPopPossible |= isPopPossible_files(
2139                     aNewDirs,
2140                     aNewFiles,
2141                     aNewSourceURL,
2142                     aNewTargetURL);
2143             }
2144         }
2145 
2146         return bPopPossible;
2147     }
2148 
isPopPossible_file(std::u16string_view rSourceURL,std::u16string_view rTargetURL,std::u16string_view rName,std::u16string_view rExt)2149     bool BackupFileHelper::isPopPossible_file(
2150         std::u16string_view rSourceURL, // source dir without trailing '/'
2151         std::u16string_view rTargetURL, // target dir without trailing '/'
2152         std::u16string_view rName,  // filename
2153         std::u16string_view rExt    // extension (or empty)
2154         )
2155     {
2156         const OUString aFileURL(createFileURL(rSourceURL, rName, rExt));
2157 
2158         if (DirectoryHelper::fileExists(aFileURL))
2159         {
2160             const OUString aPackURL(createPackURL(rTargetURL, rName));
2161             PackedFile aPackedFile(aPackURL);
2162 
2163             return !aPackedFile.empty();
2164         }
2165 
2166         return false;
2167     }
2168 
2169     /////////////////// file pop helpers ///////////////////////
2170 
tryPop_files(const std::set<OUString> & rDirs,const std::set<std::pair<OUString,OUString>> & rFiles,std::u16string_view rSourceURL,const OUString & rTargetURL)2171     bool BackupFileHelper::tryPop_files(
2172         const std::set< OUString >& rDirs,
2173         const std::set< std::pair< OUString, OUString > >& rFiles,
2174         std::u16string_view rSourceURL, // source dir without trailing '/'
2175         const OUString& rTargetURL  // target dir without trailing '/'
2176         )
2177     {
2178         bool bDidPop(false);
2179 
2180         // process files
2181         for (const auto& file : rFiles)
2182         {
2183             bDidPop |= tryPop_file(
2184                 rSourceURL,
2185                 rTargetURL,
2186                 file.first,
2187                 file.second);
2188         }
2189 
2190         // process dirs
2191         for (const auto& dir : rDirs)
2192         {
2193             OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir);
2194             OUString aNewTargetURL(rTargetURL + "/" + dir);
2195             std::set< OUString > aNewDirs;
2196             std::set< std::pair< OUString, OUString > > aNewFiles;
2197 
2198             DirectoryHelper::scanDirsAndFiles(
2199                 aNewSourceURL,
2200                 aNewDirs,
2201                 aNewFiles);
2202 
2203             if (!aNewDirs.empty() || !aNewFiles.empty())
2204             {
2205                 bDidPop |= tryPop_files(
2206                     aNewDirs,
2207                     aNewFiles,
2208                     aNewSourceURL,
2209                     aNewTargetURL);
2210             }
2211         }
2212 
2213         if (bDidPop)
2214         {
2215             // try removal of evtl. empty directory
2216             osl::Directory::remove(rTargetURL);
2217         }
2218 
2219         return bDidPop;
2220     }
2221 
tryPop_file(std::u16string_view rSourceURL,std::u16string_view rTargetURL,std::u16string_view rName,std::u16string_view rExt)2222     bool BackupFileHelper::tryPop_file(
2223         std::u16string_view rSourceURL, // source dir without trailing '/'
2224         std::u16string_view rTargetURL, // target dir without trailing '/'
2225         std::u16string_view rName,  // filename
2226         std::u16string_view rExt    // extension (or empty)
2227         )
2228     {
2229         const OUString aFileURL(createFileURL(rSourceURL, rName, rExt));
2230 
2231         if (DirectoryHelper::fileExists(aFileURL))
2232         {
2233             // try Pop for base file
2234             const OUString aPackURL(createPackURL(rTargetURL, rName));
2235             PackedFile aPackedFile(aPackURL);
2236 
2237             if (!aPackedFile.empty())
2238             {
2239                 oslFileHandle aHandle;
2240                 OUString aTempURL;
2241 
2242                 // open target temp file - it exists until deleted
2243                 if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL))
2244                 {
2245                     bool bRetval(aPackedFile.tryPop(aHandle));
2246 
2247                     // close temp file (in all cases) - it exists until deleted
2248                     osl_closeFile(aHandle);
2249 
2250                     if (bRetval)
2251                     {
2252                         // copy over existing file by first deleting original
2253                         // and moving the temp file to old original
2254                         osl::File::remove(aFileURL);
2255                         osl::File::move(aTempURL, aFileURL);
2256 
2257                         // reduce to allowed number and flush
2258                         aPackedFile.tryReduceToNumBackups(mnNumBackups);
2259                         aPackedFile.flush();
2260                     }
2261 
2262                     // delete temp file (in all cases - it may be moved already)
2263                     osl::File::remove(aTempURL);
2264 
2265                     return bRetval;
2266                 }
2267             }
2268         }
2269 
2270         return false;
2271     }
2272 
2273     /////////////////// ExtensionInfo helpers ///////////////////////
2274 
tryPush_extensionInfo(std::u16string_view rTargetURL)2275     bool BackupFileHelper::tryPush_extensionInfo(
2276         std::u16string_view rTargetURL // target dir without trailing '/'
2277         )
2278     {
2279         ExtensionInfo aExtensionInfo;
2280         OUString aTempURL;
2281         bool bRetval(false);
2282 
2283         // create current configuration and write to temp file - it exists until deleted
2284         if (aExtensionInfo.createTempFile(aTempURL))
2285         {
2286             const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo"));
2287             PackedFile aPackedFile(aPackURL);
2288             FileSharedPtr aBaseFile = std::make_shared<osl::File>(aTempURL);
2289 
2290             if (aPackedFile.tryPush(aBaseFile, mbCompress))
2291             {
2292                 // reduce to allowed number and flush
2293                 aPackedFile.tryReduceToNumBackups(mnNumBackups);
2294                 aPackedFile.flush();
2295                 bRetval = true;
2296             }
2297         }
2298 
2299         // delete temp file (in all cases)
2300         osl::File::remove(aTempURL);
2301         return bRetval;
2302     }
2303 
isPopPossible_extensionInfo(std::u16string_view rTargetURL)2304     bool BackupFileHelper::isPopPossible_extensionInfo(
2305         std::u16string_view rTargetURL // target dir without trailing '/'
2306         )
2307     {
2308         // extensionInfo always exists internally, no test needed
2309         const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo"));
2310         PackedFile aPackedFile(aPackURL);
2311 
2312         return !aPackedFile.empty();
2313     }
2314 
tryPop_extensionInfo(std::u16string_view rTargetURL)2315     bool BackupFileHelper::tryPop_extensionInfo(
2316         std::u16string_view rTargetURL // target dir without trailing '/'
2317         )
2318     {
2319         // extensionInfo always exists internally, no test needed
2320         const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo"));
2321         PackedFile aPackedFile(aPackURL);
2322 
2323         if (!aPackedFile.empty())
2324         {
2325             oslFileHandle aHandle;
2326             OUString aTempURL;
2327 
2328             // open target temp file - it exists until deleted
2329             if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL))
2330             {
2331                 bool bRetval(aPackedFile.tryPop(aHandle));
2332 
2333                 // close temp file (in all cases) - it exists until deleted
2334                 osl_closeFile(aHandle);
2335 
2336                 if (bRetval)
2337                 {
2338                     // last config is in temp file, load it to ExtensionInfo
2339                     ExtensionInfo aLoadedExtensionInfo;
2340                     FileSharedPtr aBaseFile = std::make_shared<osl::File>(aTempURL);
2341 
2342                     if (osl::File::E_None == aBaseFile->open(osl_File_OpenFlag_Read))
2343                     {
2344                         if (aLoadedExtensionInfo.read_entries(aBaseFile))
2345                         {
2346                             // get current extension info, but from XML config files
2347                             ExtensionInfo aCurrentExtensionInfo;
2348 
2349                             aCurrentExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL);
2350 
2351                             // now we have loaded last_working (aLoadedExtensionInfo) and
2352                             // current (aCurrentExtensionInfo) ExtensionInfo and may react on
2353                             // differences by de/activating these as needed
2354                             const ExtensionInfoEntryVector& aUserEntries = aCurrentExtensionInfo.getExtensionInfoEntryVector();
2355                             const ExtensionInfoEntryVector& rLoadedVector = aLoadedExtensionInfo.getExtensionInfoEntryVector();
2356                             ExtensionInfoEntryVector aToBeDisabled;
2357                             ExtensionInfoEntryVector aToBeEnabled;
2358 
2359                             for (const auto& rCurrentInfo : aUserEntries)
2360                             {
2361                                 const ExtensionInfoEntry* pLoadedInfo = nullptr;
2362 
2363                                 for (const auto& rLoadedInfo : rLoadedVector)
2364                                 {
2365                                     if (rCurrentInfo.isSameExtension(rLoadedInfo))
2366                                     {
2367                                         pLoadedInfo = &rLoadedInfo;
2368                                         break;
2369                                     }
2370                                 }
2371 
2372                                 if (nullptr != pLoadedInfo)
2373                                 {
2374                                     // loaded info contains information about the Extension rCurrentInfo
2375                                     const bool bCurrentEnabled(rCurrentInfo.isEnabled());
2376                                     const bool bLoadedEnabled(pLoadedInfo->isEnabled());
2377 
2378                                     if (bCurrentEnabled && !bLoadedEnabled)
2379                                     {
2380                                         aToBeDisabled.push_back(rCurrentInfo);
2381                                     }
2382                                     else if (!bCurrentEnabled && bLoadedEnabled)
2383                                     {
2384                                         aToBeEnabled.push_back(rCurrentInfo);
2385                                     }
2386                                 }
2387                                 else
2388                                 {
2389                                     // There is no loaded info about the Extension rCurrentInfo.
2390                                     // It needs to be disabled
2391                                     if (rCurrentInfo.isEnabled())
2392                                     {
2393                                         aToBeDisabled.push_back(rCurrentInfo);
2394                                     }
2395                                 }
2396                             }
2397 
2398                             if (!aToBeDisabled.empty() || !aToBeEnabled.empty())
2399                             {
2400                                 ExtensionInfo::changeEnableDisableStateInXML(maUserConfigWorkURL, aToBeEnabled, aToBeDisabled);
2401                             }
2402 
2403                             bRetval = true;
2404                         }
2405                     }
2406 
2407                     // reduce to allowed number and flush
2408                     aPackedFile.tryReduceToNumBackups(mnNumBackups);
2409                     aPackedFile.flush();
2410                 }
2411 
2412                 // delete temp file (in all cases - it may be moved already)
2413                 osl::File::remove(aTempURL);
2414 
2415                 return bRetval;
2416             }
2417         }
2418 
2419         return false;
2420     }
2421 
2422     /////////////////// FileDirInfo helpers ///////////////////////
2423 
fillDirFileInfo()2424     void BackupFileHelper::fillDirFileInfo()
2425     {
2426         if (!maDirs.empty() || !maFiles.empty())
2427         {
2428             // already done
2429             return;
2430         }
2431 
2432         // Information about the configuration and the role/purpose of directories in
2433         // the UserConfiguration is taken from: https://wiki.documentfoundation.org/UserProfile
2434 
2435         // fill dir and file info list to work with dependent on work mode
2436         switch (mnMode)
2437         {
2438         case 0:
2439         {
2440             // simple mode: add just registrymodifications
2441             // (the orig file in maInitialBaseURL)
2442             maFiles.insert(std::pair< OUString, OUString >(maRegModName, maExt));
2443             break;
2444         }
2445         case 1:
2446         {
2447             // defined mode: Add a selection of dirs containing User-Defined and thus
2448             // valuable configuration information.
2449             // This is clearly discussable in every single point and may be adapted/corrected
2450             // over time. Main focus is to secure User-Defined/adapted values
2451 
2452             // add registrymodifications (the orig file in maInitialBaseURL)
2453             maFiles.insert(std::pair< OUString, OUString >(maRegModName, maExt));
2454 
2455             // User-defined substitution table (Tools/AutoCorrect)
2456             maDirs.insert("autocorr");
2457 
2458             // User-Defined AutoText (Edit/AutoText)
2459             maDirs.insert("autotext");
2460 
2461             // User-defined Macros
2462             maDirs.insert("basic");
2463 
2464             // User-adapted toolbars for modules
2465             maDirs.insert("config");
2466 
2467             // Initial and User-defined Databases
2468             maDirs.insert("database");
2469 
2470             // most part of registry files
2471             maDirs.insert("registry");
2472 
2473             // User-Defined Scripts
2474             maDirs.insert("Scripts");
2475 
2476             // Template files
2477             maDirs.insert("template");
2478 
2479             // Custom Dictionaries
2480             maDirs.insert("wordbook");
2481 
2482             // Questionable - where and how is Extension stuff held and how
2483             // does this interact with enabled/disabled states which are extra handled?
2484             // Keep out of business until deeper evaluated
2485             //
2486             // maDirs.insert("extensions");
2487             // maDirs.insert("uno-packages");
2488             break;
2489         }
2490         case 2:
2491         {
2492             // whole directory. To do so, scan directory and exclude some dirs
2493             // from which we know they do not need to be secured explicitly. This
2494             // should already include registrymodifications, too.
2495             DirectoryHelper::scanDirsAndFiles(
2496                 maUserConfigWorkURL,
2497                 maDirs,
2498                 maFiles);
2499 
2500             // should not exist, but for the case an error occurred and it got
2501             // copied somehow, avoid further recursive copying/saving
2502             maDirs.erase("SafeMode");
2503 
2504             // not really needed, can be abandoned
2505             maDirs.erase("psprint");
2506 
2507             // not really needed, can be abandoned
2508             maDirs.erase("store");
2509 
2510             // not really needed, can be abandoned
2511             maDirs.erase("temp");
2512 
2513             // exclude own backup dir to avoid recursion
2514             maDirs.erase("pack");
2515 
2516             break;
2517         }
2518         }
2519     }
2520 }
2521 
2522 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2523