Skip to content
File

Blob: src/node/internal/internal_stringdecoder.ts

typescript186 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import { Buffer } from 'node-internal:internal_buffer';
27import { normalizeEncoding } from 'node-internal:internal_utils';
28import {
29 ERR_INVALID_ARG_TYPE,
30 ERR_INVALID_THIS,
31 ERR_UNKNOWN_ENCODING,
32} from 'node-internal:internal_errors';
33import { default as bufferUtil } from 'node-internal:buffer';
34import type { StringDecoder as _StringDecoder } from 'node:string_decoder';
35 
36const kIncompleteCharactersStart = 0;
37const kIncompleteCharactersEnd = 4;
38const kMissingBytes = 4;
39const kBufferedBytes = 5;
40const kEncoding = 6;
41const kSize = 7;
42 
43// The order of this array should be in sync with i18n.h
44// Encoding enum. Index of this array defines the uint8_t
45// value of an encoding.
46const encodings = [
47 'ascii',
48 'latin1',
49 'utf8',
50 'utf16le',
51 'base64',
52 'base64url',
53 'hex',
54];
55 
56const kNativeDecoder = Symbol('kNativeDecoder');
57 
58// @ts-expect-error TS2323 Cannot redeclare exported variable
59export declare class StringDecoder extends _StringDecoder {
60 [kNativeDecoder]?: Buffer & {
61 [kBufferedBytes]?: number;
62 [kMissingBytes]?: number;
63 [kEncoding]?: number;
64 };
65 
66 constructor(encoding?: string);
67 
68 encoding: string;
69 readonly lastChar: Uint8Array;
70 readonly lastNeed: number;
71 readonly lastTotal: number;
72 write(buf: ArrayBufferView | DataView | string): string;
73 end(buf?: ArrayBufferView | DataView | string): string;
74 text(buf: ArrayBufferView | DataView | string, offset?: number): string;
75}
76 
77// @ts-expect-error TS2323 Cannot redeclare exported variable
78export function StringDecoder(
79 this: StringDecoder,
80 encoding: string = 'utf8'
81): StringDecoder {
82 const normalizedEncoding = normalizeEncoding(encoding);
83 if (normalizedEncoding === undefined) {
84 throw new ERR_UNKNOWN_ENCODING(encoding);
85 }
86 this[kNativeDecoder] = Buffer.alloc(kSize);
87 this[kNativeDecoder][kEncoding] = normalizedEncoding;
88 this.encoding = encodings[normalizedEncoding] as string;
89 return this;
90}
91 
92function write(
93 this: StringDecoder,
94 buf: ArrayBufferView | DataView | string
95): string {
96 if (this[kNativeDecoder] === undefined) {
97 throw new ERR_INVALID_THIS('StringDecoder');
98 }
99 if (typeof buf === 'string') {
100 return buf;
101 }
102 if (!ArrayBuffer.isView(buf)) {
103 throw new ERR_INVALID_ARG_TYPE(
104 'buf',
105 ['Buffer', 'TypedArray', 'DataView', 'string'],
106 buf
107 );
108 }
109 const buffer = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
110 return bufferUtil.decode(buffer, this[kNativeDecoder]);
111}
112 
113function end(
114 this: StringDecoder,
115 buf?: ArrayBufferView | DataView | string
116): string {
117 if (this[kNativeDecoder] === undefined) {
118 throw new ERR_INVALID_THIS('StringDecoder');
119 }
120 let ret = '';
121 if (buf !== undefined) {
122 ret = this.write(buf);
123 }
124 if ((this[kNativeDecoder][kBufferedBytes] as number) > 0) {
125 ret += bufferUtil.flush(this[kNativeDecoder]);
126 }
127 return ret;
128}
129 
130function text(
131 this: StringDecoder,
132 buf: NodeJS.TypedArray | string,
133 offset?: number
134): string {
135 if (this[kNativeDecoder] === undefined) {
136 throw new ERR_INVALID_THIS('StringDecoder');
137 }
138 this[kNativeDecoder][kMissingBytes] = 0;
139 this[kNativeDecoder][kBufferedBytes] = 0;
140 return this.write(buf.slice(offset));
141}
142 
143StringDecoder.prototype.write = write;
144StringDecoder.prototype.end = end;
145StringDecoder.prototype.text = text;
146 
147Object.defineProperties(StringDecoder.prototype, {
148 lastChar: {
149 enumerable: true,
150 get(this: StringDecoder): Buffer {
151 if (this[kNativeDecoder] === undefined) {
152 throw new ERR_INVALID_THIS('StringDecoder');
153 }
154 return this[kNativeDecoder].subarray(
155 kIncompleteCharactersStart,
156 kIncompleteCharactersEnd
157 ) as Buffer;
158 },
159 },
160 lastNeed: {
161 enumerable: true,
162 get(this: StringDecoder): number {
163 if (this[kNativeDecoder] === undefined) {
164 throw new ERR_INVALID_THIS('StringDecoder');
165 }
166 return this[kNativeDecoder][kMissingBytes] as number;
167 },
168 },
169 lastTotal: {
170 enumerable: true,
171 get(this: StringDecoder): number {
172 if (this[kNativeDecoder] === undefined) {
173 throw new ERR_INVALID_THIS('StringDecoder');
174 }
175 return (
176 (this[kNativeDecoder][kBufferedBytes] ?? 0) +
177 (this[kNativeDecoder][kMissingBytes] ?? 0)
178 );
179 },
180 },
181});
182 
183export default {
184 StringDecoder,
185};