/** @file * Settings File Manipulation API. */ /* * Copyright (C) 2007 innotek GmbH * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE * distribution. VirtualBox OSE is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY of any kind. */ #include "VBox/settings.h" #include "Logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Global module initialization structure. * * The constructor and destructor of this structure are used to perform global * module initiaizaton and cleanup. Thee must be only one global variable of * this structure. */ static class Global { public: Global() { /* Check the parser version. The docs say it will kill the app if * there is a serious version mismatch, but I couldn't find it in the * source code (it only prints the error/warning message to the console) so * let's leave it as is for informational purposes. */ LIBXML_TEST_VERSION /* Init libxml */ xmlInitParser(); /* Save the default entity resolver before someone has replaced it */ xml.defaultEntityLoader = xmlGetExternalEntityLoader(); } ~Global() { /* Shutdown libxml */ xmlCleanupParser(); } struct { xmlExternalEntityLoader defaultEntityLoader; } xml; } gGlobal; namespace settings { // Helpers //////////////////////////////////////////////////////////////////////////////// inline int sFromHex (char aChar) { if (aChar >= '0' && aChar <= '9') return aChar - '0'; if (aChar >= 'A' && aChar <= 'F') return aChar - 'A' + 0xA; if (aChar >= 'a' && aChar <= 'f') return aChar - 'a' + 0xA; throw ENoConversion (FmtStr ("'%c' (0x%02X) is not hex", aChar, aChar)); } inline char sToHex (int aDigit) { return (aDigit < 0xA) ? aDigit + '0' : aDigit - 0xA + 'A'; } static char *duplicate_chars (const char *that) { char *result = NULL; if (that != NULL) { size_t len = strlen (that) + 1; result = new char [len]; if (result != NULL) memcpy (result, that, len); } return result; } ////////////////////////////////////////////////////////////////////////////// // string -> type conversions ////////////////////////////////////////////////////////////////////////////// uint64_t FromStringInteger (const char *aValue, bool aSigned, int aBits, uint64_t aMin, uint64_t aMax) { if (aValue == NULL) throw ENoValue(); switch (aBits) { case 8: case 16: case 32: case 64: break; default: throw ENotImplemented (RT_SRC_POS); } if (aSigned) { int64_t result; int vrc = RTStrToInt64Full (aValue, 0, &result); if (RT_SUCCESS (vrc)) { if (result >= (int64_t) aMin && result <= (int64_t) aMax) return (uint64_t) result; } } else { uint64_t result; int vrc = RTStrToUInt64Full (aValue, 0, &result); if (RT_SUCCESS (vrc)) { if (result >= aMin && result <= aMax) return result; } } throw ENoConversion (FmtStr ("'%s' is not integer", aValue)); } template<> bool FromString (const char *aValue) { if (aValue == NULL) throw ENoValue(); if (strcmp (aValue, "true") == 0 || strcmp (aValue, "1") == 0) /* This contradicts the XML Schema's interpretation of boolean: */ //strcmp (aValue, "yes") == 0 || //strcmp (aValue, "on") == 0) return true; else if (strcmp (aValue, "false") == 0 || strcmp (aValue, "0") == 0) /* This contradicts the XML Schema's interpretation of boolean: */ //strcmp (aValue, "no") == 0 || //strcmp (aValue, "off") == 0) return false; throw ENoConversion (FmtStr ("'%s' is not bool", aValue)); } template<> RTTIMESPEC FromString (const char *aValue) { if (aValue == NULL) throw ENoValue(); /* Parse ISO date (xsd:dateTime). The format is: * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)? * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */ uint32_t yyyy = 0; uint16_t mm = 0, dd = 0, hh = 0, mi = 0, ss = 0; char buf [256]; if (strlen (aValue) > ELEMENTS (buf) - 1 || sscanf (aValue, "%d-%hu-%huT%hu:%hu:%hu%s", &yyyy, &mm, &dd, &hh, &mi, &ss, buf) == 7) { /* currently, we accept only the UTC timezone ('Z'), * ignoring fractional seconds, if present */ if (buf [0] == 'Z' || (buf [0] == '.' && buf [strlen (buf) - 1] == 'Z')) { RTTIME time = { yyyy, (uint8_t) mm, 0, 0, (uint8_t) dd, (uint8_t) hh, (uint8_t) mi, (uint8_t) ss, 0, RTTIME_FLAGS_TYPE_UTC }; if (RTTimeNormalize (&time)) { RTTIMESPEC timeSpec; if (RTTimeImplode (&timeSpec, &time)) return timeSpec; } } else throw ENoConversion (FmtStr ("'%s' is not UTC date", aValue)); } throw ENoConversion (FmtStr ("'%s' is not ISO date", aValue)); } stdx::char_auto_ptr FromString (const char *aValue, size_t *aLen) { if (aValue == NULL) throw ENoValue(); /* each two chars produce one byte */ size_t len = strlen (aValue) / 2; /* therefore, the original length must be even */ if (len % 2 != 0) throw ENoConversion (FmtStr ("'%.*s' is not binary data", aLen, aValue)); stdx::char_auto_ptr result (new char [len]); const char *src = aValue; char *dst = result.get(); for (size_t i = 0; i < len; ++ i, ++ dst) { *dst = sFromHex (*src ++) << 4; *dst |= sFromHex (*src ++); } if (aLen != NULL) *aLen = len; return result; } ////////////////////////////////////////////////////////////////////////////// // type -> string conversions ////////////////////////////////////////////////////////////////////////////// stdx::char_auto_ptr ToStringInteger (uint64_t aValue, unsigned int aBase, bool aSigned, int aBits) { unsigned int flags = RTSTR_F_SPECIAL; if (aSigned) flags |= RTSTR_F_VALSIGNED; /* maximum is binary representation + terminator */ size_t len = aBits + 1; switch (aBits) { case 8: flags |= RTSTR_F_8BIT; break; case 16: flags |= RTSTR_F_16BIT; break; case 32: flags |= RTSTR_F_32BIT; break; case 64: flags |= RTSTR_F_64BIT; break; default: throw ENotImplemented (RT_SRC_POS); } stdx::char_auto_ptr result (new char [len]); int vrc = RTStrFormatNumber (result.get(), aValue, aBase, 0, 0, flags); if (RT_SUCCESS (vrc)) return result; throw EIPRTFailure (vrc); } template<> stdx::char_auto_ptr ToString (const bool &aValue, unsigned int aExtra /* = 0 */) { /* Convert to the canonical form according to XML Schema */ stdx::char_auto_ptr result (duplicate_chars (aValue ? "true" : "false")); return result; } template<> stdx::char_auto_ptr ToString (const RTTIMESPEC &aValue, unsigned int aExtra /* = 0 */) { RTTIME time; if (!RTTimeExplode (&time, &aValue)) throw ENoConversion (FmtStr ("timespec %lld ms is invalid", RTTimeSpecGetMilli (&aValue))); /* Store ISO date (xsd:dateTime). The format is: * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)? * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */ char buf [256]; RTStrPrintf (buf, sizeof (buf), "%04ld-%02hd-%02hdT%02hd:%02hd:%02hdZ", time.i32Year, (uint16_t) time.u8Month, (uint16_t) time.u8MonthDay, (uint16_t) time.u8Hour, (uint16_t) time.u8Minute, (uint16_t) time.u8Second); stdx::char_auto_ptr result (duplicate_chars (buf)); return result; } stdx::char_auto_ptr ToString (const char *aData, size_t aLen) { /* each byte will produce two hex digits and there will be a null * terminator */ stdx::char_auto_ptr result (new char [aLen * 2 + 1]); const char *src = aData; char *dst = result.get(); for (size_t i = 0; i < aLen; ++ i, ++ src) { *dst++ = sToHex ((*src) >> 4); *dst++ = sToHex ((*src) & 0xF); } *dst = '\0'; return result; } ////////////////////////////////////////////////////////////////////////////// // File Class ////////////////////////////////////////////////////////////////////////////// struct File::Data { Data() : fileName (NULL), handle (NIL_RTFILE), opened (false) {} Mode mode; char *fileName; RTFILE handle; bool opened : 1; }; File::File (Mode aMode, const char *aFileName) : m (new Data()) { m->mode = aMode; m->fileName = RTStrDup (aFileName); if (m->fileName == NULL) throw ENoMemory(); unsigned flags = 0; switch (aMode) { case Read: flags = RTFILE_O_READ; break; case Write: flags = RTFILE_O_WRITE | RTFILE_O_CREATE; break; case ReadWrite: flags = RTFILE_O_READ | RTFILE_O_WRITE; } int vrc = RTFileOpen (&m->handle, aFileName, flags); if (RT_FAILURE (vrc)) throw EIPRTFailure (vrc); m->opened = true; } File::File (Mode aMode, RTFILE aHandle, const char *aFileName /* = NULL */ ) : m (new Data()) { if (aHandle == NIL_RTFILE) throw EInvalidArg (RT_SRC_POS); m->mode = aMode; m->handle = aHandle; if (aFileName) { m->fileName = RTStrDup (aFileName); if (m->fileName == NULL) throw ENoMemory(); } setPos (0); } File::~File() { if (m->opened) RTFileClose (m->handle); RTStrFree (m->fileName); } const char *File::uri() const { return m->fileName; } uint64_t File::pos() const { uint64_t p = 0; int vrc = RTFileSeek (m->handle, 0, RTFILE_SEEK_CURRENT, &p); if (RT_SUCCESS (vrc)) return p; throw EIPRTFailure (vrc); } void File::setPos (uint64_t aPos) { uint64_t p = 0; unsigned method = RTFILE_SEEK_BEGIN; int vrc = VINF_SUCCESS; /* check if we overflow int64_t and move to INT64_MAX first */ if (((int64_t) aPos) < 0) { vrc = RTFileSeek (m->handle, INT64_MAX, method, &p); aPos -= (uint64_t) INT64_MAX; method = RTFILE_SEEK_CURRENT; } /* seek the rest */ if (RT_SUCCESS (vrc)) vrc = RTFileSeek (m->handle, (int64_t) aPos, method, &p); if (RT_SUCCESS (vrc)) return; throw EIPRTFailure (vrc); } int File::read (char *aBuf, int aLen) { size_t len = aLen; int vrc = RTFileRead (m->handle, aBuf, len, &len); if (RT_SUCCESS (vrc)) return len; throw EIPRTFailure (vrc); } int File::write (const char *aBuf, int aLen) { size_t len = aLen; int vrc = RTFileWrite (m->handle, aBuf, len, &len); if (RT_SUCCESS (vrc)) return len; throw EIPRTFailure (vrc); return -1 /* failure */; } void File::truncate() { int vrc = RTFileSetSize (m->handle, pos()); if (RT_SUCCESS (vrc)) return; throw EIPRTFailure (vrc); } ////////////////////////////////////////////////////////////////////////////// // MemoryBuf Class ////////////////////////////////////////////////////////////////////////////// struct MemoryBuf::Data { Data() : buf (NULL), len (0), uri (NULL), pos (0) {} const char *buf; size_t len; char *uri; size_t pos; }; MemoryBuf::MemoryBuf (const char *aBuf, size_t aLen, const char *aURI /* = NULL */) : m (new Data()) { if (aBuf == NULL) throw EInvalidArg (RT_SRC_POS); m->buf = aBuf; m->len = aLen; m->uri = RTStrDup (aURI); } MemoryBuf::~MemoryBuf() { RTStrFree (m->uri); } const char *MemoryBuf::uri() const { return m->uri; } uint64_t MemoryBuf::pos() const { return m->pos; } void MemoryBuf::setPos (uint64_t aPos) { size_t pos = (size_t) aPos; if ((uint64_t) pos != aPos) throw EInvalidArg(); if (pos > m->len) throw EInvalidArg(); m->pos = pos; } int MemoryBuf::read (char *aBuf, int aLen) { if (m->pos >= m->len) return 0 /* nothing to read */; size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos; memcpy (aBuf, m->buf + m->pos, len); m->pos += len; return len; } ////////////////////////////////////////////////////////////////////////////// // XmlKeyBackend Class ////////////////////////////////////////////////////////////////////////////// class XmlKeyBackend : public Key::Backend { public: XmlKeyBackend (xmlNodePtr aNode); ~XmlKeyBackend(); const char *name() const; void setName (const char *aName); const char *value (const char *aName) const; void setValue (const char *aName, const char *aValue); Key::List keys (const char *aName = NULL) const; Key findKey (const char *aName) const; Key appendKey (const char *aName); void zap(); void *position() const { return mNode; } private: xmlNodePtr mNode; xmlChar *mNodeText; DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (XmlKeyBackend); friend class XmlTreeBackend; }; XmlKeyBackend::XmlKeyBackend (xmlNodePtr aNode) : mNode (aNode), mNodeText (NULL) { AssertReturnVoid (mNode); AssertReturnVoid (mNode->type == XML_ELEMENT_NODE); } XmlKeyBackend::~XmlKeyBackend() { xmlFree (mNodeText); } const char *XmlKeyBackend::name() const { return mNode ? (char *) mNode->name : NULL; } void XmlKeyBackend::setName (const char *aName) { throw ENotImplemented (RT_SRC_POS); } const char *XmlKeyBackend::value (const char *aName) const { if (!mNode) return NULL; if (aName == NULL) { /* @todo xmlNodeListGetString (,,1) returns NULL for things like * and may want to return "" in this case to distinguish * from (where NULL is pretty much expected). */ if (!mNodeText) unconst (mNodeText) = xmlNodeListGetString (mNode->doc, mNode->children, 0); return (char *) mNodeText; } xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName); if (!attr) return NULL; if (attr->type == XML_ATTRIBUTE_NODE) { /* @todo for now, we only understand the most common case: only 1 text * node comprises the attribute's contents. Otherwise we'd need to * return a newly allocated string buffer to the caller that * concatenates all text nodes and obey him to free it or provide our * own internal map of attribute=value pairs and return const pointers * to values from this map. */ if (attr->children != NULL && attr->children->next == NULL && (attr->children->type == XML_TEXT_NODE || attr->children->type == XML_CDATA_SECTION_NODE)) return (char *) attr->children->content; } else if (attr->type == XML_ATTRIBUTE_DECL) { return (char *) ((xmlAttributePtr) attr)->defaultValue; } return NULL; } void XmlKeyBackend::setValue (const char *aName, const char *aValue) { if (!mNode) return; if (aName == NULL) { xmlChar *value = (xmlChar *) aValue; if (value != NULL) { value = xmlEncodeSpecialChars (mNode->doc, value); if (value == NULL) throw ENoMemory(); } xmlNodeSetContent (mNode, value); if (value != (xmlChar *) aValue) xmlFree (value); /* outdate the node text holder */ if (mNodeText != NULL) { xmlFree (mNodeText); mNodeText = NULL; } return; } if (aValue == NULL) { /* remove the attribute if it exists */ xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName); if (attr != NULL) { int rc = xmlRemoveProp (attr); if (rc != 0) throw EInvalidArg (RT_SRC_POS); } return; } xmlAttrPtr attr = xmlSetProp (mNode, (const xmlChar *) aName, (const xmlChar *) aValue); if (attr == NULL) throw ENoMemory(); } Key::List XmlKeyBackend::keys (const char *aName /* = NULL */) const { Key::List list; if (!mNode) return list; for (xmlNodePtr node = mNode->children; node; node = node->next) { if (node->type == XML_ELEMENT_NODE) { if (aName == NULL || strcmp (aName, (char *) node->name) == 0) list.push_back (Key (new XmlKeyBackend (node))); } } return list; } Key XmlKeyBackend::findKey (const char *aName) const { Key key; if (!mNode) return key; for (xmlNodePtr node = mNode->children; node; node = node->next) { if (node->type == XML_ELEMENT_NODE) { if (aName == NULL || strcmp (aName, (char *) node->name) == 0) { key = Key (new XmlKeyBackend (node)); break; } } } return key; } Key XmlKeyBackend::appendKey (const char *aName) { if (!mNode) return Key(); xmlNodePtr node = xmlNewChild (mNode, NULL, (const xmlChar *) aName, NULL); if (node == NULL) throw ENoMemory(); return Key (new XmlKeyBackend (node)); } void XmlKeyBackend::zap() { if (!mNode) return; xmlUnlinkNode (mNode); xmlFreeNode (mNode); mNode = NULL; } ////////////////////////////////////////////////////////////////////////////// // XmlTreeBackend Class ////////////////////////////////////////////////////////////////////////////// class XmlTreeBackend::XmlError : public XmlTreeBackend::Error { public: XmlError (xmlErrorPtr aErr) { if (!aErr) throw EInvalidArg (RT_SRC_POS); char *msg = Format (aErr); setWhat (msg); RTStrFree (msg); } /** * Composes a single message for the given error. The caller must free the * returned string using RTStrFree() when no more necessary. */ static char *Format (xmlErrorPtr aErr) { const char *msg = aErr->message ? aErr->message : ""; size_t msgLen = strlen (msg); /* strip spaces, trailing EOLs and dot-like char */ while (msgLen && strchr (" \n.?!", msg [msgLen - 1])) -- msgLen; char *finalMsg = NULL; RTStrAPrintf (&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d", msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2); return finalMsg; } }; struct XmlTreeBackend::Data { Data() : ctxt (NULL), doc (NULL) , inputResolver (NULL) , autoConverter (NULL), oldVersion (NULL) {} xmlParserCtxtPtr ctxt; xmlDocPtr doc; Key root; InputResolver *inputResolver; AutoConverter *autoConverter; char *oldVersion; std::auto_ptr trappedErr; /** * This is to avoid throwing exceptions while in libxml2 code and * redirect them to our level instead. Also used to perform clean up * by deleting the I/O stream instance and self when requested. */ struct IOCtxt { IOCtxt (Stream *aStream, std::auto_ptr &aErr) : stream (aStream), deleteStreamOnClose (false) , err (aErr) {} template void setErr (const T& aErr) { err.reset (new stdx::exception_trap (aErr)); } void resetErr() { err.reset(); } Stream *stream; bool deleteStreamOnClose; std::auto_ptr &err; }; struct InputCtxt : public IOCtxt { InputCtxt (Input *aInput, std::auto_ptr &aErr) : IOCtxt (aInput, aErr), input (aInput) {} Input *input; }; struct OutputCtxt : public IOCtxt { OutputCtxt (Output *aOutput, std::auto_ptr &aErr) : IOCtxt (aOutput, aErr), output (aOutput) {} Output *output; }; }; XmlTreeBackend::XmlTreeBackend() : m (new Data()) { /* create a parser context */ m->ctxt = xmlNewParserCtxt(); if (m->ctxt == NULL) throw ENoMemory(); } XmlTreeBackend::~XmlTreeBackend() { reset(); xmlFreeParserCtxt (m->ctxt); m->ctxt = NULL; } void XmlTreeBackend::setInputResolver (InputResolver &aResolver) { m->inputResolver = &aResolver; } void XmlTreeBackend::resetInputResolver() { m->inputResolver = NULL; } void XmlTreeBackend::setAutoConverter (AutoConverter &aConverter) { m->autoConverter = &aConverter; } void XmlTreeBackend::resetAutoConverter() { m->autoConverter = NULL; } const char *XmlTreeBackend::oldVersion() const { return m->oldVersion; } extern "C" xmlGenericErrorFunc xsltGenericError; extern "C" void *xsltGenericErrorContext; void XmlTreeBackend::rawRead (Input &aInput, const char *aSchema /* = NULL */, int aFlags /* = 0 */) { /* Reset error variables used to memorize exceptions while inside the * libxml2 code. */ m->trappedErr.reset(); /* Set up the external entity resolver. Note that we do it in a * thread-unsafe fashion because this stuff is not thread-safe in libxml2. * Making it thread-safe would require a) guarding this method with a * mutex and b) requiring our API caller not to use libxml2 on some other * thread (which is not practically possible). So, our API is not * thread-safe for now (note that there are more thread-unsafe assumptions * below like xsltGenericError which is also a libxslt limitation).*/ xmlExternalEntityLoader oldEntityLoader = xmlGetExternalEntityLoader(); sThat = this; xmlSetExternalEntityLoader (ExternalEntityLoader); xmlDocPtr doc = NULL; try { /* Note: when parsing we use XML_PARSE_NOBLANKS to instruct libxml2 to * remove text nodes that contain only blanks. This is important because * otherwise xmlSaveDoc() won't be able to do proper indentation on * output. */ /* parse the stream */ /* NOTE: new InputCtxt instance will be deleted when the stream is closed by * the libxml2 API (e.g. when calling xmlFreeParserCtxt()) */ doc = xmlCtxtReadIO (m->ctxt, ReadCallback, CloseCallback, new Data::InputCtxt (&aInput, m->trappedErr), aInput.uri(), NULL, XML_PARSE_NOBLANKS); if (doc == NULL) { /* look if there was a forwared exception from the lower level */ if (m->trappedErr.get() != NULL) m->trappedErr->rethrow(); throw XmlError (xmlCtxtGetLastError (m->ctxt)); } char *oldVersion = NULL; /* perform automatic document transformation if necessary */ if (m->autoConverter != NULL && m->autoConverter-> needsConversion (Key (new XmlKeyBackend (xmlDocGetRootElement (doc))), &oldVersion)) { xmlDocPtr xsltDoc = NULL; xsltStylesheetPtr xslt = NULL; char *errorStr = NULL; xmlGenericErrorFunc oldXsltGenericError = xsltGenericError; void *oldXsltGenericErrorContext = xsltGenericErrorContext; try { /* parse the XSLT template */ { Input *xsltInput = m->inputResolver->resolveEntity (m->autoConverter->templateUri(), NULL); /* NOTE: new InputCtxt instance will be deleted when the * stream is closed by the libxml2 API */ xsltDoc = xmlCtxtReadIO (m->ctxt, ReadCallback, CloseCallback, new Data::InputCtxt (xsltInput, m->trappedErr), m->autoConverter->templateUri(), NULL, 0); delete xsltInput; } if (xsltDoc == NULL) { /* look if there was a forwared exception from the lower level */ if (m->trappedErr.get() != NULL) m->trappedErr->rethrow(); throw XmlError (xmlCtxtGetLastError (m->ctxt)); } /* setup stylesheet compilation and transformation error * reporting. Note that we could create a new transform context * for doing xsltApplyStylesheetUser and use * xsltSetTransformErrorFunc() on it to set a dedicated error * handler but as long as we already do several non-thread-safe * hacks, this is not really important. */ xsltGenericError = ValidityErrorCallback; xsltGenericErrorContext = &errorStr; xslt = xsltParseStylesheetDoc (xsltDoc); if (xslt == NULL) { if (errorStr != NULL) throw LogicError (errorStr); /* errorStr is freed in catch(...) below */ throw LogicError (RT_SRC_POS); } /* repeat transformations until autoConverter is satisfied */ do { xmlDocPtr newDoc = xsltApplyStylesheet (xslt, doc, NULL); if (newDoc == NULL) throw LogicError (RT_SRC_POS); if (errorStr != NULL) { xmlFreeDoc (newDoc); throw Error (errorStr); /* errorStr is freed in catch(...) below */ } /* replace the old document on success */ xmlFreeDoc (doc); doc = newDoc; } while (m->autoConverter-> needsConversion (Key (new XmlKeyBackend (xmlDocGetRootElement (doc))), NULL)); RTStrFree (errorStr); /* NOTE: xsltFreeStylesheet() also fress the document * passed to xsltParseStylesheetDoc(). */ xsltFreeStylesheet (xslt); /* restore the previous generic error func */ xsltGenericError = oldXsltGenericError; xsltGenericErrorContext = oldXsltGenericErrorContext; } catch (...) { RTStrFree (errorStr); /* NOTE: xsltFreeStylesheet() also fress the document * passed to xsltParseStylesheetDoc(). */ if (xslt != NULL) xsltFreeStylesheet (xslt); else if (xsltDoc != NULL) xmlFreeDoc (xsltDoc); /* restore the previous generic error func */ xsltGenericError = oldXsltGenericError; xsltGenericErrorContext = oldXsltGenericErrorContext; RTStrFree (oldVersion); throw; } } /* validate the document */ if (aSchema != NULL) { xmlSchemaParserCtxtPtr schemaCtxt = NULL; xmlSchemaPtr schema = NULL; xmlSchemaValidCtxtPtr validCtxt = NULL; char *errorStr = NULL; try { bool valid = false; schemaCtxt = xmlSchemaNewParserCtxt (aSchema); if (schemaCtxt == NULL) throw LogicError (RT_SRC_POS); /* set our error handlers */ xmlSchemaSetParserErrors (schemaCtxt, ValidityErrorCallback, ValidityWarningCallback, &errorStr); xmlSchemaSetParserStructuredErrors (schemaCtxt, StructuredErrorCallback, &errorStr); /* load schema */ schema = xmlSchemaParse (schemaCtxt); if (schema != NULL) { validCtxt = xmlSchemaNewValidCtxt (schema); if (validCtxt == NULL) throw LogicError (RT_SRC_POS); /* instruct to create default attribute's values in the document */ if (aFlags & Read_AddDefaults) xmlSchemaSetValidOptions (validCtxt, XML_SCHEMA_VAL_VC_I_CREATE); /* set our error handlers */ xmlSchemaSetValidErrors (validCtxt, ValidityErrorCallback, ValidityWarningCallback, &errorStr); /* finally, validate */ valid = xmlSchemaValidateDoc (validCtxt, doc) == 0; } if (!valid) { /* look if there was a forwared exception from the lower level */ if (m->trappedErr.get() != NULL) m->trappedErr->rethrow(); if (errorStr == NULL) throw LogicError (RT_SRC_POS); throw Error (errorStr); /* errorStr is freed in catch(...) below */ } RTStrFree (errorStr); xmlSchemaFreeValidCtxt (validCtxt); xmlSchemaFree (schema); xmlSchemaFreeParserCtxt (schemaCtxt); } catch (...) { RTStrFree (errorStr); if (validCtxt) xmlSchemaFreeValidCtxt (validCtxt); if (schema) xmlSchemaFree (schema); if (schemaCtxt) xmlSchemaFreeParserCtxt (schemaCtxt); RTStrFree (oldVersion); throw; } } /* reset the previous tree on success */ reset(); m->doc = doc; /* assign the root key */ m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc))); /* memorize the old version string also used as a flag that * the conversion has been performed (transfers ownership) */ m->oldVersion = oldVersion; /* restore the previous entity resolver */ xmlSetExternalEntityLoader (oldEntityLoader); sThat = NULL; } catch (...) { if (doc != NULL) xmlFreeDoc (doc); /* restore the previous entity resolver */ xmlSetExternalEntityLoader (oldEntityLoader); sThat = NULL; throw; } } void XmlTreeBackend::rawWrite (Output &aOutput) { /* reset error variables used to memorize exceptions while inside the * libxml2 code */ m->trappedErr.reset(); /* set up an input stream for parsing the document. This will be deleted * when the stream is closed by the libxml2 API (e.g. when calling * xmlFreeParserCtxt()). */ Data::OutputCtxt *outputCtxt = new Data::OutputCtxt (&aOutput, m->trappedErr); /* serialize to the stream */ xmlIndentTreeOutput = 1; xmlTreeIndentString = " "; xmlSaveNoEmptyTags = 0; xmlSaveCtxtPtr saveCtxt = xmlSaveToIO (WriteCallback, CloseCallback, outputCtxt, NULL, XML_SAVE_FORMAT); if (saveCtxt == NULL) throw LogicError (RT_SRC_POS); long rc = xmlSaveDoc (saveCtxt, m->doc); if (rc == -1) { /* look if there was a forwared exception from the lower level */ if (m->trappedErr.get() != NULL) m->trappedErr->rethrow(); /* there must be an exception from the Output implementation, * otherwise the save operation must always succeed. */ throw LogicError (RT_SRC_POS); } xmlSaveClose (saveCtxt); } void XmlTreeBackend::reset() { RTStrFree (m->oldVersion); m->oldVersion = NULL; if (m->doc) { /* reset the root key's node */ GetKeyBackend (m->root)->mNode = NULL; /* free the document*/ xmlFreeDoc (m->doc); m->doc = NULL; } } Key &XmlTreeBackend::rootKey() const { return m->root; } /* static */ int XmlTreeBackend::ReadCallback (void *aCtxt, char *aBuf, int aLen) { AssertReturn (aCtxt != NULL, 0); Data::InputCtxt *ctxt = static_cast (aCtxt); /* To prevent throwing exceptions while inside libxml2 code, we catch * them and forward to our level using a couple of variables. */ try { return ctxt->input->read (aBuf, aLen); } catch (const EIPRTFailure &err) { ctxt->setErr (err); } catch (const settings::Error &err) { ctxt->setErr (err); } catch (const std::exception &err) { ctxt->setErr (err); } catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); } return -1 /* failure */; } /* static */ int XmlTreeBackend::WriteCallback (void *aCtxt, const char *aBuf, int aLen) { AssertReturn (aCtxt != NULL, 0); Data::OutputCtxt *ctxt = static_cast (aCtxt); /* To prevent throwing exceptions while inside libxml2 code, we catch * them and forward to our level using a couple of variables. */ try { return ctxt->output->write (aBuf, aLen); } catch (const EIPRTFailure &err) { ctxt->setErr (err); } catch (const settings::Error &err) { ctxt->setErr (err); } catch (const std::exception &err) { ctxt->setErr (err); } catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); } return -1 /* failure */; } /* static */ int XmlTreeBackend::CloseCallback (void *aCtxt) { AssertReturn (aCtxt != NULL, 0); Data::IOCtxt *ctxt = static_cast (aCtxt); /* To prevent throwing exceptions while inside libxml2 code, we catch * them and forward to our level using a couple of variables. */ try { /// @todo there is no explicit close semantics in Stream yet #if 0 ctxt->stream->close(); #endif /* perform cleanup when necessary */ if (ctxt->deleteStreamOnClose) delete ctxt->stream; delete ctxt; return 0 /* success */; } catch (const EIPRTFailure &err) { ctxt->setErr (err); } catch (const settings::Error &err) { ctxt->setErr (err); } catch (const std::exception &err) { ctxt->setErr (err); } catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); } return -1 /* failure */; } /* static */ void XmlTreeBackend::ValidityErrorCallback (void *aCtxt, const char *aMsg, ...) { AssertReturnVoid (aCtxt != NULL); AssertReturnVoid (aMsg != NULL); char * &str = *(char * *) aCtxt; char *newMsg = NULL; { va_list args; va_start (args, aMsg); RTStrAPrintfV (&newMsg, aMsg, args); va_end (args); } AssertReturnVoid (newMsg != NULL); /* strip spaces, trailing EOLs and dot-like char */ size_t newMsgLen = strlen (newMsg); while (newMsgLen && strchr (" \n.?!", newMsg [newMsgLen - 1])) -- newMsgLen; /* anything left? */ if (newMsgLen > 0) { if (str == NULL) { str = newMsg; newMsg [newMsgLen] = '\0'; } else { /* append to the existing string */ size_t strLen = strlen (str); char *newStr = (char *) RTMemRealloc (str, strLen + 2 + newMsgLen + 1); AssertReturnVoid (newStr != NULL); memcpy (newStr + strLen, ".\n", 2); memcpy (newStr + strLen + 2, newMsg, newMsgLen); newStr [strLen + 2 + newMsgLen] = '\0'; str = newStr; RTStrFree (newMsg); } } } /* static */ void XmlTreeBackend::ValidityWarningCallback (void *aCtxt, const char *aMsg, ...) { NOREF (aCtxt); NOREF (aMsg); } /* static */ void XmlTreeBackend::StructuredErrorCallback (void *aCtxt, xmlErrorPtr aErr) { AssertReturnVoid (aCtxt != NULL); AssertReturnVoid (aErr != NULL); char * &str = *(char * *) aCtxt; char *newMsg = XmlError::Format (aErr); AssertReturnVoid (newMsg != NULL); if (str == NULL) str = newMsg; else { /* append to the existing string */ size_t newMsgLen = strlen (newMsg); size_t strLen = strlen (str); char *newStr = (char *) RTMemRealloc (str, strLen + newMsgLen + 2); AssertReturnVoid (newStr != NULL); memcpy (newStr + strLen, ".\n", 2); memcpy (newStr + strLen + 2, newMsg, newMsgLen); str = newStr; RTStrFree (newMsg); } } /* static */ XmlTreeBackend *XmlTreeBackend::sThat = NULL; /* static */ xmlParserInputPtr XmlTreeBackend::ExternalEntityLoader (const char *aURI, const char *aID, xmlParserCtxtPtr aCtxt) { AssertReturn (sThat != NULL, NULL); if (sThat->m->inputResolver == NULL) return gGlobal.xml.defaultEntityLoader (aURI, aID, aCtxt); /* To prevent throwing exceptions while inside libxml2 code, we catch * them and forward to our level using a couple of variables. */ try { Input *input = sThat->m->inputResolver->resolveEntity (aURI, aID); if (input == NULL) return NULL; Data::InputCtxt *ctxt = new Data::InputCtxt (input, sThat->m->trappedErr); ctxt->deleteStreamOnClose = true; /* create an input buffer with custom hooks */ xmlParserInputBufferPtr bufPtr = xmlParserInputBufferCreateIO (ReadCallback, CloseCallback, ctxt, XML_CHAR_ENCODING_NONE); if (bufPtr) { /* create an input stream */ xmlParserInputPtr inputPtr = xmlNewIOInputStream (aCtxt, bufPtr, XML_CHAR_ENCODING_NONE); if (inputPtr != NULL) { /* pass over the URI to the stream struct (it's NULL by * default) */ inputPtr->filename = (char *) xmlCanonicPath ((const xmlChar *) input->uri()); return inputPtr; } } /* either of libxml calls failed */ if (bufPtr) xmlFreeParserInputBuffer (bufPtr); delete input; delete ctxt; throw ENoMemory(); } catch (const EIPRTFailure &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); } catch (const settings::Error &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); } catch (const std::exception &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); } catch (...) { sThat->m->trappedErr.reset (stdx::new_exception_trap (LogicError (RT_SRC_POS))); } return NULL; } } /* namespace settings */