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/RuntimeException.hpp>
29 #include <com/sun/star/uno/Sequence.hxx>
30 #include <o3tl/safeint.hxx>
32 #include <osl/file.hxx>
33 #include <rtl/string.h>
34 #include <rtl/string.hxx>
35 #include <rtl/textcvt.h>
36 #include <rtl/textenc.h>
37 #include <rtl/ustring.hxx>
38 #include <rtl/strbuf.hxx>
39 #include <sal/log.hxx>
40 #include <sal/types.h>
41 #include <xmlreader/span.hxx>
44 #include "groupnode.hxx"
45 #include "localizedpropertynode.hxx"
46 #include "localizedvaluenode.hxx"
47 #include "modifications.hxx"
49 #include "nodemap.hxx"
50 #include "propertynode.hxx"
52 #include "writemodfile.hxx"
60 OString
convertToUtf8(std::u16string_view text
) {
62 assert(text
.size() <= o3tl::make_unsigned(std::numeric_limits
<sal_Int32
>::max()));
63 if (!rtl_convertUStringToString(
64 &s
.pData
, text
.data(), text
.size(),
65 RTL_TEXTENCODING_UTF8
,
66 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
|
67 RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
)))
69 throw css::uno::RuntimeException(
70 "cannot convert to UTF-8");
75 } // anonymous namespace
77 TempFile::~TempFile() {
78 if (handle
== nullptr)
82 oslFileError e
= osl_closeFile(handle
);
83 if (e
!= osl_File_E_None
) {
84 SAL_WARN("configmgr", "osl_closeFile failed with " << +e
);
87 osl::FileBase::RC e
= osl::File::remove(url
);
88 if (e
!= osl::FileBase::E_None
) {
91 "osl::File::remove(" << url
<< ") failed with " << +e
);
96 oslFileError
TempFile::closeWithoutUnlink() {
98 oslFileError e
= osl_closeFile(handle
);
105 void TempFile::closeAndRename(const OUString
&_url
) {
106 oslFileError e
= flush();
107 if (e
!= osl_File_E_None
) {
108 throw css::uno::RuntimeException(
109 "cannot write to " + url
);
111 e
= osl_closeFile(handle
);
113 if (e
!= osl_File_E_None
) {
114 throw css::uno::RuntimeException(
115 "cannot close " + url
);
117 if (osl::File::move(url
, _url
) != osl::FileBase::E_None
) {
118 throw css::uno::RuntimeException(
119 "cannot move " + url
);
124 oslFileError
TempFile::flush() {
125 oslFileError e
= osl_File_E_None
;
126 if (!buffer
.isEmpty()) {
127 sal_uInt64 nBytesWritten
= 0;
128 e
= osl_writeFile(handle
, buffer
.getStr(),
129 static_cast< sal_uInt32
>(buffer
.getLength()),
131 if (nBytesWritten
!= static_cast< sal_uInt32
>(buffer
.getLength())) {
132 // queue up any error / exception until close.
133 buffer
.remove(0, static_cast< sal_Int32
>( nBytesWritten
) );
141 void TempFile::writeString(std::string_view text
) {
142 buffer
.append(text
.data(), text
.size());
143 if (buffer
.getLength() > 0x10000)
149 void writeValueContent_(TempFile
&, bool) = delete;
150 // silence loplugin:salbool
151 void writeValueContent_(TempFile
&handle
, sal_Bool value
) {
153 handle
.writeString("true");
155 handle
.writeString("false");
159 void writeValueContent_(TempFile
&handle
, sal_Int16 value
) {
160 handle
.writeString(OString::number(value
));
163 void writeValueContent_(TempFile
&handle
, sal_Int32 value
) {
164 handle
.writeString(OString::number(value
));
167 void writeValueContent_(TempFile
&handle
, sal_Int64 value
) {
168 handle
.writeString(OString::number(value
));
171 void writeValueContent_(TempFile
&handle
, double value
) {
172 handle
.writeString(OString::number(value
));
175 void writeValueContent_(TempFile
&handle
, std::u16string_view value
) {
176 writeValueContent(handle
, value
);
179 void writeValueContent_(
180 TempFile
&handle
, css::uno::Sequence
< sal_Int8
> const & value
)
182 for (const auto & v
: value
) {
183 static char const hexDigit
[16] = {
184 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
187 std::string_view(hexDigit
+ ((v
>> 4) & 0xF), 1));
188 handle
.writeString(std::string_view(hexDigit
+ (v
& 0xF), 1));
192 template< typename T
> void writeSingleValue(
193 TempFile
&handle
, css::uno::Any
const & value
)
195 handle
.writeString(">");
198 writeValueContent_(handle
, val
);
199 handle
.writeString("</value>");
202 template< typename T
> void writeListValue(
203 TempFile
&handle
, css::uno::Any
const & value
)
205 handle
.writeString(">");
206 css::uno::Sequence
< T
> val
;
208 for (sal_Int32 i
= 0; i
< val
.getLength(); ++i
) {
210 handle
.writeString(" ");
212 writeValueContent_(handle
, val
[i
]);
214 handle
.writeString("</value>");
217 template< typename T
> void writeItemListValue(
218 TempFile
&handle
, css::uno::Any
const & value
)
220 handle
.writeString(">");
221 css::uno::Sequence
< T
> val
;
223 for (const auto & i
: val
) {
224 handle
.writeString("<it>");
225 writeValueContent_(handle
, i
);
226 handle
.writeString("</it>");
228 handle
.writeString("</value>");
231 void writeValue(TempFile
&handle
, Type type
, css::uno::Any
const & value
) {
234 writeSingleValue
< sal_Bool
>(handle
, value
);
237 writeSingleValue
< sal_Int16
>(handle
, value
);
240 writeSingleValue
< sal_Int32
>(handle
, value
);
243 writeSingleValue
< sal_Int64
>(handle
, value
);
246 writeSingleValue
< double >(handle
, value
);
249 writeSingleValue
< OUString
>(handle
, value
);
252 writeSingleValue
< css::uno::Sequence
< sal_Int8
> >(handle
, value
);
254 case TYPE_BOOLEAN_LIST
:
255 writeListValue
< sal_Bool
>(handle
, value
);
257 case TYPE_SHORT_LIST
:
258 writeListValue
< sal_Int16
>(handle
, value
);
261 writeListValue
< sal_Int32
>(handle
, value
);
264 writeListValue
< sal_Int64
>(handle
, value
);
266 case TYPE_DOUBLE_LIST
:
267 writeListValue
< double >(handle
, value
);
269 case TYPE_STRING_LIST
:
270 writeItemListValue
< OUString
>(handle
, value
);
272 case TYPE_HEXBINARY_LIST
:
273 writeItemListValue
< css::uno::Sequence
< sal_Int8
> >(handle
, value
);
275 default: // TYPE_ERROR, TYPE_NIL, TYPE_ANY
276 assert(false); // this cannot happen
281 Components
& components
, TempFile
&handle
,
282 rtl::Reference
< Node
> const & parent
, std::u16string_view name
,
283 rtl::Reference
< Node
> const & node
)
285 static xmlreader::Span
const typeNames
[] = {
286 xmlreader::Span(), xmlreader::Span(), xmlreader::Span(),
287 // TYPE_ERROR, TYPE_NIL, TYPE_ANY
288 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:boolean")),
289 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:short")),
290 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:int")),
291 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:long")),
292 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:double")),
293 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:string")),
294 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:hexBinary")),
295 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:boolean-list")),
296 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:short-list")),
297 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:int-list")),
298 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:long-list")),
299 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:double-list")),
300 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:string-list")),
301 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:hexBinary-list")) };
302 switch (node
->kind()) {
303 case Node::KIND_PROPERTY
:
305 PropertyNode
* prop
= static_cast< PropertyNode
* >(node
.get());
306 handle
.writeString("<prop oor:name=\"");
307 writeAttributeValue(handle
, name
);
308 handle
.writeString("\" oor:op=\"fuse\"");
309 Type type
= prop
->getStaticType();
310 Type dynType
= getDynamicType(prop
->getValue(components
));
311 assert(dynType
!= TYPE_ERROR
);
312 if (type
== TYPE_ANY
) {
314 if (type
!= TYPE_NIL
) {
315 handle
.writeString(" oor:type=\"");
318 typeNames
[type
].begin
, typeNames
[type
].length
));
319 handle
.writeString("\"");
322 handle
.writeString("><value");
323 if (dynType
== TYPE_NIL
) {
324 handle
.writeString(" xsi:nil=\"true\"/>");
326 writeValue(handle
, type
, prop
->getValue(components
));
328 handle
.writeString("</prop>");
331 case Node::KIND_LOCALIZED_PROPERTY
:
332 handle
.writeString("<prop oor:name=\"");
333 writeAttributeValue(handle
, name
);
334 handle
.writeString("\" oor:op=\"fuse\">");
335 for (auto const& member
: node
->getMembers())
337 writeNode(components
, handle
, node
, member
.first
, member
.second
);
339 handle
.writeString("</prop>");
341 case Node::KIND_LOCALIZED_VALUE
:
343 handle
.writeString("<value");
345 handle
.writeString(" xml:lang=\"");
346 writeAttributeValue(handle
, name
);
347 handle
.writeString("\"");
349 Type type
= static_cast< LocalizedPropertyNode
* >(parent
.get())->
352 static_cast< LocalizedValueNode
* >(node
.get())->getValue());
353 Type dynType
= getDynamicType(value
);
354 assert(dynType
!= TYPE_ERROR
);
355 if (type
== TYPE_ANY
) {
357 if (type
!= TYPE_NIL
) {
358 handle
.writeString(" oor:type=\"");
361 typeNames
[type
].begin
, typeNames
[type
].length
));
362 handle
.writeString("\"");
365 if (dynType
== TYPE_NIL
) {
366 handle
.writeString(" xsi:nil=\"true\"/>");
368 writeValue(handle
, type
, value
);
372 case Node::KIND_GROUP
:
374 handle
.writeString("<node oor:name=\"");
375 writeAttributeValue(handle
, name
);
376 if (!node
->getTemplateName().isEmpty()) { // set member
377 handle
.writeString("\" oor:op=\"replace");
379 handle
.writeString("\">");
380 for (auto const& member
: node
->getMembers())
382 writeNode(components
, handle
, node
, member
.first
, member
.second
);
384 handle
.writeString("</node>");
386 case Node::KIND_ROOT
:
387 assert(false); // this cannot happen
392 // helpers to allow sorting of configmgr::Modifications::Node
393 typedef std::pair
< const OUString
, configmgr::Modifications::Node
> ModNodePairEntry
;
394 struct PairEntrySorter
396 bool operator() (const ModNodePairEntry
* pValue1
, const ModNodePairEntry
* pValue2
) const
398 return pValue1
->first
.compareTo(pValue2
->first
) < 0;
402 void writeModifications(
403 Components
& components
, TempFile
&handle
,
404 std::u16string_view parentPathRepresentation
,
405 rtl::Reference
< Node
> const & parent
, OUString
const & nodeName
,
406 rtl::Reference
< Node
> const & node
,
407 Modifications::Node
const & modifications
)
409 // It is never necessary to write oor:finalized or oor:mandatory attributes,
410 // as they cannot be set via the UNO API.
411 if (modifications
.children
.empty()) {
413 // components themselves have no parent but must have children
414 handle
.writeString("<item oor:path=\"");
415 writeAttributeValue(handle
, parentPathRepresentation
);
416 handle
.writeString("\">");
418 writeNode(components
, handle
, parent
, nodeName
, node
);
420 switch (parent
->kind()) {
421 case Node::KIND_LOCALIZED_PROPERTY
:
422 handle
.writeString("<value");
423 if (!nodeName
.isEmpty()) {
424 handle
.writeString(" xml:lang=\"");
425 writeAttributeValue(handle
, nodeName
);
426 handle
.writeString("\"");
428 handle
.writeString(" oor:op=\"remove\"/>");
430 case Node::KIND_GROUP
:
432 static_cast< GroupNode
* >(parent
.get())->isExtensible());
433 handle
.writeString("<prop oor:name=\"");
434 writeAttributeValue(handle
, nodeName
);
435 handle
.writeString("\" oor:op=\"remove\"/>");
438 handle
.writeString("<node oor:name=\"");
439 writeAttributeValue(handle
, nodeName
);
440 handle
.writeString("\" oor:op=\"remove\"/>");
443 assert(false); // this cannot happen
447 handle
.writeString("</item>\n");
451 OUString::Concat(parentPathRepresentation
) + "/" +
452 Data::createSegment(node
->getTemplateName(), nodeName
));
454 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
455 // to just reference the data instead of copying it
456 std::vector
< const ModNodePairEntry
* > ModNodePairEntryVector
;
457 ModNodePairEntryVector
.reserve(modifications
.children
.size());
459 for (const auto& rCand
: modifications
.children
)
461 ModNodePairEntryVector
.push_back(&rCand
);
465 std::sort(ModNodePairEntryVector
.begin(), ModNodePairEntryVector
.end(), PairEntrySorter());
467 // now use the list to write entries in sorted order
468 // instead of random as from the unordered map
469 for (const auto & i
: ModNodePairEntryVector
)
472 components
, handle
, pathRep
, node
, i
->first
,
473 node
->getMember(i
->first
), i
->second
);
480 void writeAttributeValue(TempFile
&handle
, std::u16string_view value
) {
483 for (; j
!= value
.size(); ++j
) {
485 value
[j
] == 0x0009 || value
[j
] == 0x000A || value
[j
] == 0x000D ||
486 (value
[j
] >= 0x0020 && value
[j
] != 0xFFFE && value
[j
] != 0xFFFF));
489 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
490 handle
.writeString("	");
494 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
495 handle
.writeString("
");
499 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
500 handle
.writeString("
");
504 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
505 handle
.writeString(""");
509 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
510 handle
.writeString("&");
514 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
515 handle
.writeString("<");
522 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
525 void writeValueContent(TempFile
&handle
, std::u16string_view value
) {
528 for (; j
!= value
.size(); ++j
) {
529 char16_t c
= value
[j
];
530 if ((c
< 0x0020 && c
!= 0x0009 && c
!= 0x000A && c
!= 0x000D) ||
531 c
== 0xFFFE || c
== 0xFFFF)
533 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
534 handle
.writeString("<unicode oor:scalar=\"");
535 handle
.writeString(OString::number(c
));
536 handle
.writeString("\"/>");
538 } else if (c
== '\x0D') {
539 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
540 handle
.writeString("
");
542 } else if (c
== '&') {
543 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
544 handle
.writeString("&");
546 } else if (c
== '<') {
547 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
548 handle
.writeString("<");
550 } else if (c
== '>') {
551 // "MUST, for compatibility, be escaped [...] when it appears in the
553 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
554 handle
.writeString(">");
558 handle
.writeString(convertToUtf8(value
.substr(i
, j
- i
)));
562 Components
& components
, OUString
const & url
, Data
const & data
)
564 sal_Int32 i
= url
.lastIndexOf('/');
566 OUString
dir(url
.copy(0, i
));
567 switch (osl::Directory::createPath(dir
)) {
568 case osl::FileBase::E_None
:
569 case osl::FileBase::E_EXIST
:
571 case osl::FileBase::E_ACCES
:
574 ("cannot create registrymodifications.xcu path (E_ACCES); changes"
578 throw css::uno::RuntimeException(
579 "cannot create directory " + dir
);
582 switch (osl::FileBase::createTempFile(&dir
, &tmp
.handle
, &tmp
.url
)) {
583 case osl::FileBase::E_None
:
585 case osl::FileBase::E_ACCES
:
588 ("cannot create temp registrymodifications.xcu (E_ACCES); changes"
592 throw css::uno::RuntimeException(
593 "cannot create temporary file in " + dir
);
596 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
597 " xmlns:oor=\"http://openoffice.org/2001/registry\""
598 " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
599 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
600 //TODO: Do not write back information about those removed items that did not
601 // come from the .xcs/.xcu files, anyway (but had been added dynamically
604 // For profilesafemode it is necessary to detect changes in the
605 // registrymodifications file, this is done based on file size in bytes and crc32.
606 // Unfortunately this write is based on writing unordered map entries, which creates
607 // valid and semantically equal XML-Files, bubt with different crc32 checksums. For
608 // the future usage it will be preferable to have easily comparable config files
609 // which is guaranteed by writing the entries in sorted order. Indeed with this change
610 // (and in the recursive writeModifications call) the same config files get written
612 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
613 // to just reference the data instead of copying it
614 std::vector
< const ModNodePairEntry
* > ModNodePairEntryVector
;
615 ModNodePairEntryVector
.reserve(data
.modifications
.getRoot().children
.size());
617 for (const auto& rCand
: data
.modifications
.getRoot().children
)
619 ModNodePairEntryVector
.push_back(&rCand
);
623 std::sort(ModNodePairEntryVector
.begin(), ModNodePairEntryVector
.end(), PairEntrySorter());
625 // now use the list to write entries in sorted order
626 // instead of random as from the unordered map
627 for (const auto& j
: ModNodePairEntryVector
)
630 components
, tmp
, u
"", rtl::Reference
< Node
>(), j
->first
,
631 data
.getComponents().findNode(Data::NO_LAYER
, j
->first
),
634 tmp
.writeString("</oor:items>\n");
635 tmp
.closeAndRename(url
);
640 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */