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