extend-node.js
  1  "use strict";
  2  var Buffer = require("buffer").Buffer;
  3  // Note: not polyfilled with safer-buffer on a purpose, as overrides Buffer
  4  
  5  // == Extend Node primitives to use iconv-lite =================================
  6  
  7  module.exports = function (iconv) {
  8      var original = undefined; // Place to keep original methods.
  9  
 10      // Node authors rewrote Buffer internals to make it compatible with
 11      // Uint8Array and we cannot patch key functions since then.
 12      // Note: this does use older Buffer API on a purpose
 13      iconv.supportsNodeEncodingsExtension = !(Buffer.from || new Buffer(0) instanceof Uint8Array);
 14  
 15      iconv.extendNodeEncodings = function extendNodeEncodings() {
 16          if (original) return;
 17          original = {};
 18  
 19          if (!iconv.supportsNodeEncodingsExtension) {
 20              console.error("ACTION NEEDED: require('iconv-lite').extendNodeEncodings() is not supported in your version of Node");
 21              console.error("See more info at https://github.com/ashtuchkin/iconv-lite/wiki/Node-v4-compatibility");
 22              return;
 23          }
 24  
 25          var nodeNativeEncodings = {
 26              'hex': true, 'utf8': true, 'utf-8': true, 'ascii': true, 'binary': true, 
 27              'base64': true, 'ucs2': true, 'ucs-2': true, 'utf16le': true, 'utf-16le': true,
 28          };
 29  
 30          Buffer.isNativeEncoding = function(enc) {
 31              return enc && nodeNativeEncodings[enc.toLowerCase()];
 32          }
 33  
 34          // -- SlowBuffer -----------------------------------------------------------
 35          var SlowBuffer = require('buffer').SlowBuffer;
 36  
 37          original.SlowBufferToString = SlowBuffer.prototype.toString;
 38          SlowBuffer.prototype.toString = function(encoding, start, end) {
 39              encoding = String(encoding || 'utf8').toLowerCase();
 40  
 41              // Use native conversion when possible
 42              if (Buffer.isNativeEncoding(encoding))
 43                  return original.SlowBufferToString.call(this, encoding, start, end);
 44  
 45              // Otherwise, use our decoding method.
 46              if (typeof start == 'undefined') start = 0;
 47              if (typeof end == 'undefined') end = this.length;
 48              return iconv.decode(this.slice(start, end), encoding);
 49          }
 50  
 51          original.SlowBufferWrite = SlowBuffer.prototype.write;
 52          SlowBuffer.prototype.write = function(string, offset, length, encoding) {
 53              // Support both (string, offset, length, encoding)
 54              // and the legacy (string, encoding, offset, length)
 55              if (isFinite(offset)) {
 56                  if (!isFinite(length)) {
 57                      encoding = length;
 58                      length = undefined;
 59                  }
 60              } else {  // legacy
 61                  var swap = encoding;
 62                  encoding = offset;
 63                  offset = length;
 64                  length = swap;
 65              }
 66  
 67              offset = +offset || 0;
 68              var remaining = this.length - offset;
 69              if (!length) {
 70                  length = remaining;
 71              } else {
 72                  length = +length;
 73                  if (length > remaining) {
 74                      length = remaining;
 75                  }
 76              }
 77              encoding = String(encoding || 'utf8').toLowerCase();
 78  
 79              // Use native conversion when possible
 80              if (Buffer.isNativeEncoding(encoding))
 81                  return original.SlowBufferWrite.call(this, string, offset, length, encoding);
 82  
 83              if (string.length > 0 && (length < 0 || offset < 0))
 84                  throw new RangeError('attempt to write beyond buffer bounds');
 85  
 86              // Otherwise, use our encoding method.
 87              var buf = iconv.encode(string, encoding);
 88              if (buf.length < length) length = buf.length;
 89              buf.copy(this, offset, 0, length);
 90              return length;
 91          }
 92  
 93          // -- Buffer ---------------------------------------------------------------
 94  
 95          original.BufferIsEncoding = Buffer.isEncoding;
 96          Buffer.isEncoding = function(encoding) {
 97              return Buffer.isNativeEncoding(encoding) || iconv.encodingExists(encoding);
 98          }
 99  
100          original.BufferByteLength = Buffer.byteLength;
101          Buffer.byteLength = SlowBuffer.byteLength = function(str, encoding) {
102              encoding = String(encoding || 'utf8').toLowerCase();
103  
104              // Use native conversion when possible
105              if (Buffer.isNativeEncoding(encoding))
106                  return original.BufferByteLength.call(this, str, encoding);
107  
108              // Slow, I know, but we don't have a better way yet.
109              return iconv.encode(str, encoding).length;
110          }
111  
112          original.BufferToString = Buffer.prototype.toString;
113          Buffer.prototype.toString = function(encoding, start, end) {
114              encoding = String(encoding || 'utf8').toLowerCase();
115  
116              // Use native conversion when possible
117              if (Buffer.isNativeEncoding(encoding))
118                  return original.BufferToString.call(this, encoding, start, end);
119  
120              // Otherwise, use our decoding method.
121              if (typeof start == 'undefined') start = 0;
122              if (typeof end == 'undefined') end = this.length;
123              return iconv.decode(this.slice(start, end), encoding);
124          }
125  
126          original.BufferWrite = Buffer.prototype.write;
127          Buffer.prototype.write = function(string, offset, length, encoding) {
128              var _offset = offset, _length = length, _encoding = encoding;
129              // Support both (string, offset, length, encoding)
130              // and the legacy (string, encoding, offset, length)
131              if (isFinite(offset)) {
132                  if (!isFinite(length)) {
133                      encoding = length;
134                      length = undefined;
135                  }
136              } else {  // legacy
137                  var swap = encoding;
138                  encoding = offset;
139                  offset = length;
140                  length = swap;
141              }
142  
143              encoding = String(encoding || 'utf8').toLowerCase();
144  
145              // Use native conversion when possible
146              if (Buffer.isNativeEncoding(encoding))
147                  return original.BufferWrite.call(this, string, _offset, _length, _encoding);
148  
149              offset = +offset || 0;
150              var remaining = this.length - offset;
151              if (!length) {
152                  length = remaining;
153              } else {
154                  length = +length;
155                  if (length > remaining) {
156                      length = remaining;
157                  }
158              }
159  
160              if (string.length > 0 && (length < 0 || offset < 0))
161                  throw new RangeError('attempt to write beyond buffer bounds');
162  
163              // Otherwise, use our encoding method.
164              var buf = iconv.encode(string, encoding);
165              if (buf.length < length) length = buf.length;
166              buf.copy(this, offset, 0, length);
167              return length;
168  
169              // TODO: Set _charsWritten.
170          }
171  
172  
173          // -- Readable -------------------------------------------------------------
174          if (iconv.supportsStreams) {
175              var Readable = require('stream').Readable;
176  
177              original.ReadableSetEncoding = Readable.prototype.setEncoding;
178              Readable.prototype.setEncoding = function setEncoding(enc, options) {
179                  // Use our own decoder, it has the same interface.
180                  // We cannot use original function as it doesn't handle BOM-s.
181                  this._readableState.decoder = iconv.getDecoder(enc, options);
182                  this._readableState.encoding = enc;
183              }
184  
185              Readable.prototype.collect = iconv._collect;
186          }
187      }
188  
189      // Remove iconv-lite Node primitive extensions.
190      iconv.undoExtendNodeEncodings = function undoExtendNodeEncodings() {
191          if (!iconv.supportsNodeEncodingsExtension)
192              return;
193          if (!original)
194              throw new Error("require('iconv-lite').undoExtendNodeEncodings(): Nothing to undo; extendNodeEncodings() is not called.")
195  
196          delete Buffer.isNativeEncoding;
197  
198          var SlowBuffer = require('buffer').SlowBuffer;
199  
200          SlowBuffer.prototype.toString = original.SlowBufferToString;
201          SlowBuffer.prototype.write = original.SlowBufferWrite;
202  
203          Buffer.isEncoding = original.BufferIsEncoding;
204          Buffer.byteLength = original.BufferByteLength;
205          Buffer.prototype.toString = original.BufferToString;
206          Buffer.prototype.write = original.BufferWrite;
207  
208          if (iconv.supportsStreams) {
209              var Readable = require('stream').Readable;
210  
211              Readable.prototype.setEncoding = original.ReadableSetEncoding;
212              delete Readable.prototype.collect;
213          }
214  
215          original = undefined;
216      }
217  }