github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/javascript/parse_package_json.go (about) 1 package javascript 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "regexp" 10 "strings" 11 12 "github.com/go-viper/mapstructure/v2" 13 14 "github.com/anchore/syft/internal" 15 "github.com/anchore/syft/syft/artifact" 16 "github.com/anchore/syft/syft/file" 17 "github.com/anchore/syft/syft/pkg" 18 "github.com/anchore/syft/syft/pkg/cataloger/generic" 19 ) 20 21 // integrity check 22 var _ generic.Parser = parsePackageJSON 23 24 // packageJSON represents a JavaScript package.json file 25 type packageJSON struct { 26 Version string `json:"version"` 27 Latest []string `json:"latest"` 28 Author person `json:"author"` 29 Authors people `json:"authors"` 30 Contributors people `json:"contributors"` 31 Maintainers people `json:"maintainers"` 32 License json.RawMessage `json:"license"` 33 Licenses json.RawMessage `json:"licenses"` 34 Name string `json:"name"` 35 Homepage string `json:"homepage"` 36 Description string `json:"description"` 37 Dependencies map[string]string `json:"dependencies"` 38 Repository repository `json:"repository"` 39 Private bool `json:"private"` 40 } 41 42 type person struct { 43 Name string `json:"name" mapstructure:"name"` 44 Email string `json:"email" mapstructure:"email"` 45 URL string `json:"url" mapstructure:"url"` 46 } 47 48 type people []person 49 50 type repository struct { 51 Type string `json:"type" mapstructure:"type"` 52 URL string `json:"url" mapstructure:"url"` 53 } 54 55 // match example: "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)" 56 // ---> name: "Isaac Z. Schlueter" email: "i@izs.me" url: "http://blog.izs.me" 57 var authorPattern = regexp.MustCompile(`^\s*(?P<name>[^<(]*)(\s+<(?P<email>.*)>)?(\s\((?P<url>.*)\))?\s*$`) 58 59 // parsePackageJSON parses a package.json and returns the discovered JavaScript packages. 60 func parsePackageJSON(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 61 var pkgs []pkg.Package 62 dec := json.NewDecoder(reader) 63 64 for { 65 var p packageJSON 66 if err := dec.Decode(&p); errors.Is(err, io.EOF) { 67 break 68 } else if err != nil { 69 return nil, nil, fmt.Errorf("failed to parse package.json file: %w", err) 70 } 71 72 // always create a package, regardless of having a valid name and/or version, 73 // a compliance filter later will remove these packages based on compliance rules 74 pkgs = append( 75 pkgs, 76 newPackageJSONPackage(ctx, resolver, p, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 77 ) 78 } 79 80 pkg.Sort(pkgs) 81 82 return pkgs, nil, nil 83 } 84 85 func (p *person) UnmarshalJSON(b []byte) error { 86 var authorStr string 87 var auth person 88 89 if err := json.Unmarshal(b, &authorStr); err == nil { 90 // successfully parsed as a string, now parse that string into fields 91 fields := internal.MatchNamedCaptureGroups(authorPattern, authorStr) 92 if err := mapstructure.Decode(fields, &auth); err != nil { 93 return fmt.Errorf("unable to decode package.json author: %w", err) 94 } 95 } else { 96 // it's a map that may contain fields of various data types (not just strings) 97 var fields map[string]interface{} 98 if err := json.Unmarshal(b, &fields); err != nil { 99 return fmt.Errorf("unable to parse package.json author: %w", err) 100 } 101 if err := mapstructure.Decode(fields, &auth); err != nil { 102 return fmt.Errorf("unable to decode package.json author: %w", err) 103 } 104 } 105 106 *p = auth 107 108 return nil 109 } 110 111 func (p *person) AuthorString() string { 112 result := p.Name 113 if p.Email != "" { 114 result += fmt.Sprintf(" <%s>", p.Email) 115 } 116 if p.URL != "" { 117 result += fmt.Sprintf(" (%s)", p.URL) 118 } 119 return result 120 } 121 122 func (r *repository) UnmarshalJSON(b []byte) error { 123 var repositoryStr string 124 var fields map[string]string 125 var repo repository 126 127 if err := json.Unmarshal(b, &repositoryStr); err != nil { 128 // string parsing did not work, assume a map was given 129 // for more information: https://docs.npmjs.com/files/package.json#people-fields-author-contributors 130 if err := json.Unmarshal(b, &fields); err != nil { 131 return fmt.Errorf("unable to parse package.json author: %w", err) 132 } 133 // translate the map into a structure 134 if err := mapstructure.Decode(fields, &repo); err != nil { 135 return fmt.Errorf("unable to decode package.json author: %w", err) 136 } 137 138 *r = repo 139 } else { 140 r.URL = repositoryStr 141 } 142 143 return nil 144 } 145 146 type npmPackageLicense struct { 147 Type string `json:"type"` 148 URL string `json:"url"` 149 } 150 151 func licenseFromJSON(b []byte) (string, error) { 152 // first try as string 153 var licenseString string 154 err := json.Unmarshal(b, &licenseString) 155 if err == nil { 156 return licenseString, nil 157 } 158 159 // then try as object (this format is deprecated) 160 var licenseObject npmPackageLicense 161 err = json.Unmarshal(b, &licenseObject) 162 if err == nil { 163 return licenseObject.Type, nil 164 } 165 166 return "", errors.New("unable to unmarshal license field as either string or object") 167 } 168 169 func (p packageJSON) licensesFromJSON() ([]string, error) { 170 if p.License == nil && p.Licenses == nil { 171 // This package.json doesn't specify any licenses whatsoever 172 return []string{}, nil 173 } 174 175 singleLicense, err := licenseFromJSON(p.License) 176 if err == nil { 177 return []string{singleLicense}, nil 178 } 179 180 multiLicense, err := licensesFromJSON(p.Licenses) 181 182 // The "licenses" field is deprecated. It should be inspected as a last resort. 183 if multiLicense != nil && err == nil { 184 mapLicenses := func(licenses []npmPackageLicense) []string { 185 mappedLicenses := make([]string, len(licenses)) 186 for i, l := range licenses { 187 mappedLicenses[i] = l.Type 188 } 189 return mappedLicenses 190 } 191 192 return mapLicenses(multiLicense), nil 193 } 194 195 return nil, err 196 } 197 198 func licensesFromJSON(b []byte) ([]npmPackageLicense, error) { 199 var licenseObject []npmPackageLicense 200 err := json.Unmarshal(b, &licenseObject) 201 if err == nil { 202 return licenseObject, nil 203 } 204 205 return nil, errors.New("unmarshal failed") 206 } 207 208 // this supports both windows and unix paths 209 var filepathSeparator = regexp.MustCompile(`[\\/]`) 210 211 func pathContainsNodeModulesDirectory(p string) bool { 212 for _, subPath := range filepathSeparator.Split(p, -1) { 213 if subPath == "node_modules" { 214 return true 215 } 216 } 217 return false 218 } 219 220 func (p *people) UnmarshalJSON(b []byte) error { 221 // Try to unmarshal as an array of strings 222 var authorStrings []string 223 if err := json.Unmarshal(b, &authorStrings); err == nil { 224 // Successfully parsed as an array of strings 225 auths := make([]person, len(authorStrings)) 226 for i, authorStr := range authorStrings { 227 // Parse each string into author fields 228 fields := internal.MatchNamedCaptureGroups(authorPattern, authorStr) 229 var auth person 230 if err := mapstructure.Decode(fields, &auth); err != nil { 231 return fmt.Errorf("unable to decode package.json author: %w", err) 232 } 233 // Trim whitespace from name if it was parsed 234 if auth.Name != "" { 235 auth.Name = strings.TrimSpace(auth.Name) 236 } 237 auths[i] = auth 238 } 239 *p = auths 240 return nil 241 } 242 243 // Try to unmarshal as an array of objects 244 var authorObjs []map[string]interface{} 245 if err := json.Unmarshal(b, &authorObjs); err == nil { 246 // Successfully parsed as an array of objects 247 auths := make([]person, len(authorObjs)) 248 for i, fields := range authorObjs { 249 var auth person 250 if err := mapstructure.Decode(fields, &auth); err != nil { 251 return fmt.Errorf("unable to decode package.json author object: %w", err) 252 } 253 auths[i] = auth 254 } 255 *p = auths 256 return nil 257 } 258 259 // If we get here, it means neither format matched 260 return fmt.Errorf("unable to parse package.json authors field: expected array of strings or array of objects") 261 } 262 263 func (p people) String() string { 264 if len(p) == 0 { 265 return "" 266 } 267 268 authorStrings := make([]string, len(p)) 269 for i, auth := range p { 270 authorStrings[i] = auth.AuthorString() 271 } 272 return strings.Join(authorStrings, ", ") 273 }