OpenDDS  Snapshot(2023/04/28-20:55)
XmlUtils.cpp
Go to the documentation of this file.
1 /*
2  * Distributed under the OpenDDS License.
3  * See: http://www.OpenDDS.org/license.html
4  */
5 
6 #include "XmlUtils.h"
7 
8 #include <dds/DCPS/debug.h>
9 
10 #include <ace/OS_NS_strings.h>
11 #include <ace/OS_NS_time.h>
12 
13 #include <xercesc/framework/MemBufInputSource.hpp>
14 #include <xercesc/sax/ErrorHandler.hpp>
15 #include <xercesc/util/XMLDateTime.hpp>
16 #include <xercesc/util/PlatformUtils.hpp>
17 #include <xercesc/util/XercesVersion.hpp>
18 
19 #include <stdexcept>
20 
22 
23 namespace OpenDDS {
24 namespace Security {
25 namespace XmlUtils {
26 
29 
30 std::string to_string(const xercesc::SAXParseException& ex)
31 {
32  return
33  to_string(ex.getSystemId()) +
34  ":" + DCPS::to_dds_string(ex.getLineNumber()) +
35  ":" + DCPS::to_dds_string(ex.getColumnNumber()) +
36  ": " + to_string(ex.getMessage());
37 }
38 
39 namespace {
40  class ErrorHandler : public xercesc::ErrorHandler {
41  public:
42  void warning(const xercesc::SAXParseException& ex)
43  {
45  ACE_ERROR((LM_ERROR, "(%P|%t) WARNING: {access_warn} "
46  "XmlUtils::ErrorHandler: %C\n",
47  to_string(ex).c_str()));
48  }
49  }
50 
51  void error(const xercesc::SAXParseException& ex)
52  {
54  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} "
55  "XmlUtils::ErrorHandler: %C\n",
56  to_string(ex).c_str()));
57  }
58  }
59 
60  void fatalError(const xercesc::SAXParseException& ex)
61  {
62  error(ex);
63  }
64 
65  void resetErrors()
66  {
67  }
68  };
69 }
70 
71 bool get_parser(ParserPtr& parser, const std::string& filename, const std::string& xml)
72 {
73  try {
75 
76  } catch (const xercesc::XMLException& ex) {
78  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
79  "XMLPlatformUtils::Initialize XMLException: %C\n",
80  to_string(ex).c_str()));
81  }
82  parser.reset();
83  return false;
84  }
85 
86  parser.reset(new xercesc::XercesDOMParser());
87 
88  parser->setDoNamespaces(true);
89  parser->setIncludeIgnorableWhitespace(false);
90  parser->setCreateCommentNodes(false);
91 
92  ErrorHandler error_handler;
93  parser->setErrorHandler(&error_handler);
94 
95  xercesc::MemBufInputSource contentbuf(
96  reinterpret_cast<const XMLByte*>(xml.c_str()), xml.size(), filename.c_str());
97 
98  try {
99  parser->parse(contentbuf);
100 
101  } catch (const xercesc::XMLException& ex) {
103  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
104  "XMLException while parsing \"%C\": %C\n",
105  filename.c_str(), to_string(ex).c_str()));
106  }
107  parser.reset();
108  return false;
109 
110  } catch (const xercesc::DOMException& ex) {
112  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
113  "DOMException while parsing \"%C\": %C\n",
114  filename.c_str(), to_string(ex).c_str()));
115  }
116  parser.reset();
117  return false;
118 
119  } catch (...) {
121  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
122  "Unexpected exception while parsing \"%C\"",
123  filename.c_str()));
124  }
125  parser.reset();
126  return false;
127  }
128 
129  if (!parser->getDocument()->getDocumentElement()) {
131  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} get_parser: "
132  "XML document \"%C\" is empty\n",
133  filename.c_str()));
134  }
135  parser.reset();
136  return false;
137  }
138 
139  return true;
140 }
141 
142 std::string to_string(const XMLCh* in)
143 {
144  char* c = xercesc::XMLString::transcode(in);
145  const std::string s(c);
146  xercesc::XMLString::release(&c);
147  return s;
148 }
149 
150 bool parse_bool(const XMLCh* in, bool& value)
151 {
152  /*
153  * The security spec specifies the XML schema spec's boolean type, but the
154  * XML schema spec doesn't specify that true and false can be capitalized
155  * like the security spec uses.
156  */
157  const std::string s = to_string(in);
158  if (!ACE_OS::strcasecmp(s.c_str(), "true") || s == "1") {
159  value = true;
160  } else if (!ACE_OS::strcasecmp(s.c_str(), "false") || s == "0") {
161  value = false;
162  } else {
163  return false;
164  }
165  return true;
166 }
167 
168 #if _XERCES_VERSION >= 30200
169 # define XMLDATETIME_HAS_GETEPOCH 1
170 #else
171 # define XMLDATETIME_HAS_GETEPOCH 0
172 namespace {
173  bool parse_time_string(
174  const std::string& whole, size_t& pos, std::string& value, size_t width = std::string::npos)
175  {
176  const bool to_end = width == std::string::npos;
177  const size_t size = whole.size();
178  if (pos >= size || (!to_end && pos + width > size)) {
179  return false;
180  }
181  value = whole.substr(pos, width);
182  pos = to_end ? size : pos + width;
183  return true;
184  }
185 
186  bool parse_time_field(
187  const std::string& whole, size_t& pos, int& value, size_t width = 2)
188  {
189  std::string string;
190  if (parse_time_string(whole, pos, string, width)) {
191  if (OpenDDS::DCPS::convertToInteger(string, value) && string[0] != '-') {
192  return true;
193  }
194  }
196  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time_field: "
197  "failed to get field at pos %B in \"%C\"\n",
198  pos, whole.c_str()));
199  }
200  return false;
201  }
202 
203  bool parse_time_char_or_end(
204  const std::string& whole, size_t& pos, const std::string& choices, char& c, bool& end)
205  {
206  std::string string;
207  if (parse_time_string(whole, pos, string, 1)) {
208  end = false;
209  c = string[0];
210  if (choices.find(c) != std::string::npos) {
211  return true;
212  }
214  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time_char_or_end: "
215  "failed to find one of \"%C\" at pos %B in \"%C\"\n",
216  choices.c_str(), pos, whole.c_str()));
217  }
218  return false;
219  }
220  end = true;
221  return true;
222  }
223 
224  bool parse_time_char(
225  const std::string& whole, size_t& pos, const std::string& choices)
226  {
227  char c;
228  bool end;
229  if (!parse_time_char_or_end(whole, pos, choices, c, end)) {
230  return false;
231  }
232  if (end) {
234  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time_char: "
235  "failed to find one of \"%C\" at end of \"%C\"\n",
236  choices.c_str(), whole.c_str()));
237  }
238  return false;
239  }
240  return true;
241  }
242 }
243 #endif // XMLDATETIME_HAS_GETEPOCH
244 
245 bool parse_time(const XMLCh* in, time_t& value)
246 {
247  xercesc::XMLDateTime xdt(in);
248  try {
249  xdt.parseDateTime();
250  } catch (const xercesc::XMLException& ex) {
252  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
253  "failed to parse date/time \"%C\": %C\n",
254  to_string(in).c_str(), to_string(ex).c_str()));
255  }
256  return false;
257  }
258 
259 #if XMLDATETIME_HAS_GETEPOCH
260  value = xdt.getEpoch();
261 #else
262  /*
263  * Doesn't seem like older Xerces' actually have a way to get the information
264  * from XMLDateTime, so we have to do it ourselves.
265  *
266  * For this we'll follow https://www.w3.org/TR/xmlschema-2/#dateTime, which
267  * is basically the same as ISO8601.
268  * The exceptions are:
269  * - We won't accept a negative datetime (-0001 is the year 2BCE), since the
270  * standard library might not be able to handle such a far back and we're
271  * certainly not expecting it.
272  * - The Schema spec says that times without timezone info should be
273  * interpreted as "local", but seems it be intentionally vague as what that
274  * means. The DDS Security 1.1 spec explicitly says in 9.4.1.3.2.2 that it
275  * would be UTC. Xerces getEpoch values match up with this in the unit test
276  * for this code, so it's also interpreting them as UTC instead of
277  * something like the local system timezone.
278  * - Since we use time_t we will accept but ignore fractional seconds.
279  * NOTE: The security spec is missing fractional seconds in a format
280  * description in a comment in example XML. This comment has been copied
281  * into many permissions files sitting in OpenDDS.
282  */
283 
284  const std::string str = to_string(in);
285  if (str[0] == '-') {
287  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
288  "date/time can't be negative: \"%C\"\n",
289  str.c_str()));
290  }
291  return false;
292  }
293 
294  /*
295  * After this the expected lexical format given by the Schema spec is:
296  * yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? ((('+' | '-') hh ':' mm) | 'Z')?
297  * or alternatively as the Security spec says, except with fractional seconds:
298  * CCYY-MM-DDThh:mm:ss[.fffff...][Z|(+|-)hh:mm]
299  * See unit tests for this code for examples
300  */
301  std::tm dttm;
302  size_t pos = 0;
303 
304  // Year
305  if (!parse_time_field(str, pos, dttm.tm_year, 4)) {
306  return false;
307  }
308  dttm.tm_year -= 1900;
309  if (!parse_time_char(str, pos, "-")) {
310  return false;
311  }
312  // Month
313  if (!parse_time_field(str, pos, dttm.tm_mon)) {
314  return false;
315  }
316  --dttm.tm_mon;
317  if (!parse_time_char(str, pos, "-")) {
318  return false;
319  }
320  // Day
321  if (!parse_time_field(str, pos, dttm.tm_mday)) {
322  return false;
323  }
324  if (!parse_time_char(str, pos, "T")) {
325  return false;
326  }
327  // Hour
328  if (!parse_time_field(str, pos, dttm.tm_hour)) {
329  return false;
330  }
331  if (!parse_time_char(str, pos, ":")) {
332  return false;
333  }
334  // Minutes
335  if (!parse_time_field(str, pos, dttm.tm_min)) {
336  return false;
337  }
338  if (!parse_time_char(str, pos, ":")) {
339  return false;
340  }
341  // Seconds
342  if (!parse_time_field(str, pos, dttm.tm_sec)) {
343  return false;
344  }
345 
346  // Ignore Fractional Seconds
347  if (str[pos] == '.') {
348  ++pos;
349  for (; str[pos] >= '0' && str[pos] <= '9'; ++pos) {
350  }
351  }
352 
353  // Optional Timezone Info
354  time_t timezone_offset = 0;
355  char tz_char = '\0';
356  bool end;
357  if (!parse_time_char_or_end(str, pos, "+-Z", tz_char, end)) {
358  return false;
359  }
360  if (!end && tz_char != 'Z') {
361  int tz_hour;
362  if (!parse_time_field(str, pos, tz_hour)) {
363  return false;
364  }
365 
366  if (!parse_time_char(str, pos, ":")) {
367  return false;
368  }
369 
370  int tz_min;
371  if (!parse_time_field(str, pos, tz_min)) {
372  return false;
373  }
374 
375  /*
376  * We will have to do the reverse of the sign to convert the local time to
377  * UTC. For example 00:00:00-06:00 (12AM CST) is 06:00:00+00:00 (6AM UTC).
378  */
379  const int timezone_offset_sign = tz_char == '+' ? -1 : 1;
380 
381  /*
382  * We could modify the tm struct here to account for the timezone, but then
383  * we'd also have to adjust the hour if the minutes carried over and the
384  * date fields if the hour carried over to another date. Instead we're
385  * going to calculate the number of seconds in the offset and add that to
386  * the time_t value later.
387  */
388  timezone_offset = timezone_offset_sign * (tz_hour * 60 * 60 + tz_min * 60);
389  }
390 
391  std::string leftover;
392  if (parse_time_string(str, pos, leftover)) {
394  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
395  "leftover characters in \"%C\": \"%C\"\n",
396  str.c_str(), leftover.c_str()));
397  }
398  return false;
399  }
400 
401  dttm.tm_isdst = 0; // Don't try to do anything with DST
402  value = std::mktime(&dttm);
403  if (value == time_t(-1)) {
405  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
406  "failed to convert to time_t \"%C\"\n",
407  to_string(in).c_str()));
408  }
409  return false;
410  }
411 
412  // mktime assumes the tm struct is in the local time, so we need to correct
413  // for that.
414  const time_t local_timezone = static_cast<time_t>(ace_timezone());
415  if (local_timezone == 0 && errno == ENOTSUP) {
417  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_time: "
418  "ace_timezone not supported\n"));
419  }
420  return false;
421  }
422  value -= local_timezone;
423 
424  // Adjust for the timezone specified in the string if there was one.
425  value += timezone_offset;
426 #endif // XMLDATETIME_HAS_GETEPOCH
427 
428  return value;
429 }
430 
431 namespace {
432  bool parse_domain_id(const xercesc::DOMNode* node, DomainId_t& value)
433  {
434  // NOTE: DomainId_t is a signed type, but a domain id actually can't be a negative value.
435  unsigned int i = 0;
436  const bool success = xercesc::XMLString::textToBin(node->getTextContent(), i) &&
437  i <= static_cast<unsigned>(domain_id_max);
438  if (success) {
439  value = static_cast<DomainId_t>(i);
440  }
441  return success;
442  }
443 }
444 
445 bool parse_domain_id_set(const xercesc::DOMNode* node, Security::DomainIdSet& domain_id_set)
446 {
447  const xercesc::DOMNodeList* const domainIdNodes = node->getChildNodes();
448  for (XMLSize_t did = 0, did_len = domainIdNodes->getLength(); did < did_len; ++did) {
449  const xercesc::DOMNode* const domainIdNode = domainIdNodes->item(did);
450  if (!is_element(domainIdNode)) {
451  continue;
452  }
453  const std::string domainIdNodeName = to_string(domainIdNode->getNodeName());
454  if (domainIdNodeName == "id") {
455  DomainId_t domain_id;
456  if (!parse_domain_id(domainIdNode, domain_id)) {
458  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
459  "Invalid domain ID \"%C\" in id\n",
460  to_string(domainIdNode).c_str()));
461  }
462  return false;
463  }
464  domain_id_set.add(domain_id);
465 
466  } else if (domainIdNodeName == "id_range") {
467  const xercesc::DOMNodeList* const domRangeIdNodes = domainIdNode->getChildNodes();
468  DomainId_t min_value = domain_id_min;
469  bool has_min = false;
470  DomainId_t max_value = domain_id_max;
471  bool has_max = false;
472  const XMLSize_t drid_len = domRangeIdNodes->getLength();
473  for (XMLSize_t drid = 0; drid < drid_len; ++drid) {
474  const xercesc::DOMNode* const domRangeIdNode = domRangeIdNodes->item(drid);
475  if (!is_element(domRangeIdNode)) {
476  continue;
477  }
478  const std::string domRangeIdNodeName = to_string(domRangeIdNode->getNodeName());
479  if ("min" == domRangeIdNodeName && !has_min) {
480  if (!parse_domain_id(domRangeIdNode, min_value)) {
482  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
483  "Invalid domain ID \"%C\" in min_value\n",
484  to_string(domRangeIdNode).c_str()));
485  }
486  return false;
487  }
488  has_min = true;
489 
490  } else if ("max" == domRangeIdNodeName && !has_max) {
491  if (!parse_domain_id(domRangeIdNode, max_value)) {
493  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
494  "Invalid domain ID \"%C\" in max_value\n",
495  to_string(domRangeIdNode).c_str()));
496  }
497  return false;
498  }
499  if (min_value > max_value) {
501  ACE_ERROR((LM_ERROR,"(%P|%t) ERROR: {access_error} parse_domain_id_set: "
502  "Permission XML Domain Range invalid.\n"));
503  }
504  return false;
505  }
506  has_max = true;
507 
508  } else {
510  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
511  "Invalid tag \"%C\" in id_range\n",
512  domRangeIdNodeName.c_str()));
513  }
514  return false;
515  }
516  }
517 
518  domain_id_set.add(min_value, max_value);
519 
520  } else {
522  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
523  "Invalid tag \"%C\" in domain ID set\n",
524  domainIdNodeName.c_str()));
525  }
526  return false;
527  }
528  }
529 
530  if (domain_id_set.empty()) {
532  ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} parse_domain_id_set: "
533  "empty domain ID set\n"));
534  }
535  return false;
536  }
537 
538  return true;
539 }
540 
541 } // namespace XmlUtils
542 } // namespace Security
543 } // namespace OpenDDS
544 
bool parse_domain_id_set(const xercesc::DOMNode *node, Security::DomainIdSet &domain_id_set)
Definition: XmlUtils.cpp:445
#define ACE_ERROR(X)
const LogLevel::Value value
Definition: debug.cpp:61
#define ENOTSUP
String to_dds_string(unsigned short to_convert)
DDS::DomainId_t DomainId_t
const DDS::Security::DomainId_t domain_id_max
Definition: DomainIdSet.h:22
const DDS::Security::DomainId_t domain_id_min
Definition: DomainIdSet.h:21
bool is_element(const xercesc::DOMNode *node)
Definition: XmlUtils.h:81
bool access_error
Permissions and Governance.
Definition: debug.h:132
DOMAINID_TYPE_NATIVE DomainId_t
long ace_timezone()
void Initialize(const CONFIGURATION_RESOURCE configuration_file, RETURN_CODE_TYPE &return_code)
Definition: FaceTSS.cpp:70
int strcasecmp(const char *s, const char *t)
bool parse_bool(const XMLCh *in, bool &value)
Definition: XmlUtils.cpp:150
bool get_parser(ParserPtr &parser, const std::string &filename, const std::string &xml)
Definition: XmlUtils.cpp:71
#define OPENDDS_END_VERSIONED_NAMESPACE_DECL
bool parse_time(const XMLCh *in, time_t &value)
Definition: XmlUtils.cpp:245
std::string to_string(const xercesc::SAXParseException &ex)
Definition: XmlUtils.cpp:30
LM_ERROR
The Internal API and Implementation of OpenDDS.
Definition: AddressCache.h:28
OpenDDS_Dcps_Export SecurityDebug security_debug
Definition: debug.cpp:32
bool convertToInteger(const String &s, T &value)