Merge branch '1.x' of ssh://authdev.it.ohio-state.edu/~scantor/git/cpp-xmltooling...
[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 <xercesc/util/XMLUniDefs.hpp>
38
39 using namespace xmltooling;
40 using namespace xercesc;
41 using namespace std;
42 using xmlconstants::XMLTOOLING_NS;
43
44 params::params(int count,...)
45 {
46     va_list args;
47     va_start(args,count);
48     while (count--)
49         v.push_back(va_arg(args,char*));
50     va_end(args);
51 }
52
53 namedparams::namedparams(int count,...)
54 {
55     count*=2;
56     va_list args;
57     va_start(args,count);
58     while (count--)
59         v.push_back(va_arg(args,char*));
60     va_end(args);
61 }
62
63 XMLToolingException::ExceptionFactoryMap XMLToolingException::m_factoryMap;
64
65 XMLToolingException* XMLToolingException::getInstance(const char* exceptionClass)
66 {
67     if (exceptionClass) {
68         ExceptionFactoryMap::const_iterator i=m_factoryMap.find(exceptionClass);
69         if (i!=m_factoryMap.end())
70             return (i->second)();
71     }
72     return new XMLToolingException();
73 }
74
75 XMLToolingException::XMLToolingException(const char* msg, const params& p)
76 {
77     if (msg)
78         m_msg=msg;
79     addProperties(p);
80 }
81
82 XMLToolingException::XMLToolingException(const char* msg, const namedparams& p)
83 {
84     if (msg)
85         m_msg=msg;
86     addProperties(p);
87 }
88
89 XMLToolingException::XMLToolingException(const std::string& msg, const params& p) : m_msg(msg)
90 {
91     addProperties(p);
92 }
93
94 XMLToolingException::XMLToolingException(const std::string& msg, const namedparams& p) : m_msg(msg)
95 {
96     addProperties(p);
97 }
98
99 void XMLToolingException::setMessage(const char* msg)
100 {
101     if (msg)
102         m_msg=msg;
103     else
104         m_msg.erase();
105     m_processedmsg.erase();
106 }
107
108 inline const char* get_digit_character()
109 {
110     static const char  s_characters[19] = 
111     {
112             '9'
113         ,   '8'
114         ,   '7'
115         ,   '6'
116         ,   '5'
117         ,   '4'
118         ,   '3'
119         ,   '2'
120         ,   '1'
121         ,   '0'
122         ,   '1'
123         ,   '2'
124         ,   '3'
125         ,   '4'
126         ,   '5'
127         ,   '6'
128         ,   '7'
129         ,   '8'
130         ,   '9'
131     };
132     static const char  *s_mid  =   s_characters + 9;
133
134     return s_mid;
135 }
136
137 inline const char* unsigned_integer_to_string(char* buf, size_t cchBuf, size_t i)
138 {
139     char* psz=buf + cchBuf - 1;     // Set psz to last char
140     *psz = 0;                       // Set terminating null
141
142     do {
143         size_t lsd = i % 10;  // Get least significant
144                                     // digit
145
146         i /= 10;                    // Prepare for next most
147                                     // significant digit
148
149         --psz;                      // Move back
150
151         *psz = get_digit_character()[lsd]; // Place the digit
152
153     } while(i!=0 && psz>buf);
154
155     return psz;
156 }
157
158 void XMLToolingException::addProperties(const params& p)
159 {
160     m_processedmsg.erase();
161     map<string,string>::size_type i=m_params.size()+1;
162     char buf[20];
163     const vector<const char*>& v=p.get();
164     for (vector<const char*>::const_iterator ci=v.begin(); ci!=v.end(); ci++) {
165         m_params[unsigned_integer_to_string(buf,sizeof(buf),i++)] = *ci;
166     }
167 }
168         
169 void XMLToolingException::addProperties(const namedparams& p)
170 {
171     m_processedmsg.erase();
172     const vector<const char*>& v=p.get();
173     for (vector<const char*>::const_iterator ci=v.begin(); ci!=v.end(); ci++) {
174         m_params.erase(*ci);
175         m_params[*ci] = *(ci+1);
176         ci++;   // advance past name to value, then loop will advance it again
177     }
178 }
179
180 const char* XMLToolingException::getProperty(unsigned int index) const
181 {
182     char buf[20];
183     map<string,string>::const_iterator i=m_params.find(unsigned_integer_to_string(buf,sizeof(buf),index));
184     return (i==m_params.end()) ? nullptr : i->second.c_str();
185 }
186
187 const char* XMLToolingException::getProperty(const char* name) const
188 {
189     map<string,string>::const_iterator i=m_params.find(name);
190     return (i==m_params.end()) ? nullptr : i->second.c_str();
191 }
192
193 const char* XMLToolingException::getMessage() const
194 {
195     if (!m_processedmsg.empty())
196         return m_processedmsg.c_str();
197     else if (m_params.empty())
198         return m_msg.c_str();
199
200     static const char* legal="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_";
201
202     // Replace any parameters in the message.
203     string::size_type i=0,start=0;
204     while (start!=string::npos && start<m_msg.length() && (i=m_msg.find("$",start))!=string::npos) {
205         if (i>start)
206             m_processedmsg += m_msg.substr(start,i-start);  // append everything in between
207         start=i+1;                                  // move start to the beginning of the token name
208         i=m_msg.find_first_not_of(legal,start);     // find token delimiter
209         if (i==start) {                             // append a non legal character
210            m_processedmsg+=m_msg[start++];
211            continue;
212         }
213         
214         // search for token in map
215         map<string,string>::const_iterator param=m_params.find(m_msg.substr(start,(i==string::npos) ? i : i-start));
216         if (param!=m_params.end()) {
217             m_processedmsg+=param->second;
218             start=i;
219         }
220     }
221     if (start!=string::npos && start<m_msg.length())
222         m_processedmsg += m_msg.substr(start,i);    // append rest of string
223     return m_processedmsg.c_str();
224 }
225
226 void xml_encode(string& s, const char* pre, const char* start, const char* post)
227 {
228     s += pre;
229     size_t pos;
230     while (start && *start) {
231         pos = strcspn(start, "\"<>&");
232         if (pos > 0) {
233             s.append(start, pos);
234             start += pos;
235         }
236         else {
237             switch (*start) {
238                 case '\'':  s += "&apos;";     break;
239                 case '<':   s += "&lt;";       break;
240                 case '>':   s += "&gt;";       break;
241                 case '&':   s += "&amp;";      break;
242                 default:    s += *start;
243             }
244             start++;
245         }
246     }
247     s += post;
248 }
249
250 string XMLToolingException::toString() const
251 {
252     string xml=string("<exception xmlns='http://www.opensaml.org/xmltooling' type='") + getClassName() + "'>";
253     const char* msg=getMessage();
254     if (msg)
255         xml_encode(xml, "<message>", msg, "</message>");
256     const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
257     for (map<string,string>::const_iterator i=m_params.begin(); i!=m_params.end(); i++) {
258         xml_encode(xml, "<param name='", i->first.c_str(), "'");
259         xml_encode(xml, ">", encoder->encode(i->second.c_str()).c_str(), "</param>");
260     }
261     xml+="</exception>";
262     return xml;
263 }
264
265 string XMLToolingException::toQueryString() const
266 {
267     string q;
268     const URLEncoder* enc = XMLToolingConfig::getConfig().getURLEncoder();
269     for (map<string,string>::const_iterator i=m_params.begin(); i!=m_params.end(); i++) {
270         if (!q.empty())
271             q += '&';
272         q = q + i->first + '=' + enc->encode(i->second.c_str());
273     }
274     return q;
275 }
276
277 XMLToolingException* XMLToolingException::fromStream(std::istream& in)
278 {
279     static const XMLCh exception[] =    UNICODE_LITERAL_9(e,x,c,e,p,t,i,o,n);
280     static const XMLCh message[] =      UNICODE_LITERAL_7(m,e,s,s,a,g,e);
281     static const XMLCh name[] =         UNICODE_LITERAL_4(n,a,m,e);
282     static const XMLCh param[] =        UNICODE_LITERAL_5(p,a,r,a,m);
283     static const XMLCh type[] =         UNICODE_LITERAL_4(t,y,p,e);
284
285     DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in);
286     
287     // Check root element.
288     const DOMElement* root=doc->getDocumentElement();
289     if (!XMLHelper::isNodeNamed(root,XMLTOOLING_NS,exception)) {
290         doc->release();
291         throw XMLToolingException("Invalid root element on serialized exception.");
292     }
293     
294     auto_ptr_char classname(root->getAttributeNS(nullptr,type));
295     auto_ptr<XMLToolingException> excep(XMLToolingException::getInstance(classname.get()));
296     
297     DOMElement* child=XMLHelper::getFirstChildElement(root,XMLTOOLING_NS,message);
298     if (child && child->hasChildNodes()) {
299         auto_ptr_char m(child->getFirstChild()->getNodeValue());
300         excep->setMessage(m.get());
301     }
302     
303     const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
304     child=XMLHelper::getFirstChildElement(root,XMLTOOLING_NS,param);
305     while (child && child->hasChildNodes()) {
306         auto_ptr_char n(child->getAttributeNS(nullptr,name));
307         char* encoded = XMLString::transcode(child->getFirstChild()->getNodeValue());
308         if (n.get() && encoded) {
309             encoder->decode(encoded);
310             excep->addProperty(n.get(), encoded);
311         }
312         XMLString::release(&encoded);
313         child=XMLHelper::getNextSiblingElement(child,XMLTOOLING_NS,param);
314     }
315
316     doc->release();
317     return excep.release();
318 }
319         
320 XMLToolingException* XMLToolingException::fromString(const char* s)
321 {
322     istringstream in(s);
323     return fromStream(in);
324 }