Skip to content

Commit

Permalink
Optional Root Check Demo
Browse files Browse the repository at this point in the history
Trying out a few options for this. Either the plain read/write APIs can specifically handle if a root tag should be validated, and it can be an argument to these same original functions, or it could be a new function you import to specifically validate whether it is a root tag or not.

This is more a demo/test and placeholder to see how this might work.

I still have to adjust the options to work with writing too.

```sh
echo '[B;1b]' | nbtify --anyroot
```

#54
  • Loading branch information
Offroaders123 committed Nov 3, 2024
1 parent ef8bbcd commit d00ab48
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 22 deletions.
5 changes: 5 additions & 0 deletions src/bin/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ENDIAN_PATTERN = /^--endian=/;
const COMPRESSION_PATTERN = /^--compression=/;
const BEDROCK_LEVEL_PATTERN = /^(?:--bedrock-level$|--bedrock-level=)/;
const SPACE_PATTERN = /^--space=/;
const ROOT_CHECK_PATTERN = /^--anyroot$/;

export const getFile = (args: string[]): string | true => !process.stdin.isTTY
? true
Expand All @@ -26,6 +27,7 @@ for (const arg of args) {
case COMPRESSION_PATTERN.test(arg):
case BEDROCK_LEVEL_PATTERN.test(arg):
case SPACE_PATTERN.test(arg):
case ROOT_CHECK_PATTERN.test(arg):
break;
default:
throw new TypeError(`Unexpected argument '${arg}'`);
Expand All @@ -42,6 +44,9 @@ export const getSNBT = (args: string[]): boolean => args
export const getJSON = (args: string[]): boolean => args
.some(arg => JSON_PATTERN.test(arg));

export const getRootCheck = (args: string[]): boolean => !args
.some(arg => ROOT_CHECK_PATTERN.test(arg));

const getRootName = (args: string[]): NBTDataOptions["rootName"] => args
.find(arg => ROOT_NAME_PATTERN.test(arg))
?.replace(ROOT_NAME_PATTERN, "");
Expand Down
21 changes: 11 additions & 10 deletions src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { readFile } from "node:fs/promises";
import { inspect } from "node:util";
import { read, write, parse, stringify, NBTData } from "../index.js";
import { readStdin, writeStdout } from "./input.js";
import { getFile, getNBT, getSNBT, getJSON, getFormat, getSpace } from "./args.js";
import { getFile, getNBT, getSNBT, getJSON, getRootCheck, getFormat, getSpace } from "./args.js";

import type { RootTag } from "../index.js";

Expand All @@ -19,6 +19,7 @@ process.on("uncaughtException", error => {
const file = getFile(args);
const nbt = getNBT(args);
const snbt = getSNBT(args);
const rootCheck = getRootCheck(args);
const json = getJSON(args);
const format = getFormat(args);
const space = getSpace(args);
Expand All @@ -28,12 +29,12 @@ process.on("uncaughtException", error => {
let input: RootTag | NBTData;

if (file === true) {
input = await readBuffer(buffer);
input = await readBuffer(buffer, rootCheck);
} else {
try {
input = await readExtension(buffer, file);
input = await readExtension(buffer, file, rootCheck);
} catch {
input = await readBuffer(buffer);
input = await readBuffer(buffer, rootCheck);
}
}

Expand All @@ -51,23 +52,23 @@ process.on("uncaughtException", error => {
: await write(output);
await writeStdout(result);

async function readExtension(buffer: Buffer, file: string): Promise<RootTag | NBTData> {
async function readExtension(buffer: Buffer, file: string, rootCheck: boolean): Promise<RootTag | NBTData> {
const extension: string = extname(file);
switch (extension) {
case ".json": return JSON.parse(buffer.toString("utf-8")) as RootTag;
case ".snbt": return parse(buffer.toString("utf-8"));
default: return read(buffer);
case ".snbt": return parse(buffer.toString("utf-8"), rootCheck);
default: return read(buffer, { rootCheck });
}
}

async function readBuffer(buffer: Buffer): Promise<RootTag | NBTData> {
async function readBuffer(buffer: Buffer, rootCheck: boolean): Promise<RootTag | NBTData> {
try {
return JSON.parse(buffer.toString("utf-8")) as RootTag;
} catch {
try {
return parse(buffer.toString("utf-8"));
return parse(buffer.toString("utf-8"), rootCheck);
} catch {
return read(buffer);
return read(buffer, { rootCheck });
}
}
}
10 changes: 7 additions & 3 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const UNQUOTED_STRING_PATTERN = /^[0-9A-Za-z.+_-]+$/;
/**
* Converts an SNBT string into an NBT object.
*/
export function parse<T extends RootTagLike = RootTag>(data: string): T {
export function parse<T extends RootTagLike = RootTag>(data: string, rootCheck: boolean = true): T {
if (typeof data !== "string") {
data satisfies never;
throw new TypeError("First parameter must be a string");
}

return new SNBTReader().parseRoot(data) as T;
return new SNBTReader().parseRoot(data, rootCheck) as T;
}

class SNBTReader {
Expand Down Expand Up @@ -47,7 +47,11 @@ class SNBTReader {
}
}

parseRoot(data: string): RootTag {
parseRoot(data: string, rootCheck: boolean): RootTag {
if (!rootCheck) {
return this.#parseTag(data, "[root]") as RootTag;
}

this.#skipWhitespace(data);

this.#i = this.#index;
Expand Down
9 changes: 5 additions & 4 deletions src/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface ReadOptions {
compression: Compression;
bedrockLevel: BedrockLevel;
strict: boolean;
rootCheck: boolean;
}

/**
Expand All @@ -35,7 +36,7 @@ export async function read<T extends RootTagLike = RootTag>(data: Uint8Array | A
}

const reader = new NBTReader(data, options.endian !== "big", options.endian === "little-varint");
let { rootName, endian, compression, bedrockLevel, strict = true } = options;
let { rootName, endian, compression, bedrockLevel, strict = true, rootCheck = true } = options;

if (rootName !== undefined && typeof rootName !== "boolean" && typeof rootName !== "string" && rootName !== null) {
rootName satisfies never;
Expand Down Expand Up @@ -116,7 +117,7 @@ export async function read<T extends RootTagLike = RootTag>(data: Uint8Array | A
bedrockLevel = reader.hasBedrockLevelHeader(endian);
}

return reader.readRoot<T>({ rootName, endian, compression, bedrockLevel, strict });
return reader.readRoot<T>({ rootName, endian, compression, bedrockLevel, strict, rootCheck });
}

class NBTReader {
Expand Down Expand Up @@ -156,7 +157,7 @@ class NBTReader {
}
}

async readRoot<T extends RootTagLike = RootTag>({ rootName, endian, compression, bedrockLevel, strict }: ReadOptions): Promise<NBTData<T>> {
async readRoot<T extends RootTagLike = RootTag>({ rootName, endian, compression, bedrockLevel, strict, rootCheck }: ReadOptions): Promise<NBTData<T>> {
if (compression !== null) {
this.#data = await decompress(this.#data, compression);
this.#view = new DataView(this.#data.buffer);
Expand All @@ -169,7 +170,7 @@ class NBTReader {
}

const type: TAG = this.#readTagType();
if (type !== TAG.LIST && type !== TAG.COMPOUND) {
if (rootCheck && type !== TAG.LIST && type !== TAG.COMPOUND) {
throw new Error(`Expected an opening List or Compound tag at the start of the buffer, encountered tag type '${type}'`);
}

Expand Down
11 changes: 6 additions & 5 deletions src/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import type { Tag, RootTag, RootTagLike, ByteTag, BooleanTag, ShortTag, IntTag,
*
* If a format option isn't specified, the value of the equivalent property on the NBTData object will be used.
*/
export async function write<T extends RootTagLike = RootTag>(data: T | NBTData<T>, options: NBTDataOptions = {}): Promise<Uint8Array> {
export async function write<T extends RootTagLike = RootTag>(data: T | NBTData<T>, options: NBTDataOptions & { rootCheck?: boolean; } = {}): Promise<Uint8Array> {
data = new NBTData(data, options);

const { rootName, endian, compression, bedrockLevel } = data as NBTData<T>;
const rootCheck: boolean = options.rootCheck ?? true;

if (typeof data !== "object" || data === null) {
data satisfies never;
Expand All @@ -39,7 +40,7 @@ export async function write<T extends RootTagLike = RootTag>(data: T | NBTData<T
}

const writer = new NBTWriter(endian !== "big", endian === "little-varint");
return writer.writeRoot(data as NBTData<T>);
return writer.writeRoot(data as NBTData<T>, rootCheck);
}

class NBTWriter {
Expand Down Expand Up @@ -82,11 +83,11 @@ class NBTWriter {
return this.#data.slice(0, this.#byteOffset);
}

async writeRoot<T extends RootTagLike = RootTag>(data: NBTData<T>): Promise<Uint8Array> {
async writeRoot<T extends RootTagLike = RootTag>(data: NBTData<T>, rootCheck: boolean): Promise<Uint8Array> {
const { data: root, rootName, endian, compression, bedrockLevel } = data;
const littleEndian: boolean = endian !== "big";
const type: Tag | null = getTagType(root);
if (type !== TAG.LIST && type !== TAG.COMPOUND) {
const type: TAG | null = getTagType(root);
if (type === null || rootCheck && type !== TAG.LIST && type !== TAG.COMPOUND) {
throw new TypeError(`Encountered unexpected Root tag type '${type}', must be either a List or Compound tag`);
}

Expand Down

0 comments on commit d00ab48

Please sign in to comment.