require 'spec_helper' require 'puppet/util/windows' if Puppet::Util::Platform.windows? describe Puppet::Util::Windows::Registry do subject do class TestRegistry include Puppet::Util::Windows::Registry extend FFI::Library ffi_lib :advapi32 attach_function :RegSetValueExW, [:handle, :pointer, :dword, :dword, :pointer, :dword], :win32_long def write_corrupt_dword(reg, valuename) # Normally DWORDs contain 4 bytes. This bad data only has 2 bad_data = [0, 0] FFI::Pointer.from_string_to_wide_string(valuename) do |name_ptr| FFI::MemoryPointer.new(:uchar, bad_data.length) do |data_ptr| data_ptr.write_array_of_uchar(bad_data) if RegSetValueExW(reg.hkey, name_ptr, 0, Win32::Registry::REG_DWORD, data_ptr, data_ptr.size) != 0 raise Puppet::Util::Windows::Error.new("Failed to write registry value") end end end end end TestRegistry.new end let(:name) { 'HKEY_LOCAL_MACHINE' } let(:path) { 'Software\Microsoft' } context "#root" do it "should lookup the root hkey" do expect(subject.root(name)).to be_instance_of(Win32::Registry::PredefinedKey) end it "should raise for unknown root keys" do expect { subject.root('HKEY_BOGUS') }.to raise_error(Puppet::Error, /Invalid registry key/) end end context "#open" do let(:hkey) { double('hklm') } let(:subkey) { double('subkey') } before :each do allow(subject).to receive(:root).and_return(hkey) end it "should yield the opened the subkey" do expect(hkey).to receive(:open).with(path, anything).and_yield(subkey) yielded = nil subject.open(name, path) {|reg| yielded = reg} expect(yielded).to eq(subkey) end if Puppet::Util::Platform.windows? [described_class::KEY64, described_class::KEY32].each do |access| it "should open the key for read access 0x#{access.to_s(16)}" do mode = described_class::KEY_READ | access expect(hkey).to receive(:open).with(path, mode) subject.open(name, path, mode) {|reg| } end end end it "should default to KEY64" do expect(hkey).to receive(:open).with(path, described_class::KEY_READ | described_class::KEY64) subject.open(hkey, path) {|hkey| } end it "should raise for a path that doesn't exist" do expect(hkey).to receive(:keyname).and_return('HKEY_LOCAL_MACHINE') expect(hkey).to receive(:open).and_raise(Win32::Registry::Error.new(2)) # file not found expect do subject.open(hkey, 'doesnotexist') {|hkey| } end.to raise_error(Puppet::Error, /Failed to open registry key 'HKEY_LOCAL_MACHINE\\doesnotexist'/) end end context "#values" do let(:key) { double('uninstall') } it "should return each value's name and data" do expect(key).not_to receive(:each_value) expect(subject).to receive(:each_value).with(key).and_yield('string', 1, 'foo').and_yield('dword', 4, 0) expect(subject.values(key)).to eq({ 'string' => 'foo', 'dword' => 0 }) end it "should return an empty hash if there are no values" do expect(key).not_to receive(:each_value) expect(subject).to receive(:each_value).with(key) expect(subject.values(key)).to eq({}) end it "passes REG_DWORD through" do expect(key).not_to receive(:each_value) expect(subject).to receive(:each_value).with(key).and_yield('dword', Win32::Registry::REG_DWORD, '1') value = subject.values(key).first[1] expect(Integer(value)).to eq(1) end context "when reading non-ASCII values" do ENDASH_UTF_8 = [0xE2, 0x80, 0x93] ENDASH_UTF_16 = [0x2013] TM_UTF_8 = [0xE2, 0x84, 0xA2] TM_UTF_16 = [0x2122] let(:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let(:puppet_key) { "SOFTWARE\\Puppet Labs"} let(:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } let(:guid) { SecureRandom.uuid } let(:regsam) { Puppet::Util::Windows::Registry::KEY32 } after(:each) do # Ruby 2.1.5 has bugs with deleting registry keys due to using ANSI # character APIs, but passing wide strings to them (facepalm) # https://github.com/ruby/ruby/blob/v2_1_5/ext/win32/lib/win32/registry.rb#L323-L329 # therefore, use our own built-in registry helper code hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS | regsam) do |reg| subject.delete_key(reg, subkey_name, regsam) end end # proof that local encodings (such as IBM437 are no longer relevant) it "will return a UTF-8 string from a REG_SZ registry value (written as UTF-16LE)", :if => Puppet::Util::Platform.windows? do # create a UTF-16LE byte array representing "–™" utf_16_bytes = ENDASH_UTF_16 + TM_UTF_16 utf_16_str = utf_16_bytes.pack('s*').force_encoding(Encoding::UTF_16LE) # and it's UTF-8 equivalent bytes utf_8_bytes = ENDASH_UTF_8 + TM_UTF_8 utf_8_str = utf_8_bytes.pack('c*').force_encoding(Encoding::UTF_8) hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS | regsam) do |reg| reg.write("#{guid}", Win32::Registry::REG_SZ, utf_16_str) # trigger Puppet::Util::Windows::Registry FFI calls keys = subject.keys(reg) vals = subject.values(reg) expect(keys).to be_empty expect(vals).to have_key(guid) # The UTF-16LE string written should come back as the equivalent UTF-8 written = vals[guid] expect(written).to eq(utf_8_str) expect(written.encoding).to eq(Encoding::UTF_8) end end end context "when reading values" do let(:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let(:puppet_key) { "SOFTWARE\\Puppet Labs"} let(:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } let(:value_name) { SecureRandom.uuid } after(:each) do hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS) do |reg| subject.delete_key(reg, subkey_name) end end [ {:name => 'REG_SZ', :type => Win32::Registry::REG_SZ, :value => 'reg sz string'}, {:name => 'REG_EXPAND_SZ', :type => Win32::Registry::REG_EXPAND_SZ, :value => 'reg expand string'}, {:name => 'REG_MULTI_SZ', :type => Win32::Registry::REG_MULTI_SZ, :value => ['string1', 'string2']}, {:name => 'REG_BINARY', :type => Win32::Registry::REG_BINARY, :value => 'abinarystring'}, {:name => 'REG_DWORD', :type => Win32::Registry::REG_DWORD, :value => 0xFFFFFFFF}, {:name => 'REG_DWORD_BIG_ENDIAN', :type => Win32::Registry::REG_DWORD_BIG_ENDIAN, :value => 0xFFFF}, {:name => 'REG_QWORD', :type => Win32::Registry::REG_QWORD, :value => 0xFFFFFFFFFFFFFFFF}, ].each do |pair| it "should return #{pair[:name]} values" do hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg| reg.write(value_name, pair[:type], pair[:value]) end hklm.open("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_READ) do |reg| vals = subject.values(reg) expect(vals).to have_key(value_name) subject.each_value(reg) do |subkey, type, data| expect(type).to eq(pair[:type]) end written = vals[value_name] expect(written).to eq(pair[:value]) end end end end context "when reading corrupt values" do let(:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let(:puppet_key) { "SOFTWARE\\Puppet Labs"} let(:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } let(:value_name) { SecureRandom.uuid } before(:each) do hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg_key| subject.write_corrupt_dword(reg_key, value_name) end end after(:each) do hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS) do |reg_key| subject.delete_key(reg_key, subkey_name) end end it "should return nil for a corrupt DWORD" do hklm.open("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg_key| vals = subject.values(reg_key) expect(vals).to have_key(value_name) expect(vals[value_name]).to be_nil end end end context 'whean reading null byte' do let(:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let(:puppet_key) { 'SOFTWARE\\Puppet Labs' } let(:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } let(:value_name) { SecureRandom.uuid } after(:each) do hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS) do |reg| subject.delete_key(reg, subkey_name) end end [ { name: 'REG_SZ', type: Win32::Registry::REG_SZ, value: "reg sz\u0000\u0000 string", expected_value: "reg sz" }, { name: 'REG_SZ_2', type: Win32::Registry::REG_SZ, value: "reg sz 2\x00\x00 string", expected_value: "reg sz 2" }, { name: 'REG_EXPAND_SZ', type: Win32::Registry::REG_EXPAND_SZ, value: "\0\0\0reg expand string", expected_value: "" } ].each do |pair| it 'reads up to the first wide null' do hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg| reg.write(value_name, pair[:type], pair[:value]) end hklm.open("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_READ) do |reg| vals = subject.values(reg) expect(vals).to have_key(value_name) subject.each_value(reg) do |_subkey, type, _data| expect(type).to eq(pair[:type]) end written = vals[value_name] expect(written).to eq(pair[:expected_value]) end end end end end context "#values_by_name" do let(:hkey) { double('hklm') } let(:subkey) { double('subkey') } before :each do allow(subject).to receive(:root).and_return(hkey) end context "when reading values" do let(:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let(:puppet_key) { "SOFTWARE\\Puppet Labs"} let(:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } before(:each) do hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg| reg.write('valuename1', Win32::Registry::REG_SZ, 'value1') reg.write('valuename2', Win32::Registry::REG_SZ, 'value2') end end after(:each) do hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS) do |reg| subject.delete_key(reg, subkey_name) end end it "should return only the values for the names specified" do hklm.open("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg_key| vals = subject.values_by_name(reg_key, ['valuename1', 'missingname']) expect(vals).to have_key('valuename1') expect(vals).to_not have_key('valuename2') expect(vals['valuename1']).to eq('value1') expect(vals['missingname']).to be_nil end end end end end end