diff --git a/archive.py b/archive.py index 4f8e5f4..eaa6448 100644 --- a/archive.py +++ b/archive.py @@ -136,16 +136,16 @@ def zip_local_file_header(filename: str, filepath: str) -> bytes: buffer[12:14] = mtime.day | (mtime.month << 5) | ((mtime.year - 1980) << 9) # Field 6: crc-32 of uncompressed data (buffer[14:18]) - buffer[14:18] = crc32(filepath).to_bytes(4) + buffer[14:18] = crc32(filepath).to_bytes(4, byteorder='little') # Field 7: compressed size (buffer[18:22]) - buffer[18:22] = stat.st_size.to_bytes(4) + buffer[18:22] = stat.st_size.to_bytes(4, byteorder='little') # Field 8: uncompressed size (buffer[22:26]) - buffer[22:26] = stat.st_size.to_bytes(4) + buffer[22:26] = stat.st_size.to_bytes(4, byteorder='little') # Field 9: filename length (buffer[26:28]) - buffer[26:28] = len(filename).to_bytes(4) + buffer[26:28] = len(filename).to_bytes(2, byteorder='little') # Field 10: extra field length (buffer[28:30]) @@ -155,7 +155,7 @@ def zip_local_file_header(filename: str, filepath: str) -> bytes: return buffer -def zip_central_directory_file_header(filename: str, filepath: str) -> bytes: +def zip_central_directory_file_header(filename: str, filepath: str, offset: int) -> bytes: buffer_size = 46 + len(filename) buffer = bytearray(buffer_size) stat = os.stat(filepath) @@ -179,16 +179,16 @@ def zip_central_directory_file_header(filename: str, filepath: str) -> bytes: buffer[14:16] = mtime.day | (mtime.month << 5) | ((mtime.year - 1980) << 9) # Field 6: crc-32 of uncompressed data (buffer[16:20]) - buffer[16:20] = crc32(filepath).to_bytes(4) + buffer[16:20] = crc32(filepath).to_bytes(4, byteorder='little') # Field 7: compressed size (buffer[20:24]) - buffer[20:24] = stat.st_size.to_bytes(4) + buffer[20:24] = stat.st_size.to_bytes(4, byteorder='little') # Field 8: uncompressed size (buffer[24:28]) - buffer[24:28] = stat.st_size.to_bytes(4) + buffer[24:28] = stat.st_size.to_bytes(4, byteorder='little') # Field 9: filename length (buffer[28:30]) - buffer[28:30] = len(filename).to_bytes(4) + buffer[28:30] = len(filename).to_bytes(2, byteorder='little') # Field 10: extra field length (buffer[30:32]) @@ -208,11 +208,45 @@ def zip_central_directory_file_header(filename: str, filepath: str) -> bytes: return buffer +def zip_end_of_central_directory(items_number: int, central_directory_size: int, central_directory_offset: int): + buffer = bytearray(22) + # Field 1: End of central directory signature = 0x06054b50 (buffer[0:4]) + buffer[0:4] = b'\x06\x05\x4b\x50' + + # Field 2: Number of this disk (buffer[4:6]) + + # Field 3: Disk where central directory starts (buffer[6:8]) + + # Field 4: Number of central directory records on this disk (buffer[8:10]) + buffer[8:10] = items_number.to_bytes(2, byteorder='little') + + # Field 5: Total number of central directory records (buffer[10:12]) + buffer[10:12] = items_number.to_bytes(2, byteorder='little') + + # Field 6: Size of central directory in bytes (buffer[12:16]) + buffer[12:16] = central_directory_size.to_bytes(4, byteorder='little') + + # Field 7: Offset of start of central directory (buffer[16:20]) + buffer[16:20] = central_directory_size.to_bytes(4, byteorder='little') + + # Field 8: Comment length (buffer[20:22]) + + # Field 9: Comment (buffer[22:]) + return buffer + + class ZipSender(ArchiveSender): def response(self): def generate(): + local_offsets = dict() + current_byte = 0 + for name, file in self.files.items(): - yield zip_local_file_header(name, file) + local_offsets[name] = current_byte + chunk = zip_local_file_header(name, file) + current_byte += chunk + + yield chunk with open(file, 'rb') as f: while True: @@ -221,8 +255,20 @@ class ZipSender(ArchiveSender): if len(bytes) == 0: break + current_byte += len(bytes) yield bytes + central_directory_size = 0 + centra_directory_offset = current_byte + + for name, file, in self.files.items(): + chunk = zip_central_directory_file_header(name, file, local_offsets[name]) + central_directory_size += len(chunk) + current_byte += len(chunk) + yield chunk + + yield zip_end_of_central_directory(len(self.files.items()), central_directory_size, centra_directory_offset) + return Response( generate(), mimetype='application/zip',