From 68348b367342dfa0e424b003f2b5ebafebcc3e45 Mon Sep 17 00:00:00 2001 From: C-3PO Date: Thu, 5 Jul 2018 18:41:24 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20end=20of=20file=20reached?= =?UTF-8?q?=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/ISsnFileEntry.ts | 2 +- src/ssn/extractFileStream.ts | 5 +++- src/ssn/getManifest.ts | 2 +- src/ssn/getSolidpkg.ts | 2 +- src/ssn/streams/arrayBufferToStream.ts | 2 +- src/ssn/streams/streamSetMaxLength.ts | 34 ++++++++++++++++++++++++++ 6 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 src/ssn/streams/streamSetMaxLength.ts diff --git a/src/interfaces/ISsnFileEntry.ts b/src/interfaces/ISsnFileEntry.ts index 73672a0..3765efb 100644 --- a/src/interfaces/ISsnFileEntry.ts +++ b/src/interfaces/ISsnFileEntry.ts @@ -24,7 +24,7 @@ interface ISsnFileEntry { name: string; /** Uncompressed size */ size: number; - /** Stored size (size of compressed data + 12 byte encryption header if applicable) */ + /** Stored size (size of compressed data + 12 byte encryption header if applicable). Does not include the local ZIP file header, which is slightly longer than 30 bytes. */ compressedSize: number; /** Decryption keys needed to decrypt the file */ decryptionKeys: [number, number, number] | undefined; diff --git a/src/ssn/extractFileStream.ts b/src/ssn/extractFileStream.ts index 929aa04..53fd088 100644 --- a/src/ssn/extractFileStream.ts +++ b/src/ssn/extractFileStream.ts @@ -4,6 +4,7 @@ import * as stream from 'stream'; import * as zlib from 'zlib'; import { ISsnFileEntry } from '../interfaces/ISsnFileEntry'; import decryptStream from './streams/decryptStream'; +import streamSetMaxLength from './streams/streamSetMaxLength'; /** Extracts the file with the given metadata from the stream. * The stream must already start at the .zip's local file header @@ -32,9 +33,11 @@ export default function extractFileStream(file: ISsnFileEntry, inputStream: stre let curStream = inputStream; + curStream = streamSetMaxLength(curStream, file.compressedSize); + //pipe into decryption if file is encrypted if (file.decryptionKeys !== undefined) { - const decryptTransform = decryptStream([...file.decryptionKeys] as [number, number, number]); + const decryptTransform = decryptStream(file.decryptionKeys); curStream = curStream.pipe(decryptTransform); } diff --git a/src/ssn/getManifest.ts b/src/ssn/getManifest.ts index ec6dc1f..e0e1abb 100644 --- a/src/ssn/getManifest.ts +++ b/src/ssn/getManifest.ts @@ -32,7 +32,7 @@ export default async function getManifest(product: Product): Promise throw new Error(`Expected .patchmanifest to contain a file called manifest.xml but it is called "${firstFile.name}".`); } - const stream = arrayBufferToStream(ssnFile, firstFile.offset, firstFile.compressedSize); + const stream = arrayBufferToStream(ssnFile, firstFile.offset); //Extract manifest.xml file const patchmanifestStream = extractFileStream(firstFile, stream); diff --git a/src/ssn/getSolidpkg.ts b/src/ssn/getSolidpkg.ts index 8c87882..e5cbcd2 100644 --- a/src/ssn/getSolidpkg.ts +++ b/src/ssn/getSolidpkg.ts @@ -37,7 +37,7 @@ export default async function getSolidpkg(product: Product, from: number, to: nu throw new Error(`Expected .solidpkg to contain a file called metafile.solid but it is called "${firstFile.name}".`); } - const stream = arrayBufferToStream(ssnFile, firstFile.offset, firstFile.compressedSize); + const stream = arrayBufferToStream(ssnFile, firstFile.offset); //Extract metafile.solid file const solidFileStream = extractFileStream(firstFile, stream); diff --git a/src/ssn/streams/arrayBufferToStream.ts b/src/ssn/streams/arrayBufferToStream.ts index 5b9cf2b..66a056d 100644 --- a/src/ssn/streams/arrayBufferToStream.ts +++ b/src/ssn/streams/arrayBufferToStream.ts @@ -11,7 +11,7 @@ export default function arrayBufferToStream(arrayBuffer: ArrayBuffer, offset: nu } let position = offset; - const endPosition = (length !== undefined) ? offset + length : arrayBuffer.byteLength; + const endPosition = (length !== undefined) ? (offset + length) : arrayBuffer.byteLength; const outStream = new stream.Readable({ read(size) { const chunkSize = Math.min(size || BUFFER_SIZE, endPosition - position); //TODO: we can probably remove BUFFER_SIZE diff --git a/src/ssn/streams/streamSetMaxLength.ts b/src/ssn/streams/streamSetMaxLength.ts new file mode 100644 index 0000000..3b8eb69 --- /dev/null +++ b/src/ssn/streams/streamSetMaxLength.ts @@ -0,0 +1,34 @@ +import * as stream from 'stream'; + +/** Takes the given ReadableStream and returns a ReadableStream with the same contents but that terminates after the given length. */ +export default function streamSetMaxLength(inputStream: stream.Readable, maxLength: number): stream.Readable { + if (maxLength <= 0) { + throw new RangeError('maxLength is out of bounds.'); + } + + let remaining = maxLength; + + const outStream = new stream.Readable({ + read(size) { + //If no size is provided, just pass through all remaining bytes + if (size === undefined) { + this.push(inputStream.read(remaining)); + remaining = 0; + //End is reached, terminate stream + this.push(null); + } else { + //Otherwise, pass through however many bytes we can + const clampedSize = Math.min(size, remaining); + this.push(inputStream.read(clampedSize)); + remaining -= clampedSize; + + //If end is reached, terminate stream + if (remaining <= 0) { + this.push(null); + } + } + }, + }); + + return outStream; +}