new_cms_scanner 0.13.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +26 -0
  4. data/app/app.rb +24 -0
  5. data/app/controllers/core/cli_options.rb +117 -0
  6. data/app/controllers/core.rb +82 -0
  7. data/app/controllers/interesting_findings.rb +25 -0
  8. data/app/finders/interesting_findings/fantastico_fileslist.rb +21 -0
  9. data/app/finders/interesting_findings/headers.rb +17 -0
  10. data/app/finders/interesting_findings/robots_txt.rb +20 -0
  11. data/app/finders/interesting_findings/search_replace_db_2.rb +19 -0
  12. data/app/finders/interesting_findings/xml_rpc.rb +61 -0
  13. data/app/finders/interesting_findings.rb +25 -0
  14. data/app/formatters/cli.rb +65 -0
  15. data/app/formatters/cli_no_color.rb +9 -0
  16. data/app/formatters/cli_no_colour.rb +17 -0
  17. data/app/formatters/json.rb +14 -0
  18. data/app/models/fantastico_fileslist.rb +34 -0
  19. data/app/models/headers.rb +44 -0
  20. data/app/models/interesting_finding.rb +48 -0
  21. data/app/models/robots_txt.rb +31 -0
  22. data/app/models/search_replace_db_2.rb +17 -0
  23. data/app/models/user.rb +35 -0
  24. data/app/models/version.rb +49 -0
  25. data/app/models/xml_rpc.rb +78 -0
  26. data/app/user_agents.txt +46 -0
  27. data/app/views/cli/core/banner.erb +1 -0
  28. data/app/views/cli/core/finished.erb +8 -0
  29. data/app/views/cli/core/help.erb +4 -0
  30. data/app/views/cli/core/started.erb +6 -0
  31. data/app/views/cli/core/version.erb +1 -0
  32. data/app/views/cli/interesting_findings/_array.erb +10 -0
  33. data/app/views/cli/interesting_findings/findings.erb +23 -0
  34. data/app/views/cli/scan_aborted.erb +5 -0
  35. data/app/views/cli/usage.erb +3 -0
  36. data/app/views/json/core/banner.erb +1 -0
  37. data/app/views/json/core/finished.erb +10 -0
  38. data/app/views/json/core/help.erb +4 -0
  39. data/app/views/json/core/started.erb +5 -0
  40. data/app/views/json/core/version.erb +1 -0
  41. data/app/views/json/interesting_findings/findings.erb +24 -0
  42. data/app/views/json/scan_aborted.erb +5 -0
  43. data/lib/cms_scanner/browser/actions.rb +48 -0
  44. data/lib/cms_scanner/browser/options.rb +90 -0
  45. data/lib/cms_scanner/browser.rb +96 -0
  46. data/lib/cms_scanner/cache/file_store.rb +77 -0
  47. data/lib/cms_scanner/cache/typhoeus.rb +25 -0
  48. data/lib/cms_scanner/controller.rb +105 -0
  49. data/lib/cms_scanner/controllers.rb +67 -0
  50. data/lib/cms_scanner/errors/http.rb +72 -0
  51. data/lib/cms_scanner/errors/scan.rb +14 -0
  52. data/lib/cms_scanner/errors.rb +11 -0
  53. data/lib/cms_scanner/exit_code.rb +25 -0
  54. data/lib/cms_scanner/finders/base_finders.rb +45 -0
  55. data/lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb +121 -0
  56. data/lib/cms_scanner/finders/finder/enumerator.rb +77 -0
  57. data/lib/cms_scanner/finders/finder/fingerprinter.rb +48 -0
  58. data/lib/cms_scanner/finders/finder/smart_url_checker/findings.rb +33 -0
  59. data/lib/cms_scanner/finders/finder/smart_url_checker.rb +60 -0
  60. data/lib/cms_scanner/finders/finder.rb +75 -0
  61. data/lib/cms_scanner/finders/finding.rb +54 -0
  62. data/lib/cms_scanner/finders/findings.rb +26 -0
  63. data/lib/cms_scanner/finders/independent_finder.rb +30 -0
  64. data/lib/cms_scanner/finders/independent_finders.rb +26 -0
  65. data/lib/cms_scanner/finders/same_type_finder.rb +19 -0
  66. data/lib/cms_scanner/finders/same_type_finders.rb +26 -0
  67. data/lib/cms_scanner/finders/unique_finder.rb +19 -0
  68. data/lib/cms_scanner/finders/unique_finders.rb +47 -0
  69. data/lib/cms_scanner/finders.rb +12 -0
  70. data/lib/cms_scanner/formatter/buffer.rb +17 -0
  71. data/lib/cms_scanner/formatter.rb +149 -0
  72. data/lib/cms_scanner/helper.rb +7 -0
  73. data/lib/cms_scanner/numeric.rb +13 -0
  74. data/lib/cms_scanner/parsed_cli.rb +37 -0
  75. data/lib/cms_scanner/progressbar_null_output.rb +23 -0
  76. data/lib/cms_scanner/public_suffix/domain.rb +42 -0
  77. data/lib/cms_scanner/references.rb +132 -0
  78. data/lib/cms_scanner/scan.rb +88 -0
  79. data/lib/cms_scanner/target/hashes.rb +45 -0
  80. data/lib/cms_scanner/target/platform/php.rb +62 -0
  81. data/lib/cms_scanner/target/platform.rb +3 -0
  82. data/lib/cms_scanner/target/scope.rb +103 -0
  83. data/lib/cms_scanner/target/server/apache.rb +27 -0
  84. data/lib/cms_scanner/target/server/generic.rb +72 -0
  85. data/lib/cms_scanner/target/server/iis.rb +29 -0
  86. data/lib/cms_scanner/target/server/nginx.rb +27 -0
  87. data/lib/cms_scanner/target/server.rb +6 -0
  88. data/lib/cms_scanner/target.rb +124 -0
  89. data/lib/cms_scanner/typhoeus/hydra.rb +12 -0
  90. data/lib/cms_scanner/typhoeus/response.rb +27 -0
  91. data/lib/cms_scanner/version.rb +6 -0
  92. data/lib/cms_scanner/vulnerability.rb +46 -0
  93. data/lib/cms_scanner/web_site.rb +145 -0
  94. data/lib/cms_scanner.rb +141 -0
  95. metadata +426 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ class Finder
