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