Coverage Report - org.owasp.dependencycheck.analyzer.RubyGemspecAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
RubyGemspecAnalyzer
74%
49/66
36%
8/22
2.5
RubyGemspecAnalyzer$1
0%
0/2
N/A
2.5
 
 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 java.io.File;
 21  
 import java.io.FileFilter;
 22  
 import java.io.FilenameFilter;
 23  
 import java.io.IOException;
 24  
 import java.nio.charset.Charset;
 25  
 import java.util.List;
 26  
 import java.util.regex.Matcher;
 27  
 import java.util.regex.Pattern;
 28  
 
 29  
 import org.apache.commons.io.FileUtils;
 30  
 import org.owasp.dependencycheck.Engine;
 31  
 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
 32  
 import org.owasp.dependencycheck.dependency.Confidence;
 33  
 import org.owasp.dependencycheck.dependency.Dependency;
 34  
 import org.owasp.dependencycheck.dependency.EvidenceCollection;
 35  
 import org.owasp.dependencycheck.exception.InitializationException;
 36  
 import org.owasp.dependencycheck.utils.FileFilterBuilder;
 37  
 import org.owasp.dependencycheck.utils.Settings;
 38  
 import org.slf4j.Logger;
 39  
 import org.slf4j.LoggerFactory;
 40  
 
 41  
 /**
 42  
  * Used to analyze Ruby Gem specifications and collect information that can be
 43  
  * used to determine the associated CPE. Regular expressions are used to parse
 44  
  * the well-defined Ruby syntax that forms the specification.
 45  
  *
 46  
  * @author Dale Visser
 47  
  */
 48  
 @Experimental
 49  22
 public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer {
 50  
 
 51  
     /**
 52  
      * The logger.
 53  
      */
 54  1
     private static final Logger LOGGER = LoggerFactory.getLogger(RubyGemspecAnalyzer.class);
 55  
     /**
 56  
      * The name of the analyzer.
 57  
      */
 58  
     private static final String ANALYZER_NAME = "Ruby Gemspec Analyzer";
 59  
 
 60  
     /**
 61  
      * The phase that this analyzer is intended to run in.
 62  
      */
 63  1
     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
 64  
 
 65  
     /**
 66  
      * The gemspec file extension.
 67  
      */
 68  
     private static final String GEMSPEC = "gemspec";
 69  
 
 70  
     /**
 71  
      * The file filter containing the list of file extensions that can be
 72  
      * analyzed.
 73  
      */
 74  1
     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(GEMSPEC).build();
 75  
     //TODO: support Rakefile
 76  
     //= FileFilterBuilder.newInstance().addExtensions(GEMSPEC).addFilenames("Rakefile").build();
 77  
 
 78  
     /**
 79  
      * The name of the version file.
 80  
      */
 81  
     private static final String VERSION_FILE_NAME = "VERSION";
 82  
 
 83  
     /**
 84  
      * @return a filter that accepts files matching the glob pattern, *.gemspec
 85  
      */
 86  
     @Override
 87  
     protected FileFilter getFileFilter() {
 88  1723
         return FILTER;
 89  
     }
 90  
 
 91  
     @Override
 92  
     protected void initializeFileTypeAnalyzer() throws InitializationException {
 93  
         // NO-OP
 94  8
     }
 95  
 
 96  
     /**
 97  
      * Returns the name of the analyzer.
 98  
      *
 99  
      * @return the name of the analyzer.
 100  
      */
 101  
     @Override
 102  
     public String getName() {
 103  20
         return ANALYZER_NAME;
 104  
     }
 105  
 
 106  
     /**
 107  
      * Returns the phase that the analyzer is intended to run in.
 108  
      *
 109  
      * @return the phase that the analyzer is intended to run in.
 110  
      */
 111  
     @Override
 112  
     public AnalysisPhase getAnalysisPhase() {
 113  12
         return ANALYSIS_PHASE;
 114  
     }
 115  
 
 116  
     /**
 117  
      * Returns the key used in the properties file to reference the analyzer's
 118  
      * enabled property.
 119  
      *
 120  
      * @return the analyzer's enabled property setting key
 121  
      */
 122  
     @Override
 123  
     protected String getAnalyzerEnabledSettingKey() {
 124  10
         return Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED;
 125  
     }
 126  
 
 127  
     /**
 128  
      * The capture group #1 is the block variable.
 129  
      */
 130  1
     private static final Pattern GEMSPEC_BLOCK_INIT = Pattern.compile("Gem::Specification\\.new\\s+?do\\s+?\\|(.+?)\\|");
 131  
 
 132  
     @Override
 133  
     protected void analyzeDependency(Dependency dependency, Engine engine)
 134  
             throws AnalysisException {
 135  
         String contents;
 136  
         try {
 137  4
             contents = FileUtils.readFileToString(dependency.getActualFile(), Charset.defaultCharset());
 138  0
         } catch (IOException e) {
 139  0
             throw new AnalysisException(
 140  
                     "Problem occurred while reading dependency file.", e);
 141  4
         }
 142  4
         final Matcher matcher = GEMSPEC_BLOCK_INIT.matcher(contents);
 143  4
         if (matcher.find()) {
 144  4
             contents = contents.substring(matcher.end());
 145  4
             final String blockVariable = matcher.group(1);
 146  
 
 147  4
             final EvidenceCollection vendor = dependency.getVendorEvidence();
 148  4
             final EvidenceCollection product = dependency.getProductEvidence();
 149  4
             final String name = addStringEvidence(product, contents, blockVariable, "name", "name", Confidence.HIGHEST);
 150  4
             if (!name.isEmpty()) {
 151  4
                 vendor.addEvidence(GEMSPEC, "name_project", name + "_project", Confidence.LOW);
 152  
             }
 153  4
             addStringEvidence(product, contents, blockVariable, "summary", "summary", Confidence.LOW);
 154  
 
 155  4
             addStringEvidence(vendor, contents, blockVariable, "author", "authors?", Confidence.HIGHEST);
 156  4
             addStringEvidence(vendor, contents, blockVariable, "email", "emails?", Confidence.MEDIUM);
 157  4
             addStringEvidence(vendor, contents, blockVariable, "homepage", "homepage", Confidence.HIGHEST);
 158  4
             addStringEvidence(vendor, contents, blockVariable, "license", "licen[cs]es?", Confidence.HIGHEST);
 159  
 
 160  4
             final String value = addStringEvidence(dependency.getVersionEvidence(), contents,
 161  
                     blockVariable, "version", "version", Confidence.HIGHEST);
 162  4
             if (value.length() < 1) {
 163  0
                 addEvidenceFromVersionFile(dependency.getActualFile(), dependency.getVersionEvidence());
 164  
             }
 165  
         }
 166  
 
 167  4
         setPackagePath(dependency);
 168  4
     }
 169  
 
 170  
     /**
 171  
      * Adds the specified evidence to the given evidence collection.
 172  
      *
 173  
      * @param evidences the collection to add the evidence to
 174  
      * @param contents the evidence contents
 175  
      * @param blockVariable the variable
 176  
      * @param field the field
 177  
      * @param fieldPattern the field pattern
 178  
      * @param confidence the confidence of the evidence
 179  
      * @return the evidence string value added
 180  
      */
 181  
     private String addStringEvidence(EvidenceCollection evidences, String contents,
 182  
             String blockVariable, String field, String fieldPattern, Confidence confidence) {
 183  28
         String value = "";
 184  
 
 185  
         //capture array value between [ ]
 186  56
         final Matcher arrayMatcher = Pattern.compile(
 187  56
                 String.format("\\s*?%s\\.%s\\s*?=\\s*?\\[(.*?)\\]", blockVariable, fieldPattern), Pattern.CASE_INSENSITIVE).matcher(contents);
 188  28
         if (arrayMatcher.find()) {
 189  11
             final String arrayValue = arrayMatcher.group(1);
 190  11
             value = arrayValue.replaceAll("['\"]", "").trim(); //strip quotes
 191  11
         } else { //capture single value between quotes
 192  34
             final Matcher matcher = Pattern.compile(
 193  34
                     String.format("\\s*?%s\\.%s\\s*?=\\s*?(['\"])(.*?)\\1", blockVariable, fieldPattern), Pattern.CASE_INSENSITIVE).matcher(contents);
 194  17
             if (matcher.find()) {
 195  17
                 value = matcher.group(2);
 196  
             }
 197  
         }
 198  28
         if (value.length() > 0) {
 199  28
             evidences.addEvidence(GEMSPEC, field, value, confidence);
 200  
         }
 201  
 
 202  28
         return value;
 203  
     }
 204  
 
 205  
     /**
 206  
      * Adds evidence from the version file.
 207  
      *
 208  
      * @param dependencyFile the dependency being analyzed
 209  
      * @param versionEvidences the version evidence
 210  
      */
 211  
     private void addEvidenceFromVersionFile(File dependencyFile, EvidenceCollection versionEvidences) {
 212  0
         final File parentDir = dependencyFile.getParentFile();
 213  0
         if (parentDir != null) {
 214  0
             final File[] matchingFiles = parentDir.listFiles(new FilenameFilter() {
 215  
                 @Override
 216  
                 public boolean accept(File dir, String name) {
 217  0
                     return name.contains(VERSION_FILE_NAME);
 218  
                 }
 219  
             });
 220  0
             if (matchingFiles == null) {
 221  0
                 return;
 222  
             }
 223  0
             for (File f : matchingFiles) {
 224  
                 try {
 225  0
                     final List<String> lines = FileUtils.readLines(f, Charset.defaultCharset());
 226  0
                     if (lines.size() == 1) { //TODO other checking?
 227  0
                         final String value = lines.get(0).trim();
 228  0
                         versionEvidences.addEvidence(GEMSPEC, "version", value, Confidence.HIGH);
 229  
                     }
 230  0
                 } catch (IOException e) {
 231  0
                     LOGGER.debug("Error reading gemspec", e);
 232  0
                 }
 233  
             }
 234  
         }
 235  0
     }
 236  
 
 237  
     /**
 238  
      * Sets the package path on the dependency.
 239  
      *
 240  
      * @param dep the dependency to alter
 241  
      */
 242  
     private void setPackagePath(Dependency dep) {
 243  4
         final File file = new File(dep.getFilePath());
 244  4
         final String parent = file.getParent();
 245  4
         if (parent != null) {
 246  4
             dep.setPackagePath(parent);
 247  
         }
 248  4
     }
 249  
 }