VirtualBox

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

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

Main/Settings: Throw LogicError only when there is no error message.

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