text: #
  # Represents a file that can be signed. A file that
  # conforms to the Mach-O ABI.
  #
  # Executable, dylib, or framework.
  #
  
  import abc
  import logging
  import os
  import tempfile
  
  from . import macho
  from .codesig import (ApplicationSlot, Codesig, EntitlementsSlot, InfoSlot,
      RequirementsSlot, ResourceDirSlot)
  from .utils import with_metaclass
  
  
  log = logging.getLogger(__name__)
  
  
  class Signable(with_metaclass(abc.ABCMeta)):
      slot_classes = []
  
      def __init__(self, bundle, path):
          log.debug("working on {0}".format(path))
          self.bundle = bundle
          self.path = path
  
          self.f = open(self.path, "rb")
          self.f.seek(0, os.SEEK_END)
          self.file_end = self.f.tell()
          self.f.seek(0)
  
          self.m = macho.MachoFile.parse_stream(self.f)
          self.arches = self._parse_arches()
  
      def _parse_arches(self):
          """ parse architectures and associated Codesig """
          arch_macho = self.m.data
          arches = []
          if 'FatArch' in arch_macho:
              for i, arch in enumerate(arch_macho.FatArch):
                  this_arch_macho = arch.MachO
                  next_macho = i + 1
                  if next_macho == len(arch_macho.FatArch):  # last
                      this_macho_end = self.file_end
                  else:
                      next_arch = arch_macho.FatArch[next_macho]
                      this_macho_end = next_arch.MachO.macho_start
                  arches.append(self._get_arch(this_arch_macho,
                                               this_macho_end))
          else:
              arches.append(self._get_arch(arch_macho,
                                           self.file_end))
  
          return arches
  
      def _get_arch(self, macho, macho_end):
          arch = {'macho': macho, 'macho_end': macho_end}
  
          arch['cmds'] = {}
          for cmd in macho.commands:
              name = cmd.cmd
              arch['cmds'][name] = cmd
  
          if 'LC_CODE_SIGNATURE' in arch['cmds']:
              arch['lc_codesig'] = arch['cmds']['LC_CODE_SIGNATURE']
              codesig_offset = arch['macho'].macho_start + arch['lc_codesig'].data.dataoff
              self.f.seek(codesig_offset)
              codesig_data = self.f.read(arch['lc_codesig'].data.datasize)
              # log.debug("codesig len: {0}".format(len(codesig_data)))
          else:
              raise Exception('At this time, isign cannot sign an unsigned app.')
              # log.info("signing from scratch!")
              # entitlements_file = '/path/to/some/entitlements.plist'
              # codesig_data = make_signature(macho, macho_end, arch['cmds'], self.f, entitlements_file)
              # arch['lc_codesig'] = arch['cmds']['LC_CODE_SIGNATURE']
  
          arch['codesig'] = Codesig(self, codesig_data)
          arch['codesig_len'] = len(codesig_data)
  
          return arch
  
      def _sign_arch(self, arch, app, signer):
  
          arch['codesig'].resign(app, signer)
  
          new_codesig_data = arch['codesig'].build_data()
          new_codesig_len = len(new_codesig_data)
          # log.debug("new codesig len: {0}".format(new_codesig_len))
  
          padding_length = arch['codesig_len'] - new_codesig_len
          new_codesig_data += b"\x00" * padding_length
          # log.debug("padded len: {0}".format(len(new_codesig_data)))
          # log.debug("----")
  
          cmd = arch['lc_codesig']
          cmd.data.datasize = len(new_codesig_data)
          cmd.bytes = macho.CodeSigRef.build(arch['lc_codesig'].data)
  
          offset = cmd.data.dataoff
          return offset, new_codesig_data
  
      def should_fill_slot(self, codesig, slot):
          slot_class = slot.__class__
          if slot_class not in self.slot_classes:
              # This signable does not have this slot
              return False
  
          if slot_class == InfoSlot and not self.bundle.info_props_changed():
              # No Info.plist changes, don't fill
              return False
  
          if slot_class == ApplicationSlot and not codesig.is_sha256_signature():
              # Application slot only needs to be zeroed out when there's a sha256 layer
              return False
  
          return True
  
      def get_changed_bundle_id(self):
          # Return a bundle ID to assign if Info.plist's CFBundleIdentifier value was changed
          if self.bundle.info_prop_changed('CFBundleIdentifier'):
              return self.bundle.get_info_prop('CFBundleIdentifier')
          else:
              return None
  
      def sign(self, app, signer):
          # copy self.f into temp, reset to beginning of file
          temp = tempfile.NamedTemporaryFile('wb', delete=False)
          self.f.seek(0)
          temp.write(self.f.read())
          temp.seek(0)
  
          # write new codesign blocks for each arch
          # offset_fmt = ("offset: {2}, write offset: {0}, "
          #               "new_codesig_data len: {1}")
          for arch in self.arches:
              offset, new_codesig_data = self._sign_arch(arch, app, signer)
              write_offset = arch['macho'].macho_start + offset
              # log.debug(offset_fmt.format(write_offset,
              #                             len(new_codesig_data),
              #                             offset))
              temp.seek(write_offset)
              temp.write(new_codesig_data)
  
          # write new headers
          temp.seek(0)
          macho.MachoFile.build_stream(self.m, temp)
          temp.close()
  
          # make copy have same permissions
          mode = os.stat(self.path).st_mode
          os.chmod(temp.name, mode)
          # log.debug("moving temporary file to {0}".format(self.path))
          os.rename(temp.name, self.path)
  
  
  class Executable(Signable):
      """ The main executable of an app. """
      slot_classes = [EntitlementsSlot,
                      ResourceDirSlot,
                      RequirementsSlot,
                      ApplicationSlot,
                      InfoSlot]
  
  
  class Dylib(Signable):
      """ A dynamic library that isn't part of its own bundle, e.g.
          the Swift libraries.
  
          TODO: Dylibs have an info slot, however the Info.plist is embedded in the __TEXT section
                of the file (__info_plist) instead of being a seperate file.
                Add read/write of the embedded Info.plist so we can include InfoSlot below.
      """
      slot_classes = [EntitlementsSlot,
                      RequirementsSlot]
  
  
  class Appex(Signable):
      """ An app extension  """
      slot_classes = [EntitlementsSlot,
                      RequirementsSlot,
                      InfoSlot]
  
  
  class Framework(Signable):
      """ The main executable of a Framework, which is a library of sorts
          but is bundled with both files and code """
      slot_classes = [ResourceDirSlot,
                      RequirementsSlot,
                      InfoSlot]
  