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}