Add parametrized messaging and serialization to exceptions.
[shibboleth/cpp-xmltooling.git] / xmltooling / util / ParserPool.cpp
1 /*\r
2  *  Copyright 2001-2006 Internet2\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * ParserPool.cpp\r
19  * \r
20  * XML parsing\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "exceptions.h"\r
25 #include "util/NDC.h"\r
26 #include "util/ParserPool.h"\r
27 #include "util/XMLHelper.h"\r
28 \r
29 #include <algorithm>\r
30 #include <functional>\r
31 #include <sys/types.h>\r
32 #include <sys/stat.h>\r
33 #include <log4cpp/Category.hh>\r
34 #include <xercesc/util/PlatformUtils.hpp>\r
35 #include <xercesc/util/XMLUniDefs.hpp>\r
36 #include <xercesc/sax/SAXException.hpp>\r
37 #include <xercesc/framework/MemBufInputSource.hpp>\r
38 #include <xercesc/framework/LocalFileInputSource.hpp>\r
39 #include <xercesc/framework/Wrapper4InputSource.hpp>\r
40 \r
41 using namespace xmltooling;\r
42 using namespace std;\r
43 using namespace log4cpp;\r
44 \r
45 ParserPool::ParserPool(bool namespaceAware, bool schemaAware)\r
46     : m_namespaceAware(namespaceAware), m_schemaAware(schemaAware), m_lock(XMLPlatformUtils::makeMutex()) {}\r
47 \r
48 ParserPool::~ParserPool()\r
49 {\r
50     while(!m_pool.empty()) {\r
51         m_pool.top()->release();\r
52         m_pool.pop();\r
53     }\r
54     XMLPlatformUtils::closeMutex(m_lock);\r
55 }\r
56 \r
57 DOMDocument* ParserPool::newDocument()\r
58 {\r
59     return DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
60 }\r
61 \r
62 DOMDocument* ParserPool::parse(DOMInputSource& domsrc)\r
63 {\r
64     DOMBuilder* parser=checkoutBuilder();\r
65     try {\r
66         DOMDocument* doc=parser->parse(domsrc);\r
67         parser->setFeature(XMLUni::fgXercesUserAdoptsDOMDocument,true);\r
68         checkinBuilder(parser);\r
69         return doc;\r
70     }\r
71     catch (...) {\r
72         checkinBuilder(parser);\r
73         throw;\r
74     }\r
75 }\r
76 \r
77 DOMDocument* ParserPool::parse(istream& is)\r
78 {\r
79     StreamInputSource src(is);\r
80     Wrapper4InputSource domsrc(&src,false);\r
81     return parse(domsrc);\r
82 }\r
83 \r
84 // Functor to double its argument separated by a character and append to a buffer\r
85 template <class T> class doubleit\r
86 {\r
87 public:\r
88     doubleit(T& t, const typename T::value_type& s) : temp(t), sep(s) {}\r
89     void operator() (const pair<T,T>& s) { temp += s.first + sep + s.first + sep; }\r
90     T& temp;\r
91     const typename T::value_type& sep;\r
92 };\r
93 \r
94 bool ParserPool::loadSchema(const XMLCh* nsURI, const XMLCh* pathname)\r
95 {\r
96     // Just check the pathname and then directly register the pair into the map.\r
97     \r
98     auto_ptr_char p(pathname);\r
99 #ifdef WIN32\r
100     struct _stat stat_buf;\r
101     if (_stat(p.get(), &stat_buf) != 0)\r
102 #else\r
103     struct stat stat_buf;\r
104     if (stat(p.get(), &stat_buf) != 0)\r
105 #endif\r
106     {\r
107 #if _DEBUG\r
108         xmltooling::NDC ndc("loadSchema");\r
109 #endif\r
110         Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");\r
111         auto_ptr_char n(nsURI);\r
112         log.error("failed to load schema for (%s), file not found (%s)",n.get(),p.get());\r
113         return false;\r
114     }\r
115 \r
116     XMLPlatformUtils::lockMutex(m_lock);\r
117 #ifdef HAVE_GOOD_STL\r
118     m_schemaLocMap[nsURI]=pathname;\r
119     m_schemaLocations.erase();\r
120     for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<xstring>(m_schemaLocations,chSpace));\r
121 #else\r
122     auto_ptr_char n(nsURI);\r
123     m_schemaLocMap[n.get()]=p.get();\r
124     m_schemaLocations.erase();\r
125     for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<string>(m_schemaLocations,' '));\r
126 #endif\r
127     XMLPlatformUtils::unlockMutex(m_lock);\r
128 \r
129     return true;\r
130 }\r
131 \r
132 bool ParserPool::loadCatalog(const XMLCh* pathname)\r
133 {\r
134 #if _DEBUG\r
135     xmltooling::NDC ndc("loadCatalog");\r
136 #endif\r
137     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");\r
138 \r
139     // XML constants\r
140     static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull };\r
141     static const XMLCh catalog[] = { chLatin_c, chLatin_a, chLatin_t, chLatin_a, chLatin_l, chLatin_o, chLatin_g, chNull };\r
142     static const XMLCh uri[] = { chLatin_u, chLatin_r, chLatin_i, chNull };\r
143     static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };\r
144     static const XMLCh CATALOG_NS[] = {\r
145         chLatin_u, chLatin_r, chLatin_n, chColon,\r
146         chLatin_o, chLatin_a, chLatin_s, chLatin_i, chLatin_s, chColon,\r
147         chLatin_n, chLatin_a, chLatin_m, chLatin_e, chLatin_s, chColon,\r
148         chLatin_t, chLatin_c, chColon,\r
149         chLatin_e, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_y, chColon,\r
150         chLatin_x, chLatin_m, chLatin_l, chLatin_n, chLatin_s, chColon,\r
151         chLatin_x, chLatin_m, chLatin_l, chColon,\r
152         chLatin_c, chLatin_a, chLatin_t, chLatin_a, chLatin_l, chLatin_o, chLatin_g, chNull\r
153     };\r
154 \r
155     // Parse the catalog with the internal parser pool.\r
156 \r
157     if (log.isDebugEnabled()) {\r
158         auto_ptr_char temp(pathname);\r
159         log.debug("loading XML catalog from %s", temp.get());\r
160     }\r
161 \r
162     LocalFileInputSource fsrc(NULL,pathname);\r
163     Wrapper4InputSource domsrc(&fsrc,false);\r
164     try {\r
165         DOMDocument* doc=XMLToolingInternalConfig::getInternalConfig().m_parserPool->parse(domsrc);\r
166         \r
167         // Check root element.\r
168         const DOMElement* root=doc->getDocumentElement();\r
169         if (!XMLHelper::isNodeNamed(root,CATALOG_NS,catalog)) {\r
170             auto_ptr_char temp(pathname);\r
171             log.error("unknown root element, failed to load XML catalog from %s", temp.get());\r
172             doc->release();\r
173             return false;\r
174         }\r
175         \r
176         // Fetch all the <uri> elements.\r
177         DOMNodeList* mappings=root->getElementsByTagNameNS(CATALOG_NS,uri);\r
178         XMLPlatformUtils::lockMutex(m_lock);\r
179         for (XMLSize_t i=0; i<mappings->getLength(); i++) {\r
180             root=static_cast<DOMElement*>(mappings->item(i));\r
181             const XMLCh* from=root->getAttributeNS(NULL,name);\r
182             const XMLCh* to=root->getAttributeNS(NULL,uri);\r
183 #ifdef HAVE_GOOD_STL\r
184             m_schemaLocMap[from]=to;\r
185 #else\r
186             auto_ptr_char f(from);\r
187             auto_ptr_char t(to);\r
188             m_schemaLocMap[f.get()]=t.get();\r
189 #endif\r
190         }\r
191         m_schemaLocations.erase();\r
192 #ifdef HAVE_GOOD_STL\r
193         for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<xstring>(m_schemaLocations,chSpace));\r
194 #else\r
195         for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<string>(m_schemaLocations,' '));\r
196 #endif\r
197         XMLPlatformUtils::unlockMutex(m_lock);\r
198         doc->release();\r
199     }\r
200     catch (XMLParserException& e) {\r
201         log.error("catalog loader caught XMLParserException: %s", e.what());\r
202         return false;\r
203     }\r
204 \r
205     return true;\r
206 }\r
207 \r
208 DOMInputSource* ParserPool::resolveEntity(const XMLCh* const publicId, const XMLCh* const systemId, const XMLCh* const baseURI)\r
209 {\r
210 #if _DEBUG\r
211     xmltooling::NDC ndc("resolveEntity");\r
212 #endif\r
213     if (!systemId)\r
214         return NULL;\r
215 \r
216     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");\r
217     if (log.isDebugEnabled()) {\r
218         auto_ptr_char sysId(systemId);\r
219         auto_ptr_char base(baseURI);\r
220         log.debug("asked to resolve %s with baseURI %s",sysId.get(),base.get() ? base.get() : "(null)");\r
221     }\r
222 \r
223     // Find well-known schemas in the specified location.\r
224 #ifdef HAVE_GOOD_STL\r
225     map<xstring,xstring>::const_iterator i=m_schemaLocMap.find(systemId);\r
226     if (i!=m_schemaLocMap.end())\r
227         return new Wrapper4InputSource(new LocalFileInputSource(NULL,i->second.c_str()));\r
228 #else\r
229     auto_ptr_char temp(systemId);\r
230     map<string,string>::const_iterator i=m_schemaLocMap.find(temp.get());\r
231     auto_ptr_XMLCh temp2(i->second.c_str());\r
232     if (i!=m_schemaLocMap.end())\r
233         return new Wrapper4InputSource(new LocalFileInputSource(NULL,temp2.get()));\r
234 #endif    \r
235 \r
236     // Shortcircuit the request.\r
237     log.warn("unauthorized entity request, blocking it");\r
238     static const XMLByte nullbuf[] = {0};\r
239     return new Wrapper4InputSource(new MemBufInputSource(nullbuf,0,systemId));\r
240 }\r
241 \r
242 bool ParserPool::handleError(const DOMError& e)\r
243 {\r
244 #ifdef _DEBUG\r
245     xmltooling::NDC ndc("handleError");\r
246 #endif\r
247     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");\r
248     DOMLocator* locator=e.getLocation();\r
249     auto_ptr_char temp(e.getMessage());\r
250 \r
251     switch (e.getSeverity()) {\r
252         case DOMError::DOM_SEVERITY_WARNING:\r
253             log.warnStream() << "warning on line " << locator->getLineNumber()\r
254                 << ", column " << locator->getColumnNumber()\r
255                 << ", message: " << temp.get() << CategoryStream::ENDLINE;\r
256             return true;\r
257 \r
258         case DOMError::DOM_SEVERITY_ERROR:\r
259             log.errorStream() << "error on line " << locator->getLineNumber()\r
260                 << ", column " << locator->getColumnNumber()\r
261                 << ", message: " << temp.get() << CategoryStream::ENDLINE;\r
262             throw XMLParserException(string("error during XML parsing: ") + (temp.get() ? temp.get() : "no message"));\r
263 \r
264         case DOMError::DOM_SEVERITY_FATAL_ERROR:\r
265             log.critStream() << "fatal error on line " << locator->getLineNumber()\r
266                 << ", column " << locator->getColumnNumber()\r
267                 << ", message: " << temp.get() << CategoryStream::ENDLINE;\r
268             throw XMLParserException(string("fatal error during XML parsing: ") + (temp.get() ? temp.get() : "no message"));\r
269     }\r
270     throw XMLParserException(string("unclassified error during XML parsing: ") + (temp.get() ? temp.get() : "no message"));\r
271 }\r
272 \r
273 DOMBuilder* ParserPool::createBuilder()\r
274 {\r
275     static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull };\r
276     DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(impltype);\r
277     DOMBuilder* parser=static_cast<DOMImplementationLS*>(impl)->createDOMBuilder(DOMImplementationLS::MODE_SYNCHRONOUS,0);\r
278     if (m_namespaceAware)\r
279         parser->setFeature(XMLUni::fgDOMNamespaces,true);\r
280     if (m_schemaAware) {\r
281         parser->setFeature(XMLUni::fgXercesSchema,true);\r
282         parser->setFeature(XMLUni::fgDOMValidation,true);\r
283         parser->setFeature(XMLUni::fgXercesCacheGrammarFromParse,true);\r
284         parser->setFeature(XMLUni::fgXercesValidationErrorAsFatal,true);\r
285         \r
286         // We build a "fake" schema location hint that binds each namespace to itself.\r
287         // This ensures the entity resolver will be given the namespace as a systemId it can check. \r
288 #ifdef HAVE_GOOD_STL\r
289         parser->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(m_schemaLocations.c_str()));\r
290 #else\r
291         auto_ptr_XMLCh temp(m_schemaLocations.c_str());\r
292         parser->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(temp.get()));\r
293 #endif\r
294     }\r
295     parser->setFeature(XMLUni::fgXercesUserAdoptsDOMDocument,true);\r
296     parser->setEntityResolver(this);\r
297     parser->setErrorHandler(this);\r
298     return parser;\r
299 }\r
300 \r
301 DOMBuilder* ParserPool::checkoutBuilder()\r
302 {\r
303     XMLPlatformUtils::lockMutex(m_lock);\r
304     try {\r
305         if (m_pool.empty()) {\r
306             DOMBuilder* builder=createBuilder();\r
307             XMLPlatformUtils::unlockMutex(m_lock);\r
308             return builder;\r
309         }\r
310         DOMBuilder* p=m_pool.top();\r
311         m_pool.pop();\r
312         if (m_schemaAware) {\r
313 #ifdef HAVE_GOOD_STL\r
314             p->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(m_schemaLocations.c_str()));\r
315 #else\r
316             auto_ptr_XMLCh temp2(m_schemaLocations.c_str());\r
317             p->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(temp2.get()));\r
318 #endif\r
319         }\r
320         XMLPlatformUtils::unlockMutex(m_lock);\r
321         return p;\r
322     }\r
323     catch(...) {\r
324         XMLPlatformUtils::unlockMutex(m_lock);\r
325         throw;\r
326     }\r
327 }\r
328 \r
329 void ParserPool::checkinBuilder(DOMBuilder* builder)\r
330 {\r
331     if (builder) {\r
332         XMLPlatformUtils::lockMutex(m_lock);\r
333         m_pool.push(builder);\r
334         XMLPlatformUtils::unlockMutex(m_lock);\r
335     }\r
336 }\r
337 \r
338 unsigned int StreamInputSource::StreamBinInputStream::readBytes(XMLByte* const toFill, const unsigned int maxToRead)\r
339 {\r
340     XMLByte* target=toFill;\r
341     unsigned int bytes_read=0,request=maxToRead;\r
342 \r
343     // Fulfill the rest by reading from the stream.\r
344     if (request && !m_is.eof()) {\r
345         try {\r
346             m_is.read(reinterpret_cast<char* const>(target),request);\r
347             m_pos+=m_is.gcount();\r
348             bytes_read+=m_is.gcount();\r
349         }\r
350         catch(...) {\r
351             Category::getInstance(XMLTOOLING_LOGCAT".StreamInputSource").critStream() <<\r
352                 "XML::StreamInputSource::StreamBinInputStream::readBytes caught an exception" << CategoryStream::ENDLINE;\r
353             *toFill=0;\r
354             return 0;\r
355         }\r
356     }\r
357     return bytes_read;\r
358 }\r