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 }