VirtualBox

source: vbox/trunk/src/VBox/Main/xml/Settings.cpp@ 6310

Last change on this file since 6310 was 6310, checked in by vboxsync, 17 years ago

Fix formatting bugs in the XML config file code. The number base was
lost, and also signed values would have been stored as unsigned.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 33.6 KB
Line 
1/** @file
2 * Settings File Manipulation API.
3 */
4
5/*
6 * Copyright (C) 2007 innotek GmbH
7 *
8 * This file is part of VirtualBox Open Source Edition (OSE), as
9 * available from http://www.215389.xyz. This file is free software;
10 * you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License as published by the Free Software Foundation,
12 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
13 * distribution. VirtualBox OSE is distributed in the hope that it will
14 * be useful, but WITHOUT ANY WARRANTY of any kind.
15 */
16
17#include "VBox/settings.h"
18
19#include "Logging.h"
20
21#include <iprt/err.h>
22#include <iprt/file.h>
23
24#include <libxml/tree.h>
25#include <libxml/parser.h>
26#include <libxml/globals.h>
27#include <libxml/xmlIO.h>
28#include <libxml/xmlsave.h>
29#include <libxml/uri.h>
30
31#include <libxml/xmlschemas.h>
32
33#include <string.h>
34
35
36/**
37 * Global module initialization structure.
38 *
39 * The constructor and destructor of this structure are used to perform global
40 * module initiaizaton and cleanup. Thee must be only one global variable of
41 * this structure.
42 */
43static
44class Global
45{
46public:
47
48 Global()
49 {
50 /* Check the parser version. The docs say it will kill the app if
51 * there is a serious version mismatch, but I couldn't find it in the
52 * source code (it only prints the error/warning message to the console) so
53 * let's leave it as is for informational purposes. */
54 LIBXML_TEST_VERSION
55
56 /* Init libxml */
57 xmlInitParser();
58
59 /* Save the default entity resolver before someone has replaced it */
60 xml.defaultEntityLoader = xmlGetExternalEntityLoader();
61 }
62
63 ~Global()
64 {
65 /* Shutdown libxml */
66 xmlCleanupParser();
67 }
68
69 struct
70 {
71 xmlExternalEntityLoader defaultEntityLoader;
72 }
73 xml;
74}
75gGlobal;
76
77
78namespace settings
79{
80
81// Helpers
82////////////////////////////////////////////////////////////////////////////////
83
84inline int sFromHex (char aChar)
85{
86 if (aChar >= '0' && aChar <= '9')
87 return aChar - '0';
88 if (aChar >= 'A' && aChar <= 'F')
89 return aChar - 'A' + 0xA;
90 if (aChar >= 'a' && aChar <= 'f')
91 return aChar - 'a' + 0xA;
92
93 throw ENoConversion (FmtStr ("'%c' (0x%02X) is not hex", aChar, aChar));
94}
95
96inline char sToHex (int aDigit)
97{
98 return (aDigit < 0xA) ? aDigit + '0' : aDigit - 0xA + 'A';
99}
100
101static char *duplicate_chars (const char *that)
102{
103 char *result = NULL;
104 if (that != NULL)
105 {
106 size_t len = strlen (that) + 1;
107 result = new char [len];
108 if (result != NULL)
109 memcpy (result, that, len);
110 }
111 return result;
112}
113
114//////////////////////////////////////////////////////////////////////////////
115// string -> type conversions
116//////////////////////////////////////////////////////////////////////////////
117
118uint64_t FromStringInteger (const char *aValue, bool aSigned,
119 int aBits, uint64_t aMin, uint64_t aMax)
120{
121 if (aValue == NULL)
122 throw ENoValue();
123
124 switch (aBits)
125 {
126 case 8:
127 case 16:
128 case 32:
129 case 64:
130 break;
131 default:
132 throw ENotImplemented (RT_SRC_POS);
133 }
134
135 if (aSigned)
136 {
137 int64_t result;
138 int vrc = RTStrToInt64Full (aValue, 0, &result);
139 if (RT_SUCCESS (vrc))
140 {
141 if (result >= (int64_t) aMin && result <= (int64_t) aMax)
142 return (uint64_t) result;
143 }
144 }
145 else
146 {
147 uint64_t result;
148 int vrc = RTStrToUInt64Full (aValue, 0, &result);
149 if (RT_SUCCESS (vrc))
150 {
151 if (result >= aMin && result <= aMax)
152 return result;
153 }
154 }
155
156 throw ENoConversion (FmtStr ("'%s' is not integer", aValue));
157}
158
159template<> bool FromString <bool> (const char *aValue)
160{
161 if (aValue == NULL)
162 throw ENoValue();
163
164 if (strcmp (aValue, "true") == 0 ||
165 strcmp (aValue, "yes") == 0 ||
166 strcmp (aValue, "on") == 0)
167 return true;
168 else if (strcmp (aValue, "false") == 0 ||
169 strcmp (aValue, "no") == 0 ||
170 strcmp (aValue, "off") == 0)
171 return false;
172
173 throw ENoConversion (FmtStr ("'%s' is not bool", aValue));
174}
175
176template<> RTTIMESPEC FromString <RTTIMESPEC> (const char *aValue)
177{
178 if (aValue == NULL)
179 throw ENoValue();
180
181 /* Parse ISO date (xsd:dateTime). The format is:
182 * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
183 * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */
184 uint32_t yyyy = 0;
185 uint16_t mm = 0, dd = 0, hh = 0, mi = 0, ss = 0;
186 char buf [256];
187 if (strlen (aValue) > ELEMENTS (buf) - 1 ||
188 sscanf (aValue, "%d-%hu-%huT%hu:%hu:%hu%s",
189 &yyyy, &mm, &dd, &hh, &mi, &ss, buf) == 7)
190 {
191 /* currently, we accept only the UTC timezone ('Z'),
192 * ignoring fractional seconds, if present */
193 if (buf [0] == 'Z' ||
194 (buf [0] == '.' && buf [strlen (buf) - 1] == 'Z'))
195 {
196 RTTIME time = { yyyy, (uint8_t) mm, 0, 0, (uint8_t) dd,
197 (uint8_t) hh, (uint8_t) mi, (uint8_t) ss, 0,
198 RTTIME_FLAGS_TYPE_UTC };
199 if (RTTimeNormalize (&time))
200 {
201 RTTIMESPEC timeSpec;
202 if (RTTimeImplode (&timeSpec, &time))
203 return timeSpec;
204 }
205 }
206 else
207 throw ENoConversion (FmtStr ("'%s' is not UTC date", aValue));
208 }
209
210 throw ENoConversion (FmtStr ("'%s' is not ISO date", aValue));
211}
212
213stdx::char_auto_ptr FromString (const char *aValue, size_t *aLen)
214{
215 if (aValue == NULL)
216 throw ENoValue();
217
218 /* each two chars produce one byte */
219 size_t len = strlen (aValue) / 2;
220
221 /* therefore, the original length must be even */
222 if (len % 2 != 0)
223 throw ENoConversion (FmtStr ("'%.*s' is not binary data",
224 aLen, aValue));
225
226 stdx::char_auto_ptr result (new char [len]);
227
228 const char *src = aValue;
229 char *dst = result.get();
230
231 for (size_t i = 0; i < len; ++ i, ++ dst)
232 {
233 *dst = sFromHex (*src ++) << 4;
234 *dst |= sFromHex (*src ++);
235 }
236
237 if (aLen != NULL)
238 *aLen = len;
239
240 return result;
241}
242
243//////////////////////////////////////////////////////////////////////////////
244// type -> string conversions
245//////////////////////////////////////////////////////////////////////////////
246
247stdx::char_auto_ptr ToStringInteger (uint64_t aValue, unsigned int aBase,
248 bool aSigned, int aBits)
249{
250 unsigned int flags = RTSTR_F_SPECIAL;
251 if (aSigned)
252 flags |= RTSTR_F_VALSIGNED;
253
254 /* maximum is binary representation + terminator */
255 size_t len = aBits + 1;
256
257 switch (aBits)
258 {
259 case 8:
260 flags |= RTSTR_F_8BIT;
261 break;
262 case 16:
263 flags |= RTSTR_F_16BIT;
264 break;
265 case 32:
266 flags |= RTSTR_F_32BIT;
267 break;
268 case 64:
269 flags |= RTSTR_F_64BIT;
270 break;
271 default:
272 throw ENotImplemented (RT_SRC_POS);
273 }
274
275 stdx::char_auto_ptr result (new char [len]);
276 int vrc = RTStrFormatNumber (result.get(), aValue, aBase, 0, 0, flags);
277 if (RT_SUCCESS (vrc))
278 return result;
279
280 throw EIPRTFailure (vrc);
281}
282
283template<> stdx::char_auto_ptr ToString <bool> (const bool &aValue,
284 unsigned int aExtra /* = 0 */)
285{
286 stdx::char_auto_ptr result (duplicate_chars (aValue ? "true" : "false"));
287 return result;
288}
289
290template<> stdx::char_auto_ptr ToString <RTTIMESPEC> (const RTTIMESPEC &aValue,
291 unsigned int aExtra /* = 0 */)
292{
293 RTTIME time;
294 if (!RTTimeExplode (&time, &aValue))
295 throw ENoConversion (FmtStr ("timespec %lld ms is invalid",
296 RTTimeSpecGetMilli (&aValue)));
297
298 /* Store ISO date (xsd:dateTime). The format is:
299 * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
300 * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */
301 char buf [256];
302 RTStrPrintf (buf, sizeof (buf),
303 "%04ld-%02hd-%02hdT%02hd:%02hd:%02hdZ",
304 time.i32Year, (uint16_t) time.u8Month, (uint16_t) time.u8MonthDay,
305 (uint16_t) time.u8Hour, (uint16_t) time.u8Minute, (uint16_t) time.u8Second);
306
307 stdx::char_auto_ptr result (duplicate_chars (buf));
308 return result;
309}
310
311stdx::char_auto_ptr ToString (const char *aData, size_t aLen)
312{
313 /* each byte will produce two hex digits and there will be a null
314 * terminator */
315 stdx::char_auto_ptr result (new char [aLen * 2 + 1]);
316
317 const char *src = aData;
318 char *dst = result.get();
319
320 for (size_t i = 0; i < aLen; ++ i, ++ src)
321 {
322 *dst++ = sToHex ((*src) >> 4);
323 *dst++ = sToHex ((*src) & 0xF);
324 }
325
326 *dst = '\0';
327
328 return result;
329}
330
331//////////////////////////////////////////////////////////////////////////////
332// File Class
333//////////////////////////////////////////////////////////////////////////////
334
335struct File::Data
336{
337 Data()
338 : fileName (NULL), handle (NIL_RTFILE), opened (false) {}
339
340 Mode mode;
341 char *fileName;
342 RTFILE handle;
343 bool opened : 1;
344};
345
346File::File (Mode aMode, const char *aFileName)
347 : m (new Data())
348{
349 m->mode = aMode;
350
351 m->fileName = RTStrDup (aFileName);
352 if (m->fileName == NULL)
353 throw ENoMemory();
354
355 unsigned flags = 0;
356 switch (aMode)
357 {
358 case Read:
359 flags = RTFILE_O_READ;
360 break;
361 case Write:
362 flags = RTFILE_O_WRITE | RTFILE_O_CREATE;
363 break;
364 case ReadWrite:
365 flags = RTFILE_O_READ | RTFILE_O_WRITE;
366 }
367
368 int vrc = RTFileOpen (&m->handle, aFileName, flags);
369 if (RT_FAILURE (vrc))
370 throw EIPRTFailure (vrc);
371
372 m->opened = true;
373}
374
375File::File (Mode aMode, RTFILE aHandle, const char *aFileName /* = NULL */ )
376 : m (new Data())
377{
378 if (aHandle == NIL_RTFILE)
379 throw EInvalidArg (RT_SRC_POS);
380
381 m->mode = aMode;
382 m->handle = aHandle;
383
384 if (aFileName)
385 {
386 m->fileName = RTStrDup (aFileName);
387 if (m->fileName == NULL)
388 throw ENoMemory();
389 }
390
391 setPos (0);
392}
393
394File::~File()
395{
396 if (m->opened)
397 RTFileClose (m->handle);
398
399 RTStrFree (m->fileName);
400}
401
402const char *File::uri() const
403{
404 return m->fileName;
405}
406
407uint64_t File::pos() const
408{
409 uint64_t p = 0;
410 int vrc = RTFileSeek (m->handle, 0, RTFILE_SEEK_CURRENT, &p);
411 if (RT_SUCCESS (vrc))
412 return p;
413
414 throw EIPRTFailure (vrc);
415}
416
417void File::setPos (uint64_t aPos)
418{
419 uint64_t p = 0;
420 unsigned method = RTFILE_SEEK_BEGIN;
421 int vrc = VINF_SUCCESS;
422
423 /* check if we overflow int64_t and move to INT64_MAX first */
424 if (((int64_t) aPos) < 0)
425 {
426 vrc = RTFileSeek (m->handle, INT64_MAX, method, &p);
427 aPos -= (uint64_t) INT64_MAX;
428 method = RTFILE_SEEK_CURRENT;
429 }
430 /* seek the rest */
431 if (RT_SUCCESS (vrc))
432 vrc = RTFileSeek (m->handle, (int64_t) aPos, method, &p);
433 if (RT_SUCCESS (vrc))
434 return;
435
436 throw EIPRTFailure (vrc);
437}
438
439int File::read (char *aBuf, int aLen)
440{
441 size_t len = aLen;
442 int vrc = RTFileRead (m->handle, aBuf, len, &len);
443 if (RT_SUCCESS (vrc))
444 return len;
445
446 throw EIPRTFailure (vrc);
447}
448
449int File::write (const char *aBuf, int aLen)
450{
451 size_t len = aLen;
452 int vrc = RTFileWrite (m->handle, aBuf, len, &len);
453 if (RT_SUCCESS (vrc))
454 return len;
455
456 throw EIPRTFailure (vrc);
457
458 return -1 /* failure */;
459}
460
461void File::truncate()
462{
463 int vrc = RTFileSetSize (m->handle, pos());
464 if (RT_SUCCESS (vrc))
465 return;
466
467 throw EIPRTFailure (vrc);
468}
469
470//////////////////////////////////////////////////////////////////////////////
471// MemoryBuf Class
472//////////////////////////////////////////////////////////////////////////////
473
474struct MemoryBuf::Data
475{
476 Data()
477 : buf (NULL), len (0), uri (NULL), pos (0) {}
478
479 const char *buf;
480 size_t len;
481 char *uri;
482
483 size_t pos;
484};
485
486MemoryBuf::MemoryBuf (const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
487 : m (new Data())
488{
489 if (aBuf == NULL)
490 throw EInvalidArg (RT_SRC_POS);
491
492 m->buf = aBuf;
493 m->len = aLen;
494 m->uri = RTStrDup (aURI);
495}
496
497MemoryBuf::~MemoryBuf()
498{
499 RTStrFree (m->uri);
500}
501
502const char *MemoryBuf::uri() const
503{
504 return m->uri;
505}
506
507uint64_t MemoryBuf::pos() const
508{
509 return m->pos;
510}
511
512void MemoryBuf::setPos (uint64_t aPos)
513{
514 size_t pos = (size_t) aPos;
515 if ((uint64_t) pos != aPos)
516 throw EInvalidArg();
517
518 if (pos > m->len)
519 throw EInvalidArg();
520
521 m->pos = pos;
522}
523
524int MemoryBuf::read (char *aBuf, int aLen)
525{
526 if (m->pos >= m->len)
527 return 0 /* nothing to read */;
528
529 size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
530 memcpy (aBuf, m->buf + m->pos, len);
531 m->pos += len;
532
533 return len;
534}
535
536//////////////////////////////////////////////////////////////////////////////
537// XmlKeyBackend Class
538//////////////////////////////////////////////////////////////////////////////
539
540class XmlKeyBackend : public Key::Backend
541{
542public:
543
544 XmlKeyBackend (xmlNodePtr aNode);
545 ~XmlKeyBackend();
546
547 const char *name() const;
548 void setName (const char *aName);
549 const char *value (const char *aName) const;
550 void setValue (const char *aName, const char *aValue);
551
552 Key::List keys (const char *aName = NULL) const;
553 Key findKey (const char *aName) const;
554
555 Key appendKey (const char *aName);
556 void zap();
557
558 void *position() const { return mNode; }
559
560private:
561
562 xmlNodePtr mNode;
563
564 xmlChar *mNodeText;
565
566 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (XmlKeyBackend);
567
568 friend class XmlTreeBackend;
569};
570
571XmlKeyBackend::XmlKeyBackend (xmlNodePtr aNode)
572 : mNode (aNode), mNodeText (NULL)
573{
574 AssertReturnVoid (mNode);
575 AssertReturnVoid (mNode->type == XML_ELEMENT_NODE);
576}
577
578XmlKeyBackend::~XmlKeyBackend()
579{
580 xmlFree (mNodeText);
581}
582
583const char *XmlKeyBackend::name() const
584{
585 return mNode ? (char *) mNode->name : NULL;
586}
587
588void XmlKeyBackend::setName (const char *aName)
589{
590 throw ENotImplemented (RT_SRC_POS);
591}
592
593const char *XmlKeyBackend::value (const char *aName) const
594{
595 if (!mNode)
596 return NULL;
597
598 if (aName == NULL)
599 {
600 /* @todo xmlNodeListGetString (,,1) returns NULL for things like
601 * <Foo></Foo> and may want to return "" in this case to distinguish
602 * from <Foo/> (where NULL is pretty much expected). */
603 if (!mNodeText)
604 unconst (mNodeText) =
605 xmlNodeListGetString (mNode->doc, mNode->children, 0);
606 return (char *) mNodeText;
607 }
608
609 xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName);
610 if (!attr)
611 return NULL;
612
613 if (attr->type == XML_ATTRIBUTE_NODE)
614 {
615 /* @todo for now, we only understand the most common case: only 1 text
616 * node comprises the attribute's contents. Otherwise we'd need to
617 * return a newly allocated string buffer to the caller that
618 * concatenates all text nodes and obey him to free it or provide our
619 * own internal map of attribute=value pairs and return const pointers
620 * to values from this map. */
621 if (attr->children != NULL &&
622 attr->children->next == NULL &&
623 (attr->children->type == XML_TEXT_NODE ||
624 attr->children->type == XML_CDATA_SECTION_NODE))
625 return (char *) attr->children->content;
626 }
627 else if (attr->type == XML_ATTRIBUTE_DECL)
628 {
629 return (char *) ((xmlAttributePtr) attr)->defaultValue;
630 }
631
632 return NULL;
633}
634
635void XmlKeyBackend::setValue (const char *aName, const char *aValue)
636{
637 if (!mNode)
638 return;
639
640 if (aName == NULL)
641 {
642 xmlChar *value = (xmlChar *) aValue;
643 if (value != NULL)
644 {
645 value = xmlEncodeSpecialChars (mNode->doc, value);
646 if (value == NULL)
647 throw ENoMemory();
648 }
649
650 xmlNodeSetContent (mNode, value);
651
652 if (value != (xmlChar *) aValue)
653 xmlFree (value);
654
655 /* outdate the node text holder */
656 if (mNodeText != NULL)
657 {
658 xmlFree (mNodeText);
659 mNodeText = NULL;
660 }
661
662 return;
663 }
664
665 if (aValue == NULL)
666 {
667 /* remove the attribute if it exists */
668 xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName);
669 if (attr != NULL)
670 {
671 int rc = xmlRemoveProp (attr);
672 if (rc != 0)
673 throw EInvalidArg (RT_SRC_POS);
674 }
675 return;
676 }
677
678 xmlAttrPtr attr = xmlSetProp (mNode, (const xmlChar *) aName,
679 (const xmlChar *) aValue);
680 if (attr == NULL)
681 throw ENoMemory();
682}
683
684Key::List XmlKeyBackend::keys (const char *aName /* = NULL */) const
685{
686 Key::List list;
687
688 if (!mNode)
689 return list;
690
691 for (xmlNodePtr node = mNode->children; node; node = node->next)
692 {
693 if (node->type == XML_ELEMENT_NODE)
694 {
695 if (aName == NULL ||
696 strcmp (aName, (char *) node->name) == 0)
697 list.push_back (Key (new XmlKeyBackend (node)));
698 }
699 }
700
701 return list;
702}
703
704Key XmlKeyBackend::findKey (const char *aName) const
705{
706 Key key;
707
708 if (!mNode)
709 return key;
710
711 for (xmlNodePtr node = mNode->children; node; node = node->next)
712 {
713 if (node->type == XML_ELEMENT_NODE)
714 {
715 if (aName == NULL ||
716 strcmp (aName, (char *) node->name) == 0)
717 {
718 key = Key (new XmlKeyBackend (node));
719 break;
720 }
721 }
722 }
723
724 return key;
725}
726
727Key XmlKeyBackend::appendKey (const char *aName)
728{
729 if (!mNode)
730 return Key();
731
732 xmlNodePtr node = xmlNewChild (mNode, NULL, (const xmlChar *) aName, NULL);
733 if (node == NULL)
734 throw ENoMemory();
735
736 return Key (new XmlKeyBackend (node));
737}
738
739void XmlKeyBackend::zap()
740{
741 if (!mNode)
742 return;
743
744 xmlUnlinkNode (mNode);
745 xmlFreeNode (mNode);
746 mNode = NULL;
747}
748
749//////////////////////////////////////////////////////////////////////////////
750// XmlTreeBackend Class
751//////////////////////////////////////////////////////////////////////////////
752
753class XmlTreeBackend::XmlError : public XmlTreeBackend::Error
754{
755public:
756
757 XmlError (xmlErrorPtr aErr)
758 {
759 if (!aErr)
760 throw EInvalidArg (RT_SRC_POS);
761
762 char *msg = Format (aErr);
763 setWhat (msg);
764 RTStrFree (msg);
765 }
766
767 /**
768 * Composes a single message for the given error. The caller must free the
769 * returned string using RTStrFree() when no more necessary.
770 */
771 static char *Format (xmlErrorPtr aErr)
772 {
773 const char *msg = aErr->message ? aErr->message : "<none>";
774 size_t msgLen = strlen (msg);
775 /* strip spaces, trailing EOLs and dot-like char */
776 while (msgLen && strchr (" \n.?!", msg [msgLen - 1]))
777 -- msgLen;
778
779 char *finalMsg = NULL;
780 RTStrAPrintf (&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
781 msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
782
783 return finalMsg;
784 }
785};
786
787struct XmlTreeBackend::Data
788{
789 Data() : ctxt (NULL), doc (NULL)
790 , inputResolver (NULL) {}
791
792 xmlParserCtxtPtr ctxt;
793 xmlDocPtr doc;
794
795 Key root;
796
797 InputResolver *inputResolver;
798
799 std::auto_ptr <stdx::exception_trap_base> trappedErr;
800
801 /**
802 * This is to avoid throwing exceptions while in libxml2 code and
803 * redirect them to our level instead. Also used to perform clean up
804 * by deleting the I/O stream instance and self when requested.
805 */
806 struct IOCtxt
807 {
808 IOCtxt (Stream *aStream, std::auto_ptr <stdx::exception_trap_base> &aErr)
809 : stream (aStream), deleteStreamOnClose (false)
810 , err (aErr) {}
811
812 template <typename T>
813 void setErr (const T& aErr) { err.reset (new stdx::exception_trap <T> (aErr)); }
814
815 void resetErr() { err.reset(); }
816
817 Stream *stream;
818 bool deleteStreamOnClose;
819
820 std::auto_ptr <stdx::exception_trap_base> &err;
821 };
822
823 struct InputCtxt : public IOCtxt
824 {
825 InputCtxt (Input *aInput, std::auto_ptr <stdx::exception_trap_base> &aErr)
826 : IOCtxt (aInput, aErr), input (aInput) {}
827
828 Input *input;
829 };
830
831 struct OutputCtxt : public IOCtxt
832 {
833 OutputCtxt (Output *aOutput, std::auto_ptr <stdx::exception_trap_base> &aErr)
834 : IOCtxt (aOutput, aErr), output (aOutput) {}
835
836 Output *output;
837 };
838};
839
840XmlTreeBackend::XmlTreeBackend()
841 : m (new Data())
842{
843 /* create a parser context */
844 m->ctxt = xmlNewParserCtxt();
845 if (m->ctxt == NULL)
846 throw ENoMemory();
847}
848
849XmlTreeBackend::~XmlTreeBackend()
850{
851 reset();
852
853 xmlFreeParserCtxt (m->ctxt);
854 m->ctxt = NULL;
855}
856
857void XmlTreeBackend::setInputResolver (InputResolver &aResolver)
858{
859 m->inputResolver = &aResolver;
860}
861
862void XmlTreeBackend::resetInputResolver()
863{
864 m->inputResolver = NULL;
865}
866
867void XmlTreeBackend::rawRead (Input &aInput, const char *aSchema /* = NULL */,
868 int aFlags /* = 0 */)
869{
870 /* Reset error variables used to memorize exceptions while inside the
871 * libxml2 code. */
872 m->trappedErr.reset();
873
874 /* Set up an input stream for parsing the document. This will be deleted
875 * when the stream is closed by the libxml2 API (e.g. when calling
876 * xmlFreeParserCtxt()). */
877 Data::InputCtxt *inputCtxt =
878 new Data::InputCtxt (&aInput, m->trappedErr);
879
880 /* Set up the external entity resolver. Note that we do it in a
881 * thread-unsafe fashion because this stuff is not thread-safe in libxml2.
882 * Making it thread-safe would require a) guarding this method with a
883 * mutex and b) requiring our API caller not to use libxml2 on some other
884 * thread (which is not practically possible). So, our API is not
885 * thread-safe for now. */
886 xmlExternalEntityLoader oldEntityLoader = xmlGetExternalEntityLoader();
887 sThat = this;
888 xmlSetExternalEntityLoader (ExternalEntityLoader);
889
890 /* Note: when parsing we use XML_PARSE_NOBLANKS to instruct libxml2 to
891 * remove text nodes that contain only blanks. This is important because
892 * otherwise xmlSaveDoc() won't be able to do proper indentation on
893 * output. */
894
895 /* parse the stream */
896 xmlDocPtr doc = xmlCtxtReadIO (m->ctxt,
897 ReadCallback, CloseCallback,
898 inputCtxt, aInput.uri(), NULL,
899 XML_PARSE_NOBLANKS);
900 if (doc == NULL)
901 {
902 /* restore the previous entity resolver */
903 xmlSetExternalEntityLoader (oldEntityLoader);
904 sThat = NULL;
905
906 /* look if there was a forwared exception from the lower level */
907 if (m->trappedErr.get() != NULL)
908 m->trappedErr->rethrow();
909
910 throw XmlError (xmlCtxtGetLastError (m->ctxt));
911 }
912
913 if (aSchema != NULL)
914 {
915 xmlSchemaParserCtxtPtr schemaCtxt = NULL;
916 xmlSchemaPtr schema = NULL;
917 xmlSchemaValidCtxtPtr validCtxt = NULL;
918 char *errorStr = NULL;
919
920 /* validate the document */
921 try
922 {
923 bool valid = false;
924
925 schemaCtxt = xmlSchemaNewParserCtxt (aSchema);
926 if (schemaCtxt == NULL)
927 throw LogicError (RT_SRC_POS);
928
929 /* set our error handlers */
930 xmlSchemaSetParserErrors (schemaCtxt, ValidityErrorCallback,
931 ValidityWarningCallback, &errorStr);
932 xmlSchemaSetParserStructuredErrors (schemaCtxt,
933 StructuredErrorCallback,
934 &errorStr);
935 /* load schema */
936 schema = xmlSchemaParse (schemaCtxt);
937 if (schema != NULL)
938 {
939 validCtxt = xmlSchemaNewValidCtxt (schema);
940 if (validCtxt == NULL)
941 throw LogicError (RT_SRC_POS);
942
943 /* instruct to create default attribute's values in the document */
944 if (aFlags & Read_AddDefaults)
945 xmlSchemaSetValidOptions (validCtxt, XML_SCHEMA_VAL_VC_I_CREATE);
946
947 /* set our error handlers */
948 xmlSchemaSetValidErrors (validCtxt, ValidityErrorCallback,
949 ValidityWarningCallback, &errorStr);
950
951 /* finally, validate */
952 valid = xmlSchemaValidateDoc (validCtxt, doc) == 0;
953 }
954
955 if (!valid)
956 {
957 /* look if there was a forwared exception from the lower level */
958 if (m->trappedErr.get() != NULL)
959 m->trappedErr->rethrow();
960
961 if (errorStr == NULL)
962 throw LogicError (RT_SRC_POS);
963
964 throw Error (errorStr);
965 /* errorStr is freed in catch(...) below */
966 }
967
968 RTStrFree (errorStr);
969
970 xmlSchemaFreeValidCtxt (validCtxt);
971 xmlSchemaFree (schema);
972 xmlSchemaFreeParserCtxt (schemaCtxt);
973 }
974 catch (...)
975 {
976 /* restore the previous entity resolver */
977 xmlSetExternalEntityLoader (oldEntityLoader);
978 sThat = NULL;
979
980 RTStrFree (errorStr);
981
982 if (validCtxt)
983 xmlSchemaFreeValidCtxt (validCtxt);
984 if (schema)
985 xmlSchemaFree (schema);
986 if (schemaCtxt)
987 xmlSchemaFreeParserCtxt (schemaCtxt);
988
989 throw;
990 }
991 }
992
993 /* restore the previous entity resolver */
994 xmlSetExternalEntityLoader (oldEntityLoader);
995 sThat = NULL;
996
997 /* reset the previous tree on success */
998 reset();
999
1000 m->doc = doc;
1001 /* assign the root key */
1002 m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc)));
1003}
1004
1005void XmlTreeBackend::rawWrite (Output &aOutput)
1006{
1007 /* reset error variables used to memorize exceptions while inside the
1008 * libxml2 code */
1009 m->trappedErr.reset();
1010
1011 /* set up an input stream for parsing the document. This will be deleted
1012 * when the stream is closed by the libxml2 API (e.g. when calling
1013 * xmlFreeParserCtxt()). */
1014 Data::OutputCtxt *outputCtxt =
1015 new Data::OutputCtxt (&aOutput, m->trappedErr);
1016
1017 /* serialize to the stream */
1018
1019 xmlIndentTreeOutput = 1;
1020 xmlTreeIndentString = " ";
1021 xmlSaveNoEmptyTags = 0;
1022
1023 xmlSaveCtxtPtr saveCtxt = xmlSaveToIO (WriteCallback, CloseCallback,
1024 outputCtxt, NULL,
1025 XML_SAVE_FORMAT);
1026 if (saveCtxt == NULL)
1027 throw LogicError (RT_SRC_POS);
1028
1029 long rc = xmlSaveDoc (saveCtxt, m->doc);
1030 if (rc == -1)
1031 {
1032 /* look if there was a forwared exception from the lower level */
1033 if (m->trappedErr.get() != NULL)
1034 m->trappedErr->rethrow();
1035
1036 /* there must be an exception from the Output implementation,
1037 * otherwise the save operation must always succeed. */
1038 throw LogicError (RT_SRC_POS);
1039 }
1040
1041 xmlSaveClose (saveCtxt);
1042}
1043
1044void XmlTreeBackend::reset()
1045{
1046 if (m->doc)
1047 {
1048 /* reset the root key's node */
1049 GetKeyBackend (m->root)->mNode = NULL;
1050 /* free the document*/
1051 xmlFreeDoc (m->doc);
1052 m->doc = NULL;
1053 }
1054}
1055
1056Key &XmlTreeBackend::rootKey() const
1057{
1058 return m->root;
1059}
1060
1061/* static */
1062int XmlTreeBackend::ReadCallback (void *aCtxt, char *aBuf, int aLen)
1063{
1064 AssertReturn (aCtxt != NULL, 0);
1065
1066 Data::InputCtxt *ctxt = static_cast <Data::InputCtxt *> (aCtxt);
1067
1068 /* To prevent throwing exceptions while inside libxml2 code, we catch
1069 * them and forward to our level using a couple of variables. */
1070 try
1071 {
1072 return ctxt->input->read (aBuf, aLen);
1073 }
1074 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1075 catch (const settings::Error &err) { ctxt->setErr (err); }
1076 catch (const std::exception &err) { ctxt->setErr (err); }
1077 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1078
1079 return -1 /* failure */;
1080}
1081
1082/* static */
1083int XmlTreeBackend::WriteCallback (void *aCtxt, const char *aBuf, int aLen)
1084{
1085 AssertReturn (aCtxt != NULL, 0);
1086
1087 Data::OutputCtxt *ctxt = static_cast <Data::OutputCtxt *> (aCtxt);
1088
1089 /* To prevent throwing exceptions while inside libxml2 code, we catch
1090 * them and forward to our level using a couple of variables. */
1091 try
1092 {
1093 return ctxt->output->write (aBuf, aLen);
1094 }
1095 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1096 catch (const settings::Error &err) { ctxt->setErr (err); }
1097 catch (const std::exception &err) { ctxt->setErr (err); }
1098 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1099
1100 return -1 /* failure */;
1101}
1102
1103/* static */
1104int XmlTreeBackend::CloseCallback (void *aCtxt)
1105{
1106 AssertReturn (aCtxt != NULL, 0);
1107
1108 Data::IOCtxt *ctxt = static_cast <Data::IOCtxt *> (aCtxt);
1109
1110 /* To prevent throwing exceptions while inside libxml2 code, we catch
1111 * them and forward to our level using a couple of variables. */
1112 try
1113 {
1114 /// @todo there is no explicit close semantics in Stream yet
1115#if 0
1116 ctxt->stream->close();
1117#endif
1118
1119 /* perform cleanup when necessary */
1120 if (ctxt->deleteStreamOnClose)
1121 delete ctxt->stream;
1122
1123 delete ctxt;
1124
1125 return 0 /* success */;
1126 }
1127 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1128 catch (const settings::Error &err) { ctxt->setErr (err); }
1129 catch (const std::exception &err) { ctxt->setErr (err); }
1130 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1131
1132 return -1 /* failure */;
1133}
1134
1135/* static */
1136void XmlTreeBackend::ValidityErrorCallback (void *aCtxt, const char *aMsg, ...)
1137{
1138 AssertReturnVoid (aCtxt != NULL);
1139 AssertReturnVoid (aMsg != NULL);
1140
1141 char * &str = *(char * *) aCtxt;
1142
1143 char *newMsg = NULL;
1144 {
1145 va_list args;
1146 va_start (args, aMsg);
1147 RTStrAPrintfV (&newMsg, aMsg, args);
1148 va_end (args);
1149 }
1150
1151 AssertReturnVoid (newMsg != NULL);
1152
1153 /* strip spaces, trailing EOLs and dot-like char */
1154 size_t newMsgLen = strlen (newMsg);
1155 while (newMsgLen && strchr (" \n.?!", newMsg [newMsgLen - 1]))
1156 -- newMsgLen;
1157
1158 if (str == NULL)
1159 {
1160 str = newMsg;
1161 newMsg [newMsgLen] = '\0';
1162 }
1163 else
1164 {
1165 /* append to the existing string */
1166 size_t strLen = strlen (str);
1167 char *newStr = (char *) RTMemRealloc (str, strLen + 2 + newMsgLen + 1);
1168 AssertReturnVoid (newStr != NULL);
1169
1170 memcpy (newStr + strLen, ".\n", 2);
1171 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1172 newStr [strLen + 2 + newMsgLen] = '\0';
1173 str = newStr;
1174 RTStrFree (newMsg);
1175 }
1176}
1177
1178/* static */
1179void XmlTreeBackend::ValidityWarningCallback (void *aCtxt, const char *aMsg, ...)
1180{
1181 NOREF (aCtxt);
1182 NOREF (aMsg);
1183}
1184
1185/* static */
1186void XmlTreeBackend::StructuredErrorCallback (void *aCtxt, xmlErrorPtr aErr)
1187{
1188 AssertReturnVoid (aCtxt != NULL);
1189 AssertReturnVoid (aErr != NULL);
1190
1191 char * &str = *(char * *) aCtxt;
1192
1193 char *newMsg = XmlError::Format (aErr);
1194 AssertReturnVoid (newMsg != NULL);
1195
1196 if (str == NULL)
1197 str = newMsg;
1198 else
1199 {
1200 /* append to the existing string */
1201 size_t newMsgLen = strlen (newMsg);
1202 size_t strLen = strlen (str);
1203 char *newStr = (char *) RTMemRealloc (str, strLen + newMsgLen + 2);
1204 AssertReturnVoid (newStr != NULL);
1205
1206 memcpy (newStr + strLen, ".\n", 2);
1207 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1208 str = newStr;
1209 RTStrFree (newMsg);
1210 }
1211}
1212
1213/* static */
1214XmlTreeBackend *XmlTreeBackend::sThat = NULL;
1215
1216/* static */
1217xmlParserInputPtr XmlTreeBackend::ExternalEntityLoader (const char *aURI,
1218 const char *aID,
1219 xmlParserCtxtPtr aCtxt)
1220{
1221 AssertReturn (sThat != NULL, NULL);
1222
1223 if (sThat->m->inputResolver == NULL)
1224 return gGlobal.xml.defaultEntityLoader (aURI, aID, aCtxt);
1225
1226 /* To prevent throwing exceptions while inside libxml2 code, we catch
1227 * them and forward to our level using a couple of variables. */
1228 try
1229 {
1230 Input *input = sThat->m->inputResolver->resolveEntity (aURI, aID);
1231 if (input == NULL)
1232 return NULL;
1233
1234 Data::InputCtxt *ctxt = new Data::InputCtxt (input, sThat->m->trappedErr);
1235 ctxt->deleteStreamOnClose = true;
1236
1237 /* create an input buffer with custom hooks */
1238 xmlParserInputBufferPtr bufPtr =
1239 xmlParserInputBufferCreateIO (ReadCallback, CloseCallback,
1240 ctxt, XML_CHAR_ENCODING_NONE);
1241 if (bufPtr)
1242 {
1243 /* create an input stream */
1244 xmlParserInputPtr inputPtr =
1245 xmlNewIOInputStream (aCtxt, bufPtr, XML_CHAR_ENCODING_NONE);
1246
1247 if (inputPtr != NULL)
1248 {
1249 /* pass over the URI to the stream struct (it's NULL by
1250 * default) */
1251 inputPtr->filename =
1252 (char *) xmlCanonicPath ((const xmlChar *) input->uri());
1253 return inputPtr;
1254 }
1255 }
1256
1257 /* either of libxml calls failed */
1258
1259 if (bufPtr)
1260 xmlFreeParserInputBuffer (bufPtr);
1261
1262 delete input;
1263 delete ctxt;
1264
1265 throw ENoMemory();
1266 }
1267 catch (const EIPRTFailure &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1268 catch (const settings::Error &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1269 catch (const std::exception &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1270 catch (...) { sThat->m->trappedErr.reset (stdx::new_exception_trap (LogicError (RT_SRC_POS))); }
1271
1272 return NULL;
1273}
1274
1275} /* namespace settings */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette