1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.joran.event;
15  
16  import static ch.qos.logback.core.CoreConstants.XML_PARSING;
17  
18  import java.io.ByteArrayInputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import javax.xml.parsers.ParserConfigurationException;
25  import javax.xml.parsers.SAXParser;
26  import javax.xml.parsers.SAXParserFactory;
27  
28  import org.xml.sax.Attributes;
29  import org.xml.sax.InputSource;
30  import org.xml.sax.Locator;
31  import org.xml.sax.SAXException;
32  import org.xml.sax.SAXParseException;
33  import org.xml.sax.helpers.DefaultHandler;
34  
35  import ch.qos.logback.core.Context;
36  import ch.qos.logback.core.joran.spi.ElementPath;
37  import ch.qos.logback.core.joran.spi.JoranException;
38  import ch.qos.logback.core.spi.ContextAware;
39  import ch.qos.logback.core.spi.ContextAwareImpl;
40  import ch.qos.logback.core.status.Status;
41  
42  public class SaxEventRecorder extends DefaultHandler implements ContextAware {
43  
44      // org.xml.sax.ext.LexicalHandler is an optional interface
45      final ContextAwareImpl contextAwareImpl;
46      final ElementPath elementPath;
47      List<SaxEvent> saxEventList = new ArrayList<SaxEvent>();
48      Locator locator;
49  
50  
51      public SaxEventRecorder(Context context) {
52          this(context, new ElementPath());
53      }
54  
55  
56      public SaxEventRecorder(Context context, ElementPath elementPath) {
57          contextAwareImpl = new ContextAwareImpl(context, this);
58          this.elementPath = elementPath;
59      }
60  
61      /**
62       * An implementation which disallows external DTDs
63       *
64       * @param publicId The public identifier, or null if none is
65       *                 available.
66       * @param systemId The system identifier provided in the XML
67       *                 document.
68       * @return
69       * @throws SAXException
70       * @throws IOException
71       * @since 1.5.13
72       */
73      @Override
74      public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
75          addWarn("Document Type Declaration (DOCTYPE) with external file reference is");
76          addWarn("disallowed to prevent Server-Side Request Forgery (SSRF) attacks.");
77          addWarn("returning contents of SYSTEM " +systemId+ " as a white space");
78          return new InputSource(new ByteArrayInputStream(" ".getBytes()));
79      }
80  
81      final public void recordEvents(InputStream inputStream) throws JoranException {
82          recordEvents(new InputSource(inputStream));
83      }
84  
85      public void recordEvents(InputSource inputSource) throws JoranException {
86          SAXParser saxParser = buildSaxParser();
87          try {
88              // the following sax property can be set in order to add 'this' as LexicalHandler to the saxParser
89              // However, this is not needed as long as resolveEntity() method is implemented as above
90              // saxParser.setProperty("http://xml.org/sax/properties/lexical-handler", this);
91  
92              saxParser.parse(inputSource, this);
93  
94              return;
95          } catch (IOException ie) {
96              handleError("I/O error occurred while parsing xml file", ie);
97          } catch (SAXException se) {
98              // Exception added into StatusManager via Sax error handling. No need to add it again
99              throw new JoranException("Problem parsing XML document. See previously reported errors.", se);
100         } catch (Exception ex) {
101             handleError("Unexpected exception while parsing XML document.", ex);
102         }
103         throw new IllegalStateException("This point can never be reached");
104     }
105 
106     private void handleError(String errMsg, Throwable t) throws JoranException {
107         addError(errMsg, t);
108         throw new JoranException(errMsg, t);
109     }
110 
111     private SAXParser buildSaxParser() throws JoranException {
112         try {
113             SAXParserFactory spf = SAXParserFactory.newInstance();
114             spf.setValidating(false);
115             //spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
116             // See LOGBACK-1465
117             spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
118             spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
119             spf.setNamespaceAware(true);
120             return spf.newSAXParser();
121         } catch (ParserConfigurationException pce) {
122             String errMsg = "Error during SAX paser configuration. See https://logback.qos.ch/codes.html#saxParserConfiguration";
123             addError(errMsg, pce);
124             throw new JoranException(errMsg, pce);
125         } catch (SAXException pce) {
126             String errMsg = "Error during parser creation or parser configuration";
127             addError(errMsg, pce);
128             throw new JoranException(errMsg, pce);
129         }
130     }
131 
132     public void startDocument() {
133     }
134 
135     public Locator getLocator() {
136         return locator;
137     }
138 
139     public void setDocumentLocator(Locator l) {
140         locator = l;
141     }
142 
143     protected boolean shouldIgnoreForElementPath(String tagName) {
144         return false;
145     }
146 
147     public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
148         String tagName = getTagName(localName, qName);
149         if (!shouldIgnoreForElementPath(tagName)) {
150             elementPath.push(tagName);
151         }
152         ElementPath current = elementPath.duplicate();
153         saxEventList.add(new StartEvent(current, namespaceURI, localName, qName, atts, getLocator()));
154     }
155 
156     public void characters(char[] ch, int start, int length) {
157         String bodyStr = new String(ch, start, length);
158         SaxEvent lastEvent = getLastEvent();
159         if (lastEvent instanceof BodyEvent) {
160             BodyEvent be = (BodyEvent) lastEvent;
161             be.append(bodyStr);
162         } else {
163             // ignore space only text if the previous event is not a BodyEvent
164             if (!isSpaceOnly(bodyStr)) {
165                 saxEventList.add(new BodyEvent(bodyStr, getLocator()));
166             }
167         }
168     }
169 
170     boolean isSpaceOnly(String bodyStr) {
171         String bodyTrimmed = bodyStr.trim();
172         return (bodyTrimmed.length() == 0);
173     }
174 
175     SaxEvent getLastEvent() {
176         if (saxEventList.isEmpty()) {
177             return null;
178         }
179         int size = saxEventList.size();
180         return saxEventList.get(size - 1);
181     }
182 
183     public void endElement(String namespaceURI, String localName, String qName) {
184         saxEventList.add(new EndEvent(namespaceURI, localName, qName, getLocator()));
185         String tagName = getTagName(localName, qName);
186         if (!shouldIgnoreForElementPath(tagName)) {
187             elementPath.pop();
188         }
189     }
190 
191     String getTagName(String localName, String qName) {
192         String tagName = localName;
193         if ((tagName == null) || (tagName.length() < 1)) {
194             tagName = qName;
195         }
196         return tagName;
197     }
198 
199     public void error(SAXParseException spe) throws SAXException {
200         addError(XML_PARSING + " - Parsing error on line " + spe.getLineNumber() + " and column " + spe.getColumnNumber());
201         addError(spe.toString());
202     }
203 
204     public void fatalError(SAXParseException spe) throws SAXException {
205         addError(XML_PARSING + " - Parsing fatal error on line " + spe.getLineNumber() + " and column " + spe.getColumnNumber());
206         addError(spe.toString());
207     }
208 
209     public void warning(SAXParseException spe) throws SAXException {
210         addWarn(XML_PARSING + " - Parsing warning on line " + spe.getLineNumber() + " and column " + spe.getColumnNumber(), spe);
211     }
212 
213     public void addError(String msg) {
214         contextAwareImpl.addError(msg);
215     }
216 
217     public void addError(String msg, Throwable ex) {
218         contextAwareImpl.addError(msg, ex);
219     }
220 
221     public void addInfo(String msg) {
222         contextAwareImpl.addInfo(msg);
223     }
224 
225     public void addInfo(String msg, Throwable ex) {
226         contextAwareImpl.addInfo(msg, ex);
227     }
228 
229     public void addStatus(Status status) {
230         contextAwareImpl.addStatus(status);
231     }
232 
233     public void addWarn(String msg) {
234         contextAwareImpl.addWarn(msg);
235     }
236 
237     public void addWarn(String msg, Throwable ex) {
238         contextAwareImpl.addWarn(msg, ex);
239     }
240 
241     public Context getContext() {
242         return contextAwareImpl.getContext();
243     }
244 
245     public void setContext(Context context) {
246         contextAwareImpl.setContext(context);
247     }
248 
249     public List<SaxEvent> getSaxEventList() {
250         return saxEventList;
251     }
252 
253 }