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