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} = primordials; 29 30const { Buffer } = require('buffer'); 31const { 32 kIncompleteCharactersStart, 33 kIncompleteCharactersEnd, 34 kMissingBytes, 35 kBufferedBytes, 36 kEncodingField, 37 kSize, 38 decode, 39 flush, 40 encodings 41} = internalBinding('string_decoder'); 42const internalUtil = require('internal/util'); 43const { 44 ERR_INVALID_ARG_TYPE, 45 ERR_UNKNOWN_ENCODING 46} = require('internal/errors').codes; 47const isEncoding = Buffer[internalUtil.kIsEncodingSymbol]; 48 49const kNativeDecoder = Symbol('kNativeDecoder'); 50 51// Do not cache `Buffer.isEncoding` when checking encoding names as some 52// modules monkey-patch it to support additional encodings 53function normalizeEncoding(enc) { 54 const nenc = internalUtil.normalizeEncoding(enc); 55 if (nenc === undefined) { 56 if (Buffer.isEncoding === isEncoding || !Buffer.isEncoding(enc)) 57 throw new ERR_UNKNOWN_ENCODING(enc); 58 return enc; 59 } 60 return nenc; 61} 62 63const encodingsMap = {}; 64for (let i = 0; i < encodings.length; ++i) 65 encodingsMap[encodings[i]] = i; 66 67// StringDecoder provides an interface for efficiently splitting a series of 68// buffers into a series of JS strings without breaking apart multi-byte 69// characters. 70function StringDecoder(encoding) { 71 this.encoding = normalizeEncoding(encoding); 72 this[kNativeDecoder] = Buffer.alloc(kSize); 73 this[kNativeDecoder][kEncodingField] = encodingsMap[this.encoding]; 74} 75 76StringDecoder.prototype.write = function write(buf) { 77 if (typeof buf === 'string') 78 return buf; 79 if (!ArrayBufferIsView(buf)) 80 throw new ERR_INVALID_ARG_TYPE('buf', 81 ['Buffer', 'TypedArray', 'DataView'], 82 buf); 83 return decode(this[kNativeDecoder], buf); 84}; 85 86StringDecoder.prototype.end = function end(buf) { 87 let ret = ''; 88 if (buf !== undefined) 89 ret = this.write(buf); 90 if (this[kNativeDecoder][kBufferedBytes] > 0) 91 ret += flush(this[kNativeDecoder]); 92 return ret; 93}; 94 95/* Everything below this line is undocumented legacy stuff. */ 96StringDecoder.prototype.text = function text(buf, offset) { 97 this[kNativeDecoder][kMissingBytes] = 0; 98 this[kNativeDecoder][kBufferedBytes] = 0; 99 return this.write(buf.slice(offset)); 100}; 101 102ObjectDefineProperties(StringDecoder.prototype, { 103 lastChar: { 104 configurable: true, 105 enumerable: true, 106 get() { 107 return this[kNativeDecoder].subarray(kIncompleteCharactersStart, 108 kIncompleteCharactersEnd); 109 } 110 }, 111 lastNeed: { 112 configurable: true, 113 enumerable: true, 114 get() { 115 return this[kNativeDecoder][kMissingBytes]; 116 } 117 }, 118 lastTotal: { 119 configurable: true, 120 enumerable: true, 121 get() { 122 return this[kNativeDecoder][kBufferedBytes] + 123 this[kNativeDecoder][kMissingBytes]; 124 } 125 } 126}); 127 128exports.StringDecoder = StringDecoder; 129