diff --git a/pymobiledevice3/cli.py b/pymobiledevice3/cli.py index 2fcee7bbd..bb171fe5c 100644 --- a/pymobiledevice3/cli.py +++ b/pymobiledevice3/cli.py @@ -12,7 +12,7 @@ from pygments import highlight, lexers, formatters from termcolor import colored -from pymobiledevice3.afc import AFCShell, AFCClient +from pymobiledevice3.services.afc import AfcShell, AfcService from pymobiledevice3.lockdown import LockdownClient from pymobiledevice3.services.diagnostics import DiagnosticsService from pymobiledevice3.services.dvt_secure_socket_proxy import DvtSecureSocketProxyService @@ -371,7 +371,7 @@ def crash(lockdown, action): ack = b'ping\x00' assert ack == lockdown.start_service('com.apple.crashreportmover').recv_exact(len(ack)) elif action == 'shell': - AFCShell(lockdown=lockdown, afcname='com.apple.crashreportcopymobile').cmdloop() + AfcShell(lockdown=lockdown, afcname='com.apple.crashreportcopymobile').cmdloop() @cli.group() @@ -383,7 +383,7 @@ def afc(): @afc.command('shell', cls=Command) def afc_shell(lockdown): """ open an AFC shell rooted at /var/mobile/Media """ - AFCShell(lockdown=lockdown, afcname='com.apple.afc').cmdloop() + AfcShell(lockdown=lockdown, afcname='com.apple.afc').cmdloop() @afc.command('pull', cls=Command) @@ -391,7 +391,7 @@ def afc_shell(lockdown): @click.argument('local_file', type=click.File('wb')) def afc_pull(lockdown, remote_file, local_file): """ open an AFC shell rooted at /var/mobile/Media """ - local_file.write(AFCClient(lockdown=lockdown).get_file_contents(remote_file)) + local_file.write(AfcService(lockdown=lockdown).get_file_contents(remote_file)) @afc.command('push', cls=Command) @@ -399,21 +399,21 @@ def afc_pull(lockdown, remote_file, local_file): @click.argument('remote_file', type=click.Path(exists=False)) def afc_push(lockdown, local_file, remote_file): """ open an AFC shell rooted at /var/mobile/Media """ - AFCClient(lockdown=lockdown).set_file_contents(remote_file, local_file.read()) + AfcService(lockdown=lockdown).set_file_contents(remote_file, local_file.read()) @afc.command('ls', cls=Command) @click.argument('remote_file', type=click.Path(exists=False)) def afc_ls(lockdown, remote_file): """ open an AFC shell rooted at /var/mobile/Media """ - pprint(AFCClient(lockdown=lockdown).read_directory(remote_file)) + pprint(AfcService(lockdown=lockdown).listdir(remote_file)) @afc.command('rm', cls=Command) @click.argument('remote_file', type=click.Path(exists=False)) def afc_rm(lockdown, remote_file): """ open an AFC shell rooted at /var/mobile/Media """ - AFCClient(lockdown=lockdown).file_remove(remote_file) + AfcService(lockdown=lockdown).rm(remote_file) @cli.command(cls=Command) diff --git a/pymobiledevice3/afc.py b/pymobiledevice3/services/afc.py similarity index 80% rename from pymobiledevice3/afc.py rename to pymobiledevice3/services/afc.py index bc10c3000..1bfae8969 100755 --- a/pymobiledevice3/afc.py +++ b/pymobiledevice3/services/afc.py @@ -105,7 +105,6 @@ 'packet_num' / Int64ul, 'operation' / afc_opcode_t, '_data_offset' / Tell, - # 'data' / Bytes(this.entire_length - this._data_offset), ) @@ -121,112 +120,75 @@ def list_to_dict(d): return res -class AFCClient(object): - def __init__(self, lockdown=None, service_name="com.apple.afc", service=None, udid=None, logger=None): - self.logger = logger or logging.getLogger(__name__) - self.serviceName = service_name - self.lockdown = lockdown if lockdown else LockdownClient(udid=udid) - self.service = service if service else self.lockdown.start_service(self.serviceName) +class AfcService(object): + def __init__(self, lockdown: LockdownClient, service_name="com.apple.afc"): + self.logger = logging.getLogger(__name__) + self.service_name = service_name + self.lockdown = lockdown + self.service = self.lockdown.start_service(self.service_name) self.packet_num = 0 - def _dispatch_packet(self, operation, data, this_length=0): - afcpack = Container(magic=AFCMAGIC, - entire_length=AFCPacket.sizeof() + len(data), - this_length=AFCPacket.sizeof() + len(data), - packet_num=self.packet_num, - operation=operation) - if this_length: - afcpack.this_length = this_length - header = AFCPacket.build(afcpack) - self.packet_num += 1 - self.service.send(header + data) - - def _receive_data(self): - res = self.service.recv_exact(AFCPacket.sizeof()) - status = afc_error_t.AFC_E_SUCCESS - data = "" - if res: - res = AFCPacket.parse(res) - assert res["entire_length"] >= AFCPacket.sizeof() - length = res["entire_length"] - AFCPacket.sizeof() - data = self.service.recv_exact(length) - if res.operation == afc_opcode_t.AFC_OP_STATUS: - if length != 8: - self.logger.error("Status length != 8") - status = afc_error_t.parse(data) - elif res.operation != afc_opcode_t.AFC_OP_DATA: - pass # print "error ?", res - return status, data - - def _do_operation(self, opcode, data: bytes = b""): - self._dispatch_packet(opcode, data) - status, data = self._receive_data() - - if status != afc_error_t.AFC_E_SUCCESS: - raise Exception(f'opcode: {opcode} failed with status: {status}') - - return data - def get_device_info(self): return list_to_dict(self._do_operation(afc_opcode_t.AFC_OP_GET_DEVINFO)) - def read_directory(self, dirname): + def listdir(self, dirname): data = self._do_operation(afc_opcode_t.AFC_OP_READ_DIR, dirname.encode()) data = data.decode('utf-8') return [x for x in data.split("\x00") if x != ""] - def make_directory(self, dirname): + def mkdir(self, dirname): return self._do_operation(afc_opcode_t.AFC_OP_MAKE_DIR, dirname.encode()) - def remove_directory(self, dirname): - info = self.get_file_info(dirname) - if not info or info.get("st_ifmt") != "S_IFDIR": - self.logger.info("remove_directory: %s not S_IFDIR", dirname) + def rmdir(self, dirname): + stat = self.stat(dirname) + if not stat or stat.get("st_ifmt") != "S_IFDIR": + self.logger.error("remove_directory: %s not S_IFDIR", dirname) return - for d in self.read_directory(dirname): + for d in self.listdir(dirname): if d == "." or d == ".." or d == "": continue - info = self.get_file_info(dirname + "/" + d) - if info.get("st_ifmt") == "S_IFDIR": - self.remove_directory(dirname + "/" + d) + current_filename = posixpath.join(dirname, d) + stat = self.stat(current_filename) + if stat.get("st_ifmt") == "S_IFDIR": + self.rmdir(current_filename) else: - self.logger.info("%s/%s", dirname, d) - self.file_remove(dirname + "/" + d) - assert len(self.read_directory(dirname)) == 2 # .. et . - return self.file_remove(dirname) + self.logger.info(current_filename) + self.rm(current_filename) + assert len(self.listdir(dirname)) == 2 # .. et . + return self.rm(dirname) - def get_file_info(self, filename): + def stat(self, filename): return list_to_dict(self._do_operation(afc_opcode_t.AFC_OP_GET_FILE_INFO, filename.encode())) - def make_link(self, target, linkname, type=AFC_SYMLINK): - linkname = linkname.encode('utf-8') + def symlink(self, target, source, type_=AFC_SYMLINK): + source = source.encode('utf-8') separator = b"\x00" return self._do_operation(afc_opcode_t.AFC_OP_MAKE_LINK, - struct.pack(" 0: if sz > MAXIMUM_READ_SIZE: @@ -241,7 +203,7 @@ def file_read(self, handle, sz): data += chunk return data - def file_write(self, handle, data, chunk_size=MAXIMUM_WRITE_SIZE): + def fwrite(self, handle, data, chunk_size=MAXIMUM_WRITE_SIZE): file_handle = struct.pack("= AFCPacket.sizeof() + length = res["entire_length"] - AFCPacket.sizeof() + data = self.service.recv_exact(length) + if res.operation == afc_opcode_t.AFC_OP_STATUS: + if length != 8: + self.logger.error("Status length != 8") + status = afc_error_t.parse(data) + elif res.operation != afc_opcode_t.AFC_OP_DATA: + pass + return status, data + + def _do_operation(self, opcode, data: bytes = b""): + self._dispatch_packet(opcode, data) + status, data = self._receive_data() + + if status != afc_error_t.AFC_E_SUCCESS: + raise Exception(f'opcode: {opcode} failed with status: {status}') + + return data + -class AFCShell(Cmd): +class AfcShell(Cmd): def __init__(self, lockdown: LockdownClient, afcname='com.apple.afc', completekey='tab'): Cmd.__init__(self, completekey=completekey) self.logger = logging.getLogger(__name__) self.lockdown = lockdown - self.afc = AFCClient(self.lockdown, service_name=afcname) + self.afc = AfcService(self.lockdown, service_name=afcname) self.curdir = '/' self.prompt = 'AFC$ ' + self.curdir + ' ' self.complete_cat = self._complete @@ -335,7 +334,7 @@ def do_pwd(self, p): def do_link(self, p): z = p.split() - self.afc.make_link(AFC_SYMLINK, z[0], z[1]) + self.afc.symlink(AFC_SYMLINK, z[0], z[1]) def do_cd(self, p): if not p.startswith("/"): @@ -344,7 +343,7 @@ def do_cd(self, p): new = p new = os.path.normpath(new).replace("\\", "/").replace("//", "/") - if self.afc.read_directory(new): + if self.afc.listdir(new): self.curdir = new self.prompt = "AFC$ %s " % new else: @@ -353,14 +352,14 @@ def do_cd(self, p): def _complete(self, text, line, begidx, endidx): filename = text.split("/")[-1] dirname = "/".join(text.split("/")[:-1]) - return [dirname + "/" + x for x in self.afc.read_directory(self.curdir + "/" + dirname) if + return [dirname + "/" + x for x in self.afc.listdir(self.curdir + "/" + dirname) if x.startswith(filename)] def do_ls(self, p): dirname = posixpath.join(self.curdir, p) if self.curdir.endswith("/"): dirname = self.curdir + p - d = self.afc.read_directory(dirname) + d = self.afc.listdir(dirname) if d: for dd in d: print(dd) @@ -373,22 +372,22 @@ def do_cat(self, p): print(data) def do_rm(self, p): - f = self.afc.get_file_info(posixpath.join(self.curdir, p)) + f = self.afc.stat(posixpath.join(self.curdir, p)) if f['st_ifmt'] == 'S_IFDIR': - self.afc.remove_directory(posixpath.join(self.curdir, p)) + self.afc.rmdir(posixpath.join(self.curdir, p)) else: - self.afc.file_remove(posixpath.join(self.curdir, p)) + self.afc.rm(posixpath.join(self.curdir, p)) def do_pull(self, user_args): args = user_args.split() if len(args) != 2: - local_path = "." + local_path = ".." remote_path = user_args else: local_path = args[1] remote_path = args[0] - remote_file_info = self.afc.get_file_info(posixpath.join(self.curdir, remote_path)) + remote_file_info = self.afc.stat(posixpath.join(self.curdir, remote_path)) if not remote_file_info: logging.error("remote file does not exist") return @@ -398,7 +397,7 @@ def do_pull(self, user_args): if not os.path.isdir(out_path): os.makedirs(out_path, MODE_MASK) - for d in self.afc.read_directory(remote_path): + for d in self.afc.listdir(remote_path): if d == "." or d == ".." or d == "": continue self.do_pull(remote_path + "/" + d + " " + local_path) @@ -423,12 +422,13 @@ def do_push(self, p): logging.info(f'from {src} to {dst}') if os.path.isdir(src): - self.afc.make_directory(os.path.join(dst)) + self.afc.mkdir(os.path.join(dst)) for x in os.listdir(src): if x.startswith("."): continue path = os.path.join(src, x) - self.do_push(path + " " + dst + "/" + path) + dst = os.path.join(dst + "/" + path) + self.do_push(path + " " + dst) else: data = open(src, "rb").read() self.afc.set_file_contents(posixpath.join(self.curdir, dst), data) @@ -441,10 +441,10 @@ def do_hexdump(self, filename): print(hexdump.hexdump(self.afc.get_file_contents(filename))) def do_mkdir(self, p): - print(self.afc.make_directory(p)) + print(self.afc.mkdir(p)) def do_rmdir(self, p): - return self.afc.remove_directory(p) + return self.afc.rmdir(p) def do_info(self, p): for k, v in self.afc.get_device_info().items(): @@ -452,8 +452,8 @@ def do_info(self, p): def do_mv(self, p): t = p.split() - return self.afc.rename_path(t[0], t[1]) + return self.afc.rename(t[0], t[1]) def do_stat(self, filename): filename = posixpath.join(self.curdir, filename) - pprint(self.afc.get_file_info(filename)) + pprint(self.afc.stat(filename)) diff --git a/pymobiledevice3/services/file_relay.py b/pymobiledevice3/services/file_relay.py index 5420d44bf..690dcddcd 100755 --- a/pymobiledevice3/services/file_relay.py +++ b/pymobiledevice3/services/file_relay.py @@ -40,7 +40,9 @@ def stop_session(self): self.logger.info("Disconecting...") self.service.close() - def request_sources(self, sources=["UserDatabases"]): + def request_sources(self, sources=None): + if sources is None: + sources = ["UserDatabases"] self.service.send_plist({"Sources": sources}) while 1: res = self.service.recv_plist() diff --git a/pymobiledevice3/services/house_arrest.py b/pymobiledevice3/services/house_arrest.py index 9ac1fe08b..22ed734d6 100755 --- a/pymobiledevice3/services/house_arrest.py +++ b/pymobiledevice3/services/house_arrest.py @@ -1,10 +1,10 @@ import logging from pymobiledevice3.lockdown import LockdownClient -from pymobiledevice3.afc import AFCClient, AFCShell +from pymobiledevice3.services.afc import AfcService, AfcShell -class HouseArrestService(AFCClient): +class HouseArrestService(AfcService): SERVICE_NAME = 'com.apple.mobile.house_arrest' def __init__(self, lockdown: LockdownClient): @@ -25,4 +25,4 @@ def send_command(self, bundle_id, cmd="VendContainer"): def shell(self, application_id, cmd="VendContainer"): res = self.send_command(application_id, cmd) if res: - AFCShell(self.lockdown).cmdloop() + AfcShell(self.lockdown).cmdloop() diff --git a/pymobiledevice3/services/installation_proxy.py b/pymobiledevice3/services/installation_proxy.py index bec9c2529..cdb95da20 100644 --- a/pymobiledevice3/services/installation_proxy.py +++ b/pymobiledevice3/services/installation_proxy.py @@ -3,7 +3,7 @@ import os from pymobiledevice3.lockdown import LockdownClient -from pymobiledevice3.afc import AFCClient +from pymobiledevice3.services.afc import AfcService client_options = { "SkipUninstall": False, @@ -54,7 +54,7 @@ def install_from_local(self, ipa_path, cmd="Install", options=None, handler=None if options is None: options = {} remote_path = posixpath.join('/', os.path.basename(ipa_path)) - afc = AFCClient(self.lockdown) + afc = AfcService(self.lockdown) afc.set_file_contents(remote_path, open(ipa_path, "rb").read()) cmd = {"Command": cmd, "ClientOptions": options, diff --git a/pymobiledevice3/services/mobilebackup.py b/pymobiledevice3/services/mobilebackup.py index de357cdce..0169079db 100755 --- a/pymobiledevice3/services/mobilebackup.py +++ b/pymobiledevice3/services/mobilebackup.py @@ -5,7 +5,7 @@ import codecs from pymobiledevice3.lockdown import LockdownClient -from pymobiledevice3.afc import AFCClient +from pymobiledevice3.services.afc import AfcService MOBILEBACKUP_E_SUCCESS = 0 MOBILEBACKUP_E_INVALID_ARG = -1 @@ -84,9 +84,9 @@ def create_info_plist(self): "IMEI": root_node.get("InternationalMobileEquipmentIdentity") or "", "Last Backup Date": datetime.datetime.now()} - afc = AFCClient(self.lockdown) + afc = AfcService(self.lockdown) iTunesFilesDict = {} - iTunesFiles = afc.read_directory("/iTunes_Control/iTunes/") + iTunesFiles = afc.listdir("/iTunes_Control/iTunes/") for i in iTunesFiles: data = afc.get_file_contents("/iTunes_Control/iTunes/" + i) diff --git a/pymobiledevice3/services/mobilebackup2.py b/pymobiledevice3/services/mobilebackup2.py index a6b07e663..b854d6512 100644 --- a/pymobiledevice3/services/mobilebackup2.py +++ b/pymobiledevice3/services/mobilebackup2.py @@ -9,7 +9,7 @@ from time import mktime, gmtime from uuid import uuid4 -from pymobiledevice3.afc import AFCClient +from pymobiledevice3.services.afc import AfcService from pymobiledevice3.services.installation_proxy import InstallationProxyService from pymobiledevice3.services.springboard import SpringBoardServicesService from pymobiledevice3.lockdown import LockdownClient @@ -52,7 +52,7 @@ def start(self): self.udid = lockdown.get_value("", "UniqueDeviceID") self.willEncrypt = lockdown.get_value("com.apple.mobile.backup", "WillEncrypt") self.escrowBag = lockdown.get_value('', 'EscrowBag') - self.afc = AFCClient(self.lockdown) # We need this to create lock files + self.afc = AfcService(self.lockdown) # We need this to create lock files self.service = self.lockdown.start_service("com.apple.mobilebackup2") if not self.service: raise Exception("MobileBackup2 init error : Could not start com.apple.mobilebackup2") diff --git a/pymobiledevice3/usbmux.py b/pymobiledevice3/usbmux.py index b819939a8..d5070ada2 100644 --- a/pymobiledevice3/usbmux.py +++ b/pymobiledevice3/usbmux.py @@ -240,7 +240,7 @@ def __init__(self, socket_path=None): self.version = 1 self.devices = self.listener.devices - def process(self, timeout=0.01): + def process(self, timeout=0): self.listener.process(timeout) def connect(self, device, port): diff --git a/tests/services/test_afc.py b/tests/services/test_afc.py index 34bc42b04..4db6d23c5 100644 --- a/tests/services/test_afc.py +++ b/tests/services/test_afc.py @@ -1,21 +1,29 @@ -# -*- coding:utf-8 -*- -""" -AFC test case -""" +from pymobiledevice3.services.afc import AfcService -from pymobiledevice3.afc import AFCClient, AFC_FOPEN_RW, AFC_FOPEN_RDONLY +TEST_FILENAME = 'test' def test_file_read_write(lockdown): - afc = AFCClient(lockdown) + afc = AfcService(lockdown) body = b'data' - handle = afc.file_open('test', AFC_FOPEN_RW) - afc.file_write(handle, body) - afc.file_close(handle) + afc.set_file_contents(TEST_FILENAME, body) + assert afc.get_file_contents(TEST_FILENAME) == body - handle = afc.file_open('test', AFC_FOPEN_RDONLY) - read_data = afc.file_read(handle, len(body)) - afc.file_close(handle) - assert body == read_data +def test_ls(lockdown): + afc = AfcService(lockdown) + filenames = afc.listdir('/') + assert 'DCIM' in filenames + + +def test_rm(lockdown): + afc = AfcService(lockdown) + afc.set_file_contents(TEST_FILENAME, b'') + filenames = afc.listdir('/') + assert TEST_FILENAME in filenames + + afc.rm(TEST_FILENAME) + + filenames = afc.listdir('/') + assert TEST_FILENAME not in filenames diff --git a/tests/services/test_dvt_secure_socket_proxy.py b/tests/services/test_dvt_secure_socket_proxy.py index 25820310d..b2cb4aa99 100644 --- a/tests/services/test_dvt_secure_socket_proxy.py +++ b/tests/services/test_dvt_secure_socket_proxy.py @@ -1,5 +1,3 @@ -# -*- coding:utf-8 -*- - from pymobiledevice3.services.dvt_secure_socket_proxy import DvtSecureSocketProxyService