class CABFile(CABFileFormat): @property def slack(self): return self.max_data - self.size def __init__(self, parameters={}): self.cab_filename = parameters.get("cab_filename", None) self.max_data = parameters.get("max_data", 0) self.cabset = parameters.get("cabset", None) self.size = 0 index_in_set = parameters.get("index_in_set", 0) cfdata_reserve = parameters.get("cfdata_reserve", 0) cfheader_reserve = parameters.get("cfheader_reserve", 0) cffolder_reserve = parameters.get("cffolder_reserve", 0) if cfheader_reserve != 0 or cffolder_reserve != 0 or cfdata_reserve != 0: flags = CFHEADER.cfhdrRESERVE_PRESENT else: flags = 0x0000 reserve = { 'cbCFHeader': cfheader_reserve, 'cbCFFolder': cffolder_reserve, 'cbCFData': cfdata_reserve } self.cfheader = CFHEADER(flags=flags, reserve=reserve) self.cfheader.iCabinet = index_in_set self.cffolder_list = [] self.cffile_list = [] self.cfdata_list = [] # This is a help for calculating fields, is not part of the specification self.folder_id = 0 ##### METHODS FOR MANAGING CABs ##### def get_cfheader(self): return self.cfheader def get_cffolder_list(self): return self.cffolder_list def get_cffile_list(self): return self.cffile_list def get_cfdata_list(self): return self.cfdata_list ##################################### def _create_cffolder(self, folder_name): new_cffolder = CFFOLDER(self.cfheader, folder_id=self.folder_id) new_cffolder.name = folder_name self.folder_id += 1 self.cfheader.add_folder(cffolder=new_cffolder) return new_cffolder def update_fields(self): # Update uoffFolderStart in CFFILE self._update_uoffFolderStart() #coffCabStart in CFFOLDER self._update_coffCabStart() # Update cCabinet and coffFiles in CFHEADER self._update_cbCabinet() self._update_coffFiles() def _check_for_scattered_prev_cffile(self, cffolder): # This method workarounds a border case that happens when we have 2 files in a folder # In wich the first file occupies more than one cab... When this happens, when the second # cffile gets added, it cannot share the scattered CFFOLDER because it doesn't work.. # We need to create an anonymous CFFOLDER here and return it last_cffile = cffolder.cffile_list[-1] if last_cffile.iFolder & CFFILE.ifoldCONTINUED_FROM_PREV == CFFILE.ifoldCONTINUED_FROM_PREV: anonymous_folder = self._create_cffolder(Utils.get_random_name(10)) self.cffolder_list.append(anonymous_folder) return anonymous_folder return cffolder def add_file(self, folder_name, filename, total_len, data): if self.size == self.max_data: raise CABException("This cab is full") if (self.size + len(data)) <= self.max_data: try: cffolder = next(_ for _ in self.cffolder_list if _.name == folder_name) # We need to check if the cffolder has a cffile scattered that continues from a PREV # If this is the case, we need to provide a new cffolder anyways.. this is how it works cffolder = self._check_for_scattered_prev_cffile(cffolder) except StopIteration: cffolder = self._create_cffolder(folder_name) self.cffolder_list.append(cffolder) cffile = CFFILE(cffolder=cffolder, total_len=total_len, filename=filename) self.cffile_list.append(cffile) # Max data per CFDATA is 0x8000 -> This is an empirical result if len(data) > 0x8000: data_chunks = [ data[i:i + 0x8000] for i in range(0, len(data), 0x8000) ] for data_chunk in data_chunks: cfdata = CFDATA(cffolder=cffolder, data=data_chunk) self.cfdata_list.append(cfdata) cffolder.add_data(cfdata) else: cfdata = CFDATA(cffolder=cffolder, data=data) self.cfdata_list.append(cfdata) # Update cCFData cffolder.add_data(cfdata) cffolder.add_file(cffile) self.update_fields() self.size += len(data) else: raise CABException("The cab hasn't enough space for the data ") def _update_uoffFolderStart(self): """Updates the Uncompressed byte offset of the start of every file's data""" for key, group in groupby(self.cffile_list, lambda x: x.cffolder.folder_id): offset = 0 for cffile in group: cffile.uoffFolderStart = offset offset += cffile.cbFile def _update_coffCabStart(self): """Update the Absolute file offset of first CFDATA block for every CFFolder""" data_start = len(self.cfheader) + sum([len(cffolder) for cffolder in self.cffolder_list]) + \ sum([len(cffile) for cffile in self.cffile_list]) self.cffolder_list[0].coffCabStart = data_start for index, cffolder in enumerate(self.cffolder_list[1:]): data_start = data_start + sum([ len(cfdata) for cfdata in self.cffolder_list[index].cfdata_list ]) cffolder.coffCabStart = data_start # current = 0 # for index, cfdata in enumerate(self.cfdata_list): # cffolder = cfdata.cffolder # if cffolder.folder_id == current: # offset = len(self.cfheader) + sum([len(cffolder) for cffolder in self.cffolder_list]) + \ # sum([len(cffile) for cffile in self.cffile_list]) # # # now the hard part # for p_data in self.cfdata_list: # if p_data.cffolder.folder_id != current: # offset += len(p_data) # else: # current += 1 # cffolder.coffCabStart = offset # break def _update_cbCabinet(self): """ Total size of this cabinet file in bytes. """ self.cfheader.cbCabinet = self.__len__() def _update_coffFiles(self): """ Absolute file offset of first CFFILE entry. """ value = len(self.cfheader) for i in self.cffolder_list: value += len(i) self.cfheader.coffFiles = value def __repr__(self): data = repr(self.cfheader) for i in self.cffolder_list: data += repr(i) for i in self.cffile_list: data += repr(i) for i in self.cfdata_list: data += repr(i) return data def __str__(self): data = str(self.cfheader) for i in self.cffolder_list: data += str(i) for i in self.cffile_list: data += str(i) for i in self.cfdata_list: data += str(i) return data def __len__(self): result = len(self.cfheader) for i in self.cffolder_list: result += len(i) for i in self.cffile_list: result += len(i) for i in self.cfdata_list: result += len(i) return result @classmethod def get_null_ended_string(cls, sz): return sz + "\x00"
def test_cfheader_add_folder(self): reserve = {'cbCFHeader': 0, 'cbCFFolder': 0, 'cbCFData': 0} cfheader = CFHEADER(flags=0, reserve=reserve) cffolder = CFFOLDER(cfheader=cfheader) cfheader.add_folder(cffolder) self.assertEquals(1, cfheader.cFolders)
class CABFile(CABFileFormat): @property def slack(self): return self.max_data - self.size def __init__(self, parameters={}): self.cab_filename = parameters.get("cab_filename", None) self.max_data = parameters.get("max_data", 0) self.cabset = parameters.get("cabset", None) self.size = 0 index_in_set = parameters.get("index_in_set", 0) cfdata_reserve = parameters.get("cfdata_reserve", 0) cfheader_reserve = parameters.get("cfheader_reserve", 0) cffolder_reserve = parameters.get("cffolder_reserve", 0) if cfheader_reserve != 0 or cffolder_reserve != 0 or cfdata_reserve != 0: flags = CFHEADER.cfhdrRESERVE_PRESENT else: flags = 0x0000 reserve = { 'cbCFHeader' : cfheader_reserve, 'cbCFFolder' : cffolder_reserve, 'cbCFData' : cfdata_reserve } self.cfheader = CFHEADER(flags=flags, reserve=reserve) self.cfheader.iCabinet = index_in_set self.cffolder_list = [] self.cffile_list = [] self.cfdata_list = [] # This is a help for calculating fields, is not part of the specification self.folder_id = 0 ##### METHODS FOR MANAGING CABs ##### def get_cfheader(self): return self.cfheader def get_cffolder_list(self): return self.cffolder_list def get_cffile_list(self): return self.cffile_list def get_cfdata_list(self): return self.cfdata_list ##################################### def _create_cffolder(self, folder_name): new_cffolder = CFFOLDER(self.cfheader, folder_id=self.folder_id) new_cffolder.name = folder_name self.folder_id += 1 self.cfheader.add_folder(cffolder=new_cffolder) return new_cffolder def update_fields(self): # Update uoffFolderStart in CFFILE self._update_uoffFolderStart() #coffCabStart in CFFOLDER self._update_coffCabStart() # Update cCabinet and coffFiles in CFHEADER self._update_cbCabinet() self._update_coffFiles() def _check_for_scattered_prev_cffile(self, cffolder): # This method workarounds a border case that happens when we have 2 files in a folder # In wich the first file occupies more than one cab... When this happens, when the second # cffile gets added, it cannot share the scattered CFFOLDER because it doesn't work.. # We need to create an anonymous CFFOLDER here and return it last_cffile = cffolder.cffile_list[-1] if last_cffile.iFolder & CFFILE.ifoldCONTINUED_FROM_PREV == CFFILE.ifoldCONTINUED_FROM_PREV: anonymous_folder = self._create_cffolder(Utils.get_random_name(10)) self.cffolder_list.append(anonymous_folder) return anonymous_folder return cffolder def add_file(self, folder_name, filename, total_len, data): if self.size == self.max_data: raise CABException("This cab is full") if (self.size + len(data)) <= self.max_data: try: cffolder = next(_ for _ in self.cffolder_list if _.name == folder_name) # We need to check if the cffolder has a cffile scattered that continues from a PREV # If this is the case, we need to provide a new cffolder anyways.. this is how it works cffolder = self._check_for_scattered_prev_cffile(cffolder) except StopIteration: cffolder = self._create_cffolder(folder_name) self.cffolder_list.append(cffolder) cffile = CFFILE(cffolder=cffolder, total_len=total_len, filename=filename) self.cffile_list.append(cffile) # Max data per CFDATA is 0x8000 -> This is an empirical result if len(data) > 0x8000: data_chunks = [data[i:i+0x8000] for i in range(0, len(data), 0x8000)] for data_chunk in data_chunks: cfdata = CFDATA(cffolder=cffolder, data=data_chunk) self.cfdata_list.append(cfdata) cffolder.add_data(cfdata) else: cfdata = CFDATA(cffolder=cffolder, data=data) self.cfdata_list.append(cfdata) # Update cCFData cffolder.add_data(cfdata) cffolder.add_file(cffile) self.update_fields() self.size += len(data) else: raise CABException("The cab hasn't enough space for the data ") def _update_uoffFolderStart(self): """Updates the Uncompressed byte offset of the start of every file's data""" for key, group in groupby(self.cffile_list, lambda x: x.cffolder.folder_id): offset = 0 for cffile in group: cffile.uoffFolderStart = offset offset += cffile.cbFile def _update_coffCabStart(self): """Update the Absolute file offset of first CFDATA block for every CFFolder""" data_start = len(self.cfheader) + sum([len(cffolder) for cffolder in self.cffolder_list]) + \ sum([len(cffile) for cffile in self.cffile_list]) self.cffolder_list[0].coffCabStart = data_start for index, cffolder in enumerate(self.cffolder_list[1:]): data_start = data_start + sum([len(cfdata) for cfdata in self.cffolder_list[index].cfdata_list]) cffolder.coffCabStart = data_start # current = 0 # for index, cfdata in enumerate(self.cfdata_list): # cffolder = cfdata.cffolder # if cffolder.folder_id == current: # offset = len(self.cfheader) + sum([len(cffolder) for cffolder in self.cffolder_list]) + \ # sum([len(cffile) for cffile in self.cffile_list]) # # # now the hard part # for p_data in self.cfdata_list: # if p_data.cffolder.folder_id != current: # offset += len(p_data) # else: # current += 1 # cffolder.coffCabStart = offset # break def _update_cbCabinet(self): """ Total size of this cabinet file in bytes. """ self.cfheader.cbCabinet = self.__len__() def _update_coffFiles(self): """ Absolute file offset of first CFFILE entry. """ value = len(self.cfheader) for i in self.cffolder_list: value += len(i) self.cfheader.coffFiles = value def __repr__(self): data = repr(self.cfheader) for i in self.cffolder_list: data += repr(i) for i in self.cffile_list: data += repr(i) for i in self.cfdata_list: data += repr(i) return data def __str__(self): data = str(self.cfheader) for i in self.cffolder_list: data += str(i) for i in self.cffile_list: data += str(i) for i in self.cfdata_list: data += str(i) return data def __len__(self): result = len(self.cfheader) for i in self.cffolder_list: result += len(i) for i in self.cffile_list: result += len(i) for i in self.cfdata_list: result += len(i) return result @classmethod def get_null_ended_string(cls, sz): return sz + "\x00"