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