UNPKG

@cloudpss/ubjson

Version:

427 lines (412 loc) 14 kB
import type { DecodeOptions } from '../options.js'; import { constants } from './constants.js'; import { UnexpectedEofError, unsupportedType } from './errors.js'; import { decode } from './string-decoder.js'; import { toUint8Array } from './utils.js'; const { defineProperty } = Object; const { fromCharCode } = String; /** 数据包装 */ export interface DecodeCursor { /** 数据 */ readonly view: DataView; /** 数据 */ readonly data: Uint8Array<ArrayBuffer>; /** 当前读指针位置 */ offset: number; /** 读取到末尾时调用 */ eof(): never; /** 选项 */ readonly options: DecodeOptions | undefined; } /** 创建数据包装 */ export function DecodeCursor(data: BufferSource, options?: DecodeOptions): DecodeCursor { const d = toUint8Array(data); const v = new DataView(d.buffer, d.byteOffset, d.byteLength); return { data: d, view: v, offset: 0, eof() { throw new UnexpectedEofError(); }, options, }; } /** * 读取第一个非 NOOP 的字节 * @returns 返回读取到的字节,如果读取到末尾则返回 undefined */ export function readMarkerOrUndefined(cursor: DecodeCursor): number | undefined { let marker: number | undefined; do { marker = cursor.data[cursor.offset++]; } while (marker === constants.NO_OP); return marker; } /** * 读取第一个非 NOOP 的字节 */ export function readMarker(cursor: DecodeCursor): number { let marker: number | undefined; do { marker = cursor.data[cursor.offset++]; } while (marker === constants.NO_OP); if (marker === undefined) return cursor.eof(); return marker; } /** 读取一个大于 0 的整数 */ export function readLength(cursor: DecodeCursor): number { const marker = readMarker(cursor); let length: number; switch (marker) { case constants.INT8: length = readInt8Data(cursor); break; case constants.UINT8: length = readUint8Data(cursor); break; case constants.INT16: length = readInt16Data(cursor); break; case constants.INT32: length = readInt32Data(cursor); break; case constants.INT64: { const l = readInt64Data(cursor); if (l < 0 || l > Number.MAX_SAFE_INTEGER) { throw new Error('Invalid length'); } length = Number(l); break; } default: throw new Error(`Unexpected marker '${fromCharCode(marker)}'(${marker}) for int length`); } if (length < 0) { throw new Error('Invalid length'); } return length; } /** readInt8Data */ export function readInt8Data(cursor: DecodeCursor): number { try { return cursor.view.getInt8(cursor.offset++); } catch { return cursor.eof(); } } /** readUint8Data */ export function readUint8Data(cursor: DecodeCursor): number { const result = cursor.data[cursor.offset++]; if (result === undefined) return cursor.eof(); return result; } /** readInt16Data */ export function readInt16Data(cursor: DecodeCursor): number { try { const result = cursor.view.getInt16(cursor.offset); cursor.offset += 2; return result; } catch { return cursor.eof(); } } /** readInt32Data */ export function readInt32Data(cursor: DecodeCursor): number { try { const result = cursor.view.getInt32(cursor.offset); cursor.offset += 4; return result; } catch { return cursor.eof(); } } /** readInt64Data */ export function readInt64Data(cursor: DecodeCursor): bigint { try { const result = cursor.view.getBigInt64(cursor.offset); cursor.offset += 8; return result; } catch { return cursor.eof(); } } /** readFloat32Data */ export function readFloat32Data(cursor: DecodeCursor): number { try { const result = cursor.view.getFloat32(cursor.offset); cursor.offset += 4; return result; } catch { return cursor.eof(); } } /** readFloat64Data */ export function readFloat64Data(cursor: DecodeCursor): number { try { const result = cursor.view.getFloat64(cursor.offset); cursor.offset += 8; return result; } catch { return cursor.eof(); } } /** 读取数据 */ export function read(cursor: DecodeCursor): unknown { const marker = readMarker(cursor); return readData(cursor, marker); } /** 处理 `__proto__` */ export function protoAction(cursor: Pick<DecodeCursor, 'options'>, obj: Record<string, unknown>, value: unknown): void { const protoAction = cursor.options?.protoAction; if (protoAction === 'error') { throw new Error('Unexpected "__proto__"'); } else if (protoAction === 'allow') { defineProperty(obj, '__proto__', { value, enumerable: true, configurable: true, writable: true, }); } else { // 'remove' return; } } /** 处理 `constructor` */ export function constructorAction( cursor: Pick<DecodeCursor, 'options'>, obj: Record<string, unknown>, value: unknown, ): void { const constructorAction = cursor.options?.constructorAction; if (constructorAction === 'error') { throw new Error('Unexpected "constructor"'); } else if (constructorAction === 'remove') { return; } else { // 'allow' // eslint-disable-next-line @typescript-eslint/dot-notation obj['constructor'] = value; } } /** 读取优化对象数据 */ function readObjectOptimizedData(cursor: DecodeCursor, marker: OptimizedFormatMarkers): unknown { const { count, type } = marker; const object: Record<string, unknown> = {}; for (let i = 0; i < count; i++) { const key = readKey(cursor); const value = readData(cursor, type ?? readMarker(cursor)); if (key === '__proto__') { protoAction(cursor, object, value); continue; } if (key === 'constructor') { constructorAction(cursor, object, value); continue; } object[key] = value; } return object; } /** 根据标签读取后续数据 */ export function readData(cursor: DecodeCursor, marker: number): unknown { // 按照出现频率排序 switch (marker) { case constants.STRING: { const length = readLength(cursor); const begin = cursor.offset; const end = begin + length; cursor.offset = end; const { data } = cursor; if (end > data.length) cursor.eof(); return decode(data, begin, end); } case constants.OBJECT: { const markers = readOptimizedFormatMarkers(cursor); if (markers == null) { // 直到 '}' const object: Record<string, unknown> = {}; while (readMarker(cursor) !== constants.OBJECT_END) { cursor.offset--; const key = readKey(cursor); const value = read(cursor); if (key === '__proto__') { protoAction(cursor, object, value); continue; } if (key === 'constructor') { constructorAction(cursor, object, value); continue; } object[key] = value; } return object; } return readObjectOptimizedData(cursor, markers); } case constants.ARRAY: { const markers = readOptimizedFormatMarkers(cursor); if (markers == null) { const array = []; for (;;) { const marker = readMarker(cursor); // 直到 ']' if (marker === constants.ARRAY_END) break; array.push(readData(cursor, marker)); } return array; } const { count, type } = markers; switch (type) { case constants.UINT8: try { const buf = new Uint8Array( cursor.data.buffer, cursor.data.byteOffset + cursor.offset, count, ).slice(); cursor.offset += count; return buf; } catch { return cursor.eof(); } case constants.INT8: try { const buf = new Int8Array( cursor.data.buffer, cursor.data.byteOffset + cursor.offset, count, ).slice(); cursor.offset += count; return buf; } catch { return cursor.eof(); } case constants.INT16: { const result = new Int16Array(count); for (let i = 0; i < count; i++) { result[i] = readInt16Data(cursor); } return result; } case constants.INT32: { const result = new Int32Array(count); for (let i = 0; i < count; i++) { result[i] = readInt32Data(cursor); } return result; } case constants.FLOAT32: { const result = new Float32Array(count); for (let i = 0; i < count; i++) { result[i] = readFloat32Data(cursor); } return result; } case constants.FLOAT64: { const result = new Float64Array(count); for (let i = 0; i < count; i++) { result[i] = readFloat64Data(cursor); } return result; } case constants.INT64: { const result = new BigInt64Array(count); for (let i = 0; i < count; i++) { result[i] = readInt64Data(cursor); } return result; } case constants.NULL: return Array.from({ length: count }).fill(null); case constants.TRUE: return Array.from({ length: count }).fill(true); case constants.FALSE: return Array.from({ length: count }).fill(false); case undefined: default: break; } const array: unknown[] = []; array.length = count; for (let i = 0; i < count; i++) { array[i] = type === undefined ? read(cursor) : readData(cursor, type); } return array; } case constants.FLOAT64: return readFloat64Data(cursor); case constants.UINT8: return readUint8Data(cursor); case constants.INT16: return readInt16Data(cursor); case constants.FLOAT32: return readFloat32Data(cursor); case constants.CHAR: return fromCharCode(readUint8Data(cursor)); case constants.INT32: return readInt32Data(cursor); case constants.INT8: return readInt8Data(cursor); case constants.NULL: return null; case constants.TRUE: return true; case constants.FALSE: return false; case constants.INT64: { const n = readInt64Data(cursor); if (n < Number.MIN_SAFE_INTEGER || n > Number.MAX_SAFE_INTEGER) { return n; } return Number(n); } case constants.HIGH_PRECISION_NUMBER: { const length = readLength(cursor); try { const _buffer = new Uint8Array(cursor.data.buffer, cursor.offset, length).slice(); cursor.offset += length; // return buffer; } catch { return cursor.eof(); } unsupportedType('high precision number'); } } if (typeof marker != 'number') return marker; throw new Error(`Unexpected marker '${fromCharCode(marker)}'(${marker})`); } /** readKey */ export function readKey(cursor: DecodeCursor): string { const length = readLength(cursor); const begin = cursor.offset; const end = begin + length; cursor.offset = end; const { data } = cursor; if (end > data.length) cursor.eof(); return decode(data, begin, end); } /** Optimized Format 数据 */ export type OptimizedFormatMarkers = { type?: number; count: number }; /** 读取 Optimized Format 数据 */ export function readOptimizedFormatMarkers(cursor: DecodeCursor): OptimizedFormatMarkers | undefined { let type; let count; switch (readMarker(cursor)) { case constants.TYPE_MARKER: type = readMarker(cursor); if (readMarker(cursor) !== constants.COUNT_MARKER) { throw new Error('Expected count marker'); } /* fall through */ case constants.COUNT_MARKER: count = readLength(cursor); break; default: // 不是 '$' 或 '#',回溯 cursor.offset--; return undefined; } return { type, count }; }