@qnighy/marshal
Advanced tools
Comparing version 0.1.1 to 0.1.2
## Unreleased | ||
## 0.1.2 | ||
- `parse` is now available as `Marshal.parse` too https://github.com/qnighy/marshal-js/pull/2 | ||
- Misc | ||
- Promote `@types/node` to `dependencies` https://github.com/qnighy/marshal-js/pull/3 | ||
- Refactor modules https://github.com/qnighy/marshal-js/pull/2 | ||
## 0.1.1 | ||
- Use `defineProperty` to construct hashes. | ||
- Use `defineProperty` to construct hashes. https://github.com/qnighy/marshal-js/pull/1 | ||
@@ -7,0 +14,0 @@ ## 0.1.0 |
@@ -1,442 +0,4 @@ | ||
import _createClass from "@babel/runtime-corejs3/helpers/createClass"; | ||
import _defineProperty from "@babel/runtime-corejs3/helpers/defineProperty"; | ||
import _classCallCheck from "@babel/runtime-corejs3/helpers/classCallCheck"; | ||
import _inherits from "@babel/runtime-corejs3/helpers/inherits"; | ||
import _createSuper from "@babel/runtime-corejs3/helpers/createSuper"; | ||
import _wrapNativeSuper from "@babel/runtime-corejs3/helpers/wrapNativeSuper"; | ||
import _concatInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/concat"; | ||
import _sliceInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/slice"; | ||
/** | ||
* An exception raised when `loadMarshal` encountered an invalid format. | ||
*/ | ||
export var MarshalError = /*#__PURE__*/function (_Error) { | ||
_inherits(MarshalError, _Error); | ||
var _super = _createSuper(MarshalError); | ||
function MarshalError() { | ||
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; | ||
_classCallCheck(this, MarshalError); | ||
return _super.call(this, "Marshal error: ".concat(message)); | ||
} | ||
return MarshalError; | ||
}( /*#__PURE__*/_wrapNativeSuper(Error)); | ||
/** | ||
* Parses a data exported by Ruby's `Marshal.load`. | ||
* @param buf A binary data to parse | ||
* @returns the decoded value | ||
* @throws {MarshalError} when the data contains an invalid format. | ||
*/ | ||
export function parse(buf) { | ||
return new Parser(buf).read(); | ||
} | ||
var Parser = /*#__PURE__*/function () { | ||
function Parser(buf) { | ||
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; | ||
_classCallCheck(this, Parser); | ||
_defineProperty(this, "symbols", []); | ||
_defineProperty(this, "objects", []); | ||
this.buf = buf; | ||
this.index = index; | ||
this.buf = Buffer.from(this.buf); | ||
} | ||
_createClass(Parser, [{ | ||
key: "read", | ||
value: function read() { | ||
this.symbols = []; | ||
this.objects = []; | ||
var major = this.readByte(); | ||
var minor = this.readByte(); | ||
if (major !== 4 || minor > 8) { | ||
var _context; | ||
throw new MarshalError(_concatInstanceProperty(_context = "incompatible marshal file format (can't be read): format version 4.8 required; ".concat(major, ".")).call(_context, minor, " given")); | ||
} | ||
return this.readAny(); | ||
} | ||
}, { | ||
key: "readAny", | ||
value: function readAny() { | ||
var tag = this.readByte(); | ||
switch (tag) { | ||
// '"': an instance of String | ||
case 0x22: | ||
return this.entry(this.readString()); | ||
// '/': an instance of Regexp | ||
case 0x2f: | ||
{ | ||
var source = this.readString(); // Discard flags | ||
this.readByte(); | ||
return this.entry(new RegExp(source)); | ||
} | ||
// '0': nil | ||
case 0x30: | ||
return null; | ||
// ':': an instance of Symbol | ||
case 0x3a: | ||
{ | ||
var sym = this.readString(); | ||
this.symbols.push(sym); | ||
return sym; | ||
} | ||
// ';': symbol reference | ||
case 0x3b: | ||
{ | ||
var symref = this.readInt(); | ||
if (symref < 0 || symref >= this.symbols.length) { | ||
throw new MarshalError("bad symbol"); | ||
} | ||
return this.symbols[symref]; | ||
} | ||
// '@': an object link | ||
case 0x40: | ||
{ | ||
var objref = this.readInt(); | ||
if (objref < 0 || objref >= this.objects.length) { | ||
throw new MarshalError("dump format error (unlinked)"); | ||
} | ||
return this.objects[objref]; | ||
} | ||
// 'C': an instance of String/Regexp/Array/Hash subclass | ||
case 0x43: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
return this.readAny(); | ||
} | ||
// 'F': false | ||
case 0x46: | ||
return false; | ||
// 'I': add instance variables | ||
case 0x49: | ||
{ | ||
var obj = this.readAny(); | ||
var length = this.readInt(); | ||
for (var i = 0; i < length; i++) { | ||
// Discard instance variables | ||
this.readAny(); | ||
this.readAny(); | ||
} | ||
return obj; | ||
} | ||
// 'M': a module or class (old format) | ||
case 0x4d: | ||
{ | ||
// Discard class/module name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'S': an instance of a struct | ||
case 0x53: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
var _length = this.readLength("struct"); | ||
var struct = this.entry({}); | ||
for (var _i = 0; _i < _length; _i++) { | ||
var key = this.readAny(); | ||
var value = this.readAny(); // Discard non-String keys | ||
if (typeof key === "number" || typeof key === "string") { | ||
Object.defineProperty(struct, key, { | ||
value: value, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true | ||
}); | ||
} | ||
} | ||
return struct; | ||
} | ||
// 'T': true | ||
case 0x54: | ||
return true; | ||
// 'U': custom format (marshal_dump) | ||
case 0x55: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
var _obj = this.entry({}); // Discard data | ||
this.readAny(); | ||
return _obj; | ||
} | ||
// '[': an instance of Array | ||
case 0x5b: | ||
{ | ||
var _length2 = this.readLength("array"); | ||
var arr = this.entry([]); | ||
for (var _i2 = 0; _i2 < _length2; _i2++) { | ||
arr.push(this.readAny()); | ||
} | ||
return arr; | ||
} | ||
// 'c': a class | ||
case 0x63: | ||
{ | ||
// Discard class name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'd': TYPE_DATA | ||
case 0x64: | ||
throw new MarshalError("unimplemented: TYPE_DATA"); | ||
// 'e': TYPE_EXTENDED | ||
case 0x65: | ||
throw new MarshalError("unimplemented: TYPE_EXTENDED"); | ||
// 'f': an instance of Float | ||
case 0x66: | ||
{ | ||
var s = this.readString(); | ||
var f = s === "inf" ? Infinity : s === "-inf" ? -Infinity : s === "nan" ? NaN : parseFloat(s); | ||
return this.entry(f); | ||
} | ||
// 'i': an instance of Integer (small) | ||
case 0x69: | ||
return this.readInt(); | ||
// 'l': an instance of Integer (large) | ||
case 0x6c: | ||
{ | ||
var signChar = this.readByte(); | ||
var _length3 = this.readLength("string") * 2; | ||
var sum = 0; | ||
var magnitude = signChar === 0x2d ? -1 : 1; | ||
for (var _i3 = 0; _i3 < _length3; _i3++) { | ||
sum += this.readByte() * magnitude; | ||
magnitude *= 256; | ||
} | ||
return this.entry(sum); | ||
} | ||
// 'm': a module | ||
case 0x6d: | ||
{ | ||
// Discard module name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'o': a plain object | ||
case 0x6f: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
var _obj2 = this.entry({}); | ||
var _length4 = this.readInt(); | ||
for (var _i4 = 0; _i4 < _length4; _i4++) { | ||
// Discard instance variables | ||
this.readAny(); | ||
this.readAny(); | ||
} | ||
return _obj2; | ||
} | ||
// 'u': old custom format (_dump) | ||
case 0x75: | ||
{ | ||
// Discard class name | ||
this.readAny(); // Discard data | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// '{': an instance of Hash (without default value) | ||
case 0x7b: | ||
{ | ||
var _length5 = this.readLength("hash"); | ||
var hash = this.entry({}); | ||
for (var _i5 = 0; _i5 < _length5; _i5++) { | ||
var _key = this.readAny(); | ||
var _value = this.readAny(); // Discard non-String keys | ||
if (typeof _key === "number" || typeof _key === "string") { | ||
Object.defineProperty(hash, _key, { | ||
value: _value, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true | ||
}); | ||
} | ||
} | ||
return hash; | ||
} | ||
// '}': an instance of Hash (with default value) | ||
case 0x7d: | ||
{ | ||
var _length6 = this.readLength("hash"); | ||
var _hash = this.entry({}); | ||
for (var _i6 = 0; _i6 < _length6; _i6++) { | ||
var _key2 = this.readAny(); | ||
var _value2 = this.readAny(); // Discard non-String keys | ||
if (typeof _key2 === "number" || typeof _key2 === "string") { | ||
Object.defineProperty(_hash, _key2, { | ||
value: _value2, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true | ||
}); | ||
} | ||
} | ||
_hash["__ruby_default"] = this.readAny(); | ||
return _hash; | ||
} | ||
default: | ||
throw new MarshalError("dump format error(0x".concat(tag.toString(16), ")")); | ||
} | ||
} | ||
}, { | ||
key: "readLength", | ||
value: function readLength(msg) { | ||
var length = this.readInt(); | ||
if (length < 0) { | ||
throw new MarshalError("negative ".concat(msg, " size (or size too big)")); | ||
} | ||
return length; | ||
} | ||
}, { | ||
key: "readInt", | ||
value: function readInt() { | ||
var tag = this.readByte(); | ||
if (tag === 0) { | ||
return 0; | ||
} | ||
if (tag >= 5 && tag < 128) { | ||
return tag - 5; | ||
} | ||
if (tag >= 128 && tag <= 251) { | ||
return tag - 251; | ||
} | ||
var length = tag < 128 ? tag : 256 - tag; | ||
var sum = 0; | ||
var magnitude = 1; | ||
for (var i = 0; i < length; i++) { | ||
sum += magnitude * this.readByte(); | ||
magnitude *= 256; | ||
} | ||
if (tag >= 128) { | ||
sum -= magnitude; | ||
} | ||
return sum; | ||
} | ||
}, { | ||
key: "readByte", | ||
value: function readByte() { | ||
if (this.index >= this.buf.byteLength) { | ||
throw new MarshalError("marshal data too short"); | ||
} | ||
var byte = this.buf.readUInt8(this.index); | ||
this.index++; | ||
return byte; | ||
} | ||
}, { | ||
key: "readString", | ||
value: function readString() { | ||
return this.readBytes().toString("utf-8"); | ||
} | ||
}, { | ||
key: "readBytes", | ||
value: function readBytes() { | ||
var _context2; | ||
var length = this.readLength("string"); | ||
if (this.index + length > this.buf.byteLength) { | ||
throw new MarshalError("marshal data too short"); | ||
} | ||
var bytes = _sliceInstanceProperty(_context2 = this.buf).call(_context2, this.index, this.index + length); | ||
this.index += length; | ||
return bytes; | ||
} | ||
}, { | ||
key: "entry", | ||
value: function entry(obj) { | ||
this.objects.push(obj); | ||
return obj; | ||
} | ||
}]); | ||
return Parser; | ||
}(); | ||
export { MarshalError } from "./error"; | ||
export { parse } from "./parse"; | ||
import * as _Marshal from "./marshal-object"; | ||
export { _Marshal as Marshal }; |
@@ -1,14 +0,3 @@ | ||
/// <reference types="node" /> | ||
/** | ||
* An exception raised when `loadMarshal` encountered an invalid format. | ||
*/ | ||
export declare class MarshalError extends Error { | ||
constructor(message?: string); | ||
} | ||
/** | ||
* Parses a data exported by Ruby's `Marshal.load`. | ||
* @param buf A binary data to parse | ||
* @returns the decoded value | ||
* @throws {MarshalError} when the data contains an invalid format. | ||
*/ | ||
export declare function parse(buf: Buffer): unknown; | ||
export { MarshalError } from "./error"; | ||
export { parse } from "./parse"; | ||
export * as Marshal from "./marshal-object"; |
"use strict"; | ||
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default; | ||
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default; | ||
@@ -8,448 +8,22 @@ Object.defineProperty(exports, "__esModule", { | ||
}); | ||
exports.parse = parse; | ||
exports.MarshalError = void 0; | ||
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); | ||
var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); | ||
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass")); | ||
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck")); | ||
var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/inherits")); | ||
var _createSuper2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createSuper")); | ||
var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/wrapNativeSuper")); | ||
/** | ||
* An exception raised when `loadMarshal` encountered an invalid format. | ||
*/ | ||
var MarshalError = /*#__PURE__*/function (_Error) { | ||
(0, _inherits2.default)(MarshalError, _Error); | ||
var _super = (0, _createSuper2.default)(MarshalError); | ||
function MarshalError() { | ||
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; | ||
(0, _classCallCheck2.default)(this, MarshalError); | ||
return _super.call(this, "Marshal error: ".concat(message)); | ||
Object.defineProperty(exports, "MarshalError", { | ||
enumerable: true, | ||
get: function get() { | ||
return _error.MarshalError; | ||
} | ||
return MarshalError; | ||
}( /*#__PURE__*/(0, _wrapNativeSuper2.default)(Error)); | ||
/** | ||
* Parses a data exported by Ruby's `Marshal.load`. | ||
* @param buf A binary data to parse | ||
* @returns the decoded value | ||
* @throws {MarshalError} when the data contains an invalid format. | ||
*/ | ||
exports.MarshalError = MarshalError; | ||
function parse(buf) { | ||
return new Parser(buf).read(); | ||
} | ||
var Parser = /*#__PURE__*/function () { | ||
function Parser(buf) { | ||
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; | ||
(0, _classCallCheck2.default)(this, Parser); | ||
(0, _defineProperty2.default)(this, "symbols", []); | ||
(0, _defineProperty2.default)(this, "objects", []); | ||
this.buf = buf; | ||
this.index = index; | ||
this.buf = Buffer.from(this.buf); | ||
}); | ||
Object.defineProperty(exports, "parse", { | ||
enumerable: true, | ||
get: function get() { | ||
return _parse.parse; | ||
} | ||
}); | ||
exports.Marshal = void 0; | ||
(0, _createClass2.default)(Parser, [{ | ||
key: "read", | ||
value: function read() { | ||
this.symbols = []; | ||
this.objects = []; | ||
var major = this.readByte(); | ||
var minor = this.readByte(); | ||
var _error = require("./error"); | ||
if (major !== 4 || minor > 8) { | ||
var _context; | ||
var _parse = require("./parse"); | ||
throw new MarshalError((0, _concat.default)(_context = "incompatible marshal file format (can't be read): format version 4.8 required; ".concat(major, ".")).call(_context, minor, " given")); | ||
} | ||
var _Marshal = _interopRequireWildcard(require("./marshal-object")); | ||
return this.readAny(); | ||
} | ||
}, { | ||
key: "readAny", | ||
value: function readAny() { | ||
var tag = this.readByte(); | ||
switch (tag) { | ||
// '"': an instance of String | ||
case 0x22: | ||
return this.entry(this.readString()); | ||
// '/': an instance of Regexp | ||
case 0x2f: | ||
{ | ||
var source = this.readString(); // Discard flags | ||
this.readByte(); | ||
return this.entry(new RegExp(source)); | ||
} | ||
// '0': nil | ||
case 0x30: | ||
return null; | ||
// ':': an instance of Symbol | ||
case 0x3a: | ||
{ | ||
var sym = this.readString(); | ||
this.symbols.push(sym); | ||
return sym; | ||
} | ||
// ';': symbol reference | ||
case 0x3b: | ||
{ | ||
var symref = this.readInt(); | ||
if (symref < 0 || symref >= this.symbols.length) { | ||
throw new MarshalError("bad symbol"); | ||
} | ||
return this.symbols[symref]; | ||
} | ||
// '@': an object link | ||
case 0x40: | ||
{ | ||
var objref = this.readInt(); | ||
if (objref < 0 || objref >= this.objects.length) { | ||
throw new MarshalError("dump format error (unlinked)"); | ||
} | ||
return this.objects[objref]; | ||
} | ||
// 'C': an instance of String/Regexp/Array/Hash subclass | ||
case 0x43: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
return this.readAny(); | ||
} | ||
// 'F': false | ||
case 0x46: | ||
return false; | ||
// 'I': add instance variables | ||
case 0x49: | ||
{ | ||
var obj = this.readAny(); | ||
var length = this.readInt(); | ||
for (var i = 0; i < length; i++) { | ||
// Discard instance variables | ||
this.readAny(); | ||
this.readAny(); | ||
} | ||
return obj; | ||
} | ||
// 'M': a module or class (old format) | ||
case 0x4d: | ||
{ | ||
// Discard class/module name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'S': an instance of a struct | ||
case 0x53: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
var _length = this.readLength("struct"); | ||
var struct = this.entry({}); | ||
for (var _i = 0; _i < _length; _i++) { | ||
var key = this.readAny(); | ||
var value = this.readAny(); // Discard non-String keys | ||
if (typeof key === "number" || typeof key === "string") { | ||
Object.defineProperty(struct, key, { | ||
value: value, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true | ||
}); | ||
} | ||
} | ||
return struct; | ||
} | ||
// 'T': true | ||
case 0x54: | ||
return true; | ||
// 'U': custom format (marshal_dump) | ||
case 0x55: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
var _obj = this.entry({}); // Discard data | ||
this.readAny(); | ||
return _obj; | ||
} | ||
// '[': an instance of Array | ||
case 0x5b: | ||
{ | ||
var _length2 = this.readLength("array"); | ||
var arr = this.entry([]); | ||
for (var _i2 = 0; _i2 < _length2; _i2++) { | ||
arr.push(this.readAny()); | ||
} | ||
return arr; | ||
} | ||
// 'c': a class | ||
case 0x63: | ||
{ | ||
// Discard class name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'd': TYPE_DATA | ||
case 0x64: | ||
throw new MarshalError("unimplemented: TYPE_DATA"); | ||
// 'e': TYPE_EXTENDED | ||
case 0x65: | ||
throw new MarshalError("unimplemented: TYPE_EXTENDED"); | ||
// 'f': an instance of Float | ||
case 0x66: | ||
{ | ||
var s = this.readString(); | ||
var f = s === "inf" ? Infinity : s === "-inf" ? -Infinity : s === "nan" ? NaN : parseFloat(s); | ||
return this.entry(f); | ||
} | ||
// 'i': an instance of Integer (small) | ||
case 0x69: | ||
return this.readInt(); | ||
// 'l': an instance of Integer (large) | ||
case 0x6c: | ||
{ | ||
var signChar = this.readByte(); | ||
var _length3 = this.readLength("string") * 2; | ||
var sum = 0; | ||
var magnitude = signChar === 0x2d ? -1 : 1; | ||
for (var _i3 = 0; _i3 < _length3; _i3++) { | ||
sum += this.readByte() * magnitude; | ||
magnitude *= 256; | ||
} | ||
return this.entry(sum); | ||
} | ||
// 'm': a module | ||
case 0x6d: | ||
{ | ||
// Discard module name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'o': a plain object | ||
case 0x6f: | ||
{ | ||
// Discard class name | ||
this.readAny(); | ||
var _obj2 = this.entry({}); | ||
var _length4 = this.readInt(); | ||
for (var _i4 = 0; _i4 < _length4; _i4++) { | ||
// Discard instance variables | ||
this.readAny(); | ||
this.readAny(); | ||
} | ||
return _obj2; | ||
} | ||
// 'u': old custom format (_dump) | ||
case 0x75: | ||
{ | ||
// Discard class name | ||
this.readAny(); // Discard data | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// '{': an instance of Hash (without default value) | ||
case 0x7b: | ||
{ | ||
var _length5 = this.readLength("hash"); | ||
var hash = this.entry({}); | ||
for (var _i5 = 0; _i5 < _length5; _i5++) { | ||
var _key = this.readAny(); | ||
var _value = this.readAny(); // Discard non-String keys | ||
if (typeof _key === "number" || typeof _key === "string") { | ||
Object.defineProperty(hash, _key, { | ||
value: _value, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true | ||
}); | ||
} | ||
} | ||
return hash; | ||
} | ||
// '}': an instance of Hash (with default value) | ||
case 0x7d: | ||
{ | ||
var _length6 = this.readLength("hash"); | ||
var _hash = this.entry({}); | ||
for (var _i6 = 0; _i6 < _length6; _i6++) { | ||
var _key2 = this.readAny(); | ||
var _value2 = this.readAny(); // Discard non-String keys | ||
if (typeof _key2 === "number" || typeof _key2 === "string") { | ||
Object.defineProperty(_hash, _key2, { | ||
value: _value2, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true | ||
}); | ||
} | ||
} | ||
_hash["__ruby_default"] = this.readAny(); | ||
return _hash; | ||
} | ||
default: | ||
throw new MarshalError("dump format error(0x".concat(tag.toString(16), ")")); | ||
} | ||
} | ||
}, { | ||
key: "readLength", | ||
value: function readLength(msg) { | ||
var length = this.readInt(); | ||
if (length < 0) { | ||
throw new MarshalError("negative ".concat(msg, " size (or size too big)")); | ||
} | ||
return length; | ||
} | ||
}, { | ||
key: "readInt", | ||
value: function readInt() { | ||
var tag = this.readByte(); | ||
if (tag === 0) { | ||
return 0; | ||
} | ||
if (tag >= 5 && tag < 128) { | ||
return tag - 5; | ||
} | ||
if (tag >= 128 && tag <= 251) { | ||
return tag - 251; | ||
} | ||
var length = tag < 128 ? tag : 256 - tag; | ||
var sum = 0; | ||
var magnitude = 1; | ||
for (var i = 0; i < length; i++) { | ||
sum += magnitude * this.readByte(); | ||
magnitude *= 256; | ||
} | ||
if (tag >= 128) { | ||
sum -= magnitude; | ||
} | ||
return sum; | ||
} | ||
}, { | ||
key: "readByte", | ||
value: function readByte() { | ||
if (this.index >= this.buf.byteLength) { | ||
throw new MarshalError("marshal data too short"); | ||
} | ||
var byte = this.buf.readUInt8(this.index); | ||
this.index++; | ||
return byte; | ||
} | ||
}, { | ||
key: "readString", | ||
value: function readString() { | ||
return this.readBytes().toString("utf-8"); | ||
} | ||
}, { | ||
key: "readBytes", | ||
value: function readBytes() { | ||
var _context2; | ||
var length = this.readLength("string"); | ||
if (this.index + length > this.buf.byteLength) { | ||
throw new MarshalError("marshal data too short"); | ||
} | ||
var bytes = (0, _slice.default)(_context2 = this.buf).call(_context2, this.index, this.index + length); | ||
this.index += length; | ||
return bytes; | ||
} | ||
}, { | ||
key: "entry", | ||
value: function entry(obj) { | ||
this.objects.push(obj); | ||
return obj; | ||
} | ||
}]); | ||
return Parser; | ||
}(); | ||
exports.Marshal = _Marshal; |
{ | ||
"name": "@qnighy/marshal", | ||
"description": "Decoder for Ruby's Marshal", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"license": "MIT", | ||
@@ -17,3 +17,6 @@ "homepage": "https://github.com/qnighy/marshal-js", | ||
"dist/**", | ||
"src/index.ts" | ||
"src/error.ts", | ||
"src/index.ts", | ||
"src/marshal-object.ts", | ||
"src/parse.ts" | ||
], | ||
@@ -35,3 +38,4 @@ "scripts": { | ||
"dependencies": { | ||
"@babel/runtime-corejs3": "^7.14.9" | ||
"@babel/runtime-corejs3": "^7.14.9", | ||
"@types/node": "^16.4.11" | ||
}, | ||
@@ -45,3 +49,2 @@ "devDependencies": { | ||
"@jest/globals": "^27.0.6", | ||
"@types/node": "^16.4.11", | ||
"@typescript-eslint/eslint-plugin": "^4.29.0", | ||
@@ -48,0 +51,0 @@ "@typescript-eslint/parser": "^4.29.0", |
@@ -14,14 +14,11 @@ ## `@qnighy/marshal` | ||
```javascript | ||
import * as Marshal from "@qnighy/marshal"; | ||
import { Marshal } from "@qnighy/marshal"; | ||
// OR | ||
const Marshal = require("@qnighy/marshal"); | ||
const { Marshal } = require("@qnighy/marshal"); | ||
const buf = Buffer.from( | ||
[ | ||
4, 8, 123, 7, 58, 9, 110, 97, 109, 101, | ||
73, 34, 8, 102, 111, 111, 6, 58, 6, 69, | ||
84, 58, 12, 110, 117, 109, 98, 101, 114, 115, | ||
91, 8, 105, 6, 105, 7, 105, 8, | ||
] | ||
); | ||
const buf = Buffer.from([ | ||
4, 8, 123, 7, 58, 9, 110, 97, 109, 101, 73, 34, 8, 102, 111, 111, 6, 58, 6, | ||
69, 84, 58, 12, 110, 117, 109, 98, 101, 114, 115, 91, 8, 105, 6, 105, 7, 105, | ||
8, | ||
]); | ||
const data = Marshal.parse(buf); | ||
@@ -33,3 +30,3 @@ // => { name: 'foo', numbers: [ 1, 2, 3 ] } | ||
### `function parse` | ||
### `Marshal.parse` | ||
@@ -48,4 +45,8 @@ Parses a data exported by Ruby's `Marshal.load`. | ||
### `class MarshalError` | ||
### `parse` | ||
Same as `Marshal.parse`. | ||
### `MarshalError` | ||
An exception raised when `loadMarshal` encountered an invalid format. | ||
@@ -52,0 +53,0 @@ |
322
src/index.ts
@@ -1,319 +0,3 @@ | ||
/** | ||
* An exception raised when `loadMarshal` encountered an invalid format. | ||
*/ | ||
export class MarshalError extends Error { | ||
constructor(message = "") { | ||
super(`Marshal error: ${message}`); | ||
} | ||
} | ||
/** | ||
* Parses a data exported by Ruby's `Marshal.load`. | ||
* @param buf A binary data to parse | ||
* @returns the decoded value | ||
* @throws {MarshalError} when the data contains an invalid format. | ||
*/ | ||
export function parse(buf: Buffer): unknown { | ||
return new Parser(buf).read(); | ||
} | ||
class Parser { | ||
private symbols: string[] = []; | ||
private objects: unknown[] = []; | ||
constructor(private buf: Buffer, private index = 0) { | ||
this.buf = Buffer.from(this.buf); | ||
} | ||
public read(): unknown { | ||
this.symbols = []; | ||
this.objects = []; | ||
const major = this.readByte(); | ||
const minor = this.readByte(); | ||
if (major !== 4 || minor > 8) { | ||
throw new MarshalError( | ||
`incompatible marshal file format (can't be read): format version 4.8 required; ${major}.${minor} given` | ||
); | ||
} | ||
return this.readAny(); | ||
} | ||
private readAny(): unknown { | ||
const tag = this.readByte(); | ||
switch (tag) { | ||
// '"': an instance of String | ||
case 0x22: | ||
return this.entry(this.readString()); | ||
// '/': an instance of Regexp | ||
case 0x2f: { | ||
const source = this.readString(); | ||
// Discard flags | ||
this.readByte(); | ||
return this.entry(new RegExp(source)); | ||
} | ||
// '0': nil | ||
case 0x30: | ||
return null; | ||
// ':': an instance of Symbol | ||
case 0x3a: { | ||
const sym = this.readString(); | ||
this.symbols.push(sym); | ||
return sym; | ||
} | ||
// ';': symbol reference | ||
case 0x3b: { | ||
const symref = this.readInt(); | ||
if (symref < 0 || symref >= this.symbols.length) { | ||
throw new MarshalError("bad symbol"); | ||
} | ||
return this.symbols[symref]; | ||
} | ||
// '@': an object link | ||
case 0x40: { | ||
const objref = this.readInt(); | ||
if (objref < 0 || objref >= this.objects.length) { | ||
throw new MarshalError("dump format error (unlinked)"); | ||
} | ||
return this.objects[objref]; | ||
} | ||
// 'C': an instance of String/Regexp/Array/Hash subclass | ||
case 0x43: { | ||
// Discard class name | ||
this.readAny(); | ||
return this.readAny(); | ||
} | ||
// 'F': false | ||
case 0x46: | ||
return false; | ||
// 'I': add instance variables | ||
case 0x49: { | ||
const obj = this.readAny(); | ||
const length = this.readInt(); | ||
for (let i = 0; i < length; i++) { | ||
// Discard instance variables | ||
this.readAny(); | ||
this.readAny(); | ||
} | ||
return obj; | ||
} | ||
// 'M': a module or class (old format) | ||
case 0x4d: { | ||
// Discard class/module name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'S': an instance of a struct | ||
case 0x53: { | ||
// Discard class name | ||
this.readAny(); | ||
const length = this.readLength("struct"); | ||
const struct: Record<string, unknown> = this.entry({}); | ||
for (let i = 0; i < length; i++) { | ||
const key = this.readAny(); | ||
const value = this.readAny(); | ||
// Discard non-String keys | ||
if (typeof key === "number" || typeof key === "string") { | ||
Object.defineProperty(struct, key, { | ||
value, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
} | ||
} | ||
return struct; | ||
} | ||
// 'T': true | ||
case 0x54: | ||
return true; | ||
// 'U': custom format (marshal_dump) | ||
case 0x55: { | ||
// Discard class name | ||
this.readAny(); | ||
const obj = this.entry({}); | ||
// Discard data | ||
this.readAny(); | ||
return obj; | ||
} | ||
// '[': an instance of Array | ||
case 0x5b: { | ||
const length = this.readLength("array"); | ||
const arr: unknown[] = this.entry([]); | ||
for (let i = 0; i < length; i++) { | ||
arr.push(this.readAny()); | ||
} | ||
return arr; | ||
} | ||
// 'c': a class | ||
case 0x63: { | ||
// Discard class name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'd': TYPE_DATA | ||
case 0x64: | ||
throw new MarshalError("unimplemented: TYPE_DATA"); | ||
// 'e': TYPE_EXTENDED | ||
case 0x65: | ||
throw new MarshalError("unimplemented: TYPE_EXTENDED"); | ||
// 'f': an instance of Float | ||
case 0x66: { | ||
const s = this.readString(); | ||
const f = | ||
s === "inf" | ||
? Infinity | ||
: s === "-inf" | ||
? -Infinity | ||
: s === "nan" | ||
? NaN | ||
: parseFloat(s); | ||
return this.entry(f); | ||
} | ||
// 'i': an instance of Integer (small) | ||
case 0x69: | ||
return this.readInt(); | ||
// 'l': an instance of Integer (large) | ||
case 0x6c: { | ||
const signChar = this.readByte(); | ||
const length = this.readLength("string") * 2; | ||
let sum = 0; | ||
let magnitude = signChar === 0x2d ? -1 : 1; | ||
for (let i = 0; i < length; i++) { | ||
sum += this.readByte() * magnitude; | ||
magnitude *= 256; | ||
} | ||
return this.entry(sum); | ||
} | ||
// 'm': a module | ||
case 0x6d: { | ||
// Discard module name | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// 'o': a plain object | ||
case 0x6f: { | ||
// Discard class name | ||
this.readAny(); | ||
const obj = this.entry({}); | ||
const length = this.readInt(); | ||
for (let i = 0; i < length; i++) { | ||
// Discard instance variables | ||
this.readAny(); | ||
this.readAny(); | ||
} | ||
return obj; | ||
} | ||
// 'u': old custom format (_dump) | ||
case 0x75: { | ||
// Discard class name | ||
this.readAny(); | ||
// Discard data | ||
this.readBytes(); | ||
return this.entry({}); | ||
} | ||
// '{': an instance of Hash (without default value) | ||
case 0x7b: { | ||
const length = this.readLength("hash"); | ||
const hash: Record<string, unknown> = this.entry({}); | ||
for (let i = 0; i < length; i++) { | ||
const key = this.readAny(); | ||
const value = this.readAny(); | ||
// Discard non-String keys | ||
if (typeof key === "number" || typeof key === "string") { | ||
Object.defineProperty(hash, key, { | ||
value, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
} | ||
} | ||
return hash; | ||
} | ||
// '}': an instance of Hash (with default value) | ||
case 0x7d: { | ||
const length = this.readLength("hash"); | ||
const hash: Record<string, unknown> = this.entry({}); | ||
for (let i = 0; i < length; i++) { | ||
const key = this.readAny(); | ||
const value = this.readAny(); | ||
// Discard non-String keys | ||
if (typeof key === "number" || typeof key === "string") { | ||
Object.defineProperty(hash, key, { | ||
value, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
} | ||
} | ||
hash["__ruby_default"] = this.readAny(); | ||
return hash; | ||
} | ||
default: | ||
throw new MarshalError(`dump format error(0x${tag.toString(16)})`); | ||
} | ||
} | ||
private readLength(msg: string): number { | ||
const length = this.readInt(); | ||
if (length < 0) { | ||
throw new MarshalError(`negative ${msg} size (or size too big)`); | ||
} | ||
return length; | ||
} | ||
private readInt(): number { | ||
const tag = this.readByte(); | ||
if (tag === 0) { | ||
return 0; | ||
} | ||
if (tag >= 5 && tag < 128) { | ||
return tag - 5; | ||
} | ||
if (tag >= 128 && tag <= 251) { | ||
return tag - 251; | ||
} | ||
const length = tag < 128 ? tag : 256 - tag; | ||
let sum = 0; | ||
let magnitude = 1; | ||
for (let i = 0; i < length; i++) { | ||
sum += magnitude * this.readByte(); | ||
magnitude *= 256; | ||
} | ||
if (tag >= 128) { | ||
sum -= magnitude; | ||
} | ||
return sum; | ||
} | ||
private readByte(): number { | ||
if (this.index >= this.buf.byteLength) { | ||
throw new MarshalError("marshal data too short"); | ||
} | ||
const byte = this.buf.readUInt8(this.index); | ||
this.index++; | ||
return byte; | ||
} | ||
private readString(): string { | ||
return this.readBytes().toString("utf-8"); | ||
} | ||
private readBytes(): Buffer { | ||
const length = this.readLength("string"); | ||
if (this.index + length > this.buf.byteLength) { | ||
throw new MarshalError("marshal data too short"); | ||
} | ||
const bytes = this.buf.slice(this.index, this.index + length); | ||
this.index += length; | ||
return bytes; | ||
} | ||
private entry<T>(obj: T): T { | ||
this.objects.push(obj); | ||
return obj; | ||
} | ||
} | ||
export { MarshalError } from "./error"; | ||
export { parse } from "./parse"; | ||
export * as Marshal from "./marshal-object"; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
38777
14
19
1068
83
2
1
+ Added@types/node@^16.4.11
+ Added@types/node@16.18.119(transitive)