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.owasp.dependencycheck.Engine;
22  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
23  import org.owasp.dependencycheck.dependency.Confidence;
24  import org.owasp.dependencycheck.dependency.Dependency;
25  import org.owasp.dependencycheck.dependency.EvidenceCollection;
26  import org.owasp.dependencycheck.utils.FileFilterBuilder;
27  import org.owasp.dependencycheck.utils.Settings;
28  
29  import java.io.FileFilter;
30  import java.io.IOException;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  /**
35   * Used to analyze Ruby Gem specifications and collect information that can be used to determine the associated CPE.
36   * Regular expressions are used to parse the well-defined Ruby syntax that forms the specification.
37   *
38   * @author Dale Visser <dvisser@ida.org>
39   */
40  public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer {
41  
42      /**
43       * The name of the analyzer.
44       */
45      private static final String ANALYZER_NAME = "Ruby Gemspec Analyzer";
46  
47      /**
48       * The phase that this analyzer is intended to run in.
49       */
50      private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
51  
52      private static final FileFilter FILTER =
53              FileFilterBuilder.newInstance().addExtensions("gemspec").addFilenames("Rakefile").build();
54  
55      private static final String EMAIL = "email";
56      private static final String GEMSPEC = "gemspec";
57  
58      /**
59       * @return a filter that accepts files named Rakefile or matching the glob pattern, *.gemspec
60       */
61      @Override
62      protected FileFilter getFileFilter() {
63          return FILTER;
64      }
65  
66      @Override
67      protected void initializeFileTypeAnalyzer() throws Exception {
68          // NO-OP
69      }
70  
71      /**
72       * Returns the name of the analyzer.
73       *
74       * @return the name of the analyzer.
75       */
76      @Override
77      public String getName() {
78          return ANALYZER_NAME;
79      }
80  
81      /**
82       * Returns the phase that the analyzer is intended to run in.
83       *
84       * @return the phase that the analyzer is intended to run in.
85       */
86      @Override
87      public AnalysisPhase getAnalysisPhase() {
88          return ANALYSIS_PHASE;
89      }
90  
91      /**
92       * Returns the key used in the properties file to reference the analyzer's enabled property.
93       *
94       * @return the analyzer's enabled property setting key
95       */
96      @Override
97      protected String getAnalyzerEnabledSettingKey() {
98          return Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED;
99      }
100 
101     /**
102      * The capture group #1 is the block variable.
103      */
104     private static final Pattern GEMSPEC_BLOCK_INIT =
105             Pattern.compile("Gem::Specification\\.new\\s+?do\\s+?\\|(.+?)\\|");
106 
107     @Override
108     protected void analyzeFileType(Dependency dependency, Engine engine)
109             throws AnalysisException {
110         String contents;
111         try {
112             contents = FileUtils.readFileToString(dependency.getActualFile());
113         } catch (IOException e) {
114             throw new AnalysisException(
115                     "Problem occurred while reading dependency file.", e);
116         }
117         final Matcher matcher = GEMSPEC_BLOCK_INIT.matcher(contents);
118         if (matcher.find()) {
119             contents = contents.substring(matcher.end());
120             final String blockVariable = matcher.group(1);
121             final EvidenceCollection vendor = dependency.getVendorEvidence();
122             addStringEvidence(vendor, contents, blockVariable, "author", Confidence.HIGHEST);
123             addListEvidence(vendor, contents, blockVariable, "authors", Confidence.HIGHEST);
124             final String email = addStringEvidence(vendor, contents, blockVariable, EMAIL, Confidence.MEDIUM);
125             if (email.isEmpty()) {
126                 addListEvidence(vendor, contents, blockVariable, EMAIL, Confidence.MEDIUM);
127             }
128             addStringEvidence(vendor, contents, blockVariable, "homepage", Confidence.MEDIUM);
129             final EvidenceCollection product = dependency.getProductEvidence();
130             final String name = addStringEvidence(product, contents, blockVariable, "name", Confidence.HIGHEST);
131             if (!name.isEmpty()) {
132                 vendor.addEvidence(GEMSPEC, "name_project", name + "_project", Confidence.LOW);
133             }
134             addStringEvidence(product, contents, blockVariable, "summary", Confidence.LOW);
135             addStringEvidence(dependency.getVersionEvidence(), contents, blockVariable, "version", Confidence.HIGHEST);
136         }
137     }
138 
139     private void addListEvidence(EvidenceCollection evidences, String contents,
140                                  String blockVariable, String field, Confidence confidence) {
141         final Matcher matcher = Pattern.compile(
142                 String.format("\\s+?%s\\.%s\\s*?=\\s*?\\[(.*?)\\]", blockVariable, field)).matcher(contents);
143         if (matcher.find()) {
144             final String value = matcher.group(1).replaceAll("['\"]", " ").trim();
145             evidences.addEvidence(GEMSPEC, field, value, confidence);
146         }
147     }
148 
149     private String addStringEvidence(EvidenceCollection evidences, String contents,
150                                      String blockVariable, String field, Confidence confidence) {
151         final Matcher matcher = Pattern.compile(
152                 String.format("\\s+?%s\\.%s\\s*?=\\s*?(['\"])(.*?)\\1", blockVariable, field)).matcher(contents);
153         String value = "";
154         if (matcher.find()) {
155             value = matcher.group(2);
156             evidences.addEvidence(GEMSPEC, field, value, confidence);
157         }
158         return value;
159     }
160 }