LibreOffice Module configmgr (master)  1
writemodfile.cxx
Go to the documentation of this file.
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  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <cassert>
23 #include <cstddef>
24 #include <limits>
25 #include <string_view>
26 
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>
31 #include <osl/file.h>
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>
42 
43 #include "data.hxx"
44 #include "groupnode.hxx"
46 #include "localizedvaluenode.hxx"
47 #include "modifications.hxx"
48 #include "node.hxx"
49 #include "nodemap.hxx"
50 #include "propertynode.hxx"
51 #include "type.hxx"
52 #include "writemodfile.hxx"
53 
54 namespace configmgr {
55 
56 class Components;
57 
58 namespace {
59 
60 OString convertToUtf8(std::u16string_view text) {
61  OString s;
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)))
68  {
69  throw css::uno::RuntimeException(
70  "cannot convert to UTF-8");
71  }
72  return s;
73 }
74 
75 } // anonymous namespace
76 
78  if (handle == nullptr)
79  return;
80 
81  if (!closed) {
82  oslFileError e = osl_closeFile(handle);
83  if (e != osl_File_E_None) {
84  SAL_WARN("configmgr", "osl_closeFile failed with " << +e);
85  }
86  }
87  osl::FileBase::RC e = osl::File::remove(url);
88  if (e != osl::FileBase::E_None) {
89  SAL_WARN(
90  "configmgr",
91  "osl::File::remove(" << url << ") failed with " << +e);
92  }
93 }
94 
95 #ifdef _WIN32
96 oslFileError TempFile::closeWithoutUnlink() {
97  flush();
98  oslFileError e = osl_closeFile(handle);
99  handle = nullptr;
100  closed = true;
101  return e;
102 }
103 #endif
104 
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);
110  }
111  e = osl_closeFile(handle);
112  closed = true;
113  if (e != osl_File_E_None) {
114  throw css::uno::RuntimeException(
115  "cannot close " + url);
116  }
117  if (osl::File::move(url, _url) != osl::FileBase::E_None) {
118  throw css::uno::RuntimeException(
119  "cannot move " + url);
120  }
121  handle = nullptr;
122 }
123 
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()),
130  &nBytesWritten);
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 ) );
134  } else {
135  buffer.setLength(0);
136  }
137  }
138  return e;
139 }
140 
141 void TempFile::writeString(std::string_view text) {
142  buffer.append(text.data(), text.size());
143  if (buffer.getLength() > 0x10000)
144  flush();
145 }
146 
147 namespace {
148 
149 void writeValueContent_(TempFile &, bool) = delete;
150  // silence loplugin:salbool
151 void writeValueContent_(TempFile &handle, sal_Bool value) {
152  if (value) {
153  handle.writeString("true");
154  } else {
155  handle.writeString("false");
156  }
157 }
158 
159 void writeValueContent_(TempFile &handle, sal_Int16 value) {
160  handle.writeString(OString::number(value));
161 }
162 
163 void writeValueContent_(TempFile &handle, sal_Int32 value) {
164  handle.writeString(OString::number(value));
165 }
166 
167 void writeValueContent_(TempFile &handle, sal_Int64 value) {
168  handle.writeString(OString::number(value));
169 }
170 
171 void writeValueContent_(TempFile &handle, double value) {
172  handle.writeString(OString::number(value));
173 }
174 
175 void writeValueContent_(TempFile &handle, std::u16string_view value) {
176  writeValueContent(handle, value);
177 }
178 
179 void writeValueContent_(
180  TempFile &handle, css::uno::Sequence< sal_Int8 > const & value)
181 {
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',
185  'D', 'E', 'F' };
186  handle.writeString(
187  std::string_view(hexDigit + ((v >> 4) & 0xF), 1));
188  handle.writeString(std::string_view(hexDigit + (v & 0xF), 1));
189  }
190 }
191 
192 template< typename T > void writeSingleValue(
193  TempFile &handle, css::uno::Any const & value)
194 {
195  handle.writeString(">");
196  T val = T();
197  value >>= val;
198  writeValueContent_(handle, val);
199  handle.writeString("</value>");
200 }
201 
202 template< typename T > void writeListValue(
203  TempFile &handle, css::uno::Any const & value)
204 {
205  handle.writeString(">");
206  css::uno::Sequence< T > val;
207  value >>= val;
208  for (sal_Int32 i = 0; i < val.getLength(); ++i) {
209  if (i != 0) {
210  handle.writeString(" ");
211  }
212  writeValueContent_(handle, std::as_const(val)[i]);
213  }
214  handle.writeString("</value>");
215 }
216 
217 template< typename T > void writeItemListValue(
218  TempFile &handle, css::uno::Any const & value)
219 {
220  handle.writeString(">");
221  css::uno::Sequence< T > val;
222  value >>= val;
223  for (const auto & i : std::as_const(val)) {
224  handle.writeString("<it>");
225  writeValueContent_(handle, i);
226  handle.writeString("</it>");
227  }
228  handle.writeString("</value>");
229 }
230 
231 void writeValue(TempFile &handle, Type type, css::uno::Any const & value) {
232  switch (type) {
233  case TYPE_BOOLEAN:
234  writeSingleValue< sal_Bool >(handle, value);
235  break;
236  case TYPE_SHORT:
237  writeSingleValue< sal_Int16 >(handle, value);
238  break;
239  case TYPE_INT:
240  writeSingleValue< sal_Int32 >(handle, value);
241  break;
242  case TYPE_LONG:
243  writeSingleValue< sal_Int64 >(handle, value);
244  break;
245  case TYPE_DOUBLE:
246  writeSingleValue< double >(handle, value);
247  break;
248  case TYPE_STRING:
249  writeSingleValue< OUString >(handle, value);
250  break;
251  case TYPE_HEXBINARY:
252  writeSingleValue< css::uno::Sequence< sal_Int8 > >(handle, value);
253  break;
254  case TYPE_BOOLEAN_LIST:
255  writeListValue< sal_Bool >(handle, value);
256  break;
257  case TYPE_SHORT_LIST:
258  writeListValue< sal_Int16 >(handle, value);
259  break;
260  case TYPE_INT_LIST:
261  writeListValue< sal_Int32 >(handle, value);
262  break;
263  case TYPE_LONG_LIST:
264  writeListValue< sal_Int64 >(handle, value);
265  break;
266  case TYPE_DOUBLE_LIST:
267  writeListValue< double >(handle, value);
268  break;
269  case TYPE_STRING_LIST:
270  writeItemListValue< OUString >(handle, value);
271  break;
272  case TYPE_HEXBINARY_LIST:
273  writeItemListValue< css::uno::Sequence< sal_Int8 > >(handle, value);
274  break;
275  default: // TYPE_ERROR, TYPE_NIL, TYPE_ANY
276  assert(false); // this cannot happen
277  }
278 }
279 
280 void writeNode(
281  Components & components, TempFile &handle,
282  rtl::Reference< Node > const & parent, std::u16string_view name,
283  rtl::Reference< Node > const & node)
284 {
285  static xmlreader::Span const typeNames[] = {
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:
304  {
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) {
313  type = dynType;
314  if (type != TYPE_NIL) {
315  handle.writeString(" oor:type=\"");
316  handle.writeString(
317  std::string_view(
318  typeNames[type].begin, typeNames[type].length));
319  handle.writeString("\"");
320  }
321  }
322  handle.writeString("><value");
323  if (dynType == TYPE_NIL) {
324  handle.writeString(" xsi:nil=\"true\"/>");
325  } else {
326  writeValue(handle, type, prop->getValue(components));
327  }
328  handle.writeString("</prop>");
329  }
330  break;
332  handle.writeString("<prop oor:name=\"");
333  writeAttributeValue(handle, name);
334  handle.writeString("\" oor:op=\"fuse\">");
335  for (auto const& member : node->getMembers())
336  {
337  writeNode(components, handle, node, member.first, member.second);
338  }
339  handle.writeString("</prop>");
340  break;
342  {
343  handle.writeString("<value");
344  if (!name.empty()) {
345  handle.writeString(" xml:lang=\"");
346  writeAttributeValue(handle, name);
347  handle.writeString("\"");
348  }
349  Type type = static_cast< LocalizedPropertyNode * >(parent.get())->
350  getStaticType();
351  css::uno::Any value(
352  static_cast< LocalizedValueNode * >(node.get())->getValue());
353  Type dynType = getDynamicType(value);
354  assert(dynType != TYPE_ERROR);
355  if (type == TYPE_ANY) {
356  type = dynType;
357  if (type != TYPE_NIL) {
358  handle.writeString(" oor:type=\"");
359  handle.writeString(
360  std::string_view(
361  typeNames[type].begin, typeNames[type].length));
362  handle.writeString("\"");
363  }
364  }
365  if (dynType == TYPE_NIL) {
366  handle.writeString(" xsi:nil=\"true\"/>");
367  } else {
368  writeValue(handle, type, value);
369  }
370  }
371  break;
372  case Node::KIND_GROUP:
373  case Node::KIND_SET:
374  handle.writeString("<node oor:name=\"");
375  writeAttributeValue(handle, name);
376  if (!node->getTemplateName().isEmpty()) { // set member
377  handle.writeString("\" oor:op=\"replace");
378  }
379  handle.writeString("\">");
380  for (auto const& member : node->getMembers())
381  {
382  writeNode(components, handle, node, member.first, member.second);
383  }
384  handle.writeString("</node>");
385  break;
386  case Node::KIND_ROOT:
387  assert(false); // this cannot happen
388  break;
389  }
390 }
391 
392 // helpers to allow sorting of configmgr::Modifications::Node
393 typedef std::pair< const OUString, configmgr::Modifications::Node > ModNodePairEntry;
394 struct PairEntrySorter
395 {
396  bool operator() (const ModNodePairEntry* pValue1, const ModNodePairEntry* pValue2) const
397  {
398  return pValue1->first.compareTo(pValue2->first) < 0;
399  }
400 };
401 
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)
408 {
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()) {
412  assert(parent.is());
413  // components themselves have no parent but must have children
414  handle.writeString("<item oor:path=\"");
415  writeAttributeValue(handle, parentPathRepresentation);
416  handle.writeString("\">");
417  if (node.is()) {
418  writeNode(components, handle, parent, nodeName, node);
419  } else {
420  switch (parent->kind()) {
422  handle.writeString("<value");
423  if (!nodeName.isEmpty()) {
424  handle.writeString(" xml:lang=\"");
425  writeAttributeValue(handle, nodeName);
426  handle.writeString("\"");
427  }
428  handle.writeString(" oor:op=\"remove\"/>");
429  break;
430  case Node::KIND_GROUP:
431  assert(
432  static_cast< GroupNode * >(parent.get())->isExtensible());
433  handle.writeString("<prop oor:name=\"");
434  writeAttributeValue(handle, nodeName);
435  handle.writeString("\" oor:op=\"remove\"/>");
436  break;
437  case Node::KIND_SET:
438  handle.writeString("<node oor:name=\"");
439  writeAttributeValue(handle, nodeName);
440  handle.writeString("\" oor:op=\"remove\"/>");
441  break;
442  default:
443  assert(false); // this cannot happen
444  break;
445  }
446  }
447  handle.writeString("</item>\n");
448  } else {
449  assert(node.is());
450  OUString pathRep(
451  OUString::Concat(parentPathRepresentation) + "/" +
452  Data::createSegment(node->getTemplateName(), nodeName));
453 
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());
458 
459  for (const auto& rCand : modifications.children)
460  {
461  ModNodePairEntryVector.push_back(&rCand);
462  }
463 
464  // sort the list
465  std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter());
466 
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)
470  {
472  components, handle, pathRep, node, i->first,
473  node->getMember(i->first), i->second);
474  }
475  }
476 }
477 
478 }
479 
480 void writeAttributeValue(TempFile &handle, std::u16string_view value) {
481  std::size_t i = 0;
482  std::size_t j = i;
483  for (; j != value.size(); ++j) {
484  assert(
485  value[j] == 0x0009 || value[j] == 0x000A || value[j] == 0x000D ||
486  (value[j] >= 0x0020 && value[j] != 0xFFFE && value[j] != 0xFFFF));
487  switch(value[j]) {
488  case '\x09':
489  handle.writeString(convertToUtf8(value.substr(i, j - i)));
490  handle.writeString("&#9;");
491  i = j + 1;
492  break;
493  case '\x0A':
494  handle.writeString(convertToUtf8(value.substr(i, j - i)));
495  handle.writeString("&#xA;");
496  i = j + 1;
497  break;
498  case '\x0D':
499  handle.writeString(convertToUtf8(value.substr(i, j - i)));
500  handle.writeString("&#xD;");
501  i = j + 1;
502  break;
503  case '"':
504  handle.writeString(convertToUtf8(value.substr(i, j - i)));
505  handle.writeString("&quot;");
506  i = j + 1;
507  break;
508  case '&':
509  handle.writeString(convertToUtf8(value.substr(i, j - i)));
510  handle.writeString("&amp;");
511  i = j + 1;
512  break;
513  case '<':
514  handle.writeString(convertToUtf8(value.substr(i, j - i)));
515  handle.writeString("&lt;");
516  i = j + 1;
517  break;
518  default:
519  break;
520  }
521  }
522  handle.writeString(convertToUtf8(value.substr(i, j - i)));
523 }
524 
525 void writeValueContent(TempFile &handle, std::u16string_view value) {
526  std::size_t i = 0;
527  std::size_t j = i;
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)
532  {
533  handle.writeString(convertToUtf8(value.substr(i, j - i)));
534  handle.writeString("<unicode oor:scalar=\"");
535  handle.writeString(OString::number(c));
536  handle.writeString("\"/>");
537  i = j + 1;
538  } else if (c == '\x0D') {
539  handle.writeString(convertToUtf8(value.substr(i, j - i)));
540  handle.writeString("&#xD;");
541  i = j + 1;
542  } else if (c == '&') {
543  handle.writeString(convertToUtf8(value.substr(i, j - i)));
544  handle.writeString("&amp;");
545  i = j + 1;
546  } else if (c == '<') {
547  handle.writeString(convertToUtf8(value.substr(i, j - i)));
548  handle.writeString("&lt;");
549  i = j + 1;
550  } else if (c == '>') {
551  // "MUST, for compatibility, be escaped [...] when it appears in the
552  // string ']]>'":
553  handle.writeString(convertToUtf8(value.substr(i, j - i)));
554  handle.writeString("&gt;");
555  i = j + 1;
556  }
557  }
558  handle.writeString(convertToUtf8(value.substr(i, j - i)));
559 }
560 
562  Components & components, OUString const & url, Data const & data)
563 {
564  sal_Int32 i = url.lastIndexOf('/');
565  assert(i != -1);
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:
570  break;
571  case osl::FileBase::E_ACCES:
572  SAL_INFO(
573  "configmgr",
574  ("cannot create registrymodifications.xcu path (E_ACCES); changes"
575  " will be lost"));
576  return;
577  default:
578  throw css::uno::RuntimeException(
579  "cannot create directory " + dir);
580  }
581  TempFile tmp;
582  switch (osl::FileBase::createTempFile(&dir, &tmp.handle, &tmp.url)) {
583  case osl::FileBase::E_None:
584  break;
585  case osl::FileBase::E_ACCES:
586  SAL_INFO(
587  "configmgr",
588  ("cannot create temp registrymodifications.xcu (E_ACCES); changes"
589  " will be lost"));
590  return;
591  default:
592  throw css::uno::RuntimeException(
593  "cannot create temporary file in " + dir);
594  }
595  tmp.writeString(
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
602  // instead):
603 
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
611 
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());
616 
617  for (const auto& rCand : data.modifications.getRoot().children)
618  {
619  ModNodePairEntryVector.push_back(&rCand);
620  }
621 
622  // sort the list
623  std::sort(ModNodePairEntryVector.begin(), ModNodePairEntryVector.end(), PairEntrySorter());
624 
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)
628  {
630  components, tmp, u"", rtl::Reference< Node >(), j->first,
631  data.getComponents().findNode(Data::NO_LAYER, j->first),
632  j->second);
633  }
634  tmp.writeString("</oor:items>\n");
635  tmp.closeAndRename(url);
636 }
637 
638 }
639 
640 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
oslFileError flush()
Node const & getRoot() const
void writeModifications(Components &components, Data &data)
Definition: dconf.cxx:1567
void writeModFile(Components &components, OUString const &url, Data const &data)
void writeAttributeValue(TempFile &handle, std::u16string_view value)
static OUString createSegment(std::u16string_view templateName, OUString const &name)
Definition: data.cxx:79
oslFileHandle handle
Modifications modifications
Definition: data.hxx:51
int i
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
float u
unsigned char sal_Bool
css::beans::Optional< css::uno::Any > getValue(std::u16string_view id)
void writeValueContent(TempFile &handle, std::u16string_view value)
NodeMap & getComponents() const
Definition: data.cxx:291
std::vector< uno::Reference< sheet::XSpreadsheetDocument > > Components
Type getDynamicType(css::uno::Any const &value)
Definition: type.cxx:101
#define SAL_INFO(area, stream)
void closeAndRename(const OUString &url)
void writeString(std::string_view text)
OStringBuffer buffer
#define SAL_WARN(area, stream)
OUString name
Definition: components.cxx:83
rtl::Reference< Node > findNode(int layer, OUString const &name) const
Definition: nodemap.cxx:43