1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
require 'cfpropertylist' if Puppet.features.cfpropertylist?
require_relative '../../puppet/util/execution'
module Puppet::Util::Plist
class FormatError < RuntimeError; end
# So I don't have to prepend every method name with 'self.' Most of the
# methods are going to be Provider methods (as opposed to methods of the
# INSTANCE of the provider).
class << self
# Defines the magic number for binary plists
#
# @api private
def binary_plist_magic_number
"bplist00"
end
# Defines a default doctype string that should be at the top of most plist
# files. Useful if we need to modify an invalid doctype string in memory.
# In version 10.9 and lower of OS X the plist at
# /System/Library/LaunchDaemons/org.ntp.ntpd.plist had an invalid doctype
# string. This corrects for that.
def plist_xml_doctype
'<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
end
# Read a plist file, whether its format is XML or in Apple's "binary1"
# format, using the CFPropertyList gem.
def read_plist_file(file_path)
# We can't really read the file until we know the source encoding in
# Ruby 1.9.x, so we use the magic number to detect it.
# NOTE: We used IO.read originally to be Ruby 1.8.x compatible.
if read_file_with_offset(file_path, binary_plist_magic_number.length) == binary_plist_magic_number
plist_obj = new_cfpropertylist(:file => file_path)
return convert_cfpropertylist_to_native_types(plist_obj)
else
plist_data = open_file_with_args(file_path, "r:UTF-8")
plist = parse_plist(plist_data, file_path)
return plist if plist
Puppet.debug "Plist #{file_path} ill-formatted, converting with plutil"
begin
plist = Puppet::Util::Execution.execute(['/usr/bin/plutil', '-convert', 'xml1', '-o', '-', file_path],
{:failonfail => true, :combine => true})
return parse_plist(plist)
rescue Puppet::ExecutionFailure => detail
message = _("Cannot read file %{file_path}; Puppet is skipping it.") % { file_path: file_path }
message += '\n' + _("Details: %{detail}") % { detail: detail }
Puppet.warning(message)
end
end
return nil
end
# Read plist text using the CFPropertyList gem.
def parse_plist(plist_data, file_path = '')
bad_xml_doctype = /^.*<!DOCTYPE plist PUBLIC -\/\/Apple Computer.*$/
# Depending on where parse_plist is called from, plist_data can be either XML or binary.
# If we get XML, make sure ruby knows it's UTF-8 so we avoid invalid byte sequence errors.
if plist_data.include?('encoding="UTF-8"') && plist_data.encoding != Encoding::UTF_8
plist_data.force_encoding(Encoding::UTF_8)
end
begin
if plist_data =~ bad_xml_doctype
plist_data.gsub!( bad_xml_doctype, plist_xml_doctype )
Puppet.debug("Had to fix plist with incorrect DOCTYPE declaration: #{file_path}")
end
rescue ArgumentError => e
Puppet.debug "Failed with #{e.class} on #{file_path}: #{e.inspect}"
return nil
end
begin
plist_obj = new_cfpropertylist(:data => plist_data)
# CFPropertyList library will raise NoMethodError for invalid data
rescue CFFormatError, NoMethodError => e
Puppet.debug "Failed with #{e.class} on #{file_path}: #{e.inspect}"
return nil
end
convert_cfpropertylist_to_native_types(plist_obj)
end
# Helper method to assist in reading a file. It's its own method for
# stubbing purposes
#
# @api private
#
# @param args [String] Extra file operation mode information to use
# (defaults to read-only mode 'r')
# This is the standard mechanism Ruby uses in the IO class, and therefore
# encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*"
# for example, a:ASCII or w+:UTF-8
def open_file_with_args(file, args)
File.open(file, args).read
end
# Helper method to assist in generating a new CFPropertyList Plist. It's
# its own method for stubbing purposes
#
# @api private
def new_cfpropertylist(plist_opts)
CFPropertyList::List.new(plist_opts)
end
# Helper method to assist in converting a native CFPropertyList object to a
# native Ruby object (hash). It's its own method for stubbing purposes
#
# @api private
def convert_cfpropertylist_to_native_types(plist_obj)
CFPropertyList.native_types(plist_obj.value)
end
# Helper method to convert a string into a CFProperty::Blob, which is
# needed to properly handle binary strings
#
# @api private
def string_to_blob(str)
CFPropertyList::Blob.new(str)
end
# Helper method to assist in reading a file with an offset value. It's its
# own method for stubbing purposes
#
# @api private
def read_file_with_offset(file_path, offset)
IO.read(file_path, offset)
end
def to_format(format)
if format.to_sym == :xml
CFPropertyList::List::FORMAT_XML
elsif format.to_sym == :binary
CFPropertyList::List::FORMAT_BINARY
elsif format.to_sym == :plain
CFPropertyList::List::FORMAT_PLAIN
else
raise FormatError.new "Unknown plist format #{format}"
end
end
# This method will write a plist file using a specified format (or XML
# by default)
def write_plist_file(plist, file_path, format = :xml)
begin
plist_to_save = CFPropertyList::List.new
plist_to_save.value = CFPropertyList.guess(plist)
plist_to_save.save(file_path, to_format(format), :formatted => true)
rescue IOError => e
Puppet.err(_("Unable to write the file %{file_path}. %{error}") % { file_path: file_path, error: e.inspect })
end
end
def dump_plist(plist_data, format = :xml)
plist_to_save = CFPropertyList::List.new
plist_to_save.value = CFPropertyList.guess(plist_data)
plist_to_save.to_str(to_format(format), :formatted => true)
end
end
end
|