File: plist.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (161 lines) | stat: -rw-r--r-- 6,242 bytes parent folder | download
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