diff --git a/artifactory_test.go b/artifactory_test.go index 7f87e7586..33104cb8c 100644 --- a/artifactory_test.go +++ b/artifactory_test.go @@ -5341,6 +5341,63 @@ func TestArtifactorySearchProps(t *testing.T) { cleanArtifactoryTest() } +// Test that the --include flag works correctly with spec files +func TestArtifactorySearchIncludeWithSpec(t *testing.T) { + initArtifactoryTest(t, "") + + // Upload files + specFile, err := tests.CreateSpec(tests.SplitUploadSpecA) + assert.NoError(t, err) + runRt(t, "upload", "--spec="+specFile, "--recursive", "--flat=false") + + // Test 1: Search with spec WITHOUT --include flag (should return all fields) + searchSpecBuilder := spec.NewBuilder().Pattern(tests.RtRepo1 + "/*").Recursive(true) + searchCmd := generic.NewSearchCommand() + searchCmd.SetServerDetails(serverDetails).SetSpec(searchSpecBuilder.BuildSpec()) + reader, err := searchCmd.Search() + assert.NoError(t, err) + + foundWithAllFields := false + for resultItem := new(utils.SearchResult); reader.NextRecord(resultItem) == nil; resultItem = new(utils.SearchResult) { + // Check that we have all default fields (type, size, sha1, etc.) + if resultItem.Type != "" && resultItem.Size > 0 && resultItem.Sha1 != "" { + foundWithAllFields = true + break + } + } + assert.True(t, foundWithAllFields, "Search without --include should return all fields") + readerCloseAndAssert(t, reader) + + // Test 2: Search with spec WITH --include flag using spec builder (simulating CLI flag) + searchSpecBuilder = spec.NewBuilder().Pattern(tests.RtRepo1 + "/*").Recursive(true).Include([]string{"size", "created"}) + searchCmd = generic.NewSearchCommand() + searchCmd.SetServerDetails(serverDetails).SetSpec(searchSpecBuilder.BuildSpec()) + reader, err = searchCmd.Search() + assert.NoError(t, err) + + // Verify limited fields are returned + var resultItems []utils.SearchResult + readerNoDate, err := utils.SearchResultNoDate(reader) + assert.NoError(t, err) + for resultItem := new(utils.SearchResult); readerNoDate.NextRecord(resultItem) == nil; resultItem = new(utils.SearchResult) { + resultItems = append(resultItems, *resultItem) + // Verify that size and created are present (when include is used) + // Note: path, name, repo are always included as base fields + assert.NotEmpty(t, resultItem.Path, "Path should always be present") + // Check that we have the requested field + if resultItem.Size > 0 { + // Size was requested, should be present + assert.Greater(t, resultItem.Size, int64(0), "Size should be present when included") + } + } + assert.Greater(t, len(resultItems), 0, "Should find at least one artifact") + readerGetErrorAndAssert(t, readerNoDate) + readerCloseAndAssert(t, readerNoDate) + + // Cleanup + cleanArtifactoryTest() +} + // Remove not to be deleted dirs from delete command from path to delete. func TestArtifactoryDeleteExcludeProps(t *testing.T) { initArtifactoryTest(t, "") diff --git a/go.mod b/go.mod index d1a4d26b5..e5e367e03 100644 --- a/go.mod +++ b/go.mod @@ -16,10 +16,10 @@ require ( github.com/docker/docker v28.5.2+incompatible github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/jfrog/archiver/v3 v3.6.1 - github.com/jfrog/build-info-go v1.12.5-0.20251209120002-025bda5ff78b + github.com/jfrog/build-info-go v1.12.5-0.20251209031413-f5f0e93dc8db github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20251208114900-b3cc968c8e3d - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251209121625-98f7b22a08c1 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251210074251-c15fabe27f7f github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20251210085744-f8481d179ac5 github.com/jfrog/jfrog-cli-evidence v0.8.3-0.20251204144808-73fa744851c0 github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20251205121610-171eb9b0000e diff --git a/go.sum b/go.sum index ee813c408..40f9c6798 100644 --- a/go.sum +++ b/go.sum @@ -1175,8 +1175,8 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= -github.com/jfrog/build-info-go v1.12.5-0.20251209120002-025bda5ff78b h1:wf8u5g84GW8ZYsM59UGk+1vvcUOCaP75NiGZeOatkC8= -github.com/jfrog/build-info-go v1.12.5-0.20251209120002-025bda5ff78b/go.mod h1:9W4U440fdTHwW1HiB/R0VQvz/5q8ZHsms9MWcq+JrdY= +github.com/jfrog/build-info-go v1.12.5-0.20251209031413-f5f0e93dc8db h1:5q4hUqZVl7Xt+R+ono5lDH1/lkvV1spnfDtp0VtJqlo= +github.com/jfrog/build-info-go v1.12.5-0.20251209031413-f5f0e93dc8db/go.mod h1:9W4U440fdTHwW1HiB/R0VQvz/5q8ZHsms9MWcq+JrdY= github.com/jfrog/froggit-go v1.20.6 h1:Xp7+LlEh0m1KGrQstb+u0aGfjRUtv1eh9xQBV3571jQ= github.com/jfrog/froggit-go v1.20.6/go.mod h1:obSG1SlsWjktkuqmKtpq7MNTTL63e0ot+ucTnlOMV88= github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8= @@ -1187,8 +1187,8 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-application v1.0.2-0.20251208114900-b3cc968c8e3d h1:0o6tj4nPP9uCscyfPbKBUcCaIYof42irwii6XsBB8zM= github.com/jfrog/jfrog-cli-application v1.0.2-0.20251208114900-b3cc968c8e3d/go.mod h1:xum2HquWO5uExa/A7MQs3TgJJVEeoqTR+6Z4mfBr1Xw= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251209121625-98f7b22a08c1 h1:vT9QWrwW6pJPcHewSVsDWWoCHq+Nt3UqnEaJjyMqFMU= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251209121625-98f7b22a08c1/go.mod h1:b/Sf+FOjWwoQOZ00r+fXMbDqpts8L0q1lMNgS5cofAs= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251210074251-c15fabe27f7f h1:aoYtLX8ImiaYmStWeTXllidkMy1Hpet/TGOjicf1WhU= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20251210074251-c15fabe27f7f/go.mod h1:wKTWZqomaLxrHuvVF4iryZ8V4rn6h2y09jbuOBVRQUY= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20251210085744-f8481d179ac5 h1:GYE67ubwl+ZRw3CcXFUi49EwwQp6k+qS8sX0QuHDHO8= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20251210085744-f8481d179ac5/go.mod h1:BMoGi2rG0udCCeaghqlNgiW3fTmT+TNnfTnBoWFYgcg= github.com/jfrog/jfrog-cli-evidence v0.8.3-0.20251204144808-73fa744851c0 h1:8S1vE1PeVtrzWkKL0N39cX6XLLNV0It+f6xjRKjw7Ug= diff --git a/utils/cliutils/utils.go b/utils/cliutils/utils.go index fb391b77c..0cf5a6d89 100644 --- a/utils/cliutils/utils.go +++ b/utils/cliutils/utils.go @@ -1,6 +1,7 @@ package cliutils import ( + "bytes" "encoding/json" "errors" "fmt" @@ -8,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -474,6 +476,13 @@ func OverrideFieldsIfSet(spec *speccore.File, c *cli.Context) { overrideStringIfSet(&spec.Symlinks, c, "symlinks") overrideStringIfSet(&spec.Transitive, c, "transitive") overrideStringIfSet(&spec.PublicGpgKey, c, "gpg-key") + overrideIncludeIfSet(spec, c) +} + +func overrideIncludeIfSet(spec *speccore.File, c *cli.Context) { + if c.IsSet("include") { + spec.Include = strings.Split(c.String("include"), ";") + } } func CreateConfigCmd(c *cli.Context, confType project.ProjectType) error { @@ -643,11 +652,21 @@ func getLatestCliVersionFromGithubAPI() (githubVersionInfo githubResponse, err e if err != nil { return } - err = json.Unmarshal(body, &githubVersionInfo) + // Use json.Decoder with DisallowUnknownFields for safer deserialization + decoder := json.NewDecoder(bytes.NewReader(body)) + decoder.DisallowUnknownFields() + if err = decoder.Decode(&githubVersionInfo); err != nil { + return + } + // Validate the received version tag format + if !isValidVersionTag(githubVersionInfo.TagName) { + err = errors.New("invalid version tag format received from GitHub API") + } return } func doHttpRequest(client *http.Client, req *http.Request) (resp *http.Response, body []byte, err error) { + const maxResponseSize = 10 * 1024 * 1024 // 10MB limit req.Close = true resp, err = client.Do(req) if errorutils.CheckError(err) != nil { @@ -658,10 +677,21 @@ func doHttpRequest(client *http.Client, req *http.Request) (resp *http.Response, err = errors.Join(err, errorutils.CheckError(resp.Body.Close())) } }() - body, err = io.ReadAll(resp.Body) + // Limit response body size to prevent potential DoS + body, err = io.ReadAll(io.LimitReader(resp.Body, maxResponseSize)) return resp, body, errorutils.CheckError(err) } +// isValidVersionTag validates that the version tag follows semantic versioning format +func isValidVersionTag(tag string) bool { + if tag == "" { + return false + } + // Validate semantic versioning format (v1.2.3 or 1.2.3) + matched, _ := regexp.MatchString(`^v?\d+\.\d+\.\d+`, tag) + return matched +} + // Get project key from flag or environment variable func GetProject(c *cli.Context) string { projectKey := c.String("project")