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 org.apache.commons.io.FileUtils;
21 import org.apache.commons.lang3.StringUtils;
22 import org.owasp.dependencycheck.Engine;
23 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
24 import org.owasp.dependencycheck.dependency.Confidence;
25 import org.owasp.dependencycheck.dependency.Dependency;
26 import org.owasp.dependencycheck.utils.Checksum;
27 import org.owasp.dependencycheck.utils.FileFilterBuilder;
28 import org.owasp.dependencycheck.utils.Settings;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import java.io.File;
33 import java.io.FileFilter;
34 import java.io.IOException;
35 import java.io.UnsupportedEncodingException;
36 import java.nio.charset.Charset;
37 import java.security.MessageDigest;
38 import java.security.NoSuchAlgorithmException;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 import org.owasp.dependencycheck.exception.InitializationException;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 @Experimental
58 public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
59
60
61
62
63 private static final Logger LOGGER = LoggerFactory.getLogger(CMakeAnalyzer.class);
64
65
66
67
68 private static final int REGEX_OPTIONS = Pattern.DOTALL
69 | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE;
70
71
72
73
74 private static final Pattern PROJECT = Pattern.compile(
75 "^ *project *\\([ \\n]*(\\w+)[ \\n]*.*?\\)", REGEX_OPTIONS);
76
77
78
79
80
81
82
83
84 private static final Pattern SET_VERSION = Pattern
85 .compile(
86 "^ *set\\s*\\(\\s*(\\w+)_version\\s+\"?(\\d+(?:\\.\\d+)+)[\\s\"]?\\)",
87 REGEX_OPTIONS);
88
89
90
91
92 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(".cmake")
93 .addFilenames("CMakeLists.txt").build();
94
95
96
97
98
99
100 @Override
101 public String getName() {
102 return "CMake Analyzer";
103 }
104
105
106
107
108
109
110 @Override
111 public AnalysisPhase getAnalysisPhase() {
112 return AnalysisPhase.INFORMATION_COLLECTION;
113 }
114
115
116
117
118
119
120 @Override
121 protected FileFilter getFileFilter() {
122 return FILTER;
123 }
124
125
126
127
128
129
130
131 @Override
132 protected void initializeFileTypeAnalyzer() throws InitializationException {
133 try {
134 getSha1MessageDigest();
135 } catch (IllegalStateException ex) {
136 setEnabled(false);
137 throw new InitializationException("Unable to create SHA1 MessageDigest", ex);
138 }
139 }
140
141
142
143
144
145
146
147
148
149 @Override
150 protected void analyzeDependency(Dependency dependency, Engine engine)
151 throws AnalysisException {
152 final File file = dependency.getActualFile();
153 final String parentName = file.getParentFile().getName();
154 final String name = file.getName();
155 dependency.setDisplayFileName(String.format("%s%c%s", parentName, File.separatorChar, name));
156 String contents;
157 try {
158 contents = FileUtils.readFileToString(file, Charset.defaultCharset()).trim();
159 } catch (IOException e) {
160 throw new AnalysisException(
161 "Problem occurred while reading dependency file.", e);
162 }
163
164 if (StringUtils.isNotBlank(contents)) {
165 final Matcher m = PROJECT.matcher(contents);
166 int count = 0;
167 while (m.find()) {
168 count++;
169 LOGGER.debug(String.format(
170 "Found project command match with %d groups: %s",
171 m.groupCount(), m.group(0)));
172 final String group = m.group(1);
173 LOGGER.debug("Group 1: " + group);
174 dependency.getProductEvidence().addEvidence(name, "Project",
175 group, Confidence.HIGH);
176 }
177 LOGGER.debug("Found {} matches.", count);
178 analyzeSetVersionCommand(dependency, engine, contents);
179 }
180 }
181
182
183
184
185
186
187
188
189
190
191 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
192 value = "DM_DEFAULT_ENCODING",
193 justification = "Default encoding is only used if UTF-8 is not available")
194 private void analyzeSetVersionCommand(Dependency dependency, Engine engine, String contents) {
195 Dependency currentDep = dependency;
196
197 final Matcher m = SET_VERSION.matcher(contents);
198 int count = 0;
199 while (m.find()) {
200 count++;
201 LOGGER.debug("Found project command match with {} groups: {}",
202 m.groupCount(), m.group(0));
203 String product = m.group(1);
204 final String version = m.group(2);
205 LOGGER.debug("Group 1: " + product);
206 LOGGER.debug("Group 2: " + version);
207 final String aliasPrefix = "ALIASOF_";
208 if (product.startsWith(aliasPrefix)) {
209 product = product.replaceFirst(aliasPrefix, "");
210 }
211 if (count > 1) {
212
213 currentDep = new Dependency(dependency.getActualFile());
214 currentDep.setDisplayFileName(String.format("%s:%s", dependency.getDisplayFileName(), product));
215 final String filePath = String.format("%s:%s", dependency.getFilePath(), product);
216 currentDep.setFilePath(filePath);
217
218 byte[] path;
219 try {
220 path = filePath.getBytes("UTF-8");
221 } catch (UnsupportedEncodingException ex) {
222 path = filePath.getBytes();
223 }
224 final MessageDigest sha1 = getSha1MessageDigest();
225 currentDep.setSha1sum(Checksum.getHex(sha1.digest(path)));
226 engine.getDependencies().add(currentDep);
227 }
228 final String source = currentDep.getDisplayFileName();
229 currentDep.getProductEvidence().addEvidence(source, "Product",
230 product, Confidence.MEDIUM);
231 currentDep.getVersionEvidence().addEvidence(source, "Version",
232 version, Confidence.MEDIUM);
233 }
234 LOGGER.debug(String.format("Found %d matches.", count));
235 }
236
237 @Override
238 protected String getAnalyzerEnabledSettingKey() {
239 return Settings.KEYS.ANALYZER_CMAKE_ENABLED;
240 }
241
242
243
244
245
246
247 private MessageDigest getSha1MessageDigest() {
248 try {
249 return MessageDigest.getInstance("SHA1");
250 } catch (NoSuchAlgorithmException e) {
251 LOGGER.error(e.getMessage());
252 throw new IllegalStateException("Failed to obtain the SHA1 message digest.", e);
253 }
254 }
255 }