diff --git a/lib/puppet/parser/functions/generate.rb b/lib/puppet/parser/functions/generate.rb index 91f7b2240..226a5b7e1 100644 --- a/lib/puppet/parser/functions/generate.rb +++ b/lib/puppet/parser/functions/generate.rb @@ -1,31 +1,37 @@ # Runs an external command and returns the results Puppet::Parser::Functions::newfunction(:generate, :type => :rvalue, :doc => "Calls an external command on the Puppet master and returns the results of the command. Any arguments are passed to the external command as arguments. If the generator does not exit with return code of 0, the generator is considered to have failed and a parse error is thrown. Generators can only have file separators, alphanumerics, dashes, and periods in them. This function will attempt to protect you from malicious generator calls (e.g., those with '..' in them), but it can never be entirely safe. No subshell is used to execute generators, so all shell metacharacters are passed directly to the generator.") do |args| - raise Puppet::ParseError, "Generators must be fully qualified" unless args[0] =~ /^#{File::SEPARATOR}/ + raise Puppet::ParseError, "Generators must be fully qualified" unless Puppet::Util.absolute_path?(args[0]) - unless args[0] =~ /^[-#{File::SEPARATOR}\w.]+$/ + if Puppet.features.microsoft_windows? + valid = args[0] =~ /^[a-z]:(?:[\/\\][\w.-]+)+$/i + else + valid = args[0] =~ /^[-\/\w.]+$/ + end + + unless valid raise Puppet::ParseError, "Generators can only contain alphanumerics, file separators, and dashes" end if args[0] =~ /\.\./ raise Puppet::ParseError, "Can not use generators with '..' in them." end begin Dir.chdir(File.dirname(args[0])) { Puppet::Util.execute(args) } rescue Puppet::ExecutionFailure => detail raise Puppet::ParseError, "Failed to execute generator #{args[0]}: #{detail}" end end diff --git a/spec/unit/parser/functions/generate_spec.rb b/spec/unit/parser/functions/generate_spec.rb index 6c90ae531..e50805393 100755 --- a/spec/unit/parser/functions/generate_spec.rb +++ b/spec/unit/parser/functions/generate_spec.rb @@ -1,43 +1,85 @@ #!/usr/bin/env rspec require 'spec_helper' describe "the generate function" do before :all do Puppet::Parser::Functions.autoloader.loadall end - before :each do - @scope = Puppet::Parser::Scope.new - end + let(:scope) { Puppet::Parser::Scope.new } it "should exist" do Puppet::Parser::Functions.function("generate").should == "function_generate" end - it "should accept a fully-qualified path as a command" do - command = File::SEPARATOR + "command" - Puppet::Util.expects(:execute).with([command]).returns("yay") - lambda { @scope.function_generate([command]) }.should_not raise_error(Puppet::ParseError) + it " accept a fully-qualified path as a command" do + command = File.expand_path('/command/foo') + Dir.expects(:chdir).with(File.dirname(command)).returns("yay") + scope.function_generate([command]).should == "yay" end it "should not accept a relative path as a command" do - command = "command" - lambda { @scope.function_generate([command]) }.should raise_error(Puppet::ParseError) + lambda { scope.function_generate(["command"]) }.should raise_error(Puppet::ParseError) + end + + it "should not accept a command containing illegal characters" do + lambda { scope.function_generate([File.expand_path('/##/command')]) }.should raise_error(Puppet::ParseError) end - # Really not sure how to implement this test, just sure it needs - # to be implemented. - it "should not accept a command containing illegal characters" + it "should not accept a command containing spaces" do + lambda { scope.function_generate([File.expand_path('/com mand')]) }.should raise_error(Puppet::ParseError) + end it "should not accept a command containing '..'" do - command = File::SEPARATOR + "command#{File::SEPARATOR}..#{File::SEPARATOR}" - lambda { @scope.function_generate([command]) }.should raise_error(Puppet::ParseError) + command = File.expand_path("/command/../") + lambda { scope.function_generate([command]) }.should raise_error(Puppet::ParseError) end it "should execute the generate script with the correct working directory" do - command = File::SEPARATOR + "command" - Dir.expects(:chdir).with(File.dirname(command)).yields - Puppet::Util.expects(:execute).with([command]).returns("yay") - lambda { @scope.function_generate([command]) }.should_not raise_error(Puppet::ParseError) + command = File.expand_path("/command") + Dir.expects(:chdir).with(File.dirname(command)).returns("yay") + scope.function_generate([command]).should == 'yay' + end + + describe "on Windows" do + before :each do + Puppet.features.stubs(:microsoft_windows?).returns(true) + end + + it "should accept lower-case drive letters" do + command = 'd:/command/foo' + Dir.expects(:chdir).with(File.dirname(command)).returns("yay") + scope.function_generate([command]).should == 'yay' + end + + it "should accept upper-case drive letters" do + command = 'D:/command/foo' + Dir.expects(:chdir).with(File.dirname(command)).returns("yay") + scope.function_generate([command]).should == 'yay' + end + + it "should accept forward and backslashes in the path" do + command = 'D:\command/foo\bar' + Dir.expects(:chdir).with(File.dirname(command)).returns("yay") + scope.function_generate([command]).should == 'yay' + end + + it "should reject colons when not part of the drive letter" do + lambda { scope.function_generate(['C:/com:mand']) }.should raise_error(Puppet::ParseError) + end + + it "should reject root drives" do + lambda { scope.function_generate(['C:/']) }.should raise_error(Puppet::ParseError) + end + end + + describe "on non-Windows" do + before :each do + Puppet.features.stubs(:microsoft_windows?).returns(false) + end + + it "should reject backslashes" do + lambda { scope.function_generate(['/com\\mand']) }.should raise_error(Puppet::ParseError) + end end end