Skip to content

Commit 350fe33

Browse files
feat(java): add support for proxy configuration from Maven settings.xml (#10187)
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
1 parent ccf5a5a commit 350fe33

7 files changed

Lines changed: 333 additions & 6 deletions

File tree

pkg/dependency/parser/java/pom/parse.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"io"
9+
"net"
910
"net/http"
1011
"net/url"
1112
"os"
@@ -78,6 +79,7 @@ type Parser struct {
7879
remoteRepos repositories
7980
offline bool
8081
servers []Server
82+
httpClient *http.Client
8183
}
8284

8385
func NewParser(filePath string, opts ...option) *Parser {
@@ -103,6 +105,33 @@ func NewParser(filePath string, opts ...option) *Parser {
103105
settings: o.settingsRepos,
104106
}
105107

108+
var httpOpts xhttp.Options
109+
if len(s.Proxies) > 0 {
110+
httpOpts.Proxy = func(req *http.Request) (*url.URL, error) {
111+
protocol := req.URL.Scheme
112+
proxies := s.effectiveProxies(protocol, req.URL.Hostname())
113+
// No Maven proxy -> fallback to environment
114+
if len(proxies) == 0 {
115+
return http.ProxyFromEnvironment(req)
116+
}
117+
// proxy retrieves the first active proxy matching the requested protocol.
118+
// Maven evaluates proxies in order and uses the first one that matches,
119+
// allowing for protocol-specific proxy configuration (e.g., http, https).
120+
proxy := proxies[0]
121+
122+
proxyURL := &url.URL{
123+
Scheme: proxy.Protocol,
124+
Host: net.JoinHostPort(proxy.Host, proxy.Port),
125+
}
126+
if proxy.Username != "" && proxy.Password != "" {
127+
proxyURL.User = url.UserPassword(proxy.Username, proxy.Password)
128+
}
129+
return proxyURL, nil
130+
}
131+
}
132+
133+
tr := xhttp.NewTransport(httpOpts)
134+
106135
return &Parser{
107136
logger: log.WithPrefix("pom"),
108137
rootPath: filepath.Clean(filePath),
@@ -111,6 +140,9 @@ func NewParser(filePath string, opts ...option) *Parser {
111140
remoteRepos: remoteRepos,
112141
offline: o.offline,
113142
servers: s.Servers,
143+
httpClient: &http.Client{
144+
Transport: tr.Build(),
145+
},
114146
}
115147
}
116148

@@ -808,8 +840,7 @@ func (p *Parser) fetchPomFileNameFromMavenMetadata(ctx context.Context, repoURL
808840
return "", nil
809841
}
810842

811-
client := xhttp.Client()
812-
resp, err := client.Do(req)
843+
resp, err := p.httpClient.Do(req)
813844
if err != nil {
814845
if shouldReturnError(err) {
815846
return "", err
@@ -847,8 +878,7 @@ func (p *Parser) fetchPOMFromRemoteRepository(ctx context.Context, repoURL url.U
847878
return nil, nil
848879
}
849880

850-
client := xhttp.Client()
851-
resp, err := client.Do(req)
881+
resp, err := p.httpClient.Do(req)
852882
if err != nil {
853883
if shouldReturnError(err) {
854884
return nil, err

pkg/dependency/parser/java/pom/repository.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ func resolvePomRepos(servers []Server, pomRepos []pomRepository) []repository {
3232
r := repository{
3333
// "<enabled>: true or false for whether this repository is enabled for the respective type (releases or snapshots). By default, this is true."
3434
// cf. https://maven.apache.org/pom.html#Repositories
35-
releaseEnabled: rep.ReleasesEnabled == "true" || rep.ReleasesEnabled == "",
36-
snapshotEnabled: rep.SnapshotsEnabled == "true" || rep.SnapshotsEnabled == "",
35+
releaseEnabled: rep.ReleasesEnabled == trueString || rep.ReleasesEnabled == "",
36+
snapshotEnabled: rep.SnapshotsEnabled == trueString || rep.SnapshotsEnabled == "",
3737
}
3838

3939
// Add only enabled repositories

pkg/dependency/parser/java/pom/settings.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ package pom
33
import (
44
"encoding/xml"
55
"os"
6+
"path"
67
"path/filepath"
78
"slices"
9+
"strings"
810

911
"github.com/samber/lo"
1012
"github.com/samber/lo/mutable"
1113
"golang.org/x/net/html/charset"
1214
)
1315

16+
const trueString = "true"
17+
1418
type Server struct {
1519
ID string `xml:"id"`
1620
Username string `xml:"username"`
@@ -23,11 +27,23 @@ type Profile struct {
2327
ActiveByDefault bool `xml:"activation>activeByDefault"`
2428
}
2529

30+
type Proxy struct {
31+
ID string `xml:"id"`
32+
Active string `xml:"active"`
33+
Protocol string `xml:"protocol"`
34+
Host string `xml:"host"`
35+
Port string `xml:"port"`
36+
Username string `xml:"username"`
37+
Password string `xml:"password"`
38+
NonProxyHosts string `xml:"nonProxyHosts"`
39+
}
40+
2641
type settings struct {
2742
LocalRepository string `xml:"localRepository"`
2843
Servers []Server `xml:"servers>server"`
2944
Profiles []Profile `xml:"profiles>profile"`
3045
ActiveProfiles []string `xml:"activeProfiles>activeProfile"`
46+
Proxies []Proxy `xml:"proxies>proxy"`
3147
}
3248

3349
func (s settings) effectiveRepositories() []repository {
@@ -48,6 +64,44 @@ func (s settings) effectiveRepositories() []repository {
4864
return resolvePomRepos(s.Servers, pomRepos)
4965
}
5066

67+
func (s settings) effectiveProxies(protocol, hostname string) []Proxy {
68+
var proxies []Proxy
69+
for _, proxy := range s.Proxies {
70+
if !proxy.isActive() || !strings.EqualFold(proxy.Protocol, protocol) {
71+
continue
72+
}
73+
if hostname != "" && proxy.isNonProxyHost(hostname) {
74+
continue
75+
}
76+
proxies = append(proxies, proxy)
77+
}
78+
return proxies
79+
}
80+
81+
func (p Proxy) isActive() bool {
82+
return p.Active == trueString || p.Active == ""
83+
}
84+
85+
func (p Proxy) isNonProxyHost(host string) bool {
86+
if p.NonProxyHosts == "" {
87+
return false
88+
}
89+
90+
hosts := strings.SplitSeq(p.NonProxyHosts, "|")
91+
for h := range hosts {
92+
h = strings.TrimSpace(h)
93+
if h == "" {
94+
continue
95+
}
96+
97+
matched, err := path.Match(strings.ToLower(h), strings.ToLower(host))
98+
if err == nil && matched {
99+
return true
100+
}
101+
}
102+
return false
103+
}
104+
51105
func readSettings() settings {
52106
s := settings{}
53107

@@ -82,6 +136,11 @@ func readSettings() settings {
82136
})
83137
// Merge active profiles
84138
s.ActiveProfiles = lo.Uniq(append(s.ActiveProfiles, globalSettings.ActiveProfiles...))
139+
140+
// Merge proxies
141+
s.Proxies = lo.UniqBy(append(s.Proxies, globalSettings.Proxies...), func(p Proxy) string {
142+
return p.ID
143+
})
85144
}
86145

87146
return s
@@ -125,4 +184,15 @@ func expandAllEnvPlaceholders(s *settings) {
125184
for i, activeProfile := range s.ActiveProfiles {
126185
s.ActiveProfiles[i] = evaluateVariable(activeProfile, nil, nil)
127186
}
187+
188+
for i, proxy := range s.Proxies {
189+
s.Proxies[i].ID = evaluateVariable(proxy.ID, nil, nil)
190+
s.Proxies[i].Active = evaluateVariable(proxy.Active, nil, nil)
191+
s.Proxies[i].Protocol = evaluateVariable(proxy.Protocol, nil, nil)
192+
s.Proxies[i].Host = evaluateVariable(proxy.Host, nil, nil)
193+
s.Proxies[i].Port = evaluateVariable(proxy.Port, nil, nil)
194+
s.Proxies[i].Username = evaluateVariable(proxy.Username, nil, nil)
195+
s.Proxies[i].Password = evaluateVariable(proxy.Password, nil, nil)
196+
s.Proxies[i].NonProxyHosts = evaluateVariable(proxy.NonProxyHosts, nil, nil)
197+
}
128198
}

0 commit comments

Comments
 (0)