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