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) 2016 IBM Corporation. All Rights Reserved.
17 */
18 package org.owasp.dependencycheck.analyzer;
19
20 import java.io.File;
21 import java.io.FilenameFilter;
22
23 import org.owasp.dependencycheck.Engine;
24 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
25 import org.owasp.dependencycheck.dependency.Dependency;
26
27 /**
28 * This analyzer accepts the fully resolved .gemspec created by the Ruby bundler
29 * (http://bundler.io) for better evidence results. It also tries to resolve the
30 * dependency packagePath to where the gem is actually installed. Then during
31 * the {@link org.owasp.dependencycheck.analyzer.AnalysisPhase#PRE_FINDING_ANALYSIS}
32 * {@link DependencyMergingAnalyzer} will merge two .gemspec dependencies
33 * together if <code>Dependency.getPackagePath()</code> are the same.
34 *
35 * Ruby bundler creates new .gemspec files under a folder called
36 * "specifications" at deploy time, in addition to the original .gemspec files
37 * from source. The bundler generated .gemspec files always contain fully
38 * resolved attributes thus provide more accurate evidences, whereas the
39 * original .gemspec from source often contain variables for attributes that
40 * can't be used for evidences.
41 *
42 * Note this analyzer share the same
43 * {@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_RUBY_GEMSPEC_ENABLED}
44 * as {@link RubyGemspecAnalyzer}, so it will enabled/disabled with
45 * {@link RubyGemspecAnalyzer}.
46 *
47 * @author Bianca Jiang (https://twitter.com/biancajiang)
48 */
49 @Experimental
50 public class RubyBundlerAnalyzer extends RubyGemspecAnalyzer {
51
52 /**
53 * The name of the analyzer.
54 */
55 private static final String ANALYZER_NAME = "Ruby Bundler Analyzer";
56
57 /**
58 * Folder name that contains .gemspec files created by "bundle install"
59 */
60 private static final String SPECIFICATIONS = "specifications";
61
62 /**
63 * Folder name that contains the gems by "bundle install"
64 */
65 private static final String GEMS = "gems";
66
67 /**
68 * Returns the name of the analyzer.
69 *
70 * @return the name of the analyzer.
71 */
72 @Override
73 public String getName() {
74 return ANALYZER_NAME;
75 }
76
77 /**
78 * Only accept *.gemspec files generated by "bundle install --deployment"
79 * under "specifications" folder.
80 *
81 * @param pathname the path name to test
82 * @return true if the analyzer can process the given file; otherwise false
83 */
84 @Override
85 public boolean accept(File pathname) {
86
87 boolean accepted = super.accept(pathname);
88 if (accepted) {
89 final File parentDir = pathname.getParentFile();
90 accepted = parentDir != null && parentDir.getName().equals(SPECIFICATIONS);
91 }
92
93 return accepted;
94 }
95
96 @Override
97 protected void analyzeDependency(Dependency dependency, Engine engine)
98 throws AnalysisException {
99 super.analyzeDependency(dependency, engine);
100
101 //find the corresponding gem folder for this .gemspec stub by "bundle install --deployment"
102 final File gemspecFile = dependency.getActualFile();
103 final String gemFileName = gemspecFile.getName();
104 final String gemName = gemFileName.substring(0, gemFileName.lastIndexOf(".gemspec"));
105 final File specificationsDir = gemspecFile.getParentFile();
106 if (specificationsDir != null && specificationsDir.getName().equals(SPECIFICATIONS) && specificationsDir.exists()) {
107 final File parentDir = specificationsDir.getParentFile();
108 if (parentDir != null && parentDir.exists()) {
109 final File gemsDir = new File(parentDir, GEMS);
110 if (gemsDir.exists()) {
111 final File[] matchingFiles = gemsDir.listFiles(new FilenameFilter() {
112 @Override
113 public boolean accept(File dir, String name) {
114 return name.equals(gemName);
115 }
116 });
117
118 if (matchingFiles != null && matchingFiles.length > 0) {
119 final String gemPath = matchingFiles[0].getAbsolutePath();
120 if (dependency.getActualFilePath().equals(dependency.getFilePath())) {
121 if (gemPath != null) {
122 dependency.setPackagePath(gemPath);
123 }
124 } else {
125 //.gemspec's actualFilePath and filePath are different when it's from a compressed file
126 //in which case actualFilePath is the temp directory used by decompression.
127 //packagePath should use the filePath of the identified gem file in "gems" folder
128 final File gemspecStub = new File(dependency.getFilePath());
129 final File specDir = gemspecStub.getParentFile();
130 if (specDir != null && specDir.getName().equals(SPECIFICATIONS)) {
131 final File gemsDir2 = new File(specDir.getParentFile(), GEMS);
132 final File packageDir = new File(gemsDir2, gemName);
133 dependency.setPackagePath(packageDir.getAbsolutePath());
134 }
135 }
136 }
137 }
138 }
139 }
140 }
141 }