from flask import Response import functools import os # 4MiB chunks CHUNK_SIZE = 4_194_304 # Specification de l'entête du fichier # Numéro Nom Début Taille Description # 1 name 0 100 Nom du fichier # 2 mode 100 8 Permissions # 3 uid 108 8 Propriétaire (inutilisé si format étendu) # 4 gid 116 8 Groupe (inutilisé si format étendu) # 5 size 124 12 Taille du fichier en octets. # 6 mtime 136 12 Dernière modification en temps Unix. # 7 chksum 148 8 Somme de contrôle de l'en-tête où ce champ est considéré comme rempli d'espaces (32) # 8 type flag 156 1 Type de fichier # 9 linkname 157 100 Nom du fichier pointé par ce lien symbolique (Si le type indique un lien symbolique) def header_chunk(filename: str, filepath: str) -> bytes: stat = os.stat(filepath) buffer = bytearray() # Field 1: filename on 100 bytes buffer += filename.encode('ascii') buffer += b'\x00' * (100 - len(filename)) # Field 2: mode, on 8 bytes # TODO we put 777 for test buffer += '0000777'.encode('ascii') + b'\x00' # Field 3: owner, we put 1000, default user buffer += '0001000'.encode('ascii') + b'\x00' # Field 4: group, we put 1000 buffer += '0001000'.encode('ascii') + b'\x00' # Field 5: file size in bytes in ascii buffer += str(stat.st_size).rjust(11, '0').encode('ascii') + b'\x00' # Field 6: last modified, zeros for now # buffer += '00000000000'.encode('ascii') + b'\x00' buffer += '1721728914'.rjust(11, '0').encode('ascii') + b'\x00' # Field 7: checksum, we put spaces and we will edit it at the end buffer += ' '.encode('ascii') # Field 8: type flag, 0 because we only have regular files buffer += b'0' # Field 9: linkname, \x00s because we only have regular files buffer += b'\x00' * 100 # POSIX 1003.1-1990: 255 empty bytes buffer += b'\x00' * 255 # Compute the checksum checksum = str(functools.reduce(lambda x, y: x + y, buffer)).rjust(6, '0').encode('ascii') + b'\x00 ' buffer[148:156] = checksum return bytes(buffer) class TarSender: def __init__(self): self.files: dict[str, str] = {} def add_file(self, filename: str, filepath: str): self.files[filename] = filepath def response(self): def generate(): for name, file in self.files.items(): yield header_chunk(name, file) bytes_sent = 0 with open(file, 'rb') as f: while True: bytes = f.read(CHUNK_SIZE) if len(bytes) == 0: break bytes_sent += len(bytes) yield bytes # Because tar use records of 512 bytes, we need to pad the # file with zeroes to fill the last chunk yield b'\x00' * (512 - bytes_sent % 512) return Response( generate(), mimetype='application/x-tar', headers={'Content-Disposition': 'attachment; filename="archive.tar"'} )