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