Created
June 14, 2024 10:20
-
-
Save eai04191/c532a47caad96bc348fee9fd08aab2a7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface ZipInfo { | |
compressedSize: number; | |
fileNameLength: number; | |
extraFieldLength: number; | |
fileName: string; | |
files: ZipFile[]; | |
} | |
export interface ZipFile { | |
filename: string; | |
data: Blob; | |
} | |
export async function parseZipFromStream( | |
stream: ReadableStream<Uint8Array>, | |
): Promise<ZipInfo> { | |
const reader = stream.getReader(); | |
let buffer = new Uint8Array(0); | |
// データを必要な長さまで読み込むヘルパー関数 | |
const readIntoBuffer = async (size: number): Promise<void> => { | |
while (buffer.length < size) { | |
const { done, value } = await reader.read(); | |
if (done) throw new Error("Unexpected end of stream"); | |
buffer = concatUint8Arrays(buffer, value); | |
} | |
}; | |
// データを指定の位置から読む関数 | |
const readUint16 = (offset: number): number => { | |
return buffer[offset] | (buffer[offset + 1] << 8); | |
}; | |
const readUint32 = (offset: number): number => { | |
return ( | |
buffer[offset] | | |
(buffer[offset + 1] << 8) | | |
(buffer[offset + 2] << 16) | | |
(buffer[offset + 3] << 24) | |
); | |
}; | |
const readString = (offset: number, length: number): string => { | |
return new TextDecoder().decode( | |
buffer.subarray(offset, offset + length), | |
); | |
}; | |
const zipFile: ZipInfo = { | |
compressedSize: 0, | |
fileNameLength: 0, | |
extraFieldLength: 0, | |
fileName: "", | |
files: [], | |
}; | |
while (true) { | |
await readIntoBuffer(30); // 最小ヘッダーサイズ | |
if (readUint32(0) !== 0x04034b50) break; // ローカルファイルヘッダーのシグネチャ | |
const compressedSize = readUint32(18); | |
const fileNameLength = readUint16(26); | |
const extraFieldLength = readUint16(28); | |
await readIntoBuffer( | |
30 + fileNameLength + extraFieldLength + compressedSize, | |
); | |
const fileName = readString(30, fileNameLength); | |
const headerOffset = 30 + fileNameLength + extraFieldLength; | |
const fileData = buffer.subarray( | |
headerOffset, | |
headerOffset + compressedSize, | |
); | |
buffer = buffer.subarray(headerOffset + compressedSize); // バッファをスライスして次のエントリに対応 | |
const decompressedData = await decompressData(fileData); | |
const fileEntry: ZipFile = { | |
filename: fileName, | |
data: decompressedData, | |
}; | |
zipFile.files.push(fileEntry); | |
zipFile.compressedSize += compressedSize; | |
zipFile.fileNameLength += fileNameLength; | |
zipFile.extraFieldLength += extraFieldLength; | |
} | |
return zipFile; | |
} | |
async function decompressData(data: Uint8Array): Promise<Blob> { | |
const stream = new ReadableStream<Uint8Array>({ | |
start(controller) { | |
controller.enqueue(data); | |
controller.close(); | |
}, | |
}); | |
const decompressionStream = new DecompressionStream("deflate-raw"); | |
const decompressedStream = stream.pipeThrough(decompressionStream); | |
const reader = decompressedStream.getReader(); | |
const chunks: Uint8Array[] = []; | |
while (true) { | |
const { done, value } = await reader.read(); | |
if (done) break; | |
if (value) chunks.push(value); | |
} | |
const decompressedData = concatUint8Arrays(...chunks); | |
return new Blob([decompressedData]); | |
} | |
function concatUint8Arrays(...arrays: Uint8Array[]): Uint8Array { | |
const totalLength = arrays.reduce((acc, arr) => acc + arr.length, 0); | |
const result = new Uint8Array(totalLength); | |
let offset = 0; | |
for (const arr of arrays) { | |
result.set(arr, offset); | |
offset += arr.length; | |
} | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment