Xerces 3 revisions.
[shibboleth/cpp-xmltooling.git] / xmltooling / util / ParserPool.cpp
1 /*
2  *  Copyright 2001-2008 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  * ParserPool.cpp
19  * 
20  * A thread-safe pool of parsers that share characteristics.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "logging.h"
26 #include "util/NDC.h"
27 #include "util/ParserPool.h"
28 #include "util/XMLHelper.h"
29
30 #include <algorithm>
31 #include <functional>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <xercesc/util/PlatformUtils.hpp>
35 #include <xercesc/util/XMLUniDefs.hpp>
36 #include <xercesc/sax/SAXException.hpp>
37 #include <xercesc/framework/MemBufInputSource.hpp>
38 #include <xercesc/framework/LocalFileInputSource.hpp>
39 #include <xercesc/framework/Wrapper4InputSource.hpp>
40
41 using namespace xmltooling::logging;
42 using namespace xmltooling;
43 using namespace xercesc;
44 using namespace std;
45
46
47 namespace {
48     class MyErrorHandler : public DOMErrorHandler {
49     public:
50         unsigned int errors;
51
52         MyErrorHandler() : errors(0) {}
53
54         bool handleError(const DOMError& e)
55         {
56 #ifdef _DEBUG
57             xmltooling::NDC ndc("handleError");
58 #endif
59             Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");
60
61             DOMLocator* locator=e.getLocation();
62             auto_ptr_char temp(e.getMessage());
63
64             switch (e.getSeverity()) {
65                 case DOMError::DOM_SEVERITY_WARNING:
66                     log.warnStream() << "warning on line " << locator->getLineNumber()
67                         << ", column " << locator->getColumnNumber()
68                         << ", message: " << temp.get() << logging::eol;
69                     return true;
70
71                 case DOMError::DOM_SEVERITY_ERROR:
72                     ++errors;
73                     log.errorStream() << "error on line " << locator->getLineNumber()
74                         << ", column " << locator->getColumnNumber()
75                         << ", message: " << temp.get() << logging::eol;
76                     return true;
77
78                 case DOMError::DOM_SEVERITY_FATAL_ERROR:
79                     ++errors;
80                     log.errorStream() << "fatal error on line " << locator->getLineNumber()
81                         << ", column " << locator->getColumnNumber()
82                         << ", message: " << temp.get() << logging::eol;
83                     return true;
84             }
85
86             ++errors;
87             log.errorStream() << "undefined error type on line " << locator->getLineNumber()
88                 << ", column " << locator->getColumnNumber()
89                 << ", message: " << temp.get() << logging::eol;
90             return false;
91         }
92     };
93 }
94
95
96 ParserPool::ParserPool(bool namespaceAware, bool schemaAware)
97     : m_namespaceAware(namespaceAware), m_schemaAware(schemaAware), m_lock(Mutex::create()), m_security(new SecurityManager()) {}
98
99 ParserPool::~ParserPool()
100 {
101     while(!m_pool.empty()) {
102         m_pool.top()->release();
103         m_pool.pop();
104     }
105     delete m_lock;
106     delete m_security;
107 }
108
109 DOMDocument* ParserPool::newDocument()
110 {
111     return DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();
112 }
113
114 #ifdef XMLTOOLING_XERCESC_COMPLIANT_DOMLS
115
116 DOMDocument* ParserPool::parse(DOMLSInput& domsrc)
117 {
118     DOMLSParser* parser=checkoutBuilder();
119     XercesJanitor<DOMLSParser> janitor(parser);
120     try {
121         MyErrorHandler deh;
122         parser->getDomConfig()->setParameter(XMLUni::fgDOMErrorHandler, dynamic_cast<DOMErrorHandler*>(&deh));
123         DOMDocument* doc=parser->parse(&domsrc);
124         if (deh.errors) {
125             doc->release();
126             throw XMLParserException("XML error(s) during parsing, check log for specifics");
127         }
128         parser->getDomConfig()->setParameter(XMLUni::fgDOMErrorHandler, (void*)NULL);
129         parser->getDomConfig()->setParameter(XMLUni::fgXercesUserAdoptsDOMDocument, true);
130         checkinBuilder(janitor.release());
131         return doc;
132     }
133     catch (XMLException& ex) {
134         parser->getDomConfig()->setParameter(XMLUni::fgDOMErrorHandler, (void*)NULL);
135         parser->getDomConfig()->setParameter(XMLUni::fgXercesUserAdoptsDOMDocument, true);
136         checkinBuilder(janitor.release());
137         auto_ptr_char temp(ex.getMessage());
138         throw XMLParserException(string("Xerces error during parsing: ") + (temp.get() ? temp.get() : "no message"));
139     }
140     catch (XMLToolingException&) {
141         parser->getDomConfig()->setParameter(XMLUni::fgDOMErrorHandler, (void*)NULL);
142         parser->getDomConfig()->setParameter(XMLUni::fgXercesUserAdoptsDOMDocument, true);
143         checkinBuilder(janitor.release());
144         throw;
145     }
146 }
147
148 #else
149
150 DOMDocument* ParserPool::parse(DOMInputSource& domsrc)
151 {
152     DOMBuilder* parser=checkoutBuilder();
153     XercesJanitor<DOMBuilder> janitor(parser);
154     try {
155         MyErrorHandler deh;
156         parser->setErrorHandler(&deh);
157         DOMDocument* doc=parser->parse(domsrc);
158         if (deh.errors) {
159             doc->release();
160             throw XMLParserException("XML error(s) during parsing, check log for specifics");
161         }
162         parser->setErrorHandler(NULL);
163         parser->setFeature(XMLUni::fgXercesUserAdoptsDOMDocument, true);
164         checkinBuilder(janitor.release());
165         return doc;
166     }
167     catch (XMLException& ex) {
168         parser->setErrorHandler(NULL);
169         parser->setFeature(XMLUni::fgXercesUserAdoptsDOMDocument, true);
170         checkinBuilder(janitor.release());
171         auto_ptr_char temp(ex.getMessage());
172         throw XMLParserException(string("Xerces error during parsing: ") + (temp.get() ? temp.get() : "no message"));
173     }
174     catch (XMLToolingException&) {
175         parser->setErrorHandler(NULL);
176         parser->setFeature(XMLUni::fgXercesUserAdoptsDOMDocument, true);
177         checkinBuilder(janitor.release());
178         throw;
179     }
180 }
181
182 #endif
183
184 DOMDocument* ParserPool::parse(istream& is)
185 {
186     StreamInputSource src(is);
187     Wrapper4InputSource domsrc(&src,false);
188     return parse(domsrc);
189 }
190
191 // Functor to double its argument separated by a character and append to a buffer
192 template <class T> class doubleit
193 {
194 public:
195     doubleit(T& t, const typename T::value_type& s) : temp(t), sep(s) {}
196     void operator() (const pair<const T,T>& s) { temp += s.first + sep + s.first + sep; }
197     T& temp;
198     const typename T::value_type& sep;
199 };
200
201 bool ParserPool::loadSchema(const XMLCh* nsURI, const XMLCh* pathname)
202 {
203     // Just check the pathname and then directly register the pair into the map.
204     
205     auto_ptr_char p(pathname);
206 #ifdef WIN32
207     struct _stat stat_buf;
208     if (_stat(p.get(), &stat_buf) != 0)
209 #else
210     struct stat stat_buf;
211     if (stat(p.get(), &stat_buf) != 0)
212 #endif
213     {
214 #if _DEBUG
215         xmltooling::NDC ndc("loadSchema");
216 #endif
217         Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");
218         auto_ptr_char n(nsURI);
219         log.error("failed to load schema for (%s), file not found (%s)",n.get(),p.get());
220         return false;
221     }
222
223     Lock lock(m_lock);
224 #ifdef HAVE_GOOD_STL
225     m_schemaLocMap[nsURI]=pathname;
226     m_schemaLocations.erase();
227     for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<xstring>(m_schemaLocations,chSpace));
228 #else
229     auto_ptr_char n(nsURI);
230     m_schemaLocMap[n.get()]=p.get();
231     m_schemaLocations.erase();
232     for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<string>(m_schemaLocations,' '));
233 #endif
234
235     return true;
236 }
237
238 bool ParserPool::loadCatalog(const XMLCh* pathname)
239 {
240 #if _DEBUG
241     xmltooling::NDC ndc("loadCatalog");
242 #endif
243     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");
244
245     // XML constants
246     static const XMLCh catalog[] =  UNICODE_LITERAL_7(c,a,t,a,l,o,g);
247     static const XMLCh system[] =   UNICODE_LITERAL_6(s,y,s,t,e,m);
248     static const XMLCh systemId[] = UNICODE_LITERAL_8(s,y,s,t,e,m,I,d);
249     static const XMLCh uri[] =      UNICODE_LITERAL_3(u,r,i);
250     static const XMLCh CATALOG_NS[] = {
251         chLatin_u, chLatin_r, chLatin_n, chColon,
252         chLatin_o, chLatin_a, chLatin_s, chLatin_i, chLatin_s, chColon,
253         chLatin_n, chLatin_a, chLatin_m, chLatin_e, chLatin_s, chColon,
254         chLatin_t, chLatin_c, chColon,
255         chLatin_e, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_y, chColon,
256         chLatin_x, chLatin_m, chLatin_l, chLatin_n, chLatin_s, chColon,
257         chLatin_x, chLatin_m, chLatin_l, chColon,
258         chLatin_c, chLatin_a, chLatin_t, chLatin_a, chLatin_l, chLatin_o, chLatin_g, chNull
259     };
260
261     // Parse the catalog with the internal parser pool.
262
263     if (log.isDebugEnabled()) {
264         auto_ptr_char temp(pathname);
265         log.debug("loading XML catalog from %s", temp.get());
266     }
267
268     LocalFileInputSource fsrc(NULL,pathname);
269     Wrapper4InputSource domsrc(&fsrc,false);
270     try {
271         DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(domsrc);
272         XercesJanitor<DOMDocument> janitor(doc);
273         
274         // Check root element.
275         const DOMElement* root=doc->getDocumentElement();
276         if (!XMLHelper::isNodeNamed(root,CATALOG_NS,catalog)) {
277             auto_ptr_char temp(pathname);
278             log.error("unknown root element, failed to load XML catalog from %s", temp.get());
279             return false;
280         }
281         
282         // Fetch all the <system> elements.
283         DOMNodeList* mappings=root->getElementsByTagNameNS(CATALOG_NS,system);
284         Lock lock(m_lock);
285         for (XMLSize_t i=0; i<mappings->getLength(); i++) {
286             root=static_cast<DOMElement*>(mappings->item(i));
287             const XMLCh* from=root->getAttributeNS(NULL,systemId);
288             const XMLCh* to=root->getAttributeNS(NULL,uri);
289 #ifdef HAVE_GOOD_STL
290             m_schemaLocMap[from]=to;
291 #else
292             auto_ptr_char f(from);
293             auto_ptr_char t(to);
294             m_schemaLocMap[f.get()]=t.get();
295 #endif
296         }
297         m_schemaLocations.erase();
298 #ifdef HAVE_GOOD_STL
299         for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<xstring>(m_schemaLocations,chSpace));
300 #else
301         for_each(m_schemaLocMap.begin(),m_schemaLocMap.end(),doubleit<string>(m_schemaLocations,' '));
302 #endif
303     }
304     catch (exception& e) {
305         log.error("catalog loader caught exception: %s", e.what());
306         return false;
307     }
308
309     return true;
310 }
311
312 #ifdef XMLTOOLING_XERCESC_COMPLIANT_DOMLS
313 DOMLSInput* ParserPool::resolveResource(
314             const XMLCh *const resourceType,
315             const XMLCh *const namespaceUri,
316             const XMLCh *const publicId,
317             const XMLCh *const systemId,
318             const XMLCh *const baseURI
319             )
320 #else
321 DOMInputSource* ParserPool::resolveEntity(
322     const XMLCh* const publicId, const XMLCh* const systemId, const XMLCh* const baseURI
323     )
324 #endif
325 {
326 #if _DEBUG
327     xmltooling::NDC ndc("resolveEntity");
328 #endif
329     if (!systemId)
330         return NULL;
331
332     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ParserPool");
333     if (log.isDebugEnabled()) {
334         auto_ptr_char sysId(systemId);
335         auto_ptr_char base(baseURI);
336         log.debug("asked to resolve %s with baseURI %s",sysId.get(),base.get() ? base.get() : "(null)");
337     }
338
339 #ifdef HAVE_GOOD_STL
340     // Find well-known schemas in the specified location.
341     map<xstring,xstring>::const_iterator i=m_schemaLocMap.find(systemId);
342     if (i!=m_schemaLocMap.end())
343         return new Wrapper4InputSource(new LocalFileInputSource(baseURI,i->second.c_str()));
344
345     // Check for entity as a value in the map.
346     for (i=m_schemaLocMap.begin(); i!=m_schemaLocMap.end(); ++i) {
347         if (XMLString::endsWith(i->second.c_str(), systemId))
348             return new Wrapper4InputSource(new LocalFileInputSource(baseURI,i->second.c_str()));
349     }
350
351     // We'll allow anything without embedded slashes.
352     if (XMLString::indexOf(systemId, chForwardSlash)==-1)
353         return new Wrapper4InputSource(new LocalFileInputSource(baseURI,systemId));
354 #else
355     // Find well-known schemas in the specified location.
356     auto_ptr_char temp(systemId);
357     map<string,string>::const_iterator i=m_schemaLocMap.find(temp.get());
358     if (i!=m_schemaLocMap.end()) {
359         auto_ptr_XMLCh temp2(i->second.c_str());
360         return new Wrapper4InputSource(new LocalFileInputSource(baseURI,temp2.get()));
361     }
362
363     // Check for entity as a value in the map.
364     for (i=m_schemaLocMap.begin(); i!=m_schemaLocMap.end(); ++i) {
365         auto_ptr_XMLCh temp2(i->second.c_str());
366         if (XMLString::endsWith(temp2.get(), systemId))
367             return new Wrapper4InputSource(new LocalFileInputSource(baseURI,temp2.get()));
368     }
369
370     // We'll allow anything without embedded slashes.
371     if (XMLString::indexOf(systemId, chForwardSlash)==-1)
372         return new Wrapper4InputSource(new LocalFileInputSource(baseURI,systemId));
373 #endif    
374
375     // Shortcircuit the request.
376 #ifdef HAVE_GOOD_STL
377     auto_ptr_char temp(systemId);
378 #endif
379     log.debug("unauthorized entity request (%s), blocking it", temp.get());
380     static const XMLByte nullbuf[] = {0};
381     return new Wrapper4InputSource(new MemBufInputSource(nullbuf,0,systemId));
382 }
383
384 #ifdef XMLTOOLING_XERCESC_COMPLIANT_DOMLS
385
386 DOMLSParser* ParserPool::createBuilder()
387 {
388     static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull };
389     DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(impltype);
390     DOMLSParser* parser=static_cast<DOMImplementationLS*>(impl)->createLSParser(DOMImplementationLS::MODE_SYNCHRONOUS,NULL);
391     parser->getDomConfig()->setParameter(XMLUni::fgDOMNamespaces, m_namespaceAware);
392     if (m_schemaAware) {
393         parser->getDomConfig()->setParameter(XMLUni::fgDOMNamespaces, true);
394         parser->getDomConfig()->setParameter(XMLUni::fgXercesSchema, true);
395         parser->getDomConfig()->setParameter(XMLUni::fgDOMValidate, true);
396         parser->getDomConfig()->setParameter(XMLUni::fgXercesCacheGrammarFromParse, true);
397         
398         // We build a "fake" schema location hint that binds each namespace to itself.
399         // This ensures the entity resolver will be given the namespace as a systemId it can check. 
400 #ifdef HAVE_GOOD_STL
401         parser->getDomConfig()->setParameter(XMLUni::fgXercesSchemaExternalSchemaLocation, const_cast<XMLCh*>(m_schemaLocations.c_str()));
402 #else
403         auto_ptr_XMLCh temp(m_schemaLocations.c_str());
404         parser->getDomConfig()->setParameter(XMLUni::fgXercesSchemaExternalSchemaLocation, const_cast<XMLCh*>(temp.get()));
405 #endif
406     }
407     parser->getDomConfig()->setParameter(XMLUni::fgXercesUserAdoptsDOMDocument, true);
408     parser->getDomConfig()->setParameter(XMLUni::fgXercesDisableDefaultEntityResolution, true);
409     parser->getDomConfig()->setParameter(XMLUni::fgDOMResourceResolver, dynamic_cast<DOMLSResourceResolver*>(this));
410     parser->getDomConfig()->setParameter(XMLUni::fgXercesSecurityManager, m_security);
411     return parser;
412 }
413
414 DOMLSParser* ParserPool::checkoutBuilder()
415 {
416     Lock lock(m_lock);
417     if (m_pool.empty()) {
418         DOMLSParser* builder=createBuilder();
419         return builder;
420     }
421     DOMLSParser* p=m_pool.top();
422     m_pool.pop();
423     if (m_schemaAware) {
424 #ifdef HAVE_GOOD_STL
425         p->getDomConfig()->setParameter(XMLUni::fgXercesSchemaExternalSchemaLocation, const_cast<XMLCh*>(m_schemaLocations.c_str()));
426 #else
427         auto_ptr_XMLCh temp2(m_schemaLocations.c_str());
428         p->getDomConfig()->setParameter(XMLUni::fgXercesSchemaExternalSchemaLocation, const_cast<XMLCh*>(temp2.get()));
429 #endif
430     }
431     return p;
432 }
433
434 void ParserPool::checkinBuilder(DOMLSParser* builder)
435 {
436     if (builder) {
437         Lock lock(m_lock);
438         m_pool.push(builder);
439     }
440 }
441
442 #else
443
444 DOMBuilder* ParserPool::createBuilder()
445 {
446     static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull };
447     DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(impltype);
448     DOMBuilder* parser=static_cast<DOMImplementationLS*>(impl)->createDOMBuilder(DOMImplementationLS::MODE_SYNCHRONOUS,0);
449     parser->setFeature(XMLUni::fgDOMNamespaces, m_namespaceAware);
450     if (m_schemaAware) {
451         parser->setFeature(XMLUni::fgDOMNamespaces, true);
452         parser->setFeature(XMLUni::fgXercesSchema, true);
453         parser->setFeature(XMLUni::fgDOMValidation, true);
454         parser->setFeature(XMLUni::fgXercesCacheGrammarFromParse, true);
455         
456         // We build a "fake" schema location hint that binds each namespace to itself.
457         // This ensures the entity resolver will be given the namespace as a systemId it can check. 
458 #ifdef HAVE_GOOD_STL
459         parser->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(m_schemaLocations.c_str()));
460 #else
461         auto_ptr_XMLCh temp(m_schemaLocations.c_str());
462         parser->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(temp.get()));
463 #endif
464     }
465     parser->setProperty(XMLUni::fgXercesSecurityManager, m_security);
466     parser->setFeature(XMLUni::fgXercesUserAdoptsDOMDocument, true);
467     parser->setEntityResolver(this);
468     return parser;
469 }
470
471 DOMBuilder* ParserPool::checkoutBuilder()
472 {
473     Lock lock(m_lock);
474     if (m_pool.empty()) {
475         DOMBuilder* builder=createBuilder();
476         return builder;
477     }
478     DOMBuilder* p=m_pool.top();
479     m_pool.pop();
480     if (m_schemaAware) {
481 #ifdef HAVE_GOOD_STL
482         p->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(m_schemaLocations.c_str()));
483 #else
484         auto_ptr_XMLCh temp2(m_schemaLocations.c_str());
485         p->setProperty(XMLUni::fgXercesSchemaExternalSchemaLocation,const_cast<XMLCh*>(temp2.get()));
486 #endif
487     }
488     return p;
489 }
490
491 void ParserPool::checkinBuilder(DOMBuilder* builder)
492 {
493     if (builder) {
494         Lock lock(m_lock);
495         m_pool.push(builder);
496     }
497 }
498
499 #endif
500
501 xsecsize_t StreamInputSource::StreamBinInputStream::readBytes(XMLByte* const toFill, const xsecsize_t maxToRead)
502 {
503     XMLByte* target=toFill;
504     xsecsize_t bytes_read=0,request=maxToRead;
505
506     // Fulfill the rest by reading from the stream.
507     if (request && !m_is.eof() && !m_is.fail()) {
508         try {
509             m_is.read(reinterpret_cast<char* const>(target),request);
510             m_pos+=m_is.gcount();
511             bytes_read+=m_is.gcount();
512         }
513         catch(ios_base::failure& e) {
514             Category::getInstance(XMLTOOLING_LOGCAT".StreamInputSource").critStream()
515                 << "XML::StreamInputSource::StreamBinInputStream::readBytes caught an exception: " << e.what()
516                 << logging::eol;
517             *toFill=0;
518             return 0;
519         }
520     }
521     return bytes_read;
522 }