Set fourth file version digit to signify rebuild.
[shibboleth/cpp-xmltooling.git] / xmltooling / exceptions.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  * exceptions.cpp
23  * 
24  * Exception classes
25  */
26  
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "XMLToolingConfig.h"
30 #include "util/URLEncoder.h"
31 #include "util/XMLConstants.h"
32 #include "util/XMLHelper.h"
33
34 #include <stdarg.h>
35 #include <memory>
36 #include <sstream>
37 #include <boost/lexical_cast.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
39
40 using namespace xmltooling;
41 using namespace xercesc;
42 using namespace boost;
43 using namespace std;
44 using xmlconstants::XMLTOOLING_NS;
45
46 params::params(int count,...)
47 {
48     va_list args;
49     va_start(args,count);
50     while (count--)
51         v.push_back(va_arg(args,char*));
52     va_end(args);
53 }
54
55 namedparams::namedparams(int count,...)
56 {
57     count*=2;
58     va_list args;
59     va_start(args,count);
60     while (count--)
61         v.push_back(va_arg(args,char*));
62     va_end(args);
63 }
64
65 XMLToolingException::ExceptionFactoryMap XMLToolingException::m_factoryMap;
66
67 XMLToolingException* XMLToolingException::getInstance(const char* exceptionClass)
68 {
69     if (exceptionClass) {
70         ExceptionFactoryMap::const_iterator i=m_factoryMap.find(exceptionClass);
71         if (i!=m_factoryMap.end())
72             return (i->second)();
73     }
74     return new XMLToolingException();
75 }
76
77 XMLToolingException::XMLToolingException(const char* msg, const params& p)
78 {
79     if (msg)
80         m_msg=msg;
81     addProperties(p);
82 }
83
84 XMLToolingException::XMLToolingException(const char* msg, const namedparams& p)
85 {
86     if (msg)
87         m_msg=msg;
88     addProperties(p);
89 }
90
91 XMLToolingException::XMLToolingException(const std::string& msg, const params& p) : m_msg(msg)
92 {
93     addProperties(p);
94 }
95
96 XMLToolingException::XMLToolingException(const std::string& msg, const namedparams& p) : m_msg(msg)
97 {
98     addProperties(p);
99 }
100
101 void XMLToolingException::setMessage(const char* msg)
102 {
103     if (msg)
104         m_msg=msg;
105     else
106         m_msg.erase();
107     m_processedmsg.erase();
108 }
109
110 void XMLToolingException::addProperties(const params& p)
111 {
112     m_processedmsg.erase();
113     map<string,string>::size_type i = m_params.size() + 1;
114     const vector<const char*>& v=p.get();
115     for (vector<const char*>::const_iterator ci = v.begin(); ci != v.end(); ++ci) {
116         try {
117             m_params[lexical_cast<string>(i++)] = *ci;
118         }
119         catch (bad_lexical_cast&) {
120         }
121     }
122 }
123         
124 void XMLToolingException::addProperties(const namedparams& p)
125 {
126     m_processedmsg.erase();
127     const vector<const char*>& v = p.get();
128     for (vector<const char*>::const_iterator ci = v.begin(); ci != v.end(); ++ci) {
129         m_params.erase(*ci);
130         m_params[*ci] = *(ci+1);
131         ++ci;   // advance past name to value, then loop will advance it again
132     }
133 }
134
135 const char* XMLToolingException::getProperty(unsigned int index) const
136 {
137     try {
138         map<string,string>::const_iterator i = m_params.find(lexical_cast<string>(index));
139         return (i==m_params.end()) ? nullptr : i->second.c_str();
140     }
141     catch (bad_lexical_cast&) {
142         return nullptr;
143     }
144 }
145
146 const char* XMLToolingException::getProperty(const char* name) const
147 {
148     map<string,string>::const_iterator i = m_params.find(name);
149     return (i==m_params.end()) ? nullptr : i->second.c_str();
150 }
151
152 const char* XMLToolingException::getMessage() const
153 {
154     if (!m_processedmsg.empty())
155         return m_processedmsg.c_str();
156     else if (m_params.empty())
157         return m_msg.c_str();
158
159     static const char* legal="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_";
160
161     // Replace any parameters in the message.
162     string::size_type i=0,start=0;
163     while (start!=string::npos && start<m_msg.length() && (i=m_msg.find("$",start))!=string::npos) {
164         if (i>start)
165             m_processedmsg += m_msg.substr(start,i-start);  // append everything in between
166         start=i+1;                                  // move start to the beginning of the token name
167         i=m_msg.find_first_not_of(legal,start);     // find token delimiter
168         if (i==start) {                             // append a non legal character
169            m_processedmsg+=m_msg[start++];
170            continue;
171         }
172         
173         // search for token in map
174         map<string,string>::const_iterator param=m_params.find(m_msg.substr(start,(i==string::npos) ? i : i-start));
175         if (param!=m_params.end()) {
176             m_processedmsg+=param->second;
177             start=i;
178         }
179     }
180     if (start!=string::npos && start<m_msg.length())
181         m_processedmsg += m_msg.substr(start,i);    // append rest of string
182     return m_processedmsg.c_str();
183 }
184
185 void xml_encode(string& s, const char* pre, const char* start, const char* post)
186 {
187     s += pre;
188     size_t pos;
189     while (start && *start) {
190         pos = strcspn(start, "\"<>&");
191         if (pos > 0) {
192             s.append(start, pos);
193             start += pos;
194         }
195         else {
196             switch (*start) {
197                 case '\'':  s += "&apos;";     break;
198                 case '<':   s += "&lt;";       break;
199                 case '>':   s += "&gt;";       break;
200                 case '&':   s += "&amp;";      break;
201                 default:    s += *start;
202             }
203             start++;
204         }
205     }
206     s += post;
207 }
208
209 string XMLToolingException::toString() const
210 {
211     string xml=string("<exception xmlns='http://www.opensaml.org/xmltooling' type='") + getClassName() + "'>";
212     const char* msg=getMessage();
213     if (msg)
214         xml_encode(xml, "<message>", msg, "</message>");
215     const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
216     for (map<string,string>::const_iterator i = m_params.begin(); i != m_params.end(); ++i) {
217         xml_encode(xml, "<param name='", i->first.c_str(), "'");
218         xml_encode(xml, ">", encoder->encode(i->second.c_str()).c_str(), "</param>");
219     }
220     xml+="</exception>";
221     return xml;
222 }
223
224 string XMLToolingException::toQueryString() const
225 {
226     string q;
227     const URLEncoder* enc = XMLToolingConfig::getConfig().getURLEncoder();
228     for (map<string,string>::const_iterator i = m_params.begin(); i != m_params.end(); ++i) {
229         if (!q.empty())
230             q += '&';
231         q = q + i->first + '=' + enc->encode(i->second.c_str());
232     }
233     return q;
234 }
235
236 XMLToolingException* XMLToolingException::fromStream(std::istream& in)
237 {
238     static const XMLCh exception[] =    UNICODE_LITERAL_9(e,x,c,e,p,t,i,o,n);
239     static const XMLCh message[] =      UNICODE_LITERAL_7(m,e,s,s,a,g,e);
240     static const XMLCh name[] =         UNICODE_LITERAL_4(n,a,m,e);
241     static const XMLCh param[] =        UNICODE_LITERAL_5(p,a,r,a,m);
242     static const XMLCh type[] =         UNICODE_LITERAL_4(t,y,p,e);
243
244     DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in);
245     
246     // Check root element.
247     const DOMElement* root=doc->getDocumentElement();
248     if (!XMLHelper::isNodeNamed(root,XMLTOOLING_NS,exception)) {
249         doc->release();
250         throw XMLToolingException("Invalid root element on serialized exception.");
251     }
252     
253     auto_ptr_char classname(root->getAttributeNS(nullptr,type));
254     auto_ptr<XMLToolingException> excep(XMLToolingException::getInstance(classname.get()));
255     
256     DOMElement* child=XMLHelper::getFirstChildElement(root,XMLTOOLING_NS,message);
257     if (child && child->hasChildNodes()) {
258         auto_ptr_char m(child->getFirstChild()->getNodeValue());
259         excep->setMessage(m.get());
260     }
261     
262     const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
263     child=XMLHelper::getFirstChildElement(root,XMLTOOLING_NS,param);
264     while (child && child->hasChildNodes()) {
265         auto_ptr_char n(child->getAttributeNS(nullptr,name));
266         char* encoded = XMLString::transcode(child->getFirstChild()->getNodeValue());
267         if (n.get() && encoded) {
268             encoder->decode(encoded);
269             excep->addProperty(n.get(), encoded);
270         }
271         XMLString::release(&encoded);
272         child=XMLHelper::getNextSiblingElement(child,XMLTOOLING_NS,param);
273     }
274
275     doc->release();
276     return excep.release();
277 }
278         
279 XMLToolingException* XMLToolingException::fromString(const char* s)
280 {
281     istringstream in(s);
282     return fromStream(in);
283 }