6
+ # Module to provide an easy way to enumerate items such as plugins, themes etc
7
+ module Enumerator
8
+ # @return [ Hash ]
9
+ def head_or_get_request_params
10
+ # Disabling the cache, as it causes a 'stack level too deep' exception
11
+ # with a large number of requests.
12
+ # See https://github.com/typhoeus/typhoeus/issues/408
13
+ @head_or_get_request_params ||= target.head_or_get_params.merge(cache_ttl: 0)
14
+ end
15
+
16
+ # @return [ Array<Integer> ]
17
+ def valid_response_codes
18
+ @valid_response_codes ||= [200]
19
+ end
20
+
21
+ # @param [ Hash ] The target urls
22
+ # @param [ Hash ] opts
23
+ # @option opts [ Boolean ] :show_progression Wether or not to display the progress bar
24
+ # @option opts [ Regexp ] :exclude_content
25
+ # @option opts [ Boolean, Array, String ] :check_full_response
26
+ #
27
+ # @yield [ Typhoeus::Response, String ]
28
+ def enumerate(urls, opts = {})
29
+ create_progress_bar(opts.merge(total: urls.size))
30
+
31
+ urls.each do |url, id|
32
+ request = browser.forge_request(url, head_or_get_request_params)
33
+
34
+ request.on_complete do |head_res|
35
+ progress_bar.increment
36
+
37
+ next unless valid_response_codes.include?(head_res.code)
38
+
39
+ next if opts[:exclude_content] && head_res.response_headers&.match(opts[:exclude_content])
40
+
41
+ head_or_full_res = maybe_get_full_response(head_res, opts)
42
+
43
+ yield head_or_full_res, id if head_or_full_res
44
+ end
45
+
46
+ hydra.queue(request)
47
+ end
48
+
49
+ hydra.run
50
+ end
51
+
52
+ # @param [ Typhoeus::Response ] head_res
53
+ # @param [ Hash ] opts
54
+ #
55
+ # @return [ Typhoeus::Response, nil ]
56
+ def maybe_get_full_response(head_res, opts)
57
+ return head_res unless opts[:check_full_response] == true ||
58
+ Array(opts[:check_full_response]).include?(head_res.code)
59
+
60
+ full_res = NS::Browser.get(head_res.effective_url, full_request_params)
61
+
62
+ return unless valid_response_codes.include?(full_res.code)
63
+
64
+ return if target.homepage_or_404?(full_res) ||
65
+ (opts[:exclude_content] && full_res.body&.match(opts[:exclude_content]))
66
+
67
+ full_res
68
+ end
69
+
70
+ # @return [ Hash ]
71
+ def full_request_params
72
+ @full_request_params ||= {}
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ class Finder
6
+ # Module to provide an easy way to fingerprint things such as versions
7
+ module Fingerprinter
8
+ include Enumerator
9
+
10
+ # @param [ Hash ] fingerprints The fingerprints
11
+ # Format should be like the following:
12
+ # {
13
+ # file_path_1: {
14
+ # md5_hash_1: version_1,
15
+ # md5_hash_2: [version_2]
16
+ # },
17
+ # file_path_2: {
18
+ # md5_hash_3: [version_1, version_2],
19
+ # md5_hash_4: version_3
20
+ # }
21
+ # }
22
+ # Note that the version can either be an array or a string
23
+ #
24
+ # @param [ Hash ] opts
25
+ # @option opts [ Boolean ] :show_progression Wether or not to display the progress bar
26
+ #
27
+ # @yield [ Mixed, String, String ] version/s, url, hash The version associated to the
28
+ # fingerprint of the url
29
+ def fingerprint(fingerprints, opts = {})
30
+ enum_opts = opts.merge(check_full_response: 200)
31
+
32
+ enumerate(fingerprints.transform_keys { |k| target.url(k) }, enum_opts) do |res, fingerprint|
33
+ md5sum = hexdigest(res.body)
34
+
35
+ next unless fingerprint.key?(md5sum)
36
+
37
+ yield fingerprint[md5sum], res.effective_url, md5sum
38
+ end
39
+ end
40
+
41
+ # @return [ String ] The hashed value for the given body
42
+ def hexdigest(body)
43
+ Digest::MD5.hexdigest(body)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ class Finder
6
+ module SmartURLChecker
7
+ # Findings
8
+ class Findings < Array
9
+ def <<(finding)
10
+ return self unless finding
11
+
12
+ each do |f|
13
+ next unless f == finding && f.found_by == finding.found_by
14
+
15
+ # This makes sure entries added are unique
16
+ # and prevent pages redirecting to the same one to be added twice
17
+ entries_to_add = finding.interesting_entries - f.interesting_entries
18
+ return self if entries_to_add.empty?
19
+
20
+ entries_to_add.each { |entry| f.interesting_entries << entry }
21
+
22
+ f.confidence += finding.confidence
23
+
24
+ return self
25
+ end
26
+
27
+ super(finding)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cms_scanner/finders/finder/smart_url_checker/findings'
4
+
5
+ module CMSScanner
6
+ module Finders
7
+ class Finder
8
+ # Smart URL Checker
9
+ # Typically used when some URLs are potentially in the homepage. If they are found
10
+ # in it, they will be checked in the #passive (like a browser/client would do when loading the page).
11
+ # Otherwise they will be checked in the #aggressive
12
+ module SmartURLChecker
13
+ # @param [ Array<String> ] urls
14
+ # @param [ Hash ] opts
15
+ #
16
+ # @return []
17
+ def process_urls(_urls, _opts = {})
18
+ raise NotImplementedError
19
+ end
20
+
21
+ # @param [ Hash ] opts
22
+ #
23
+ # @return [ Array<Finding> ]
24
+ def passive(opts = {})
25
+ process_urls(passive_urls(opts), opts)
26
+ end
27
+
28
+ # @param [ Hash ] opts
29
+ #
30
+ # @return [ Array<String> ]
31
+ def passive_urls(_opts = {})
32
+ target.in_scope_uris(target.homepage_res, passive_urls_xpath).map(&:to_s)
33
+ end
34
+
35
+ # @return [ String ]
36
+ def passive_urls_xpath
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # @param [ Hash ] opts
41
+ #
42
+ # @return [ Array<Finding> ]
43
+ def aggressive(opts = {})
44
+ # To avoid scanning the same twice
45
+ urls = aggressive_urls(opts)
46
+ urls -= passive_urls(opts) if opts[:mode] == :mixed
47
+
48
+ process_urls(urls, opts)
49
+ end
50
+
51
+ # @param [ Hash ] opts
52
+ #
53
+ # @return [ Array<String> ]
54
+ def aggressive_urls(_opts = {})
55
+ raise NotImplementedError
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cms_scanner/finders/finder/smart_url_checker'
4
+ require 'cms_scanner/finders/finder/enumerator'
5
+ require 'cms_scanner/finders/finder/fingerprinter'
6
+ require 'cms_scanner/finders/finder/breadth_first_dictionary_attack'
7
+
8
+ module CMSScanner
9
+ module Finders
10
+ # Finder
11
+ class Finder
12
+ # Constants for common found_by
13
+ DIRECT_ACCESS = 'Direct Access (Aggressive Detection)'
14
+
15
+ attr_accessor :target, :progress_bar
16
+
17
+ def initialize(target)
18
+ @target = target
19
+ end
20
+
21
+ # @return [ String ] The titleized name of the finder
22
+ def titleize
23
+ # Put a _ char before any digits except those at the end, which will be replaced by a space
24
+ # Otherwise, class such as Error404Page are returned as Error404 Page instead of Error 404 page
25
+ # The keep_id_suffix is to concevert classes such as CssId to Css Id instead of Css
26
+
27
+ @titleize ||= self.class.to_s.demodulize.gsub(/(\d+)[a-z]+/i, '_\0').titleize(keep_id_suffix: true)
28
+ end
29
+
30
+ # @param [ Hash ] _opts
31
+ def passive(_opts = {}); end
32
+
33
+ # @param [ Hash ] _opts
34
+ def aggressive(_opts = {}); end
35
+
36
+ # @param [ Hash ] opts See https://github.com/jfelchner/ruby-progressbar/wiki/Options
37
+ # @option opts [ Boolean ] :show_progression
38
+ #
39
+ # @return [ ProgressBar::Base ]
40
+ def create_progress_bar(opts = {})
41
+ bar_opts = { format: '%t %a <%B> (%c / %C) %P%% %e' }
42
+ bar_opts[:output] = ProgressBarNullOutput unless opts[:show_progression]
43
+
44
+ @progress_bar = ::ProgressBar.create(bar_opts.merge(opts))
45
+ end
46
+
47
+ # @return [ Browser ]
48
+ def browser
49
+ @browser ||= NS::Browser.instance
50
+ end
51
+
52
+ # @return [ Typhoeus::Hydra ]
53
+ def hydra
54
+ @hydra ||= browser.hydra
55
+ end
56
+
57
+ # @param [String, Class ] klass
58
+ # @return [ String ]
59
+ def found_by(klass = self.class)
60
+ labels = %w[aggressive passive]
61
+
62
+ caller_locations.each do |call|
63
+ label = call.label
64
+
65
+ next unless labels.include? label
66
+
67
+ title = klass.to_s.demodulize.gsub(/(\d+)[a-z]+/i, '_\0').titleize(keep_id_suffix: true)
68
+
69
+ return "#{title} (#{label.capitalize} Detection)"
70
+ end
71
+ nil
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # Finding
6
+ module Finding
7
+ # Fix for "Double/Dynamic Inclusion Problem"
8
+ def self.included(base)
9
+ base.include References
10
+ super(base)
11
+ end
12
+
13
+ FINDING_OPTS = %i[confidence confirmed_by references found_by interesting_entries].freeze
14
+
15
+ attr_accessor(*FINDING_OPTS)
16
+
17
+ # @return [ Array ]
18
+ def confirmed_by
19
+ @confirmed_by ||= []
20
+ end
21
+
22
+ # Should be overriden in child classes
23
+ # @return [ Array ]
24
+ def interesting_entries
25
+ @interesting_entries ||= []
26
+ end
27
+
28
+ # @return [ Integer ]
29
+ def confidence
30
+ @confidence ||= 0
31
+ end
32
+
33
+ # @param [ Integer ] value
34
+ def confidence=(value)
35
+ @confidence = value >= 100 ? 100 : value
36
+ end
37
+
38
+ # @param [ Hash ] opts
39
+ def parse_finding_options(opts = {})
40
+ FINDING_OPTS.each { |opt| send("#{opt}=", opts[opt]) if opts.key?(opt) }
41
+ end
42
+
43
+ # TODO: maybe also check for interesting_entries and confirmed_by ?
44
+ # So far this is used in specs only
45
+ def eql?(other)
46
+ self == other && confidence == other.confidence && found_by == other.found_by
47
+ end
48
+
49
+ def <=>(other)
50
+ to_s.downcase <=> other.to_s.downcase
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # Findings container
6
+ class Findings < Array
7
+ # Override to include the confirmed_by logic
8
+ #
9
+ # @param [ Finding ] finding
10
+ def <<(finding)
11
+ return self unless finding
12
+
13
+ each do |found|
14
+ next unless found == finding
15
+
16
+ found.confirmed_by << finding
17
+ found.confidence += finding.confidence
18
+
19
+ return self
20
+ end
21
+
22
+ super(finding)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # Independent Finder
6
+ module IndependentFinder
7
+ extend ActiveSupport::Concern
8
+
9
+ # See ActiveSupport::Concern
10
+ module ClassMethods
11
+ def find(target, opts = {})
12
+ new(target).find(opts)
13
+ end
14
+ end
15
+
16
+ # @param [ Hash ] opts
17
+ # @option opts [ Symbol ] mode (:mixed, :passive, :aggressive)
18
+ #
19
+ # @return [ Findings ]
20
+ def find(opts = {})
21
+ finders.run(opts)
22
+ end
23
+
24
+ # @return [ Array ]
25
+ def finders
26
+ @finders ||= NS::Finders::IndependentFinders.new
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # This class is designed to handle independent results
6
+ # which are not related with each others
7
+ # e.g: interesting files
8
+ class IndependentFinders < BaseFinders
9
+ # @param [ Hash ] opts
10
+ # @option opts [ Symbol ] mode :mixed, :passive or :aggressive
11
+ #
12
+ # @return [ Findings ]
13
+ def run(opts = {})
14
+ methods = symbols_from_mode(opts[:mode])
15
+
16
+ each do |finder|
17
+ methods.each do |symbol|
18
+ run_finder(finder, symbol, opts)
19
+ end
20
+ end
21
+
22
+ filter_findings
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # Same Type Finder
6
+ module SameTypeFinder
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ include IndependentFinder
10
+
11
+ # @return [ Array ]
12
+ def finders
13
+ @finders ||= NS::Finders::SameTypeFinders.new
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # This class is designed to handle same type results, such as enumeration of plugins,
6
+ # themes etc.
7
+ class SameTypeFinders < BaseFinders
8
+ # @param [ Hash ] opts
9
+ # @option opts [ Symbol ] :mode :mixed, :passive or :aggressive
10
+ # @option opts [ Boolean ] :sort Wether or not to sort the findings
11
+ #
12
+ # @return [ Findings ]
13
+ def run(opts = {})
14
+ symbols_from_mode(opts[:mode]).each do |symbol|
15
+ each do |finder|
16
+ run_finder(finder, symbol, opts)
17
+ end
18
+ end
19
+
20
+ findings.sort! if opts[:sort]
21
+
22
+ filter_findings
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # Unique Finder
6
+ module UniqueFinder
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ include IndependentFinder
10
+
11
+ # @return [ Array ]
12
+ def finders
13
+ @finders ||= NS::Finders::UniqueFinders.new
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Finders
5
+ # This class is designed to return a unique result such as a version
6
+ # Note: Finders contained can return multiple results but the #run will only
7
+ # returned the best finding
8
+ class UniqueFinders < BaseFinders
9
+ # @param [ Hash ] opts
10
+ # @option opts [ Symbol ] :mode :mixed, :passive or :aggressive
11
+ # @option opts [ Int ] :confidence_threshold If a finding's confidence reaches this value,
12
+ # it will be returned as the best finding.
13
+ # Default is 100.
14
+ # If <= 0, all finders will be ran.
15
+ #
16
+ # @return [ Object, false ] The best finding or false if none
17
+ def run(opts = {})
18
+ opts[:confidence_threshold] ||= 100
19
+
20
+ symbols_from_mode(opts[:mode]).each do |symbol|
21
+ each do |finder|
22
+ run_finder(finder, symbol, opts)
23
+
24
+ next if opts[:confidence_threshold] <= 0
25
+
26
+ findings.each { |f| return f if f.confidence >= opts[:confidence_threshold] }
27
+ end
28
+ end
29
+
30
+ filter_findings
31
+ end
32
+
33
+ protected
34
+
35
+ # @return [ Object, false ] The best finding or false if none
36
+ def filter_findings
37
+ # results are sorted by confidence ASC
38
+ findings.sort_by!(&:confidence)
39
+
40
+ # If all findings have the same confidence, false is returned
41
+ return false if findings.size > 1 && findings.first.confidence == findings.last.confidence
42
+
43
+ findings.last || false
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cms_scanner/finders/finder'
4
+ require 'cms_scanner/finders/finding'
5
+ require 'cms_scanner/finders/findings'
6
+ require 'cms_scanner/finders/base_finders'
7
+ require 'cms_scanner/finders/independent_finders'
8
+ require 'cms_scanner/finders/independent_finder'
9
+ require 'cms_scanner/finders/unique_finders'
10
+ require 'cms_scanner/finders/unique_finder'
11
+ require 'cms_scanner/finders/same_type_finders'
12
+ require 'cms_scanner/finders/same_type_finder'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMSScanner
4
+ module Formatter
5
+ # Module used to output the rendered views into a buffer
6
+ # and beautify it a the end of the scan
7
+ module Buffer
8
+ def output(tpl, vars = {}, controller_name = nil)
9
+ buffer << render(tpl, vars, controller_name).encode('UTF-8', invalid: :replace, undef: :replace)
10
+ end
11
+
12
+ def buffer
13
+ @buffer ||= +''
14
+ end
15
+ end
16
+ end
17
+ end