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 com.github.packageurl.MalformedPackageURLException;
21 import com.github.packageurl.PackageURL;
22 import com.github.packageurl.PackageURLBuilder;
23 import org.apache.commons.io.filefilter.NameFileFilter;
24 import org.apache.commons.io.filefilter.SuffixFileFilter;
25 import org.owasp.dependencycheck.Engine;
26 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
27 import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
28 import org.owasp.dependencycheck.dependency.Confidence;
29 import org.owasp.dependencycheck.dependency.Dependency;
30 import org.owasp.dependencycheck.dependency.EvidenceType;
31 import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
32 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
33 import org.owasp.dependencycheck.exception.InitializationException;
34 import org.owasp.dependencycheck.utils.FileFilterBuilder;
35 import org.owasp.dependencycheck.utils.Settings;
36 import org.owasp.dependencycheck.utils.UrlStringUtils;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import javax.annotation.concurrent.ThreadSafe;
41 import java.io.File;
42 import java.io.FileFilter;
43 import java.io.IOException;
44 import java.nio.charset.StandardCharsets;
45 import java.nio.file.Files;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48
49
50
51
52
53
54
55 @Experimental
56 @ThreadSafe
57 public class PythonPackageAnalyzer extends AbstractFileTypeAnalyzer {
58
59
60
61
62 private static final Logger LOGGER = LoggerFactory.getLogger(PythonPackageAnalyzer.class);
63
64
65
66
67
68 public static final String DEPENDENCY_ECOSYSTEM = Ecosystem.PYTHON;
69
70
71
72
73 private static final int REGEX_OPTIONS = Pattern.DOTALL | Pattern.CASE_INSENSITIVE;
74
75
76
77
78 private static final String EXTENSIONS = "py";
79
80
81
82
83 private static final Pattern MODULE_DOCSTRING = Pattern.compile("^(['\\\"]{3})(.*?)\\1", REGEX_OPTIONS);
84
85
86
87
88 private static final Pattern VERSION_PATTERN = Pattern.compile("\\b(__)?version(__)? *= *(['\"]+)(\\d+\\.\\d+.*?)\\3",
89 REGEX_OPTIONS);
90
91
92
93
94 private static final Pattern TITLE_PATTERN = compileAssignPattern("title");
95
96
97
98
99 private static final Pattern SUMMARY_PATTERN = compileAssignPattern("summary");
100
101
102
103
104 private static final Pattern URI_PATTERN = compileAssignPattern("ur[il]");
105
106
107
108
109 private static final Pattern HOMEPAGE_PATTERN = compileAssignPattern("home_?page");
110
111
112
113
114 private static final Pattern AUTHOR_PATTERN = compileAssignPattern("author");
115
116
117
118
119 private static final FileFilter INIT_PY_FILTER = new NameFileFilter("__init__.py");
120
121
122
123
124 private static final FileFilter PY_FILTER = new SuffixFileFilter(".py");
125
126
127
128
129 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(EXTENSIONS).build();
130
131
132
133
134
135
136 @Override
137 public String getName() {
138 return "Python Package Analyzer";
139 }
140
141
142
143
144
145
146 @Override
147 public AnalysisPhase getAnalysisPhase() {
148 return AnalysisPhase.INFORMATION_COLLECTION;
149 }
150
151
152
153
154
155
156 @Override
157 protected String getAnalyzerEnabledSettingKey() {
158 return Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED;
159 }
160
161
162
163
164
165
166 @Override
167 protected FileFilter getFileFilter() {
168 return FILTER;
169 }
170
171
172
173
174
175
176
177 @Override
178 protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
179
180 }
181
182
183
184
185
186
187
188 private static Pattern compileAssignPattern(String name) {
189 return Pattern.compile(
190 String.format("\\b(__)?%s(__)?\\b *= *(['\"]+)(.*?)\\3", name),
191 REGEX_OPTIONS);
192 }
193
194
195
196
197
198
199
200
201
202 @Override
203 protected void analyzeDependency(Dependency dependency, Engine engine)
204 throws AnalysisException {
205 dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
206 final File file = dependency.getActualFile();
207 final File parent = file.getParentFile();
208 final String parentName = parent.getName();
209 if (INIT_PY_FILTER.accept(file)) {
210
211
212
213 dependency.addEvidence(EvidenceType.PRODUCT, file.getName(), "PackageName", parentName, Confidence.HIGHEST);
214 dependency.setName(parentName);
215
216 final File[] fileList = parent.listFiles(PY_FILTER);
217 if (fileList != null) {
218 for (final File sourceFile : fileList) {
219 analyzeFileContents(dependency, sourceFile);
220 }
221 }
222 } else {
223 engine.removeDependency(dependency);
224 }
225 }
226
227
228
229
230
231
232
233
234
235
236 private void analyzeFileContents(Dependency dependency, File file)
237 throws AnalysisException {
238 final String contents;
239 try {
240 contents = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8).trim();
241 } catch (IOException e) {
242 throw new AnalysisException("Problem occurred while reading dependency file.", e);
243 }
244 if (!contents.isEmpty()) {
245 final String source = file.getName();
246 gatherEvidence(dependency, EvidenceType.VERSION, VERSION_PATTERN, contents,
247 source, "SourceVersion", Confidence.MEDIUM);
248 addSummaryInfo(dependency, SUMMARY_PATTERN, 4, contents,
249 source, "summary");
250 if (INIT_PY_FILTER.accept(file)) {
251 addSummaryInfo(dependency, MODULE_DOCSTRING, 2,
252 contents, source, "docstring");
253 }
254 gatherEvidence(dependency, EvidenceType.PRODUCT, TITLE_PATTERN, contents,
255 source, "SourceTitle", Confidence.LOW);
256
257 gatherEvidence(dependency, EvidenceType.VENDOR, AUTHOR_PATTERN, contents,
258 source, "SourceAuthor", Confidence.MEDIUM);
259 gatherHomePageEvidence(dependency, EvidenceType.VENDOR, URI_PATTERN,
260 source, "URL", contents);
261 gatherHomePageEvidence(dependency, EvidenceType.VENDOR, HOMEPAGE_PATTERN,
262 source, "HomePage", contents);
263
264 try {
265 final PackageURLBuilder builder = PackageURLBuilder.aPackageURL().withType("pypi").withName(dependency.getName());
266 if (dependency.getVersion() != null) {
267 builder.withVersion(dependency.getVersion());
268 }
269 final PackageURL purl = builder.build();
270 dependency.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
271 } catch (MalformedPackageURLException ex) {
272 LOGGER.debug("Unable to build package url for python", ex);
273 final GenericIdentifier id;
274 if (dependency.getVersion() != null) {
275 id = new GenericIdentifier("generic:" + dependency.getName() + "@" + dependency.getVersion(), Confidence.HIGHEST);
276 } else {
277 id = new GenericIdentifier("generic:" + dependency.getName(), Confidence.HIGHEST);
278 }
279 dependency.addSoftwareIdentifier(id);
280 }
281 }
282 }
283
284
285
286
287
288
289
290
291
292
293
294 private void addSummaryInfo(Dependency dependency, Pattern pattern,
295 int group, String contents, String source, String key) {
296 final Matcher matcher = pattern.matcher(contents);
297 final boolean found = matcher.find();
298 if (found) {
299 JarAnalyzer.addDescription(dependency, matcher.group(group),
300 source, key);
301 }
302 }
303
304
305
306
307
308
309
310
311
312
313
314 private void gatherHomePageEvidence(Dependency dependency, EvidenceType type, Pattern pattern,
315 String source, String name, String contents) {
316 final Matcher matcher = pattern.matcher(contents);
317 if (matcher.find()) {
318 final String url = matcher.group(4);
319 if (UrlStringUtils.isUrl(url)) {
320 dependency.addEvidence(type, source, name, url, Confidence.MEDIUM);
321 }
322 }
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336
337 private void gatherEvidence(Dependency dependency, EvidenceType type, Pattern pattern, String contents,
338 String source, String name, Confidence confidence) {
339 final Matcher matcher = pattern.matcher(contents);
340 final boolean found = matcher.find();
341 if (found) {
342 dependency.addEvidence(type, source, name, matcher.group(4), confidence);
343 if (type == EvidenceType.VERSION) {
344
345 dependency.setVersion(matcher.group(4));
346 final String dispName = String.format("%s:%s", dependency.getName(), dependency.getVersion());
347 dependency.setDisplayFileName(dispName);
348 }
349 }
350 }
351 }