feat(java): dereference all maven settings.xml env placeholders (#9024)

This commit is contained in:
kennyk
2025-06-20 13:39:23 +02:00
committed by GitHub
parent 99c5151d6e
commit 5aade698c7
7 changed files with 131 additions and 66 deletions

View File

@@ -2,9 +2,6 @@ package pom
import (
"fmt"
"os"
"regexp"
"slices"
"strings"
"sync"
@@ -17,7 +14,6 @@ import (
)
var (
varRegexp = regexp.MustCompile(`\${(\S+?)}`)
emptyVersionWarn = sync.OnceFunc(func() {
log.WithPrefix("pom").Warn("Dependency version cannot be determined. Child dependencies will not be found.",
// e.g. https://trivy.dev/latest/docs/coverage/language/java/#empty-dependency-version
@@ -133,44 +129,3 @@ func (v1 version) shouldOverride(v2 version) bool {
func (v1 version) String() string {
return v1.ver
}
func evaluateVariable(s string, props map[string]string, seenProps []string) string {
if props == nil {
props = make(map[string]string)
}
for _, m := range varRegexp.FindAllStringSubmatch(s, -1) {
var newValue string
// env.X: https://maven.apache.org/pom.html#Properties
// e.g. env.PATH
if strings.HasPrefix(m[1], "env.") {
newValue = os.Getenv(strings.TrimPrefix(m[1], "env."))
} else {
// <properties> might include another property.
// e.g. <animal.sniffer.skip>${skipTests}</animal.sniffer.skip>
ss, ok := props[m[1]]
if ok {
// search for looped properties
if slices.Contains(seenProps, ss) {
printLoopedPropertiesStack(m[0], seenProps)
return ""
}
seenProps = append(seenProps, ss) // save evaluated props to check if we get this prop again
newValue = evaluateVariable(ss, props, seenProps)
seenProps = []string{} // clear props if we returned from recursive. Required for correct work with 2 same props like ${foo}-${foo}
}
}
s = strings.ReplaceAll(s, m[0], newValue)
}
return strings.TrimSpace(s)
}
func printLoopedPropertiesStack(env string, usedProps []string) {
var s string
for _, prop := range usedProps {
s += fmt.Sprintf("%s -> ", prop)
}
log.Warn("Lopped properties were detected", log.String("prop", s+env))
}

View File

@@ -832,3 +832,11 @@ func packageID(name, version string) string {
func isSnapshot(ver string) bool {
return strings.HasSuffix(ver, "SNAPSHOT") || ver == "LATEST"
}
func isDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}
return fileInfo.IsDir(), err
}

View File

@@ -76,5 +76,17 @@ func openSettings(filePath string) (settings, error) {
if err = decoder.Decode(&s); err != nil {
return settings{}, err
}
expandAllEnvPlaceholders(&s)
return s, nil
}
func expandAllEnvPlaceholders(s *settings) {
s.LocalRepository = evaluateVariable(s.LocalRepository, nil, nil)
for i, server := range s.Servers {
s.Servers[i].ID = evaluateVariable(server.ID, nil, nil)
s.Servers[i].Username = evaluateVariable(server.Username, nil, nil)
s.Servers[i].Password = evaluateVariable(server.Password, nil, nil)
}
}

View File

@@ -122,6 +122,35 @@ func Test_ReadSettings(t *testing.T) {
},
wantSettings: settings{},
},
{
name: "environment placeholders are dereferenced",
envs: map[string]string{
"HOME": filepath.Join("testdata", "settings", "user-settings-with-env-placeholders"),
"MAVEN_HOME": "NOT_EXISTING_PATH",
"LOCAL_REPO_PT_1": "part1",
"LOCAL_REPO_PT_2": "part2",
"SERVER_ID": "server-id-from-env",
"USERNAME": "username-from-env",
"PASSWORD": "password-from-env",
},
wantSettings: settings{
LocalRepository: "part1/part2/.m2/repository",
Servers: []Server{
{
ID: "user-server",
},
{
ID: "server-id-from-env",
Username: "username-from-env",
Password: "password-from-env",
},
{
ID: "server-with-name-only",
Username: "test-user-only",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>${env.LOCAL_REPO_PT_1}/${env.LOCAL_REPO_PT_2}/.m2/repository</localRepository>
<servers>
<server>
<id>user-server</id>
</server>
<server>
<id>${env.SERVER_ID}</id>
<username>${env.USERNAME}</username>
<password>${env.PASSWORD}</password>
</server>
<server>
<id>server-with-name-only</id>
<username>test-user-only</username>
</server>
</servers>
</settings>

View File

@@ -1,21 +0,0 @@
package pom
import (
"os"
"strings"
)
func isDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}
return fileInfo.IsDir(), err
}
func isProperty(version string) bool {
if version != "" && strings.HasPrefix(version, "${") && strings.HasSuffix(version, "}") {
return true
}
return false
}

View File

@@ -0,0 +1,61 @@
package pom
import (
"fmt"
"os"
"regexp"
"slices"
"strings"
"github.com/aquasecurity/trivy/pkg/log"
)
var varRegexp = regexp.MustCompile(`\${(\S+?)}`)
func isProperty(s string) bool {
if s != "" && strings.HasPrefix(s, "${") && strings.HasSuffix(s, "}") {
return true
}
return false
}
func evaluateVariable(s string, props map[string]string, seenProps []string) string {
if props == nil {
props = make(map[string]string)
}
for _, m := range varRegexp.FindAllStringSubmatch(s, -1) {
var newValue string
// env.X: https://maven.apache.org/pom.html#Properties
// e.g. env.PATH
if strings.HasPrefix(m[1], "env.") {
newValue = os.Getenv(strings.TrimPrefix(m[1], "env."))
} else {
// <properties> might include another property.
// e.g. <animal.sniffer.skip>${skipTests}</animal.sniffer.skip>
ss, ok := props[m[1]]
if ok {
// search for looped properties
if slices.Contains(seenProps, ss) {
printLoopedPropertiesStack(m[0], seenProps)
return ""
}
seenProps = append(seenProps, ss) // save evaluated props to check if we get this prop again
newValue = evaluateVariable(ss, props, seenProps)
seenProps = []string{} // clear props if we returned from recursive. Required for correct work with 2 same props like ${foo}-${foo}
}
}
s = strings.ReplaceAll(s, m[0], newValue)
}
return strings.TrimSpace(s)
}
func printLoopedPropertiesStack(env string, usedProps []string) {
var s string
for _, prop := range usedProps {
s += fmt.Sprintf("%s -> ", prop)
}
log.Warn("Lopped properties were detected", log.String("prop", s+env))
}