| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  | # typed: true | 
					
						
							|  |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 15:13:12 -08:00
										 |  |  | require "extend/array" | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  | require "rubocops/shared/helper_functions" | 
					
						
							| 
									
										
										
										
											2022-11-05 21:27:52 +00:00
										 |  |  | require "shellwords" | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | module RuboCop | 
					
						
							|  |  |  |   module Cop | 
					
						
							| 
									
										
										
										
											2021-04-14 16:08:37 +01:00
										 |  |  |     module Homebrew | 
					
						
							| 
									
										
										
										
											2021-05-03 20:25:03 +01:00
										 |  |  |       # https://github.com/ruby/ruby/blob/v2_6_3/process.c#L2430-L2460 | 
					
						
							|  |  |  |       SHELL_BUILTINS = %w[
 | 
					
						
							|  |  |  |         ! | 
					
						
							|  |  |  |         . | 
					
						
							|  |  |  |         : | 
					
						
							|  |  |  |         break | 
					
						
							|  |  |  |         case | 
					
						
							|  |  |  |         continue | 
					
						
							|  |  |  |         do | 
					
						
							|  |  |  |         done | 
					
						
							|  |  |  |         elif | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |         esac | 
					
						
							|  |  |  |         eval | 
					
						
							|  |  |  |         exec | 
					
						
							|  |  |  |         exit | 
					
						
							|  |  |  |         export | 
					
						
							|  |  |  |         fi | 
					
						
							|  |  |  |         for | 
					
						
							|  |  |  |         if | 
					
						
							|  |  |  |         in | 
					
						
							|  |  |  |         readonly | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |         set | 
					
						
							|  |  |  |         shift | 
					
						
							|  |  |  |         then | 
					
						
							|  |  |  |         times | 
					
						
							|  |  |  |         trap | 
					
						
							|  |  |  |         unset | 
					
						
							|  |  |  |         until | 
					
						
							|  |  |  |         while | 
					
						
							|  |  |  |       ].freeze | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # https://github.com/ruby/ruby/blob/v2_6_3/process.c#L2495 | 
					
						
							|  |  |  |       SHELL_METACHARACTERS = %W[* ? { } [ ] < > ( ) ~ & | \\ $ ; ' ` " \n #].freeze | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  |       # This cop makes sure that shell command arguments are separated. | 
					
						
							|  |  |  |       # | 
					
						
							|  |  |  |       # @api private | 
					
						
							|  |  |  |       class ShellCommands < Base | 
					
						
							|  |  |  |         include HelperFunctions | 
					
						
							|  |  |  |         extend AutoCorrector | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         MSG = "Separate `%<method>s` commands into `%<good_args>s`" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         TARGET_METHODS = [ | 
					
						
							|  |  |  |           [nil, :system], | 
					
						
							|  |  |  |           [nil, :safe_system], | 
					
						
							|  |  |  |           [nil, :quiet_system], | 
					
						
							|  |  |  |           [:Utils, :popen_read], | 
					
						
							|  |  |  |           [:Utils, :safe_popen_read], | 
					
						
							|  |  |  |           [:Utils, :popen_write], | 
					
						
							|  |  |  |           [:Utils, :safe_popen_write], | 
					
						
							|  |  |  |         ].freeze | 
					
						
							|  |  |  |         RESTRICT_ON_SEND = TARGET_METHODS.map(&:second).uniq.freeze | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def on_send(node) | 
					
						
							|  |  |  |           TARGET_METHODS.each do |target_class, target_method| | 
					
						
							| 
									
										
										
										
											2023-04-18 15:06:50 -07:00
										 |  |  |             next if node.method_name != target_method | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             target_receivers = if target_class.nil? | 
					
						
							|  |  |  |               [nil, s(:const, nil, :Kernel), s(:const, nil, :Homebrew)] | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               [s(:const, nil, target_class)] | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |             next unless target_receivers.include?(node.receiver) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             first_arg = node.arguments.first | 
					
						
							|  |  |  |             arg_count = node.arguments.count | 
					
						
							|  |  |  |             if first_arg&.hash_type? # popen methods allow env hash | 
					
						
							|  |  |  |               first_arg = node.arguments.second | 
					
						
							|  |  |  |               arg_count -= 1
 | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |             next if first_arg.nil? || arg_count >= 2
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             first_arg_str = string_content(first_arg) | 
					
						
							| 
									
										
										
										
											2021-05-03 20:25:03 +01:00
										 |  |  |             stripped_first_arg_str = string_content(first_arg, strip_dynamic: true) | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             split_args = first_arg_str.shellsplit | 
					
						
							|  |  |  |             next if split_args.count <= 1
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-03 20:25:03 +01:00
										 |  |  |             # Only separate when no shell metacharacters are present | 
					
						
							|  |  |  |             command = split_args.first | 
					
						
							|  |  |  |             next if SHELL_BUILTINS.any?(command) | 
					
						
							|  |  |  |             next if command&.include?("=") | 
					
						
							|  |  |  |             next if SHELL_METACHARACTERS.any? { |meta| stripped_first_arg_str.include?(meta) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  |             good_args = split_args.map { |arg| "\"#{arg}\"" }.join(", ") | 
					
						
							|  |  |  |             method_string = if target_class | 
					
						
							|  |  |  |               "#{target_class}.#{target_method}" | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               target_method.to_s | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |             add_offense(first_arg, message: format(MSG, method: method_string, good_args:)) do |corrector| | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  |               corrector.replace(first_arg.source_range, good_args) | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2021-05-03 20:25:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       # This cop disallows shell metacharacters in `exec` calls. | 
					
						
							|  |  |  |       # | 
					
						
							|  |  |  |       # @api private | 
					
						
							|  |  |  |       class ExecShellMetacharacters < Base | 
					
						
							|  |  |  |         include HelperFunctions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-28 10:09:59 +01:00
										 |  |  |         MSG = "Don't use shell metacharacters in `exec`. " \ | 
					
						
							| 
									
										
										
										
											2021-05-03 20:25:03 +01:00
										 |  |  |               "Implement the logic in Ruby instead, using methods like `$stdout.reopen`." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         RESTRICT_ON_SEND = [:exec].freeze | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def on_send(node) | 
					
						
							|  |  |  |           return if node.receiver.present? && node.receiver != s(:const, nil, :Kernel) | 
					
						
							|  |  |  |           return if node.arguments.count != 1
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           stripped_arg_str = string_content(node.arguments.first, strip_dynamic: true) | 
					
						
							|  |  |  |           command = string_content(node.arguments.first).shellsplit.first | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return if SHELL_BUILTINS.none?(command) && | 
					
						
							|  |  |  |                     !command&.include?("=") && | 
					
						
							|  |  |  |                     SHELL_METACHARACTERS.none? { |meta| stripped_arg_str.include?(meta) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           add_offense(node.arguments.first, message: MSG) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2021-03-17 15:27:26 +00:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |