Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / configmgr / source / writemodfile.cxx
blob017e925dee48673b3d76916071890697519c4bf9
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>
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 <osl/file.h>
31 #include <osl/file.hxx>
32 #include <rtl/string.h>
33 #include <rtl/string.hxx>
34 #include <rtl/textcvt.h>
35 #include <rtl/textenc.h>
36 #include <rtl/ustring.hxx>
37 #include <rtl/strbuf.hxx>
38 #include <sal/log.hxx>
39 #include <sal/types.h>
40 #include <xmlreader/span.hxx>
42 #include "data.hxx"
43 #include "groupnode.hxx"
44 #include "localizedpropertynode.hxx"
45 #include "localizedvaluenode.hxx"
46 #include "modifications.hxx"
47 #include "node.hxx"
48 #include "nodemap.hxx"
49 #include "propertynode.hxx"
50 #include "type.hxx"
51 #include "writemodfile.hxx"
53 namespace configmgr {
55 class Components;
57 namespace {
59 OString convertToUtf8(std::u16string_view text) {
60 OString s;
61 assert(text.size() <= sal_uInt32(std::numeric_limits<sal_Int32>::max()));
62 if (!rtl_convertUStringToString(
63 &s.pData, text.data(), text.size(),
64 RTL_TEXTENCODING_UTF8,
65 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
66 RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
68 throw css::uno::RuntimeException(
69 "cannot convert to UTF-8");
71 return s;
74 } // anonymous namespace
76 TempFile::~TempFile() {
77 if (handle != nullptr) {
78 if (!closed) {
79 oslFileError e = osl_closeFile(handle);
80 if (e != osl_File_E_None) {
81 SAL_WARN("configmgr", "osl_closeFile failed with " << +e);
84 osl::FileBase::RC e = osl::File::remove(url);
85 if (e != osl::FileBase::E_None) {
86 SAL_WARN(
87 "configmgr",
88 "osl::File::remove(" << url << ") failed with " << +e);
93 #ifdef _WIN32
94 oslFileError TempFile::closeWithoutUnlink() {
95 flush();
96 oslFileError e = osl_closeFile(handle);
97 handle = nullptr;
98 closed = true;
99 return e;
101 #endif
103 void TempFile::closeAndRename(const OUString &_url) {
104 oslFileError e = flush();
105 if (e != osl_File_E_None) {
106 throw css::uno::RuntimeException(
107 "cannot write to " + url);
109 e = osl_closeFile(handle);
110 closed = true;
111 if (e != osl_File_E_None) {
112 throw css::uno::RuntimeException(
113 "cannot close " + url);
115 if (osl::File::move(url, _url) != osl::FileBase::E_None) {
116 throw css::uno::RuntimeException(
117 "cannot move " + url);
119 handle = nullptr;
122 oslFileError TempFile::flush() {
123 oslFileError e = osl_File_E_None;
124 if (!buffer.isEmpty()) {
125 sal_uInt64 nBytesWritten = 0;
126 e = osl_writeFile(handle, buffer.getStr(),
127 static_cast< sal_uInt32 >(buffer.getLength()),
128 &nBytesWritten);
129 if (nBytesWritten != static_cast< sal_uInt32 >(buffer.getLength())) {
130 // queue up any error / exception until close.
131 buffer.remove(0, static_cast< sal_Int32 >( nBytesWritten ) );
132 } else {
133 buffer.setLength(0);
136 return e;
139 void TempFile::writeString(std::string_view text) {
140 buffer.append(text.data(), text.size());
141 if (buffer.getLength() > 0x10000)
142 flush();
145 namespace {
147 void writeValueContent_(TempFile &, bool) = delete;
148 // silence loplugin:salbool
149 void writeValueContent_(TempFile &handle, sal_Bool value) {
150 if (value) {
151 handle.writeString("true");
152 } else {
153 handle.writeString("false");
157 void writeValueContent_(TempFile &handle, sal_Int16 value) {
158 handle.writeString(OString::number(value));
161 void writeValueContent_(TempFile &handle, sal_Int32 value) {
162 handle.writeString(OString::number(value));
165 void writeValueContent_(TempFile &handle, sal_Int64 value) {
166 handle.writeString(OString::number(value));
169 void writeValueContent_(TempFile &handle, double value) {
170 handle.writeString(OString::number(value));
173 void writeValueContent_(TempFile &handle, const OUString& value) {
174 writeValueContent(handle, value);
177 void writeValueContent_(
178 TempFile &handle, css::uno::Sequence< sal_Int8 > const & value)
180 for (sal_Int32 i = 0; i < value.getLength(); ++i) {
181 static char const hexDigit[16] = {
182 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
183 'D', 'E', 'F' };
184 handle.writeString(
185 std::string_view(hexDigit + ((value[i] >> 4) & 0xF), 1));
186 handle.writeString(std::string_view(hexDigit + (value[i] & 0xF), 1));
190 template< typename T > void writeSingleValue(
191 TempFile &handle, css::uno::Any const & value)
193 handle.writeString(">");
194 T val = T();
195 value >>= val;
196 writeValueContent_(handle, val);
197 handle.writeString("</value>");
200 template< typename T > void writeListValue(
201 TempFile &handle, css::uno::Any const & value)
203 handle.writeString(">");
204 css::uno::Sequence< T > val;
205 value >>= val;
206 for (sal_Int32 i = 0; i < val.getLength(); ++i) {
207 if (i != 0) {
208 handle.writeString(" ");
210 writeValueContent_(handle, val[i]);
212 handle.writeString("</value>");
215 template< typename T > void writeItemListValue(
216 TempFile &handle, css::uno::Any const & value)
218 handle.writeString(">");
219 css::uno::Sequence< T > val;
220 value >>= val;
221 for (sal_Int32 i = 0; i < val.getLength(); ++i) {
222 handle.writeString("<it>");
223 writeValueContent_(handle, val[i]);
224 handle.writeString("</it>");
226 handle.writeString("</value>");
229 void writeValue(TempFile &handle, Type type, css::uno::Any const & value) {
230 switch (type) {
231 case TYPE_BOOLEAN:
232 writeSingleValue< sal_Bool >(handle, value);
233 break;
234 case TYPE_SHORT:
235 writeSingleValue< sal_Int16 >(handle, value);
236 break;
237 case TYPE_INT:
238 writeSingleValue< sal_Int32 >(handle, value);
239 break;
240 case TYPE_LONG:
241 writeSingleValue< sal_Int64 >(handle, value);
242 break;
243 case TYPE_DOUBLE:
244 writeSingleValue< double >(handle, value);
245 break;
246 case TYPE_STRING:
247 writeSingleValue< OUString >(handle, value);
248 break;
249 case TYPE_HEXBINARY:
250 writeSingleValue< css::uno::Sequence< sal_Int8 > >(handle, value);
251 break;
252 case TYPE_BOOLEAN_LIST:
253 writeListValue< sal_Bool >(handle, value);
254 break;
255 case TYPE_SHORT_LIST:
256 writeListValue< sal_Int16 >(handle, value);
257 break;
258 case TYPE_INT_LIST:
259 writeListValue< sal_Int32 >(handle, value);
260 break;
261 case TYPE_LONG_LIST:
262 writeListValue< sal_Int64 >(handle, value);
263 break;
264 case TYPE_DOUBLE_LIST:
265 writeListValue< double >(handle, value);
266 break;
267 case TYPE_STRING_LIST:
268 writeItemListValue< OUString >(handle, value);
269 break;
270 case TYPE_HEXBINARY_LIST:
271 writeItemListValue< css::uno::Sequence< sal_Int8 > >(handle, value);
272 break;
273 default: // TYPE_ERROR, TYPE_NIL, TYPE_ANY
274 assert(false); // this cannot happen
278 void writeNode(
279 Components & components, TempFile &handle,
280 rtl::Reference< Node > const & parent, std::u16string_view name,
281 rtl::Reference< Node > const & node)
283 static xmlreader::Span const typeNames[] = {
284 xmlreader::Span(), xmlreader::Span(), xmlreader::Span(),
285 // TYPE_ERROR, TYPE_NIL, TYPE_ANY
286 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:boolean")),
287 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:short")),
288 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:int")),
289 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:long")),
290 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:double")),
291 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:string")),
292 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("xs:hexBinary")),
293 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:boolean-list")),
294 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:short-list")),
295 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:int-list")),
296 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:long-list")),
297 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:double-list")),
298 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:string-list")),
299 xmlreader::Span(RTL_CONSTASCII_STRINGPARAM("oor:hexBinary-list")) };
300 switch (node->kind()) {
301 case Node::KIND_PROPERTY:
303 PropertyNode * prop = static_cast< PropertyNode * >(node.get());
304 handle.writeString("<prop oor:name=\"");
305 writeAttributeValue(handle, name);
306 handle.writeString("\" oor:op=\"fuse\"");
307 Type type = prop->getStaticType();
308 Type dynType = getDynamicType(prop->getValue(components));
309 assert(dynType != TYPE_ERROR);
310 if (type == TYPE_ANY) {
311 type = dynType;
312 if (type != TYPE_NIL) {
313 handle.writeString(" oor:type=\"");
314 handle.writeString(
315 std::string_view(
316 typeNames[type].begin, typeNames[type].length));
317 handle.writeString("\"");
320 handle.writeString("><value");
321 if (dynType == TYPE_NIL) {
322 handle.writeString(" xsi:nil=\"true\"/>");
323 } else {
324 writeValue(handle, type, prop->getValue(components));
326 handle.writeString("</prop>");
328 break;
329 case Node::KIND_LOCALIZED_PROPERTY:
330 handle.writeString("<prop oor:name=\"");
331 writeAttributeValue(handle, name);
332 handle.writeString("\" oor:op=\"fuse\">");
333 for (auto const& member : node->getMembers())
335 writeNode(components, handle, node, member.first, member.second);
337 handle.writeString("</prop>");
338 break;
339 case Node::KIND_LOCALIZED_VALUE:
341 handle.writeString("<value");
342 if (!name.empty()) {
343 handle.writeString(" xml:lang=\"");
344 writeAttributeValue(handle, name);
345 handle.writeString("\"");
347 Type type = static_cast< LocalizedPropertyNode * >(parent.get())->
348 getStaticType();
349 css::uno::Any value(
350 static_cast< LocalizedValueNode * >(node.get())->getValue());
351 Type dynType = getDynamicType(value);
352 assert(dynType != TYPE_ERROR);
353 if (type == TYPE_ANY) {
354 type = dynType;
355 if (type != TYPE_NIL) {
356 handle.writeString(" oor:type=\"");
357 handle.writeString(
358 std::string_view(
359 typeNames[type].begin, typeNames[type].length));
360 handle.writeString("\"");
363 if (dynType == TYPE_NIL) {
364 handle.writeString(" xsi:nil=\"true\"/>");
365 } else {
366 writeValue(handle, type, value);
369 break;
370 case Node::KIND_GROUP:
371 case Node::KIND_SET:
372 handle.writeString("<node oor:name=\"");
373 writeAttributeValue(handle, name);
374 if (!node->getTemplateName().isEmpty()) { // set member
375 handle.writeString("\" oor:op=\"replace");
377 handle.writeString("\">");
378 for (auto const& member : node->getMembers())
380 writeNode(components, handle, node, member.first, member.second);
382 handle.writeString("</node>");
383 break;
384 case Node::KIND_ROOT:
385 assert(false); // this cannot happen
386 break;
390 // helpers to allow sorting of configmgr::Modifications::Node
391 typedef std::pair< const OUString, configmgr::Modifications::Node > ModNodePairEntry;
392 struct PairEntrySorter
394 bool operator() (const ModNodePairEntry* pValue1, const ModNodePairEntry* pValue2) const
396 return pValue1->first.compareTo(pValue2->first) < 0;
400 void writeModifications(
401 Components & components, TempFile &handle,
402 OUString const & parentPathRepresentation,
403 rtl::Reference< Node > const & parent, OUString const & nodeName,
404 rtl::Reference< Node > const & node,
405 Modifications::Node const & modifications)
407 // It is never necessary to write oor:finalized or oor:mandatory attributes,
408 // as they cannot be set via the UNO API.
409 if (modifications.children.empty()) {
410 assert(parent.is());
411 // components themselves have no parent but must have children
412 handle.writeString("<item oor:path=\"");
413 writeAttributeValue(handle, parentPathRepresentation);
414 handle.writeString("\">");
415 if (node.is()) {
416 writeNode(components, handle, parent, nodeName, node);
417 } else {
418 switch (parent->kind()) {
419 case Node::KIND_LOCALIZED_PROPERTY:
420 handle.writeString("<value");
421 if (!nodeName.isEmpty()) {
422 handle.writeString(" xml:lang=\"");
423 writeAttributeValue(handle, nodeName);
424 handle.writeString("\"");
426 handle.writeString(" oor:op=\"remove\"/>");
427 break;
428 case Node::KIND_GROUP:
429 assert(
430 static_cast< GroupNode * >(parent.get())->isExtensible());
431 handle.writeString("<prop oor:name=\"");
432 writeAttributeValue(handle, nodeName);
433 handle.writeString("\" oor:op=\"remove\"/>");
434 break;
435 case Node::KIND_SET:
436 handle.writeString("<node oor:name=\"");
437 writeAttributeValue(handle, nodeName);
438 handle.writeString("\" oor:op=\"remove\"/>");
439 break;
440 default:
441 assert(false); // this cannot happen
442 break;
445 handle.writeString("</item>\n");
446 } else {
447 assert(node.is());
448 OUString pathRep(
449 parentPathRepresentation + "/" +
450 Data::createSegment(node->getTemplateName(), nodeName));
452 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
453 // to just reference the data instead of copying it
454 std::vector< const ModNodePairEntry* > ModNodePairEntryVector;
455 ModNodePairEntryVector.reserve(modifications.children.size());
457 for (const auto& rCand : modifications.children)
459 ModNodePairEntryVector.push_back(&rCand);
462 // sort the list
463 std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter());
465 // now use the list to write entries in sorted order
466 // instead of random as from the unordered map
467 for (const auto & i : ModNodePairEntryVector)
469 writeModifications(
470 components, handle, pathRep, node, i->first,
471 node->getMember(i->first), i->second);
478 void writeAttributeValue(TempFile &handle, std::u16string_view value) {
479 std::size_t i = 0;
480 std::size_t j = i;
481 for (; j != value.size(); ++j) {
482 assert(
483 value[j] == 0x0009 || value[j] == 0x000A || value[j] == 0x000D ||
484 (value[j] >= 0x0020 && value[j] != 0xFFFE && value[j] != 0xFFFF));
485 switch(value[j]) {
486 case '\x09':
487 handle.writeString(convertToUtf8(value.substr(i, j - i)));
488 handle.writeString("&#9;");
489 i = j + 1;
490 break;
491 case '\x0A':
492 handle.writeString(convertToUtf8(value.substr(i, j - i)));
493 handle.writeString("&#xA;");
494 i = j + 1;
495 break;
496 case '\x0D':
497 handle.writeString(convertToUtf8(value.substr(i, j - i)));
498 handle.writeString("&#xD;");
499 i = j + 1;
500 break;
501 case '"':
502 handle.writeString(convertToUtf8(value.substr(i, j - i)));
503 handle.writeString("&quot;");
504 i = j + 1;
505 break;
506 case '&':
507 handle.writeString(convertToUtf8(value.substr(i, j - i)));
508 handle.writeString("&amp;");
509 i = j + 1;
510 break;
511 case '<':
512 handle.writeString(convertToUtf8(value.substr(i, j - i)));
513 handle.writeString("&lt;");
514 i = j + 1;
515 break;
516 default:
517 break;
520 handle.writeString(convertToUtf8(value.substr(i, j - i)));
523 void writeValueContent(TempFile &handle, std::u16string_view value) {
524 std::size_t i = 0;
525 std::size_t j = i;
526 for (; j != value.size(); ++j) {
527 char16_t c = value[j];
528 if ((c < 0x0020 && c != 0x0009 && c != 0x000A && c != 0x000D) ||
529 c == 0xFFFE || c == 0xFFFF)
531 handle.writeString(convertToUtf8(value.substr(i, j - i)));
532 handle.writeString("<unicode oor:scalar=\"");
533 handle.writeString(OString::number(c));
534 handle.writeString("\"/>");
535 i = j + 1;
536 } else if (c == '\x0D') {
537 handle.writeString(convertToUtf8(value.substr(i, j - i)));
538 handle.writeString("&#xD;");
539 i = j + 1;
540 } else if (c == '&') {
541 handle.writeString(convertToUtf8(value.substr(i, j - i)));
542 handle.writeString("&amp;");
543 i = j + 1;
544 } else if (c == '<') {
545 handle.writeString(convertToUtf8(value.substr(i, j - i)));
546 handle.writeString("&lt;");
547 i = j + 1;
548 } else if (c == '>') {
549 // "MUST, for compatibility, be escaped [...] when it appears in the
550 // string ']]>'":
551 handle.writeString(convertToUtf8(value.substr(i, j - i)));
552 handle.writeString("&gt;");
553 i = j + 1;
556 handle.writeString(convertToUtf8(value.substr(i, j - i)));
559 void writeModFile(
560 Components & components, OUString const & url, Data const & data)
562 sal_Int32 i = url.lastIndexOf('/');
563 assert(i != -1);
564 OUString dir(url.copy(0, i));
565 switch (osl::Directory::createPath(dir)) {
566 case osl::FileBase::E_None:
567 case osl::FileBase::E_EXIST:
568 break;
569 case osl::FileBase::E_ACCES:
570 SAL_INFO(
571 "configmgr",
572 ("cannot create registrymodifications.xcu path (E_ACCES); changes"
573 " will be lost"));
574 return;
575 default:
576 throw css::uno::RuntimeException(
577 "cannot create directory " + dir);
579 TempFile tmp;
580 switch (osl::FileBase::createTempFile(&dir, &tmp.handle, &tmp.url)) {
581 case osl::FileBase::E_None:
582 break;
583 case osl::FileBase::E_ACCES:
584 SAL_INFO(
585 "configmgr",
586 ("cannot create temp registrymodifications.xcu (E_ACCES); changes"
587 " will be lost"));
588 return;
589 default:
590 throw css::uno::RuntimeException(
591 "cannot create temporary file in " + dir);
593 tmp.writeString(
594 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<oor:items"
595 " xmlns:oor=\"http://openoffice.org/2001/registry\""
596 " xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
597 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
598 //TODO: Do not write back information about those removed items that did not
599 // come from the .xcs/.xcu files, anyway (but had been added dynamically
600 // instead):
602 // For profilesafemode it is necessary to detect changes in the
603 // registrymodifications file, this is done based on file size in bytes and crc32.
604 // Unfortunately this write is based on writing unordered map entries, which creates
605 // valid and semantically equal XML-Files, bubt with different crc32 checksums. For
606 // the future usage it will be preferable to have easily comparable config files
607 // which is guaranteed by writing the entries in sorted order. Indeed with this change
608 // (and in the recursive writeModifications call) the same config files get written
610 // copy configmgr::Modifications::Node's to a sortable list. Use pointers
611 // to just reference the data instead of copying it
612 std::vector< const ModNodePairEntry* > ModNodePairEntryVector;
613 ModNodePairEntryVector.reserve(data.modifications.getRoot().children.size());
615 for (const auto& rCand : data.modifications.getRoot().children)
617 ModNodePairEntryVector.push_back(&rCand);
620 // sort the list
621 std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter());
623 // now use the list to write entries in sorted order
624 // instead of random as from the unordered map
625 for (const auto& j : ModNodePairEntryVector)
627 writeModifications(
628 components, tmp, "", rtl::Reference< Node >(), j->first,
629 data.getComponents().findNode(Data::NO_LAYER, j->first),
630 j->second);
632 tmp.writeString("</oor:items>\n");
633 tmp.closeAndRename(url);
638 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */