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) 2015 Institute for Defense Analyses. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.analyzer;
19  
20  import org.apache.commons.io.FileUtils;
21  import org.apache.commons.lang3.StringUtils;
22  import org.owasp.dependencycheck.Engine;
23  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
24  import org.owasp.dependencycheck.dependency.Confidence;
25  import org.owasp.dependencycheck.dependency.Dependency;
26  import org.owasp.dependencycheck.utils.Checksum;
27  import org.owasp.dependencycheck.utils.FileFilterBuilder;
28  import org.owasp.dependencycheck.utils.Settings;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import java.io.File;
33  import java.io.FileFilter;
34  import java.io.IOException;
35  import java.security.MessageDigest;
36  import java.security.NoSuchAlgorithmException;
37  import java.util.regex.Matcher;
38  import java.util.regex.Pattern;
39  
40  /**
41   * <p>
42   * Used to analyze CMake build files, and collect information that can be used to determine the associated CPE.</p>
43   * <p/>
44   * <p>
45   * Note: This analyzer catches straightforward invocations of the project command, plus some other observed patterns of version
46   * inclusion in real CMake projects. Many projects make use of older versions of CMake and/or use custom "homebrew" ways to insert
47   * version information. Hopefully as the newer CMake call pattern grows in usage, this analyzer allow more CPEs to be
48   * identified.</p>
49   *
50   * @author Dale Visser <dvisser@ida.org>
51   */
52  public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
53  
54      /**
55       * The logger.
56       */
57      private static final Logger LOGGER = LoggerFactory.getLogger(CMakeAnalyzer.class);
58  
59      /**
60       * Used when compiling file scanning regex patterns.
61       */
62      private static final int REGEX_OPTIONS = Pattern.DOTALL
63              | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE;
64  
65      private static final Pattern PROJECT = Pattern.compile(
66              "^ *project *\\([ \\n]*(\\w+)[ \\n]*.*?\\)", REGEX_OPTIONS);
67  
68      // Group 1: Product
69      // Group 2: Version
70      private static final Pattern SET_VERSION = Pattern
71              .compile(
72                      "^ *set\\s*\\(\\s*(\\w+)_version\\s+\"?(\\d+(?:\\.\\d+)+)[\\s\"]?\\)",
73                      REGEX_OPTIONS);
74  
75      /**
76       * Detects files that can be analyzed.
77       */
78      private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(".cmake")
79              .addFilenames("CMakeLists.txt").build();
80  
81      /**
82       * A reference to SHA1 message digest.
83       */
84      private static MessageDigest sha1 = null;
85  
86      static {
87          try {
88              sha1 = MessageDigest.getInstance("SHA1");
89          } catch (NoSuchAlgorithmException e) {
90              LOGGER.error(e.getMessage());
91          }
92      }
93  
94      /**
95       * Returns the name of the CMake analyzer.
96       *
97       * @return the name of the analyzer
98       *
99       */
100     @Override
101     public String getName() {
102         return "CMake Analyzer";
103     }
104 
105     /**
106      * Tell that we are used for information collection.
107      *
108      * @return INFORMATION_COLLECTION
109      */
110     @Override
111     public AnalysisPhase getAnalysisPhase() {
112         return AnalysisPhase.INFORMATION_COLLECTION;
113     }
114 
115     /**
116      * Returns the set of supported file extensions.
117      *
118      * @return the set of supported file extensions
119      */
120     @Override
121     protected FileFilter getFileFilter() {
122         return FILTER;
123     }
124 
125     /**
126      * No-op initializer implementation.
127      *
128      * @throws Exception never thrown
129      */
130     @Override
131     protected void initializeFileTypeAnalyzer() throws Exception {
132         // Nothing to do here.
133     }
134 
135     /**
136      * Analyzes python packages and adds evidence to the dependency.
137      *
138      * @param dependency the dependency being analyzed
139      * @param engine the engine being used to perform the scan
140      * @throws AnalysisException thrown if there is an unrecoverable error analyzing the dependency
141      */
142     @Override
143     protected void analyzeFileType(Dependency dependency, Engine engine)
144             throws AnalysisException {
145         final File file = dependency.getActualFile();
146         final String parentName = file.getParentFile().getName();
147         final String name = file.getName();
148         dependency.setDisplayFileName(String.format("%s%c%s", parentName, File.separatorChar, name));
149         String contents;
150         try {
151             contents = FileUtils.readFileToString(file).trim();
152         } catch (IOException e) {
153             throw new AnalysisException(
154                     "Problem occurred while reading dependency file.", e);
155         }
156 
157         if (StringUtils.isNotBlank(contents)) {
158             final Matcher m = PROJECT.matcher(contents);
159             int count = 0;
160             while (m.find()) {
161                 count++;
162                 LOGGER.debug(String.format(
163                         "Found project command match with %d groups: %s",
164                         m.groupCount(), m.group(0)));
165                 final String group = m.group(1);
166                 LOGGER.debug("Group 1: " + group);
167                 dependency.getProductEvidence().addEvidence(name, "Project",
168                         group, Confidence.HIGH);
169             }
170             LOGGER.debug("Found {} matches.", count);
171             analyzeSetVersionCommand(dependency, engine, contents);
172         }
173     }
174 
175     private void analyzeSetVersionCommand(Dependency dependency, Engine engine, String contents) {
176         final Dependency orig = dependency;
177         final Matcher m = SET_VERSION.matcher(contents);
178         int count = 0;
179         while (m.find()) {
180             count++;
181             LOGGER.debug("Found project command match with {} groups: {}",
182                     m.groupCount(), m.group(0));
183             String product = m.group(1);
184             final String version = m.group(2);
185             LOGGER.debug("Group 1: " + product);
186             LOGGER.debug("Group 2: " + version);
187             final String aliasPrefix = "ALIASOF_";
188             if (product.startsWith(aliasPrefix)) {
189                 product = product.replaceFirst(aliasPrefix, "");
190             }
191             if (count > 1) {
192                 //TODO - refactor so we do not assign to the parameter (checkstyle)
193                 dependency = new Dependency(orig.getActualFile());
194                 dependency.setDisplayFileName(String.format("%s:%s", orig.getDisplayFileName(), product));
195                 final String filePath = String.format("%s:%s", orig.getFilePath(), product);
196                 dependency.setFilePath(filePath);
197 
198                 // prevents coalescing into the dependency provided by engine
199                 dependency.setSha1sum(Checksum.getHex(sha1.digest(filePath.getBytes())));
200                 engine.getDependencies().add(dependency);
201             }
202             final String source = dependency.getDisplayFileName();
203             dependency.getProductEvidence().addEvidence(source, "Product",
204                     product, Confidence.MEDIUM);
205             dependency.getVersionEvidence().addEvidence(source, "Version",
206                     version, Confidence.MEDIUM);
207         }
208         LOGGER.debug(String.format("Found %d matches.", count));
209     }
210 
211     @Override
212     protected String getAnalyzerEnabledSettingKey() {
213         return Settings.KEYS.ANALYZER_CMAKE_ENABLED;
214     }
215 }