View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2016 IBM Corporation. All Rights Reserved.
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.owasp.dependencycheck.Engine;
24  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
25  import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
26  import org.owasp.dependencycheck.dependency.Confidence;
27  import org.owasp.dependencycheck.dependency.Dependency;
28  import org.owasp.dependencycheck.dependency.EvidenceType;
29  import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
30  import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
31  import org.owasp.dependencycheck.utils.FileFilterBuilder;
32  import org.owasp.dependencycheck.utils.Settings;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import javax.annotation.concurrent.ThreadSafe;
37  import java.io.File;
38  import java.io.FileFilter;
39  import java.io.IOException;
40  import java.nio.charset.StandardCharsets;
41  import java.nio.file.Files;
42  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  
45  /**
46   * This analyzer is used to analyze the SWIFT Package Manager
47   * (https://swift.org/package-manager/). It collects information about a package
48   * from Package.swift files.
49   *
50   * @author Bianca Jiang (https://twitter.com/biancajiang)
51   */
52  @Experimental
53  @ThreadSafe
54  public class SwiftPackageManagerAnalyzer extends AbstractFileTypeAnalyzer {
55  
56      /**
57       * The logger.
58       */
59      private static final Logger LOGGER = LoggerFactory.getLogger(SwiftPackageManagerAnalyzer.class);
60  
61      /**
62       * A descriptor for the type of dependencies processed or added by this
63       * analyzer.
64       */
65      public static final String DEPENDENCY_ECOSYSTEM = Ecosystem.IOS;
66  
67      /**
68       * The name of the analyzer.
69       */
70      private static final String ANALYZER_NAME = "SWIFT Package Manager Analyzer";
71  
72      /**
73       * The phase that this analyzer is intended to run in.
74       */
75      private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
76  
77      /**
78       * The file name to scan.
79       */
80      public static final String SPM_FILE_NAME = "Package.swift";
81  
82      /**
83       * Filter that detects files named "Package.swift".
84       */
85      private static final FileFilter SPM_FILE_FILTER = FileFilterBuilder.newInstance().addFilenames(SPM_FILE_NAME).build();
86  
87      /**
88       * The capture group #1 is the block variable. e.g. "import
89       * PackageDescription let package = Package( name: "Gloss" )"
90       */
91      private static final Pattern SPM_BLOCK_PATTERN = Pattern.compile("let[^=]+=\\s*Package\\s*\\(\\s*([^)]*)\\s*\\)", Pattern.DOTALL);
92  
93      /**
94       * Returns the FileFilter
95       *
96       * @return the FileFilter
97       */
98      @Override
99      protected FileFilter getFileFilter() {
100         return SPM_FILE_FILTER;
101     }
102 
103     @Override
104     protected void prepareFileTypeAnalyzer(Engine engine) {
105         // NO-OP
106     }
107 
108     /**
109      * Returns the name of the analyzer.
110      *
111      * @return the name of the analyzer.
112      */
113     @Override
114     public String getName() {
115         return ANALYZER_NAME;
116     }
117 
118     /**
119      * Returns the phase that the analyzer is intended to run in.
120      *
121      * @return the phase that the analyzer is intended to run in.
122      */
123     @Override
124     public AnalysisPhase getAnalysisPhase() {
125         return ANALYSIS_PHASE;
126     }
127 
128     /**
129      * Returns the key used in the properties file to reference the analyzer's
130      * enabled property.
131      *
132      * @return the analyzer's enabled property setting key
133      */
134     @Override
135     protected String getAnalyzerEnabledSettingKey() {
136         return Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED;
137     }
138 
139     @Override
140     protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
141         try {
142             analyzeSpmFileDependency(dependency);
143 
144         } catch (IOException ex) {
145             throw new AnalysisException(
146                     "Problem occurred while reading dependency file: " + dependency.getActualFilePath(), ex);
147         }
148     }
149 
150     /**
151      * Analyzes the SPM file and adds the evidence to the dependency.
152      *
153      * @param dependency the dependency
154      * @throws IOException thrown if there is an error analyzing the `podspec`
155      * file
156      */
157     private void analyzeSpmFileDependency(Dependency dependency)
158             throws AnalysisException, IOException {
159         dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
160 
161         final String contents = new String(Files.readAllBytes(dependency.getActualFile().toPath()), StandardCharsets.UTF_8);
162 
163         final Matcher matcher = SPM_BLOCK_PATTERN.matcher(contents);
164         if (matcher.find()) {
165             final String packageDescription = matcher.group(1);
166             if (packageDescription.isEmpty()) {
167                 return;
168             }
169 
170             // TODO SPM is currently under development for SWIFT 3. Its current metadata includes
171             // package name and dependencies.
172             // Future interesting metadata: version, license, homepage, author, summary,
173             // etc.
174             final String name = addStringEvidence(dependency, EvidenceType.PRODUCT, packageDescription, "name", "name", Confidence.HIGHEST);
175             if (name != null && !name.isEmpty()) {
176                 dependency.addEvidence(EvidenceType.VENDOR, SPM_FILE_NAME, "name_project", name, Confidence.HIGHEST);
177                 dependency.setName(name);
178             } else {
179                 // if we can't get the name from the meta, then assume the name is the name of
180                 // the parent folder containing the package.swift file.
181                 dependency.setName(dependency.getActualFile().getParentFile().getName());
182             }
183             if (dependency.getVersion() != null && !dependency.getVersion().isEmpty()) {
184                 dependency.setDisplayFileName(String.format("%s:%s", dependency.getName(), dependency.getVersion()));
185             } else {
186                 dependency.setDisplayFileName(dependency.getName());
187             }
188 
189             try {
190                 final PackageURLBuilder builder = PackageURLBuilder.aPackageURL().withType("swift").withName(dependency.getName());
191                 if (dependency.getVersion() != null) {
192                     builder.withVersion(dependency.getVersion());
193                 }
194                 final PackageURL purl = builder.build();
195                 dependency.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
196             } catch (MalformedPackageURLException ex) {
197                 LOGGER.debug("Unable to build package url for swift", ex);
198                 final GenericIdentifier id;
199                 if (dependency.getVersion() != null) {
200                     id = new GenericIdentifier("swift:" + dependency.getName() + "@" + dependency.getVersion(), Confidence.HIGHEST);
201                 } else {
202                     id = new GenericIdentifier("swift:" + dependency.getName(), Confidence.HIGHEST);
203                 }
204                 dependency.addSoftwareIdentifier(id);
205             }
206         }
207         setPackagePath(dependency);
208     }
209 
210     /**
211      * Extracts evidence from the package description and adds it to the given
212      * evidence collection.
213      *
214      * @param dependency the dependency being analyzed
215      * @param type the type of evidence to add
216      * @param packageDescription the text to extract evidence from
217      * @param field the name of the field being searched for
218      * @param fieldPattern the field pattern within the contents to search for
219      * @param confidence the confidence level of the evidence if found
220      * @return the string that was added as evidence
221      */
222     private String addStringEvidence(Dependency dependency, EvidenceType type,
223                                      String packageDescription, String field, String fieldPattern, Confidence confidence) {
224         String value = "";
225 
226         final Matcher matcher = Pattern.compile(
227                 String.format("%s *:\\s*\"([^\"]*)", fieldPattern), Pattern.DOTALL).matcher(packageDescription);
228         if (matcher.find()) {
229             value = matcher.group(1);
230         }
231 
232         if (value != null) {
233             value = value.trim();
234             if (value.length() > 0) {
235                 dependency.addEvidence(type, SPM_FILE_NAME, field, value, confidence);
236             }
237         }
238         return value;
239     }
240 
241     /**
242      * Sets the package path on the given dependency.
243      *
244      * @param dep the dependency to update
245      */
246     private void setPackagePath(Dependency dep) {
247         final File file = new File(dep.getFilePath());
248         final String parent = file.getParent();
249         if (parent != null) {
250             dep.setPackagePath(parent);
251         }
252     }
253 }