Bump version to 6.0-36
[LibreOffice.git] / configmgr / source / writemodfile.cxx
blobe34b604dafd54ef05fa31eb0f47e7b3feed4cdbb
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <cassert>
23 #include <cstddef>
24 #include <limits>
26 #include <com/sun/star/uno/Any.hxx>
27 #include <com/sun/star/uno/Reference.hxx>
28 #include <com/sun/star/uno/RuntimeException.hpp>
29 #include <com/sun/star/uno/Sequence.hxx>
30 #include <com/sun/star/uno/XInterface.hpp>
31 #include <o3tl/string_view.hxx>
32 #include <osl/file.h>
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>
46 #include "data.hxx"
47 #include "groupnode.hxx"
48 #include "localizedpropertynode.hxx"
49 #include "localizedvaluenode.hxx"
50 #include "modifications.hxx"
51 #include "node.hxx"
52 #include "nodemap.hxx"
53 #include "propertynode.hxx"
54 #include "type.hxx"
55 #include "writemodfile.hxx"
57 namespace configmgr {
59 class Components;
61 namespace {
63 OString convertToUtf8(o3tl::u16string_view text) {
64 OString s;
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");
75 return s;
78 } // anonymous namespace
80 TempFile::~TempFile() {
81 if (handle != nullptr) {
82 if (!closed) {
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) {
90 SAL_WARN(
91 "configmgr",
92 "osl::File::remove(" << url << ") failed with " << +e);
97 #ifdef _WIN32
98 oslFileError TempFile::closeWithoutUnlink() {
99 flush();
100 oslFileError e = osl_closeFile(handle);
101 handle = nullptr;
102 closed = true;
103 return e;
105 #endif
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);
114 closed = true;
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);
123 handle = nullptr;
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()),
132 &nBytesWritten);
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 ) );
136 } else {
137 buffer.setLength(0);
140 return e;
143 void TempFile::writeString(o3tl::string_view text) {
144 buffer.append(text.data(), text.size());
145 if (buffer.getLength() > 0x10000)
146 flush();
149 namespace {
151 void writeValueContent_(TempFile &, bool) = delete;
152 // silence loplugin:salbool
153 void writeValueContent_(TempFile &handle, sal_Bool value) {
154 if (value) {
155 handle.writeString("true");
156 } else {
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',
187 'D', 'E', 'F' };
188 handle.writeString(
189 o3tl::string_view(hexDigit + ((value[i] >> 4) & 0xF), 1));
190 handle.writeString(o3tl::string_view(hexDigit + (value[i] & 0xF), 1));
194 template< typename T > void writeSingleValue(
195 TempFile &handle, css::uno::Any const & value)
197 handle.writeString(">");
198 T val = T();
199 value >>= val;
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;
209 value >>= val;
210 for (sal_Int32 i = 0; i < val.getLength(); ++i) {
211 if (i != 0) {
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;
224 value >>= 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) {
234 switch (type) {
235 case TYPE_BOOLEAN:
236 writeSingleValue< sal_Bool >(handle, value);
237 break;
238 case TYPE_SHORT:
239 writeSingleValue< sal_Int16 >(handle, value);
240 break;
241 case TYPE_INT:
242 writeSingleValue< sal_Int32 >(handle, value);
243 break;
244 case TYPE_LONG:
245 writeSingleValue< sal_Int64 >(handle, value);
246 break;
247 case TYPE_DOUBLE:
248 writeSingleValue< double >(handle, value);
249 break;
250 case TYPE_STRING:
251 writeSingleValue< OUString >(handle, value);
252 break;
253 case TYPE_HEXBINARY:
254 writeSingleValue< css::uno::Sequence< sal_Int8 > >(handle, value);
255 break;
256 case TYPE_BOOLEAN_LIST:
257 writeListValue< sal_Bool >(handle, value);
258 break;
259 case TYPE_SHORT_LIST:
260 writeListValue< sal_Int16 >(handle, value);
261 break;
262 case TYPE_INT_LIST:
263 writeListValue< sal_Int32 >(handle, value);
264 break;
265 case TYPE_LONG_LIST:
266 writeListValue< sal_Int64 >(handle, value);
267 break;
268 case TYPE_DOUBLE_LIST:
269 writeListValue< double >(handle, value);
270 break;
271 case TYPE_STRING_LIST:
272 writeItemListValue< OUString >(handle, value);
273 break;
274 case TYPE_HEXBINARY_LIST:
275 writeItemListValue< css::uno::Sequence< sal_Int8 > >(handle, value);
276 break;
277 default: // TYPE_ERROR, TYPE_NIL, TYPE_ANY
278 assert(false); // this cannot happen
282 void writeNode(
283 Components & components, TempFile &handle,
284 rtl::Reference< Node > const & parent, o3tl::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) {
315 type = dynType;
316 if (type != TYPE_NIL) {
317 handle.writeString(" oor:type=\"");
318 handle.writeString(
319 o3tl::string_view(
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\"/>");
327 } else {
328 writeValue(handle, type, prop->getValue(components));
330 handle.writeString("</prop>");
332 break;
333 case Node::KIND_LOCALIZED_PROPERTY:
334 handle.writeString("<prop oor:name=\"");
335 writeAttributeValue(handle, name);
336 handle.writeString("\" oor:op=\"fuse\">");
337 for (NodeMap::const_iterator i(node->getMembers().begin());
338 i != node->getMembers().end(); ++i)
340 writeNode(components, handle, node, i->first, i->second);
342 handle.writeString("</prop>");
343 break;
344 case Node::KIND_LOCALIZED_VALUE:
346 handle.writeString("<value");
347 if (!name.empty()) {
348 handle.writeString(" xml:lang=\"");
349 writeAttributeValue(handle, name);
350 handle.writeString("\"");
352 Type type = static_cast< LocalizedPropertyNode * >(parent.get())->
353 getStaticType();
354 css::uno::Any value(
355 static_cast< LocalizedValueNode * >(node.get())->getValue());
356 Type dynType = getDynamicType(value);
357 assert(dynType != TYPE_ERROR);
358 if (type == TYPE_ANY) {
359 type = dynType;
360 if (type != TYPE_NIL) {
361 handle.writeString(" oor:type=\"");
362 handle.writeString(
363 o3tl::string_view(
364 typeNames[type].begin, typeNames[type].length));
365 handle.writeString("\"");
368 if (dynType == TYPE_NIL) {
369 handle.writeString(" xsi:nil=\"true\"/>");
370 } else {
371 writeValue(handle, type, value);
374 break;
375 case Node::KIND_GROUP:
376 case Node::KIND_SET:
377 handle.writeString("<node oor:name=\"");
378 writeAttributeValue(handle, name);
379 if (!node->getTemplateName().isEmpty()) { // set member
380 handle.writeString("\" oor:op=\"replace");
382 handle.writeString("\">");
383 for (NodeMap::const_iterator i(node->getMembers().begin());
384 i != node->getMembers().end(); ++i)
386 writeNode(components, handle, node, i->first, i->second);
388 handle.writeString("</node>");
389 break;
390 case Node::KIND_ROOT:
391 assert(false); // this cannot happen
392 break;
396 // helpers to allow sorting of configmgr::Modifications::Node
397 typedef std::pair< const rtl::OUString, configmgr::Modifications::Node > ModNodePairEntry;
398 struct PairEntrySorter
400 bool operator() (const ModNodePairEntry* pValue1, const ModNodePairEntry* pValue2) const
402 return pValue1->first.compareTo(pValue2->first) < 0;
406 void writeModifications(
407 Components & components, TempFile &handle,
408 OUString const & parentPathRepresentation,
409 rtl::Reference< Node > const & parent, OUString const & nodeName,
410 rtl::Reference< Node > const & node,
411 Modifications::Node const & modifications)
413 // It is never necessary to write oor:finalized or oor:mandatory attributes,
414 // as they cannot be set via the UNO API.
415 if (modifications.children.empty()) {
416 assert(parent.is());
417 // components themselves have no parent but must have children
418 handle.writeString("<item oor:path=\"");
419 writeAttributeValue(handle, parentPathRepresentation);
420 handle.writeString("\">");
421 if (node.is()) {
422 writeNode(components, handle, parent, nodeName, node);
423 } else {
424 switch (parent->kind()) {
425 case Node::KIND_LOCALIZED_PROPERTY:
426 handle.writeString("<value");
427 if (!nodeName.isEmpty()) {
428 handle.writeString(" xml:lang=\"");
429 writeAttributeValue(handle, nodeName);
430 handle.writeString("\"");
432 handle.writeString(" oor:op=\"remove\"/>");
433 break;
434 case Node::KIND_GROUP:
435 assert(
436 static_cast< GroupNode * >(parent.get())->isExtensible());
437 handle.writeString("<prop oor:name=\"");
438 writeAttributeValue(handle, nodeName);
439 handle.writeString("\" oor:op=\"remove\"/>");
440 break;
441 case Node::KIND_SET:
442 handle.writeString("<node oor:name=\"");
443 writeAttributeValue(handle, nodeName);
444 handle.writeString("\" oor:op=\"remove\"/>");
445 break;
446 default:
447 assert(false); // this cannot happen
448 break;
451 handle.writeString("</item>\n");
452 } else {
453 assert(node.is());
454 OUString pathRep(
455 parentPathRepresentation + "/" +
456 Data::createSegment(node->getTemplateName(), nodeName));
458 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
459 // to just reference the data instead of copying it
460 std::vector< const ModNodePairEntry* > ModNodePairEntryVector;
461 ModNodePairEntryVector.reserve(modifications.children.size());
463 for (const auto& rCand : modifications.children)
465 ModNodePairEntryVector.push_back(&rCand);
468 // sort the list
469 std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter());
471 // now use the list to write entries in sorted order
472 // instead of random as from the unordered map
473 for (const auto & i : ModNodePairEntryVector)
475 writeModifications(
476 components, handle, pathRep, node, i->first,
477 node->getMember(i->first), i->second);
484 void writeAttributeValue(TempFile &handle, o3tl::u16string_view value) {
485 std::size_t i = 0;
486 std::size_t j = i;
487 for (; j != value.size(); ++j) {
488 assert(
489 value[j] == 0x0009 || value[j] == 0x000A || value[j] == 0x000D ||
490 (value[j] >= 0x0020 && value[j] != 0xFFFE && value[j] != 0xFFFF));
491 switch(value[j]) {
492 case '\x09':
493 handle.writeString(convertToUtf8(value.substr(i, j - i)));
494 handle.writeString("&#9;");
495 i = j + 1;
496 break;
497 case '\x0A':
498 handle.writeString(convertToUtf8(value.substr(i, j - i)));
499 handle.writeString("&#xA;");
500 i = j + 1;
501 break;
502 case '\x0D':
503 handle.writeString(convertToUtf8(value.substr(i, j - i)));
504 handle.writeString("&#xD;");
505 i = j + 1;
506 break;
507 case '"':
508 handle.writeString(convertToUtf8(value.substr(i, j - i)));
509 handle.writeString("&quot;");
510 i = j + 1;
511 break;
512 case '&':
513 handle.writeString(convertToUtf8(value.substr(i, j - i)));
514 handle.writeString("&amp;");
515 i = j + 1;
516 break;
517 case '<':
518 handle.writeString(convertToUtf8(value.substr(i, j - i)));
519 handle.writeString("&lt;");
520 i = j + 1;
521 break;
522 default:
523 break;
526 handle.writeString(convertToUtf8(value.substr(i, j - i)));
529 void writeValueContent(TempFile &handle, o3tl::u16string_view value) {
530 std::size_t i = 0;
531 std::size_t j = i;
532 for (; j != value.size(); ++j) {
533 char16_t c = value[j];
534 if ((c < 0x0020 && c != 0x0009 && c != 0x000A && c != 0x000D) ||
535 c == 0xFFFE || c == 0xFFFF)
537 handle.writeString(convertToUtf8(value.substr(i, j - i)));
538 handle.writeString("<unicode oor:scalar=\"");
539 handle.writeString(OString::number(c));
540 handle.writeString("\"/>");
541 i = j + 1;
542 } else if (c == '\x0D') {
543 handle.writeString(convertToUtf8(value.substr(i, j - i)));
544 handle.writeString("&#xD;");
545 i = j + 1;
546 } else if (c == '&') {
547 handle.writeString(convertToUtf8(value.substr(i, j - i)));
548 handle.writeString("&amp;");
549 i = j + 1;
550 } else if (c == '<') {
551 handle.writeString(convertToUtf8(value.substr(i, j - i)));
552 handle.writeString("&lt;");
553 i = j + 1;
554 } else if (c == '>') {
555 // "MUST, for compatibility, be escaped [...] when it appears in the
556 // string ']]>'":
557 handle.writeString(convertToUtf8(value.substr(i, j - i)));
558 handle.writeString("&gt;");
559 i = j + 1;
562 handle.writeString(convertToUtf8(value.substr(i, j - i)));
565 void writeModFile(
566 Components & components, OUString const & url, Data const & data)
568 sal_Int32 i = url.lastIndexOf('/');
569 assert(i != -1);
570 OUString dir(url.copy(0, i));
571 switch (osl::Directory::createPath(dir)) {
572 case osl::FileBase::E_None:
573 case osl::FileBase::E_EXIST:
574 break;
575 case osl::FileBase::E_ACCES:
576 SAL_INFO(
577 "configmgr",
578 ("cannot create registrymodifications.xcu path (E_ACCES); changes"
579 " will be lost"));
580 return;
581 default:
582 throw css::uno::RuntimeException(
583 "cannot create directory " + dir);
585 TempFile tmp;
586 switch (osl::FileBase::createTempFile(&dir, &tmp.handle, &tmp.url)) {
587 case osl::FileBase::E_None:
588 break;
589 case osl::FileBase::E_ACCES:
590 SAL_INFO(
591 "configmgr",
592 ("cannot create temp registrymodifications.xcu (E_ACCES); changes"
593 " will be lost"));
594 return;
595 default:
596 throw css::uno::RuntimeException(
597 "cannot create temporary file in " + dir);
599 tmp.writeString(
600 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
601 " xmlns:oor=\"http://openoffice.org/2001/registry\""
602 " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
603 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
604 //TODO: Do not write back information about those removed items that did not
605 // come from the .xcs/.xcu files, anyway (but had been added dynamically
606 // instead):
608 // For profilesafemode it is necessary to detect changes in the
609 // registrymodifications file, this is done based on file size in bytes and crc32.
610 // Unfortunately this write is based on writing unordered map entries, which creates
611 // valid and semantically equal XML-Files, bubt with different crc32 checksums. For
612 // the future usage it will be preferable to have easily comparable config files
613 // which is guaranteed by writing the entries in sorted order. Indeed with this change
614 // (and in the recursive writeModifications call) the same config files get written
616 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
617 // to just reference the data instead of copying it
618 std::vector< const ModNodePairEntry* > ModNodePairEntryVector;
619 ModNodePairEntryVector.reserve(data.modifications.getRoot().children.size());
621 for (const auto& rCand : data.modifications.getRoot().children)
623 ModNodePairEntryVector.push_back(&rCand);
626 // sort the list
627 std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter());
629 // now use the list to write entries in sorted order
630 // instead of random as from the unordered map
631 for (const auto& j : ModNodePairEntryVector)
633 writeModifications(
634 components, tmp, "", rtl::Reference< Node >(), j->first,
635 data.getComponents().findNode(Data::NO_LAYER, j->first),
636 j->second);
638 tmp.writeString("</oor:items>\n");
639 tmp.closeAndRename(url);
644 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */