View Javadoc
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.action;
15  
16  import java.io.File;
17  import java.io.FileInputStream;
18  import java.io.FileOutputStream;
19  import java.io.IOException;
20  import java.net.MalformedURLException;
21  import java.util.Arrays;
22  import java.util.HashMap;
23  import java.util.Stack;
24  import java.util.function.Supplier;
25  
26  import ch.qos.logback.core.model.processor.IncludeModelHandler;
27  import org.junit.jupiter.api.AfterEach;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.BeforeEach;
30  import org.junit.jupiter.api.Test;
31  import org.xml.sax.SAXParseException;
32  
33  import ch.qos.logback.core.Context;
34  import ch.qos.logback.core.ContextBase;
35  import ch.qos.logback.core.joran.TrivialConfigurator;
36  import ch.qos.logback.core.joran.action.ext.StackAction;
37  import ch.qos.logback.core.joran.spi.ElementSelector;
38  import ch.qos.logback.core.joran.spi.JoranException;
39  import ch.qos.logback.core.model.IncludeModel;
40  import ch.qos.logback.core.model.StackModel;
41  import ch.qos.logback.core.model.TopModel;
42  import ch.qos.logback.core.model.processor.DefaultProcessor;
43  import ch.qos.logback.core.model.processor.NOPModelHandler;
44  import ch.qos.logback.core.model.processor.StackModelHandler;
45  import ch.qos.logback.core.status.Status;
46  import ch.qos.logback.core.testUtil.CoreTestConstants;
47  import ch.qos.logback.core.testUtil.FileTestUtil;
48  import ch.qos.logback.core.testUtil.RandomUtil;
49  import ch.qos.logback.core.status.testUtil.StatusChecker;
50  
51  import static ch.qos.logback.core.joran.JoranConstants.CONFIGURATION_TAG;
52  import static ch.qos.logback.core.joran.JoranConstants.INCLUDED_TAG;
53  
54  
55  public class IncludeModelHandlerTest {
56  
57      final static String INCLUDE_KEY = "includeKey";
58      final static String SUB_FILE_KEY = "subFileKey";
59      final static String SECOND_FILE_KEY = "secondFileKey";
60  
61      Context context = new ContextBase();
62      StatusChecker statusChecker = new StatusChecker(context);
63      TrivialConfigurator tc;
64  
65      static final String INCLUSION_DIR_PREFIX = CoreTestConstants.JORAN_INPUT_PREFIX + "inclusion/";
66  
67      static final String TOP_BY_FILE = INCLUSION_DIR_PREFIX + "topByFile.xml";
68  
69      static final String TOP_OPTIONAL = INCLUSION_DIR_PREFIX + "topOptional.xml";
70  
71      static final String TOP_OPTIONAL_RESOURCE = INCLUSION_DIR_PREFIX + "topOptionalResource.xml";
72  
73      static final String INTERMEDIARY_FILE = INCLUSION_DIR_PREFIX + "intermediaryByFile.xml";
74  
75      static final String SUB_FILE = INCLUSION_DIR_PREFIX + "subByFile.xml";
76  
77      static final String MULTI_INCLUDE_BY_FILE = INCLUSION_DIR_PREFIX + "multiIncludeByFile.xml";
78  
79      static final String SECOND_FILE = INCLUSION_DIR_PREFIX + "second.xml";
80  
81      static final String TOP_BY_URL = INCLUSION_DIR_PREFIX + "topByUrl.xml";
82  
83      static final String TOP_BY_ENTITY = INCLUSION_DIR_PREFIX + "topByEntity.xml";
84  
85      static final String INCLUDE_BY_RESOURCE = INCLUSION_DIR_PREFIX + "topByResource.xml";
86  
87      static final String INCLUDED_FILE = INCLUSION_DIR_PREFIX + "included.xml";
88      static final String URL_TO_INCLUDE = "file:./" + INCLUDED_FILE;
89  
90      static final String INVALID = INCLUSION_DIR_PREFIX + "invalid.xml";
91  
92      static final String INCLUDED_AS_RESOURCE = "asResource/joran/inclusion/includedAsResource.xml";
93  
94      int diff = RandomUtil.getPositiveInt();
95  
96  
97      @BeforeEach
98      public void setUp() throws Exception {
99          FileTestUtil.makeTestOutputDir();
100         HashMap<ElementSelector, Supplier<Action>> rulesMap = new HashMap<>();
101         rulesMap.put(new ElementSelector(CONFIGURATION_TAG), () -> new TopElementAction());
102         rulesMap.put(new ElementSelector(CONFIGURATION_TAG+"/include"), () -> new IncludeAction());
103         rulesMap.put(new ElementSelector(CONFIGURATION_TAG+"/stack"), () -> new StackAction());
104 
105         tc = new TrivialConfigurator(rulesMap) {
106 
107             @Override
108             protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
109                 defaultProcessor.addHandler(TopModel.class, NOPModelHandler::makeInstance);
110                 defaultProcessor.addHandler(IncludeModel.class, IncludeModelHandler::makeInstance);
111                 defaultProcessor.addHandler(StackModel.class, StackModelHandler::makeInstance);
112             }
113 
114             public void buildModelInterpretationContext() {
115                 super.buildModelInterpretationContext();
116                 this.modelInterpretationContext.setConfiguratorSupplier(  () -> this.makeAnotherInstance() );
117             }
118         };
119 
120         tc.setContext(context);
121         tc.getRuleStore().addPathPathMapping(INCLUDED_TAG, CONFIGURATION_TAG);
122     }
123 
124     @AfterEach
125     public void tearDown() throws Exception {
126         //StatusPrinter.printInCaseOfErrorsOrWarnings(context);
127         context = null;
128         System.clearProperty(INCLUDE_KEY);
129         System.clearProperty(SECOND_FILE_KEY);
130         System.clearProperty(SUB_FILE_KEY);
131         // StackAction.reset();
132     }
133 
134     @Test
135     public void basicFile() throws JoranException {
136         System.setProperty(INCLUDE_KEY, INCLUDED_FILE);
137         tc.doConfigure(TOP_BY_FILE);
138         //StatusPrinter.print(context);
139         verifyConfig(new String[] { "IA", "IB" });
140     }
141 
142     @Test
143     public void optionalFile() throws JoranException {
144         tc.doConfigure(TOP_OPTIONAL);
145         verifyConfig(new String[] { "IA", "IB" });
146         //StatusPrinter.print(context);
147     }
148 
149     @Test
150     public void optionalResource() throws JoranException {
151         tc.doConfigure(TOP_OPTIONAL_RESOURCE);
152         verifyConfig(new String[] { "IA", "IB" });
153         //StatusPrinter.print(context);
154         Assertions.assertEquals(Status.INFO, statusChecker.getHighestLevel(0));
155     }
156 
157     @Test
158     public void basicResource() throws JoranException {
159         System.setProperty(INCLUDE_KEY, INCLUDED_AS_RESOURCE);
160         tc.doConfigure(INCLUDE_BY_RESOURCE);
161         verifyConfig(new String[] { "AR_A", "AR_B" });
162     }
163 
164     @Test
165     public void basicURL() throws JoranException {
166         System.setProperty(INCLUDE_KEY, URL_TO_INCLUDE);
167         tc.doConfigure(TOP_BY_URL);
168         //StatusPrinter.print(context);
169         verifyConfig(new String[] { "IA", "IB" });
170     }
171 
172     @Test
173     public void noFileFound() throws JoranException {
174         System.setProperty(INCLUDE_KEY, "toto");
175         tc.doConfigure(TOP_BY_FILE);
176         Assertions.assertEquals(Status.WARN, statusChecker.getHighestLevel(0));
177     }
178 
179     @Test
180     public void withCorruptFile() throws JoranException, IOException {
181         String tmpOut = copyToTemp(INVALID);
182         System.setProperty(INCLUDE_KEY, tmpOut);
183         tc.doConfigure(TOP_BY_FILE);
184         Assertions.assertEquals(Status.ERROR, statusChecker.getHighestLevel(0));
185         //StatusPrinter.print(context);
186         Assertions.assertTrue(statusChecker.containsException(SAXParseException.class));
187 
188         // we like to erase the temp file in order to see
189         // if http://jira.qos.ch/browse/LBCORE-122 was fixed
190         File f = new File(tmpOut);
191         Assertions.assertTrue(f.exists());
192         Assertions.assertTrue(f.delete());
193 
194     }
195 
196     String copyToTemp(String in) throws IOException {
197         FileInputStream fis = new FileInputStream(in);
198         String out = CoreTestConstants.OUTPUT_DIR_PREFIX + "out" + diff;
199         FileOutputStream fos = new FileOutputStream(out);
200         int b;
201         while ((b = fis.read()) != -1) {
202             fos.write(b);
203         }
204         fis.close();
205         fos.close();
206         return out;
207     }
208 
209     @Test
210     public void malformedURL() throws JoranException {
211         String MALFORMED = "htp://logback.qos.ch";
212 
213         System.setProperty(INCLUDE_KEY, MALFORMED);
214         tc.doConfigure(TOP_BY_URL);
215         Assertions.assertEquals(Status.ERROR, statusChecker.getHighestLevel(0));
216         Assertions.assertTrue(statusChecker.containsException(MalformedURLException.class));
217     }
218 
219     @Test
220     public void unknownURL() throws JoranException {
221         System.setProperty(INCLUDE_KEY, "http://logback2345.qos.ch");
222         tc.doConfigure(TOP_BY_URL);
223         Assertions.assertEquals(Status.WARN, statusChecker.getHighestLevel(0));
224     }
225 
226     @Test
227     public void nestedInclude() throws JoranException {
228         System.setProperty(SUB_FILE_KEY, SUB_FILE);
229         System.setProperty(INCLUDE_KEY, INTERMEDIARY_FILE);
230         tc.doConfigure(TOP_BY_FILE);
231         Stack<String> expected = new Stack<String>();
232         expected.push("a");
233         expected.push("b");
234         expected.push("c");
235         @SuppressWarnings({ "unchecked", "rawtypes" })
236         Stack<String> aStack = (Stack) context.getObject(StackModelHandler.STACK_TEST);
237         Assertions.assertEquals(expected, aStack);
238     }
239 
240     @Test
241     public void multiInclude() throws JoranException {
242         System.setProperty(INCLUDE_KEY, INCLUDED_FILE);
243         System.setProperty(SECOND_FILE_KEY, SECOND_FILE);
244         tc.doConfigure(MULTI_INCLUDE_BY_FILE);
245         verifyConfig(new String[] { "IA", "IB", "SECOND" });
246     }
247 
248     // See LOGBACK-1465 - xxe vulnerability
249     @Test
250     public void includeAsEntity() throws JoranException {
251         tc.doConfigure(TOP_BY_ENTITY);
252         // when entity inclusion is enabled
253         // verifyConfig(new String[] { "EA", "EB" });
254 
255         // when entity inclusion disabled
256         verifyConfig(null);
257     }
258 
259     void verifyConfig(String[] expected) {
260         @SuppressWarnings({ "unchecked", "rawtypes" })
261         Stack<String> aStack = (Stack) context.getObject(StackModelHandler.STACK_TEST);
262 
263         if(expected == null) {
264             Assertions.assertNull(aStack);
265             return;
266         } 
267         
268         Stack<String> witness = new Stack<String>();
269         witness.addAll(Arrays.asList(expected));
270             
271         Assertions.assertEquals(witness, aStack);
272     }
273 
274 }