VirtualBox

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

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

Main/Settings: Perform conversion in a loop to allow for multi-step version to version updates (e.g. v1.0->v1.1->v1.2) which saves from patching all previous transformation rules when on every version change but still makes it possible to update from too old versions to the most recent one.

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