diff --git a/docs/docs/vulnerability/detection/language.md b/docs/docs/vulnerability/detection/language.md index efa3344b1c1..4226a90517e 100644 --- a/docs/docs/vulnerability/detection/language.md +++ b/docs/docs/vulnerability/detection/language.md @@ -24,7 +24,7 @@ | | *gradle.lockfile | - | - | ✅ | ✅ | excluded | - | | Go | Binaries built by Go[^6] | ✅ | ✅ | - | - | excluded | - | | | go.mod[^7] | - | - | ✅ | ✅ | included | - | -| Rust | Cargo.lock | ✅ | ✅ | ✅ | ✅ | included | - | +| Rust | Cargo.lock | ✅ | ✅ | ✅ | ✅ | excluded | ✅ | | | Binaries built with [cargo-auditable](https://github.com/rust-secure-code/cargo-auditable) | ✅ | ✅ | - | - | excluded | - | | C/C++ | conan.lock[^13] | - | - | ✅ | ✅ | excluded | - | | Elixir | mix.lock[^13] | - | - | ✅ | ✅ | excluded | ✅ | @@ -47,3 +47,4 @@ Example: [Dockerfile](https://github.com/aquasecurity/trivy-ci-test/blob/main/Do [^11]: ✅ means "enabled" and `-` means "disabled" in the git repository scanning [^12]: ✅ means that Trivy detects line numbers where each dependency is declared in the scanned file. Only supported in [json](../examples/report.md#json) and [sarif](../examples/report.md#sarif) formats. SARIF uses `startline == 1 and endline == 1` for unsupported file types [^13]: To scan a filename other than the default filename use [file-patterns](../examples/others.md#file-patterns) +[^14]: When you scan `Cargo.lock` and `Cargo.toml` together. See about it [here](../languages/rust.md#cargo). diff --git a/docs/docs/vulnerability/languages/rust.md b/docs/docs/vulnerability/languages/rust.md index 4835bd78b1a..7e164fa2c9b 100644 --- a/docs/docs/vulnerability/languages/rust.md +++ b/docs/docs/vulnerability/languages/rust.md @@ -6,7 +6,7 @@ The following table provides an outline of the features Trivy offers. | Package manager | File | Transitive dependencies | Dev dependencies | License | Dependency graph | Position | |-----------------|------------|:-----------------------:|:-----------------|:-------:|:----------------:|:--------:| -| Cargo | Cargo.lock | ✅ | Included | - | ✅ | ✅ | +| Cargo | Cargo.lock | ✅ | Excluded[^1] | - | ✅ | ✅ | In addition, it supports binaries built with [cargo-auditable](https://github.com/rust-secure-code/cargo-auditable). @@ -17,8 +17,15 @@ In addition, it supports binaries built with [cargo-auditable](https://github.co ### Cargo Trivy searches for `Cargo.lock` to detect dependencies. -Since `Cargo.lock` doesn't have information about the development dependencies, it also includes them. + +Trivy also supports dependency trees; however, to display an accurate tree, it needs to know whether each package is a direct dependency of the project. +Since this information is not included in `Cargo.lock`, Trivy parses `Cargo.toml`, which should be located next to `Cargo.lock`. +If you want to see the dependency tree, please ensure that `Cargo.toml` is present. + +Scan `Cargo.lock` and `Cargo.toml` together also removes developer dependencies. ### Binaries Trivy scans binaries built with [cargo-auditable](https://github.com/rust-secure-code/cargo-auditable). -If such a binary exists, Trivy will identify it as being built with cargo-audit and scan it. \ No newline at end of file +If such a binary exists, Trivy will identify it as being built with cargo-audit and scan it. + +[^1]: When you scan Cargo.lock and Cargo.toml together. \ No newline at end of file diff --git a/go.mod b/go.mod index ae455e4d544..5b90fc6755a 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Azure/go-autorest/autorest v0.11.28 github.com/Azure/go-autorest/autorest/adal v0.9.21 github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 + github.com/BurntSushi/toml v1.2.1 github.com/CycloneDX/cyclonedx-go v0.7.0 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible github.com/Masterminds/sprig/v3 v3.2.3 @@ -117,7 +118,6 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index cef1171fa78..9c32b76a688 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -2,42 +2,248 @@ package cargo import ( "context" + "errors" + "fmt" + "io/fs" "os" "path/filepath" + "sort" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "golang.org/x/xerrors" + "github.com/BurntSushi/toml" + "github.com/samber/lo" + + dio "github.com/aquasecurity/go-dep-parser/pkg/io" "github.com/aquasecurity/go-dep-parser/pkg/rust/cargo" + godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types" + "github.com/aquasecurity/go-version/pkg/semver" + goversion "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func init() { - analyzer.RegisterAnalyzer(&cargoLibraryAnalyzer{}) + analyzer.RegisterPostAnalyzer(analyzer.TypeCargo, newCargoAnalyzer) } const version = 1 -type cargoLibraryAnalyzer struct{} +var requiredFiles = []string{ + types.CargoLock, + types.CargoToml, +} + +type cargoAnalyzer struct { + lockParser godeptypes.Parser + comparer compare.GenericComparer +} + +func newCargoAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &cargoAnalyzer{ + lockParser: cargo.NewParser(), + comparer: compare.GenericComparer{}, + }, nil +} + +func (a cargoAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application -func (a cargoLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - res, err := language.Analyze(types.Cargo, input.FilePath, input.Content, cargo.NewParser()) + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.CargoLock + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r dio.ReadSeekerAt) error { + // Parse Cargo.lock + app, err := a.parseCargoLock(path, r) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Parse Cargo.toml alongside Cargo.lock to identify the direct dependencies + if err = a.removeDevDependencies(input.FS, filepath.Dir(path), app); err != nil { + return err + } + sort.Sort(types.Packages(app.Libraries)) + apps = append(apps, *app) + + return nil + }) if err != nil { - return nil, xerrors.Errorf("error with Cargo.lock: %w", err) + return nil, xerrors.Errorf("cargo walk error: %w", err) } - return res, nil + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil } -func (a cargoLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { +func (a cargoAnalyzer) Required(filePath string, _ os.FileInfo) bool { fileName := filepath.Base(filePath) - return fileName == types.CargoLock + return slices.Contains(requiredFiles, fileName) } -func (a cargoLibraryAnalyzer) Type() analyzer.Type { +func (a cargoAnalyzer) Type() analyzer.Type { return analyzer.TypeCargo } -func (a cargoLibraryAnalyzer) Version() int { +func (a cargoAnalyzer) Version() int { return version } + +func (a cargoAnalyzer) parseCargoLock(path string, r dio.ReadSeekerAt) (*types.Application, error) { + return language.Parse(types.Cargo, path, r, a.lockParser) +} + +func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types.Application) error { + cargoTOMLPath := filepath.Join(dir, types.CargoToml) + directDeps, err := a.parseCargoTOML(fsys, cargoTOMLPath) + if errors.Is(err, fs.ErrNotExist) { + log.Logger.Debugf("Cargo: %s not found", cargoTOMLPath) + return nil + } else if err != nil { + return xerrors.Errorf("unable to parse %s: %w", cargoTOMLPath, err) + } + + // Cargo.toml file can contain same libraries with different versions. + // Save versions separately for version comparison by comparator + pkgIDs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + return pkg.ID, pkg + }) + + // Identify direct dependencies + pkgs := map[string]types.Package{} + for name, constraint := range directDeps { + for _, pkg := range app.Libraries { + if pkg.Name != name { + continue + } + + if match, err := a.matchVersion(pkg.Version, constraint); err != nil { + log.Logger.Warnf("Unable to match Cargo version: package: %s, error: %s", pkg.ID, err) + continue + } else if match { + // Mark as a direct dependency + pkg.Indirect = false + pkgs[pkg.ID] = pkg + break + } + } + } + + // Walk indirect dependencies + // Since it starts from direct dependencies, devDependencies will not appear in this walk. + for _, pkg := range pkgs { + a.walkIndirectDependencies(pkg, pkgIDs, pkgs) + } + + pkgSlice := maps.Values(pkgs) + sort.Sort(types.Packages(pkgSlice)) + + // Save only prod libraries + app.Libraries = pkgSlice + return nil +} + +type cargoToml struct { + Dependencies Dependencies `toml:"dependencies"` + Target map[string]map[string]Dependencies `toml:"target"` + Workspace map[string]Dependencies `toml:"workspace"` +} + +type Dependencies map[string]interface{} + +func (a cargoAnalyzer) parseCargoTOML(fsys fs.FS, path string) (map[string]string, error) { + // Parse Cargo.json + f, err := fsys.Open(path) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + tomlFile := cargoToml{} + deps := map[string]string{} + _, err = toml.NewDecoder(f).Decode(&tomlFile) + if err != nil { + return nil, xerrors.Errorf("toml decode error: %w", err) + } + + dependencies := tomlFile.Dependencies + + // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies + for _, target := range tomlFile.Target { + maps.Copy(dependencies, target["dependencies"]) + } + + // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace + maps.Copy(dependencies, tomlFile.Workspace["dependencies"]) + + for name, value := range tomlFile.Dependencies { + switch ver := value.(type) { + case string: + // e.g. regex = "1.5" + deps[name] = ver + case map[string]interface{}: + // e.g. serde = { version = "1.0", features = ["derive"] } + for k, v := range ver { + if k == "version" { + if vv, ok := v.(string); ok { + deps[name] = vv + } + break + } + } + } + } + + return deps, nil +} + +func (a cargoAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs map[string]types.Package, deps map[string]types.Package) { + for _, pkgID := range pkg.DependsOn { + if _, ok := deps[pkgID]; ok { + continue + } + + dep, ok := pkgIDs[pkgID] + if !ok { + continue + } + + dep.Indirect = true + deps[dep.ID] = dep + a.walkIndirectDependencies(dep, pkgIDs, deps) + } +} + +// cf. https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html +func (a cargoAnalyzer) matchVersion(currentVersion, constraint string) (bool, error) { + // `` == `^` - https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements + // Add `^` for correct version comparison + // - 1.2.3 -> ^1.2.3 + // - 1.2.* -> 1.2.* + // - ^1.2 -> ^1.2 + if _, err := goversion.Parse(constraint); err == nil { + constraint = fmt.Sprintf("^%s", constraint) + } + + ver, err := semver.Parse(currentVersion) + if err != nil { + return false, xerrors.Errorf("version error (%s): %s", currentVersion, err) + } + + c, err := semver.NewConstraints(constraint) + if err != nil { + return false, xerrors.Errorf("constraint error (%s): %s", currentVersion, err) + } + + return c.Check(ver), nil +} diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go new file mode 100644 index 00000000000..6cdee699586 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go @@ -0,0 +1,304 @@ +package cargo + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/detector/library/compare" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "testing" +) + +func Test_cargoAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "Cargo.lock", + Libraries: []types.Package{ + { + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Indirect: true, + Locations: []types.Location{{StartLine: 4, EndLine: 11}}, + DependsOn: []string{"memchr@2.5.0"}, + }, + { + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Indirect: true, + Locations: []types.Location{{StartLine: 22, EndLine: 26}}, + }, + { + ID: "memchr@1.0.2", + Name: "memchr", + Version: "1.0.2", + Indirect: false, + Locations: []types.Location{{StartLine: 28, EndLine: 35}}, + DependsOn: []string{"libc@0.2.140"}, + }, + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: true, + Locations: []types.Location{{StartLine: 37, EndLine: 41}}, + }, + { + ID: "regex@1.7.3", + Name: "regex", + Version: "1.7.3", + Indirect: false, + Locations: []types.Location{{StartLine: 43, EndLine: 52}}, + DependsOn: []string{"aho-corasick@0.7.20", "memchr@2.5.0", "regex-syntax@0.6.29"}, + }, + { + ID: "regex-syntax@0.5.6", + Name: "regex-syntax", + Version: "0.5.6", + Indirect: false, + Locations: []types.Location{{StartLine: 54, EndLine: 61}}, + DependsOn: []string{"ucd-util@0.1.10"}, + }, + { + ID: "regex-syntax@0.6.29", + Name: "regex-syntax", + Version: "0.6.29", + Indirect: true, + Locations: []types.Location{{StartLine: 63, EndLine: 67}}, + }, + { + ID: "ucd-util@0.1.10", + Name: "ucd-util", + Version: "0.1.10", + Indirect: true, + Locations: []types.Location{{StartLine: 69, EndLine: 73}}, + }, + }, + }, + }, + }, + }, + { + name: "no Cargo.toml", + dir: "testdata/no-cargo-toml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Cargo, + FilePath: "Cargo.lock", + Libraries: []types.Package{ + { + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Indirect: false, + Locations: []types.Location{{StartLine: 4, EndLine: 11}}, + DependsOn: []string{"memchr@2.5.0"}, + }, + { + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Indirect: false, + Locations: []types.Location{{StartLine: 13, EndLine: 20}}, + DependsOn: []string{"memchr@1.0.2", "regex@1.7.3", "regex-syntax@0.5.6"}, + }, + { + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Indirect: false, + Locations: []types.Location{{StartLine: 22, EndLine: 26}}, + }, + { + ID: "memchr@1.0.2", + Name: "memchr", + Version: "1.0.2", + Indirect: false, + Locations: []types.Location{{StartLine: 28, EndLine: 35}}, + DependsOn: []string{"libc@0.2.140"}, + }, + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: false, + Locations: []types.Location{{StartLine: 37, EndLine: 41}}, + }, + { + ID: "regex@1.7.3", + Name: "regex", + Version: "1.7.3", + Indirect: false, + Locations: []types.Location{{StartLine: 43, EndLine: 52}}, + DependsOn: []string{"aho-corasick@0.7.20", "memchr@2.5.0", "regex-syntax@0.6.29"}, + }, + { + ID: "regex-syntax@0.5.6", + Name: "regex-syntax", + Version: "0.5.6", + Indirect: false, + Locations: []types.Location{{StartLine: 54, EndLine: 61}}, + DependsOn: []string{"ucd-util@0.1.10"}, + }, + { + ID: "regex-syntax@0.6.29", + Name: "regex-syntax", + Version: "0.6.29", + Indirect: false, + Locations: []types.Location{{StartLine: 63, EndLine: 67}}, + }, + { + ID: "ucd-util@0.1.10", + Name: "ucd-util", + Version: "0.1.10", + Indirect: false, + Locations: []types.Location{{StartLine: 69, EndLine: 73}}, + }, + { + ID: "winapi@0.3.9", + Name: "winapi", + Version: "0.3.9", + Indirect: false, + Locations: []types.Location{{StartLine: 75, EndLine: 83}}, + DependsOn: []string{"winapi-i686-pc-windows-gnu@0.4.0", "winapi-x86_64-pc-windows-gnu@0.4.0"}, + }, + { + ID: "winapi-i686-pc-windows-gnu@0.4.0", + Name: "winapi-i686-pc-windows-gnu", + Version: "0.4.0", + Indirect: false, + Locations: []types.Location{{StartLine: 85, EndLine: 89}}, + }, + { + ID: "winapi-x86_64-pc-windows-gnu@0.4.0", + Name: "winapi-x86_64-pc-windows-gnu", + Version: "0.4.0", + Indirect: false, + Locations: []types.Location{{StartLine: 91, EndLine: 95}}, + }, + }, + }, + }, + }, + }, + { + name: "broken Cargo.lock", + dir: "testdata/sad", + wantErr: "failed to parse Cargo.lock", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newCargoAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMatchVersion(t *testing.T) { + tests := []struct { + name string + version string // version from Cargo.lock + constraint string // version from Cargo.toml + want bool + }{ + { + name: "major version == 0.", + version: "0.0.4", + constraint: "0.0.3", + want: false, + }, + { + name: "major version > 0.", + version: "1.2.4", + constraint: "1.2.3", + want: true, + }, + { + name: "Caret prefix", + version: "1.5.0", + constraint: "^1.2", + want: true, + }, + { + name: "Tilde prefix. Minor version", + version: "1.3.4", + constraint: "~ 1.2", + want: false, + }, + { + name: "Tilde prefix. Patch version", + version: "1.2.4", + constraint: "~ 1.2.3", + want: true, + }, + { + name: "Comparison prefix", + version: "2.5.0", + constraint: "< 2.5.0", + want: false, + }, + { + name: "Multiple prefixes", + version: "2.5.0", + constraint: ">= 2.5, < 2.5.1", + want: true, + }, + { + name: "= prefix", + version: "2.5.0", + constraint: "= 2.5", + want: true, + }, + { + name: "`*` constraint", + version: "2.5.0", + constraint: "*", + want: true, + }, + { + name: "constraint with `.*`", + version: "2.5.0", + constraint: "2.5.*", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := cargoAnalyzer{ + comparer: compare.GenericComparer{}, + } + match, _ := a.matchVersion(tt.version, tt.constraint) + assert.Equal(t, tt.want, match) + }) + } +} diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.lock new file mode 100644 index 00000000000..af569f24584 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.lock @@ -0,0 +1,95 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc 0.2.140 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "ucd-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.toml b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.toml new file mode 100644 index 00000000000..26b080c2575 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/happy/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "app" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +regex = "=1.7.3" + +[target.'cfg(not(target_os = "windows"))'.dependencies] +memchr = { version = "1.*", optional = true } + +[workspace.dependencies] +regex-syntax = { version = "<0.6"} + +[dev-dependencies] +winapi = "*" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/no-cargo-toml/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/no-cargo-toml/Cargo.lock new file mode 100644 index 00000000000..af569f24584 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/no-cargo-toml/Cargo.lock @@ -0,0 +1,95 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc 0.2.140 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "ucd-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/rust/cargo/testdata/sad/Cargo.lock b/pkg/fanal/analyzer/language/rust/cargo/testdata/sad/Cargo.lock new file mode 100644 index 00000000000..8e2f0bef135 --- /dev/null +++ b/pkg/fanal/analyzer/language/rust/cargo/testdata/sad/Cargo.lock @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 581a6696164..0688d020738 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -74,6 +74,7 @@ const ( GemfileLock = "Gemfile.lock" CargoLock = "Cargo.lock" + CargoToml = "Cargo.toml" ConanLock = "conan.lock"