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 org.apache.commons.io.filefilter.NameFileFilter;
28 import org.apache.commons.io.filefilter.SuffixFileFilter;
29 import org.apache.commons.io.input.AutoCloseInputStream;
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 collectWheelMetadata(
232 dependency,
233 getMatchingFile(getMatchingFile(temp, folderFilter),
234 metadataFilter));
235 }
236
237
238
239
240
241
242
243 @Override
244 protected void initializeFileTypeAnalyzer() throws InitializationException {
245 try {
246 final File baseDir = Settings.getTempDirectory();
247 tempFileLocation = File.createTempFile("check", "tmp", baseDir);
248 if (!tempFileLocation.delete()) {
249 setEnabled(false);
250 final String msg = String.format(
251 "Unable to delete temporary file '%s'.",
252 tempFileLocation.getAbsolutePath());
253 throw new InitializationException(msg);
254 }
255 if (!tempFileLocation.mkdirs()) {
256 setEnabled(false);
257 final String msg = String.format(
258 "Unable to create directory '%s'.",
259 tempFileLocation.getAbsolutePath());
260 throw new InitializationException(msg);
261 }
262 } catch (IOException ex) {
263 setEnabled(false);
264 throw new InitializationException("Unable to create a temporary file", ex);
265 }
266 }
267
268
269
270
271 @Override
272 public void close() {
273 if (tempFileLocation != null && tempFileLocation.exists()) {
274 LOGGER.debug("Attempting to delete temporary files");
275 final boolean success = FileUtils.delete(tempFileLocation);
276 if (!success) {
277 LOGGER.warn(
278 "Failed to delete some temporary files, see the log for more details");
279 }
280 }
281 }
282
283
284
285
286
287
288
289 private static void collectWheelMetadata(Dependency dependency, File file) {
290 final InternetHeaders headers = getManifestProperties(file);
291 addPropertyToEvidence(headers, dependency.getVersionEvidence(),
292 "Version", Confidence.HIGHEST);
293 addPropertyToEvidence(headers, dependency.getProductEvidence(), "Name",
294 Confidence.HIGHEST);
295 final String url = headers.getHeader("Home-page", null);
296 final EvidenceCollection vendorEvidence = dependency
297 .getVendorEvidence();
298 if (StringUtils.isNotBlank(url)) {
299 if (UrlStringUtils.isUrl(url)) {
300 vendorEvidence.addEvidence(METADATA, "vendor", url,
301 Confidence.MEDIUM);
302 }
303 }
304 addPropertyToEvidence(headers, vendorEvidence, "Author", Confidence.LOW);
305 final String summary = headers.getHeader("Summary", null);
306 if (StringUtils.isNotBlank(summary)) {
307 JarAnalyzer
308 .addDescription(dependency, summary, METADATA, "summary");
309 }
310 }
311
312
313
314
315
316
317
318
319
320 private static void addPropertyToEvidence(InternetHeaders headers,
321 EvidenceCollection evidence, String property, Confidence confidence) {
322 final String value = headers.getHeader(property, null);
323 LOGGER.debug("Property: {}, Value: {}", property, value);
324 if (StringUtils.isNotBlank(value)) {
325 evidence.addEvidence(METADATA, property, value, confidence);
326 }
327 }
328
329
330
331
332
333
334
335
336
337 private static File getMatchingFile(File folder, FilenameFilter filter) {
338 File result = null;
339 final File[] matches = folder.listFiles(filter);
340 if (null != matches && 1 == matches.length) {
341 result = matches[0];
342 }
343 return result;
344 }
345
346
347
348
349
350
351
352 private static InternetHeaders getManifestProperties(File manifest) {
353 final InternetHeaders result = new InternetHeaders();
354 if (null == manifest) {
355 LOGGER.debug("Manifest file not found.");
356 } else {
357 try {
358 result.load(new AutoCloseInputStream(new BufferedInputStream(
359 new FileInputStream(manifest))));
360 } catch (MessagingException e) {
361 LOGGER.warn(e.getMessage(), e);
362 } catch (FileNotFoundException e) {
363 LOGGER.warn(e.getMessage(), e);
364 }
365 }
366 return result;
367 }
368
369
370
371
372
373
374
375
376 private File getNextTempDirectory() throws AnalysisException {
377 File directory;
378
379
380
381 do {
382 dirCount += 1;
383 directory = new File(tempFileLocation, String.valueOf(dirCount));
384 } while (directory.exists());
385 if (!directory.mkdirs()) {
386 throw new AnalysisException(String.format(
387 "Unable to create temp directory '%s'.",
388 directory.getAbsolutePath()));
389 }
390 return directory;
391 }
392 }