railroader 4.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. 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,8 @@
1
+ #!/usr/bin/env ruby
2
+ #Adjust path in case called directly and not through gem
3
+ $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
4
+
5
+ require 'railroader'
6
+ require 'railroader/commandline'
7
+
8
+ Railroader::Commandline.start
@@ -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