class_docstr: OLE container object
  
  This class encapsulates the interface to an OLE 2 structured
  storage file.  Use the listdir and openstream methods to
  access the contents of this file.
  
  Object names are given as a list of strings, one for each subentry
  level.  The root entry should be omitted.  For example, the following
  code extracts all image streams from a Microsoft Image Composer file::
  
      ole = OleFileIO("fan.mic")
  
      for entry in ole.listdir():
          if entry[1:2] == "Image":
              fin = ole.openstream(entry)
              fout = open(entry[0:1], "wb")
              while True:
                  s = fin.read(8192)
                  if not s:
                      break
                  fout.write(s)
  
  You can use the viewer application provided with the Python Imaging
  Library to view the resulting files (which happens to be standard
  TIFF files).
class_docstr_tok: ["OLE","container","object","This","class","encapsulates","the","interface","to","an","OLE","2","structured","storage","file",".","Use","the","listdir","and","openstream","methods","to","access","the","contents","of","this","file",".","Object","names","are","given","as","a","list","of","strings",",","one","for","each","subentry","level",".","The","root","entry","should","be","omitted",".","For","example",",","the","following","code","extracts","all","image","streams","from","a","Microsoft","Image","Composer","file",":",":","ole","=","OleFileIO","(","``","fan.mic","''",")","for","entry","in","ole.listdir","(",")",":","if","entry","[","1:2","]","==","``","Image","''",":","fin","=","ole.openstream","(","entry",")","fout","=","open","(","entry","[","0:1","]",",","``","wb","''",")","while","True",":","s","=","fin.read","(","8192",")","if","not","s",":","break","fout.write","(","s",")","You","can","use","the","viewer","application","provided","with","the","Python","Imaging","Library","to","view","the","resulting","files","(","which","happens","to","be","standard","TIFF","files",")","."]
docstr: Open an OLE2 file in read-only or read/write mode.
  Read and parse the header, FAT and directory.
  
  :param filename: string-like or file-like object, OLE file to parse
  
      - if filename is a string smaller than 1536 bytes, it is the path
        of the file to open. (bytes or unicode string)
      - if filename is a string longer than 1535 bytes, it is parsed
        as the content of an OLE file in memory. (bytes type only)
      - if filename is a file-like object (with read, seek and tell methods),
        it is parsed as-is.
  
  :param write_mode: bool, if True the file is opened in read/write mode instead
      of read-only by default. (ignored if filename is not a path)
