VirtualBox

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

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

Main/Settings: FromString <bool>: Recognize "0" and "1" and forget "on/off" and "yes/no" (to match XML Schema's definition of boolean).

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