github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/ruby/parse_gemspec.go (about) 1 package ruby 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "fmt" 7 "regexp" 8 "strings" 9 10 "github.com/mitchellh/mapstructure" 11 12 "github.com/anchore/syft/internal" 13 "github.com/anchore/syft/syft/artifact" 14 "github.com/anchore/syft/syft/file" 15 "github.com/anchore/syft/syft/pkg" 16 "github.com/anchore/syft/syft/pkg/cataloger/generic" 17 ) 18 19 var _ generic.Parser = parseGemFileLockEntries 20 21 type postProcessor func(string) []string 22 23 type gemData struct { 24 Licenses []string `mapstructure:"licenses" json:"licenses,omitempty"` 25 pkg.GemMetadata `mapstructure:",squash" json:",inline"` 26 } 27 28 // match example: Al\u003Ex ---> 003E 29 var unicodePattern = regexp.MustCompile(`\\u(?P<unicode>[0-9A-F]{4})`) 30 31 var patterns = map[string]*regexp.Regexp{ 32 // match example: name = "railties".freeze ---> railties 33 "name": regexp.MustCompile(`.*\.name\s*=\s*["']{1}(?P<name>.*)["']{1} *`), 34 35 // match example: version = "1.0.4".freeze ---> 1.0.4 36 "version": regexp.MustCompile(`.*\.version\s*=\s*["']{1}(?P<version>.*)["']{1} *`), 37 38 // match example: 39 // homepage = "https://github.com/anchore/syft".freeze ---> https://github.com/anchore/syft 40 "homepage": regexp.MustCompile(`.*\.homepage\s*=\s*["']{1}(?P<homepage>.*)["']{1} *`), 41 42 // match example: files = ["exe/bundle".freeze, "exe/bundler".freeze] ---> "exe/bundle".freeze, "exe/bundler".freeze 43 "files": regexp.MustCompile(`.*\.files\s*=\s*\[(?P<files>.*)] *`), 44 45 // match example: authors = ["Andr\u00E9 Arko".freeze, "Samuel Giddins".freeze, "Colby Swandale".freeze, 46 // "Hiroshi Shibata".freeze, "David Rodr\u00EDguez".freeze, "Grey Baker".freeze...] 47 "authors": regexp.MustCompile(`.*\.authors\s*=\s*\[(?P<authors>.*)] *`), 48 49 // match example: licenses = ["MIT".freeze] ----> "MIT".freeze 50 "licenses": regexp.MustCompile(`.*\.licenses\s*=\s*\[(?P<licenses>.*)] *`), 51 } 52 53 var postProcessors = map[string]postProcessor{ 54 "files": processList, 55 "authors": processList, 56 "licenses": processList, 57 } 58 59 func processList(s string) []string { 60 var results []string 61 for _, item := range strings.Split(s, ",") { 62 results = append(results, strings.Trim(item, "\" ")) 63 } 64 return results 65 } 66 67 func parseGemSpecEntries(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 68 var pkgs []pkg.Package 69 var fields = make(map[string]interface{}) 70 scanner := bufio.NewScanner(reader) 71 72 for scanner.Scan() { 73 line := scanner.Text() 74 75 sanitizedLine := strings.TrimSpace(line) 76 sanitizedLine = strings.ReplaceAll(sanitizedLine, ".freeze", "") 77 sanitizedLine = renderUtf8(sanitizedLine) 78 79 if sanitizedLine == "" { 80 continue 81 } 82 83 for field, pattern := range patterns { 84 matchMap := internal.MatchNamedCaptureGroups(pattern, sanitizedLine) 85 if value := matchMap[field]; value != "" { 86 if pp := postProcessors[field]; pp != nil { 87 fields[field] = pp(value) 88 } else { 89 fields[field] = value 90 } 91 // TODO: know that a line could actually match on multiple patterns, this is unlikely though 92 break 93 } 94 } 95 } 96 97 if fields["name"] != "" && fields["version"] != "" { 98 var metadata gemData 99 if err := mapstructure.Decode(fields, &metadata); err != nil { 100 return nil, nil, fmt.Errorf("unable to decode gem metadata: %w", err) 101 } 102 103 pkgs = append( 104 pkgs, 105 newGemspecPackage( 106 metadata, 107 reader.Location, 108 ), 109 ) 110 } 111 112 return pkgs, nil, nil 113 } 114 115 // renderUtf8 takes any string escaped string sub-sections from the ruby string and replaces those sections with the UTF8 runes. 116 func renderUtf8(s string) string { 117 fullReplacement := unicodePattern.ReplaceAllStringFunc(s, func(unicodeSection string) string { 118 var replacement string 119 // note: the json parser already has support for interpreting hex-representations of unicode escaped strings as unicode runes. 120 // we can do this ourselves with strconv.Atoi, or leverage the existing json package. 121 if err := json.Unmarshal([]byte(`"`+unicodeSection+`"`), &replacement); err != nil { 122 return unicodeSection 123 } 124 return replacement 125 }) 126 return fullReplacement 127 }