1/*
2 * Copyright (c) 2011-2015, Intel Corporation
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation and/or
13 * other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its contributors
16 * may be used to endorse or promote products derived from this software without
17 * specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
23 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "XmlDocSource.h"
32#include "AlwaysAssert.hpp"
33#include <libxml/tree.h>
34#include <libxml/xmlschemas.h>
35#include <libxml/parser.h>
36#include <libxml/xinclude.h>
37#include <libxml/uri.h>
38#include <memory>
39#include <stdexcept>
40
41using std::string;
42using xml_unique_ptr = std::unique_ptr<xmlChar, decltype(xmlFree)>;
43
44CXmlDocSource::CXmlDocSource(_xmlDoc *pDoc, bool bValidateWithSchema, _xmlNode *pRootNode)
45    : _pDoc(pDoc), _pRootNode(pRootNode), _strRootElementType(""), _strRootElementName(""),
46      _strNameAttributeName(""), _bValidateWithSchema(bValidateWithSchema)
47{
48}
49
50CXmlDocSource::CXmlDocSource(_xmlDoc *pDoc, bool bValidateWithSchema,
51                             const string &strRootElementType, const string &strRootElementName,
52                             const string &strNameAttributeName)
53    : _pDoc(pDoc), _pRootNode(xmlDocGetRootElement(pDoc)), _strRootElementType(strRootElementType),
54      _strRootElementName(strRootElementName), _strNameAttributeName(strNameAttributeName),
55      _bValidateWithSchema(bValidateWithSchema)
56{
57}
58
59CXmlDocSource::~CXmlDocSource()
60{
61    if (_pDoc) {
62        // Free XML doc
63        xmlFreeDoc(_pDoc);
64        _pDoc = NULL;
65    }
66}
67
68void CXmlDocSource::getRootElement(CXmlElement &xmlRootElement) const
69{
70    xmlRootElement.setXmlElement(_pRootNode);
71}
72
73string CXmlDocSource::getRootElementName() const
74{
75    return (const char *)_pRootNode->name;
76}
77
78string CXmlDocSource::getRootElementAttributeString(const string &strAttributeName) const
79{
80    CXmlElement topMostElement(_pRootNode);
81
82    string attribute;
83    topMostElement.getAttribute(strAttributeName, attribute);
84    return attribute;
85}
86
87void CXmlDocSource::setSchemaBaseUri(const string &uri)
88{
89    _schemaBaseUri = uri;
90}
91
92string CXmlDocSource::getSchemaBaseUri()
93{
94    return _schemaBaseUri;
95}
96
97string CXmlDocSource::getSchemaUri() const
98{
99    // Adding a trailing '/' is a bit dirty but works fine on both Linux and
100    // Windows in order to make sure that libxml2's URI handling methods
101    // interpret the base URI as a folder.
102    return mkUri(_schemaBaseUri + "/", getRootElementName() + ".xsd");
103}
104
105_xmlDoc *CXmlDocSource::getDoc() const
106{
107    return _pDoc;
108}
109
110bool CXmlDocSource::isParsable() const
111{
112    // Check that the doc has been created
113    return _pDoc != NULL;
114}
115
116bool CXmlDocSource::populate(CXmlSerializingContext &serializingContext)
117{
118    // Check that the doc has been created
119    if (!_pDoc) {
120
121        serializingContext.setError("Could not parse document ");
122
123        return false;
124    }
125
126    // Validate if necessary
127    if (_bValidateWithSchema) {
128        if (!isInstanceDocumentValid()) {
129
130            serializingContext.setError("Document is not valid");
131
132            return false;
133        }
134    }
135
136    // Check Root element type
137    if (getRootElementName() != _strRootElementType) {
138
139        serializingContext.setError("Error: Wrong XML structure document ");
140        serializingContext.appendLineToError("Root Element " + getRootElementName() +
141                                             " mismatches expected type " + _strRootElementType);
142
143        return false;
144    }
145
146    if (!_strNameAttributeName.empty()) {
147
148        string strRootElementNameCheck = getRootElementAttributeString(_strNameAttributeName);
149
150        // Check Root element name attribute (if any)
151        if (!_strRootElementName.empty() && strRootElementNameCheck != _strRootElementName) {
152
153            serializingContext.setError("Error: Wrong XML structure document ");
154            serializingContext.appendLineToError(
155                _strRootElementType + " element " + _strRootElementName + " mismatches expected " +
156                _strRootElementType + " type " + strRootElementNameCheck);
157
158            return false;
159        }
160    }
161
162    return true;
163}
164
165bool CXmlDocSource::isInstanceDocumentValid()
166{
167#ifdef LIBXML_SCHEMAS_ENABLED
168    string schemaUri = getSchemaUri();
169
170    xmlDocPtr pSchemaDoc = xmlReadFile(schemaUri.c_str(), NULL, XML_PARSE_NONET);
171
172    if (!pSchemaDoc) {
173        // Unable to load Schema
174        return false;
175    }
176
177    xmlSchemaParserCtxtPtr pParserCtxt = xmlSchemaNewDocParserCtxt(pSchemaDoc);
178
179    if (!pParserCtxt) {
180
181        // Unable to create schema context
182        xmlFreeDoc(pSchemaDoc);
183        return false;
184    }
185
186    // Get Schema
187    xmlSchemaPtr pSchema = xmlSchemaParse(pParserCtxt);
188
189    if (!pSchema) {
190
191        // Invalid Schema
192        xmlSchemaFreeParserCtxt(pParserCtxt);
193        xmlFreeDoc(pSchemaDoc);
194        return false;
195    }
196    xmlSchemaValidCtxtPtr pValidationCtxt = xmlSchemaNewValidCtxt(pSchema);
197
198    if (!pValidationCtxt) {
199
200        // Unable to create validation context
201        xmlSchemaFree(pSchema);
202        xmlSchemaFreeParserCtxt(pParserCtxt);
203        xmlFreeDoc(pSchemaDoc);
204        return false;
205    }
206
207    bool isDocValid = xmlSchemaValidateDoc(pValidationCtxt, _pDoc) == 0;
208
209    xmlSchemaFreeValidCtxt(pValidationCtxt);
210    xmlSchemaFree(pSchema);
211    xmlSchemaFreeParserCtxt(pParserCtxt);
212    xmlFreeDoc(pSchemaDoc);
213
214    return isDocValid;
215#else
216    return true;
217#endif
218}
219
220std::string CXmlDocSource::mkUri(const std::string &base, const std::string &relative)
221{
222    xml_unique_ptr baseUri(xmlPathToURI((const xmlChar *)base.c_str()), xmlFree);
223    xml_unique_ptr relativeUri(xmlPathToURI((const xmlChar *)relative.c_str()), xmlFree);
224    /* return null pointer if baseUri or relativeUri are null pointer  */
225    xml_unique_ptr xmlUri(xmlBuildURI(relativeUri.get(), baseUri.get()), xmlFree);
226
227    ALWAYS_ASSERT(xmlUri != nullptr, "unable to make URI from: \"" << base << "\" and \""
228                                                                   << relative << "\"");
229
230    return (const char *)xmlUri.get();
231}
232
233_xmlDoc *CXmlDocSource::mkXmlDoc(const string &source, bool fromFile, bool xincludes,
234                                 CXmlSerializingContext &serializingContext)
235{
236    _xmlDoc *doc = NULL;
237    if (fromFile) {
238        doc = xmlReadFile(source.c_str(), NULL, 0);
239    } else {
240        doc = xmlReadMemory(source.c_str(), (int)source.size(), "", NULL, 0);
241    }
242
243    if (doc == NULL) {
244        string errorMsg = "libxml failed to read";
245        if (fromFile) {
246            errorMsg += " \"" + source + "\"";
247        }
248        serializingContext.appendLineToError(errorMsg);
249
250        return NULL;
251    }
252
253    if (xincludes and (xmlXIncludeProcess(doc) < 0)) {
254        serializingContext.appendLineToError("libxml failed to resolve XIncludes");
255
256        xmlFreeDoc(doc);
257        doc = NULL;
258    }
259
260    return doc;
261}
262