-
Notifications
You must be signed in to change notification settings - Fork 170
Allow decoding raw strings #235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,17 @@ export type DecoderOptions<ContextType = undefined> = Readonly< | |
*/ | ||
useBigInt64: boolean; | ||
|
||
/** | ||
* By default, string values will be decoded as UTF-8 strings. However, if this option is true, | ||
* string values will be returned as Uint8Arrays without additional decoding. | ||
* | ||
* This is useful if the strings may contain invalid UTF-8 sequences. | ||
* | ||
* Note that this option only applies to string values, not map keys. Additionally, when | ||
* enabled, raw string length is limited by the maxBinLength option. | ||
*/ | ||
Comment on lines
+29
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not do this for map keys as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This library doesn't support binary keys, so I decided to skip that case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it doesn't. Sounds a good decision. |
||
rawStrings: boolean; | ||
|
||
/** | ||
* Maximum string length. | ||
* | ||
|
@@ -195,6 +206,7 @@ export class Decoder<ContextType = undefined> { | |
private readonly extensionCodec: ExtensionCodecType<ContextType>; | ||
private readonly context: ContextType; | ||
private readonly useBigInt64: boolean; | ||
private readonly rawStrings: boolean; | ||
private readonly maxStrLength: number; | ||
private readonly maxBinLength: number; | ||
private readonly maxArrayLength: number; | ||
|
@@ -215,6 +227,7 @@ export class Decoder<ContextType = undefined> { | |
this.context = (options as { context: ContextType } | undefined)?.context as ContextType; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined | ||
|
||
this.useBigInt64 = options?.useBigInt64 ?? false; | ||
this.rawStrings = options?.rawStrings ?? false; | ||
this.maxStrLength = options?.maxStrLength ?? UINT32_MAX; | ||
this.maxBinLength = options?.maxBinLength ?? UINT32_MAX; | ||
this.maxArrayLength = options?.maxArrayLength ?? UINT32_MAX; | ||
|
@@ -399,7 +412,7 @@ export class Decoder<ContextType = undefined> { | |
} else { | ||
// fixstr (101x xxxx) 0xa0 - 0xbf | ||
const byteLength = headByte - 0xa0; | ||
object = this.decodeUtf8String(byteLength, 0); | ||
object = this.decodeString(byteLength, 0); | ||
} | ||
} else if (headByte === 0xc0) { | ||
// nil | ||
|
@@ -451,15 +464,15 @@ export class Decoder<ContextType = undefined> { | |
} else if (headByte === 0xd9) { | ||
// str 8 | ||
const byteLength = this.lookU8(); | ||
object = this.decodeUtf8String(byteLength, 1); | ||
object = this.decodeString(byteLength, 1); | ||
} else if (headByte === 0xda) { | ||
// str 16 | ||
const byteLength = this.lookU16(); | ||
object = this.decodeUtf8String(byteLength, 2); | ||
object = this.decodeString(byteLength, 2); | ||
} else if (headByte === 0xdb) { | ||
// str 32 | ||
const byteLength = this.lookU32(); | ||
object = this.decodeUtf8String(byteLength, 4); | ||
object = this.decodeString(byteLength, 4); | ||
} else if (headByte === 0xdc) { | ||
// array 16 | ||
const size = this.readU16(); | ||
|
@@ -637,6 +650,13 @@ export class Decoder<ContextType = undefined> { | |
this.stack.pushArrayState(size); | ||
} | ||
|
||
private decodeString(byteLength: number, headerOffset: number): string | Uint8Array { | ||
if (!this.rawStrings || this.stateIsMapKey()) { | ||
return this.decodeUtf8String(byteLength, headerOffset); | ||
} | ||
return this.decodeBinary(byteLength, headerOffset); | ||
} | ||
|
||
private decodeUtf8String(byteLength: number, headerOffset: number): string { | ||
if (byteLength > this.maxStrLength) { | ||
throw new DecodeError( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import assert from "assert"; | ||
import { encode, decode } from "../src"; | ||
import type { DecoderOptions } from "../src"; | ||
|
||
describe("decode with rawStrings specified", () => { | ||
const options = { rawStrings: true } satisfies DecoderOptions; | ||
|
||
it("decodes string as binary", () => { | ||
const actual = decode(encode("foo"), options); | ||
const expected = Uint8Array.from([0x66, 0x6f, 0x6f]); | ||
assert.deepStrictEqual(actual, expected); | ||
}); | ||
|
||
it("decodes invalid UTF-8 string as binary", () => { | ||
const invalidUtf8String = Uint8Array.from([61, 180, 118, 220, 39, 166, 43, 68, 219, 116, 105, 84, 121, 46, 122, 136, 233, 221, 15, 174, 247, 19, 50, 176, 184, 221, 66, 188, 171, 36, 135, 121]); | ||
const encoded = Uint8Array.from([196, 32, 61, 180, 118, 220, 39, 166, 43, 68, 219, 116, 105, 84, 121, 46, 122, 136, 233, 221, 15, 174, 247, 19, 50, 176, 184, 221, 66, 188, 171, 36, 135, 121]); | ||
|
||
const actual = decode(encoded, options); | ||
assert.deepStrictEqual(actual, invalidUtf8String); | ||
}); | ||
|
||
it("decodes object keys as strings", () => { | ||
const actual = decode(encode({ key: "foo" }), options); | ||
const expected = { key: Uint8Array.from([0x66, 0x6f, 0x6f]) }; | ||
assert.deepStrictEqual(actual, expected); | ||
}); | ||
|
||
it("ignores maxStrLength", () => { | ||
const lengthLimitedOptions = { ...options, maxStrLength: 1 } satisfies DecoderOptions; | ||
|
||
const actual = decode(encode("foo"), lengthLimitedOptions); | ||
const expected = Uint8Array.from([0x66, 0x6f, 0x6f]); | ||
assert.deepStrictEqual(actual, expected); | ||
}); | ||
|
||
it("respects maxBinLength", () => { | ||
const lengthLimitedOptions = { ...options, maxBinLength: 1 } satisfies DecoderOptions; | ||
|
||
assert.throws(() => { | ||
decode(encode("foo"), lengthLimitedOptions); | ||
}, /max length exceeded/i); | ||
}); | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.