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.util;
15  
16  import static ch.qos.logback.core.subst.NodeToStringTransformer.CIRCULAR_VARIABLE_REFERENCE_DETECTED;
17  import static ch.qos.logback.core.subst.Parser.EXPECTING_DATA_AFTER_LEFT_ACCOLADE;
18  import static java.util.concurrent.TimeUnit.SECONDS;
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertThrows;
21  import static org.junit.jupiter.api.Assertions.fail;
22  
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import ch.qos.logback.core.testUtil.RandomUtil;
27  import org.junit.jupiter.api.BeforeEach;
28  import org.junit.jupiter.api.Disabled;
29  import org.junit.jupiter.api.Test;
30  
31  import ch.qos.logback.core.Context;
32  import ch.qos.logback.core.ContextBase;
33  import ch.qos.logback.core.joran.spi.JoranException;
34  import ch.qos.logback.core.spi.ScanException;
35  import org.junit.jupiter.api.Timeout;
36  
37  public class OptionHelperTest {
38  
39      String text = "Testing ${v1} variable substitution ${v2}";
40      String expected = "Testing if variable substitution works";
41      Context context = new ContextBase();
42      Map<String, String> secondaryMap;
43  
44      int diff = RandomUtil.getPositiveInt();
45  
46      @BeforeEach
47      public void setUp() throws Exception {
48          secondaryMap = new HashMap<String, String>();
49      }
50  
51      @Test
52      public void testLiteral() throws ScanException {
53          String noSubst = "hello world";
54          String result = OptionHelper.substVars(noSubst, context);
55          assertEquals(noSubst, result);
56      }
57  
58      @Test
59      public void testUndefinedValues() throws ScanException {
60          String withUndefinedValues = "${axyz}";
61  
62          String result = OptionHelper.substVars(withUndefinedValues, context);
63          assertEquals("axyz" + OptionHelper._IS_UNDEFINED, result);
64      }
65  
66      @Test
67      public void testSubstVarsVariableNotClosed() throws ScanException {
68          String noSubst = "testing if ${v1 works";
69  
70          try {
71              @SuppressWarnings("unused")
72              String result = OptionHelper.substVars(noSubst, context);
73              fail();
74          } catch (IllegalArgumentException e) {
75              // ok
76          }
77      }
78  
79      @Test
80      public void testSubstVarsContextOnly() throws ScanException {
81          context.putProperty("v1", "if");
82          context.putProperty("v2", "works");
83  
84          String result = OptionHelper.substVars(text, context);
85          assertEquals(expected, result);
86      }
87  
88      @Test
89      public void testSubstVarsSystemProperties() throws ScanException {
90          System.setProperty("v1", "if");
91          System.setProperty("v2", "works");
92  
93          String result = OptionHelper.substVars(text, context);
94          assertEquals(expected, result);
95  
96          System.clearProperty("v1");
97          System.clearProperty("v2");
98      }
99  
100     @Test
101     public void testSubstVarsWithDefault() throws ScanException {
102         context.putProperty("v1", "if");
103         String textWithDefault = "Testing ${v1} variable substitution ${v2:-toto}";
104         String resultWithDefault = "Testing if variable substitution toto";
105 
106         String result = OptionHelper.substVars(textWithDefault, context);
107         assertEquals(resultWithDefault, result);
108     }
109 
110     @Test
111     public void testSubstVarsRecursive() throws ScanException {
112         context.putProperty("v1", "if");
113         context.putProperty("v2", "${v3}");
114         context.putProperty("v3", "works");
115 
116         String result = OptionHelper.substVars(text, context);
117         assertEquals(expected, result);
118     }
119 
120     @Test
121     public void testSubstVarsTwoLevelsDeep() throws ScanException {
122         context.putProperty("v1", "if");
123         context.putProperty("v2", "${v3}");
124         context.putProperty("v3", "${v4}");
125         context.putProperty("v4", "works");
126 
127         String result = OptionHelper.substVars(text, context);
128         assertEquals(expected, result);
129     }
130 
131     @Test
132     public void testSubstVarsTwoLevelsWithDefault() throws ScanException {
133         // Example input taken from LOGBCK-943 bug report
134         context.putProperty("APP_NAME", "LOGBACK");
135         context.putProperty("ARCHIVE_SUFFIX", "archive.log");
136         context.putProperty("LOG_HOME", "${logfilepath.default:-logs}");
137         context.putProperty("ARCHIVE_PATH", "${LOG_HOME}/archive/${APP_NAME}");
138 
139         String result = OptionHelper.substVars("${ARCHIVE_PATH}_trace_${ARCHIVE_SUFFIX}", context);
140         assertEquals("logs/archive/LOGBACK_trace_archive.log", result);
141     }
142 
143     @Test
144     @Timeout(value = 1, unit = SECONDS)
145     public void stubstVarsShouldNotGoIntoInfiniteLoop() throws ScanException {
146         context.putProperty("v1", "if");
147         context.putProperty("v2", "${v3}");
148         context.putProperty("v3", "${v4}");
149         context.putProperty("v4", "${v2}c");
150 
151         Exception e = assertThrows(Exception.class, () -> {
152             OptionHelper.substVars(text, context);
153         });
154         String expectedMessage =  CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${v2} --> ${v3} --> ${v4} --> ${v2}]";
155         assertEquals(expectedMessage, e.getMessage());
156     }
157     
158     @Test
159     public void nonCircularGraphShouldWork() throws ScanException {
160         context.putProperty("A", "${B} and ${C}");
161         context.putProperty("B", "${B1}");
162         context.putProperty("B1", "B1-value");
163         context.putProperty("C", "${C1} and ${B}");
164         context.putProperty("C1", "C1-value");
165 
166         String result = OptionHelper.substVars("${A}", context);
167         assertEquals("B1-value and C1-value and B1-value", result);
168     }
169 
170     @Test
171     @Timeout(value = 1, unit = SECONDS)
172     public void detectCircularReferences0() throws ScanException {
173         context.putProperty("A", "${A}");
174         Exception e = assertThrows(IllegalArgumentException.class, () -> {
175            OptionHelper.substVars("${A}", context);
176         });
177         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${A} --> ${A}]";
178         assertEquals(expectedMessage, e.getMessage());
179     }
180 
181     @Test
182     @Timeout(value = 1, unit = SECONDS)
183     public void detectCircularReferences1() throws ScanException {
184         context.putProperty("A", "${A}a");
185 
186         Exception e = assertThrows(IllegalArgumentException.class, () -> {
187             OptionHelper.substVars("${A}", context);
188         });
189         
190         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${A} --> ${A}]";
191         assertEquals(expectedMessage, e.getMessage());
192     }
193 
194     @Test
195     @Timeout(value = 1, unit = SECONDS)
196     public void detectCircularReferences2() throws ScanException {
197         context.putProperty("A", "${B}");
198         context.putProperty("B", "${C}");
199         context.putProperty("C", "${A}");
200 
201         Exception e = assertThrows(IllegalArgumentException.class, () -> {
202            OptionHelper.substVars("${A}", context);
203         });
204         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${A} --> ${B} --> ${C} --> ${A}]";
205         assertEquals(expectedMessage, e.getMessage());
206     }
207 
208     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46755
209     @Test
210     public void recursionErrorWithNullLiteralPayload() throws ScanException {
211 
212         Exception e = assertThrows(IllegalArgumentException.class, () -> {
213            OptionHelper.substVars("abc${AA$AA${}}}xyz", context);
214         });
215         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${AA} --> ${}]";
216         assertEquals(expectedMessage, e.getMessage());
217     }
218 
219     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46892
220     @Test
221     public void leftAccoladeFollowedByDefaultStateWithNoLiteral() throws ScanException {
222         Exception e = assertThrows(ScanException.class, () -> {
223             OptionHelper.substVars("x{:-a}", context);
224         });
225         String expectedMessage = EXPECTING_DATA_AFTER_LEFT_ACCOLADE;
226         assertEquals(expectedMessage, e.getMessage());
227     }
228 
229     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46966
230     @Test
231     public void nestedEmptyVariables() throws ScanException {
232 
233         Exception e = assertThrows(Exception.class, () -> {
234             OptionHelper.substVars("${${${}}}", context);
235         });
236         String expectedMessage =  CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${ ?  ? } --> ${ ? } --> ${}]";
237         assertEquals(expectedMessage, e.getMessage());
238     }
239     
240     
241     
242     @Test
243     public void detectCircularReferencesInDefault() throws ScanException {
244         context.putProperty("A", "${B:-${A}}");
245      
246 
247         Exception e = assertThrows(IllegalArgumentException.class, () -> {
248             OptionHelper.substVars("${A}", context);
249         });
250 
251         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${A} --> ${B} --> ${A}]";
252         assertEquals(expectedMessage, e.getMessage());
253     }
254 
255     @Test
256     @Timeout(value = 1, unit = SECONDS)
257     public void detectCircularReferences3() throws ScanException {
258         context.putProperty("A", "${B}");
259         context.putProperty("B", "${C}");
260         context.putProperty("C", "${A}");
261 
262         Exception e = assertThrows(IllegalArgumentException.class, () -> {
263             OptionHelper.substVars("${B} ", context);
264         });
265         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED + "${B} --> ${C} --> ${A} --> ${B}]";
266         assertEquals(expectedMessage, e.getMessage());
267 
268     }
269 
270     @Test
271     @Timeout(value = 1, unit = SECONDS)
272     public void detectCircularReferences4() throws ScanException {
273         context.putProperty("A", "${B}");
274         context.putProperty("B", "${C}");
275         context.putProperty("C", "${A}");
276 
277         
278         Exception e = assertThrows(IllegalArgumentException.class, () -> {
279             OptionHelper.substVars("${C} and ${A}", context);
280         });
281         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${C} --> ${A} --> ${B} --> ${C}]";
282         assertEquals(expectedMessage, e.getMessage());
283     }
284 
285     @Test
286     public void detectCircularReferences5() throws ScanException {
287         context.putProperty("A", "${B} and ${C}");
288         context.putProperty("B", "${B1}");
289         context.putProperty("B1", "B1-value");
290         context.putProperty("C", "${C1}");
291         context.putProperty("C1", "here's the loop: ${A}");
292 
293         
294         Exception e = assertThrows(IllegalArgumentException.class, () -> {
295             OptionHelper.substVars("${A}", context);
296         });
297         String expectedMessage = CIRCULAR_VARIABLE_REFERENCE_DETECTED+"${A} --> ${C} --> ${C1} --> ${A}]";
298         assertEquals(expectedMessage, e.getMessage());
299     }
300 
301     @Test
302     public void defaultValueReferencingAVariable() throws ScanException {
303         context.putProperty("v1", "k1");
304         String result = OptionHelper.substVars("${undef:-${v1}}", context);
305         assertEquals("k1", result);
306     }
307 
308     @Test
309     public void jackrabbit_standalone() throws ScanException {
310         String r = OptionHelper.substVars("${jackrabbit.log:-${repo:-jackrabbit}/log/jackrabbit.log}", context);
311         assertEquals("jackrabbit/log/jackrabbit.log", r);
312     }
313 
314     @Test
315     public void emptyVariableIsAccepted() throws JoranException, ScanException {
316         String varName = "var"+diff;
317         context.putProperty(varName, "");
318         String r = OptionHelper.substVars("x ${"+varName+"} b", context);
319         assertEquals("x  b", r);
320     }
321 
322     // https://jira.qos.ch/browse/LOGBACK-1012
323     // conflicts with the idea that variables assigned the empty string are valid
324     @Disabled
325     @Test
326     public void defaultExpansionForEmptyVariables() throws JoranException, ScanException {
327         String varName = "var"+diff;
328         context.putProperty(varName, "");
329 
330         String r = OptionHelper.substVars("x ${"+varName+":-def} b", context);
331         assertEquals("x def b", r);
332     }
333 
334     @Test
335     public void emptyDefault() throws ScanException {
336         String r = OptionHelper.substVars("a${undefinedX:-}b", context);
337         assertEquals("ab", r);
338     }
339 
340     @Test
341     public void openBraceAsLastCharacter() throws JoranException, ScanException {
342         Exception e = assertThrows(IllegalArgumentException.class, () -> {
343             OptionHelper.substVars("a{a{", context);
344         });
345         String expectedMessage = "All tokens consumed but was expecting \"}\"";
346         assertEquals(expectedMessage, e.getMessage());
347     }
348 
349     
350     @Test
351     public void trailingColon_LOGBACK_1140() throws ScanException {
352         String prefix = "c:";
353         String suffix = "/tmp";
354         context.putProperty("var", prefix);
355         String r = OptionHelper.substVars("${var}" + suffix, context);
356         assertEquals(prefix + suffix, r);
357     }
358 
359 
360 
361 
362     @Test
363     public void curlyBraces_LOGBACK_1101() throws ScanException {
364         {
365             String input = "foo{bar}";
366             String r = OptionHelper.substVars(input, context);
367             assertEquals(input, r);
368         }
369         {
370             String input = "{foo{\"bar\"}}";
371             String r = OptionHelper.substVars(input, context);
372             assertEquals(input, r);
373         }
374         {
375             String input = "a:{y}";
376             String r = OptionHelper.substVars(input, context);
377             assertEquals(input, r);
378         }
379         {
380             String input = "{world:{yay}}";
381             String r = OptionHelper.substVars(input, context);
382             assertEquals(input, r);
383         }
384         {
385             String input = "{hello:{world:yay}}";
386             String r = OptionHelper.substVars(input, context);
387             assertEquals(input, r);
388         }
389         {
390             String input = "{\"hello\":{\"world\":\"yay\"}}";
391             String r = OptionHelper.substVars(input, context);
392             assertEquals(input, r);
393         }
394     }
395 }