Exercise 3 solution import gzip import io import struct NAMES = {0: "FTEXT", 1: "FHCRC", 2: "FEXTRA", 3: "FNAME", 4: "FCOMMENT"} def make(data, name=None): buf = io.BytesIO() with gzip.GzipFile(filename=name or "", fileobj=buf, mode="wb", mtime=0) as g: g.write(data) return buf.getvalue() def read_header(raw): if len(raw) < 10: raise ValueError("too short for a gzip header") magic, method, flg, mtime, xfl, os_ = struct.unpack_from("<2sBBIBB", raw, 0) if magic != b"\x1f\x8b": raise ValueError("not gzip") if method != 8: raise ValueError(f"unknown method {method}") if flg & 0b11100000: raise ValueError("reserved flag bits set") flags = [n for bit, n in NAMES.items() if flg & (1 << bit)] if flg & 0b00000100: raise ValueError("FEXTRA not handled here") filename = None if flg & 0b00001000: end = raw.find(b"\x00", 10) if end == -1: raise ValueError("file name is not terminated") filename = raw[10:end].decode("latin-1") return flags, filename plain = make(b"hello") named = make(b"hello", "notes.txt") print(read_header(plain)) print(read_header(named)) print(named[:10].hex(" ")) for label, bad in [ ("magic", b"PK" + named[2:]), ("short", named[:5]), ("reserved", named[:3] + bytes([named[3] | 0x80]) + named[4:]), ("unterminated", named[:10] + b"abc"), ("extra", named[:3] + bytes([named[3] | 4]) + named[4:]), ]: try: read_header(bad) except ValueError as e: print(label, "->", e) print(gzip.decompress(named)) # ([], None) # (['FNAME'], 'notes.txt') # 1f 8b 08 08 00 00 00 00 02 ff # magic -> not gzip # short -> too short for a gzip header # reserved -> reserved flag bits set # unterminated -> file name is not terminated # extra -> FEXTRA not handled here # b'hello' (Run as written; the output matched the comments.) Explanation: The first ten bytes unpack with one format string; the flags byte is then tested bit by bit, with the reserved top three bits rejected first. When the name flag is set, the name is the bytes from offset 10 up to the next zero byte, and a missing zero byte is an error rather than a guess. The extra-data flag would put a length-prefixed block before the name, so this reader refuses it instead of mis-reading the name. The mtime is fixed at 0 so the bytes are the same on every run.