| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- import * as text from './text.js';
- const json = {};
- const bson = {};
- json.TextReader = class {
- static open(data) {
- const decoder = text.Decoder.open(data);
- let state = '';
- for (let i = 0; i < 0x1000; i++) {
- const c = decoder.decode();
- if (state === 'match') {
- break;
- }
- if (c === undefined || c === '\0') {
- if (state === '') {
- return null;
- }
- break;
- }
- if (c <= ' ') {
- if (c !== ' ' && c !== '\n' && c !== '\r' && c !== '\t') {
- return null;
- }
- continue;
- }
- switch (state) {
- case '':
- if (c === '{') {
- state = '{}';
- } else if (c === '[') {
- state = '[]';
- } else {
- return null;
- }
- break;
- case '[]':
- if (c !== '"' && c !== '-' && c !== '+' && c !== '{' && c !== '[' && (c < '0' || c > '9')) {
- return null;
- }
- state = 'match';
- break;
- case '{}':
- if (c !== '"') {
- return null;
- }
- state = 'match';
- break;
- default:
- break;
- }
- }
- return new json.TextReader(decoder);
- }
- constructor(decoder) {
- this._decoder = decoder;
- this._decoder.position = 0;
- this._escape = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
- }
- read() {
- const stack = [];
- this._decoder.position = 0;
- this._position = 0;
- this._char = this._decoder.decode();
- this._whitespace();
- let obj = null;
- let first = true;
- for (;;) {
- if (Array.isArray(obj)) {
- this._whitespace();
- let c = this._char;
- if (c === ']') {
- this._next();
- this._whitespace();
- if (stack.length > 0) {
- obj = stack.pop();
- first = false;
- continue;
- }
- if (this._char !== undefined) {
- this._unexpected();
- }
- return obj;
- }
- if (!first) {
- if (this._char !== ',') {
- this._unexpected();
- }
- this._next();
- this._whitespace();
- c = this._char;
- }
- first = false;
- switch (c) {
- case '{': {
- this._next();
- stack.push(obj);
- const item = {};
- obj.push(item);
- obj = item;
- first = true;
- break;
- }
- case '[': {
- this._next();
- stack.push(obj);
- const item = [];
- obj.push(item);
- obj = item;
- first = true;
- break;
- }
- default: {
- obj.push(c === '"' ? this._string() : this._literal());
- break;
- }
- }
- } else if (obj instanceof Object) {
- this._whitespace();
- let c = this._char;
- if (c === '}') {
- this._next();
- this._whitespace();
- if (stack.length > 0) {
- obj = stack.pop();
- first = false;
- continue;
- }
- if (this._char !== undefined) {
- this._unexpected();
- }
- return obj;
- }
- if (!first) {
- if (this._char !== ',') {
- this._unexpected();
- }
- this._next();
- this._whitespace();
- c = this._char;
- }
- first = false;
- if (c === '"') {
- const key = this._string();
- switch (key) {
- case '__proto__':
- throw new json.Error(`Invalid key '${key}' ${this._location()}`);
- default:
- break;
- }
- this._whitespace();
- if (this._char !== ':') {
- this._unexpected();
- }
- this._next();
- this._whitespace();
- c = this._char;
- switch (c) {
- case '{': {
- this._next();
- stack.push(obj);
- const value = {};
- obj[key] = value;
- obj = value;
- first = true;
- break;
- }
- case '[': {
- this._next();
- stack.push(obj);
- const value = [];
- obj[key] = value;
- obj = value;
- first = true;
- break;
- }
- default: {
- obj[key] = c === '"' ? this._string() : this._literal();
- break;
- }
- }
- this._whitespace();
- continue;
- }
- this._unexpected();
- } else {
- const c = this._char;
- switch (c) {
- case '{': {
- this._next();
- this._whitespace();
- obj = {};
- first = true;
- break;
- }
- case '[': {
- this._next();
- this._whitespace();
- obj = [];
- first = true;
- break;
- }
- default: {
- let value = null;
- if (c === '"') {
- value = this._string();
- } else if (c >= '0' && c <= '9') {
- value = this._number();
- } else {
- value = this._literal();
- }
- this._whitespace();
- if (this._char !== undefined) {
- this._unexpected();
- }
- return value;
- }
- }
- }
- }
- }
- _next() {
- if (this._char === undefined) {
- this._unexpected();
- }
- this._position = this._decoder.position;
- this._char = this._decoder.decode();
- }
- _whitespace() {
- while (this._char === ' ' || this._char === '\n' || this._char === '\r' || this._char === '\t') {
- this._next();
- }
- }
- _literal() {
- const c = this._char;
- if (c >= '0' && c <= '9') {
- return this._number();
- }
- switch (c) {
- case 't': this._expect('true'); return true;
- case 'f': this._expect('false'); return false;
- case 'n': this._expect('null'); return null;
- case 'N': this._expect('NaN'); return NaN;
- case 'I': this._expect('Infinity'); return Infinity;
- case '-': return this._number();
- default: this._unexpected();
- }
- return null;
- }
- _number() {
- let value = '';
- if (this._char === '-') {
- value = '-';
- this._next();
- }
- if (this._char === 'I') {
- this._expect('Infinity');
- return -Infinity;
- }
- const c = this._char;
- if (c < '0' || c > '9') {
- this._unexpected();
- }
- value += c;
- this._next();
- if (c === '0') {
- const n = this._char;
- if (n >= '0' && n <= '9') {
- this._unexpected();
- }
- }
- while (this._char >= '0' && this._char <= '9') {
- value += this._char;
- this._next();
- }
- if (this._char === '.') {
- value += '.';
- this._next();
- const n = this._char;
- if (n < '0' || n > '9') {
- this._unexpected();
- }
- while (this._char >= '0' && this._char <= '9') {
- value += this._char;
- this._next();
- }
- }
- if (this._char === 'e' || this._char === 'E') {
- value += this._char;
- this._next();
- const s = this._char;
- if (s === '-' || s === '+') {
- value += this._char;
- this._next();
- }
- const c = this._char;
- if (c < '0' || c > '9') {
- this._unexpected();
- }
- value += this._char;
- this._next();
- while (this._char >= '0' && this._char <= '9') {
- value += this._char;
- this._next();
- }
- }
- return Number(value);
- }
- _string() {
- let value = '';
- this._next();
- while (this._char !== '"') {
- if (this._char === '\\') {
- this._next();
- if (this._char === 'u') {
- this._next();
- let uffff = 0;
- for (let i = 0; i < 4; i ++) {
- const hex = parseInt(this._char, 16);
- if (!isFinite(hex)) {
- this._unexpected();
- }
- this._next();
- uffff = uffff * 16 + hex;
- }
- value += String.fromCharCode(uffff);
- } else if (this._escape[this._char]) {
- value += this._escape[this._char];
- this._next();
- } else {
- this._unexpected();
- }
- } else if (this._char < ' ') {
- this._unexpected();
- } else {
- value += this._char;
- this._next();
- }
- }
- this._next();
- return value;
- }
- _expect(value) {
- for (let i = 0; i < value.length; i++) {
- if (value[i] !== this._char) {
- this._unexpected();
- }
- this._next();
- }
- }
- _unexpected() {
- let c = this._char;
- if (c === undefined) {
- throw new json.Error('Unexpected end of JSON input.');
- } else if (c === '"') {
- c = 'string';
- } else if ((c >= '0' && c <= '9') || c === '-') {
- c = 'number';
- } else {
- if (c < ' ' || c > '\x7F') {
- const name = Object.keys(this._escape).filter((key) => this._escape[key] === c);
- c = (name.length === 1) ? `\\${name}` : `\\u${(`000${c.charCodeAt(0).toString(16)}`).slice(-4)}`;
- }
- c = `token '${c}'`;
- }
- throw new json.Error(`Unexpected ${c} ${this._location()}`);
- }
- _location() {
- let line = 1;
- let column = 1;
- this._decoder.position = 0;
- let c = '';
- do {
- if (this._decoder.position === this._position) {
- return `at ${line}:${column}.`;
- }
- c = this._decoder.decode();
- if (c === '\n') {
- line++;
- column = 1;
- } else {
- column++;
- }
- }
- while (c !== undefined);
- return `at ${line}:${column}.`;
- }
- };
- json.BinaryReader = class {
- static open(data) {
- const length = data.length;
- const buffer = data instanceof Uint8Array ? data : data.peek(Math.min(data.length, 8));
- const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
- if (buffer.length >= 4 && view.getInt32(0, true) === length) {
- return new json.BinaryReader(data, 'bson');
- }
- const [signature] = buffer;
- if (signature === 0x7B || signature === 0x5B) {
- return new json.BinaryReader(data, 'ubj');
- }
- return null;
- }
- constructor(data, format) {
- this._buffer = data instanceof Uint8Array ? data : data.peek();
- this._format = format;
- }
- read() {
- switch (this._format) {
- case 'bson':
- return this._readBson();
- case 'ubj':
- return this._readUbj();
- default:
- throw new json.Error(`Unsupported binary JSON format '${this._format}'.`);
- }
- }
- _readBson() {
- const buffer = this._buffer;
- const length = buffer.length;
- const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
- const asciiDecoder = new TextDecoder('ascii');
- const utf8Decoder = new TextDecoder('utf-8');
- let position = 0;
- const skip = (offset) => {
- position += offset;
- if (position > length) {
- throw new bson.Error(`Expected ${position + length} more bytes. The file might be corrupted. Unexpected end of file.`);
- }
- };
- const header = () => {
- const start = position;
- skip(4);
- const size = view.getInt32(start, 4);
- if (size < 5 || start + size > length || buffer[start + size - 1] !== 0x00) {
- throw new bson.Error('Invalid file size.');
- }
- };
- header();
- const stack = [];
- let obj = {};
- for (;;) {
- skip(1);
- const type = buffer[position - 1];
- if (type === 0x00) {
- if (stack.length === 0) {
- break;
- }
- obj = stack.pop();
- continue;
- }
- const start = position;
- position = buffer.indexOf(0x00, start) + 1;
- const key = asciiDecoder.decode(buffer.subarray(start, position - 1));
- let value = null;
- switch (type) {
- case 0x01: { // float64
- const start = position;
- skip(8);
- value = view.getFloat64(start, true);
- break;
- }
- case 0x02: { // string
- skip(4);
- const size = view.getInt32(position - 4, true);
- const start = position;
- skip(size);
- value = utf8Decoder.decode(buffer.subarray(start, position - 1));
- if (buffer[position - 1] !== 0) {
- throw new bson.Error('String missing terminal 0.');
- }
- break;
- }
- case 0x03: { // object
- header();
- value = {};
- break;
- }
- case 0x04: { // array
- header();
- value = [];
- break;
- }
- case 0x05: { // bytes
- const start = position;
- skip(5);
- const size = view.getInt32(start, true);
- const subtype = buffer[start + 4];
- if (subtype !== 0x00) {
- throw new bson.Error(`Unsupported binary subtype '${subtype}'.`);
- }
- skip(size);
- value = buffer.subarray(start + 5, position);
- break;
- }
- case 0x08: { // boolean
- skip(1);
- value = buffer[position - 1];
- if (value > 1) {
- throw new bson.Error(`Invalid boolean value '${value}'.`);
- }
- value = value === 1 ? true : false;
- break;
- }
- case 0x0A:
- value = null;
- break;
- case 0x10: {
- const start = position;
- skip(4);
- value = view.getInt32(start, true);
- break;
- }
- case 0x11: { // uint64
- const start = position;
- skip(8);
- value = Number(view.getBigUint64(start, true));
- break;
- }
- case 0x12: { // int64
- const start = position;
- skip(8);
- value = Number(view.getBigInt64(start, true));
- break;
- }
- default: {
- throw new bson.Error(`Unsupported value type '${type}'.`);
- }
- }
- if (Array.isArray(obj)) {
- if (obj.length !== parseInt(key, 10)) {
- throw new bson.Error(`Invalid array index '${key}'.`);
- }
- obj.push(value);
- } else {
- switch (key) {
- case '__proto__':
- case 'constructor':
- case 'prototype':
- throw new bson.Error(`Invalid key '${key}' at ${position}'.`);
- default:
- break;
- }
- obj[key] = value;
- }
- if (type === 0x03 || type === 0x04) {
- stack.push(obj);
- obj = value;
- }
- }
- if (position !== length) {
- throw new bson.Error(`Unexpected data at '${position}'.`);
- }
- return obj;
- }
- _readUbj() {
- throw new json.Error('Unsupported JSON UBJ data.');
- }
- };
- json.Error = class extends Error {
- constructor(message) {
- super(message);
- this.name = 'JSON Error';
- }
- };
- bson.Error = class extends Error {
- constructor(message) {
- super(message);
- this.name = 'BSON Error';
- }
- };
- export const TextReader = json.TextReader;
- export const BinaryReader = json.BinaryReader;
|