Avoid potential negative array index access to cached text.
[LibreOffice.git] / configmgr / source / xcsparser.cxx
blob35f61fa1959e1badbd4f549eca1d137a182bb5e6
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 <set>
25 #include <com/sun/star/uno/Any.hxx>
26 #include <com/sun/star/uno/RuntimeException.hpp>
27 #include <rtl/ref.hxx>
28 #include <rtl/strbuf.hxx>
29 #include <rtl/string.hxx>
30 #include <rtl/ustring.hxx>
31 #include <xmlreader/span.hxx>
32 #include <xmlreader/xmlreader.hxx>
34 #include "data.hxx"
35 #include "localizedpropertynode.hxx"
36 #include "groupnode.hxx"
37 #include "node.hxx"
38 #include "nodemap.hxx"
39 #include "parsemanager.hxx"
40 #include "propertynode.hxx"
41 #include "setnode.hxx"
42 #include "xcsparser.hxx"
43 #include "xmldata.hxx"
45 namespace configmgr {
47 namespace {
49 // Conservatively merge a template or component (and its recursive parts) into
50 // an existing instance:
51 void merge(
52 rtl::Reference< Node > const & original,
53 rtl::Reference< Node > const & update)
55 assert(
56 original.is() && update.is() && original->kind() == update->kind() &&
57 update->getFinalized() == Data::NO_LAYER);
58 if (update->getLayer() < original->getLayer() ||
59 update->getLayer() > original->getFinalized())
60 return;
62 switch (original->kind()) {
63 case Node::KIND_PROPERTY:
64 case Node::KIND_LOCALIZED_PROPERTY:
65 case Node::KIND_LOCALIZED_VALUE:
66 break; //TODO: merge certain parts?
67 case Node::KIND_GROUP:
68 for (auto const& updateMember : update->getMembers())
70 NodeMap & members = original->getMembers();
71 NodeMap::iterator i1(members.find(updateMember.first));
72 if (i1 == members.end()) {
73 if (updateMember.second->kind() == Node::KIND_PROPERTY &&
74 static_cast< GroupNode * >(
75 original.get())->isExtensible())
77 members.insert(updateMember);
79 } else if (updateMember.second->kind() == i1->second->kind()) {
80 merge(i1->second, updateMember.second);
83 break;
84 case Node::KIND_SET:
85 for (auto const& updateMember : update->getMembers())
87 NodeMap & members = original->getMembers();
88 NodeMap::iterator i1(members.find(updateMember.first));
89 if (i1 == members.end()) {
90 if (static_cast< SetNode * >(original.get())->
91 isValidTemplate(updateMember.second->getTemplateName()))
93 members.insert(updateMember);
95 } else if (updateMember.second->kind() == i1->second->kind() &&
96 (updateMember.second->getTemplateName() ==
97 i1->second->getTemplateName()))
99 merge(i1->second, updateMember.second);
102 break;
103 case Node::KIND_ROOT:
104 assert(false); // this cannot happen
105 break;
111 XcsParser::XcsParser(int layer, Data & data):
112 valueParser_(layer), data_(data), state_(STATE_START), ignoring_(), bIsParsingInfo_(false)
115 XcsParser::~XcsParser() {}
117 xmlreader::XmlReader::Text XcsParser::getTextMode() {
118 if (bIsParsingInfo_)
119 return xmlreader::XmlReader::Text::Raw;
120 return valueParser_.getTextMode();
123 bool XcsParser::startElement(
124 xmlreader::XmlReader & reader, int nsId, xmlreader::Span const & name,
125 std::set< OUString > const * /*existingDependencies*/)
127 //TODO: ignoring component-schema import, component-schema uses, and
128 // prop constraints; accepting all four at illegal places (and with
129 // illegal content):
130 if (ignoring_ > 0
131 || (nsId == xmlreader::XmlReader::NAMESPACE_NONE
132 && (name == "import" || name == "uses" || name == "constraints" || name == "desc"
133 // the following are unused by LO but valid
134 || name == "deprecated" || name == "author" || name == "label")))
136 assert(ignoring_ < LONG_MAX);
137 ++ignoring_;
138 return true;
141 if (bIsParsingInfo_)
142 return true;
143 if (valueParser_.startElement(reader, nsId, name)) {
144 return true;
146 if (state_ == STATE_START) {
147 if (nsId == ParseManager::NAMESPACE_OOR &&
148 name == "component-schema")
150 handleComponentSchema(reader);
151 state_ = STATE_COMPONENT_SCHEMA;
152 ignoring_ = 0;
153 return true;
155 } else {
156 switch (state_) {
157 case STATE_COMPONENT_SCHEMA:
158 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
159 name == "templates")
161 state_ = STATE_TEMPLATES;
162 return true;
164 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
165 name == "info")
167 bIsParsingInfo_ = true;
168 return true;
170 [[fallthrough]];
171 case STATE_TEMPLATES_DONE:
172 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
173 name == "component")
175 state_ = STATE_COMPONENT;
176 assert(elements_.empty());
177 elements_.push(
178 Element(
179 new GroupNode(valueParser_.getLayer(), false, ""),
180 componentName_));
181 return true;
183 break;
184 case STATE_TEMPLATES:
185 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
186 name == "info")
188 bIsParsingInfo_ = true;
189 return true;
191 if (elements_.empty()) {
192 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
193 name == "group")
195 handleGroup(reader, true);
196 return true;
198 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
199 name == "set")
201 handleSet(reader, true);
202 return true;
204 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
205 name == "info")
207 bIsParsingInfo_ = true;
208 return true;
210 break;
212 [[fallthrough]];
213 case STATE_COMPONENT:
214 assert(!elements_.empty());
215 switch (elements_.top().node->kind()) {
216 case Node::KIND_PROPERTY:
217 case Node::KIND_LOCALIZED_PROPERTY:
218 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
219 name == "value")
221 handlePropValue(reader, elements_.top().node);
222 return true;
224 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
225 name == "info")
227 bIsParsingInfo_ = true;
228 return true;
230 break;
231 case Node::KIND_GROUP:
232 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
233 name == "prop")
235 handleProp(reader);
236 return true;
238 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
239 name == "node-ref")
241 handleNodeRef(reader);
242 return true;
244 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
245 name == "group")
247 handleGroup(reader, false);
248 return true;
250 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
251 name == "set")
253 handleSet(reader, false);
254 return true;
256 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
257 name == "info")
259 bIsParsingInfo_ = true;
260 return true;
262 break;
263 case Node::KIND_SET:
264 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
265 name == "item")
267 handleSetItem(
268 reader,
269 static_cast< SetNode * >(elements_.top().node.get()));
270 return true;
272 if (nsId == xmlreader::XmlReader::NAMESPACE_NONE &&
273 name == "info")
275 bIsParsingInfo_ = true;
276 return true;
278 break;
279 default: // Node::KIND_LOCALIZED_VALUE
280 assert(false); // this cannot happen
281 break;
283 break;
284 case STATE_COMPONENT_DONE:
285 break;
286 default: // STATE_START
287 assert(false); // this cannot happen
288 break;
291 throw css::uno::RuntimeException(
292 "bad member <" + name.convertFromUtf8() + "> in " + reader.getUrl());
295 void XcsParser::endElement(xmlreader::XmlReader const & reader) {
296 if (ignoring_ > 0) {
297 --ignoring_;
298 return;
300 if (bIsParsingInfo_)
302 bIsParsingInfo_ = false;
303 return;
305 if (valueParser_.endElement()) {
306 return;
308 if (!elements_.empty()) {
309 Element top(std::move(elements_.top()));
310 elements_.pop();
311 if (top.node.is()) {
312 if (top.node->kind() == Node::KIND_PROPERTY
313 || top.node->kind() == Node::KIND_LOCALIZED_PROPERTY)
315 // Remove whitespace from description_ resulting from line breaks/indentation in xml files
316 OUString desc(description_.makeStringAndClear());
317 desc = desc.trim();
318 while (desc.indexOf(" ") != -1)
319 desc = desc.replaceAll(" ", " ");
320 top.node->setDescription(desc);
322 if (elements_.empty()) {
323 switch (state_) {
324 case STATE_TEMPLATES:
326 auto itPair = data_.templates.insert({top.name, top.node});
327 if (!itPair.second) {
328 merge(itPair.first->second, top.node);
331 break;
332 case STATE_COMPONENT:
334 NodeMap & components = data_.getComponents();
335 auto itPair = components.insert({top.name, top.node});
336 if (!itPair.second) {
337 merge(itPair.first->second, top.node);
339 state_ = STATE_COMPONENT_DONE;
341 break;
342 default:
343 assert(false);
344 throw css::uno::RuntimeException(
345 "this cannot happen");
347 } else {
348 if (!elements_.top().node->getMembers().insert(
349 NodeMap::value_type(top.name, top.node)).second)
351 throw css::uno::RuntimeException(
352 "duplicate " + top.name + " in " + reader.getUrl());
356 } else {
357 switch (state_) {
358 case STATE_COMPONENT_SCHEMA:
359 // To support old, broken extensions with .xcs files that contain
360 // empty <component-schema> elements:
361 state_ = STATE_COMPONENT_DONE;
362 break;
363 case STATE_TEMPLATES:
364 state_ = STATE_TEMPLATES_DONE;
365 break;
366 case STATE_TEMPLATES_DONE:
367 throw css::uno::RuntimeException(
368 "no component element in " + reader.getUrl());
369 case STATE_COMPONENT_DONE:
370 break;
371 default:
372 assert(false); // this cannot happen
377 void XcsParser::characters(xmlreader::Span const & text) {
378 if (bIsParsingInfo_)
380 description_.append(text.convertFromUtf8());
381 return;
383 valueParser_.characters(text);
386 void XcsParser::handleComponentSchema(xmlreader::XmlReader & reader) {
387 //TODO: oor:version, xml:lang attributes
388 OStringBuffer buf(256);
389 buf.append('.');
390 bool hasPackage = false;
391 bool hasName = false;
392 for (;;) {
393 int attrNsId;
394 xmlreader::Span attrLn;
395 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
396 break;
398 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "package")
400 if (hasPackage) {
401 throw css::uno::RuntimeException(
402 "multiple component-schema package attributes in " +
403 reader.getUrl());
405 hasPackage = true;
406 xmlreader::Span s(reader.getAttributeValue(false));
407 buf.insert(0, s.begin, s.length);
408 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
409 attrLn == "name")
411 if (hasName) {
412 throw css::uno::RuntimeException(
413 "multiple component-schema name attributes in " +
414 reader.getUrl());
416 hasName = true;
417 xmlreader::Span s(reader.getAttributeValue(false));
418 buf.append(s.begin, s.length);
421 if (!hasPackage) {
422 throw css::uno::RuntimeException(
423 "no component-schema package attribute in " + reader.getUrl());
425 if (!hasName) {
426 throw css::uno::RuntimeException(
427 "no component-schema name attribute in " + reader.getUrl());
429 componentName_ = xmlreader::Span(buf.getStr(), buf.getLength()).
430 convertFromUtf8();
433 void XcsParser::handleNodeRef(xmlreader::XmlReader & reader) {
434 bool hasName = false;
435 OUString name;
436 OUString component(componentName_);
437 bool hasNodeType = false;
438 OUString nodeType;
439 for (;;) {
440 int attrNsId;
441 xmlreader::Span attrLn;
442 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
443 break;
445 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
446 hasName = true;
447 name = reader.getAttributeValue(false).convertFromUtf8();
448 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
449 attrLn == "component")
451 component = reader.getAttributeValue(false).convertFromUtf8();
452 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
453 attrLn == "node-type")
455 hasNodeType = true;
456 nodeType = reader.getAttributeValue(false).convertFromUtf8();
459 if (!hasName) {
460 throw css::uno::RuntimeException(
461 "no node-ref name attribute in " + reader.getUrl());
463 rtl::Reference< Node > tmpl(
464 data_.getTemplate(
465 valueParser_.getLayer(),
466 xmldata::parseTemplateReference(
467 component, hasNodeType, nodeType, nullptr)));
468 if (!tmpl.is()) {
469 //TODO: this can erroneously happen as long as import/uses attributes
470 // are not correctly processed
471 throw css::uno::RuntimeException(
472 "unknown node-ref " + name + " in " + reader.getUrl());
474 rtl::Reference< Node > node(tmpl->clone(false));
475 node->setLayer(valueParser_.getLayer());
476 elements_.push(Element(node, name));
479 void XcsParser::handleProp(xmlreader::XmlReader & reader) {
480 bool hasName = false;
481 OUString name;
482 valueParser_.type_ = TYPE_ERROR;
483 bool localized = false;
484 bool nillable = true;
485 for (;;) {
486 int attrNsId;
487 xmlreader::Span attrLn;
488 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
489 break;
491 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
492 hasName = true;
493 name = reader.getAttributeValue(false).convertFromUtf8();
494 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
495 attrLn == "type")
497 valueParser_.type_ = xmldata::parseType(
498 reader, reader.getAttributeValue(true));
499 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
500 attrLn == "localized")
502 localized = xmldata::parseBoolean(reader.getAttributeValue(true));
503 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
504 attrLn == "nillable")
506 nillable = xmldata::parseBoolean(reader.getAttributeValue(true));
509 if (!hasName) {
510 throw css::uno::RuntimeException(
511 "no prop name attribute in " + reader.getUrl());
513 if (valueParser_.type_ == TYPE_ERROR) {
514 throw css::uno::RuntimeException(
515 "no prop type attribute in " + reader.getUrl());
517 elements_.push(
518 Element(
519 (localized
520 ? rtl::Reference< Node >(
521 new LocalizedPropertyNode(
522 valueParser_.getLayer(), valueParser_.type_, nillable))
523 : rtl::Reference< Node >(
524 new PropertyNode(
525 valueParser_.getLayer(), valueParser_.type_, nillable,
526 css::uno::Any(), false))),
527 name));
530 void XcsParser::handlePropValue(
531 xmlreader::XmlReader & reader, rtl::Reference< Node > const & property)
533 xmlreader::Span attrSeparator;
534 for (;;) {
535 int attrNsId;
536 xmlreader::Span attrLn;
537 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
538 break;
540 if (attrNsId == ParseManager::NAMESPACE_OOR &&
541 attrLn == "separator")
543 attrSeparator = reader.getAttributeValue(false);
544 if (attrSeparator.length == 0) {
545 throw css::uno::RuntimeException(
546 "bad oor:separator attribute in " + reader.getUrl());
550 valueParser_.separator_ = OString(
551 attrSeparator.begin, attrSeparator.length);
552 valueParser_.start(property);
555 void XcsParser::handleGroup(xmlreader::XmlReader & reader, bool isTemplate) {
556 bool hasName = false;
557 OUString name;
558 bool extensible = false;
559 for (;;) {
560 int attrNsId;
561 xmlreader::Span attrLn;
562 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
563 break;
565 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
566 hasName = true;
567 name = reader.getAttributeValue(false).convertFromUtf8();
568 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
569 attrLn == "extensible")
571 extensible = xmldata::parseBoolean(reader.getAttributeValue(true));
574 if (!hasName) {
575 throw css::uno::RuntimeException(
576 "no group name attribute in " + reader.getUrl());
578 if (isTemplate) {
579 name = Data::fullTemplateName(componentName_, name);
581 elements_.push(
582 Element(
583 new GroupNode(
584 valueParser_.getLayer(), extensible,
585 isTemplate ? name : OUString()),
586 name));
589 void XcsParser::handleSet(xmlreader::XmlReader & reader, bool isTemplate) {
590 bool hasName = false;
591 OUString name;
592 OUString component(componentName_);
593 bool hasNodeType = false;
594 OUString nodeType;
595 for (;;) {
596 int attrNsId;
597 xmlreader::Span attrLn;
598 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
599 break;
601 if (attrNsId == ParseManager::NAMESPACE_OOR && attrLn == "name") {
602 hasName = true;
603 name = reader.getAttributeValue(false).convertFromUtf8();
604 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
605 attrLn == "component")
607 component = reader.getAttributeValue(false).convertFromUtf8();
608 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
609 attrLn == "node-type")
611 hasNodeType = true;
612 nodeType = reader.getAttributeValue(false).convertFromUtf8();
615 if (!hasName) {
616 throw css::uno::RuntimeException(
617 "no set name attribute in " + reader.getUrl());
619 if (isTemplate) {
620 name = Data::fullTemplateName(componentName_, name);
622 elements_.push(
623 Element(
624 new SetNode(
625 valueParser_.getLayer(),
626 xmldata::parseTemplateReference(
627 component, hasNodeType, nodeType, nullptr),
628 isTemplate ? name : OUString()),
629 name));
632 void XcsParser::handleSetItem(xmlreader::XmlReader & reader, SetNode * set) {
633 OUString component(componentName_);
634 bool hasNodeType = false;
635 OUString nodeType;
636 for (;;) {
637 int attrNsId;
638 xmlreader::Span attrLn;
639 if (!reader.nextAttribute(&attrNsId, &attrLn)) {
640 break;
642 if (attrNsId == ParseManager::NAMESPACE_OOR &&
643 attrLn == "component")
645 component = reader.getAttributeValue(false).convertFromUtf8();
646 } else if (attrNsId == ParseManager::NAMESPACE_OOR &&
647 attrLn == "node-type")
649 hasNodeType = true;
650 nodeType = reader.getAttributeValue(false).convertFromUtf8();
653 set->getAdditionalTemplateNames().push_back(
654 xmldata::parseTemplateReference(component, hasNodeType, nodeType, nullptr));
655 elements_.push(Element(rtl::Reference< Node >(), ""));
660 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */