3eb039d678adb85cedf0cac65627a241c149d84c
[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             return false;\r
173         }\r
174         \r
175         // Fetch all the <uri> elements.\r
176         DOMNodeList* mappings=root->getElementsByTagNameNS(CATALOG_NS,uri);\r
177         XMLPlatformUtils::lockMutex(m_lock);\r
178         for (XMLSize_t i=0; i<mappings->getLength(); i++) {\r
179             root=static_cast<DOMElement*>(mappings->item(i));\r
180             const XMLCh* from=root->getAttributeNS(NULL,name);\r
181             const XMLCh* to=root->getAttributeNS(NULL,uri);\r
182 #ifdef HAVE_GOOD_STL\r
183             m_schemaLocMap[from]=to;\r
184 #else\r
185             auto_ptr_char f(from);\r
186             auto_ptr_char t(to);\r
187             m_schemaLocMap[f.get()]=t.get();\r
188 #endif\r
189         }\r
190         m_schemaLocations.erase();\r
191 #ifdef HAVE_GOOD_STL\r
192         for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<xstring>(m_schemaLocations,chSpace));\r
193 #else\r
194         for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<string>(m_schemaLocations,' '));\r
195 #endif\r
196         XMLPlatformUtils::unlockMutex(m_lock);\r
197     }\r
198     catch (XMLParserException& e) {\r
199         log.error("catalog loader caught XMLParserException: %s", e.what());\r
200         return false;\r
201     }\r
202 \r
203     return true;\r
204 }\r
205 \r
206 DOMInputSource* ParserPool::resolveEntity(const XMLCh* const publicId, const XMLCh* const systemId, const XMLCh* const baseURI)\r
207 {\r
208 #if _DEBUG\r
209     xmltooling::NDC ndc("resolveEntity");\r
210 #endif\r
211     if (!systemId)\r
212         return NULL;\r
213 \r
214     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");\r
215     if (log.isDebugEnabled()) {\r
216         auto_ptr_char sysId(systemId);\r
217         auto_ptr_char base(baseURI);\r
218         log.debug("asked to resolve %s with baseURI %s",sysId.get(),base.get() ? base.get() : "(null)");\r
219     }\r
220 \r
221     // Find well-known schemas in the specified location.\r
222 #ifdef HAVE_GOOD_STL\r
223     map<xstring,xstring>::const_iterator i=m_schemaLocMap.find(systemId);\r
224     if (i!=m_schemaLocMap.end())\r
225         return new Wrapper4InputSource(new LocalFileInputSource(NULL,i->second.c_str()));\r
226 #else\r
227     auto_ptr_char temp(systemId);\r
228     map<string,string>::const_iterator i=m_schemaLocMap.find(temp.get());\r
229     auto_ptr_XMLCh temp2(i->second.c_str());\r
230     if (i!=m_schemaLocMap.end())\r
231         return new Wrapper4InputSource(new LocalFileInputSource(NULL,temp2.get()));\r
232 #endif    \r
233 \r
234     // Shortcircuit the request.\r
235     log.warn("unauthorized entity request, blocking it");\r
236     static const XMLByte nullbuf[] = {0};\r
237     return new Wrapper4InputSource(new MemBufInputSource(nullbuf,0,systemId));\r
238 }\r
239 \r
240 bool ParserPool::handleError(const DOMError& e)\r
241 {\r
242 #ifdef _DEBUG\r
243     xmltooling::NDC ndc("handleError");\r
244 #endif\r
245     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");\r
246     DOMLocator* locator=e.getLocation();\r
247     auto_ptr_char temp(e.getMessage());\r
248 \r
249     switch (e.getSeverity()) {\r
250         case DOMError::DOM_SEVERITY_WARNING:\r
251             log.warnStream() << "warning on line " << locator->getLineNumber()\r
252                 << ", column " << locator->getColumnNumber()\r
253                 << ", message: " << temp.get() << CategoryStream::ENDLINE;\r
254             return true;\r
255 \r
256         case DOMError::DOM_SEVERITY_ERROR:\r
257             log.errorStream() << "error on line " << locator->getLineNumber()\r
258                 << ", column " << locator->getColumnNumber()\r
259                 << ", message: " << temp.get() << CategoryStream::ENDLINE;\r
260             throw XMLParserException(string("error during XML parsing: ") + (temp.get() ? temp.get() : "no message"));\r
261 \r
262         case DOMError::DOM_SEVERITY_FATAL_ERROR:\r
263             log.critStream() << "fatal error on line " << locator->getLineNumber()\r
264                 << ", column " << locator->getColumnNumber()\r
265                 << ", message: " << temp.get() << CategoryStream::ENDLINE;\r
266             throw XMLParserException(string("fatal error during XML parsing: ") + (temp.get() ? temp.get() : "no message"));\r
267     }\r
268     throw XMLParserException(string("unclassified error during XML parsing: ") + (temp.get() ? temp.get() : "no message"));\r
269 }\r
270 \r
271 DOMBuilder* ParserPool::createBuilder()\r
272 {\r
273     static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull };\r
274     DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(impltype);\r
275     DOMBuilder* parser=static_cast<DOMImplementationLS*>(impl)->createDOMBuilder(DOMImplementationLS::MODE_SYNCHRONOUS,0);\r
276     if (m_namespaceAware)\r
277         parser->setFeature(XMLUni::fgDOMNamespaces,true);\r
278     if (m_schemaAware) {\r
279         parser->setFeature(XMLUni::fgXercesSchema,true);\r
280         parser->setFeature(XMLUni::fgDOMValidation,true);\r
281         parser->setFeature(XMLUni::fgXercesCacheGrammarFromParse,true);\r
282         parser->setFeature(XMLUni::fgXercesValidationErrorAsFatal,true);\r
283         \r
284         // We build a "fake" schema location hint that binds each namespace to itself.\r
285         // This ensures the entity resolver will be given the namespace as a systemId it can check. \r
286 #ifdef HAVE_GOOD_STL\r
287         parser->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(m_schemaLocations.c_str()));\r
288 #else\r
289         auto_ptr_XMLCh temp(m_schemaLocations.c_str());\r
290         parser->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(temp.get()));\r
291 #endif\r
292     }\r
293     parser->setFeature(XMLUni::fgXercesUserAdoptsDOMDocument,true);\r
294     parser->setEntityResolver(this);\r
295     parser->setErrorHandler(this);\r
296     return parser;\r
297 }\r
298 \r
299 DOMBuilder* ParserPool::checkoutBuilder()\r
300 {\r
301     XMLPlatformUtils::lockMutex(m_lock);\r
302     try {\r
303         if (m_pool.empty()) {\r
304             DOMBuilder* builder=createBuilder();\r
305             XMLPlatformUtils::unlockMutex(m_lock);\r
306             return builder;\r
307         }\r
308         DOMBuilder* p=m_pool.top();\r
309         m_pool.pop();\r
310         if (m_schemaAware) {\r
311 #ifdef HAVE_GOOD_STL\r
312             p->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(m_schemaLocations.c_str()));\r
313 #else\r
314             auto_ptr_XMLCh temp2(m_schemaLocations.c_str());\r
315             p->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(temp2.get()));\r
316 #endif\r
317         }\r
318         XMLPlatformUtils::unlockMutex(m_lock);\r
319         return p;\r
320     }\r
321     catch(...) {\r
322         XMLPlatformUtils::unlockMutex(m_lock);\r
323         throw;\r
324     }\r
325 }\r
326 \r
327 void ParserPool::checkinBuilder(DOMBuilder* builder)\r
328 {\r
329     if (builder) {\r
330         XMLPlatformUtils::lockMutex(m_lock);\r
331         m_pool.push(builder);\r
332         XMLPlatformUtils::unlockMutex(m_lock);\r
333     }\r
334 }\r
335 \r
336 unsigned int StreamInputSource::StreamBinInputStream::readBytes(XMLByte* const toFill, const unsigned int maxToRead)\r
337 {\r
338     XMLByte* target=toFill;\r
339     unsigned int bytes_read=0,request=maxToRead;\r
340 \r
341     // Fulfill the rest by reading from the stream.\r
342     if (request && !m_is.eof()) {\r
343         try {\r
344             m_is.read(reinterpret_cast<char* const>(target),request);\r
345             m_pos+=m_is.gcount();\r
346             bytes_read+=m_is.gcount();\r
347         }\r
348         catch(...) {\r
349             Category::getInstance(XMLTOOLING_LOGCAT".StreamInputSource").critStream() <<\r
350                 "XML::StreamInputSource::StreamBinInputStream::readBytes caught an exception" << CategoryStream::ENDLINE;\r
351             *toFill=0;\r
352             return 0;\r
353         }\r
354     }\r
355     return bytes_read;\r
356 }\r