1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, 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 v2.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  
15  package ch.qos.logback.core.model.util;
16  
17  import ch.qos.logback.core.Context;
18  import ch.qos.logback.core.spi.ContextAwareBase;
19  import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
20  import ch.qos.logback.core.spi.ScanException;
21  import ch.qos.logback.core.util.OptionHelper;
22  
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  /**
27   * Helper methods to deal with properties.
28   *
29   * <p>This class acts as a small container for substitution properties and
30   * delegates actual variable substitution to {@link OptionHelper#substVars}.
31   * It also offers a convenience method to mask confidential property values
32   * (for example passwords) by returning a blurred placeholder.</p>
33   *
34   * @since 1.5.1
35   */
36  public class VariableSubstitutionsHelper extends ContextAwareBase implements ContextAwarePropertyContainer {
37  
38      static final String PASSWORD = "password";
39      static final String SECRET = "secret";
40      static final String CONFIDENTIAL = "confidential";
41  
42      static final String BLURRED_STR = "******";
43  
44      protected Map<String, String> propertiesMap;
45  
46      /**
47       * Create a helper backed by an empty property map.
48       *
49       * @param context the logback context to associate with this helper; may be null
50       */
51      public VariableSubstitutionsHelper(Context context) {
52          this.setContext(context);
53          this.propertiesMap = new HashMap<>();
54      }
55  
56      /**
57       * Create a helper pre-populated with the contents of {@code otherMap}.
58       * The provided map is copied and further modifications do not affect the
59       * original map.
60       *
61       * @param context the logback context to associate with this helper; may be null
62       * @param otherMap initial properties to copy; if null an empty map is created
63       */
64      public VariableSubstitutionsHelper(Context context, Map<String, String> otherMap) {
65          this.setContext(context);
66          this.propertiesMap = new HashMap<>(otherMap);
67      }
68  
69      /**
70       * Perform variable substitution on the provided reference string.
71       *
72       * <p>Returns {@code null} if {@code ref} is {@code null}. On parse errors
73       * the original input string is returned and an error is logged.</p>
74       *
75       * @param ref the string possibly containing variables to substitute
76       * @return the string with substitutions applied, or {@code null} if {@code ref} was {@code null}
77       */
78      @Override
79      public String subst(String ref) {
80          if (ref == null) {
81              return null;
82          }
83  
84          try {
85              return OptionHelper.substVars(ref, this, context);
86          } catch (ScanException | IllegalArgumentException e) {
87              addError("Problem while parsing [" + ref + "]", e);
88              return ref;
89          }
90      }
91  
92      /**
93       * Return a blurred placeholder for confidential properties.
94       *
95       * <p>If the property name {@code ref} contains any of the case-insensitive
96       * substrings {@code "password"}, {@code "secret"} or {@code "confidential"}
97       * this method returns a fixed blurred string ("******"). Otherwise, the
98       * supplied {@code substituted} value is returned unchanged.</p>
99       *
100      * @param ref the property name to inspect; must not be {@code null}
101      * @param substituted the substituted value to return when the property is not confidential
102      * @return a blurred placeholder when the property appears confidential, otherwise {@code substituted}
103      * @throws IllegalArgumentException when {@code ref} is {@code null}
104      */
105     public String sanitizeIfConfidential(String ref, String substituted) {
106         if(ref == null) {
107             throw new IllegalArgumentException("ref cannot be null");
108         }
109 
110         String lowerCaseRef = ref.toLowerCase();
111 
112         if(lowerCaseRef.contains(PASSWORD) || lowerCaseRef.contains(SECRET) || lowerCaseRef.contains(CONFIDENTIAL)) {
113             return BLURRED_STR;
114         } else
115             return substituted;
116     }
117 
118     /**
119      * Add or overwrite a substitution property.
120      *
121      * <p>Null keys or values are ignored. Values are trimmed before storing
122      * to avoid surprises caused by leading or trailing whitespace.</p>
123      *
124      * @param key the property name; ignored if {@code null}
125      * @param value the property value; ignored if {@code null}
126      */
127     @Override
128     public void addSubstitutionProperty(String key, String value) {
129         if (key == null || value == null) {
130             return;
131         }
132         // values with leading or trailing spaces are bad. We remove them now.
133         value = value.trim();
134         propertiesMap.put(key, value);
135     }
136 
137     /**
138      * Retrieve a property value by name.
139      *
140      * @param key the property name
141      * @return the property value or {@code null} if not present
142      */
143     @Override
144     public String getProperty(String key) {
145         return propertiesMap.get(key);
146     }
147 
148     /**
149      * Return a shallow copy of the internal property map.
150      *
151      * <p>The returned map is a copy and modifications to it do not affect the
152      * internal state of this helper.</p>
153      *
154      * @return a copy of the property map
155      */
156     @Override
157     public Map<String, String> getCopyOfPropertyMap() {
158         return new HashMap<>(propertiesMap);
159     }
160 }