1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.analyzer;
19
20 import com.github.packageurl.MalformedPackageURLException;
21 import com.github.packageurl.PackageURL;
22 import com.github.packageurl.PackageURLBuilder;
23 import org.owasp.dependencycheck.Engine;
24 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
25 import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
26 import org.owasp.dependencycheck.dependency.Confidence;
27 import org.owasp.dependencycheck.dependency.Dependency;
28 import org.owasp.dependencycheck.dependency.EvidenceType;
29 import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
30 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
31 import org.owasp.dependencycheck.exception.InitializationException;
32 import org.owasp.dependencycheck.utils.FileFilterBuilder;
33 import org.owasp.dependencycheck.utils.Settings;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import javax.annotation.concurrent.ThreadSafe;
38 import java.io.File;
39 import java.io.FileFilter;
40 import java.io.IOException;
41 import java.nio.charset.StandardCharsets;
42 import java.nio.file.Files;
43 import java.util.List;
44 import java.util.regex.Matcher;
45 import java.util.regex.Pattern;
46
47
48
49
50
51
52
53
54 @Experimental
55 @ThreadSafe
56 public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer {
57
58
59
60
61
62 public static final String DEPENDENCY_ECOSYSTEM = Ecosystem.RUBY;
63
64
65
66 private static final Logger LOGGER = LoggerFactory.getLogger(RubyGemspecAnalyzer.class);
67
68
69
70 private static final String ANALYZER_NAME = "Ruby Gemspec Analyzer";
71
72
73
74 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
75
76
77
78 private static final String GEMSPEC = "gemspec";
79
80
81
82
83 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(GEMSPEC).build();
84
85
86
87
88
89
90 private static final String VERSION_FILE_NAME = "VERSION";
91
92
93
94
95 private static final Pattern GEMSPEC_BLOCK_INIT = Pattern.compile("Gem::Specification\\.new\\s+?do\\s+?\\|(.+?)\\|");
96
97
98
99
100 @Override
101 protected FileFilter getFileFilter() {
102 return FILTER;
103 }
104
105 @Override
106 protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
107
108 }
109
110
111
112
113
114
115 @Override
116 public String getName() {
117 return ANALYZER_NAME;
118 }
119
120
121
122
123
124
125 @Override
126 public AnalysisPhase getAnalysisPhase() {
127 return ANALYSIS_PHASE;
128 }
129
130
131
132
133
134
135
136 @Override
137 protected String getAnalyzerEnabledSettingKey() {
138 return Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED;
139 }
140
141 @Override
142 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
143 dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
144 String contents;
145 try {
146 contents = new String(Files.readAllBytes(dependency.getActualFile().toPath()), StandardCharsets.UTF_8);
147 } catch (IOException e) {
148 throw new AnalysisException(
149 "Problem occurred while reading dependency file.", e);
150 }
151 final Matcher matcher = GEMSPEC_BLOCK_INIT.matcher(contents);
152 if (matcher.find()) {
153 contents = contents.substring(matcher.end());
154 final String blockVariable = matcher.group(1);
155
156 final String name = addStringEvidence(dependency, EvidenceType.PRODUCT, contents, blockVariable, "name", "name", Confidence.HIGHEST);
157 if (!name.isEmpty()) {
158 dependency.addEvidence(EvidenceType.VENDOR, GEMSPEC, "name_project", name + "_project", Confidence.LOW);
159 dependency.setName(name);
160 }
161 final String description = addStringEvidence(dependency, EvidenceType.PRODUCT, contents, blockVariable,
162 "summary", "summary", Confidence.LOW);
163 if (description != null && !description.isEmpty()) {
164 dependency.setDescription(description);
165 }
166 addStringEvidence(dependency, EvidenceType.VENDOR, contents, blockVariable,
167 "author", "authors?", Confidence.HIGHEST);
168 addStringEvidence(dependency, EvidenceType.VENDOR, contents, blockVariable,
169 "email", "emails?", Confidence.MEDIUM);
170 addStringEvidence(dependency, EvidenceType.VENDOR, contents, blockVariable,
171 "homepage", "homepage", Confidence.HIGHEST);
172 final String license = addStringEvidence(dependency, EvidenceType.VENDOR, contents, blockVariable,
173 "license", "licen[cs]es?", Confidence.HIGHEST);
174 if (license != null && !license.isEmpty()) {
175 dependency.setLicense(license);
176 }
177 final String value = addStringEvidence(dependency, EvidenceType.VERSION, contents,
178 blockVariable, "version", "version", Confidence.HIGHEST);
179 if (value.length() < 1) {
180 final String version = addEvidenceFromVersionFile(dependency, EvidenceType.VERSION, dependency.getActualFile());
181 if (version != null) {
182 dependency.setVersion(version);
183 }
184 } else {
185 dependency.setVersion(value);
186 }
187 }
188 if (dependency.getName() != null && dependency.getVersion() != null) {
189 dependency.setDisplayFileName(String.format("%s:%s", dependency.getName(), dependency.getVersion()));
190 }
191
192 try {
193 final PackageURLBuilder builder = PackageURLBuilder.aPackageURL().withType("gem").withName(dependency.getName());
194 if (dependency.getVersion() != null) {
195 builder.withVersion(dependency.getVersion());
196 }
197 final PackageURL purl = builder.build();
198 dependency.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
199 } catch (MalformedPackageURLException ex) {
200 LOGGER.debug("Unable to build package url for python", ex);
201 final GenericIdentifier id;
202 if (dependency.getVersion() != null) {
203 id = new GenericIdentifier("gem:" + dependency.getName() + "@" + dependency.getVersion(), Confidence.HIGHEST);
204 } else {
205 id = new GenericIdentifier("gem:" + dependency.getName(), Confidence.HIGHEST);
206 }
207 dependency.addSoftwareIdentifier(id);
208 }
209
210 setPackagePath(dependency);
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224
225 private String addStringEvidence(Dependency dependency, EvidenceType type, String contents,
226 String blockVariable, String field, String fieldPattern, Confidence confidence) {
227 String value = "";
228
229
230 final Matcher arrayMatcher = Pattern.compile(
231 String.format("\\s*?%s\\.%s\\s*?=\\s*?\\[(.*?)\\]", blockVariable, fieldPattern), Pattern.CASE_INSENSITIVE).matcher(contents);
232 if (arrayMatcher.find()) {
233 final String arrayValue = arrayMatcher.group(1);
234 value = arrayValue.replaceAll("['\"]", "").trim();
235 } else {
236 final Matcher matcher = Pattern.compile(
237 String.format("\\s*?%s\\.%s\\s*?=\\s*?(['\"])(.*?)\\1", blockVariable, fieldPattern), Pattern.CASE_INSENSITIVE).matcher(contents);
238 if (matcher.find()) {
239 value = matcher.group(2);
240 }
241 }
242 if (value.length() > 0) {
243 dependency.addEvidence(type, GEMSPEC, field, value, confidence);
244 }
245
246 return value;
247 }
248
249
250
251
252
253
254
255
256
257 private String addEvidenceFromVersionFile(Dependency dependency, EvidenceType type, File dependencyFile) {
258 final File parentDir = dependencyFile.getParentFile();
259 String version = null;
260 int versionCount = 0;
261 if (parentDir != null) {
262 final File[] matchingFiles = parentDir.listFiles((dir, name) -> name.contains(VERSION_FILE_NAME));
263 if (matchingFiles == null) {
264 return null;
265 }
266 for (File f : matchingFiles) {
267 try {
268 final List<String> lines = Files.readAllLines(f.toPath(), StandardCharsets.UTF_8);
269 if (lines.size() == 1) {
270 final String value = lines.get(0).trim();
271 if (version == null || !version.equals(value)) {
272 version = value;
273 versionCount++;
274 }
275
276 dependency.addEvidence(type, GEMSPEC, "version", value, Confidence.HIGH);
277 }
278 } catch (IOException e) {
279 LOGGER.debug("Error reading gemspec", e);
280 }
281 }
282 }
283 if (versionCount == 1) {
284 return version;
285 }
286 return null;
287 }
288
289
290
291
292
293
294 private void setPackagePath(Dependency dep) {
295 final File file = new File(dep.getFilePath());
296 final String parent = file.getParent();
297 if (parent != null) {
298 dep.setPackagePath(parent);
299 }
300 }
301 }