Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ require (
github.com/git-pkgs/vers v0.2.5 // indirect
github.com/package-url/packageurl-go v0.1.6 // indirect
)

replace github.com/git-pkgs/pom => ../pom
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/bazelbuild/buildtools v0.0.0-20260319080235-05d2ebe49b0f h1:khsR2MoXO+WButt3L5nnkAOGO17BAr0G/pGHZczF5/E=
github.com/bazelbuild/buildtools v0.0.0-20260319080235-05d2ebe49b0f/go.mod h1:PLNUetjLa77TCCziPsz0EI8a6CUxgC+1jgmWv0H25tg=
github.com/git-pkgs/pom v0.1.4 h1:C6st+XSbF75eKuwfdkDZZtYHoTcaWRIEQYar5VtszUo=
github.com/git-pkgs/pom v0.1.4/go.mod h1:ufdMBe1lKzqOeP9IUb9NPZ458xKV8E8NvuyBMxOfwIk=
github.com/git-pkgs/purl v0.1.12 h1:qCskrEU1LWQhCkIVZd992W5++Bsxazvx2Cx1/65qCvU=
github.com/git-pkgs/purl v0.1.12/go.mod h1:ofp4mHsR0cUeVONQaf33n6Wxg2QTEvtUdRfCedI8ouA=
github.com/git-pkgs/vers v0.2.5 h1:tDtUMik9Iw1lyPHdT5V6LXjLo9LsJc0xOawURz7ibQU=
Expand Down
8 changes: 8 additions & 0 deletions internal/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ type Parser interface {
Parse(filename string, content []byte) ([]Dependency, error)
}

// FSRootParser is optionally implemented by parsers that can consult
// neighbouring files on disk (e.g. pom.xml following <relativePath> to a
// parent). The fsRoot argument bounds that lookup; an empty string means
// no filesystem access.
type FSRootParser interface {
ParseInRoot(filename string, content []byte, fsRoot string) ([]Dependency, error)
}

// ParseError is returned when parsing fails.
type ParseError struct {
Filename string
Expand Down
13 changes: 9 additions & 4 deletions internal/maven/maven.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"strings"

"github.com/git-pkgs/manifests/internal/core"
"github.com/git-pkgs/pom"

Check failure on line 11 in internal/maven/maven.go

View workflow job for this annotation

GitHub Actions / test (1.25)

github.com/git-pkgs/pom@v0.1.4: replacement directory ../pom does not exist
)

const (
Expand All @@ -27,20 +27,25 @@
}

// pomXMLParser parses pom.xml files. It computes a local-only effective
// POM: parents reachable via <relativePath> on disk are merged so that
// ${project.version} and properties defined in a multi-module root
// resolve, but nothing is fetched over the network. Anything that would
// POM: when given a filesystem root, parents reachable via <relativePath>
// on disk inside that root are merged so that ${project.version} and
// properties defined in a multi-module root resolve. Nothing is fetched
// over the network and nothing outside fsRoot is read. Anything that would
// need a remote parent or BOM is left as-is and the dependency keeps its
// raw ${...} version.
type pomXMLParser struct{}

func (p *pomXMLParser) Parse(filename string, content []byte) ([]core.Dependency, error) {
return p.ParseInRoot(filename, content, "")
}

func (p *pomXMLParser) ParseInRoot(filename string, content []byte, fsRoot string) ([]core.Dependency, error) {
root, err := pom.ParsePOM(content)
if err != nil {
return nil, &core.ParseError{Filename: filename, Err: err}
}

fetcher := pom.NewLocalFetcherFrom(root, filepath.Dir(filename))
fetcher := pom.NewLocalFetcherFrom(root, filepath.Dir(filename), fsRoot)
ep, err := pom.NewResolver(fetcher).ResolvePOM(context.Background(), root, pom.Options{})
if err != nil {
return nil, &core.ParseError{Filename: filename, Err: err}
Expand Down
44 changes: 43 additions & 1 deletion internal/maven/maven_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func TestPomMultiModuleLocalParent(t *testing.T) {
t.Fatalf("read fixture: %v", err)
}
parser := &pomXMLParser{}
deps, err := parser.Parse("../../testdata/maven/multimodule/child/pom.xml", content)
deps, err := parser.ParseInRoot("../../testdata/maven/multimodule/child/pom.xml", content, "../../testdata/maven/multimodule")
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
Expand All @@ -384,6 +384,48 @@ func TestPomMultiModuleLocalParent(t *testing.T) {
}
}

func TestPomMultiModuleNoFSRoot(t *testing.T) {
content, err := os.ReadFile("../../testdata/maven/multimodule/child/pom.xml")
if err != nil {
t.Fatalf("read fixture: %v", err)
}
parser := &pomXMLParser{}
deps, err := parser.Parse("../../testdata/maven/multimodule/child/pom.xml", content)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
got := map[string]core.Dependency{}
for _, d := range deps {
got[d.Name] = d
}
if v := got["org.openjdk.jmh:jmh-core"].Version; v == "1.37" {
t.Errorf("jmh-core resolved to %q without FSRoot; parent should not have been read", v)
}
if v := got["org.lib:lib"].Version; v == "2.5" {
t.Errorf("lib resolved to %q without FSRoot; parent depMgmt should not have been read", v)
}
}

func TestPomMultiModuleFSRootJail(t *testing.T) {
content, err := os.ReadFile("../../testdata/maven/multimodule/child/pom.xml")
if err != nil {
t.Fatalf("read fixture: %v", err)
}
parser := &pomXMLParser{}
// fsRoot is the child dir itself, so ../pom.xml is outside the jail.
deps, err := parser.ParseInRoot("../../testdata/maven/multimodule/child/pom.xml", content, "../../testdata/maven/multimodule/child")
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
got := map[string]core.Dependency{}
for _, d := range deps {
got[d.Name] = d
}
if v := got["org.lib:lib"].Version; v == "2.5" {
t.Errorf("lib resolved to %q; parent outside fsRoot should have been refused", v)
}
}

func TestPomDependenciesNoRequirement(t *testing.T) {
content, err := os.ReadFile("../../testdata/maven/pom_dependencies_no_requirement.xml")
if err != nil {
Expand Down
26 changes: 24 additions & 2 deletions manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,36 @@ type ParseResult struct {
Dependencies []Dependency
}

// Options configures Parse.
type Options struct {
// FSRoot, when non-empty, allows parsers that consult neighbouring
// files on disk (currently only pom.xml, for parent <relativePath>
// resolution) to do so within this directory. Paths outside it are
// refused. When empty, parsing is a pure function of content and no
// filesystem access occurs; this is the safe choice for untrusted
// input.
FSRoot string
}

// Parse parses a manifest or lockfile and returns its dependencies.
func Parse(filename string, content []byte) (*ParseResult, error) {
func Parse(filename string, content []byte, opts ...Options) (*ParseResult, error) {
var o Options
if len(opts) > 0 {
o = opts[0]
}

parser, eco, kind := core.IdentifyParser(filename)
if parser == nil {
return nil, &UnknownFileError{Filename: filename}
}

deps, err := parser.Parse(filename, content)
var deps []Dependency
var err error
if fp, ok := parser.(core.FSRootParser); ok {
deps, err = fp.ParseInRoot(filename, content, o.FSRoot)
} else {
deps, err = parser.Parse(filename, content)
}
if err != nil {
return nil, err
}
Expand Down
Loading