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