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