From 652b8d47d5b2ec60ddcc2a466818628a0c223d80 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Thu, 13 Oct 2016 16:57:41 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + app.js | 67 +++++++++ lib/iso9660/buffer-reader.js | 43 ++++++ lib/iso9660/index.js | 72 ++++++++++ lib/iso9660/object/directory.js | 87 ++++++++++++ lib/iso9660/object/file.js | 13 ++ lib/iso9660/parse/descriptors/boot-record.js | 13 ++ .../descriptors/descriptor-set-terminator.js | 5 + .../parse/descriptors/primary-volume.js | 35 +++++ .../parse/descriptors/supplementary-volume.js | 5 + .../parse/descriptors/volume-partition.js | 5 + lib/iso9660/parse/directory-record.js | 48 +++++++ lib/iso9660/parse/path-table-record.js | 28 ++++ lib/iso9660/parse/path-table.js | 34 +++++ lib/iso9660/parse/volume-descriptor.js | 60 ++++++++ lib/iso9660/round-to-next-sector.js | 7 + lib/iso9660/types.js | 130 ++++++++++++++++++ lib/remove-right-padding.js | 5 + lib/round-even.js | 5 + lib/seekable/fs.js | 17 +++ lib/seekable/http.js | 34 +++++ package.json | 29 ++++ 22 files changed, 745 insertions(+) create mode 100644 .gitignore create mode 100644 app.js create mode 100644 lib/iso9660/buffer-reader.js create mode 100644 lib/iso9660/index.js create mode 100644 lib/iso9660/object/directory.js create mode 100644 lib/iso9660/object/file.js create mode 100644 lib/iso9660/parse/descriptors/boot-record.js create mode 100644 lib/iso9660/parse/descriptors/descriptor-set-terminator.js create mode 100644 lib/iso9660/parse/descriptors/primary-volume.js create mode 100644 lib/iso9660/parse/descriptors/supplementary-volume.js create mode 100644 lib/iso9660/parse/descriptors/volume-partition.js create mode 100644 lib/iso9660/parse/directory-record.js create mode 100644 lib/iso9660/parse/path-table-record.js create mode 100644 lib/iso9660/parse/path-table.js create mode 100644 lib/iso9660/parse/volume-descriptor.js create mode 100644 lib/iso9660/round-to-next-sector.js create mode 100644 lib/iso9660/types.js create mode 100644 lib/remove-right-padding.js create mode 100644 lib/round-even.js create mode 100644 lib/seekable/fs.js create mode 100644 lib/seekable/http.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..964df08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +log.txt +test +node_modules diff --git a/app.js b/app.js new file mode 100644 index 0000000..c49d1a0 --- /dev/null +++ b/app.js @@ -0,0 +1,67 @@ +'use strict'; + +const Promise = require("bluebird"); +const streamToPromise = require("stream-to-promise"); +const fs = require("fs"); +const memoizee = require("memoizee"); +const archy = require("archy"); + +const httpSeekable = require("./lib/seekable/http"); +const fsSeekable = require("./lib/seekable/fs"); +const iso9660 = require("./lib/iso9660"); + + +let imagePath = process.argv[2]; + +Promise.try(() => { + if (imagePath.match(/^https?:\/\//)) { + return httpSeekable(imagePath); + } else { + return fsSeekable(imagePath); + } +}).then((seekable) => { + let image = iso9660(seekable); + + return Promise.try(() => { + return image.getRootDirectory(); + }).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)); + }); + + /* + let image = iso9660(seekable); + + return Promise.try(() => { + return image.getPathTable(); + }).then((paths) => { + //console.log(paths); + return image.getPrimaryVolumeDescriptor(); + }).then((primaryVolumeDescriptor) => { + console.log(JSON.stringify(primaryVolumeDescriptor, (key, value) => { + if (value != null && value.type === "Buffer") { + return require("util").inspect(Buffer.from(value)); + } else { + return value; + } + }, "\t")); + }) + */ +}); diff --git a/lib/iso9660/buffer-reader.js b/lib/iso9660/buffer-reader.js new file mode 100644 index 0000000..73e9020 --- /dev/null +++ b/lib/iso9660/buffer-reader.js @@ -0,0 +1,43 @@ +'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 } + } + + return Object.keys(typeSpecs).reduce((methods, type) => { + let options = typeSpecs[type]; + + if (options.length == null) { + methods[type] = function(offset, length) { + return types[type](buffer.slice(offset, offset + length)); + } + } else { + methods[type] = function(offset) { + return types[type](buffer.slice(offset, offset + options.length)); + } + } + + return methods; + }, {}); +}; diff --git a/lib/iso9660/index.js b/lib/iso9660/index.js new file mode 100644 index 0000000..f17c74d --- /dev/null +++ b/lib/iso9660/index.js @@ -0,0 +1,72 @@ +'use strict'; + +const Promise = require("bluebird"); +const streamToPromise = require("stream-to-promise"); +const memoizee = require("memoizee"); + +const parseVolumeDescriptor = require("./parse/volume-descriptor"); +const parsePathTable = require("./parse/path-table"); + +let imageOffset = 32 * 1024; // first 32KB are system area, ref http://wiki.osdev.org/ISO_9660#System_Area + +module.exports = function createImage(seekable) { + let image = { + createReadStream: function createReadStream(start, end) { + return Promise.try(() => { + return seekable.createReadStream(start, end); + }); + }, + readRange: function readRange(start, end) { + return Promise.try(() => { + return this.createReadStream(start, end); + }).then((stream) => { + return streamToPromise(stream); + }); + }, + + getPrimaryVolumeDescriptorBuffer: memoizee(function getPrimaryVolumeDescriptorBuffer() { + return this.readRange(imageOffset, imageOffset + 2047); + }), + getPrimaryVolumeDescriptor: function getPrimaryVolumeDescriptor() { + return Promise.try(() => { + return this.getPrimaryVolumeDescriptorBuffer(); + }).then((buffer) => { + return parseVolumeDescriptor(buffer); + }); + }, + + getSectorOffset: function getSectorOffset(sector) { + return Promise.try(() => { + return this.getPrimaryVolumeDescriptor(); + }).then((primaryVolumeDescriptor) => { + return primaryVolumeDescriptor.data.sectorSize * sector; + }); + }, + + getPathTable: function getPathTable() { + 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); + }); + }); + }, + getRootDirectory: function getRootDirectory() { + return Promise.try(() => { + return this.getPrimaryVolumeDescriptor(); + }).then((primaryVolumeDescriptor) => { + return createDirectory(primaryVolumeDescriptor.data.rootDirectory); + }); + } + } + + const createDirectory = require("./object/directory")(image); + + return image; +} diff --git a/lib/iso9660/object/directory.js b/lib/iso9660/object/directory.js new file mode 100644 index 0000000..d9fc6cf --- /dev/null +++ b/lib/iso9660/object/directory.js @@ -0,0 +1,87 @@ +'use strict'; + +const Promise = require("bluebird"); +const memoizee = require("memoizee"); +const debug = require("debug")("iso9660:object:directory"); + +const parseDirectoryRecord = require("../parse/directory-record"); + +module.exports = function(image) { + const createFile = require("./file")(image); + + return function createDirectory(directoryRecord, parent) { + debug(`Creating new directory object for ${directoryRecord.identifier.filename}`); + + return { + type: "directory", + filename: directoryRecord.identifier.filename, + version: directoryRecord.identifier.version, + parent: parent, + recordingDate: directoryRecord.recordingDate, + + 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 = []; + + 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); + } 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}`); + + return createDirectory(record, this); + } else { + debug(`Found file: ${record.identifier.filename}`); + + return createFile(record, this); + } + }); + }); + }), + getTree: function getTree() { + return Promise.try(() => { + return this.getChildren(); + }).map((child) => { + if (child.type === "directory") { + return Promise.try(() => { + return child.getTree(); + }).then((tree) => { + return Object.assign({ + children: tree + }, child); + }); + } else { + return child; + } + }); + } + } + } +} diff --git a/lib/iso9660/object/file.js b/lib/iso9660/object/file.js new file mode 100644 index 0000000..6b25e63 --- /dev/null +++ b/lib/iso9660/object/file.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = function(image) { + return function createFile(record, parent) { + return { + type: "file", + filename: record.identifier.filename, + version: record.identifier.version, + parent: parent, + recordingDate: record.recordingDate + } + } +} diff --git a/lib/iso9660/parse/descriptors/boot-record.js b/lib/iso9660/parse/descriptors/boot-record.js new file mode 100644 index 0000000..df1c91e --- /dev/null +++ b/lib/iso9660/parse/descriptors/boot-record.js @@ -0,0 +1,13 @@ +'use strict'; + +const createBufferReader = require("../../buffer-reader"); + +module.exports = function parseBootRecord(data) { + let bufferReader = createBufferReader(data); + + return { + bootSystemIdentifier: bufferReader.strA(0, 32), + bootIdentifier: bufferReader.strA(32, 32), + bootData: data.slice(64, 2041) // FIXME: El Torito + } +}; diff --git a/lib/iso9660/parse/descriptors/descriptor-set-terminator.js b/lib/iso9660/parse/descriptors/descriptor-set-terminator.js new file mode 100644 index 0000000..71f6ed2 --- /dev/null +++ b/lib/iso9660/parse/descriptors/descriptor-set-terminator.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function parseVolumeDescriptorSetTerminator(data) { + return {}; +}; diff --git a/lib/iso9660/parse/descriptors/primary-volume.js b/lib/iso9660/parse/descriptors/primary-volume.js new file mode 100644 index 0000000..f1e8086 --- /dev/null +++ b/lib/iso9660/parse/descriptors/primary-volume.js @@ -0,0 +1,35 @@ +'use strict'; + +const createBufferReader = require("../../buffer-reader"); +const parseDirectoryRecord = require("../directory-record"); + +module.exports = function parsePrimaryVolumeDescriptor(data) { + let bufferReader = createBufferReader(data); + + return { + systemIdentifier: bufferReader.strA(1, 32), + volumeIdentifier: bufferReader.strD(33, 32), + sectorCount: bufferReader.int32_LSB_MSB(73), + setSize: bufferReader.int16_LSB_MSB(113), + sequenceNumber: bufferReader.int16_LSB_MSB(117), + sectorSize: bufferReader.int16_LSB_MSB(121), + pathTableSize: bufferReader.int32_LSB_MSB(125), + pathTableLocationL: bufferReader.int32_LSB(133), // FIXME: Pointer? (location is expressed in 'sectors') + 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? + creationDate: bufferReader.decDatetime(806), + modificationDate: bufferReader.decDatetime(823), + expirationDate: bufferReader.decDatetime(840), + effectiveDate: bufferReader.decDatetime(857), + fileStructureVersion: bufferReader.int8(874), + applicationData: data.slice(876, 876 + 512) + } +}; diff --git a/lib/iso9660/parse/descriptors/supplementary-volume.js b/lib/iso9660/parse/descriptors/supplementary-volume.js new file mode 100644 index 0000000..f22feb1 --- /dev/null +++ b/lib/iso9660/parse/descriptors/supplementary-volume.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function parseSupplementaryVolumeDescriptor(data) { + throw new Error("Not implemented yet"); +}; diff --git a/lib/iso9660/parse/descriptors/volume-partition.js b/lib/iso9660/parse/descriptors/volume-partition.js new file mode 100644 index 0000000..1ecd122 --- /dev/null +++ b/lib/iso9660/parse/descriptors/volume-partition.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function parseVolumePartitionDescriptor(data) { + throw new Error("Not implemented yet"); +}; diff --git a/lib/iso9660/parse/directory-record.js b/lib/iso9660/parse/directory-record.js new file mode 100644 index 0000000..fbf4e9b --- /dev/null +++ b/lib/iso9660/parse/directory-record.js @@ -0,0 +1,48 @@ +'use strict'; + +const createBufferReader = require("../buffer-reader"); +const roundEven = require("../../round-even"); + +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. + */ + + return { + hidden: !!(value & 1), + directory: !!(value & 2), + associated: !!(value & 4), + inEAR: !!(value & 8), + permissionsInEAR: !!(value & 16), + moreRecordsFollowing: !!(value & 128) + } +} + +module.exports = function parseDirectoryRecord(data) { + let bufferReader = createBufferReader(data); + + let identifierLength = bufferReader.int8(32); + let identifierEnd = roundEven(32 + identifierLength); + + return { + recordLength: bufferReader.int8(0), + extendedAttributeRecordLength: bufferReader.int8(1), + extentLocation: bufferReader.int32_LSB_MSB(2), + extentSize: bufferReader.int32_LSB_MSB(10), + recordingDate: bufferReader.directoryDatetime(18), + fileFlags: parseFileFlags(bufferReader.int8(25)), + interleavedUnitSize: bufferReader.int8(26), + interleavedGapSize: bufferReader.int8(27), + sequenceNumber: bufferReader.int16_LSB_MSB(28), + identifierLength: identifierLength, + identifier: bufferReader.strFilename(33, identifierLength), + systemUse: data.slice(identifierEnd, 254) // FIXME: Extensions! + } +}; diff --git a/lib/iso9660/parse/path-table-record.js b/lib/iso9660/parse/path-table-record.js new file mode 100644 index 0000000..ff1cfcf --- /dev/null +++ b/lib/iso9660/parse/path-table-record.js @@ -0,0 +1,28 @@ +'use strict'; + +const createBufferReader = require("../buffer-reader"); + +module.exports = function parsePathTableRecord(data, tableType) { + let bufferReader = createBufferReader(data); + + let extentLocation, parentDirectoryIndex; + let identifierLength = bufferReader.int8(0); + + if (tableType === "L") { + extentLocation = bufferReader.int32_LSB(2); + parentDirectoryIndex = bufferReader.int16_LSB(6); + } else if (tableType === "M") { + extentLocation = bufferReader.int32_MSB(2); + parentDirectoryIndex = bufferReader.int16_MSB(6); + } else { + throw new Error("Unknown path table type"); + } + + return { + identifierLength: identifierLength, + earLength: bufferReader.int8(1), + extentLocation: extentLocation, + parentDirectoryIndex: parentDirectoryIndex, + identifier: bufferReader.strFilename(8, identifierLength) + } +}; diff --git a/lib/iso9660/parse/path-table.js b/lib/iso9660/parse/path-table.js new file mode 100644 index 0000000..59e0dcf --- /dev/null +++ b/lib/iso9660/parse/path-table.js @@ -0,0 +1,34 @@ +'use strict'; + +const debug = require("debug")("iso9660:parse:path-table"); + +const roundEven = require("../../round-even"); +const parsePathTableRecord = require("./path-table-record"); + +module.exports = function parsePathTable(buffer, sectorSize) { + function roundToNextSector(position) { + return Math.ceil(position / sectorSize) * sectorSize; + } + + let records = []; + let pos = 0; + + while (pos < buffer.length) { + let recordIdentifierLength = buffer.readUInt8(pos); + let recordLength = roundEven(recordIdentifierLength + 8); + + if (recordIdentifierLength === 0) { + /* Reached the end of records for this section, throw away the remainder. */ + debug("Ran out of records, skipping to next sector..."); + pos = roundToNextSector(pos); + } else { + let parsedRecord = parsePathTableRecord(buffer.slice(pos, pos + recordLength), "L"); + debug(`Found: ${parsedRecord.identifier.filename}`); + records.push(parsedRecord); + + pos += recordLength; + } + } + + return records; +}; diff --git a/lib/iso9660/parse/volume-descriptor.js b/lib/iso9660/parse/volume-descriptor.js new file mode 100644 index 0000000..71aed6f --- /dev/null +++ b/lib/iso9660/parse/volume-descriptor.js @@ -0,0 +1,60 @@ +'use strict'; + +const Buffer = require("safe-buffer").Buffer; + +const createBufferReader = require("../buffer-reader"); +const parseBootRecord = require("./descriptors/boot-record"); +const parsePrimaryVolumeDescriptor = require("./descriptors/primary-volume"); +const parseSupplementaryVolumeDescriptor = require("./descriptors/supplementary-volume"); +const parseVolumePartitionDescriptor = require("./descriptors/volume-partition"); +const parseVolumeDescriptorSetTerminator = require("./descriptors/descriptor-set-terminator"); + +let identifierCD001 = Buffer.from("CD001"); + +function getVolumeDescriptorType(typeNumber) { + if (typeNumber === 0) { + return "boot"; + } else if (typeNumber === 1) { + return "primary"; + } else if (typeNumber === 2) { + return "supplementary"; + } else if (typeNumber === 3) { + return "partition"; + } else if (typeNumber >= 4 && typeNumber <= 254) { + return "reserved"; + } else if (typeNumber === 255) { + return "terminator"; + } else { + throw new Error("Unknown volume descriptor type"); + } +} + +function parseVolumeDescriptorData(type, dataBuffer) { + if (type === "boot") { + return parseBootRecord(dataBuffer); + } else if (type === "primary") { + return parsePrimaryVolumeDescriptor(dataBuffer); + } else if (type === "supplementary") { + return parseSupplementaryVolumeDescriptor(dataBuffer); + } else if (type === "partition") { + return parseVolumePartitionDescriptor(dataBuffer); + } else if (type === "terminator") { + return parseVolumeDescriptorSetTerminator(dataBuffer); + } +} + +module.exports = function parseVolumeDescriptor(data) { + if (!data.slice(1, 6).equals(identifierCD001)) { + throw new Error("Not a valid volume descriptor - CD001 identifier is missing"); + } + + let buffer = createBufferReader(data); + + let descriptorType = getVolumeDescriptorType(buffer.int8(0)); + + return { + type: descriptorType, + version: buffer.int8(6), + data: parseVolumeDescriptorData(descriptorType, data.slice(7, 2048)) + } +}; diff --git a/lib/iso9660/round-to-next-sector.js b/lib/iso9660/round-to-next-sector.js new file mode 100644 index 0000000..b7dbb72 --- /dev/null +++ b/lib/iso9660/round-to-next-sector.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(sectorSize) { + return function roundToNextSector(position) { + return Math.ceil(position / sectorSize) * sectorSize; + } +} diff --git a/lib/iso9660/types.js b/lib/iso9660/types.js new file mode 100644 index 0000000..f620b6a --- /dev/null +++ b/lib/iso9660/types.js @@ -0,0 +1,130 @@ +'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)))); + } +}; diff --git a/lib/remove-right-padding.js b/lib/remove-right-padding.js new file mode 100644 index 0000000..7d45ee5 --- /dev/null +++ b/lib/remove-right-padding.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function removeRightPadding(string) { + return string.replace(/[ ]+$/, ""); +}; diff --git a/lib/round-even.js b/lib/round-even.js new file mode 100644 index 0000000..080b209 --- /dev/null +++ b/lib/round-even.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function roundEven(number) { + return Math.ceil((number) / 2) * 2; +}; diff --git a/lib/seekable/fs.js b/lib/seekable/fs.js new file mode 100644 index 0000000..c9e09d1 --- /dev/null +++ b/lib/seekable/fs.js @@ -0,0 +1,17 @@ +'use strict'; + +const fs = require("fs"); +const debug = require("debug")("seekable:fs"); + +module.exports = function getHttpSeekable(targetPath) { + return { + createReadStream: function createReadStream(start, end) { + debug(`${start}-${end} -- ${targetPath}`); + + return fs.createReadStream(targetPath, { + start: start, + end: end + }); + } + } +}; diff --git a/lib/seekable/http.js b/lib/seekable/http.js new file mode 100644 index 0000000..3d30308 --- /dev/null +++ b/lib/seekable/http.js @@ -0,0 +1,34 @@ +'use strict'; + +const Promise = require("bluebird"); +const bhttp = require("bhttp"); +const debug = require("debug")("seekable:http"); + +function generateRangeHeader(start, end) { + if (start != null && end != null) { + return `bytes=${start}-${end}`; + } else if (start != null && end == null) { + return `bytes=${start}-`; + } else if (start == null && end != null) { + return `bytes=-${end}`; + } else { + return null; + } +} + +module.exports = function getHttpSeekable(targetUrl) { + return { + createReadStream: function createReadStream(start, end) { + return Promise.try(() => { + debug(`${start}-${end} -- ${targetUrl}`); + + return bhttp.get(targetUrl, { + stream: true, + headers: { + "range": generateRangeHeader(start, end) // FIXME: What if there is no range specified at all? Is `null` valid then? + } + }); + }); + } + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..d33d2a0 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "iso-spider", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@git.cryto.net:joepie91/iso-spider.git" + }, + "author": "", + "license": "ISC", + "dependencies": { + "archy": "^1.0.0", + "bhttp": "^1.2.4", + "bluebird": "^3.4.6", + "debug": "^2.2.0", + "memoizee": "^0.4.1", + "moment-timezone": "^0.5.6", + "promise-while-loop": "^1.0.1", + "safe-buffer": "^5.0.1", + "stream-to-promise": "^2.2.0" + } +}