#!/usr/bin/env python
# encoding: utf-8
"""
decode_iphone_backup.py

Decodes MobileSync/Backup/*.mdbackup files into a normal filesystem.

*** 1. I strongly recommend running this off a COPY of the backup folder rather than your live one. ***
*** 2. I am not responsible for anything bad that happens as a result of using this code ***
*** 3. If you don't understand the above, don't use the script.

Created by: IVS
Create Date: Monday 9th July 2007
Contact: chat.with.ivs[@t]gmail.com

Thanks to JavaCoderEx, CentroniX, Lixivial for beta testing !
"""

import base64, os, commands, sys, getopt
from sgmllib import SGMLParser

def usage():
	print "MobileSync Backup Folder Decoder\n"
	print "Usage: " + sys.argv[0] + " (List of .mdbackup files to convert.  Wildcards are supported) \n"
	print "Built by ivs"

class bplist_converter:
	def decode_bplist(self, plist_filename):
		decode_command = "plutil -convert xml1 \"%(plist_filename)s\"" % locals()
		status=os.system(decode_command)
		if not status == 0:
			print "Error converting from binary plist using plutil."
			sys.exit(2)

class data_section:
	def __init__(self):
		self.data = []
		self.path = ""
	def decode(self):
		return base64.b64decode("".join(self.data))
	def write(self):
		self.path = os.path.join("MobileSyncExport",self.path)
		thepath, thefile = os.path.split(self.path)       
		
		#if the folders don't exists, make 'em
		if not os.path.exists(thepath):
			os.makedirs(thepath) 
		
		# convert from base64
		if (sys.version[:1] > (2,4)):	
			output_text = base64.b64decode("".join(self.data))
		else: 
			output_text = base64.decodestring("".join(self.data))		

		if output_text == "":
			print "No text to output."
			print "path " + self.path
			return
		
		print "the file:" + thefile	 
				
		output_file = open(self.path, 'wb') 
			
		output_file.write(output_text)
		output_file.close()
		
		# check here if it's a plist and decode it using plutil
		if output_text[0:6] == "bplist":
			c = bplist_converter()
			c.decode_bplist(self.path)
		
		
class plist_processor(SGMLParser):
	def reset(self):
		SGMLParser.reset(self)
		self.inkey = 0
		self.indata = 0
		self.sections = []
		self.currentkey = ""
		self.currentdata=data_section()
		
	def start_key(self, attrs): 
		self.inkey = 1
		
	def end_key(self):        
		self.inkey = 0
		
	def start_string(self, attrs):
		self.start_data(attrs)
	def end_string(self):
		self.end_data()
	
	def start_data(self, attrs):
		self.indata = 1	
	def end_data(self):
		self.indata = 0
		
	def unknown_starttag(self, tag, attrs):
		#strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
		#self.handle_data("<%(tag)s%(strattrs)s>" % locals())
		return None

	def unknown_endtag(self, tag):
		#self.handle_data("</%(tag)s>" % locals())
		return None
    
	def do_false(self, tag): 
		return None          
		
	def process_key_path(self, text):
		self.currentdata.path = text
	
	def process_key_data(self, text):
		self.currentdata.data.append(text)

	def process_key_string(self,text):
		self.process_key_data(text)	   
	
	def process_key_greylist(self,text):       
		#print "Inkey: " + str(self.inkey) + " key: " + str(self.currentkey)
		self.currentdata.path = text
		return None
	                            
	def process_key_version(self, text):
		# We don't need to do anything with the version key.
		return None

	def handle_data(self, text):
		# called for each block of plain text, i.e. outside of any tag and
		# not containing any character or entity references
		# Store the original text verbatim.             
		if self.inkey == 1:
			self.currentkey = text.lower()	
			print "In key: %(text)s" % locals()
		elif self.indata == 1:
			try: 
				key_function = getattr(self,"process_key_%s" % self.currentkey )
				key_function(text)
			except AttributeError:
				print "Warning: No function exists to handle key: %s.  It will be ignored." % self.currentkey

	def write(self):
		self.currentdata.write()
	def output(self):
		print "Path: %s \n" % self.currentdata.path
		print "Data: %s \n" % self.currentdata.decode()

def main(argv):
	
	try:
		opts, args = getopt.getopt(argv[1:], "",[])
	except getopt.GetoptError:
		usage()
		sys.exit(2)
	
	if args.__len__() == 0:
		usage()
		sys.exit(2)
	
	converter = bplist_converter()
	
	for filename in args:
		print "Processing: " + filename
		parser = plist_processor()
		plist_name = filename
		plist_file = open(filename, 'r')
		plist_text = plist_file.read()
		if plist_text[0:6] == "bplist":
			plist_file.close()
			converter.decode_bplist(plist_name)
			plist_file = open(plist_name, 'r')
			plist_text = plist_file.read()
		parser.feed(plist_text)
		parser.write()

if __name__ == "__main__":
	main(sys.argv)
