Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/hp/ipkg.py: 38%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

52 statements  

1import io 

2from pathlib import Path 

3 

4from structlog import get_logger 

5 

6from unblob.file_utils import ( 

7 Endian, 

8 File, 

9 FileSystem, 

10 InvalidInputFormat, 

11 StructParser, 

12 snull, 

13) 

14from unblob.models import ( 

15 Extractor, 

16 ExtractResult, 

17 HandlerDoc, 

18 HandlerType, 

19 HexString, 

20 Reference, 

21 StructHandler, 

22 ValidChunk, 

23) 

24 

25logger = get_logger() 

26 

27C_DEFINITIONS = r""" 

28 typedef struct ipkg_file_entry { 

29 char name[256]; 

30 uint64 offset; 

31 uint64 size; 

32 uint32 crc32; 

33 } ipkg_toc_entry_t; 

34 

35 typedef struct ipkg_header { 

36 char magic[4]; 

37 uint16 major; 

38 uint16 minor; 

39 uint32 toc_offset; 

40 uint32 unknown_1; 

41 uint32 toc_entries; 

42 uint32 unknown_2[2]; 

43 uint32 always_null; 

44 char file_version[256]; 

45 char product_name[256]; 

46 char ipkg_name[256]; 

47 char signature[256]; 

48 } ipkg_header_t; 

49""" 

50 

51 

52def is_valid_header(header) -> bool: 

53 if header.toc_offset == 0 or header.toc_entries == 0: 

54 return False 

55 try: 

56 snull(header.ipkg_name).decode("utf-8") 

57 snull(header.file_version).decode("utf-8") 

58 snull(header.product_name).decode("utf-8") 

59 except UnicodeDecodeError: 

60 return False 

61 return True 

62 

63 

64class HPIPKGExtractor(Extractor): 

65 def __init__(self): 

66 self._struct_parser = StructParser(C_DEFINITIONS) 

67 

68 def extract(self, inpath: Path, outdir: Path): 

69 entries = [] 

70 fs = FileSystem(outdir) 

71 with File.from_path(inpath) as file: 

72 header = self._struct_parser.parse("ipkg_header_t", file, Endian.LITTLE) 

73 file.seek(header.toc_offset, io.SEEK_SET) 

74 for _ in range(header.toc_entries): 

75 entry = self._struct_parser.parse( 

76 "ipkg_toc_entry_t", file, Endian.LITTLE 

77 ) 

78 entry_path = Path(snull(entry.name).decode("utf-8")) 

79 if entry_path.parent.name: 

80 raise InvalidInputFormat("Entry name contains directories.") 

81 entries.append( 

82 ( 

83 Path(entry_path.name), 

84 entry.offset, 

85 entry.size, 

86 ) 

87 ) 

88 

89 for carve_path, start_offset, size in entries: 

90 fs.carve(carve_path, file, start_offset, size) 

91 

92 return ExtractResult(reports=fs.problems) 

93 

94 

95class HPIPKGHandler(StructHandler): 

96 NAME = "ipkg" 

97 

98 PATTERNS = [HexString("69 70 6B 67 01 00 03 00")] 

99 

100 C_DEFINITIONS = C_DEFINITIONS 

101 HEADER_STRUCT = "ipkg_header_t" 

102 EXTRACTOR = HPIPKGExtractor() 

103 

104 DOC = HandlerDoc( 

105 name="HP IPKG", 

106 description="HP IPKG firmware archives consist of a custom header, followed by a table of contents and file entries. Each entry specifies metadata such as file name, offset, size, and CRC32 checksum.", 

107 handler_type=HandlerType.ARCHIVE, 

108 vendor="HP", 

109 references=[ 

110 Reference( 

111 title="hpbdl", 

112 url="https://github.com/tylerwhall/hpbdl", 

113 ) 

114 ], 

115 limitations=[], 

116 ) 

117 

118 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None: 

119 header = self.parse_header(file, endian=Endian.LITTLE) 

120 

121 if not is_valid_header(header): 

122 raise InvalidInputFormat("Invalid IPKG header.") 

123 

124 file.seek(start_offset + header.toc_offset, io.SEEK_SET) 

125 end_offset = -1 

126 for _ in range(header.toc_entries): 

127 entry = self._struct_parser.parse("ipkg_toc_entry_t", file, Endian.LITTLE) 

128 end_offset = max(end_offset, start_offset + entry.offset + entry.size) 

129 

130 return ValidChunk(start_offset=start_offset, end_offset=end_offset)