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

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

53 statements  

1import io 

2from pathlib import Path 

3from typing import Optional 

4 

5from structlog import get_logger 

6 

7from unblob.file_utils import ( 

8 Endian, 

9 File, 

10 FileSystem, 

11 InvalidInputFormat, 

12 StructParser, 

13 snull, 

14) 

15from unblob.models import ( 

16 Extractor, 

17 ExtractResult, 

18 HandlerDoc, 

19 HandlerType, 

20 HexString, 

21 Reference, 

22 StructHandler, 

23 ValidChunk, 

24) 

25 

26logger = get_logger() 

27 

28C_DEFINITIONS = r""" 

29 typedef struct ipkg_file_entry { 

30 char name[256]; 

31 uint64 offset; 

32 uint64 size; 

33 uint32 crc32; 

34 } ipkg_toc_entry_t; 

35 

36 typedef struct ipkg_header { 

37 char magic[4]; 

38 uint16 major; 

39 uint16 minor; 

40 uint32 toc_offset; 

41 uint32 unknown_1; 

42 uint32 toc_entries; 

43 uint32 unknown_2[2]; 

44 uint32 always_null; 

45 char file_version[256]; 

46 char product_name[256]; 

47 char ipkg_name[256]; 

48 char signature[256]; 

49 } ipkg_header_t; 

50""" 

51 

52 

53def is_valid_header(header) -> bool: 

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

55 return False 

56 try: 

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

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

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

60 except UnicodeDecodeError: 

61 return False 

62 return True 

63 

64 

65class HPIPKGExtractor(Extractor): 

66 def __init__(self): 

67 self._struct_parser = StructParser(C_DEFINITIONS) 

68 

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

70 entries = [] 

71 fs = FileSystem(outdir) 

72 with File.from_path(inpath) as file: 

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

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

75 for _ in range(header.toc_entries): 

76 entry = self._struct_parser.parse( 

77 "ipkg_toc_entry_t", file, Endian.LITTLE 

78 ) 

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

80 if entry_path.parent.name: 

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

82 entries.append( 

83 ( 

84 Path(entry_path.name), 

85 entry.offset, 

86 entry.size, 

87 ) 

88 ) 

89 

90 for carve_path, start_offset, size in entries: 

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

92 

93 return ExtractResult(reports=fs.problems) 

94 

95 

96class HPIPKGHandler(StructHandler): 

97 NAME = "ipkg" 

98 

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

100 

101 C_DEFINITIONS = C_DEFINITIONS 

102 HEADER_STRUCT = "ipkg_header_t" 

103 EXTRACTOR = HPIPKGExtractor() 

104 

105 DOC = HandlerDoc( 

106 name="HP IPKG", 

107 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.", 

108 handler_type=HandlerType.ARCHIVE, 

109 vendor="HP", 

110 references=[ 

111 Reference( 

112 title="hpbdl", 

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

114 ) 

115 ], 

116 limitations=[], 

117 ) 

118 

119 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]: 

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

121 

122 if not is_valid_header(header): 

123 raise InvalidInputFormat("Invalid IPKG header.") 

124 

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

126 end_offset = -1 

127 for _ in range(header.toc_entries): 

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

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

130 

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