• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23
24const {
25  ArrayBufferIsView,
26  ObjectDefineProperties,
27  Symbol,
28  TypedArrayPrototypeSubarray,
29} = primordials;
30
31const { Buffer } = require('buffer');
32const {
33  kIncompleteCharactersStart,
34  kIncompleteCharactersEnd,
35  kMissingBytes,
36  kBufferedBytes,
37  kEncodingField,
38  kSize,
39  decode,
40  flush,
41  encodings,
42} = internalBinding('string_decoder');
43const internalUtil = require('internal/util');
44const {
45  ERR_INVALID_ARG_TYPE,
46  ERR_INVALID_THIS,
47  ERR_UNKNOWN_ENCODING,
48} = require('internal/errors').codes;
49const isEncoding = Buffer[internalUtil.kIsEncodingSymbol];
50
51const kNativeDecoder = Symbol('kNativeDecoder');
52
53// Do not cache `Buffer.isEncoding` when checking encoding names as some
54// modules monkey-patch it to support additional encodings
55/**
56 * Normalize encoding notation
57 * @param {string} enc
58 * @returns {"utf8" | "utf16le" | "hex" | "ascii"
59 *           | "base64" | "latin1" | "base64url"}
60 * @throws {TypeError} Throws an error when encoding is invalid
61 */
62function normalizeEncoding(enc) {
63  const nenc = internalUtil.normalizeEncoding(enc);
64  if (nenc === undefined) {
65    if (Buffer.isEncoding === isEncoding || !Buffer.isEncoding(enc))
66      throw new ERR_UNKNOWN_ENCODING(enc);
67    return enc;
68  }
69  return nenc;
70}
71
72const encodingsMap = {};
73for (let i = 0; i < encodings.length; ++i)
74  encodingsMap[encodings[i]] = i;
75
76/**
77 * StringDecoder provides an interface for efficiently splitting a series of
78 * buffers into a series of JS strings without breaking apart multi-byte
79 * characters.
80 * @param {string} [encoding=utf-8]
81 */
82function StringDecoder(encoding) {
83  this.encoding = normalizeEncoding(encoding);
84  this[kNativeDecoder] = Buffer.alloc(kSize);
85  this[kNativeDecoder][kEncodingField] = encodingsMap[this.encoding];
86}
87
88/**
89 * Returns a decoded string, omitting any incomplete multi-bytes
90 * characters at the end of the Buffer, or TypedArray, or DataView
91 * @param {string | Buffer | TypedArray | DataView} buf
92 * @returns {string}
93 * @throws {TypeError} Throws when buf is not in one of supported types
94 */
95StringDecoder.prototype.write = function write(buf) {
96  if (typeof buf === 'string')
97    return buf;
98  if (!ArrayBufferIsView(buf))
99    throw new ERR_INVALID_ARG_TYPE('buf',
100                                   ['Buffer', 'TypedArray', 'DataView'],
101                                   buf);
102  if (!this[kNativeDecoder]) {
103    throw new ERR_INVALID_THIS('StringDecoder');
104  }
105  return decode(this[kNativeDecoder], buf);
106};
107
108/**
109 * Returns any remaining input stored in the internal buffer as a string.
110 * After end() is called, the stringDecoder object can be reused for new
111 * input.
112 * @param {string | Buffer | TypedArray | DataView} [buf]
113 * @returns {string}
114 */
115StringDecoder.prototype.end = function end(buf) {
116  let ret = '';
117  if (buf !== undefined)
118    ret = this.write(buf);
119  if (this[kNativeDecoder][kBufferedBytes] > 0)
120    ret += flush(this[kNativeDecoder]);
121  return ret;
122};
123
124/* Everything below this line is undocumented legacy stuff. */
125/**
126 *
127 * @param {string | Buffer | TypedArray | DataView} buf
128 * @param {number} offset
129 * @returns {string}
130 */
131StringDecoder.prototype.text = function text(buf, offset) {
132  this[kNativeDecoder][kMissingBytes] = 0;
133  this[kNativeDecoder][kBufferedBytes] = 0;
134  return this.write(buf.slice(offset));
135};
136
137ObjectDefineProperties(StringDecoder.prototype, {
138  lastChar: {
139    __proto__: null,
140    configurable: true,
141    enumerable: true,
142    get() {
143      return TypedArrayPrototypeSubarray(this[kNativeDecoder],
144                                         kIncompleteCharactersStart,
145                                         kIncompleteCharactersEnd);
146    },
147  },
148  lastNeed: {
149    __proto__: null,
150    configurable: true,
151    enumerable: true,
152    get() {
153      return this[kNativeDecoder][kMissingBytes];
154    },
155  },
156  lastTotal: {
157    __proto__: null,
158    configurable: true,
159    enumerable: true,
160    get() {
161      return this[kNativeDecoder][kBufferedBytes] +
162             this[kNativeDecoder][kMissingBytes];
163    },
164  },
165});
166
167exports.StringDecoder = StringDecoder;
168