Skip to content

Commit 7352928

Browse files
committed
(WIP) (CONT-234) Fix bug in get-title-tokens function
This is a work in progress. Prior to this commit, the get-title-tokens function was not operating as expected. It contained a bug where if the title of a resource were in single quotes, it would return all tokens prior to the COLON at the end of the title, rather than just the tokens from the beginning of the title. As well as this, the resource type the title belonged to wasn't being checked, resulting in resources other than execs having their titles checked for unsafe interpolations. get_title_tokens now returns an array of hashes where each hash contains a title's tokens as an array and its resource_type. The resulting hash containing all of the titles is mapped to a new variable exec_title_tokens if its resource_type is an exec. Before titles are checked for unsafe interpolations, exec_title_tokens is checked incase it is empty. Introduced the check_unsafe_title method which removes the need for nested loops (since exec_title_tokens will be an array of arrays of title tokens). This is similar to what is done to check commands for unsafe interpolations but with slight differences. There may be scope to combine check_unsafe_title and check_unsafe_interpolations. The function interpolated? has been added to improve readability. Furthermore, this function checks if an interpolated string is present by checking if DQPRE or DQMID are present in the string, rather than checking if a :VARIABLE is present as this would flag non-interpolated variables. In check_unsafe_interpolations(), when looking for command parameters, the iteration is skipped unless command, onlyif or unless are found in the exec but a check has been added to ensure these are of type :NAME before they are checked to prevent undesirable behaviour. E.g. if a token with value 'command' 'onlyif' or 'unless' is found, it was checked for unsafe interpolations as if a command parameter was found.
1 parent 3e263e0 commit 7352928

File tree

2 files changed

+97
-46
lines changed

2 files changed

+97
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
PuppetLint.new_check(:check_unsafe_interpolations) do
22
COMMANDS = Array['command', 'onlyif', 'unless']
3+
INTERPOLATED_STRINGS = Array[:DQPRE, :DQMID]
34
def check
45
# Gather any exec commands' resources into an array
56
exec_resources = resource_indexes.map { |resource|
67
resource_parameters = resource[:param_tokens].map(&:value)
78
resource if resource[:type].value == 'exec' && !(COMMANDS & resource_parameters).empty?
89
}.compact
910

10-
# Get title tokens with modified puppet lint function
11-
title_tokens_list = get_title_tokens
11+
# Filter the list of titles returned by get_title_tokens for those contained in an exec
12+
exec_title_tokens = get_title_tokens.select { |title|
13+
title if title[:resource_type].value == 'exec'
14+
}.compact
1215

1316
# Iterate over title tokens and raise a warning if any are variables
14-
title_tokens_list[:tokens].each do |token|
15-
if token.type == :VARIABLE
16-
notify_warning(token)
17+
unless exec_title_tokens.empty?
18+
exec_title_tokens.each do |title|
19+
check_unsafe_title(title)
1720
end
1821
end
1922

@@ -23,11 +26,18 @@ def check
2326
end
2427
end
2528

26-
# Iterates over tokens in a command and raises a warning if an interpolated variable is found
29+
# Iterate over the tokens in a title and raise a warning if an interpolated variable is found
30+
def check_unsafe_title(title)
31+
title[:tokens].each do |token|
32+
notify_warning(token.next_code_token) if interpolated?(token)
33+
end
34+
end
35+
36+
# Iterates over an exec resource and if a command, onlyif or unless paramter is found, it is checked for unsafe interpolations
2737
def check_unsafe_interpolations(command_resources)
2838
command_resources[:tokens].each do |token|
29-
# We are only interested in tokens from command onwards
30-
next unless token.type == :NAME
39+
# Skip iteration if token isn't a command of type :NAME
40+
next unless COMMANDS.include?(token.value) && token.type == :NAME
3141
# Don't check the command if it is parameterised
3242
next if parameterised?(token)
3343

@@ -54,9 +64,7 @@ def check_command(token)
5464
# Iterate through tokens in command
5565
while current_token.type != :NEWLINE
5666
# Check if token is a varibale and if it is parameterised
57-
if current_token.type == :VARIABLE
58-
rule_violations.append(current_token)
59-
end
67+
rule_violations.append(current_token.next_code_token) if interpolated?(current_token)
6068
current_token = current_token.next_token
6169
end
6270

