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.BufferedInputStream;
21 import java.io.File;
22 import java.io.FileFilter;
23 import java.io.FileInputStream;
24 import java.io.FileNotFoundException;
25 import java.io.FilenameFilter;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import org.apache.commons.io.filefilter.NameFileFilter;
29 import org.apache.commons.io.filefilter.SuffixFileFilter;
30 import org.apache.commons.lang3.StringUtils;
31 import org.owasp.dependencycheck.Engine;
32 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
33 import org.owasp.dependencycheck.dependency.Confidence;
34 import org.owasp.dependencycheck.dependency.Dependency;
35 import org.owasp.dependencycheck.dependency.EvidenceCollection;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import javax.mail.MessagingException;
40 import javax.mail.internet.InternetHeaders;
41 import org.owasp.dependencycheck.exception.InitializationException;
42 import org.owasp.dependencycheck.utils.ExtractionException;
43 import org.owasp.dependencycheck.utils.ExtractionUtil;
44 import org.owasp.dependencycheck.utils.FileFilterBuilder;
45 import org.owasp.dependencycheck.utils.FileUtils;
46 import org.owasp.dependencycheck.utils.Settings;
47 import org.owasp.dependencycheck.utils.UrlStringUtils;
48 import java.util.concurrent.atomic.AtomicInteger;
49
50
51
52
53
54
55
56
57 @Experimental
58 public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
59
60
61
62
63 private static final String PKG_INFO = "PKG-INFO";
64
65
66
67
68 private static final String METADATA = "METADATA";
69
70
71
72
73 private static final Logger LOGGER = LoggerFactory
74 .getLogger(PythonDistributionAnalyzer.class);
75
76
77
78
79
80 private static final AtomicInteger DIR_COUNT = new AtomicInteger(0);
81
82
83
84
85 private static final String ANALYZER_NAME = "Python Distribution Analyzer";
86
87
88
89 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
90
91
92
93
94 private static final String[] EXTENSIONS = {"whl", "egg", "zip"};
95
96
97
98
99 private static final FileFilter EGG_OR_ZIP = FileFilterBuilder.newInstance().addExtensions("egg", "zip").build();
100
101
102
103
104 private static final FileFilter WHL_FILTER = FileFilterBuilder.newInstance().addExtensions("whl").build();
105
106
107
108
109 private File tempFileLocation;
110
111
112
113
114
115 private static final FilenameFilter DIST_INFO_FILTER = new SuffixFileFilter(
116 ".dist-info");
117
118
119
120
121 private static final FilenameFilter EGG_INFO_FILTER = new NameFileFilter(
122 "EGG-INFO");
123
124
125
126
127 private static final NameFileFilter METADATA_FILTER = new NameFileFilter(
128 METADATA);
129
130
131
132
133 private static final NameFileFilter PKG_INFO_FILTER = new NameFileFilter(
134 PKG_INFO);
135
136
137
138
139 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addFileFilters(
140 METADATA_FILTER, PKG_INFO_FILTER).addExtensions(EXTENSIONS).build();
141
142
143
144
145
146
147 @Override
148 protected FileFilter getFileFilter() {
149 return FILTER;
150 }
151
152
153
154
155
156
157 @Override
158 public String getName() {
159 return ANALYZER_NAME;
160 }
161
162
163
164
165
166
167 @Override
168 public AnalysisPhase getAnalysisPhase() {
169 return ANALYSIS_PHASE;
170 }
171
172
173
174
175
176
177
178 @Override
179 protected String getAnalyzerEnabledSettingKey() {
180 return Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED;
181 }
182
183 @Override
184 protected void analyzeDependency(Dependency dependency, Engine engine)
185 throws AnalysisException {
186 final File actualFile = dependency.getActualFile();
187 if (WHL_FILTER.accept(actualFile)) {
188 collectMetadataFromArchiveFormat(dependency, DIST_INFO_FILTER,
189 METADATA_FILTER);
190 } else if (EGG_OR_ZIP.accept(actualFile)) {
191 collectMetadataFromArchiveFormat(dependency, EGG_INFO_FILTER,
192 PKG_INFO_FILTER);
193 } else {
194 final String name = actualFile.getName();
195 final boolean metadata = METADATA.equals(name);
196 if (metadata || PKG_INFO.equals(name)) {
197 final File parent = actualFile.getParentFile();
198 final String parentName = parent.getName();
199 dependency.setDisplayFileName(parentName + "/" + name);
200 if (parent.isDirectory()
201 && (metadata && parentName.endsWith(".dist-info")
202 || parentName.endsWith(".egg-info") || "EGG-INFO"
203 .equals(parentName))) {
204 collectWheelMetadata(dependency, actualFile);
205 }
206 }
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219 private void collectMetadataFromArchiveFormat(Dependency dependency,
220 FilenameFilter folderFilter, FilenameFilter metadataFilter)
221 throws AnalysisException {
222 final File temp = getNextTempDirectory();
223 LOGGER.debug("{} exists? {}", temp, temp.exists());
224 try {
225 ExtractionUtil.extractFilesUsingFilter(
226 new File(dependency.getActualFilePath()), temp,
227 metadataFilter);
228 } catch (ExtractionException ex) {
229 throw new AnalysisException(ex);
230 }
231
232 File matchingFile = getMatchingFile(temp, folderFilter);
233 if (matchingFile != null) {
234 matchingFile = getMatchingFile(matchingFile, metadataFilter);
235 if (matchingFile != null) {
236 collectWheelMetadata(dependency, matchingFile);
237 }
238 }
239 }
240
241
242
243
244
245
246
247 @Override
248 protected void initializeFileTypeAnalyzer() throws InitializationException {
249 try {
250 final File baseDir = Settings.getTempDirectory();
251 tempFileLocation = File.createTempFile("check", "tmp", baseDir);
252 if (!tempFileLocation.delete()) {
253 setEnabled(false);
254 final String msg = String.format(
255 "Unable to delete temporary file '%s'.",
256 tempFileLocation.getAbsolutePath());
257 throw new InitializationException(msg);
258 }
259 if (!tempFileLocation.mkdirs()) {
260 setEnabled(false);
261 final String msg = String.format(
262 "Unable to create directory '%s'.",
263 tempFileLocation.getAbsolutePath());
264 throw new InitializationException(msg);
265 }
266 } catch (IOException ex) {
267 setEnabled(false);
268 throw new InitializationException("Unable to create a temporary file", ex);
269 }
270 }
271
272
273
274
275 @Override
276 public void closeAnalyzer() {
277 if (tempFileLocation != null && tempFileLocation.exists()) {
278 LOGGER.debug("Attempting to delete temporary files");
279 final boolean success = FileUtils.delete(tempFileLocation);
280 if (!success && tempFileLocation.exists()) {
281 final String[] l = tempFileLocation.list();
282 if (l != null && l.length > 0) {
283 LOGGER.warn("Failed to delete some temporary files, see the log for more details");
284 }
285 }
286 }
287 }
288
289
290
291
292
293
294
295 private static void collectWheelMetadata(Dependency dependency, File file) {
296 final InternetHeaders headers = getManifestProperties(file);
297 addPropertyToEvidence(headers, dependency.getVersionEvidence(),
298 "Version", Confidence.HIGHEST);
299 addPropertyToEvidence(headers, dependency.getProductEvidence(), "Name",
300 Confidence.HIGHEST);
301 final String url = headers.getHeader("Home-page", null);
302 final EvidenceCollection vendorEvidence = dependency
303 .getVendorEvidence();
304 if (StringUtils.isNotBlank(url)) {
305 if (UrlStringUtils.isUrl(url)) {
306 vendorEvidence.addEvidence(METADATA, "vendor", url,
307 Confidence.MEDIUM);
308 }
309 }
310 addPropertyToEvidence(headers, vendorEvidence, "Author", Confidence.LOW);
311 final String summary = headers.getHeader("Summary", null);
312 if (StringUtils.isNotBlank(summary)) {
313 JarAnalyzer
314 .addDescription(dependency, summary, METADATA, "summary");
315 }
316 }
317
318
319
320
321
322
323
324
325
326 private static void addPropertyToEvidence(InternetHeaders headers,
327 EvidenceCollection evidence, String property, Confidence confidence) {
328 final String value = headers.getHeader(property, null);
329 LOGGER.debug("Property: {}, Value: {}", property, value);
330 if (StringUtils.isNotBlank(value)) {
331 evidence.addEvidence(METADATA, property, value, confidence);
332 }
333 }
334
335
336
337
338
339
340
341
342
343 private static File getMatchingFile(File folder, FilenameFilter filter) {
344 File result = null;
345 final File[] matches = folder.listFiles(filter);
346 if (null != matches && 1 == matches.length) {
347 result = matches[0];
348 }
349 return result;
350 }
351
352
353
354
355
356
357
358 private static InternetHeaders getManifestProperties(File manifest) {
359 final InternetHeaders result = new InternetHeaders();
360 if (null == manifest) {
361 LOGGER.debug("Manifest file not found.");
362 } else {
363 InputStream in = null;
364 try {
365 in = new BufferedInputStream(new FileInputStream(manifest));
366 result.load(in);
367 } catch (MessagingException e) {
368 LOGGER.warn(e.getMessage(), e);
369 } catch (FileNotFoundException e) {
370 LOGGER.warn(e.getMessage(), e);
371 } finally {
372 if (in != null) {
373 try {
374 in.close();
375 } catch (IOException ex) {
376 LOGGER.debug("failed to close input stream", ex);
377 }
378 }
379 }
380 }
381 return result;
382 }
383
384
385
386
387
388
389
390
391 private File getNextTempDirectory() throws AnalysisException {
392 File directory;
393
394
395
396 do {
397 final int dirCount = DIR_COUNT.incrementAndGet();
398 directory = new File(tempFileLocation, String.valueOf(dirCount));
399 } while (directory.exists());
400 if (!directory.mkdirs()) {
401 throw new AnalysisException(String.format(
402 "Unable to create temp directory '%s'.",
403 directory.getAbsolutePath()));
404 }
405 return directory;
406 }
407 }