diff --git a/src/getSolidpkg.ts b/src/ssn/getSolidpkg.ts similarity index 83% rename from src/getSolidpkg.ts rename to src/ssn/getSolidpkg.ts index f1f1045..78e1c09 100644 --- a/src/getSolidpkg.ts +++ b/src/ssn/getSolidpkg.ts @@ -1,6 +1,6 @@ -import getUrlContents from './getUrlContents'; -import { Product } from './interfaces/ISettings'; -import verifyProductName from './verifyProductName'; +import getUrlContents from '../getUrlContents'; +import { Product } from '../interfaces/ISettings'; +import verifyProductName from '../verifyProductName'; export default function getSolidpkg(product: Product, from: number, to: number): Promise { //Verify function arguments diff --git a/src/ssn/modifyPassword.ts b/src/ssn/modifyPassword.ts new file mode 100644 index 0000000..0f7e041 --- /dev/null +++ b/src/ssn/modifyPassword.ts @@ -0,0 +1,23 @@ +/** Takes a password as it is included in the extra field, and returns a password that can be used to decode the file. */ +export default function modifyPassword(passwordIn: Uint8Array) { + const passwordLength = passwordIn.byteLength; + const passwordOut = new Uint8Array(passwordLength); + + for (let i = 0; i < passwordLength; i += 1) { + if (passwordIn[i] === 0) { break; } + let curChar = passwordIn[i] + (1 << (i % 32)); + if (curChar > 0x7E) { + if (curChar === 0xFF || curChar === 0x7F) { + curChar = 0x3F; + } else { + curChar = curChar & 0x7F; + } + } + if (curChar < 0x21) { + curChar = (curChar | (1 << ((curChar % 3) + 5))) + 1; + } + passwordOut[i] = curChar; + } + + return passwordOut; +} diff --git a/src/readSolidpkg.ts b/src/ssn/readSolidpkg.ts similarity index 67% rename from src/readSolidpkg.ts rename to src/ssn/readSolidpkg.ts index b9ae32e..717ef91 100644 --- a/src/readSolidpkg.ts +++ b/src/ssn/readSolidpkg.ts @@ -1,9 +1,15 @@ +import { TextDecoder } from 'util'; +import modifyPassword from './modifyPassword'; + const MAGIC_END_OF_CENTRAL_DIR = 0x06054b50; const MAGIC_CENTRAL_DIR = 0x02014b50; const COMPRESSION_DEFLATE = 8; +const Decoder = new TextDecoder('utf-8'); + export default function readSolidpkg(buffer: Buffer) { - const dv = new DataView(buffer.buffer); + const arrayBuffer = buffer.buffer; + const dv = new DataView(arrayBuffer); //--------------- READ END OF CENTRAL DIR --------------- @@ -75,10 +81,37 @@ export default function readSolidpkg(buffer: Buffer) { const fileCommentLength = dv.getUint16(pos, true); pos += 2; pos += 8; //skip disk num start and file attributes const relOffset = dv.getUint32(pos, true); pos += 4; - pos += fileNameLength; //skip file name - //TODO: read password from extra field + const fileName = Decoder.decode(new DataView(arrayBuffer, pos, fileNameLength)); pos += fileNameLength; //skip file name + //read password from extra field + let encodedPassword: Uint8Array | undefined; + while (pos + 4 <= extraFieldLength) { + const fieldId = dv.getUint16(pos, true); pos += 2; + const fieldLength = dv.getUint16(pos, true); pos += 2; + switch (fieldId) { + case 0x8810: { //password + if (fieldLength > 120) { + throw new Error(`Password is too long, it should be 120 characters at most but is ${fieldLength} characters long.`); + } + const passwordLength = fieldLength; + encodedPassword = new Uint8Array(arrayBuffer, pos, fieldLength); + break; + } + case 0x80AE: //unknown, some kind of hash or checksum, ignore it + break; + default: + //unknown field, ignore it + } + pos += fieldLength; + } + if (typeof encodedPassword === 'undefined') { + throw new Error('Could not find password in extra field.'); + } + + const decodedPassword = modifyPassword(encodedPassword); + + //TODO: read encrypted + compressed file //... - return { lastMod1, lastMod2, fileCrc, comprSize, uncomprSize, fileNameLength, extraFieldLength, fileCommentLength, relOffset }; + return { lastMod1, lastMod2, fileCrc, comprSize, uncomprSize, fileNameLength, extraFieldLength, fileCommentLength, relOffset, fileName, encodedPassword, decodedPassword }; }