126 lines
3.7 KiB
Ruby
126 lines
3.7 KiB
Ruby
#
|
|
# This is a Ruby port of the Python manifest parsing code posted to:
|
|
# http://stackoverflow.com/questions/3085153/how-to-parse-the-manifest-mbdb-file-in-an-ios-4-0-itunes-backup/3130860#3130860
|
|
#
|
|
|
|
class AppleBackupManifestDB
|
|
|
|
attr_accessor :entry_offsets
|
|
attr_accessor :entries
|
|
attr_accessor :mbdb, :mbdx
|
|
attr_accessor :mbdb_data, :mbdx_data
|
|
attr_accessor :mbdb_offset, :mbdx_offset
|
|
|
|
def initialize(mbdb_data, mbdx_data)
|
|
self.entries = {}
|
|
self.entry_offsets = {}
|
|
self.mbdb_data = mbdb_data
|
|
self.mbdx_data = mbdx_data
|
|
parse_mbdb
|
|
parse_mbdx
|
|
end
|
|
|
|
def self.from_files(mbdb_file, mbdx_file)
|
|
mbdb_data = ""
|
|
::File.open(mbdb_file, "rb") {|fd| mbdb_data = fd.read(fd.stat.size) }
|
|
mbdx_data = ""
|
|
::File.open(mbdx_file, "rb") {|fd| mbdx_data = fd.read(fd.stat.size) }
|
|
|
|
self.new(mbdb_data, mbdx_data)
|
|
end
|
|
|
|
def parse_mbdb
|
|
raise ArgumentError, "Not valid MBDB data" if self.mbdb_data[0,4] != "mbdb"
|
|
self.mbdb_offset = 4
|
|
self.mbdb_offset = self.mbdb_offset + 2 # Maps to \x05 \x00 (unknown)
|
|
|
|
while self.mbdb_offset < self.mbdb_data.length
|
|
info = {}
|
|
info[:start_offset] = self.mbdb_offset
|
|
info[:domain] = mbdb_read_string
|
|
info[:filename] = mbdb_read_string
|
|
info[:linktarget] = mbdb_read_string
|
|
info[:datahash] = mbdb_read_string
|
|
info[:unknown1] = mbdb_read_string
|
|
info[:mode] = mbdb_read_int(2)
|
|
info[:unknown2] = mbdb_read_int(4)
|
|
info[:unknown3] = mbdb_read_int(4)
|
|
info[:uid] = mbdb_read_int(4)
|
|
info[:gid] = mbdb_read_int(4)
|
|
info[:mtime] = Time.at(mbdb_read_int(4))
|
|
info[:atime] = Time.at(mbdb_read_int(4))
|
|
info[:ctime] = Time.at(mbdb_read_int(4))
|
|
info[:length] = mbdb_read_int(8)
|
|
info[:flag] = mbdb_read_int(1)
|
|
property_count = mbdb_read_int(1)
|
|
info[:properties] = {}
|
|
1.upto(property_count) do |i|
|
|
k = mbdb_read_string
|
|
v = mbdb_read_string
|
|
info[:properties][k] = v
|
|
end
|
|
self.entry_offsets[ info[:start_offset] ] = info
|
|
end
|
|
self.mbdb_data = ""
|
|
end
|
|
|
|
def parse_mbdx
|
|
raise ArgumentError, "Not a valid MBDX file" if self.mbdx_data[0,4] != "mbdx"
|
|
|
|
self.mbdx_offset = 4
|
|
self.mbdx_offset = self.mbdx_offset + 2 # Maps to \x02 \x00 (unknown)
|
|
|
|
file_count = mbdx_read_int(4)
|
|
|
|
while self.mbdx_offset < self.mbdx_data.length
|
|
file_id = self.mbdx_data[self.mbdx_offset, 20].unpack("C*").map{|c| "%02x" % c}.join
|
|
self.mbdx_offset += 20
|
|
entry_offset = mbdx_read_int(4) + 6
|
|
mode = mbdx_read_int(2)
|
|
entry = entry_offsets[ entry_offset ]
|
|
# May be corrupted if there is no matching entry, but what to do about it?
|
|
next if not entry
|
|
self.entries[file_id] = entry.merge({:mbdx_mode => mode, :file_id => file_id})
|
|
end
|
|
self.mbdx_data = ""
|
|
end
|
|
|
|
def mbdb_read_string
|
|
raise RuntimeError, "Corrupted MBDB file" if self.mbdb_offset > self.mbdb_data.length
|
|
len = self.mbdb_data[self.mbdb_offset, 2].unpack("n")[0]
|
|
self.mbdb_offset += 2
|
|
return '' if len == 65535
|
|
val = self.mbdb_data[self.mbdb_offset, len]
|
|
self.mbdb_offset += len
|
|
return val
|
|
end
|
|
|
|
def mbdb_read_int(size)
|
|
val = 0
|
|
size.downto(1) do |i|
|
|
val = (val << 8) + self.mbdb_data[self.mbdb_offset, 1].unpack("C")[0]
|
|
self.mbdb_offset += 1
|
|
end
|
|
val
|
|
end
|
|
|
|
def mbdx_read_string
|
|
raise RuntimeError, "Corrupted MBDX file" if self.mbdx_offset > self.mbdx_data.length
|
|
len = self.mbdx_data[self.mbdx_offset, 2].unpack("n")[0]
|
|
self.mbdx_offset += 2
|
|
return '' if len == 65535
|
|
val = self.mbdx_data[self.mbdx_offset, len]
|
|
self.mbdx_offset += len
|
|
return val
|
|
end
|
|
|
|
def mbdx_read_int(size)
|
|
val = 0
|
|
size.downto(1) do |i|
|
|
val = (val << 8) + self.mbdx_data[self.mbdx_offset, 1].unpack("C")[0]
|
|
self.mbdx_offset += 1
|
|
end
|
|
val
|
|
end
|
|
end
|