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