Skip to content

Commit b92f4c7

Browse files
authored
feat(materials): add support for tar.gz junit bundles (#1659)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent 5c1c110 commit b92f4c7

File tree

3 files changed

+124
-32
lines changed

3 files changed

+124
-32
lines changed

pkg/attestation/crafter/materials/junit/junit.go

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
package junit
1717

1818
import (
19+
"archive/tar"
1920
"archive/zip"
2021
"bufio"
22+
"compress/gzip"
2123
"errors"
2224
"fmt"
2325
"io"
2426
"io/fs"
2527
"net/http"
2628
"os"
27-
"path/filepath"
2829
"strings"
2930

3031
"github.com/joshdk/go-junit"
@@ -51,9 +52,14 @@ func Ingest(filePath string) ([]junit.Suite, error) {
5152
mime := http.DetectContentType(buf)
5253
switch strings.Split(mime, ";")[0] {
5354
case "application/zip":
54-
suites, err = ingestArchive(filePath)
55+
suites, err = ingestZipArchive(filePath)
5556
if err != nil {
56-
return nil, fmt.Errorf("could not ingest JUnit XML: %w", err)
57+
return nil, fmt.Errorf("could not ingest Zip archive : %w", err)
58+
}
59+
case "application/x-gzip":
60+
suites, err = ingestGzipArchive(filePath)
61+
if err != nil {
62+
return nil, fmt.Errorf("could not ingest GZip archive: %w", err)
5763
}
5864
case "text/xml", "application/xml":
5965
suites, err = junit.IngestFile(filePath)
@@ -70,52 +76,74 @@ func Ingest(filePath string) ([]junit.Suite, error) {
7076
return suites, nil
7177
}
7278

73-
func ingestArchive(filename string) ([]junit.Suite, error) {
74-
archive, err := zip.OpenReader(filename)
79+
func ingestGzipArchive(filename string) ([]junit.Suite, error) {
80+
result := make([]junit.Suite, 0)
81+
82+
f, err := os.Open(filename)
7583
if err != nil {
76-
return nil, fmt.Errorf("could not open zip archive: %w", err)
84+
return nil, fmt.Errorf("can't open the file: %w", err)
7785
}
78-
defer archive.Close()
79-
dir, err := os.MkdirTemp("", "junit")
86+
defer f.Close()
87+
88+
// Decompress the file if possible
89+
uncompressedStream, err := gzip.NewReader(f)
8090
if err != nil {
81-
return nil, fmt.Errorf("could not create temporary directory: %w", err)
91+
return nil, fmt.Errorf("can't uncompress file, unexpected material type: %w", err)
8292
}
83-
for _, zf := range archive.File {
84-
if zf.FileInfo().IsDir() {
85-
continue
86-
}
87-
// extract file to dir
88-
// nolint: gosec
89-
path := filepath.Join(dir, zf.Name)
9093

91-
// Check for ZipSlip (Directory traversal)
92-
if !strings.HasPrefix(path, filepath.Clean(dir)+string(os.PathSeparator)) {
93-
return nil, fmt.Errorf("illegal file path: %s", path)
94+
// Create a tar reader
95+
tarReader := tar.NewReader(uncompressedStream)
96+
for {
97+
header, err := tarReader.Next()
98+
if err != nil {
99+
if errors.Is(err, io.EOF) {
100+
// Reached the end of tar archive
101+
break
102+
}
103+
return nil, fmt.Errorf("can't read tar header: %w", err)
104+
}
105+
// Check if the file is a regular file
106+
if header.Typeflag != tar.TypeReg || !strings.HasSuffix(header.Name, ".xml") {
107+
continue // Skip if it's not a regular file
94108
}
95109

96-
f, err := os.Create(path)
110+
suites, err := junit.IngestReader(tarReader)
97111
if err != nil {
98-
return nil, fmt.Errorf("could not open file %s: %w", path, err)
112+
return nil, fmt.Errorf("can't ingest JUnit XML file %q: %w", header.Name, err)
113+
}
114+
result = append(result, suites...)
115+
}
116+
117+
return result, nil
118+
}
119+
120+
func ingestZipArchive(filename string) ([]junit.Suite, error) {
121+
result := make([]junit.Suite, 0)
122+
123+
archive, err := zip.OpenReader(filename)
124+
if err != nil {
125+
return nil, fmt.Errorf("could not open zip archive: %w", err)
126+
}
127+
defer archive.Close()
128+
129+
for _, zf := range archive.File {
130+
if zf.FileInfo().IsDir() || !strings.HasSuffix(zf.Name, ".xml") {
131+
continue
99132
}
100133

101134
rc, err := zf.Open()
102135
if err != nil {
103-
return nil, fmt.Errorf("could not open file %s: %w", path, err)
136+
return nil, fmt.Errorf("could not open file %q: %w", zf.Name, err)
104137
}
105138

106-
_, err = f.ReadFrom(rc)
139+
suites, err := junit.IngestReader(rc)
107140
if err != nil {
108-
return nil, fmt.Errorf("could not read file %s: %w", path, err)
141+
return nil, fmt.Errorf("could not ingest JUnit XML file %q: %w", zf.Name, err)
109142
}
110143

111-
rc.Close()
112-
f.Close()
144+
result = append(result, suites...)
145+
_ = rc.Close()
113146
}
114147

115-
suites, err := junit.IngestDir(dir)
116-
if err != nil {
117-
return nil, fmt.Errorf("could not ingest JUnit XML: %w", err)
118-
}
119-
120-
return suites, nil
148+
return result, nil
121149
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// Copyright 2024 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package junit
17+
18+
import (
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func TestIngest(t *testing.T) {
25+
cases := []struct {
26+
name string
27+
filename string
28+
nSuites int
29+
expectErr bool
30+
}{
31+
{
32+
name: "single junit report",
33+
filename: "../testdata/junit.xml",
34+
nSuites: 2,
35+
},
36+
{
37+
name: "zipped reports",
38+
filename: "../testdata/tests.zip",
39+
nSuites: 13,
40+
},
41+
{
42+
name: "gzipped reports",
43+
filename: "../testdata/tests.tar.gz",
44+
nSuites: 13,
45+
},
46+
{
47+
name: "invalid xml",
48+
filename: "../testdata/junit-invalid.xml",
49+
expectErr: true,
50+
},
51+
}
52+
53+
for _, tc := range cases {
54+
t.Run(tc.name, func(t *testing.T) {
55+
suites, err := Ingest(tc.filename)
56+
if tc.expectErr {
57+
assert.Error(t, err)
58+
return
59+
}
60+
assert.NoError(t, err)
61+
assert.Equal(t, tc.nSuites, len(suites))
62+
})
63+
}
64+
}
26.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)