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