VirtualBox

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

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

Main/Settigs: Redone semi-faulty r29679: When reading from settings files, reopen them instead of reusing the existing hande to allow for concurrent multithreaded reads. Provided libxml2/libxslt reentrance (using global serialization) when parsing settings files.

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

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