1
+ PuppetLint . new_check ( :check_unsafe_interpolations ) do
2
+ COMMANDS = Array [ 'command' , 'onlyif' , 'unless' ]
3
+ INTERPOLATED_STRINGS = Array [ :DQPRE , :DQMID ]
4
+ USELESS_CHARS = Array [ :WHITESPACE , :COMMA ]
5
+ def check
6
+ # Gather any exec commands' resources into an array
7
+ exec_resources = resource_indexes . map { |resource |
8
+ resource_parameters = resource [ :param_tokens ] . map ( &:value )
9
+ resource if resource [ :type ] . value == 'exec' && !( COMMANDS & resource_parameters ) . empty?
10
+ } . compact
11
+
12
+ # Iterate over title tokens and raise a warning if any are variables
13
+ unless get_exec_titles . empty?
14
+ get_exec_titles . each do |title |
15
+ check_unsafe_title ( title )
16
+ end
17
+ end
18
+
19
+ # Iterate over each command found in any exec
20
+ exec_resources . each do |command_resources |
21
+ check_unsafe_interpolations ( command_resources )
22
+ end
23
+ end
24
+
25
+ # Iterate over the tokens in a title and raise a warning if an interpolated variable is found
26
+ def check_unsafe_title ( title )
27
+ title . each do |token |
28
+ notify_warning ( token . next_code_token ) if interpolated? ( token )
29
+ end
30
+ end
31
+
32
+ # Iterates over an exec resource and if a command, onlyif or unless paramter is found, it is checked for unsafe interpolations
33
+ def check_unsafe_interpolations ( command_resources )
34
+ command_resources [ :tokens ] . each do |token |
35
+ # Skip iteration if token isn't a command of type :NAME
36
+ next unless COMMANDS . include? ( token . value ) && token . type == :NAME
37
+ # Don't check the command if it is parameterised
38
+ next if parameterised? ( token )
39
+
40
+ check_command ( token ) . each do |t |
41
+ notify_warning ( t )
42
+ end
43
+ end
44
+ end
45
+
46
+ # Raises a warning given a token and message
47
+ def notify_warning ( token )
48
+ notify :warning ,
49
+ message : "unsafe interpolation of variable '#{ token . value } ' in exec command" ,
50
+ line : token . line ,
51
+ column : token . column
52
+ end
53
+
54
+ # Iterates over the tokens in a command and adds it to an array of violations if it is an input variable
55
+ def check_command ( token )
56
+ # Initialise variables needed in while loop
57
+ rule_violations = [ ]
58
+ current_token = token
59
+
60
+ # Iterate through tokens in command
61
+ while current_token . type != :NEWLINE
62
+ # Check if token is a varibale and if it is parameterised
63
+ rule_violations . append ( current_token . next_code_token ) if interpolated? ( current_token )
64
+ current_token = current_token . next_token
65
+ end
66
+
67
+ rule_violations
68
+ end
69
+
70
+ # A command is parameterised if its args are placed in an array
71
+ # This function checks if the current token is a :FARROW and if so, if it is followed by an LBRACK
72
+ def parameterised? ( token )
73
+ current_token = token
74
+ while current_token . type != :NEWLINE
75
+ return true if current_token . type == :FARROW && current_token . next_token . next_token . type == :LBRACK
76
+ current_token = current_token . next_token
77
+ end
78
+ end
79
+
80
+ # This function is a replacement for puppet_lint's title_tokens function which assumes titles have single quotes
81
+ # This function adds a check for titles in double quotes where there could be interpolated variables
82
+ def get_exec_titles
83
+ result = [ ]
84
+ tokens . each_index do |token_idx |
85
+ next unless [ 'exec' ] . include? ( tokens [ token_idx ] . value )
86
+ # We have a resource declaration. Now find the title
87
+ tokens_array = [ ]
88
+ # Check if title is an array
89
+ if tokens [ token_idx ] . next_code_token . next_code_token . type == :LBRACK
90
+ # Get the start and end indices of the array of titles
91
+ array_start_idx = tokens . rindex { |r | r . type == :LBRACK }
92
+ array_end_idx = tokens . rindex { |r | r . type == :RBRACK }
93
+
94
+ # Grab everything within the array
95
+ title_array_tokens = tokens [ ( array_start_idx + 1 ) ..( array_end_idx - 1 ) ]
96
+ tokens_array . concat ( title_array_tokens . reject do |token |
97
+ USELESS_CHARS . include? ( token . type )
98
+ end )
99
+ result << tokens_array
100
+ # Check if title is double quotes string
101
+ elsif tokens [ token_idx ] . next_code_token . next_code_token . type == :DQPRE
102
+ # Find the start and end of the title
103
+ title_start_idx = tokens . find_index ( tokens [ token_idx ] . next_code_token . next_code_token )
104
+ title_end_idx = title_start_idx + index_offset_for ( ':' , tokens [ title_start_idx ..tokens . length ] )
105
+
106
+ result << tokens [ title_start_idx ..title_end_idx ]
107
+ # Title is in single quotes
108
+ else
109
+ tokens_array . concat ( [ tokens [ token_idx ] . next_code_token . next_code_token ] )
110
+
111
+ result << tokens_array
112
+ end
113
+ end
114
+ result
115
+ end
116
+
117
+ def interpolated? ( token )
118
+ INTERPOLATED_STRINGS . include? ( token . type )
119
+ end
120
+
121
+ # Finds the index offset of the next instance of `value` in `tokens_slice` from the original index
122
+ def index_offset_for ( value , tokens_slice )
123
+ i = 0
124
+ while i < tokens_slice . length
125
+ return i if value . include? ( tokens_slice [ i ] . value )
126
+ i += 1
127
+ end
128
+ end
129
+ end
130
+
0 commit comments