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