1"use strict"; 2var Buffer = require("safer-buffer").Buffer; 3 4// Export Node.js internal encodings. 5 6module.exports = { 7 // Encodings 8 utf8: { type: "_internal", bomAware: true}, 9 cesu8: { type: "_internal", bomAware: true}, 10 unicode11utf8: "utf8", 11 12 ucs2: { type: "_internal", bomAware: true}, 13 utf16le: "ucs2", 14 15 binary: { type: "_internal" }, 16 base64: { type: "_internal" }, 17 hex: { type: "_internal" }, 18 19 // Codec. 20 _internal: InternalCodec, 21}; 22 23//------------------------------------------------------------------------------ 24 25function InternalCodec(codecOptions, iconv) { 26 this.enc = codecOptions.encodingName; 27 this.bomAware = codecOptions.bomAware; 28 29 if (this.enc === "base64") 30 this.encoder = InternalEncoderBase64; 31 else if (this.enc === "cesu8") { 32 this.enc = "utf8"; // Use utf8 for decoding. 33 this.encoder = InternalEncoderCesu8; 34 35 // Add decoder for versions of Node not supporting CESU-8 36 if (Buffer.from('eda0bdedb2a9', 'hex').toString() !== '') { 37 this.decoder = InternalDecoderCesu8; 38 this.defaultCharUnicode = iconv.defaultCharUnicode; 39 } 40 } 41} 42 43InternalCodec.prototype.encoder = InternalEncoder; 44InternalCodec.prototype.decoder = InternalDecoder; 45 46//------------------------------------------------------------------------------ 47 48// We use node.js internal decoder. Its signature is the same as ours. 49var StringDecoder = require('string_decoder').StringDecoder; 50 51if (!StringDecoder.prototype.end) // Node v0.8 doesn't have this method. 52 StringDecoder.prototype.end = function() {}; 53 54 55function InternalDecoder(options, codec) { 56 this.decoder = new StringDecoder(codec.enc); 57} 58 59InternalDecoder.prototype.write = function(buf) { 60 if (!Buffer.isBuffer(buf)) { 61 buf = Buffer.from(buf); 62 } 63 64 return this.decoder.write(buf); 65} 66 67InternalDecoder.prototype.end = function() { 68 return this.decoder.end(); 69} 70 71 72//------------------------------------------------------------------------------ 73// Encoder is mostly trivial 74 75function InternalEncoder(options, codec) { 76 this.enc = codec.enc; 77} 78 79InternalEncoder.prototype.write = function(str) { 80 return Buffer.from(str, this.enc); 81} 82 83InternalEncoder.prototype.end = function() { 84} 85 86 87//------------------------------------------------------------------------------ 88// Except base64 encoder, which must keep its state. 89 90function InternalEncoderBase64(options, codec) { 91 this.prevStr = ''; 92} 93 94InternalEncoderBase64.prototype.write = function(str) { 95 str = this.prevStr + str; 96 var completeQuads = str.length - (str.length % 4); 97 this.prevStr = str.slice(completeQuads); 98 str = str.slice(0, completeQuads); 99 100 return Buffer.from(str, "base64"); 101} 102 103InternalEncoderBase64.prototype.end = function() { 104 return Buffer.from(this.prevStr, "base64"); 105} 106 107 108//------------------------------------------------------------------------------ 109// CESU-8 encoder is also special. 110 111function InternalEncoderCesu8(options, codec) { 112} 113 114InternalEncoderCesu8.prototype.write = function(str) { 115 var buf = Buffer.alloc(str.length * 3), bufIdx = 0; 116 for (var i = 0; i < str.length; i++) { 117 var charCode = str.charCodeAt(i); 118 // Naive implementation, but it works because CESU-8 is especially easy 119 // to convert from UTF-16 (which all JS strings are encoded in). 120 if (charCode < 0x80) 121 buf[bufIdx++] = charCode; 122 else if (charCode < 0x800) { 123 buf[bufIdx++] = 0xC0 + (charCode >>> 6); 124 buf[bufIdx++] = 0x80 + (charCode & 0x3f); 125 } 126 else { // charCode will always be < 0x10000 in javascript. 127 buf[bufIdx++] = 0xE0 + (charCode >>> 12); 128 buf[bufIdx++] = 0x80 + ((charCode >>> 6) & 0x3f); 129 buf[bufIdx++] = 0x80 + (charCode & 0x3f); 130 } 131 } 132 return buf.slice(0, bufIdx); 133} 134 135InternalEncoderCesu8.prototype.end = function() { 136} 137 138//------------------------------------------------------------------------------ 139// CESU-8 decoder is not implemented in Node v4.0+ 140 141function InternalDecoderCesu8(options, codec) { 142 this.acc = 0; 143 this.contBytes = 0; 144 this.accBytes = 0; 145 this.defaultCharUnicode = codec.defaultCharUnicode; 146} 147 148InternalDecoderCesu8.prototype.write = function(buf) { 149 var acc = this.acc, contBytes = this.contBytes, accBytes = this.accBytes, 150 res = ''; 151 for (var i = 0; i < buf.length; i++) { 152 var curByte = buf[i]; 153 if ((curByte & 0xC0) !== 0x80) { // Leading byte 154 if (contBytes > 0) { // Previous code is invalid 155 res += this.defaultCharUnicode; 156 contBytes = 0; 157 } 158 159 if (curByte < 0x80) { // Single-byte code 160 res += String.fromCharCode(curByte); 161 } else if (curByte < 0xE0) { // Two-byte code 162 acc = curByte & 0x1F; 163 contBytes = 1; accBytes = 1; 164 } else if (curByte < 0xF0) { // Three-byte code 165 acc = curByte & 0x0F; 166 contBytes = 2; accBytes = 1; 167 } else { // Four or more are not supported for CESU-8. 168 res += this.defaultCharUnicode; 169 } 170 } else { // Continuation byte 171 if (contBytes > 0) { // We're waiting for it. 172 acc = (acc << 6) | (curByte & 0x3f); 173 contBytes--; accBytes++; 174 if (contBytes === 0) { 175 // Check for overlong encoding, but support Modified UTF-8 (encoding NULL as C0 80) 176 if (accBytes === 2 && acc < 0x80 && acc > 0) 177 res += this.defaultCharUnicode; 178 else if (accBytes === 3 && acc < 0x800) 179 res += this.defaultCharUnicode; 180 else 181 // Actually add character. 182 res += String.fromCharCode(acc); 183 } 184 } else { // Unexpected continuation byte 185 res += this.defaultCharUnicode; 186 } 187 } 188 } 189 this.acc = acc; this.contBytes = contBytes; this.accBytes = accBytes; 190 return res; 191} 192 193InternalDecoderCesu8.prototype.end = function() { 194 var res = 0; 195 if (this.contBytes > 0) 196 res += this.defaultCharUnicode; 197 return res; 198} 199