railroader 4.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES.md +1091 -0
- data/FEATURES +16 -0
- data/README.md +174 -0
- data/bin/railroader +8 -0
- data/lib/railroader/app_tree.rb +191 -0
- data/lib/railroader/call_index.rb +219 -0
- data/lib/railroader/checks/base_check.rb +505 -0
- data/lib/railroader/checks/check_basic_auth.rb +88 -0
- data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/railroader/checks/check_content_tag.rb +200 -0
- data/lib/railroader/checks/check_create_with.rb +74 -0
- data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
- data/lib/railroader/checks/check_default_routes.rb +86 -0
- data/lib/railroader/checks/check_deserialize.rb +56 -0
- data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
- data/lib/railroader/checks/check_digest_dos.rb +38 -0
- data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
- data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
- data/lib/railroader/checks/check_escape_function.rb +21 -0
- data/lib/railroader/checks/check_evaluation.rb +35 -0
- data/lib/railroader/checks/check_execute.rb +189 -0
- data/lib/railroader/checks/check_file_access.rb +71 -0
- data/lib/railroader/checks/check_file_disclosure.rb +35 -0
- data/lib/railroader/checks/check_filter_skipping.rb +31 -0
- data/lib/railroader/checks/check_forgery_setting.rb +81 -0
- data/lib/railroader/checks/check_header_dos.rb +31 -0
- data/lib/railroader/checks/check_i18n_xss.rb +48 -0
- data/lib/railroader/checks/check_jruby_xml.rb +36 -0
- data/lib/railroader/checks/check_json_encoding.rb +47 -0
- data/lib/railroader/checks/check_json_parsing.rb +107 -0
- data/lib/railroader/checks/check_link_to.rb +132 -0
- data/lib/railroader/checks/check_link_to_href.rb +146 -0
- data/lib/railroader/checks/check_mail_to.rb +49 -0
- data/lib/railroader/checks/check_mass_assignment.rb +196 -0
- data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
- data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
- data/lib/railroader/checks/check_model_attributes.rb +119 -0
- data/lib/railroader/checks/check_model_serialize.rb +67 -0
- data/lib/railroader/checks/check_nested_attributes.rb +38 -0
- data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/railroader/checks/check_number_to_currency.rb +74 -0
- data/lib/railroader/checks/check_permit_attributes.rb +43 -0
- data/lib/railroader/checks/check_quote_table_name.rb +40 -0
- data/lib/railroader/checks/check_redirect.rb +256 -0
- data/lib/railroader/checks/check_regex_dos.rb +68 -0
- data/lib/railroader/checks/check_render.rb +97 -0
- data/lib/railroader/checks/check_render_dos.rb +37 -0
- data/lib/railroader/checks/check_render_inline.rb +53 -0
- data/lib/railroader/checks/check_response_splitting.rb +21 -0
- data/lib/railroader/checks/check_route_dos.rb +42 -0
- data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
- data/lib/railroader/checks/check_secrets.rb +40 -0
- data/lib/railroader/checks/check_select_tag.rb +59 -0
- data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
- data/lib/railroader/checks/check_send.rb +47 -0
- data/lib/railroader/checks/check_send_file.rb +19 -0
- data/lib/railroader/checks/check_session_manipulation.rb +35 -0
- data/lib/railroader/checks/check_session_settings.rb +176 -0
- data/lib/railroader/checks/check_simple_format.rb +58 -0
- data/lib/railroader/checks/check_single_quotes.rb +101 -0
- data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
- data/lib/railroader/checks/check_sql.rb +700 -0
- data/lib/railroader/checks/check_sql_cves.rb +106 -0
- data/lib/railroader/checks/check_ssl_verify.rb +48 -0
- data/lib/railroader/checks/check_strip_tags.rb +89 -0
- data/lib/railroader/checks/check_symbol_dos.rb +71 -0
- data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/railroader/checks/check_translate_bug.rb +45 -0
- data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
- data/lib/railroader/checks/check_unscoped_find.rb +57 -0
- data/lib/railroader/checks/check_validation_regex.rb +116 -0
- data/lib/railroader/checks/check_weak_hash.rb +148 -0
- data/lib/railroader/checks/check_without_protection.rb +80 -0
- data/lib/railroader/checks/check_xml_dos.rb +45 -0
- data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
- data/lib/railroader/checks.rb +209 -0
- data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
- data/lib/railroader/commandline.rb +179 -0
- data/lib/railroader/differ.rb +66 -0
- data/lib/railroader/file_parser.rb +54 -0
- data/lib/railroader/format/style.css +133 -0
- data/lib/railroader/options.rb +339 -0
- data/lib/railroader/parsers/rails2_erubis.rb +6 -0
- data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/railroader/parsers/rails3_erubis.rb +81 -0
- data/lib/railroader/parsers/template_parser.rb +108 -0
- data/lib/railroader/processor.rb +102 -0
- data/lib/railroader/processors/alias_processor.rb +1229 -0
- data/lib/railroader/processors/base_processor.rb +295 -0
- data/lib/railroader/processors/config_processor.rb +14 -0
- data/lib/railroader/processors/controller_alias_processor.rb +278 -0
- data/lib/railroader/processors/controller_processor.rb +249 -0
- data/lib/railroader/processors/erb_template_processor.rb +77 -0
- data/lib/railroader/processors/erubis_template_processor.rb +92 -0
- data/lib/railroader/processors/gem_processor.rb +64 -0
- data/lib/railroader/processors/haml_template_processor.rb +191 -0
- data/lib/railroader/processors/lib/basic_processor.rb +37 -0
- data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
- data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
- data/lib/railroader/processors/lib/find_call.rb +183 -0
- data/lib/railroader/processors/lib/find_return_value.rb +166 -0
- data/lib/railroader/processors/lib/module_helper.rb +111 -0
- data/lib/railroader/processors/lib/processor_helper.rb +88 -0
- data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/railroader/processors/lib/render_helper.rb +181 -0
- data/lib/railroader/processors/lib/render_path.rb +107 -0
- data/lib/railroader/processors/lib/route_helper.rb +68 -0
- data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
- data/lib/railroader/processors/library_processor.rb +74 -0
- data/lib/railroader/processors/model_processor.rb +91 -0
- data/lib/railroader/processors/output_processor.rb +144 -0
- data/lib/railroader/processors/route_processor.rb +17 -0
- data/lib/railroader/processors/slim_template_processor.rb +111 -0
- data/lib/railroader/processors/template_alias_processor.rb +118 -0
- data/lib/railroader/processors/template_processor.rb +85 -0
- data/lib/railroader/report/config/remediation.yml +71 -0
- data/lib/railroader/report/ignore/config.rb +153 -0
- data/lib/railroader/report/ignore/interactive.rb +362 -0
- data/lib/railroader/report/pager.rb +112 -0
- data/lib/railroader/report/renderer.rb +24 -0
- data/lib/railroader/report/report_base.rb +292 -0
- data/lib/railroader/report/report_codeclimate.rb +79 -0
- data/lib/railroader/report/report_csv.rb +55 -0
- data/lib/railroader/report/report_hash.rb +23 -0
- data/lib/railroader/report/report_html.rb +216 -0
- data/lib/railroader/report/report_json.rb +45 -0
- data/lib/railroader/report/report_markdown.rb +107 -0
- data/lib/railroader/report/report_table.rb +117 -0
- data/lib/railroader/report/report_tabs.rb +17 -0
- data/lib/railroader/report/report_text.rb +198 -0
- data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
- data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/error_overview.html.erb +29 -0
- data/lib/railroader/report/templates/header.html.erb +58 -0
- data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/overview.html.erb +38 -0
- data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
- data/lib/railroader/report/templates/template_overview.html.erb +21 -0
- data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
- data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
- data/lib/railroader/report.rb +88 -0
- data/lib/railroader/rescanner.rb +483 -0
- data/lib/railroader/scanner.rb +321 -0
- data/lib/railroader/tracker/collection.rb +93 -0
- data/lib/railroader/tracker/config.rb +154 -0
- data/lib/railroader/tracker/constants.rb +171 -0
- data/lib/railroader/tracker/controller.rb +161 -0
- data/lib/railroader/tracker/library.rb +17 -0
- data/lib/railroader/tracker/model.rb +90 -0
- data/lib/railroader/tracker/template.rb +33 -0
- data/lib/railroader/tracker.rb +362 -0
- data/lib/railroader/util.rb +503 -0
- data/lib/railroader/version.rb +3 -0
- data/lib/railroader/warning.rb +294 -0
- data/lib/railroader/warning_codes.rb +117 -0
- data/lib/railroader.rb +544 -0
- data/lib/ruby_parser/bm_sexp.rb +626 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- metadata +386 -0
data/FEATURES
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Can detect:
|
2
|
+
-Possibly unescaped model attributes or parameters in views (Cross-Site Scripting)
|
3
|
+
-Bad string interpolation in calls to Model.find, Model.last, Model.first, etc., as well as chained calls (SQL Injection)
|
4
|
+
-String interpolation in find_by_sql (SQL Injection)
|
5
|
+
-String interpolation or params in calls to system, exec, and syscall and `` (Command Injection)
|
6
|
+
-Unrestricted mass assignments
|
7
|
+
-Global restriction of mass assignment
|
8
|
+
-Missing call to protect_from_forgery in ApplicationController (CSRF protection)
|
9
|
+
-Default routes, per-controller and globally
|
10
|
+
-Redirects based on params (probably too broad currently)
|
11
|
+
-Validation regexes not using \A and \z
|
12
|
+
-Calls to render with dynamic paths
|
13
|
+
|
14
|
+
General capabilities:
|
15
|
+
-Search for method calls based on target class and/or method name
|
16
|
+
-Determine 'output' of templates using ERB, Erubis, or HAML. Can handle automatic escaping
|
data/README.md
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
[![Railroader Logo](http://railroader.org/images/logo_medium.png)](http://railroader.org/)
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/david-a-wheeler/railroader.svg?branch=master)](https://travis-ci.org/david-a-wheeler/railroader)
|
4
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/1b08a5c74695cb0d11ec/maintainability)](https://codeclimate.com/github/david-a-wheeler/railroader/maintainability)
|
5
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/1b08a5c74695cb0d11ec/test_coverage)](https://codeclimate.com/github/david-a-wheeler/railroader/test_coverage)
|
6
|
+
[![Gitter](https://badges.gitter.im/david-a-wheeler/railroader.svg)](https://gitter.im/david-a-wheeler/railroader)
|
7
|
+
|
8
|
+
# Railroader
|
9
|
+
|
10
|
+
Railroader is an open source static analysis tool which checks Ruby on Rails applications for security vulnerabilities.
|
11
|
+
|
12
|
+
Railroader is a fork of the Brakeman analysis tool version 4.3.1 (the last version of Brakeman that was open source software). A key distinguishing feature is that Railroader is open source software (OSS), while Brakeman is not open source software any more. Railroader is licensed under the [MIT-LICENSE](MIT-LICENSE). As a result, Railroader can be freely used for any purpose, including any commercial purposes. In addition, contributors to Railroader (unlike Brakeman) retain their copyrights.
|
13
|
+
|
14
|
+
If you are interested in Brakeman, please see the [Brakeman site instead](https://brakemanscanner.org/) instead!
|
15
|
+
|
16
|
+
We are currently in a transition process, because we have just started creating Railroader as a fork of Brakeman. Some names in the process of changing - help is wanted to complete it. We need to change the name, because we assume that Synopsys owns the trademarks and in any case we want to make sure there is *no* confusion by anyone that Railroader is Brakeman (they are now different projects).
|
17
|
+
|
18
|
+
# Installation
|
19
|
+
|
20
|
+
Using RubyGems:
|
21
|
+
|
22
|
+
gem install railroader
|
23
|
+
|
24
|
+
Using Bundler:
|
25
|
+
|
26
|
+
group :development do
|
27
|
+
gem 'railroader', :require => false
|
28
|
+
end
|
29
|
+
|
30
|
+
# Usage
|
31
|
+
|
32
|
+
From a Rails application's root directory:
|
33
|
+
|
34
|
+
railroader
|
35
|
+
|
36
|
+
Outside of Rails root:
|
37
|
+
|
38
|
+
railroader /path/to/rails/application
|
39
|
+
|
40
|
+
# Compatibility
|
41
|
+
|
42
|
+
Railroader should work with any version of Rails from 2.3.x to 5.x.
|
43
|
+
|
44
|
+
Railroader can analyze code written with Ruby 1.8 syntax and newer, but requires at least Ruby 1.9.3 to run.
|
45
|
+
|
46
|
+
# Basic Options
|
47
|
+
|
48
|
+
For a full list of options, use `railroader --help` or see the [OPTIONS.md](OPTIONS.md) file.
|
49
|
+
|
50
|
+
To specify an output file for the results:
|
51
|
+
|
52
|
+
railroader -o output_file
|
53
|
+
|
54
|
+
The output format is determined by the file extension or by using the `-f` option. Current options are: `text`, `html`, `tabs`, `json`, `markdown`, `csv`, and `codeclimate`.
|
55
|
+
|
56
|
+
Multiple output files can be specified:
|
57
|
+
|
58
|
+
railroader -o output.html -o output.json
|
59
|
+
|
60
|
+
To suppress informational warnings and just output the report:
|
61
|
+
|
62
|
+
railroader -q
|
63
|
+
|
64
|
+
Note all Railroader output except reports are sent to stderr, making it simple to redirect stdout to a file and just get the report.
|
65
|
+
|
66
|
+
To see all kinds of debugging information:
|
67
|
+
|
68
|
+
railroader -d
|
69
|
+
|
70
|
+
Specific checks can be skipped, if desired. The name needs to be the correct case. For example, to skip looking for default routes (`DefaultRoutes`):
|
71
|
+
|
72
|
+
railroader -x DefaultRoutes
|
73
|
+
|
74
|
+
Multiple checks should be separated by a comma:
|
75
|
+
|
76
|
+
railroader -x DefaultRoutes,Redirect
|
77
|
+
|
78
|
+
To do the opposite and only run a certain set of tests:
|
79
|
+
|
80
|
+
railroader -t SQL,ValidationRegex
|
81
|
+
|
82
|
+
If Railroader is running a bit slow, try
|
83
|
+
|
84
|
+
railroader --faster
|
85
|
+
|
86
|
+
This will disable some features, but will probably be much faster (currently it is the same as `--skip-libs --no-branching`). *WARNING*: This may cause Railroader to miss some vulnerabilities.
|
87
|
+
|
88
|
+
By default, Railroader will return a non-zero exit code if any security warnings are found or scanning errors are encountered. To disable this:
|
89
|
+
|
90
|
+
railroader --no-exit-on-warn --no-exit-on-error
|
91
|
+
|
92
|
+
To skip certain files or directories that Railroader may have trouble parsing, use:
|
93
|
+
|
94
|
+
railroader --skip-files file1,/path1/,path2/
|
95
|
+
|
96
|
+
To compare results of a scan with a previous scan, use the JSON output option and then:
|
97
|
+
|
98
|
+
railroader --compare old_report.json
|
99
|
+
|
100
|
+
This will output JSON with two lists: one of fixed warnings and one of new warnings.
|
101
|
+
|
102
|
+
Railroader will ignore warnings if configured to do so. By default, it looks for a configuration file in `config/railroader.ignore`.
|
103
|
+
To create and manage this file, use:
|
104
|
+
|
105
|
+
railroader -I
|
106
|
+
|
107
|
+
# Warning information
|
108
|
+
|
109
|
+
See [warning\_types](docs/warning_types) for more information on the warnings reported by this tool.
|
110
|
+
|
111
|
+
# Warning context
|
112
|
+
|
113
|
+
The HTML output format provides an excerpt from the original application source where a warning was triggered. Due to the processing done while looking for vulnerabilities, the source may not resemble the reported warning and reported line numbers may be slightly off. However, the context still provides a quick look into the code which raised the warning.
|
114
|
+
|
115
|
+
# Confidence levels
|
116
|
+
|
117
|
+
Railroader assigns a confidence level to each warning. This provides a rough estimate of how certain the tool is that a given warning is actually a problem. Naturally, these ratings should not be taken as absolute truth.
|
118
|
+
|
119
|
+
There are three levels of confidence:
|
120
|
+
|
121
|
+
+ High - Either this is a simple warning (boolean value) or user input is very likely being used in unsafe ways.
|
122
|
+
+ Medium - This generally indicates an unsafe use of a variable, but the variable may or may not be user input.
|
123
|
+
+ Weak - Typically means user input was indirectly used in a potentially unsafe manner.
|
124
|
+
|
125
|
+
To only get warnings above a given confidence level:
|
126
|
+
|
127
|
+
railroader -w3
|
128
|
+
|
129
|
+
The `-w` switch takes a number from 1 to 3, with 1 being low (all warnings) and 3 being high (only highest confidence warnings).
|
130
|
+
|
131
|
+
# Configuration files
|
132
|
+
|
133
|
+
Railroader options can stored and read from YAML files. To simplify the process of writing a configuration file, the `-C` option will output the currently set options.
|
134
|
+
|
135
|
+
Options passed in on the commandline have priority over configuration files.
|
136
|
+
|
137
|
+
The default config locations are `./config/railroader.yml`, `~/.railroader/config.yml`, and `/etc/railroader/config.yml`
|
138
|
+
|
139
|
+
The `-c` option can be used to specify a configuration file to use.
|
140
|
+
|
141
|
+
# Continuous Integration
|
142
|
+
|
143
|
+
There is a [plugin available](http://railroaderscanner.org/docs/jenkins/) for Jenkins/Hudson.
|
144
|
+
|
145
|
+
For even more continuous testing, try the [Guard plugin](https://github.com/guard/guard-railroader).
|
146
|
+
|
147
|
+
# Building
|
148
|
+
|
149
|
+
git clone git://github.com/david-a-wheeler/railroader.git
|
150
|
+
cd railroader
|
151
|
+
gem build railroader.gemspec
|
152
|
+
gem install railroader*.gem
|
153
|
+
|
154
|
+
<!-- # Who is Using Railroader?
|
155
|
+
|
156
|
+
* [Code Climate](https://codeclimate.com/)
|
157
|
+
* [GitHub](https://github.com/)
|
158
|
+
* [Groupon](http://www.groupon.com/)
|
159
|
+
* [New Relic](http://newrelic.com)
|
160
|
+
* [Twitter](https://twitter.com/)
|
161
|
+
|
162
|
+
[..and more!](http://railroaderscanner.org/railroader_users)
|
163
|
+
|
164
|
+
-->
|
165
|
+
|
166
|
+
# Homepage/News
|
167
|
+
|
168
|
+
Website: http://railroader.org/
|
169
|
+
|
170
|
+
Twitter: https://twitter.com/railroader
|
171
|
+
|
172
|
+
# License
|
173
|
+
|
174
|
+
See [MIT-LICENSE](MIT-LICENSE).
|
data/bin/railroader
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Railroader
|
4
|
+
class AppTree
|
5
|
+
VIEW_EXTENSIONS = %w[html.erb html.haml rhtml js.erb html.slim].join(",")
|
6
|
+
|
7
|
+
attr_reader :root
|
8
|
+
|
9
|
+
def self.from_options(options)
|
10
|
+
root = File.expand_path options[:app_path]
|
11
|
+
|
12
|
+
# Convert files into Regexp for matching
|
13
|
+
init_options = {}
|
14
|
+
if options[:skip_files]
|
15
|
+
init_options[:skip_files] = regex_for_paths(options[:skip_files])
|
16
|
+
end
|
17
|
+
|
18
|
+
if options[:only_files]
|
19
|
+
init_options[:only_files] = regex_for_paths(options[:only_files])
|
20
|
+
end
|
21
|
+
init_options[:additional_libs_path] = options[:additional_libs_path]
|
22
|
+
init_options[:engine_paths] = options[:engine_paths]
|
23
|
+
new(root, init_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Accepts an array of filenames and paths with the following format and
|
27
|
+
# returns a Regexp to match them:
|
28
|
+
# * "path1/file1.rb" - Matches a specific filename in the project directory.
|
29
|
+
# * "path1/" - Matches any path that conatains "path1" in the project directory.
|
30
|
+
# * "/path1/ - Matches any path that is rooted at "path1" in the project directory.
|
31
|
+
#
|
32
|
+
def self.regex_for_paths(paths)
|
33
|
+
path_regexes = paths.map do |f|
|
34
|
+
# If path ends in a file separator then we assume it is a path rather
|
35
|
+
# than a filename.
|
36
|
+
if f.end_with?(File::SEPARATOR)
|
37
|
+
# If path starts with a file separator then we assume that they
|
38
|
+
# want the project relative path to start with this path prefix.
|
39
|
+
if f.start_with?(File::SEPARATOR)
|
40
|
+
"\\A#{Regexp.escape f}"
|
41
|
+
# If it ends in a file separator, but does not begin with a file
|
42
|
+
# separator then we assume the path can match any path component in
|
43
|
+
# the project.
|
44
|
+
else
|
45
|
+
Regexp.escape f
|
46
|
+
end
|
47
|
+
else
|
48
|
+
"#{Regexp.escape f}\\z"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
Regexp.new("(?:" << path_regexes.join("|") << ")")
|
52
|
+
end
|
53
|
+
private_class_method(:regex_for_paths)
|
54
|
+
|
55
|
+
def initialize(root, init_options = {})
|
56
|
+
@root = root
|
57
|
+
@project_root_path = Pathname.new(@root)
|
58
|
+
@skip_files = init_options[:skip_files]
|
59
|
+
@only_files = init_options[:only_files]
|
60
|
+
@additional_libs_path = init_options[:additional_libs_path] || []
|
61
|
+
@engine_paths = init_options[:engine_paths] || []
|
62
|
+
@absolute_engine_paths = @engine_paths.select { |path| path.start_with?(File::SEPARATOR) }
|
63
|
+
@relative_engine_paths = @engine_paths - @absolute_engine_paths
|
64
|
+
end
|
65
|
+
|
66
|
+
def expand_path(path)
|
67
|
+
File.expand_path(path, @root)
|
68
|
+
end
|
69
|
+
|
70
|
+
def read(path)
|
71
|
+
File.read(File.join(@root, path))
|
72
|
+
end
|
73
|
+
|
74
|
+
# This variation requires full paths instead of paths based
|
75
|
+
# off the project root. I'd prefer to get all the code outside
|
76
|
+
# of AppTree using project-root based paths (e.g. app/models/user.rb)
|
77
|
+
# instead of full paths, but I suspect it's an incompatible change.
|
78
|
+
def read_path(path)
|
79
|
+
File.read(path)
|
80
|
+
end
|
81
|
+
|
82
|
+
def exists?(path)
|
83
|
+
File.exist?(File.join(@root, path))
|
84
|
+
end
|
85
|
+
|
86
|
+
# This is a pair for #read_path. Again, would like to kill these
|
87
|
+
def path_exists?(path)
|
88
|
+
File.exist?(path)
|
89
|
+
end
|
90
|
+
|
91
|
+
def initializer_paths
|
92
|
+
@initializer_paths ||= prioritize_concerns(find_paths("config/initializers"))
|
93
|
+
end
|
94
|
+
|
95
|
+
def controller_paths
|
96
|
+
@controller_paths ||= prioritize_concerns(find_paths("app/**/controllers"))
|
97
|
+
end
|
98
|
+
|
99
|
+
def model_paths
|
100
|
+
@model_paths ||= prioritize_concerns(find_paths("app/**/models"))
|
101
|
+
end
|
102
|
+
|
103
|
+
def template_paths
|
104
|
+
@template_paths ||= find_paths("app/**/views", "*.{#{VIEW_EXTENSIONS}}") +
|
105
|
+
find_paths("app/**/views", "*.{erb,haml,slim}").reject { |path| File.basename(path).count(".") > 1 }
|
106
|
+
end
|
107
|
+
|
108
|
+
def layout_exists?(name)
|
109
|
+
!Dir.glob("#{root_search_pattern}app/views/layouts/#{name}.html.{erb,haml,slim}").empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
def lib_paths
|
113
|
+
@lib_files ||= find_paths("lib").reject { |path| path.include? "/generators/" or path.include? "lib/tasks/" or path.include? "lib/templates/" } +
|
114
|
+
find_additional_lib_paths +
|
115
|
+
find_helper_paths +
|
116
|
+
find_job_paths
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def find_helper_paths
|
122
|
+
find_paths "app/helpers"
|
123
|
+
end
|
124
|
+
|
125
|
+
def find_job_paths
|
126
|
+
find_paths "app/jobs"
|
127
|
+
end
|
128
|
+
|
129
|
+
def find_additional_lib_paths
|
130
|
+
@additional_libs_path.collect{ |path| find_paths path }.flatten
|
131
|
+
end
|
132
|
+
|
133
|
+
def find_paths(directory, extensions = ".rb")
|
134
|
+
select_files(glob_files(directory, "*", extensions).sort)
|
135
|
+
end
|
136
|
+
|
137
|
+
def glob_files(directory, name, extensions = ".rb")
|
138
|
+
pattern = "#{root_search_pattern}#{directory}/**/#{name}#{extensions}"
|
139
|
+
|
140
|
+
Dir.glob(pattern)
|
141
|
+
end
|
142
|
+
|
143
|
+
def select_files(paths)
|
144
|
+
paths = select_only_files(paths)
|
145
|
+
reject_skipped_files(paths)
|
146
|
+
end
|
147
|
+
|
148
|
+
def select_only_files(paths)
|
149
|
+
return paths unless @only_files
|
150
|
+
|
151
|
+
paths.select do |path|
|
152
|
+
match_path @only_files, path
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def reject_skipped_files(paths)
|
157
|
+
return paths unless @skip_files
|
158
|
+
|
159
|
+
paths.reject do |path|
|
160
|
+
match_path @skip_files, path
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def match_path files, path
|
165
|
+
absolute_path = Pathname.new(path)
|
166
|
+
# relative root never has a leading separator. But, we use a leading
|
167
|
+
# separator in a @skip_files entry to imply that a directory is
|
168
|
+
# "absolute" with respect to the project directory.
|
169
|
+
project_relative_path = File.join(
|
170
|
+
File::SEPARATOR,
|
171
|
+
absolute_path.relative_path_from(@project_root_path).to_s
|
172
|
+
)
|
173
|
+
|
174
|
+
files.match(project_relative_path)
|
175
|
+
end
|
176
|
+
|
177
|
+
def root_search_pattern
|
178
|
+
return @root_search_pattern if @root_search_pattern
|
179
|
+
abs = @absolute_engine_paths.to_a.map { |path| path.gsub /#{File::SEPARATOR}+$/, '' }
|
180
|
+
rel = @relative_engine_paths.to_a.map { |path| path.gsub /#{File::SEPARATOR}+$/, '' }
|
181
|
+
|
182
|
+
roots = ([@root] + abs).join(",")
|
183
|
+
rel_engines = (rel + [""]).join("/,")
|
184
|
+
@root_search_patrern = "{#{roots}}/{#{rel_engines}}"
|
185
|
+
end
|
186
|
+
|
187
|
+
def prioritize_concerns paths
|
188
|
+
paths.partition { |path| path.include? "concerns" }.flatten
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
#Stores call sites to look up later.
|
4
|
+
class Railroader::CallIndex
|
5
|
+
|
6
|
+
#Initialize index with calls from FindAllCalls
|
7
|
+
def initialize calls
|
8
|
+
@calls_by_method = Hash.new { |h, k| h[k] = [] }
|
9
|
+
@calls_by_target = Hash.new { |h, k| h[k] = [] }
|
10
|
+
|
11
|
+
index_calls calls
|
12
|
+
end
|
13
|
+
|
14
|
+
#Find calls matching specified option hash.
|
15
|
+
#
|
16
|
+
#Options:
|
17
|
+
#
|
18
|
+
# * :target - symbol, array of symbols, or regular expression to match target(s)
|
19
|
+
# * :method - symbol, array of symbols, or regular expression to match method(s)
|
20
|
+
# * :chained - boolean, whether or not to match against a whole method chain (false by default)
|
21
|
+
# * :nested - boolean, whether or not to match against a method call that is a target itself (false by default)
|
22
|
+
def find_calls options
|
23
|
+
target = options[:target] || options[:targets]
|
24
|
+
method = options[:method] || options[:methods]
|
25
|
+
nested = options[:nested]
|
26
|
+
|
27
|
+
if options[:chained]
|
28
|
+
return find_chain options
|
29
|
+
#Find by narrowest category
|
30
|
+
elsif target and method and target.is_a? Array and method.is_a? Array
|
31
|
+
if target.length > method.length
|
32
|
+
calls = filter_by_target calls_by_methods(method), target
|
33
|
+
else
|
34
|
+
calls = calls_by_targets(target)
|
35
|
+
calls = filter_by_method calls, method
|
36
|
+
end
|
37
|
+
|
38
|
+
#Find by target, then by methods, if provided
|
39
|
+
elsif target
|
40
|
+
calls = calls_by_target target
|
41
|
+
|
42
|
+
if calls and method
|
43
|
+
calls = filter_by_method calls, method
|
44
|
+
end
|
45
|
+
|
46
|
+
#Find calls with no explicit target
|
47
|
+
#with either :target => nil or :target => false
|
48
|
+
elsif (options.key? :target or options.key? :targets) and not target and method
|
49
|
+
calls = calls_by_method method
|
50
|
+
calls = filter_by_target calls, nil
|
51
|
+
|
52
|
+
#Find calls by method
|
53
|
+
elsif method
|
54
|
+
calls = calls_by_method method
|
55
|
+
else
|
56
|
+
raise "Invalid arguments to CallCache#find_calls: #{options.inspect}"
|
57
|
+
end
|
58
|
+
|
59
|
+
return [] if calls.nil?
|
60
|
+
|
61
|
+
#Remove calls that are actually targets of other calls
|
62
|
+
#Unless those are explicitly desired
|
63
|
+
calls = filter_nested calls unless nested
|
64
|
+
|
65
|
+
calls
|
66
|
+
end
|
67
|
+
|
68
|
+
def remove_template_indexes template_name = nil
|
69
|
+
[@calls_by_method, @calls_by_target].each do |calls_by|
|
70
|
+
calls_by.each do |_name, calls|
|
71
|
+
calls.delete_if do |call|
|
72
|
+
from_template call, template_name
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_indexes_by_class classes
|
79
|
+
[@calls_by_method, @calls_by_target].each do |calls_by|
|
80
|
+
calls_by.each do |_name, calls|
|
81
|
+
calls.delete_if do |call|
|
82
|
+
call[:location][:type] == :class and classes.include? call[:location][:class]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def index_calls calls
|
89
|
+
calls.each do |call|
|
90
|
+
@calls_by_method[call[:method]] << call
|
91
|
+
|
92
|
+
target = call[:target]
|
93
|
+
|
94
|
+
if not target.is_a? Sexp
|
95
|
+
@calls_by_target[target] << call
|
96
|
+
elsif target.node_type == :params or target.node_type == :session
|
97
|
+
@calls_by_target[target.node_type] << call
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def find_chain options
|
105
|
+
target = options[:target] || options[:targets]
|
106
|
+
method = options[:method] || options[:methods]
|
107
|
+
|
108
|
+
calls = calls_by_method method
|
109
|
+
|
110
|
+
return [] if calls.nil?
|
111
|
+
|
112
|
+
calls = filter_by_chain calls, target
|
113
|
+
end
|
114
|
+
|
115
|
+
def calls_by_target target
|
116
|
+
if target.is_a? Array
|
117
|
+
calls_by_targets target
|
118
|
+
else
|
119
|
+
@calls_by_target[target]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def calls_by_targets targets
|
124
|
+
calls = []
|
125
|
+
|
126
|
+
targets.each do |target|
|
127
|
+
calls.concat @calls_by_target[target] if @calls_by_target.key? target
|
128
|
+
end
|
129
|
+
|
130
|
+
calls
|
131
|
+
end
|
132
|
+
|
133
|
+
def calls_by_method method
|
134
|
+
if method.is_a? Array
|
135
|
+
calls_by_methods method
|
136
|
+
elsif method.is_a? Regexp
|
137
|
+
calls_by_methods_regex method
|
138
|
+
else
|
139
|
+
@calls_by_method[method.to_sym]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def calls_by_methods methods
|
144
|
+
methods = methods.map { |m| m.to_sym }
|
145
|
+
calls = []
|
146
|
+
|
147
|
+
methods.each do |method|
|
148
|
+
calls.concat @calls_by_method[method] if @calls_by_method.key? method
|
149
|
+
end
|
150
|
+
|
151
|
+
calls
|
152
|
+
end
|
153
|
+
|
154
|
+
def calls_by_methods_regex methods_regex
|
155
|
+
calls = []
|
156
|
+
@calls_by_method.each do |key, value|
|
157
|
+
calls.concat value if key.to_s.match methods_regex
|
158
|
+
end
|
159
|
+
calls
|
160
|
+
end
|
161
|
+
|
162
|
+
def calls_with_no_target
|
163
|
+
@calls_by_target[nil]
|
164
|
+
end
|
165
|
+
|
166
|
+
def filter calls, key, value
|
167
|
+
if value.is_a? Array
|
168
|
+
values = Set.new value
|
169
|
+
|
170
|
+
calls.select do |call|
|
171
|
+
values.include? call[key]
|
172
|
+
end
|
173
|
+
elsif value.is_a? Regexp
|
174
|
+
calls.select do |call|
|
175
|
+
call[key].to_s.match value
|
176
|
+
end
|
177
|
+
else
|
178
|
+
calls.select do |call|
|
179
|
+
call[key] == value
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def filter_by_method calls, method
|
185
|
+
filter calls, :method, method
|
186
|
+
end
|
187
|
+
|
188
|
+
def filter_by_target calls, target
|
189
|
+
filter calls, :target, target
|
190
|
+
end
|
191
|
+
|
192
|
+
def filter_nested calls
|
193
|
+
filter calls, :nested, false
|
194
|
+
end
|
195
|
+
|
196
|
+
def filter_by_chain calls, target
|
197
|
+
if target.is_a? Array
|
198
|
+
targets = Set.new target
|
199
|
+
|
200
|
+
calls.select do |call|
|
201
|
+
targets.include? call[:chain].first
|
202
|
+
end
|
203
|
+
elsif target.is_a? Regexp
|
204
|
+
calls.select do |call|
|
205
|
+
call[:chain].first.to_s.match target
|
206
|
+
end
|
207
|
+
else
|
208
|
+
calls.select do |call|
|
209
|
+
call[:chain].first == target
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def from_template call, template_name
|
215
|
+
return false unless call[:location][:type] == :template
|
216
|
+
return true if template_name.nil?
|
217
|
+
call[:location][:template] == template_name
|
218
|
+
end
|
219
|
+
end
|