class Analyzer::Ruby::Rails

Defined in:

analyzer/analyzers/ruby/rails.cr

Constant Summary

DEVISE_ROUTES = [{"GET", "/sign_in"}, {"POST", "/sign_in"}, {"DELETE", "/sign_out"}, {"POST", ""}, {"GET", "/sign_up"}, {"GET", "/edit"}, {"PUT", ""}, {"PATCH", ""}, {"DELETE", ""}, {"GET", "/password/new"}, {"POST", "/password"}, {"GET", "/password/edit"}, {"PUT", "/password"}, {"PATCH", "/password"}, {"GET", "/confirmation/new"}, {"POST", "/confirmation"}, {"GET", "/confirmation"}, {"GET", "/unlock/new"}, {"POST", "/unlock"}, {"GET", "/unlock"}]

Devise routes generated by devise_for :scope. Tuples are {METHOD, sub_path} appended to the scope's base path.

DOORKEEPER_ROUTES = [{"GET", "/oauth/authorize", "authorizations", "new"}, {"POST", "/oauth/authorize", "authorizations", "create"}, {"DELETE", "/oauth/authorize", "authorizations", "destroy"}, {"POST", "/oauth/token", "tokens", "create"}, {"POST", "/oauth/revoke", "tokens", "revoke"}, {"POST", "/oauth/introspect", "tokens", "introspect"}, {"GET", "/oauth/token/info", "token_info", "show"}, {"GET", "/oauth/applications", "applications", "index"}, {"POST", "/oauth/applications", "applications", "create"}, {"GET", "/oauth/applications/new", "applications", "new"}, {"GET", "/oauth/applications/:id", "applications", "show"}, {"GET", "/oauth/applications/:id/edit", "applications", "edit"}, {"PATCH", "/oauth/applications/:id", "applications", "update"}, {"PUT", "/oauth/applications/:id", "applications", "update"}, {"DELETE", "/oauth/applications/:id", "applications", "destroy"}, {"GET", "/oauth/authorized_applications", "authorized_applications", "index"}, {"DELETE", "/oauth/authorized_applications/:id", "authorized_applications", "destroy"}]

Routes generated by Doorkeeper's use_doorkeeper (the de-facto OAuth2 provider for Rails — Mastodon, GitLab, Discourse plugins, …). Each tuple is {METHOD, sub_path, group, action}. group matches the skip_controllers symbols so a use_doorkeeper do skip_controllers :applications end block drops the right rows, and indexes the controllers foo: 'bar' remap used to resolve the implementing controller for params/callees. Paths are appended to /oauth under the scope where use_doorkeeper runs.

INLINE_ROUTE_BLOCK = /^(member|collection|new)\s*\{\s*(.+?)\s*\}\s*$/

Rails commonly writes compact inline route scopes: collection { get "preview" } / member { post :use }. The parser is stack-based, so normalize these to the same do/end structure before route extraction.

LOOP_OPENER = /^%([wi])[\[\(\{]([^\]\)\}]*)[\]\)\}]\s*\.each(?:_with_index)?\s+do\s*\|\s*([a-z_]\w*)[^|]*\|\s*$/

A %w[...].each do |var| / %i[...].each_with_index do |var, i| block over a STATIC literal list unrolls to one copy of its body per element with the loop variable's #{var} interpolations substituted. discourse alone wraps ~46 routes in %w[users u].each — without this, get "#{root_path}/..." leaks {root_path} into ~130 endpoints (a fabricated path param) AND the real /users + /u variants are lost.

Only literal %w/%i receivers are expanded: a dynamic Model.scopes.each do |s| cannot be resolved to values at parse time, so it is left for the normal parser (its block opens a neutral frame).

MAX_LOOP_DEPTH = 3

noir scans arbitrary untrusted repos, so loop unrolling must not be a DoS vector — expansion is multiplicative (elements ^ nesting). A crafted routes.rb with deep nesting or a huge %w[...] list could otherwise blow up CPU/memory. Real apps use tiny shallow lists (discourse's biggest is the 2-element %w[users u]), so modest caps preserve all genuine recall while bounding the worst case. Over a limit, the block is passed through unexpanded (the pre-unrolling behavior — a transparent frame).

MAX_LOOP_OUTPUT = 20000
MAX_LOOP_VALUES = 100

Instance Method Summary

Instance methods inherited from class Analyzer::Ruby::RubyEngine

line_to_endpoint(content : String, details : Details | Nil = nil) : Endpoint line_to_endpoint

Class methods inherited from class Analyzer::Ruby::RubyEngine

ruby_test_path?(path : String) : Bool ruby_test_path?

Instance methods inherited from class Analyzer

analyze analyze, base_path : String base_path, base_paths : Array(String) base_paths, callees_needed? : Bool callees_needed?, logger : NoirLogger logger, parallel_analyze(files : Array(String), &block : String -> Nil) parallel_analyze, read_file_content(path : String) : String read_file_content, result : Array(Endpoint) result, url : String url

Constructor methods inherited from class Analyzer

new(options : Hash(String, YAML::Any)) new

Macros inherited from class Analyzer

define_getter_methods(names) define_getter_methods

Instance methods inherited from module FileHelper

all_files : Array(String) all_files, get_files_by_extension(extension : String) : Array(String) get_files_by_extension, get_files_by_prefix(prefix : String) : Array(String) get_files_by_prefix, get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String) get_files_by_prefix_and_extension, get_public_dir_files(base_path : String, folder : String) : Array(String) get_public_dir_files, get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String) get_public_files

Instance Method Detail

def analyze #

[View source]
def controller_to_endpoint(path : String, url : String, resource : String, path_prefix : String = "", only : Array(String) = [] of String, except : Array(String) = [] of String, singular : Bool = false) #

[View source]