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 java.io.File;
21 import java.io.FileFilter;
22 import java.io.FilenameFilter;
23 import java.io.IOException;
24 import java.nio.charset.Charset;
25 import java.util.List;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import org.apache.commons.io.FileUtils;
30 import org.owasp.dependencycheck.Engine;
31 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
32 import org.owasp.dependencycheck.dependency.Confidence;
33 import org.owasp.dependencycheck.dependency.Dependency;
34 import org.owasp.dependencycheck.dependency.EvidenceCollection;
35 import org.owasp.dependencycheck.exception.InitializationException;
36 import org.owasp.dependencycheck.utils.FileFilterBuilder;
37 import org.owasp.dependencycheck.utils.Settings;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41
42
43
44
45
46
47
48 @Experimental
49 public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer {
50
51
52
53
54 private static final Logger LOGGER = LoggerFactory.getLogger(RubyGemspecAnalyzer.class);
55
56
57
58 private static final String ANALYZER_NAME = "Ruby Gemspec Analyzer";
59
60
61
62
63 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
64
65
66
67
68 private static final String GEMSPEC = "gemspec";
69
70
71
72
73
74 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(GEMSPEC).build();
75
76
77
78
79
80
81 private static final String VERSION_FILE_NAME = "VERSION";
82
83
84
85
86 @Override
87 protected FileFilter getFileFilter() {
88 return FILTER;
89 }
90
91 @Override
92 protected void initializeFileTypeAnalyzer() throws InitializationException {
93
94 }
95
96
97
98
99
100
101 @Override
102 public String getName() {
103 return ANALYZER_NAME;
104 }
105
106
107
108
109
110
111 @Override
112 public AnalysisPhase getAnalysisPhase() {
113 return ANALYSIS_PHASE;
114 }
115
116
117
118
119
120
121
122 @Override
123 protected String getAnalyzerEnabledSettingKey() {
124 return Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED;
125 }
126
127
128
129
130 private static final Pattern GEMSPEC_BLOCK_INIT = Pattern.compile("Gem::Specification\\.new\\s+?do\\s+?\\|(.+?)\\|");
131
132 @Override
133 protected void analyzeDependency(Dependency dependency, Engine engine)
134 throws AnalysisException {
135 String contents;
136 try {
137 contents = FileUtils.readFileToString(dependency.getActualFile(), Charset.defaultCharset());
138 } catch (IOException e) {
139 throw new AnalysisException(
140 "Problem occurred while reading dependency file.", e);
141 }
142 final Matcher matcher = GEMSPEC_BLOCK_INIT.matcher(contents);
143 if (matcher.find()) {
144 contents = contents.substring(matcher.end());
145 final String blockVariable = matcher.group(1);
146
147 final EvidenceCollection vendor = dependency.getVendorEvidence();
148 final EvidenceCollection product = dependency.getProductEvidence();
149 final String name = addStringEvidence(product, contents, blockVariable, "name", "name", Confidence.HIGHEST);
150 if (!name.isEmpty()) {
151 vendor.addEvidence(GEMSPEC, "name_project", name + "_project", Confidence.LOW);
152 }
153 addStringEvidence(product, contents, blockVariable, "summary", "summary", Confidence.LOW);
154
155 addStringEvidence(vendor, contents, blockVariable, "author", "authors?", Confidence.HIGHEST);
156 addStringEvidence(vendor, contents, blockVariable, "email", "emails?", Confidence.MEDIUM);
157 addStringEvidence(vendor, contents, blockVariable, "homepage", "homepage", Confidence.HIGHEST);
158 addStringEvidence(vendor, contents, blockVariable, "license", "licen[cs]es?", Confidence.HIGHEST);
159
160 final String value = addStringEvidence(dependency.getVersionEvidence(), contents,
161 blockVariable, "version", "version", Confidence.HIGHEST);
162 if (value.length() < 1) {
163 addEvidenceFromVersionFile(dependency.getActualFile(), dependency.getVersionEvidence());
164 }
165 }
166
167 setPackagePath(dependency);
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181 private String addStringEvidence(EvidenceCollection evidences, String contents,
182 String blockVariable, String field, String fieldPattern, Confidence confidence) {
183 String value = "";
184
185
186 final Matcher arrayMatcher = Pattern.compile(
187 String.format("\\s*?%s\\.%s\\s*?=\\s*?\\[(.*?)\\]", blockVariable, fieldPattern), Pattern.CASE_INSENSITIVE).matcher(contents);
188 if (arrayMatcher.find()) {
189 final String arrayValue = arrayMatcher.group(1);
190 value = arrayValue.replaceAll("['\"]", "").trim();
191 } else {
192 final Matcher matcher = Pattern.compile(
193 String.format("\\s*?%s\\.%s\\s*?=\\s*?(['\"])(.*?)\\1", blockVariable, fieldPattern), Pattern.CASE_INSENSITIVE).matcher(contents);
194 if (matcher.find()) {
195 value = matcher.group(2);
196 }
197 }
198 if (value.length() > 0) {
199 evidences.addEvidence(GEMSPEC, field, value, confidence);
200 }
201
202 return value;
203 }
204
205
206
207
208
209
210
211 private void addEvidenceFromVersionFile(File dependencyFile, EvidenceCollection versionEvidences) {
212 final File parentDir = dependencyFile.getParentFile();
213 if (parentDir != null) {
214 final File[] matchingFiles = parentDir.listFiles(new FilenameFilter() {
215 @Override
216 public boolean accept(File dir, String name) {
217 return name.contains(VERSION_FILE_NAME);
218 }
219 });
220 if (matchingFiles == null) {
221 return;
222 }
223 for (File f : matchingFiles) {
224 try {
225 final List<String> lines = FileUtils.readLines(f, Charset.defaultCharset());
226 if (lines.size() == 1) {
227 final String value = lines.get(0).trim();
228 versionEvidences.addEvidence(GEMSPEC, "version", value, Confidence.HIGH);
229 }
230 } catch (IOException e) {
231 LOGGER.debug("Error reading gemspec", e);
232 }
233 }
234 }
235 }
236
237
238
239
240
241
242 private void setPackagePath(Dependency dep) {
243 final File file = new File(dep.getFilePath());
244 final String parent = file.getParent();
245 if (parent != null) {
246 dep.setPackagePath(parent);
247 }
248 }
249 }