|
1 | 1 | package lockfile |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "bufio" |
5 | | - "fmt" |
6 | | - "net/url" |
7 | | - "os" |
8 | | - "strings" |
9 | | - |
10 | | - "github.com/g-rath/osv-detector/internal/cachedregexp" |
| 4 | + "github.com/google/osv-scalibr/extractor/filesystem/language/javascript/yarnlock" |
11 | 5 | ) |
12 | 6 |
|
13 | 7 | const YarnEcosystem = NpmEcosystem |
14 | 8 |
|
15 | | -func shouldSkipYarnLine(line string) bool { |
16 | | - return line == "" || strings.HasPrefix(line, "#") |
17 | | -} |
18 | | - |
19 | | -func groupPackageLines(scanner *bufio.Scanner) [][]string { |
20 | | - var groups [][]string |
21 | | - var group []string |
22 | | - |
23 | | - for scanner.Scan() { |
24 | | - line := scanner.Text() |
25 | | - |
26 | | - if shouldSkipYarnLine(line) { |
27 | | - continue |
28 | | - } |
29 | | - |
30 | | - // represents the start of a new dependency |
31 | | - if !strings.HasPrefix(line, " ") { |
32 | | - if len(group) > 0 { |
33 | | - groups = append(groups, group) |
34 | | - } |
35 | | - group = make([]string, 0) |
36 | | - } |
37 | | - |
38 | | - group = append(group, line) |
39 | | - } |
40 | | - |
41 | | - if len(group) > 0 { |
42 | | - groups = append(groups, group) |
43 | | - } |
44 | | - |
45 | | - return groups |
46 | | -} |
47 | | - |
48 | | -func extractYarnPackageName(str string) string { |
49 | | - str = strings.TrimPrefix(str, "\"") |
50 | | - str, _, _ = strings.Cut(str, ",") |
51 | | - str, isScoped := strings.CutPrefix(str, "@") |
52 | | - |
53 | | - name, right, _ := strings.Cut(str, "@") |
54 | | - |
55 | | - if strings.HasPrefix(right, "npm:") && strings.Contains(right, "@") { |
56 | | - return extractYarnPackageName(strings.TrimPrefix(right, "npm:")) |
57 | | - } |
58 | | - |
59 | | - if isScoped { |
60 | | - name = "@" + name |
61 | | - } |
62 | | - |
63 | | - return name |
64 | | -} |
65 | | - |
66 | | -func determineYarnPackageVersion(group []string) string { |
67 | | - re := cachedregexp.MustCompile(`^ {2}"?version"?:? "?([\w-.+]+)"?$`) |
68 | | - |
69 | | - for _, s := range group { |
70 | | - matched := re.FindStringSubmatch(s) |
71 | | - |
72 | | - if matched != nil { |
73 | | - return matched[1] |
74 | | - } |
75 | | - } |
76 | | - |
77 | | - // todo: decide what to do here - maybe panic...? |
78 | | - return "" |
79 | | -} |
80 | | - |
81 | | -func determineYarnPackageResolution(group []string) string { |
82 | | - re := cachedregexp.MustCompile(`^ {2}"?(?:resolution:|resolved)"? "([^ '"]+)"$`) |
83 | | - |
84 | | - for _, s := range group { |
85 | | - matched := re.FindStringSubmatch(s) |
86 | | - |
87 | | - if matched != nil { |
88 | | - return matched[1] |
89 | | - } |
90 | | - } |
91 | | - |
92 | | - // todo: decide what to do here - maybe panic...? |
93 | | - return "" |
94 | | -} |
95 | | - |
96 | | -func tryExtractCommit(resolution string) string { |
97 | | - // language=GoRegExp |
98 | | - matchers := []string{ |
99 | | - // ssh://... |
100 | | - // git://... |
101 | | - // git+ssh://... |
102 | | - // git+https://... |
103 | | - `(?:^|.+@)(?:git(?:\+(?:ssh|https))?|ssh)://.+#(\w+)$`, |
104 | | - // https://....git/... |
105 | | - `(?:^|.+@)https://.+\.git#(\w+)$`, |
106 | | - `https://codeload\.github\.com(?:/[\w-.]+){2}/tar\.gz/(\w+)$`, |
107 | | - `.+#commit[:=](\w+)$`, |
108 | | - // github:... |
109 | | - // gitlab:... |
110 | | - // bitbucket:... |
111 | | - `^(?:github|gitlab|bitbucket):.+#(\w+)$`, |
112 | | - } |
113 | | - |
114 | | - for _, matcher := range matchers { |
115 | | - re := cachedregexp.MustCompile(matcher) |
116 | | - matched := re.FindStringSubmatch(resolution) |
117 | | - |
118 | | - if matched != nil { |
119 | | - return matched[1] |
120 | | - } |
121 | | - } |
122 | | - |
123 | | - u, err := url.Parse(resolution) |
124 | | - |
125 | | - if err == nil { |
126 | | - gitRepoHosts := []string{ |
127 | | - "bitbucket.org", |
128 | | - "github.com", |
129 | | - "gitlab.com", |
130 | | - } |
131 | | - |
132 | | - for _, host := range gitRepoHosts { |
133 | | - if u.Host != host { |
134 | | - continue |
135 | | - } |
136 | | - |
137 | | - if u.RawQuery != "" { |
138 | | - queries := u.Query() |
139 | | - |
140 | | - if queries.Has("ref") { |
141 | | - return queries.Get("ref") |
142 | | - } |
143 | | - } |
144 | | - |
145 | | - return u.Fragment |
146 | | - } |
147 | | - } |
148 | | - |
149 | | - return "" |
150 | | -} |
151 | | - |
152 | | -func parsePackageGroup(group []string) PackageDetails { |
153 | | - name := extractYarnPackageName(group[0]) |
154 | | - version := determineYarnPackageVersion(group) |
155 | | - resolution := determineYarnPackageResolution(group) |
156 | | - |
157 | | - if version == "" { |
158 | | - _, _ = fmt.Fprintf( |
159 | | - os.Stderr, |
160 | | - "Failed to determine version of %s while parsing a yarn.lock - please report this!\n", |
161 | | - name, |
162 | | - ) |
163 | | - } |
164 | | - |
165 | | - return PackageDetails{ |
166 | | - Name: name, |
167 | | - Version: version, |
168 | | - Ecosystem: YarnEcosystem, |
169 | | - CompareAs: YarnEcosystem, |
170 | | - Commit: tryExtractCommit(resolution), |
171 | | - } |
172 | | -} |
173 | | - |
174 | 9 | func ParseYarnLock(pathToLockfile string) ([]PackageDetails, error) { |
175 | | - file, err := os.Open(pathToLockfile) |
176 | | - if err != nil { |
177 | | - return []PackageDetails{}, fmt.Errorf("could not open %s: %w", pathToLockfile, err) |
178 | | - } |
179 | | - defer file.Close() |
180 | | - |
181 | | - scanner := bufio.NewScanner(file) |
182 | | - |
183 | | - packageGroups := groupPackageLines(scanner) |
184 | | - |
185 | | - if err := scanner.Err(); err != nil { |
186 | | - return []PackageDetails{}, fmt.Errorf("error while scanning %s: %w", pathToLockfile, err) |
187 | | - } |
188 | | - |
189 | | - packages := make([]PackageDetails, 0, len(packageGroups)) |
190 | | - |
191 | | - for _, group := range packageGroups { |
192 | | - if group[0] == "__metadata:" || strings.HasSuffix(group[0], "@workspace:.\":") { |
193 | | - continue |
194 | | - } |
195 | | - |
196 | | - packages = append(packages, parsePackageGroup(group)) |
197 | | - } |
198 | | - |
199 | | - return packages, nil |
| 10 | + return extract(pathToLockfile, yarnlock.New(), YarnEcosystem) |
200 | 11 | } |
0 commit comments