001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, 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 v2.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 */
014
015package ch.qos.logback.core.model.util;
016
017import ch.qos.logback.core.Context;
018import ch.qos.logback.core.spi.ContextAwareBase;
019import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
020import ch.qos.logback.core.spi.ScanException;
021import ch.qos.logback.core.util.OptionHelper;
022
023import java.util.HashMap;
024import java.util.Map;
025
026/**
027 * Helper methods to deal with properties.
028 *
029 * <p>This class acts as a small container for substitution properties and
030 * delegates actual variable substitution to {@link OptionHelper#substVars}.
031 * It also offers a convenience method to mask confidential property values
032 * (for example passwords) by returning a blurred placeholder.</p>
033 *
034 * @since 1.5.1
035 */
036public class VariableSubstitutionsHelper extends ContextAwareBase implements ContextAwarePropertyContainer {
037
038    static final String PASSWORD = "password";
039    static final String SECRET = "secret";
040    static final String CONFIDENTIAL = "confidential";
041
042    static final String BLURRED_STR = "******";
043
044    protected Map<String, String> propertiesMap;
045
046    /**
047     * Create a helper backed by an empty property map.
048     *
049     * @param context the logback context to associate with this helper; may be null
050     */
051    public VariableSubstitutionsHelper(Context context) {
052        this.setContext(context);
053        this.propertiesMap = new HashMap<>();
054    }
055
056    /**
057     * Create a helper pre-populated with the contents of {@code otherMap}.
058     * The provided map is copied and further modifications do not affect the
059     * original map.
060     *
061     * @param context the logback context to associate with this helper; may be null
062     * @param otherMap initial properties to copy; if null an empty map is created
063     */
064    public VariableSubstitutionsHelper(Context context, Map<String, String> otherMap) {
065        this.setContext(context);
066        this.propertiesMap = new HashMap<>(otherMap);
067    }
068
069    /**
070     * Perform variable substitution on the provided reference string.
071     *
072     * <p>Returns {@code null} if {@code ref} is {@code null}. On parse errors
073     * the original input string is returned and an error is logged.</p>
074     *
075     * @param ref the string possibly containing variables to substitute
076     * @return the string with substitutions applied, or {@code null} if {@code ref} was {@code null}
077     */
078    @Override
079    public String subst(String ref) {
080        if (ref == null) {
081            return null;
082        }
083
084        try {
085            return OptionHelper.substVars(ref, this, context);
086        } catch (ScanException | IllegalArgumentException e) {
087            addError("Problem while parsing [" + ref + "]", e);
088            return ref;
089        }
090    }
091
092    /**
093     * Return a blurred placeholder for confidential properties.
094     *
095     * <p>If the property name {@code ref} contains any of the case-insensitive
096     * substrings {@code "password"}, {@code "secret"} or {@code "confidential"}
097     * this method returns a fixed blurred string ("******"). Otherwise, the
098     * supplied {@code substituted} value is returned unchanged.</p>
099     *
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}