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.fasterxml.jackson.databind.JsonNode;
21 import com.fasterxml.jackson.databind.ObjectMapper;
22 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
23 import com.github.packageurl.MalformedPackageURLException;
24 import com.github.packageurl.PackageURL;
25 import com.github.packageurl.PackageURLBuilder;
26 import org.owasp.dependencycheck.Engine;
27 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
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.PurlIdentifier;
32 import org.owasp.dependencycheck.utils.Checksum;
33 import org.owasp.dependencycheck.utils.FileFilterBuilder;
34 import org.owasp.dependencycheck.utils.Settings;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import javax.annotation.concurrent.ThreadSafe;
39 import java.io.File;
40 import java.io.FileFilter;
41 import java.io.IOException;
42 import java.util.Iterator;
43 import java.util.Map;
44
45
46
47
48
49
50
51 @Experimental
52 @ThreadSafe
53 public class DartAnalyzer extends AbstractFileTypeAnalyzer {
54
55
56
57
58
59
60
61
62
63
64 private static final Logger LOGGER = LoggerFactory.getLogger(DartAnalyzer.class);
65
66
67
68
69 private static final String LOCK_FILE = "pubspec.lock";
70
71
72
73 private static final String YAML_FILE = "pubspec.yaml";
74
75 @Override
76 protected FileFilter getFileFilter() {
77 return FileFilterBuilder.newInstance().addFilenames(LOCK_FILE, YAML_FILE).build();
78 }
79
80 @Override
81 protected void prepareFileTypeAnalyzer(Engine engine) {
82
83 }
84
85 @Override
86 public String getName() {
87 return "Dart Package Analyzer";
88 }
89
90
91
92
93 @Override
94 public AnalysisPhase getAnalysisPhase() {
95 return AnalysisPhase.INFORMATION_COLLECTION;
96 }
97
98
99
100
101
102
103
104 @Override
105 protected String getAnalyzerEnabledSettingKey() {
106 return Settings.KEYS.ANALYZER_DART_ENABLED;
107 }
108
109 @Override
110 protected void analyzeDependency(Dependency dependency, Engine engine)
111 throws AnalysisException {
112 final String fileName = dependency.getFileName();
113 LOGGER.debug("Checking file {}", fileName);
114
115 switch (fileName) {
116 case LOCK_FILE:
117 analyzeLockFileDependencies(dependency, engine);
118 break;
119 case YAML_FILE:
120 analyzeYamlFileDependencies(dependency, engine);
121 break;
122 default:
123
124 }
125 }
126
127 private void analyzeYamlFileDependencies(Dependency yamlFileDependency, Engine engine) throws AnalysisException {
128 engine.removeDependency(yamlFileDependency);
129 final File yamlFile = yamlFileDependency.getActualFile();
130 if (YAML_FILE.equals(yamlFile.getName())) {
131 final File lock = new File(yamlFile.getParentFile(), LOCK_FILE);
132 if (lock.isFile()) {
133 LOGGER.debug("Skipping {} because {} exists", yamlFile, lock);
134 return;
135 }
136 }
137
138 final JsonNode rootNode;
139 final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
140 try {
141 rootNode = objectMapper.readTree(yamlFile);
142 } catch (IOException e) {
143 throw new AnalysisException("Problem occurred while reading dependency file.", e);
144 }
145
146 if (rootNode.hasNonNull("dependencies")) {
147 final Iterator<Map.Entry<String, JsonNode>> dependencies = rootNode.get("dependencies").fields();
148 addYamlDependenciesToEngine(dependencies, yamlFile, engine);
149 }
150
151 if (rootNode.hasNonNull("dev_dependencies")) {
152 final Iterator<Map.Entry<String, JsonNode>> devDependencies = rootNode.get("dev_dependencies").fields();
153 addYamlDependenciesToEngine(devDependencies, yamlFile, engine);
154 }
155
156 addYamlDartDependencyToEngine(rootNode, yamlFile, engine);
157 }
158
159 private void analyzeLockFileDependencies(Dependency lockFileDependency, Engine engine) throws AnalysisException {
160 engine.removeDependency(lockFileDependency);
161
162 final JsonNode rootNode;
163 final File lockFile = lockFileDependency.getActualFile();
164 final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
165 try {
166 rootNode = objectMapper.readTree(lockFile);
167 } catch (IOException e) {
168 throw new AnalysisException("Problem occurred while reading dependency lockFile.", e);
169 }
170
171 addLockFileDependenciesToEngine(lockFile, engine, rootNode);
172 addLockFileDartVersionToEngine(lockFile, engine, rootNode);
173 }
174
175 private void addLockFileDartVersionToEngine(File file, Engine engine, JsonNode rootNode) throws AnalysisException {
176 final String dartVersion = rootNode.get("sdks").get("dart").textValue();
177 final String minimumVersion = extractMinimumVersion(dartVersion);
178
179 engine.addDependency(
180 createDependencyFromNameAndVersion(file, "dart_software_development_kit", minimumVersion));
181 }
182
183 private void addLockFileDependenciesToEngine(File file, Engine engine, JsonNode rootNode) throws AnalysisException {
184 for (JsonNode nextPackage : rootNode.get("packages")) {
185 final JsonNode description = nextPackage.get("description");
186 if (description == null) {
187 continue;
188 }
189
190 final JsonNode nameNode = description.get("name");
191 if (nameNode == null) {
192 continue;
193 }
194
195 final JsonNode versionNode = nextPackage.get("version");
196 if (versionNode == null) {
197 continue;
198 }
199
200 final String name = nameNode.asText();
201 final String version = versionNode.asText();
202 LOGGER.debug("Found dependency in {} file, name: {}, version: {}", LOCK_FILE, name, version);
203
204 engine.addDependency(
205 createDependencyFromNameAndVersion(file, name, version)
206 );
207 }
208 }
209
210 private void addYamlDependenciesToEngine(Iterator<Map.Entry<String, JsonNode>> dependencies, File file, Engine engine) throws AnalysisException {
211 while (dependencies.hasNext()) {
212 final Map.Entry<String, JsonNode> entry = dependencies.next();
213
214 final String name = entry.getKey();
215 final String versionRaw = entry.getValue().asText();
216 final String version = extractMinimumVersion(versionRaw);
217 LOGGER.debug("Found dependency in {} file, name: {}, version: {}", YAML_FILE, name, version);
218
219 engine.addDependency(
220 createDependencyFromNameAndVersion(file, name, version)
221 );
222 }
223 }
224
225 private void addYamlDartDependencyToEngine(JsonNode rootNode, File file, Engine engine) throws AnalysisException {
226 if (rootNode.hasNonNull("environment") && rootNode.get("environment").hasNonNull("sdk")) {
227 final String dartVersion = rootNode.get("environment").get("sdk").textValue();
228 final String minimumVersion = extractMinimumVersion(dartVersion);
229
230 engine.addDependency(
231 createDependencyFromNameAndVersion(file, "dart_software_development_kit", minimumVersion));
232 }
233 }
234
235 private Dependency createDependencyFromNameAndVersion(File file, String name, String version) throws AnalysisException {
236 final Dependency dependency = new Dependency(file, true);
237
238
239
240 dependency.setName(name);
241 dependency.setVersion(version);
242
243 final PackageURL packageURL;
244 try {
245 packageURL = PackageURLBuilder
246 .aPackageURL()
247 .withType("pub")
248 .withName(dependency.getName())
249 .withVersion(version.isEmpty() ? null : version)
250 .build();
251
252 } catch (MalformedPackageURLException e) {
253 throw new AnalysisException("Problem occurred while reading dependency file.", e);
254 }
255
256 dependency.addSoftwareIdentifier(new PurlIdentifier(packageURL, Confidence.HIGHEST));
257
258 dependency.addEvidence(EvidenceType.PRODUCT, file.getName(), "name", name, Confidence.HIGHEST);
259 dependency.addEvidence(EvidenceType.VENDOR, file.getName(), "name", name, Confidence.HIGHEST);
260 dependency.addEvidence(EvidenceType.VENDOR, file.getName(), "name", "dart", Confidence.HIGHEST);
261 if (!version.isEmpty()) {
262 dependency.addEvidence(EvidenceType.VERSION, file.getName(), "version", version, Confidence.MEDIUM);
263 }
264
265 final String packagePath = String.format("%s:%s", name, version);
266 dependency.setSha1sum(Checksum.getSHA1Checksum(packagePath));
267 dependency.setSha256sum(Checksum.getSHA256Checksum(packagePath));
268 dependency.setMd5sum(Checksum.getMD5Checksum(packagePath));
269 dependency.setPackagePath(packagePath);
270 dependency.setDisplayFileName(packagePath);
271
272 return dependency;
273 }
274
275 private String extractMinimumVersion(String versionRaw) {
276 final String version;
277 if (versionRaw.contains("^")) {
278 version = versionRaw.replace("^", "");
279 } else if (versionRaw.contains("<")) {
280
281 final String firstPart = versionRaw.split("<")[0].trim();
282 version = firstPart.replace(">=", "").trim();
283 } else if (versionRaw.contains("any") || "null".equals(versionRaw)) {
284 version = "";
285 } else {
286 version = versionRaw;
287 }
288
289 LOGGER.debug("Extracted minimum version: {} from raw version: {}", version, versionRaw);
290 return version;
291 }
292 }