2929import java .util .regex .MatchResult ;
3030import java .util .regex .Matcher ;
3131import java .util .regex .Pattern ;
32+ import javax .annotation .Nullable ;
3233import org .snakeyaml .engine .v2 .api .Load ;
3334import org .snakeyaml .engine .v2 .api .LoadSettings ;
3435import org .snakeyaml .engine .v2 .common .ScalarStyle ;
@@ -51,7 +52,7 @@ public final class FileConfiguration {
5152
5253 private static final Logger logger = Logger .getLogger (FileConfiguration .class .getName ());
5354 private static final Pattern ENV_VARIABLE_REFERENCE =
54- Pattern .compile ("\\ ${1,2} \\ {([a-zA-Z_][a-zA-Z0-9_]*)(:-([^\n }]*))?}" );
55+ Pattern .compile ("\\ $\\ {([a-zA-Z_][a-zA-Z0-9_]*)(:-([^\n }]*))?}" );
5556 private static final ComponentLoader DEFAULT_COMPONENT_LOADER =
5657 SpiHelper .serviceComponentLoader (FileConfiguration .class .getClassLoader ());
5758
@@ -297,6 +298,10 @@ protected Map<Object, Object> constructMapping(MappingNode node) {
297298 return mapping ;
298299 }
299300
301+ private static final String ESCAPE_SEQUENCE = "$$" ;
302+ private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE .length ();
303+ private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$' ;
304+
300305 private Object constructValueObject (Node node ) {
301306 Object value = constructObject (node );
302307 if (!(node instanceof ScalarNode )) {
@@ -307,44 +312,69 @@ private Object constructValueObject(Node node) {
307312 }
308313
309314 String val = (String ) value ;
315+ ScalarStyle scalarStyle = ((ScalarNode ) node ).getScalarStyle ();
316+
317+ // Iterate through val left to right, search for escape sequence "$$"
318+ // For the substring of val between the last escape sequence and the next found, perform
319+ // environment variable substitution
320+ // Add the escape replacement character '$' in place of each escape sequence found
321+
322+ int lastEscapeIndexEnd = 0 ;
323+ StringBuilder newVal = null ;
324+ while (true ) {
325+ int escapeIndex = val .indexOf (ESCAPE_SEQUENCE , lastEscapeIndexEnd );
326+ int substitutionEndIndex = escapeIndex == -1 ? val .length () : escapeIndex ;
327+ newVal = envVarSubstitution (newVal , val , lastEscapeIndexEnd , substitutionEndIndex );
328+ if (escapeIndex == -1 ) {
329+ break ;
330+ } else {
331+ newVal .append (ESCAPE_SEQUENCE_REPLACEMENT );
332+ }
333+ lastEscapeIndexEnd = escapeIndex + ESCAPE_SEQUENCE_LENGTH ;
334+ if (lastEscapeIndexEnd >= val .length ()) {
335+ break ;
336+ }
337+ }
338+
339+ // If the value was double quoted, retain the double quotes so we don't change a value
340+ // intended to be a string to a different type after environment variable substitution
341+ if (scalarStyle == ScalarStyle .DOUBLE_QUOTED ) {
342+ newVal .insert (0 , "\" " );
343+ newVal .append ("\" " );
344+ }
345+ return load .loadFromString (newVal .toString ());
346+ }
347+
348+ private StringBuilder envVarSubstitution (
349+ @ Nullable StringBuilder newVal , String source , int startIndex , int endIndex ) {
350+ String val = source .substring (startIndex , endIndex );
310351 Matcher matcher = ENV_VARIABLE_REFERENCE .matcher (val );
352+
311353 if (!matcher .find ()) {
312- return value ;
354+ return newVal == null ? new StringBuilder (val ) : newVal .append (val );
355+ }
356+
357+ if (newVal == null ) {
358+ newVal = new StringBuilder ();
313359 }
314360
315361 int offset = 0 ;
316- StringBuilder newVal = new StringBuilder ();
317- ScalarStyle scalarStyle = ((ScalarNode ) node ).getScalarStyle ();
318362 do {
319363 MatchResult matchResult = matcher .toMatchResult ();
320- newVal .append (val , offset , matchResult .start ());
321-
322- String ref = val .substring (matchResult .start (), matchResult .end ());
323- // $$ indicates that env var reference is escaped. Strip the leading $.
324- if (ref .startsWith ("$$" )) {
325- newVal .append (ref .substring (1 ));
326- } else {
327- String envVarKey = matcher .group (1 );
328- String defaultValue = matcher .group (3 );
329- if (defaultValue == null ) {
330- defaultValue = "" ;
331- }
332- String replacement = environmentVariables .getOrDefault (envVarKey , defaultValue );
333- newVal .append (replacement );
364+ String envVarKey = matcher .group (1 );
365+ String defaultValue = matcher .group (3 );
366+ if (defaultValue == null ) {
367+ defaultValue = "" ;
334368 }
335-
369+ String replacement = environmentVariables .getOrDefault (envVarKey , defaultValue );
370+ newVal .append (val , offset , matchResult .start ()).append (replacement );
336371 offset = matchResult .end ();
337372 } while (matcher .find ());
338373 if (offset != val .length ()) {
339374 newVal .append (val , offset , val .length ());
340375 }
341- // If the value was double quoted, retain the double quotes so we don't change a value
342- // intended to be a string to a different type after environment variable substitution
343- if (scalarStyle == ScalarStyle .DOUBLE_QUOTED ) {
344- newVal .insert (0 , "\" " );
345- newVal .append ("\" " );
346- }
347- return load .loadFromString (newVal .toString ());
376+
377+ return newVal ;
348378 }
349379 }
350380}
0 commit comments