1+ from __future__ import annotations
2+
13import logging
24import os
35import struct
46from collections import OrderedDict
57from itertools import groupby
68from operator import itemgetter
9+ from typing import BinaryIO
710
811from dissect .util .stream import RangeStream , RunlistStream
912
2730
2831
2932class ExFAT :
30- def __init__ (self , fh ):
33+ def __init__ (self , fh : BinaryIO ):
3134 self .filesystem = fh
3235 fh .seek (0 )
3336
@@ -56,7 +59,7 @@ def __init__(self, fh):
5659 self .upcase_table = self .root_directory .upcase_entry
5760 self .volume_label = self .root_directory .volume_entry .volume_label .strip ("\x00 " )
5861
59- def cluster_to_sector (self , cluster ) :
62+ def cluster_to_sector (self , cluster : int ) -> int | None :
6063 """
6164 Returns the clusters' corresponding sector address
6265
@@ -70,7 +73,7 @@ def cluster_to_sector(self, cluster):
7073 sector = ((cluster - 2 ) * (2 ** self .vbr .sectors_per_cluster_exp )) + self .cluster_heap_sector
7174 return sector if sector > 0 else None
7275
73- def sector_to_cluster (self , sector ) :
76+ def sector_to_cluster (self , sector : int ) -> int | None :
7477 """
7578 Returns the sectors' corresponding cluster address
7679
@@ -84,7 +87,9 @@ def sector_to_cluster(self, sector):
8487 cluster = ((sector - self .cluster_to_sector (2 )) // self .sector_size ) + 2
8588 return cluster if cluster >= 2 else None
8689
87- def runlist (self , starting_cluster , not_fragmented = True , size = None ):
90+ def runlist (
91+ self , starting_cluster : int , not_fragmented : bool = True , size : int | None = None
92+ ) -> list [tuple [int , int ]]:
8893 """
8994 Creates a RunlistStream compatible runlist from exFAT FAT structures, in sectors.
9095
@@ -120,7 +125,7 @@ def runlist(self, starting_cluster, not_fragmented=True, size=None):
120125
121126 return runlist
122127
123- def get_cluster_chain (self , starting_cluster ) :
128+ def get_cluster_chain (self , starting_cluster : int ) -> list [ int ] :
124129 """
125130 Reads the on disk FAT to construct the cluster chain
126131
@@ -136,17 +141,17 @@ def get_cluster_chain(self, starting_cluster):
136141
137142 if starting_cluster < 2 :
138143 return chain
139- else :
140- while next_ != EOC :
141- self .fat .seek (starting_cluster * FAT_ENTRY_SIZE )
142- next_ = struct .unpack ("<L" , self .fat .read (FAT_ENTRY_SIZE ))[0 ]
143- chain .append (starting_cluster )
144- starting_cluster = next_
145144
146- return chain
145+ while next_ != EOC :
146+ self .fat .seek (starting_cluster * FAT_ENTRY_SIZE )
147+ next_ = struct .unpack ("<L" , self .fat .read (FAT_ENTRY_SIZE ))[0 ]
148+ chain .append (starting_cluster )
149+ starting_cluster = next_
150+
151+ return chain
147152
148153 @staticmethod
149- def _utc_timezone (timezone ) :
154+ def _utc_timezone (timezone : int ) -> dict [ str , str | int ] :
150155 """
151156 Converts a Microsoft exFAT timezone byte to its UTC timezone equivalent
152157
@@ -173,12 +178,12 @@ def _utc_timezone(timezone):
173178 utc_name = f"UTC{ hours :+03} :{ minutes :02} "
174179
175180 return {"name" : utc_name , "offset" : utc_minute_offset }
176- else :
177- return {"name" : "localtime" , "offset" : 0 }
181+
182+ return {"name" : "localtime" , "offset" : 0 }
178183
179184
180185class RootDirectory :
181- def __init__ (self , fh , location , exfat ):
186+ def __init__ (self , fh : BinaryIO , location : int , exfat : ExFAT ):
182187 self .exfat = exfat
183188 self .location = location
184189 self .size = 0
@@ -189,7 +194,7 @@ def __init__(self, fh, location, exfat):
189194 self .dict = OrderedDict ()
190195 self ._parse_root_dir (fh )
191196
192- def _parse_root_dir (self , fh ) :
197+ def _parse_root_dir (self , fh : BinaryIO ) -> None :
193198 """
194199 Parses the passed fh to construct the Root directory object"""
195200
@@ -206,7 +211,7 @@ def _parse_root_dir(self, fh):
206211 self .root_dir = RunlistStream (fh , runlist , self .size , self .exfat .sector_size )
207212 self .dict = self ._create_root_dir (self .root_dir )
208213
209- def _parse_subdir (self , entry ) :
214+ def _parse_subdir (self , entry : c_exfat . FILE ) -> OrderedDict :
210215 """
211216 Parses the given sub directory file directory entry for containing files
212217
@@ -225,7 +230,7 @@ def _parse_subdir(self, entry):
225230 return self ._parse_file_entries (fh )
226231
227232 @staticmethod
228- def _construct_filename (fn_entries , is_dir = False ):
233+ def _construct_filename (fn_entries : list [ c_exfat . FILENAME_DIRECTORY_ENTRY ] , is_dir : bool = False ) -> str :
229234 """
230235 Assembles the filename from given file name directory entries
231236
@@ -247,7 +252,7 @@ def _construct_filename(fn_entries, is_dir=False):
247252
248253 return filename if not is_dir else filename + "/"
249254
250- def _parse_file_entries (self , fh ) :
255+ def _parse_file_entries (self , fh : BinaryIO ) -> OrderedDict :
251256 """
252257 Finds and parses file entries in a given file handle (file like object)
253258
@@ -271,10 +276,7 @@ def _parse_file_entries(self, fh):
271276 fnentry_count = metadata .subentry_count - 1
272277
273278 stream = c_exfat .STREAM_DIRECTORY_ENTRY (fh .read (DIR_ENTRY_SIZE ))
274- fn_entries = []
275-
276- for _ in range (fnentry_count ):
277- fn_entries .append (c_exfat .FILENAME_DIRECTORY_ENTRY (fh .read (DIR_ENTRY_SIZE )))
279+ fn_entries = [c_exfat .FILENAME_DIRECTORY_ENTRY (fh .read (DIR_ENTRY_SIZE )) for _ in range (fnentry_count )]
278280
279281 file_ = c_exfat .FILE (metadata = metadata , stream = stream , fn_entries = fn_entries )
280282 if file_ .metadata .attributes .directory :
@@ -294,15 +296,15 @@ def _parse_file_entries(self, fh):
294296
295297 return entries
296298
297- def _non_file_entries (self , entry ) :
299+ def _non_file_entries (self , entry : c_exfat . FILE_DIRECTORY_ENTRY ) -> None :
298300 if entry .entry_type == VOLUME_LABEL_ENTRY or entry .entry_type == NO_VOLUME_LABEL_ENTRY :
299301 self .volume_entry = c_exfat .VOLUME_DIRECTORY_ENTRY (entry .dumps ())
300302 elif entry .entry_type == BITMAP_ENTRY :
301303 self .bitmap_entry = c_exfat .BITMAP_DIRECTORY_ENTRY (entry .dumps ())
302304 elif entry .entry_type == UPCASE_TABLE_ENTRY :
303305 self .upcase_entry = c_exfat .UPCASE_DIRECTORY_ENTRY (entry .dumps ())
304306
305- def _create_root_dir (self , root_dir ) :
307+ def _create_root_dir (self , root_dir : BinaryIO ) -> OrderedDict :
306308 """
307309 Since exFAT does not have a dedicated root directory entry
308310 one has to be constructed form available parameters during filesystem parsing.
0 commit comments