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