github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/nix/parse_store_path.go (about) 1 package nix 2 3 import ( 4 "fmt" 5 "path" 6 "regexp" 7 "strings" 8 ) 9 10 var ( 11 numericPattern = regexp.MustCompile(`\d`) 12 13 // attempts to find the right-most example of something that appears to be a version (semver or otherwise) 14 // example input: h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin 15 // example output: 16 // version: "2.34-210" 17 // major: "2" 18 // minor: "34" 19 // patch: "210" 20 // (there are other capture groups, but they can be ignored) 21 rightMostVersionIshPattern = regexp.MustCompile(`-(?P<version>(?P<major>[0-9][a-zA-Z0-9]*)(\.(?P<minor>[0-9][a-zA-Z0-9]*))?(\.(?P<patch>0|[1-9][a-zA-Z0-9]*)){0,3}(?:-(?P<prerelease>\d*[.0-9a-zA-Z-]*)*)?(?:\+(?P<metadata>[.0-9a-zA-Z-]+(?:\.[.0-9a-zA-Z-]+)*))?)`) 22 23 unstableVersion = regexp.MustCompile(`-(?P<version>unstable-\d{4}-\d{2}-\d{2})$`) 24 ) 25 26 // checkout the package naming conventions here: https://nixos.org/manual/nixpkgs/stable/#sec-package-naming 27 28 type nixStorePath struct { 29 StorePath string 30 OutputHash string 31 Name string 32 Version string 33 Output string 34 } 35 36 func (p nixStorePath) isValidPackage() bool { 37 return p.Name != "" && p.Version != "" 38 } 39 40 func findParentNixStorePath(source string) string { 41 source = strings.TrimRight(source, "/") 42 indicator := "nix/store/" 43 start := strings.Index(source, indicator) 44 if start == -1 { 45 return "" 46 } 47 48 startOfHash := start + len(indicator) 49 nextField := strings.Index(source[startOfHash:], "/") 50 if nextField == -1 { 51 return "" 52 } 53 startOfSubPath := startOfHash + nextField 54 55 return source[0:startOfSubPath] 56 } 57 58 func parseNixStorePath(og string) *nixStorePath { 59 if strings.HasSuffix(og, ".drv") { 60 // ignore derivations 61 return nil 62 } 63 64 source := path.Base(og) 65 66 versionStartIdx, versionIsh, prerelease := findVersionIsh(source) 67 if versionStartIdx == -1 { 68 return nil 69 } 70 71 hashName := strings.TrimSuffix(source[0:versionStartIdx], "-") 72 hashNameFields := strings.Split(hashName, "-") 73 if len(hashNameFields) < 2 { 74 return nil 75 } 76 hash, name := hashNameFields[0], strings.Join(hashNameFields[1:], "-") 77 78 prereleaseFields := strings.Split(prerelease, "-") 79 lastPrereleaseField := prereleaseFields[len(prereleaseFields)-1] 80 81 var version = versionIsh 82 var output string 83 if !hasNumeric(lastPrereleaseField) { 84 // this last prerelease field is probably a nix output 85 version = strings.TrimSuffix(versionIsh, fmt.Sprintf("-%s", lastPrereleaseField)) 86 output = lastPrereleaseField 87 } 88 89 if og != "" && !strings.HasPrefix(og, "/") { 90 og = fmt.Sprintf("/%s", og) 91 } 92 93 return &nixStorePath{ 94 StorePath: og, 95 OutputHash: hash, 96 Name: name, 97 Version: version, 98 Output: output, 99 } 100 } 101 102 func hasNumeric(s string) bool { 103 return numericPattern.MatchString(s) 104 } 105 106 func findVersionIsh(input string) (int, string, string) { 107 // we want to return the index of the start of the "version" group (the first capture group). 108 // note that the match indices are in the form of [start, end, start, end, ...]. Also note that the 109 // capture group for version in both regexes are the same index, but if the regexes are changed 110 // this code will start to fail. 111 112 // check for unstable version pattern first 113 if match := unstableVersion.FindStringSubmatch(input); match != nil { 114 indices := unstableVersion.FindStringSubmatchIndex(input) 115 versionStart := indices[2] // index of first capture group's start 116 version := match[1] // first capture group is the version 117 return versionStart, version, "" 118 } 119 120 // try the regular version pattern 121 match := rightMostVersionIshPattern.FindStringSubmatch(input) 122 if match == nil { 123 return -1, "", "" 124 } 125 126 version := match[1] // capture group 1 is the version 127 indices := rightMostVersionIshPattern.FindStringSubmatchIndex(input) 128 versionStart := indices[2] // index of first capture group's start 129 prerelease := match[7] // capture group 7 is the prerelease version 130 131 return versionStart, version, prerelease 132 }