@@ -20,6 +20,7 @@ class PropertyValueFinder
2020 require_relative "pom_fetcher"
2121
2222 DOT_SEPARATOR_REGEX = %r{\. (?!\d +([.\/ _\- ]|$)+)}
23+ MAVEN_PROPERTY_REGEX = /\$ \{ .+?/
2324
2425 sig do
2526 params (
@@ -37,10 +38,15 @@ def initialize(dependency_files:, credentials: [])
3738 )
3839 end
3940
41+ # rubocop:disable Metrics/PerceivedComplexity
4042 sig do
41- params ( property_name : String , callsite_pom : DependencyFile ) . returns ( T . nilable ( T ::Hash [ Symbol , T . untyped ] ) )
43+ params (
44+ property_name : String ,
45+ callsite_pom : DependencyFile ,
46+ seen_properties : T ::Set [ String ]
47+ ) . returns ( T . nilable ( T ::Hash [ Symbol , T . untyped ] ) )
4248 end
43- def property_details ( property_name :, callsite_pom :)
49+ def property_details ( property_name :, callsite_pom :, seen_properties : Set . new )
4450 pom = callsite_pom
4551 doc = Nokogiri ::XML ( pom . content )
4652 doc . remove_namespaces!
@@ -63,53 +69,66 @@ def property_details(property_name:, callsite_pom:)
6369 raise DependencyFileNotEvaluatable , e . message
6470 end
6571
66- # and value is an expression
67- if node && /\$ \{ (?<expression>.+)\} / . match ( node . content . strip )
68- return extract_value_from_expression (
69- expression : node . content . strip ,
72+ if node . nil? && parent_pom ( pom )
73+ return property_details (
7074 property_name : property_name ,
71- callsite_pom : callsite_pom
75+ callsite_pom : T . must ( parent_pom ( pom ) ) ,
76+ seen_properties : seen_properties
7277 )
7378 end
79+ return nil unless node
7480
75- # If we found a property, return it
76- return { file : pom . name , node : node , value : node . content . strip } if node
81+ content = node . content . strip
7782
78- # Otherwise, look for a value in this pom's parent
79- return unless ( parent = parent_pom ( pom ) )
83+ # Detect infinite recursion such as ${property1} where property1=${property1}
84+ if seen_properties . include? ( property_name )
85+ raise Dependabot ::DependencyFileNotParseable . new (
86+ callsite_pom . name ,
87+ "Error trying to resolve recursive expression '${#{ property_name } }'."
88+ )
89+ end
8090
81- property_details (
82- property_name : property_name ,
83- callsite_pom : parent
84- )
91+ seen_properties << property_name
92+
93+ # If the content has no placeholders, return it as-is
94+ return { file : pom . name , node : node , value : content } unless content . match? ( MAVEN_PROPERTY_REGEX )
95+
96+ resolve_property_placeholder ( content , callsite_pom , pom , node , seen_properties )
8597 end
98+ # rubocop:enable Metrics/PerceivedComplexity
8699
87100 private
88101
89- sig { returns ( T ::Array [ DependencyFile ] ) }
90- attr_reader :dependency_files
91-
102+ # Extract property placeholders from a string and resolve them
103+ # These properties can be simple properties such as ${project.version}
104+ # or more complex such as ${my.property.${other.property}} or constant.${property}
105+ # See https://maven.apache.org/pom.html#properties
92106 sig do
93107 params (
94- expression : String ,
95- property_name : String ,
96- callsite_pom : DependencyFile
97- )
98- . returns ( T . nilable ( T ::Hash [ Symbol , String ] ) )
108+ content : String ,
109+ callsite_pom : DependencyFile ,
110+ pom : DependencyFile ,
111+ node : T . untyped ,
112+ seen_properties : T ::Set [ String ]
113+ ) . returns ( T . nilable ( T ::Hash [ Symbol , T . untyped ] ) )
99114 end
100- def extract_value_from_expression ( expression :, property_name :, callsite_pom :)
101- # and the expression is pointing to self then raise the error
102- if expression . eql? ( "${#{ property_name } }" )
103- raise Dependabot ::DependencyFileNotParseable . new (
104- callsite_pom . name ,
105- "Error trying to resolve recursive expression '#{ expression } '."
115+ def resolve_property_placeholder ( content , callsite_pom , pom , node , seen_properties )
116+ resolved_value = content . gsub ( /\$ \{ (.+?)}/ ) do
117+ inner_name = Regexp . last_match ( 1 )
118+ resolved = property_details (
119+ property_name : T . must ( inner_name ) ,
120+ callsite_pom : callsite_pom ,
121+ seen_properties : seen_properties
106122 )
123+ T . must ( resolved ) [ :value ]
107124 end
108125
109- # and the expression is pointing to another tag, then get the value of that tag
110- property_details ( property_name : T . must ( expression . slice ( 2 ..-2 ) ) , callsite_pom : callsite_pom )
126+ { file : pom . name , node : node , value : resolved_value }
111127 end
112128
129+ sig { returns ( T ::Array [ DependencyFile ] ) }
130+ attr_reader :dependency_files
131+
113132 sig { params ( property_name : String ) . returns ( String ) }
114133 def sanitize_property_name ( property_name )
115134 property_name . sub ( /^pom\. / , "" ) . sub ( /^project\. / , "" )
0 commit comments