001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.joran.event;
015
016import static ch.qos.logback.core.CoreConstants.XML_PARSING;
017
018import java.io.ByteArrayInputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.ArrayList;
022import java.util.List;
023
024import javax.xml.parsers.ParserConfigurationException;
025import javax.xml.parsers.SAXParser;
026import javax.xml.parsers.SAXParserFactory;
027
028import org.xml.sax.Attributes;
029import org.xml.sax.InputSource;
030import org.xml.sax.Locator;
031import org.xml.sax.SAXException;
032import org.xml.sax.SAXParseException;
033import org.xml.sax.helpers.DefaultHandler;
034
035import ch.qos.logback.core.Context;
036import ch.qos.logback.core.joran.spi.ElementPath;
037import ch.qos.logback.core.joran.spi.JoranException;
038import ch.qos.logback.core.spi.ContextAware;
039import ch.qos.logback.core.spi.ContextAwareImpl;
040import ch.qos.logback.core.status.Status;
041
042public class SaxEventRecorder extends DefaultHandler implements ContextAware {
043
044    // org.xml.sax.ext.LexicalHandler is an optional interface
045    final ContextAwareImpl contextAwareImpl;
046    final ElementPath elementPath;
047    List<SaxEvent> saxEventList = new ArrayList<SaxEvent>();
048    Locator locator;
049
050
051    public SaxEventRecorder(Context context) {
052        this(context, new ElementPath());
053    }
054
055
056    public SaxEventRecorder(Context context, ElementPath elementPath) {
057        contextAwareImpl = new ContextAwareImpl(context, this);
058        this.elementPath = elementPath;
059    }
060
061    /**
062     * An implementation which disallows external DTDs
063     *
064     * @param publicId The public identifier, or null if none is
065     *                 available.
066     * @param systemId The system identifier provided in the XML
067     *                 document.
068     * @return
069     * @throws SAXException
070     * @throws IOException
071     * @since 1.5.13
072     */
073    @Override
074    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
075        addWarn("Document Type Declaration (DOCTYPE) with external file reference is");
076        addWarn("disallowed to prevent Server-Side Request Forgery (SSRF) attacks.");
077        addWarn("returning contents of SYSTEM " +systemId+ " as a white space");
078        return new InputSource(new ByteArrayInputStream(" ".getBytes()));
079    }
080
081    final public void recordEvents(InputStream inputStream) throws JoranException {
082        recordEvents(new InputSource(inputStream));
083    }
084
085    public void recordEvents(InputSource inputSource) throws JoranException {
086        SAXParser saxParser = buildSaxParser();
087        try {
088            // the following sax property can be set in order to add 'this' as LexicalHandler to the saxParser
089            // However, this is not needed as long as resolveEntity() method is implemented as above
090            // saxParser.setProperty("http://xml.org/sax/properties/lexical-handler", this);
091
092            saxParser.parse(inputSource, this);
093
094            return;
095        } catch (IOException ie) {
096            handleError("I/O error occurred while parsing xml file", ie);
097        } catch (SAXException se) {
098            // Exception added into StatusManager via Sax error handling. No need to add it again
099            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}