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