docstr_tok: ["Open","an","OLE2","file","in","read-only","or","read\/write","mode",".","Read","and","parse","the","header",",","FAT","and","directory",".",":","param","filename",":","string-like","or","file-like","object",",","OLE","file","to","parse","-","if","filename","is","a","string","smaller","than","1536","bytes",",","it","is","the","path","of","the","file","to","open",".","(","bytes","or","unicode","string",")","-","if","filename","is","a","string","longer","than","1535","bytes",",","it","is","parsed","as","the","content","of","an","OLE","file","in","memory",".","(","bytes","type","only",")","-","if","filename","is","a","file-like","object","(","with","read",",","seek","and","tell","methods",")",",","it","is","parsed","as-is",".",":","param","write_mode",":","bool",",","if","True","the","file","is","opened","in","read\/write","mode","instead","of","read-only","by","default",".","(","ignored","if","filename","is","not","a","path",")"]
code: def open(self, filename, write_mode=False):
      """
      Open an OLE2 file in read-only or read/write mode.
      Read and parse the header, FAT and directory.
  
      :param filename: string-like or file-like object, OLE file to parse
  
          - if filename is a string smaller than 1536 bytes, it is the path
            of the file to open. (bytes or unicode string)
          - if filename is a string longer than 1535 bytes, it is parsed
            as the content of an OLE file in memory. (bytes type only)
          - if filename is a file-like object (with read, seek and tell methods),
            it is parsed as-is.
  
      :param write_mode: bool, if True the file is opened in read/write mode instead
          of read-only by default. (ignored if filename is not a path)
      """
      self.write_mode = write_mode
      # [PL] check if filename is a string-like or file-like object:
      # (it is better to check for a read() method)
      if hasattr(filename, "read"):
          # TODO: also check seek and tell methods?
          # file-like object: use it directly
          self.fp = filename
      elif (
          isinstance(filename, bytes)
          and len(filename) >= MINIMAL_OLEFILE_SIZE
      ):
          # filename is a bytes string containing the OLE file to be parsed:
          # convert it to BytesIO
          self.fp = io.BytesIO(filename)
      else:
          # string-like object: filename of file on disk
          if self.write_mode:
              # open file in mode 'read with update, binary'
              # According to https://docs.python.org/2/library/functions.html#open
              # 'w' would truncate the file, 'a' may only append on some Unixes
              mode = "r+b"
          else:
              # read-only mode by default
              mode = "rb"
          self.fp = open(filename, mode)
      # obtain the filesize by using seek and tell, which should work on most
      # file-like objects:
      # TODO: do it above, using getsize with filename when possible?
      # TODO: fix code to fail with clear exception when filesize cannot be obtained
      filesize = 0
      self.fp.seek(0, os.SEEK_END)
      try:
          filesize = self.fp.tell()
      finally:
          self.fp.seek(0)
      self._filesize = filesize
      log.debug(
          "File size: %d bytes (%Xh)" % (self._filesize, self._filesize)
      )
  
      # lists of streams in FAT and MiniFAT, to detect duplicate references
      # (list of indexes of first sectors of each stream)
      self._used_streams_fat = []
      self._used_streams_minifat = []
  
      header = self.fp.read(512)
  
      if len(header) != 512 or header[:8] != MAGIC:
          log.debug("Magic = %r instead of %r" % (header[:8], MAGIC))
          self._raise_defect(
              DEFECT_FATAL, "not an OLE2 structured storage file"
          )
  
      # [PL] header structure according to AAF specifications:
      ##Header
      ##struct StructuredStorageHeader { // [offset from start (bytes), length (bytes)]
      ##BYTE _abSig[8]; // [00H,08] {0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1,
      ##                // 0x1a, 0xe1} for current version
      ##CLSID _clsid;   // [08H,16] reserved must be zero (WriteClassStg/
      ##                // GetClassFile uses root directory class id)
      ##USHORT _uMinorVersion; // [18H,02] minor version of the format: 33 is
      ##                       // written by reference implementation
      ##USHORT _uDllVersion;   // [1AH,02] major version of the dll/format: 3 for
      ##                       // 512-byte sectors, 4 for 4 KB sectors
      ##USHORT _uByteOrder;    // [1CH,02] 0xFFFE: indicates Intel byte-ordering
      ##USHORT _uSectorShift;  // [1EH,02] size of sectors in power-of-two;
      ##                       // typically 9 indicating 512-byte sectors
      ##USHORT _uMiniSectorShift; // [20H,02] size of mini-sectors in power-of-two;
      ##                          // typically 6 indicating 64-byte mini-sectors
      ##USHORT _usReserved; // [22H,02] reserved, must be zero
      ##ULONG _ulReserved1; // [24H,04] reserved, must be zero
      ##FSINDEX _csectDir; // [28H,04] must be zero for 512-byte sectors,
      ##                   // number of SECTs in directory chain for 4 KB
      ##                   // sectors
      ##FSINDEX _csectFat; // [2CH,04] number of SECTs in the FAT chain
      ##SECT _sectDirStart; // [30H,04] first SECT in the directory chain
      ##DFSIGNATURE _signature; // [34H,04] signature used for transactions; must
      ##                        // be zero. The reference implementation
      ##                        // does not support transactions
      ##ULONG _ulMiniSectorCutoff; // [38H,04] maximum size for a mini stream;
      ##                           // typically 4096 bytes
      ##SECT _sectMiniFatStart; // [3CH,04] first SECT in the MiniFAT chain
      ##FSINDEX _csectMiniFat; // [40H,04] number of SECTs in the MiniFAT chain
      ##SECT _sectDifStart; // [44H,04] first SECT in the DIFAT chain
      ##FSINDEX _csectDif; // [48H,04] number of SECTs in the DIFAT chain
      ##SECT _sectFat[109]; // [4CH,436] the SECTs of first 109 FAT sectors
      ##};
  
      # [PL] header decoding:
      # '<' indicates little-endian byte ordering for Intel (cf. struct module help)
      fmt_header = "<8s16sHHHHHHLLLLLLLLLL"
      header_size = struct.calcsize(fmt_header)
      log.debug(
          "fmt_header size = %d, +FAT = %d"
          % (header_size, header_size + 109 * 4)
      )
      header1 = header[:header_size]
      (
          self.header_signature,
          self.header_clsid,
          self.minor_version,
          self.dll_version,
          self.byte_order,
          self.sector_shift,
          self.mini_sector_shift,
          self.reserved1,
          self.reserved2,
          self.num_dir_sectors,
          self.num_fat_sectors,
          self.first_dir_sector,
          self.transaction_signature_number,
          self.mini_stream_cutoff_size,
          self.first_mini_fat_sector,
          self.num_mini_fat_sectors,
          self.first_difat_sector,
          self.num_difat_sectors,
      ) = struct.unpack(fmt_header, header1)
      log.debug(struct.unpack(fmt_header, header1))
  
      if self.header_signature != MAGIC:
          # OLE signature should always be present
          self._raise_defect(DEFECT_FATAL, "incorrect OLE signature")
      if self.header_clsid != bytearray(16):
          # according to AAF specs, CLSID should always be zero
          self._raise_defect(
              DEFECT_INCORRECT, "incorrect CLSID in OLE header"
          )
      log.debug("Minor Version = %d" % self.minor_version)
      # TODO: according to MS-CFB, minor version should be 0x003E
      log.debug(
          "DLL Version   = %d (expected: 3 or 4)" % self.dll_version
      )
      if self.dll_version not in [3, 4]:
          # version 3: usual format, 512 bytes per sector
          # version 4: large format, 4K per sector
          self._raise_defect(
              DEFECT_INCORRECT, "incorrect DllVersion in OLE header"
          )
      log.debug("Byte Order    = %X (expected: FFFE)" % self.byte_order)
      if self.byte_order != 0xFFFE:
          # For now only common little-endian documents are handled correctly
          self._raise_defect(
              DEFECT_INCORRECT, "incorrect ByteOrder in OLE header"
          )
          # TODO: add big-endian support for documents created on Mac ?
          # But according to [MS-CFB] ? v20140502, ByteOrder MUST be 0xFFFE.
      self.sector_size = 2**self.sector_shift
      log.debug(
          "Sector Size   = %d bytes (expected: 512 or 4096)"
          % self.sector_size
      )
      if self.sector_size not in [512, 4096]:
          self._raise_defect(
              DEFECT_INCORRECT, "incorrect sector_size in OLE header"
          )
      if (self.dll_version == 3 and self.sector_size != 512) or (
          self.dll_version == 4 and self.sector_size != 4096
      ):
          self._raise_defect(
              DEFECT_INCORRECT,
              "sector_size does not match DllVersion in OLE header",
          )
      self.mini_sector_size = 2**self.mini_sector_shift
      log.debug(
          "MiniFAT Sector Size   = %d bytes (expected: 64)"
          % self.mini_sector_size
      )
      if self.mini_sector_size not in [64]:
          self._raise_defect(
              DEFECT_INCORRECT,
              "incorrect mini_sector_size in OLE header",
          )
      if self.reserved1 != 0 or self.reserved2 != 0:
          self._raise_defect(
              DEFECT_INCORRECT,
              "incorrect OLE header (non-null reserved bytes)",
          )
      log.debug(
          "Number of Directory sectors = %d" % self.num_dir_sectors
      )
      # Number of directory sectors (only allowed if DllVersion != 3)
      if self.sector_size == 512 and self.num_dir_sectors != 0:
          self._raise_defect(
              DEFECT_INCORRECT,
              "incorrect number of directory sectors in OLE header",
          )
      log.debug("Number of FAT sectors = %d" % self.num_fat_sectors)
      # num_fat_sectors = number of FAT sectors in the file
      log.debug("First Directory sector  = %Xh" % self.first_dir_sector)
      # first_dir_sector = 1st sector containing the directory
      log.debug(
          "Transaction Signature Number    = %d"
          % self.transaction_signature_number
      )
      # Signature should be zero, BUT some implementations do not follow this
      # rule => only a potential defect:
      # (according to MS-CFB, may be != 0 for applications supporting file
      # transactions)
      if self.transaction_signature_number != 0:
          self._raise_defect(
              DEFECT_POTENTIAL,
              "incorrect OLE header (transaction_signature_number>0)",
          )
      log.debug(
          "Mini Stream cutoff size = %Xh (expected: 1000h)"
          % self.mini_stream_cutoff_size
      )
      # MS-CFB: This integer field MUST be set to 0x00001000. This field
      # specifies the maximum size of a user-defined data stream allocated
      # from the mini FAT and mini stream, and that cutoff is 4096 bytes.
      # Any user-defined data stream larger than or equal to this cutoff size
      # must be allocated as normal sectors from the FAT.
      if self.mini_stream_cutoff_size != 0x1000:
          self._raise_defect(
              DEFECT_INCORRECT,
              "incorrect mini_stream_cutoff_size in OLE header",
          )
          # if no exception is raised, the cutoff size is fixed to 0x1000
          log.warning(
              "Fixing the mini_stream_cutoff_size to 4096 (mandatory value) instead of %d"
              % self.mini_stream_cutoff_size
          )
          self.mini_stream_cutoff_size = 0x1000
      # TODO: check if these values are OK
      log.debug(
          "First MiniFAT sector      = %Xh" % self.first_mini_fat_sector
      )
      log.debug(
          "Number of MiniFAT sectors = %d" % self.num_mini_fat_sectors
      )
      log.debug(
          "First DIFAT sector        = %Xh" % self.first_difat_sector
      )
      log.debug(
          "Number of DIFAT sectors   = %d" % self.num_difat_sectors
      )
  
      # calculate the number of sectors in the file
      # (-1 because header doesn't count)
      self.nb_sect = (
          (filesize + self.sector_size - 1) // self.sector_size
      ) - 1
      log.debug(
          "Maximum number of sectors in the file: %d (%Xh)"
          % (self.nb_sect, self.nb_sect)
      )
      # TODO: change this test, because an OLE file MAY contain other data
      # after the last sector.
  
      # file clsid
      self.header_clsid = _clsid(header[8:24])
  
      # TODO: remove redundant attributes, and fix the code which uses them?
      self.sectorsize = self.sector_size  # 1 << i16(header, 30)
      self.minisectorsize = (
          self.mini_sector_size
      )  # 1 << i16(header, 32)
      self.minisectorcutoff = (
          self.mini_stream_cutoff_size
      )  # i32(header, 56)
  
      # check known streams for duplicate references (these are always in FAT,
      # never in MiniFAT):
      self._check_duplicate_stream(self.first_dir_sector)
      # check MiniFAT only if it is not empty:
      if self.num_mini_fat_sectors:
          self._check_duplicate_stream(self.first_mini_fat_sector)
      # check DIFAT only if it is not empty:
      if self.num_difat_sectors:
          self._check_duplicate_stream(self.first_difat_sector)
  
      # Load file allocation tables
      self.loadfat(header)
      # Load directory.  This sets both the direntries list (ordered by sid)
      # and the root (ordered by hierarchy) members.
      self.loaddirectory(self.first_dir_sector)
      self.minifatsect = self.first_mini_fat_sector
  
imports: from __future__ import print_function
  import io
  import sys
  import struct, array, os.path, datetime, logging
label_desc: Use image node_id 6 for calling the OleFileIO obj's underlying member method code with example usage: obj.open(filename, write_mode) without return types