@@ -76,45 +84,48 @@ def parameterised?(token)
7684
# This function is a replacement for puppet_lint's title_tokens function which assumes titles have single quotes
7785
# This function adds a check for titles in double quotes where there could be interpolated variables
7886
def get_title_tokens
79-
tokens_array = []
80-
result = {}
87+
result = []
8188
tokens.each_index do |token_idx|
82-
if tokens[token_idx].type == :COLON
83-
# Check if title is an array
84-
if tokens[token_idx - 1].type == :RBRACK
85-
array_start_idx = tokens.rindex do |r|
86-
r.type == :LBRACK
87-
end
88-
title_array_tokens = tokens[(array_start_idx + 1)..(token_idx - 2)]
89-
tokens_array.concat(title_array_tokens.select do |token|
90-
{ STRING: true, NAME: true }.include?(token.type)
91-
end)
92-
result = {
93-
tokens: tokens_array,
94-
resource_type: tokens[array_start_idx].prev_code_token.prev_code_token
95-
}
96-
# Check if title is double quotes string
97-
elsif tokens[token_idx - 1].type == :DQPOST
98-
# Find index of the start of the title
99-
title_start_idx = tokens.rindex do |r|
100-
r.type == :DQPRE
101-
end
102-
result = {
103-
# Title is tokens from :DQPRE to the index before :COLON
104-
tokens: tokens[title_start_idx..(token_idx - 1)],
105-
resource_type: tokens[title_start_idx].prev_code_token.prev_code_token
106-
}
107-
# Title is in single quotes
108-
else
109-
next_token = tokens[token_idx].next_code_token
110-
tokens_array.concat(tokens[..token_idx - 1]) unless next_token.type == :LBRACE
111-
result = {
112-
tokens: tokens_array,
113-
resource_type: tokens[token_idx - 1].prev_code_token.prev_code_token
114-
}
89+
next unless tokens[token_idx].type == :COLON
90+
tokens_array = []
91+
# Check if title is an array
92+
if tokens[token_idx - 1].type == :RBRACK
93+
array_start_idx = tokens.rindex do |r|
94+
r.type == :LBRACK
95+
end
96+
title_array_tokens = tokens[(array_start_idx + 1)..(token_idx - 2)]
97+
tokens_array.concat(title_array_tokens.select do |token|
98+
{ STRING: true, NAME: true }.include?(token.type)
99+
end)
100+
result << {
101+
tokens: tokens_array,
102+
resource_type: tokens[array_start_idx].prev_code_token.prev_code_token
103+
}
104+
# Check if title is double quotes string
105+
elsif tokens[token_idx - 1].type == :DQPOST
106+
# Find index of the start of the title
107+
title_start_idx = tokens.rindex do |r|
108+
r.type == :DQPRE
115109
end
110+
result << {
111+
# Title is tokens from :DQPRE to the index before :COLON
112+
tokens: tokens[title_start_idx..(token_idx - 1)],
113+
resource_type: tokens[title_start_idx].prev_code_token.prev_code_token
114+
}
115+
# Title is in single quotes
116+
else
117+
next_token = tokens[token_idx].next_code_token
118+
tokens_array.concat([tokens[token_idx - 1]]) unless next_token.type == :LBRACE
119+
result << {
120+
tokens: tokens_array,
121+
resource_type: tokens[token_idx - 1].prev_code_token.prev_code_token
122+
}
116123
end
117124
end
118125
result
119126
end
127+
128+
def interpolated?(token)
129+
INTERPOLATED_STRINGS.include?(token.type)
130+
end
120131
end

spec/puppet-lint/plugins/check_unsafe_interpolations_spec.rb

+40
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,45 @@ class foo {
120120
expect(problems).to have(0).problems
121121
end
122122
end
123+
124+
context 'file resource' do
125+
let(:code) do
126+
<<-PUPPET
127+
class foo {
128+
file { '/etc/bar':
129+
ensure => file,
130+
backup => false,
131+
content => $baz,
132+
}
133+
}
134+
PUPPET
135+
end
136+
137+
it 'detects zero problems' do
138+
expect(problems).to have(0).problems
139+
end
140+
end
141+
142+
context 'file resource and an exec with unsafe interpolation in command' do
143+
let(:code) do
144+
<<-PUPPET
145+
class foo {
146+
file { '/etc/bar':
147+
ensure => file,
148+
backup => false,
149+
content => $baz,
150+
}
151+
152+
exec { 'qux':
153+
command => "echo ${foo}",
154+
}
155+
}
156+
PUPPET
157+
end
158+
159+
it 'detects one problem' do
160+
expect(problems).to have(1).problems
161+
end
162+
end
123163
end
124164
end

0 commit comments

Comments
 (0)