1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
25 #include <string_view>
27 #include <com/sun/star/uno/Any.hxx>
28 #include <com/sun/star/uno/Reference.hxx>
29 #include <com/sun/star/uno/RuntimeException.hpp>
30 #include <com/sun/star/uno/Sequence.hxx>
31 #include <com/sun/star/uno/XInterface.hpp>
33 #include <osl/file.hxx>
34 #include <rtl/string.h>
35 #include <rtl/string.hxx>
36 #include <rtl/textcvt.h>
37 #include <rtl/textenc.h>
38 #include <rtl/ustrbuf.hxx>
39 #include <rtl/ustring.h>
40 #include <rtl/ustring.hxx>
41 #include <rtl/strbuf.hxx>
42 #include <sal/log.hxx>
43 #include <sal/types.h>
44 #include <xmlreader/span.hxx>
47 #include "groupnode.hxx"
48 #include "localizedpropertynode.hxx"
49 #include "localizedvaluenode.hxx"
50 #include "modifications.hxx"
52 #include "nodemap.hxx"
53 #include "propertynode.hxx"
55 #include "writemodfile.hxx"
63 OString
convertToUtf8(std::u16string_view text
) {
65 assert(text
.size() <= sal_uInt32(std::numeric_limits
<sal_Int32
>::max()));
66 if (!rtl_convertUStringToString(
67 &s
.pData
, text
.data(), text
.size(),
68 RTL_TEXTENCODING_UTF8
,
69 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
|
70 RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
)))
72 throw css::uno::RuntimeException(
73 "cannot convert to UTF-8");
78 } // anonymous namespace
80 TempFile::~TempFile() {
81 if (handle
!= nullptr) {
83 oslFileError e
= osl_closeFile(handle
);
84 if (e
!= osl_File_E_None
) {
85 SAL_WARN("configmgr", "osl_closeFile failed with " << +e
);
88 osl::FileBase::RC e
= osl::File::remove(url
);
89 if (e
!= osl::FileBase::E_None
) {
92 "osl::File::remove(" << url
<< ") failed with " << +e
);
98 oslFileError
TempFile::closeWithoutUnlink() {
100 oslFileError e
= osl_closeFile(handle
);
107 void TempFile::closeAndRename(const OUString
&_url
) {
108 oslFileError e
= flush();
109 if (e
!= osl_File_E_None
) {
110 throw css::uno::RuntimeException(
111 "cannot write to " + url
);
113 e
= osl_closeFile(handle
);
115 if (e
!= osl_File_E_None
) {
116 throw css::uno::RuntimeException(
117 "cannot close " + url
);
119 if (osl::File::move(url
, _url
) != osl::FileBase::E_None
) {
120 throw css::uno::RuntimeException(
121 "cannot move " + url
);
126 oslFileError
TempFile::flush() {
127 oslFileError e
= osl_File_E_None
;
128 if (!buffer
.isEmpty()) {
129 sal_uInt64 nBytesWritten
= 0;
130 e
= osl_writeFile(handle
, buffer
.getStr(),
131 static_cast< sal_uInt32
>(buffer
.getLength()),
133 if (nBytesWritten
!= static_cast< sal_uInt32
>(buffer
.getLength())) {
134 // queue up any error / exception until close.
135 buffer
.remove(0, static_cast< sal_Int32
>( nBytesWritten
) );
143 void TempFile::writeString(std::string_view text
) {
144 buffer
.append(text
.data(), text
.size());
145 if (buffer
.getLength() > 0x10000)
151 void writeValueContent_(TempFile
&, bool) = delete;
152 // silence loplugin:salbool
153 void writeValueContent_(TempFile
&handle
, sal_Bool value
) {
155 handle
.writeString("true");
157 handle
.writeString("false");
161 void writeValueContent_(TempFile
&handle
, sal_Int16 value
) {
162 handle
.writeString(OString::number(value
));
165 void writeValueContent_(TempFile
&handle
, sal_Int32 value
) {
166 handle
.writeString(OString::number(value
));
169 void writeValueContent_(TempFile
&handle
, sal_Int64 value
) {
170 handle
.writeString(OString::number(value
));
173 void writeValueContent_(TempFile
&handle
, double value
) {
174 handle
.writeString(OString::number(value
));
177 void writeValueContent_(TempFile
&handle
, const OUString
& value
) {
178 writeValueContent(handle
, value
);
181 void writeValueContent_(
182 TempFile
&handle
, css::uno::Sequence
< sal_Int8
> const & value
)
184 for (sal_Int32 i
= 0; i
< value
.getLength(); ++i
) {
185 static char const hexDigit
[16] = {
186 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
189 std::string_view(hexDigit
+ ((value
[i
] >> 4) & 0xF), 1));
190 handle
.writeString(std::string_view(hexDigit
+ (value
[i
] & 0xF), 1));
194 template< typename T
> void writeSingleValue(
195 TempFile
&handle
, css::uno::Any
const & value
)
197 handle
.writeString(">");
200 writeValueContent_(handle
, val
);
201 handle
.writeString("</value>");
204 template< typename T
> void writeListValue(
205 TempFile
&handle
, css::uno::Any
const & value
)
207 handle
.writeString(">");
208 css::uno::Sequence
< T
> val
;
210 for (sal_Int32 i
= 0; i
< val
.getLength(); ++i
) {
212 handle
.writeString(" ");
214 writeValueContent_(handle
, val
[i
]);
216 handle
.writeString("</value>");
219 template< typename T
> void writeItemListValue(
220 TempFile
&handle
, css::uno::Any
const & value
)
222 handle
.writeString(">");
223 css::uno::Sequence
< T
> val
;
225 for (sal_Int32 i
= 0; i
< val
.getLength(); ++i
) {
226 handle
.writeString("<it>");
227 writeValueContent_(handle
, val
[i
]);
228 handle
.writeString("</it>");
230 handle
.writeString("</value>");
233 void writeValue(TempFile
&handle
, Type type
, css::uno::Any
const & value
) {
236 writeSingleValue
< sal_Bool
>(handle
, value
);
239 writeSingleValue
< sal_Int16
>(handle
, value
);
242 writeSingleValue
< sal_Int32
>(handle
, value
);
245 writeSingleValue
< sal_Int64
>(handle
, value
);
248 writeSingleValue
< double >(handle
, value
);
251 writeSingleValue
< OUString
>(handle
, value
);
254 writeSingleValue
< css::uno::Sequence
< sal_Int8
> >(handle
, value
);
256 case TYPE_BOOLEAN_LIST
:
257 writeListValue
< sal_Bool
>(handle
, value
);
259 case TYPE_SHORT_LIST
:
260 writeListValue
< sal_Int16
>(handle
, value
);
263 writeListValue
< sal_Int32
>(handle
, value
);
266 writeListValue
< sal_Int64
>(handle
, value
);
268 case TYPE_DOUBLE_LIST
:
269 writeListValue
< double >(handle
, value
);
271 case TYPE_STRING_LIST
:
272 writeItemListValue
< OUString
>(handle
, value
);
274 case TYPE_HEXBINARY_LIST
:
275 writeItemListValue
< css::uno::Sequence
< sal_Int8
> >(handle
, value
);
277 default: // TYPE_ERROR, TYPE_NIL, TYPE_ANY
278 assert(false); // this cannot happen
283 Components
& components
, TempFile
&handle
,
284 rtl::Reference
< Node
> const & parent
, std::u16string_view name
,
285 rtl::Reference
< Node
> const & node
)
287 static xmlreader::Span
const typeNames
[] = {
288 xmlreader::Span(), xmlreader::Span(), xmlreader::Span(),
289 // TYPE_ERROR, TYPE_NIL, TYPE_ANY
290 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:boolean")),
291 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:short")),
292 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:int")),
293 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:long")),
294 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:double")),
295 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:string")),
296 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:hexBinary")),
297 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:boolean-list")),
298 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:short-list")),
299 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:int-list")),
300 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:long-list")),
301 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:double-list")),
302 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:string-list")),
303 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:hexBinary-list")) };
304 switch (node
->kind()) {
305 case Node::KIND_PROPERTY
:
307 PropertyNode
* prop
= static_cast< PropertyNode
* >(node
.get());
308 handle
.writeString("<prop oor:name=\"");
309 writeAttributeValue(handle
, name
);
310 handle
.writeString("\" oor:op=\"fuse\"");
311 Type type
= prop
->getStaticType();
312 Type dynType
= getDynamicType(prop
->getValue(components
));
313 assert(dynType
!= TYPE_ERROR
);
314 if (type
== TYPE_ANY
) {
316 if (type
!= TYPE_NIL
) {
317 handle
.writeString(" oor:type=\"");
320 typeNames
[type
].begin
, typeNames
[type
].length
));
321 handle
.writeString("\"");
324 handle
.writeString("><value");
325 if (dynType
== TYPE_NIL
) {
326 handle
.writeString(" xsi:nil=\"true\"/>");
328 writeValue(handle
, type
, prop
->getValue(components
));
330 handle
.writeString("</prop>");
333 case Node::KIND_LOCALIZED_PROPERTY
:
334 handle
.writeString("<prop oor:name=\"");
335 writeAttributeValue(handle
, name
);
336 handle
.writeString("\" oor:op=\"fuse\">");
337 for (auto const& member
: node
->getMembers())
339 writeNode(components
, handle
, node
, member
.first
, member
.second
);
341 handle
.writeString("</prop>");
343 case Node::KIND_LOCALIZED_VALUE
:
345 handle
.writeString("<value");
347 handle
.writeString(" xml:lang=\"");
348 writeAttributeValue(handle
, name
);
349 handle
.writeString("\"");
351 Type type
= static_cast< LocalizedPropertyNode
* >(parent
.get())->
354 static_cast< LocalizedValueNode
* >(node
.get())->getValue());
355 Type dynType
= getDynamicType(value
);
356 assert(dynType
!= TYPE_ERROR
);
357 if (type
== TYPE_ANY
) {
359 if (type
!= TYPE_NIL
) {
360 handle
.writeString(" oor:type=\"");
363 typeNames
[type
].begin
, typeNames
[type
].length
));
364 handle
.writeString("\"");
367 if (dynType
== TYPE_NIL
) {
368 handle
.writeString(" xsi:nil=\"true\"/>");
370 writeValue(handle
, type
, value
);
374 case Node::KIND_GROUP
:
376 handle
.writeString("<node oor:name=\"");
377 writeAttributeValue(handle
, name
);
378 if (!node
->getTemplateName().isEmpty()) { // set member
379 handle
.writeString("\" oor:op=\"replace");
381 handle
.writeString("\">");
382 for (auto const& member
: node
->getMembers())
384 writeNode(components
, handle
, node
, member
.first
, member
.second
);
386 handle
.writeString("</node>");
388 case Node::KIND_ROOT
:
389 assert(false); // this cannot happen
394 // helpers to allow sorting of configmgr::Modifications::Node
395 typedef std::pair
< const OUString
, configmgr::Modifications::Node
> ModNodePairEntry
;
396 struct PairEntrySorter
398 bool operator() (const ModNodePairEntry
* pValue1
, const ModNodePairEntry
* pValue2
) const
400 return pValue1
->first
.compareTo(pValue2
->first
) < 0;
404 void writeModifications(
405 Components
& components
, TempFile
&handle
,
406 OUString
const & parentPathRepresentation
,
407 rtl::Reference
< Node
> const & parent
, OUString
const & nodeName
,
408 rtl::Reference
< Node
> const & node
,
409 Modifications::Node
const & modifications
)
411 // It is never necessary to write oor:finalized or oor:mandatory attributes,
412 // as they cannot be set via the UNO API.
413 if (modifications
.children
.empty()) {
415 // components themselves have no parent but must have children
416 handle
.writeString("<item oor:path=\"");
417 writeAttributeValue(handle
, parentPathRepresentation
);
418 handle
.writeString("\">");
420 writeNode(components
, handle
, parent
, nodeName
, node
);
422 switch (parent
->kind()) {
423 case Node::KIND_LOCALIZED_PROPERTY
:
424 handle
.writeString("<value");
425 if (!nodeName
.isEmpty()) {
426 handle
.writeString(" xml:lang=\"");
427 writeAttributeValue(handle
, nodeName
);
428 handle
.writeString("\"");
430 handle
.writeString(" oor:op=\"remove\"/>");
432 case Node::KIND_GROUP
:
434 static_cast< GroupNode
* >(parent
.get())->isExtensible());
435 handle
.writeString("<prop oor:name=\"");
436 writeAttributeValue(handle
, nodeName
);
437 handle
.writeString("\" oor:op=\"remove\"/>");
440 handle
.writeString("<node oor:name=\"");
441 writeAttributeValue(handle
, nodeName
);
442 handle
.writeString("\" oor:op=\"remove\"/>");
445 assert(false); // this cannot happen
449 handle
.writeString("</item>\n");
453 parentPathRepresentation
+ "/" +
454 Data::createSegment(node
->getTemplateName(), nodeName
));
456 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
457 // to just reference the data instead of copying it
458 std::vector
< const ModNodePairEntry
* > ModNodePairEntryVector
;
459 ModNodePairEntryVector
.reserve(modifications
.children
.size());
461 for (const auto& rCand
: modifications
.children
)
463 ModNodePairEntryVector
.push_back(&rCand
);
467 std::sort(ModNodePairEntryVector
.begin(), ModNodePairEntryVector
.end(), PairEntrySorter());
469 // now use the list to write entries in sorted order
470 // instead of random as from the unordered map
471 for (const auto & i
: ModNodePairEntryVector
)
474 components
, handle
, pathRep
, node
, i
->first
,
475 node
->getMember(i
->first
), i
->second
);
482 void writeAttributeValue(TempFile
&handle
, std::u16string_view value
) {
485 for (; j
!= value
.size(); ++j
) {
487 value
[j
] == 0x0009 || value
[j
] == 0x000A || value
[j
] == 0x000D ||
488 (value
[j
] >= 0x0020 && value
[j
] != 0xFFFE && value
[j
] != 0xFFFF));
491 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
492 handle
.writeString("	");
496 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
497 handle
.writeString("
");
501 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
502 handle
.writeString("
");
506 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
507 handle
.writeString(""");
511 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
512 handle
.writeString("&");
516 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
517 handle
.writeString("<");
524 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
527 void writeValueContent(TempFile
&handle
, std::u16string_view value
) {
530 for (; j
!= value
.size(); ++j
) {
531 char16_t c
= value
[j
];
532 if ((c
< 0x0020 && c
!= 0x0009 && c
!= 0x000A && c
!= 0x000D) ||
533 c
== 0xFFFE || c
== 0xFFFF)
535 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
536 handle
.writeString("<unicode oor:scalar=\"");
537 handle
.writeString(OString::number(c
));
538 handle
.writeString("\"/>");
540 } else if (c
== '\x0D') {
541 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
542 handle
.writeString("
");
544 } else if (c
== '&') {
545 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
546 handle
.writeString("&");
548 } else if (c
== '<') {
549 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
550 handle
.writeString("<");
552 } else if (c
== '>') {
553 // "MUST, for compatibility, be escaped [...] when it appears in the
555 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
556 handle
.writeString(">");
560 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
564 Components
& components
, OUString
const & url
, Data
const & data
)
566 sal_Int32 i
= url
.lastIndexOf('/');
568 OUString
dir(url
.copy(0, i
));
569 switch (osl::Directory::createPath(dir
)) {
570 case osl::FileBase::E_None
:
571 case osl::FileBase::E_EXIST
:
573 case osl::FileBase::E_ACCES
:
576 ("cannot create registrymodifications.xcu path (E_ACCES); changes"
580 throw css::uno::RuntimeException(
581 "cannot create directory " + dir
);
584 switch (osl::FileBase::createTempFile(&dir
, &tmp
.handle
, &tmp
.url
)) {
585 case osl::FileBase::E_None
:
587 case osl::FileBase::E_ACCES
:
590 ("cannot create temp registrymodifications.xcu (E_ACCES); changes"
594 throw css::uno::RuntimeException(
595 "cannot create temporary file in " + dir
);
598 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
599 " xmlns:oor=\"http://openoffice.org/2001/registry\""
600 " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
601 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
602 //TODO: Do not write back information about those removed items that did not
603 // come from the .xcs/.xcu files, anyway (but had been added dynamically
606 // For profilesafemode it is necessary to detect changes in the
607 // registrymodifications file, this is done based on file size in bytes and crc32.
608 // Unfortunately this write is based on writing unordered map entries, which creates
609 // valid and semantically equal XML-Files, bubt with different crc32 checksums. For
610 // the future usage it will be preferable to have easily comparable config files
611 // which is guaranteed by writing the entries in sorted order. Indeed with this change
612 // (and in the recursive writeModifications call) the same config files get written
614 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
615 // to just reference the data instead of copying it
616 std::vector
< const ModNodePairEntry
* > ModNodePairEntryVector
;
617 ModNodePairEntryVector
.reserve(data
.modifications
.getRoot().children
.size());
619 for (const auto& rCand
: data
.modifications
.getRoot().children
)
621 ModNodePairEntryVector
.push_back(&rCand
);
625 std::sort(ModNodePairEntryVector
.begin(), ModNodePairEntryVector
.end(), PairEntrySorter());
627 // now use the list to write entries in sorted order
628 // instead of random as from the unordered map
629 for (const auto& j
: ModNodePairEntryVector
)
632 components
, tmp
, "", rtl::Reference
< Node
>(), j
->first
,
633 data
.getComponents().findNode(Data::NO_LAYER
, j
->first
),
636 tmp
.writeString("</oor:items>\n");
637 tmp
.closeAndRename(url
);
642 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */