github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/javascript/parse_package_json.go (about) 1 package javascript 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "regexp" 8 9 "github.com/mitchellh/mapstructure" 10 11 "github.com/anchore/syft/internal" 12 "github.com/anchore/syft/internal/log" 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 // integrity check 20 var _ generic.Parser = parsePackageJSON 21 22 type packageJSON struct { 23 Name string `json:"name"` 24 Version string `json:"version"` 25 Author author `json:"author"` 26 License json.RawMessage `json:"license"` 27 Licenses json.RawMessage `json:"licenses"` 28 Homepage string `json:"homepage"` 29 Private bool `json:"private"` 30 Description string `json:"description"` 31 Develop bool `json:"dev"` // lock v3 32 Repository repository `json:"repository"` 33 Dependencies map[string]string `json:"dependencies"` 34 DevDependencies map[string]string `json:"devDependencies"` 35 PeerDependencies map[string]string `json:"peerDependencies"` 36 PeerDependenciesMeta map[string]struct { 37 Optional bool `json:"optional"` 38 } `json:"peerDependenciesMeta"` 39 File string `json:"-"` 40 } 41 42 type author struct { 43 Name string `json:"name" mapstruct:"name"` 44 Email string `json:"email" mapstruct:"email"` 45 URL string `json:"url" mapstruct:"url"` 46 } 47 48 type repository struct { 49 Type string `json:"type" mapstructure:"type"` 50 URL string `json:"url" mapstructure:"url"` 51 } 52 53 // match example: "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)" 54 // ---> name: "Isaac Z. Schlueter" email: "i@izs.me" url: "http://blog.izs.me" 55 var authorPattern = regexp.MustCompile(`^\s*(?P<name>[^<(]*)(\s+<(?P<email>.*)>)?(\s\((?P<url>.*)\))?\s*$`) 56 57 func parsePackageJSON(resolver file.Resolver, e *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 58 pkgjson, err := parsePackageJSONFile(resolver, e, reader) 59 if err != nil { 60 return nil, nil, err 61 } 62 63 if !pkgjson.hasNameAndVersionValues() { 64 log.Debugf("encountered package.json file without a name and/or version field, ignoring (path=%q)", reader.Location.AccessPath()) 65 return nil, nil, nil 66 } 67 68 rootPkg := newPackageJSONRootPackage(*pkgjson, reader.Location) 69 return []pkg.Package{rootPkg}, nil, nil 70 } 71 72 // parsePackageJSON parses a package.json and returns the discovered JavaScript packages. 73 func parsePackageJSONFile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) (*packageJSON, error) { 74 var js *packageJSON 75 decoder := json.NewDecoder(reader) 76 err := decoder.Decode(&js) 77 if err != nil { 78 return nil, err 79 } 80 81 return js, nil 82 } 83 84 func (a *author) UnmarshalJSON(b []byte) error { 85 var authorStr string 86 var fields map[string]string 87 var auth author 88 89 if err := json.Unmarshal(b, &authorStr); err != nil { 90 // string parsing did not work, assume a map was given 91 // for more information: https://docs.npmjs.com/files/package.json#people-fields-author-contributors 92 if err := json.Unmarshal(b, &fields); err != nil { 93 return fmt.Errorf("unable to parse package.json author: %w", err) 94 } 95 } else { 96 // parse out "name <email> (url)" into an author struct 97 fields = internal.MatchNamedCaptureGroups(authorPattern, authorStr) 98 } 99 100 // translate the map into a structure 101 if err := mapstructure.Decode(fields, &auth); err != nil { 102 return fmt.Errorf("unable to decode package.json author: %w", err) 103 } 104 105 *a = auth 106 107 return nil 108 } 109 110 func (a *author) AuthorString() string { 111 result := a.Name 112 if a.Email != "" { 113 result += fmt.Sprintf(" <%s>", a.Email) 114 } 115 if a.URL != "" { 116 result += fmt.Sprintf(" (%s)", a.URL) 117 } 118 return result 119 } 120 121 func (r *repository) UnmarshalJSON(b []byte) error { 122 var repositoryStr string 123 var fields map[string]string 124 var repo repository 125 126 if err := json.Unmarshal(b, &repositoryStr); err != nil { 127 // string parsing did not work, assume a map was given 128 // for more information: https://docs.npmjs.com/files/package.json#people-fields-author-contributors 129 if err := json.Unmarshal(b, &fields); err != nil { 130 return fmt.Errorf("unable to parse package.json author: %w", err) 131 } 132 // translate the map into a structure 133 if err := mapstructure.Decode(fields, &repo); err != nil { 134 return fmt.Errorf("unable to decode package.json author: %w", err) 135 } 136 137 *r = repo 138 } else { 139 r.URL = repositoryStr 140 } 141 142 return nil 143 } 144 145 type npmPackageLicense struct { 146 Type string `json:"type"` 147 URL string `json:"url"` 148 } 149 150 func licenseFromJSON(b []byte) (string, error) { 151 // first try as string 152 var licenseString string 153 err := json.Unmarshal(b, &licenseString) 154 if err == nil { 155 return licenseString, nil 156 } 157 158 // then try as object (this format is deprecated) 159 var licenseObject npmPackageLicense 160 err = json.Unmarshal(b, &licenseObject) 161 if err == nil { 162 return licenseObject.Type, nil 163 } 164 165 return "", errors.New("unable to unmarshal license field as either string or object") 166 } 167 168 func (p packageJSON) licensesFromJSON() ([]string, error) { 169 if p.License == nil && p.Licenses == nil { 170 // This package.json doesn't specify any licenses whatsoever 171 return []string{}, nil 172 } 173 174 singleLicense, err := licenseFromJSON(p.License) 175 if err == nil { 176 return []string{singleLicense}, nil 177 } 178 179 multiLicense, err := licensesFromJSON(p.Licenses) 180 181 // The "licenses" field is deprecated. It should be inspected as a last resort. 182 if multiLicense != nil && err == nil { 183 mapLicenses := func(licenses []npmPackageLicense) []string { 184 mappedLicenses := make([]string, len(licenses)) 185 for i, l := range licenses { 186 mappedLicenses[i] = l.Type 187 } 188 return mappedLicenses 189 } 190 191 return mapLicenses(multiLicense), nil 192 } 193 194 return nil, err 195 } 196 197 func licensesFromJSON(b []byte) ([]npmPackageLicense, error) { 198 var licenseObject []npmPackageLicense 199 err := json.Unmarshal(b, &licenseObject) 200 if err == nil { 201 return licenseObject, nil 202 } 203 204 return nil, errors.New("unmarshal failed") 205 } 206 207 func (p packageJSON) hasNameAndVersionValues() bool { 208 return p.Name != "" && p.Version != "" 209 } 210 211 // this supports both windows and unix paths 212 var filepathSeparator = regexp.MustCompile(`[\\/]`) 213 214 func pathContainsNodeModulesDirectory(p string) bool { 215 for _, subPath := range filepathSeparator.Split(p, -1) { 216 if subPath == "node_modules" { 217 return true 218 } 219 } 220 return false 221 }