Further work

master
Sven Slootweg 7 years ago
parent 902870ffac
commit 909f20a097

@ -27,23 +27,7 @@ Promise.try(() => {
}).then((rootDirectory) => {
return rootDirectory.getTree();
}).then((tree) => {
function makeArchy(node) {
if (node.type === "directory") {
return {
label: node.filename,
nodes: node.children.map(makeArchy)
}
} else {
return node.filename
}
}
let archyNodes = {
label: "(root)",
nodes: tree.map(makeArchy)
}
console.log(archy(archyNodes));
});
/*

@ -0,0 +1,58 @@
'use strict';
const Promise = require("bluebird");
const isoParser = require("../lib/iso9660");
const yargs = require("yargs");
const fsSeekable = require("../lib/seekable/fs");
const httpSeekable = require("../lib/seekable/http");
const renderVolumeTable = require("../lib/render/volume-table");
const renderFilesystemTree = require("../lib/render/filesystem-tree");
let argv = yargs.argv;
let command, target, subCommands;
if (argv._.length === 1) {
command = "volumes";
target = argv._[0];
} else if (argv._.length > 1) {
[command, target, ...subCommands] = argv._;
} else {
throw new Error("You must specify at least a target file or URL");
}
Promise.try(() => {
if (target.match(/^https?:\/\//)) {
return httpSeekable(target);
} else {
return fsSeekable(target);
}
}).then((seekable) => {
let parser = isoParser(seekable);
if (command === "volumes") {
return Promise.try(() => {
return parser.getUniqueVolumeDescriptors();
}).then((volumeDescriptors) => {
console.log(renderVolumeTable(volumeDescriptors));
});
} else if (command === "tree") {
return Promise.try(() => {
if (argv.volume != null) {
let volumeId = parseInt(argv.volume);
if (isNaN(volumeId)) {
throw new Error("--volume argument must be numeric");
}
return parser.getRootDirectory(volumeId);
} else {
return parser.getRootDirectory();
}
}).then((rootDirectory) => {
return rootDirectory.getTree();
}).then((tree) => {
console.log(renderFilesystemTree(tree));
})
}
});

@ -0,0 +1,21 @@
'use strict';
module.exports = function(rules) {
let fields = rules.map((field, i) => {
if (field != null) {
return {
field: field,
value: Math.pow(2, i)
}
} else {
return null;
}
}).filter((item) => item != null);
return function parseBits(value) {
return fields.reduce((flags, item) => {
flags[item.field] = !!(value & item.value);
return flags;
}, {});
}
}

@ -0,0 +1,36 @@
'use strict';
module.exports = function createBufferReaderFactory(rules) {
return function createBufferReader(buffer) {
let defaultMethods = {
slice: function readSlice(start, length) {
return buffer.slice(start, start + length);
}
}
let customMethods = Object.keys(rules).map((ruleName) => {
let rule = rules[ruleName];
let readerFunction;
return {
key: ruleName,
value: function(start, length) {
let actualLength;
if (rule.length != null) {
actualLength = rule.length;
} else {
actualLength = length;
}
return rule.decode(buffer.slice(start, start + actualLength));
}
}
}).reduce((methods, item) => {
methods[item.key] = item.value;
return methods;
}, {});
return Object.assign(defaultMethods, customMethods);
}
};

@ -0,0 +1,17 @@
'use strict';
const deepEql = require("deep-eql");
module.exports = function deeplyUnique(items) {
return items.reduce((uniques, item) => {
let index = uniques.findIndex((uniqueItem) => {
return (uniqueItem.type === item.type && deepEql(uniqueItem.data, item.data));
});
if (index === -1) {
return uniques.concat([item]);
} else {
return uniques;
}
}, []);
}

@ -1,43 +1,182 @@
'use strict';
const types = require("./types");
module.exports = function createBufferReader(buffer) {
let typeSpecs = {
strA: {},
strD: {},
strFilename: {},
int8: { length: 1 },
sint8: { length: 1 },
int16_LSB: { length: 2 },
int16_MSB: { length: 2 },
int16_LSB_MSB: { length: 4 },
sint16_LSB: { length: 2 },
sint16_MSB: { length: 2 },
sint16_LSB_MSB: { length: 4 },
int32_LSB: { length: 4 },
int32_MSB: { length: 4 },
int32_LSB_MSB: { length: 8 },
sint32_LSB: { length: 4 },
sint32_MSB: { length: 4 },
sint32_LSB_MSB: { length: 8 },
decDatetime: { length: 17 },
directoryDatetime: { length: 7 }
}
const moment = require("moment");
const bufferReaderFactory = require("../buffer-reader");
const decode = require("./decode");
const removeRightPadding = require("../remove-right-padding");
function offsetInMinutes(value) {
/* "Time zone offset from GMT in 15 minute intervals, starting at interval -48 (west) and running up to interval 52 (east).
* So value 0 indicates interval -48 which equals GMT-12 hours, and value 100 indicates interval 52 which equals GMT+13 hours."
*
* ref: http://wiki.osdev.org/ISO_9660#Date.2Ftime_format
*/
return (value - 48) * 15;
}
module.exports = function createReader(buffer, {encoding} = {encoding: "ascii"}) {
let bufferReaderRules = {
/* String types */
strA: {
decode: function parseStrA(buffer) {
return removeRightPadding(decode(buffer, "ascii"));
}
},
strD: {
decode: function parseStrD(buffer) {
return bufferReaderRules.strA.decode(buffer);
}
},
strA1: {
decode: function parseStrA(buffer) {
return removeRightPadding(decode(buffer, encoding));
}
},
strD1: {
decode: function parseStrD(buffer) {
return bufferReaderRules.strA1.decode(buffer);
}
},
strFilename: {
decode: function parseStrFilename(buffer) {
let decodedString = decode(buffer, encoding);
let [filename, version] = decodedString.split(";");
return {filename, version};
}
},
return Object.keys(typeSpecs).reduce((methods, type) => {
let options = typeSpecs[type];
/* 8-bit integers */
int8: {
length: 1,
decode: function parseInt8(buffer) {
return buffer.readUInt8(0);
}
},
sint8: {
length: 1,
decode: function parseSInt8(buffer) {
return buffer.readInt8(0);
}
},
if (options.length == null) {
methods[type] = function(offset, length) {
return types[type](buffer.slice(offset, offset + length));
/* 16-bit integers */
int16_LSB: {
length: 2,
decode: function parseInt16_LSB(buffer) {
return buffer.readUInt16LE(0);
}
},
int16_MSB: {
length: 2,
decode: function parseInt16_LSB(buffer) {
return buffer.readUInt16BE(0);
}
},
int16_LSB_MSB: {
length: 4,
decode: function parseInt16_LSB(buffer) {
return bufferReaderRules.int16_LSB.decode(buffer.slice(0, 2));
}
},
sint16_LSB: {
length: 2,
decode: function parseSInt16_LSB(buffer) {
return buffer.readInt16LE(0);
}
} else {
methods[type] = function(offset) {
return types[type](buffer.slice(offset, offset + options.length));
},
sint16_MSB: {
length: 2,
decode: function parseSInt16_LSB(buffer) {
return buffer.readInt16BE(0);
}
},
sint16_LSB_MSB: {
length: 4,
decode: function parseSInt16_LSB(buffer) {
return bufferReaderRules.sint16_LSB.decode(buffer.slice(0, 2));
}
},
/* 32-bit integers */
int32_LSB: {
length: 4,
decode: function parseInt32_LSB(buffer) {
return buffer.readUInt32LE(0);
}
},
int32_MSB: {
length: 4,
decode: function parseInt32_LSB(buffer) {
return buffer.readUInt32BE(0);
}
},
int32_LSB_MSB: {
length: 8,
decode: function parseInt32_LSB(buffer) {
return bufferReaderRules.int32_LSB.decode(buffer.slice(0, 4));
}
},
sint32_LSB: {
length: 4,
decode: function parseSInt32_LSB(buffer) {
return buffer.readInt32LE(0);
}
},
sint32_MSB: {
length: 4,
decode: function parseSInt32_LSB(buffer) {
return buffer.readInt32BE(0);
}
},
sint32_LSB_MSB: {
length: 8,
decode: function parseSInt32_LSB(buffer) {
return bufferReaderRules.sint32_LSB.decode(buffer.slice(0, 4));
}
},
/* Date/time */
decDatetime: {
length: 17,
decode: function parseDecDatetime(buffer) {
let year = parseInt(bufferReaderRules.strD.decode(buffer.slice(0, 4)));
let month = parseInt(bufferReaderRules.strD.decode(buffer.slice(4, 6))) - 1; // "Note that like moment(Array) and new Date(year, month, date), months are 0 indexed."
let day = parseInt(bufferReaderRules.strD.decode(buffer.slice(6, 8)));
let hour = parseInt(bufferReaderRules.strD.decode(buffer.slice(8, 10)));
let minute = parseInt(bufferReaderRules.strD.decode(buffer.slice(10, 12)));
let second = parseInt(bufferReaderRules.strD.decode(buffer.slice(12, 14)));
let centisecond = parseInt(bufferReaderRules.strD.decode(buffer.slice(14, 16)));
let timezoneOffset = bufferReaderRules.int8.decode(buffer.slice(16, 17));
if (year === 0 && month === 0 && day === 0 && hour === 0 && minute === 0 && second === 0 && centisecond === 0 && timezoneOffset === 0) {
return null;
} else {
return moment({
year, month, day, hour, minute, second,
millisecond: centisecond * 10,
}).utcOffset(offsetInMinutes(timezoneOffset));
}
}
},
directoryDatetime: {
length: 7,
decode: function parseDirectoryDatetime(buffer) {
return moment({
year: 1900 + bufferReaderRules.int8.decode(buffer.slice(0, 0 + 1)),
month: bufferReaderRules.int8.decode(buffer.slice(1, 1 + 1)) - 1,
day: bufferReaderRules.int8.decode(buffer.slice(2, 2 + 1)),
hour: bufferReaderRules.int8.decode(buffer.slice(3, 3 + 1)),
minute: bufferReaderRules.int8.decode(buffer.slice(4, 4 + 1)),
second: bufferReaderRules.int8.decode(buffer.slice(5, 5 + 1)),
}).utcOffset(offsetInMinutes(bufferReaderRules.int8.decode(buffer.slice(6, 6 + 1))));
}
}
}
return methods;
}, {});
return bufferReaderFactory(bufferReaderRules)(buffer);
};

@ -0,0 +1,19 @@
'use strict';
const iconvLite = require("iconv-lite");
let encodingMap = {
"ascii": "ascii",
/* The following are strictly not the same thing, but there's backwards compatibility. */
"ucs2-level1": "utf16-be",
"ucs2-level2": "utf16-be",
"ucs2-level3": "utf16-be"
}
module.exports = function decode(buffer, encoding) {
if (encodingMap[encoding] == null) {
throw new Error(`No such encoding: ${encoding}`);
}
return iconvLite.decode(buffer, encodingMap[encoding]);
};

@ -3,9 +3,13 @@
const Promise = require("bluebird");
const streamToPromise = require("stream-to-promise");
const memoizee = require("memoizee");
const promiseWhile = require("promise-while-loop");
const parseVolumeDescriptor = require("./parse/volume-descriptor");
const parsePathTable = require("./parse/path-table");
const deeplyUnique = require("../deeply-unique");
const readVolumeDescriptors = require("./read/volume-descriptors");
const readPathTable = require("./read/path-table");
let imageOffset = 32 * 1024; // first 32KB are system area, ref http://wiki.osdev.org/ISO_9660#System_Area
@ -23,47 +27,75 @@ module.exports = function createImage(seekable) {
return streamToPromise(stream);
});
},
readSectors: function readSectors(firstSector, sectorCount) {
return Promise.try(() => {
return this.getSectorSize();
}).then((sectorSize) => {
return this.readRange(firstSector * sectorSize, (firstSector + sectorCount) * sectorSize - 1);
});
},
getPrimaryVolumeDescriptorBuffer: memoizee(function getPrimaryVolumeDescriptorBuffer() {
return this.readRange(imageOffset, imageOffset + 2047);
getVolumeDescriptors: memoizee(function getVolumeDescriptors() {
return readVolumeDescriptors(this);
}),
getPrimaryVolumeDescriptor: function getPrimaryVolumeDescriptor() {
getUniqueVolumeDescriptors: function getUniqueVolumeDescriptors() {
return Promise.try(() => {
return this.getPrimaryVolumeDescriptorBuffer();
}).then((buffer) => {
return parseVolumeDescriptor(buffer);
return this.getVolumeDescriptors();
}).then((descriptors) => {
return deeplyUnique(descriptors);
});
},
getPrimaryVolumeDescriptor: function getPrimaryVolumeDescriptor() {
return Promise.try(() => {
return this.getVolumeDescriptors();
}).then((descriptors) => {
let primaryVolumeDescriptor = descriptors.find((descriptor) => descriptor.type === "primary");
getSectorOffset: function getSectorOffset(sector) {
if (primaryVolumeDescriptor != null) {
return primaryVolumeDescriptor;
} else {
throw new Error("No primary volume descriptor could be found");
}
})
},
getSectorSize: function getSectorSize() {
return Promise.try(() => {
return this.getPrimaryVolumeDescriptor();
}).then((primaryVolumeDescriptor) => {
return primaryVolumeDescriptor.data.sectorSize * sector;
return primaryVolumeDescriptor.data.sectorSize;
});
},
getPathTable: function getPathTable() {
getSectorOffset: function getSectorOffset(sector) { // FIXME: Remove?
return Promise.try(() => {
return this.getPrimaryVolumeDescriptor();
}).then((primaryVolumeDescriptor) => {
return Promise.try(() => {
let start = primaryVolumeDescriptor.data.pathTableLocationL * primaryVolumeDescriptor.data.sectorSize;
let end = start + primaryVolumeDescriptor.data.pathTableSize - 1;
return this.readRange(start, end);
}).then((buffer) => {
return parsePathTable(buffer, primaryVolumeDescriptor.data.sectorSize);
});
return this.getSectorSize();
}).then((sectorSize) => {
return sectorSize * sector;
});
},
getRootDirectory: function getRootDirectory() {
getPathTable: function getPathTable() {
return readPathTable(this);
},
getRootDirectory: memoizee(function getRootDirectory(volumeIndex) {
return Promise.try(() => {
return this.getPrimaryVolumeDescriptor();
}).then((primaryVolumeDescriptor) => {
return createDirectory(primaryVolumeDescriptor.data.rootDirectory);
if (volumeIndex == null) {
return this.getPrimaryVolumeDescriptor();
} else {
return Promise.try(() => {
return this.getVolumeDescriptors();
}).then((descriptors) => {
if (descriptors[volumeIndex] == null) {
throw new Error("No volume descriptor with that ID exists");
} else {
return descriptors[volumeIndex];
}
});
}
}).then((volumeDescriptor) => {
return createDirectory(volumeDescriptor.data.rootDirectory);
});
}
})
}
const createDirectory = require("./object/directory")(image);

@ -4,11 +4,20 @@ const Promise = require("bluebird");
const memoizee = require("memoizee");
const debug = require("debug")("iso9660:object:directory");
const parseDirectoryRecord = require("../parse/directory-record");
const readExtendedAttributeRecord = require("../read/extended-attribute-record");
const readDirectoryExtent = require("../read/directory-extent");
module.exports = function(image) {
const createFile = require("./file")(image);
function getExtendedAttributeRecord(extentLocation, extendedAttributeRecordLength) {
if (extendedAttributeRecordLength === 0) {
return {};
} else {
return readExtendedAttributeRecord(extentLocation, extendedAttributeRecordLength);
}
}
return function createDirectory(directoryRecord, parent) {
debug(`Creating new directory object for ${directoryRecord.identifier.filename}`);
@ -18,52 +27,32 @@ module.exports = function(image) {
version: directoryRecord.identifier.version,
parent: parent,
recordingDate: directoryRecord.recordingDate,
encoding: directoryRecord.encoding,
record: directoryRecord,
getChildren: memoizee(function getChildren() {
return Promise.try(() => {
function getDirectoryContents() {
return Promise.try(() => {
return image.getSectorOffset(directoryRecord.extentLocation);
}).then((sectorOffset) => {
return image.readRange(sectorOffset, sectorOffset + directoryRecord.extentSize - 1);
});
}
return Promise.all([
image.getPrimaryVolumeDescriptor(),
getDirectoryContents()
]);
}).spread((primaryVolumeDescriptor, directoryContents) => {
const roundToNextSector = require("../round-to-next-sector")(primaryVolumeDescriptor.data.sectorSize);
let pos = 0;
let records = [];
return readDirectoryExtent(image, directoryRecord.extentLocation, directoryRecord.extentSize, directoryRecord.encoding);
}).then((directoryRecords) => {
/* The first two records are . and .. respectively, so we'll strip these off. */
let realDirectoryRecords = directoryRecords.slice(2);
while (pos < directoryContents.length) {
let recordLength = directoryContents.readUInt8(pos);
if (recordLength === 0) {
/* We ran out of records for this sector, skip to the next. */
pos = roundToNextSector(pos);
return Promise.map(realDirectoryRecords, (directoryRecord) => {
if (directoryRecord.fileFlags.directory) {
debug(`Found directory: ${directoryRecord.identifier.filename}`);
return createDirectory(directoryRecord, this);
} else {
let directoryRecord = parseDirectoryRecord(directoryContents.slice(pos, pos + recordLength));
records.push(directoryRecord);
pos += recordLength;
}
}
return records.slice(2).map((record) => {
if (record.fileFlags.directory) {
debug(`Found directory: ${record.identifier.filename}`);
debug(`Found file: ${directoryRecord.identifier.filename}`);
return createDirectory(record, this);
} else {
debug(`Found file: ${record.identifier.filename}`);
return createFile(record, this);
return Promise.try(() => {
return getExtendedAttributeRecord(directoryRecord.extentLocation, directoryRecord.extendedAttributeRecordLength);
}).then((extendedAttributeRecord) => {
directoryRecord.extendedAttributeRecord = extendedAttributeRecord;
return createFile(directoryRecord, this);
});
}
});
});
})
}),
getTree: function getTree() {
return Promise.try(() => {

@ -3,12 +3,16 @@
const createBufferReader = require("../../buffer-reader");
const parseDirectoryRecord = require("../directory-record");
module.exports = function parsePrimaryVolumeDescriptor(data) {
let bufferReader = createBufferReader(data);
module.exports = function parsePrimaryVolumeDescriptor(data, encoding = "ascii") {
let bufferReader = createBufferReader(data, {
encoding: encoding
});
/* NOTE: Technically, the spec states that we should use strA and strD in a primary volume descriptor, and not strA1 and strD1. However, using the encoding-specific versions makes it easier to base the supplementary volume descriptor parser on this one, and since this parser defaults to an 'ascii' encoding anyway, it doesn't matter from a functional perspective. */
return {
systemIdentifier: bufferReader.strA(1, 32),
volumeIdentifier: bufferReader.strD(33, 32),
systemIdentifier: bufferReader.strA1(1, 32),
volumeIdentifier: bufferReader.strD1(33, 32),
sectorCount: bufferReader.int32_LSB_MSB(73),
setSize: bufferReader.int16_LSB_MSB(113),
sequenceNumber: bufferReader.int16_LSB_MSB(117),
@ -18,13 +22,14 @@ module.exports = function parsePrimaryVolumeDescriptor(data) {
optionalPathTableLocationL: bufferReader.int32_LSB(137), // FIXME: Pointer? (location is expressed in 'sectors')
pathTableLocationM: bufferReader.int32_MSB(141), // FIXME: Pointer? (location is expressed in 'sectors')
optionalPathTableLocationM: bufferReader.int32_MSB(145), // FIXME: Pointer? (location is expressed in 'sectors')
rootDirectory: parseDirectoryRecord(data.slice(149, 149 + 34)),
publisherIdentifier: bufferReader.strA(311, 128), // FIXME: null for unspecified & extended publisher information
dataPreparerIdentifier: bufferReader.strA(439, 128), // FIXME: null for unspecified & extended publisher information
applicationIdentifier: bufferReader.strA(567, 128), // FIXME: null for unspecified & extended publisher information
copyrightFile: bufferReader.strD(695, 38), // FIXME: seek for file, optionally?
abstractFile: bufferReader.strD(733, 36), // FIXME: seek for file, optionally?
bibliographicFile: bufferReader.strD(769, 37), // FIXME: seek for file, optionally?
rootDirectory: parseDirectoryRecord(bufferReader.slice(149, 34), {encoding: encoding}),
volumeSetIdentifier: bufferReader.strD1(183, 128),
publisherIdentifier: bufferReader.strA1(311, 128), // FIXME: null for unspecified & extended publisher information
dataPreparerIdentifier: bufferReader.strA1(439, 128), // FIXME: null for unspecified & extended publisher information
applicationIdentifier: bufferReader.strA1(567, 128), // FIXME: null for unspecified & extended publisher information
copyrightFile: bufferReader.strD1(695, 36), // FIXME: seek for file, optionally?
abstractFile: bufferReader.strD1(732, 36), // FIXME: seek for file, optionally?
bibliographicFile: bufferReader.strD1(769, 36), // FIXME: seek for file, optionally?
creationDate: bufferReader.decDatetime(806),
modificationDate: bufferReader.decDatetime(823),
expirationDate: bufferReader.decDatetime(840),

@ -1,5 +1,38 @@
'use strict';
const createBufferReader = require("../../buffer-reader");
const createBitParser = require("../../../bit-parser");
const parsePrimaryVolumeDescriptor = require("./primary-volume");
const parseEscapeSequences = require("../../parse/escape-sequences");
/* Supplementary volume flags: (ref http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf)
* Bit Description
* 0 If set, the Escape Sequences field specifies at least one escape sequence not registered according to ISO 2375.
* 1 - 7 Reserved
*/
let parseVolumeFlags = createBitParser([
"hasNonISO2375EscapeSequences",
null,
null,
null,
null,
null,
null,
null
]);
module.exports = function parseSupplementaryVolumeDescriptor(data) {
throw new Error("Not implemented yet");
let bufferReader = createBufferReader(data);
let escapeSequences = data.slice(81, 113);
let encodings = parseEscapeSequences(escapeSequences);
/* The format of a primary and supplementary volume descriptor are mostly the same - the only real difference is that a supplementary volume descriptor will also have a volumeFlags and escapeSequences field. We can therefore just parse this volume descriptor like a primary volume descriptor, and then merge in the additional fields. */
return Object.assign(parsePrimaryVolumeDescriptor(data, encodings[0]), {
volumeFlags: parseVolumeFlags(bufferReader.int8(0)),
escapeSequences: escapeSequences,
encodings: encodings
});
};

@ -2,36 +2,39 @@
const createBufferReader = require("../buffer-reader");
const roundEven = require("../../round-even");
const createBitParser = require("../../bit-parser");
const decode = require("../decode");
function parseFileFlags(value) {
/* File flags: (ref http://wiki.osdev.org/ISO_9660#Directories)
* Bit Description
* 0 If set, the existence of this file need not be made known to the user (basically a 'hidden' flag.
* 1 If set, this record describes a directory (in other words, it is a subdirectory extent).
* 2 If set, this file is an "Associated File".
* 3 If set, the extended attribute record contains information about the format of this file.
* 4 If set, owner and group permissions are set in the extended attribute record.
* 5 & 6 Reserved
* 7 If set, this is not the final directory record for this file (for files spanning several extents, for example files over 4GiB long.
*/
/* File flags: (ref http://wiki.osdev.org/ISO_9660#Directories)
* Bit Description
* 0 If set, the existence of this file need not be made known to the user (basically a 'hidden' flag.
* 1 If set, this record describes a directory (in other words, it is a subdirectory extent).
* 2 If set, this file is an "Associated File".
* 3 If set, the extended attribute record contains information about the format of this file.
* 4 If set, owner and group permissions are set in the extended attribute record.
* 5 & 6 Reserved
* 7 If set, this is not the final directory record for this file (for files spanning several extents, for example files over 4GiB long.
*/
return {
hidden: !!(value & 1),
directory: !!(value & 2),
associated: !!(value & 4),
inEAR: !!(value & 8),
permissionsInEAR: !!(value & 16),
moreRecordsFollowing: !!(value & 128)
}
}
let parseFileFlags = createBitParser([
"hidden",
"directory",
"associated",
"inEAR",
"permissionsInEAR",
null,
null,
"moreRecordsFollowing"
]);
module.exports = function parseDirectoryRecord(data) {
let bufferReader = createBufferReader(data);
module.exports = function parseDirectoryRecord(data, {encoding} = {encoding: "ascii"}) {
let bufferReader = createBufferReader(data, {encoding: encoding});
let identifierLength = bufferReader.int8(32);
let identifierEnd = roundEven(32 + identifierLength);
return {
encoding: encoding,
recordLength: bufferReader.int8(0),
extendedAttributeRecordLength: bufferReader.int8(1),
extentLocation: bufferReader.int32_LSB_MSB(2),

@ -0,0 +1,41 @@
'use strict';
const renderBytes = require("../../render/bytes");
let sequenceMap = {
"ucs2-level1": Buffer.from([0x25, 0x2F, 0x40]),
"ucs2-level2": Buffer.from([0x25, 0x2F, 0x43]),
"ucs2-level3": Buffer.from([0x25, 0x2F, 0x45])
}
module.exports = function parseEscapeSequences(escapeSequences) {
let pos = 0;
let finished = false;
let encodings = [];
while (finished === false) {
if (escapeSequences.readUInt8(pos) === 0) {
/* We've run out of escape sequences to parse */
break;
}
/* We only check for 3-byte-long escape sequences, for now... */
let slice3 = escapeSequences.slice(pos, pos + 3);
let encoding = Object.keys(sequenceMap).find((encoding) => sequenceMap[encoding].equals(slice3));
if (encoding == null) {
throw new Error(`Encountered unrecognized escape sequence; remaining bytes to parse: ${renderBytes(escapeSequences.slice(pos))}`);
} else {
pos += sequenceMap[encoding].length;
encodings.push(encoding);
}
}
if (encodings.length === 0) {
return ["ascii"];
} else {
return encodings;
}
};

@ -0,0 +1,80 @@
'use strict';
const createBufferReader = require("../buffer-reader");
const createBitParser = require("../../bit-parser");
const decode = require("../decode");
const parseEscapeSequences = require("./escape-sequences");
let parsePermissions = createBitParser([
"groupOwnerReadable",
null,
"groupOwnerExecutable",
null,
"ownerReadable",
null,
"ownerExecutable",
null,
"groupReadable",
null,
"groupExecutable",
null,
"worldReadable",
null,
"worldExecutable",
null
], {inverse: [0, 2, 4, 6, 8, 10, 12, 14]})
function parseRecordFormat(value) {
switch (value) {
case 0:
return "unspecified";
case 1:
return "fixedLength";
case 2:
return "variableLengthLSB";
case 3:
return "variableLengthMSB";
default:
throw new Error(`Unknown record format encountered: ${value}`);
}
}
function parseRecordAttributes(value) {
switch (value) {
case 0:
return "lineFeedCarriageReturn";
case 1:
return "iso1539";
case 2:
return "containedInRecord";
default:
throw new Error(`Unknown record attributes encountered: ${value}`);
}
}
module.exports = function parseExtendedAttributeRecord(data, {encoding} = {encoding: "ascii"}) {
let bufferReader = createBufferReader(data, {encoding: encoding});
let applicationUseLength = bufferReader.int16_LSB_MSB(246);
let escapeSequenceLength = bufferReader.int8(181);
return {
ownerIdentification: bufferReader.int16_LSB_MSB(0),
groupIdentification: bufferReader.int16_LSB_MSB(4),
permissions: parsePermissions(bufferReader.int16_MSB(8)),
creationDate: bufferReader.decDatetime(10),
modificationDate: bufferReader.decDatetime(27),
expirationDate: bufferReader.decDatetime(44),
effectiveDate: bufferReader.decDatetime(61),
recordFormat: parseRecordFormat(bufferReader.int8(78)),
recordAttributes: parseRecordAttributes(bufferReader.int8(79)),
recordLength: bufferReader.int16_LSB_MSB(80),
systemIdentifier: bufferReader.strA1(84, 32),
systemUse: bufferReader.slice(116, 64),
earVersion: bufferReader.int8(180),
escapeSequenceLength: escapeSequenceLength,
applicationUseLength: applicationUseLength,
applicationUse: bufferReader.slice(250, applicationUseLength),
escapeSequences: parseEscapeSequences(bufferReader.slice(250 + applicationUseLength, escapeSequenceLength))
}
};

@ -0,0 +1,40 @@
'use strict';
const Promise = require("bluebird");
const parseDirectoryRecord = require("../parse/directory-record");
module.exports = function readDirectoryExtent(image, sector, length, encoding) {
return Promise.try(() => {
return image.getSectorSize();
}).then((sectorSize) => {
const roundToNextSector = require("../round-to-next-sector")(sectorSize);
function splitRecordBuffers(buffer) {
let pos = 0;
let recordBuffers = [];
while (pos < buffer.length) {
let recordLength = buffer.readUInt8(pos);
if (recordLength === 0) {
/* We ran out of records for this sector, skip to the next. */
pos = roundToNextSector(pos);
} else {
let directoryRecord = buffer.slice(pos, pos + recordLength);
recordBuffers.push(directoryRecord);
pos += recordLength;
}
}
return recordBuffers;
}
return Promise.try(() => {
let sectorOffset = sector * sectorSize;
return image.readRange(sectorOffset, sectorOffset + length - 1);
}).then((buffer) => {
let recordBuffers = splitRecordBuffers(buffer);
})
})
};

@ -0,0 +1,13 @@
'use strict';
const Promise = require("bluebird");
const parseExtendedAttributeRecord = require("../parse/extended-attribute-record");
module.exports = function readExtendedAttributeRecord(image, sector, sectorCount) {
return Promise.try(() => {
return image.readSectors(sector, sectorCount);
}).then((buffer) => {
return parseExtendedAttributeRecord(buffer);
});
};

@ -0,0 +1,20 @@
'use strict';
const Promise = require("bluebird");
const parsePathTable = require("../parse/path-table");
module.exports = function readPathTable(image) {
return Promise.try(() => {
return image.getPrimaryVolumeDescriptor();
}).then((primaryVolumeDescriptor) => {
return Promise.try(() => {
let start = primaryVolumeDescriptor.data.pathTableLocationL * primaryVolumeDescriptor.data.sectorSize;
let end = start + primaryVolumeDescriptor.data.pathTableSize - 1;
return image.readRange(start, end);
}).then((buffer) => {
return parsePathTable(buffer, primaryVolumeDescriptor.data.sectorSize);
});
});
}

@ -0,0 +1,34 @@
'use strict';
const Promise = require("bluebird");
const promiseWhile = require("promise-while-loop");
const parseVolumeDescriptor = require("../parse/volume-descriptor");
module.exports = function readVolumeDescriptors(image) {
return Promise.try(() => {
let encounteredTerminator = false;
let currentSector = 16;
return promiseWhile(() => encounteredTerminator === false, (lastResult) => {
return Promise.try(() => {
let offset = currentSector * 2048; // Always assume a sector size of 2048 for volume descriptors
return image.readRange(offset, offset + 2047);
}).then((buffer) => {
let descriptor = parseVolumeDescriptor(buffer);
if (descriptor.type === "terminator") {
encounteredTerminator = true;
}
currentSector += 1;
return descriptor;
});
});
}).map((volumeDescriptor, i) => {
return Object.assign({
volumeDescriptorIndex: i
}, volumeDescriptor);
});
};

@ -1,130 +0,0 @@
'use strict';
const moment = require("moment");
const removeRightPadding = require("../remove-right-padding");
/* Basic ISO 9660 types: (ref http://wiki.osdev.org/ISO_9660#Numerical_formats)
* int8 Unsigned 8-bit integer.
* sint8 Signed 8-bit integer.
* int16_LSB Little-endian encoded unsigned 16-bit integer.
* int16_MSB Big-endian encoded unsigned 16-bit integer.
* int16_LSB-MSB Little-endian followed by big-endian encoded unsigned 16-bit integer.
* sint16_LSB Little-endian encoded signed 16-bit integer.
* sint16_MSB Big-endian encoded signed 16-bit integer.
* sint16_LSB-MSB Little-endian followed by big-endian encoded signed 16-bit integer.
* int32_LSB Little-endian encoded unsigned 32-bit integer.
* int32_MSB Big-endian encoded unsigned 32-bit integer.
* int32_LSB-MSB Little-endian followed by big-endian encoded unsigned 32-bit integer.
* sint32_LSB Little-endian encoded signed 32-bit integer.
* sint32_MSB Big-endian encoded signed 32-bit integer.
* sint32_LSB-MSB Little-endian followed by big-endian encoded signed 32-bit integer.
*
* "Where a both-endian format is present, the x86 architecture makes use of the first little-endian sequence and ignores the big-endian sequence."
*/
function offsetInMinutes(value) {
/* "Time zone offset from GMT in 15 minute intervals, starting at interval -48 (west) and running up to interval 52 (east).
* So value 0 indicates interval -48 which equals GMT-12 hours, and value 100 indicates interval 52 which equals GMT+13 hours."
*
* ref: http://wiki.osdev.org/ISO_9660#Date.2Ftime_format
*/
return (value - 48) * 15;
}
module.exports = {
/* String types */
strA: function parseStrA(buffer) {
return removeRightPadding(buffer.toString("ascii"));
},
strD: function parseStrD(buffer) {
return this.strA(buffer);
},
strFilename: function parseStrFilename(buffer) {
let str = this.strA(buffer);
let [filename, version] = str.split(";");
return {filename, version};
},
/* 8-bit integers */
int8: function parseInt8(buffer) {
return buffer.readUInt8(0);
},
sint8: function parseSInt8(buffer) {
return buffer.readInt8(0);
},
/* 16-bit integers */
int16_LSB: function parseInt16_LSB(buffer) {
return buffer.readUInt16LE(0);
},
int16_MSB: function parseInt16_LSB(buffer) {
return buffer.readUInt16BE(0);
},
int16_LSB_MSB: function parseInt16_LSB(buffer) {
return this.int16_LSB(buffer.slice(0, 2));
},
sint16_LSB: function parseSInt16_LSB(buffer) {
return buffer.readInt16LE(0);
},
sint16_MSB: function parseSInt16_LSB(buffer) {
return buffer.readInt16BE(0);
},
sint16_LSB_MSB: function parseSInt16_LSB(buffer) {
return this.sint16_LSB(buffer.slice(0, 2));
},
/* 32-bit integers */
int32_LSB: function parseInt32_LSB(buffer) {
return buffer.readUInt32LE(0);
},
int32_MSB: function parseInt32_LSB(buffer) {
return buffer.readUInt32BE(0);
},
int32_LSB_MSB: function parseInt32_LSB(buffer) {
return this.int32_LSB(buffer.slice(0, 4));
},
sint32_LSB: function parseSInt32_LSB(buffer) {
return buffer.readInt32LE(0);
},
sint32_MSB: function parseSInt32_LSB(buffer) {
return buffer.readInt32BE(0);
},
sint32_LSB_MSB: function parseSInt32_LSB(buffer) {
return this.sint32_LSB(buffer.slice(0, 4));
},
/* Date/time */
decDatetime: function parseDecDatetime(buffer) {
let year = parseInt(this.strD(buffer.slice(0, 4)));
let month = parseInt(this.strD(buffer.slice(4, 6))) - 1; // "Note that like moment(Array) and new Date(year, month, date), months are 0 indexed."
let day = parseInt(this.strD(buffer.slice(6, 8)));
let hour = parseInt(this.strD(buffer.slice(8, 10)));
let minute = parseInt(this.strD(buffer.slice(10, 12)));
let second = parseInt(this.strD(buffer.slice(12, 14)));
let centisecond = parseInt(this.strD(buffer.slice(14, 16)));
let timezoneOffset = this.int8(buffer.slice(16, 17));
if (year === 0 && month === 0 && day === 0 && hour === 0 && minute === 0 && second === 0 && centisecond === 0 && timezoneOffset === 0) {
return null;
} else {
return moment({
year, month, day, hour, minute, second,
millisecond: centisecond * 10,
}).utcOffset(offsetInMinutes(timezoneOffset));
}
},
directoryDatetime: function parseDirectoryDatetime(buffer) {
return moment({
year: 1900 + this.int8(buffer.slice(0, 0 + 1)),
month: this.int8(buffer.slice(1, 1 + 1)) - 1,
day: this.int8(buffer.slice(2, 2 + 1)),
hour: this.int8(buffer.slice(3, 3 + 1)),
minute: this.int8(buffer.slice(4, 4 + 1)),
second: this.int8(buffer.slice(5, 5 + 1)),
}).utcOffset(offsetInMinutes(this.int8(buffer.slice(6, 6 + 1))));
}
};

@ -1,5 +1,5 @@
'use strict';
module.exports = function removeRightPadding(string) {
return string.replace(/[ ]+$/, "");
};
return string.replace(/[ \u0000]+$/, "");
}

@ -0,0 +1,5 @@
'use strict';
module.exports = function renderBytes(buffer) {
return Array.from(buffer.values()).map((byte) => byte.toString(16)).join(" ");
};

@ -0,0 +1,24 @@
'use strict';
const archy = require("archy");
const util = require("util");
function makeArchyNode(node) {
if (node.type === "directory") {
return {
label: node.filename,
nodes: node.children.map(makeArchyNode)
}
} else {
return node.filename
}
}
module.exports = function renderFilesystemTree(rootDirectory) {
let archyTree = {
label: "(root)",
nodes: rootDirectory.map(makeArchyNode)
}
return archy(archyTree);
};

@ -0,0 +1,72 @@
'use strict';
const dotty = require("dotty");
const asciiDataTable = require("ascii-data-table");
const defaultValue = require("default-value");
const buffertrim = require("buffertrim");
const renderBytes = require("./bytes");
function formatCreationDate(creationDate) {
if (creationDate != null) {
return creationDate.format("llll");
} else {
return "";
}
}
function formatVolumePosition(sequenceNumber, setSize) {
if (sequenceNumber != null) {
return `${sequenceNumber}/${setSize}`;
} else {
return "";
}
}
function formatEncodings(encodings) {
if (encodings != null) {
return encodings.join(", ");
} else {
return "";
}
}
let headers = [
"ID",
"Type",
"System",
"Volume",
"Volume identifier",
"Encoding",
"Extent",
"Creation date"
];
module.exports = function renderVolumeTable(volumeDescriptors) {
let tableData = volumeDescriptors.map((descriptor, i) => {
let volumeIndex = descriptor.volumeDescriptorIndex;
let volumeType = descriptor.type;
let systemIdentifier = defaultValue(descriptor.data.systemIdentifier, "");
let volumeIdentifier = defaultValue(descriptor.data.volumeIdentifier, "");
let extentSector = defaultValue(dotty.get(descriptor, "data.rootDirectory.extentLocation"), "");
let creationDate = formatCreationDate(descriptor.data.creationDate);
let volumePosition = formatVolumePosition(descriptor.data.sequenceNumber, descriptor.data.setSize);
let encodings = formatEncodings(descriptor.data.encodings);
return [
volumeIndex,
volumeType,
systemIdentifier,
volumePosition,
volumeIdentifier,
encodings,
extentSector,
creationDate
];
});
let tableDataWithHeaders = [headers].concat(tableData);
return asciiDataTable.default.run(tableDataWithHeaders);
};

@ -14,12 +14,21 @@
"license": "WTFPL",
"dependencies": {
"archy": "^1.0.0",
"ascii-data-table": "^1.3.3",
"bhttp": "^1.2.4",
"bluebird": "^3.4.6",
"buffertrim": "^1.0.0",
"debug": "^2.2.0",
"deep-eql": "^1.0.3",
"default-value": "^1.0.0",
"dotty": "0.0.2",
"iconv-lite": "^0.4.13",
"memoizee": "^0.4.1",
"moment": "^2.15.1",
"promise-while-loop": "^1.0.1",
"safe-buffer": "^5.0.1",
"stream-to-promise": "^2.2.0"
"stream-to-promise": "^2.2.0",
"table": "^3.8.3",
"yargs": "^6.3.0"
}
}

@ -0,0 +1,13 @@
'use strict';
const table = require("table");
table.default([ [ 0, 'PC', 'SC2KSEPC', '1/1', 24, 'Mon, Apr 13, 1998 8:28 PM' ],
[ 1, 'PC', 'SC2KSEPC', '1/1', 24, 'Mon, Apr 13, 1998 8:28 PM' ],
[ 2,
'\u0000P\u0000C\u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000',
'\u0000S\u0000C\u00002\u0000K\u0000S\u0000E\u0000P\u0000C\u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000',
'1/1',
25,
'Mon, Apr 13, 1998 8:28 PM' ],
[ 3, '', '', 'undefined/undefined', '', '' ] ]);
Loading…
Cancel
Save