Merge branch '1.x' of ssh://authdev.it.ohio-state.edu/~scantor/git/cpp-xmltooling...
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectMarshaller.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * AbstractXMLObjectMarshaller.cpp
23  * 
24  * A thread-safe abstract marshaller.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "io/AbstractXMLObjectMarshaller.h"
30 #ifndef XMLTOOLING_NO_XMLSEC
31     #include "security/Credential.h"
32     #include "signature/Signature.h"
33 #endif
34 #include "util/NDC.h"
35 #include "util/XMLConstants.h"
36 #include "util/XMLHelper.h"
37
38 #include <algorithm>
39 #include <functional>
40 #include <xercesc/util/XMLUniDefs.hpp>
41
42 #ifndef XMLTOOLING_NO_XMLSEC
43     using namespace xmlsignature;
44 #endif
45 using namespace xmlconstants;
46 using namespace xmltooling;
47 using namespace xercesc;
48 using namespace std;
49
50 AbstractXMLObjectMarshaller::AbstractXMLObjectMarshaller()
51 {
52 }
53
54 AbstractXMLObjectMarshaller::~AbstractXMLObjectMarshaller()
55 {
56 }
57
58 void AbstractXMLObjectMarshaller::setDocumentElement(DOMDocument* document, DOMElement* element) const
59 {
60     DOMElement* documentRoot = document->getDocumentElement();
61     if (documentRoot)
62         document->replaceChild(element, documentRoot);
63     else
64         document->appendChild(element);
65 }
66
67 DOMElement* AbstractXMLObjectMarshaller::marshall(
68     DOMDocument* document
69 #ifndef XMLTOOLING_NO_XMLSEC
70     ,const vector<Signature*>* sigs
71     ,const Credential* credential
72 #endif
73     ) const
74 {
75 #ifdef _DEBUG
76     xmltooling::NDC ndc("marshall");
77 #endif
78
79     if (m_log.isDebugEnabled()) {
80         m_log.debug("starting to marshal %s", getElementQName().toString().c_str());
81     }
82
83     DOMElement* cachedDOM=getDOM();
84     if (cachedDOM) {
85         if (!document || document==cachedDOM->getOwnerDocument()) {
86             m_log.debug("XMLObject has a usable cached DOM, reusing it");
87             if (document)
88                 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);
89             releaseParentDOM(true);
90             return cachedDOM;
91         }
92         
93         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
94         // Without an adoptNode option to maintain the child pointers, we have to either import the
95         // DOM while somehow reassigning all the nested references (which amounts to a complete
96         // *unmarshall* operation), or we just release the existing DOM and hope that we can get
97         // it back. This depends on all objects being able to preserve their DOM at all costs.
98         releaseChildrenDOM(true);
99         releaseDOM();
100     }
101     
102     // If we get here, we didn't have a usable DOM (and/or we released the one we had).
103     prepareForMarshalling();
104
105     // We may need to create our own document.
106     bool bindDocument=false;
107     if (!document) {
108         document=DOMImplementationRegistry::getDOMImplementation(nullptr)->createDocument();
109         bindDocument=true;
110     }
111     
112     XercesJanitor<DOMDocument> janitor(bindDocument ? document : nullptr);
113
114     m_log.debug("creating root element to marshall");
115     DOMElement* domElement = document->createElementNS(
116         getElementQName().getNamespaceURI(), getElementQName().getLocalPart()
117         );
118     setDocumentElement(document, domElement);
119 #ifndef XMLTOOLING_NO_XMLSEC
120     marshallInto(domElement, sigs, credential);
121 #else
122     marshallInto(domElement);
123 #endif
124     //Recache the DOM.
125     m_log.debug("caching DOM for XMLObject (document is %sbound)", bindDocument ? "" : "not ");
126     setDOM(domElement, bindDocument);
127     janitor.release();  // safely transferred
128     releaseParentDOM(true);
129
130     return domElement;
131 }
132
133 DOMElement* AbstractXMLObjectMarshaller::marshall(
134     DOMElement* parentElement
135 #ifndef XMLTOOLING_NO_XMLSEC
136     ,const vector<Signature*>* sigs
137     ,const Credential* credential
138 #endif
139     ) const
140 {
141 #ifdef _DEBUG
142     xmltooling::NDC ndc("marshall");
143 #endif
144
145     if (m_log.isDebugEnabled()) {
146         m_log.debug("starting to marshalling %s", getElementQName().toString().c_str());
147     }
148
149     DOMElement* cachedDOM=getDOM();
150     if (cachedDOM) {
151         if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {
152             m_log.debug("XMLObject has a usable cached DOM, reusing it");
153             if (parentElement!=cachedDOM->getParentNode()) {
154                 parentElement->appendChild(cachedDOM);
155                 releaseParentDOM(true);
156             }
157             return cachedDOM;
158         }
159         
160         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
161         // Without an adoptNode option to maintain the child pointers, we have to either import the
162         // DOM while somehow reassigning all the nested references (which amounts to a complete
163         // *unmarshall* operation), or we just release the existing DOM and hope that we can get
164         // it back. This depends on all objects being able to preserve their DOM at all costs.
165         releaseChildrenDOM(true);
166         releaseDOM();
167     }
168     
169     // If we get here, we didn't have a usable DOM (and/or we released the one we had).
170     prepareForMarshalling();
171
172     m_log.debug("creating root element to marshall");
173     DOMElement* domElement = parentElement->getOwnerDocument()->createElementNS(
174         getElementQName().getNamespaceURI(), getElementQName().getLocalPart()
175         );
176     parentElement->appendChild(domElement);
177 #ifndef XMLTOOLING_NO_XMLSEC
178     marshallInto(domElement, sigs, credential);
179 #else
180     marshallInto(domElement);
181 #endif
182
183     //Recache the DOM.
184     m_log.debug("caching DOM for XMLObject");
185     setDOM(domElement, false);
186     releaseParentDOM(true);
187
188     return domElement;
189 }
190
191 void AbstractXMLObjectMarshaller::marshallInto(
192     DOMElement* targetElement
193 #ifndef XMLTOOLING_NO_XMLSEC
194     ,const vector<Signature*>* sigs
195     ,const Credential* credential
196 #endif
197     ) const
198 {
199     if (getElementQName().hasPrefix())
200         targetElement->setPrefix(getElementQName().getPrefix());
201
202     if (m_schemaLocation || m_noNamespaceSchemaLocation) {
203         static const XMLCh schemaLocation[] = {
204             chLatin_x, chLatin_s, chLatin_i, chColon,
205             chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a,
206             chLatin_L, chLatin_o, chLatin_c, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull
207             };
208         static const XMLCh noNamespaceSchemaLocation[] = {
209             chLatin_x, chLatin_s, chLatin_i, chColon,
210             chLatin_n, chLatin_o, chLatin_N, chLatin_a, chLatin_m, chLatin_e, chLatin_s, chLatin_p, chLatin_a, chLatin_c, chLatin_e,
211             chLatin_S, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a,
212             chLatin_L, chLatin_o, chLatin_c, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull
213             };
214         if (targetElement->getParentNode()==nullptr || targetElement->getParentNode()->getNodeType()==DOMNode::DOCUMENT_NODE) {
215             if (m_schemaLocation)
216                 targetElement->setAttributeNS(XSI_NS,schemaLocation,m_schemaLocation); 
217             if (m_noNamespaceSchemaLocation)
218                 targetElement->setAttributeNS(XSI_NS,noNamespaceSchemaLocation,m_noNamespaceSchemaLocation); 
219         }
220     }
221     
222     static const XMLCh _nil[] = { chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_n, chLatin_i, chLatin_l, chNull };
223     
224     if (m_nil != xmlconstants::XML_BOOL_NULL) {
225         switch (m_nil) {
226             case xmlconstants::XML_BOOL_TRUE:
227                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_TRUE);
228                 break;
229             case xmlconstants::XML_BOOL_ONE:
230                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_ONE);
231                 break;
232             case xmlconstants::XML_BOOL_FALSE:
233                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_FALSE);
234                 break;
235             case xmlconstants::XML_BOOL_ZERO:
236                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_ZERO);
237                 break;
238         }
239         m_log.debug("adding XSI namespace to list of namespaces visibly used by XMLObject");
240         addNamespace(Namespace(XSI_NS, XSI_PREFIX, false, Namespace::VisiblyUsed));
241     }
242
243     marshallElementType(targetElement);
244     marshallNamespaces(targetElement);
245     marshallAttributes(targetElement);
246     
247 #ifndef XMLTOOLING_NO_XMLSEC
248     marshallContent(targetElement,credential);
249     if (sigs) {
250         for_each(sigs->begin(),sigs->end(),bind2nd(mem_fun1_t<void,Signature,const Credential*>(&Signature::sign),credential));
251     }
252 #else
253     marshallContent(targetElement);
254 #endif
255 }
256
257 void AbstractXMLObjectMarshaller::marshallElementType(DOMElement* domElement) const
258 {
259     const QName* type = getSchemaType();
260     if (type) {
261         m_log.debug("setting xsi:type attribute for XMLObject");
262         
263         const XMLCh* typeLocalName = type->getLocalPart();
264         if (!typeLocalName || !*typeLocalName) {
265             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");
266         }
267
268         static const XMLCh xsitype[] = {
269             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull
270             };
271         
272         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);
273         const XMLCh* prefix=type->getPrefix();
274         if (prefix && *prefix) {
275             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
276             *xsivalue=chNull;
277             XMLString::catString(xsivalue,prefix);
278             static const XMLCh colon[] = {chColon, chNull};
279             XMLString::catString(xsivalue,colon);
280             XMLString::catString(xsivalue,typeLocalName);
281         }   
282         domElement->setAttributeNS(XSI_NS, xsitype, xsivalue);
283         if (xsivalue != typeLocalName)
284             XMLString::release(&xsivalue);
285
286         m_log.debug("adding XSI namespace to list of namespaces visibly used by XMLObject");
287         addNamespace(Namespace(XSI_NS, XSI_PREFIX, false, Namespace::VisiblyUsed));
288     }
289 }
290
291 class _addns : public binary_function<DOMElement*,Namespace,void> {
292 public:
293     void operator()(DOMElement* domElement, const Namespace& ns) const {
294         const XMLCh* prefix=ns.getNamespacePrefix();
295         const XMLCh* uri=ns.getNamespaceURI();
296         
297         // Check for xmlns:xml.
298         if (XMLString::equals(prefix, XML_PREFIX) && XMLString::equals(uri, XML_NS))
299             return;
300
301         // Check to see if the prefix is already declared properly above this node.
302         if (!ns.alwaysDeclare()) {
303             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);
304             if (declared && XMLString::equals(declared,uri))
305                 return;
306         }
307             
308         if (prefix && *prefix) {
309             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
310             *xmlns=chNull;
311             XMLString::catString(xmlns,XMLNS_PREFIX);
312             static const XMLCh colon[] = {chColon, chNull};
313             XMLString::catString(xmlns,colon);
314             XMLString::catString(xmlns,prefix);
315             domElement->setAttributeNS(XMLNS_NS, xmlns, uri);
316             delete[] xmlns;
317         }
318         else {
319             domElement->setAttributeNS(XMLNS_NS, XMLNS_PREFIX, uri);
320         }
321     }
322
323     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {
324         // Return nullptr if no declaration in effect. The empty string signifies the null namespace.
325         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {
326             // At the root, the default namespace is set to the null namespace.
327             if (!prefix || !*prefix)
328                 return &chNull;
329             return nullptr;    // we're done
330         }
331         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();
332         if (!attributes)
333             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent
334         DOMNode* childNode;
335         DOMAttr* attribute;
336         for (XMLSize_t i=0; i<attributes->getLength(); i++) {
337             childNode = attributes->item(i);
338             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?
339                 continue;
340             attribute = static_cast<DOMAttr*>(childNode);
341             if (!XMLString::equals(attribute->getNamespaceURI(),XMLNS_NS))
342                 continue;   // not a namespace declaration
343             // Local name should be the prefix and the value would be the URI, except for the default namespace.
344             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLNS_PREFIX))
345                 return attribute->getNodeValue();
346             else if (XMLString::equals(prefix,attribute->getLocalName()))
347                 return attribute->getNodeValue();
348         }
349         // Defer to parent.
350         return lookupNamespaceURI(n->getParentNode(),prefix);
351     }
352 };
353
354 void AbstractXMLObjectMarshaller::marshallNamespaces(DOMElement* domElement) const
355 {
356     m_log.debug("marshalling namespace attributes for XMLObject");
357     const set<Namespace>& namespaces = getNamespaces();
358     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));
359 }
360
361 void AbstractXMLObjectMarshaller::marshallContent(
362     DOMElement* domElement
363 #ifndef XMLTOOLING_NO_XMLSEC
364     ,const Credential* credential
365 #endif
366     ) const
367 {
368     m_log.debug("marshalling text and child elements for XMLObject");
369     
370     unsigned int pos=0;
371     const XMLCh* val = getTextContent(pos);
372     if (val && *val)
373         domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
374     
375     const list<XMLObject*>& children=getOrderedChildren();
376     for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i) {
377         if (*i) {
378 #ifndef XMLTOOLING_NO_XMLSEC
379             (*i)->marshall(domElement,nullptr,credential);
380 #else
381             (*i)->marshall(domElement);
382 #endif
383             val = getTextContent(++pos);
384             if (val && *val)
385                 domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
386         }
387     }
388 }
389
390 void AbstractXMLObjectMarshaller::marshallAttributes(DOMElement* domElement) const
391 {
392 }
393
394 void AbstractXMLObjectMarshaller::prepareForMarshalling() const
395 {
396 }