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