libbase64.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. 'use strict';
  2. var stream = require('stream');
  3. var util = require('util');
  4. var Transform = stream.Transform;
  5. // expose to the world
  6. module.exports = {
  7. encode: encode,
  8. decode: decode,
  9. wrap: wrap,
  10. Encoder: Encoder,
  11. Decoder: Decoder
  12. };
  13. /**
  14. * Encodes a Buffer into a base64 encoded string
  15. *
  16. * @param {Buffer} buffer Buffer to convert
  17. * @returns {String} base64 encoded string
  18. */
  19. function encode(buffer) {
  20. if (typeof buffer === 'string') {
  21. buffer = new Buffer(buffer, 'utf-8');
  22. }
  23. return buffer.toString('base64');
  24. }
  25. /**
  26. * Decodes a base64 encoded string to a Buffer object
  27. *
  28. * @param {String} str base64 encoded string
  29. * @returns {Buffer} Decoded value
  30. */
  31. function decode(str) {
  32. str = (str || '');
  33. return new Buffer(str, 'base64');
  34. }
  35. /**
  36. * Adds soft line breaks to a base64 string
  37. *
  38. * @param {String} str base64 encoded string that might need line wrapping
  39. * @param {Number} [lineLength=76] Maximum allowed length for a line
  40. * @returns {String} Soft-wrapped base64 encoded string
  41. */
  42. function wrap(str, lineLength) {
  43. str = (str || '').toString();
  44. lineLength = lineLength || 76;
  45. if (str.length <= lineLength) {
  46. return str;
  47. }
  48. return str.replace(new RegExp('.{' + lineLength + '}', 'g'), '$&\r\n').trim();
  49. }
  50. /**
  51. * Creates a transform stream for encoding data to base64 encoding
  52. *
  53. * @constructor
  54. * @param {Object} options Stream options
  55. * @param {Number} [options.lineLength=76] Maximum lenght for lines, set to false to disable wrapping
  56. */
  57. function Encoder(options) {
  58. // init Transform
  59. this.options = options || {};
  60. if (this.options.lineLength !== false) {
  61. this.options.lineLength = this.options.lineLength || 76;
  62. }
  63. this._curLine = '';
  64. this._remainingBytes = false;
  65. this.inputBytes = 0;
  66. this.outputBytes = 0;
  67. Transform.call(this, this.options);
  68. }
  69. util.inherits(Encoder, Transform);
  70. Encoder.prototype._transform = function(chunk, encoding, done) {
  71. var b64, _self = this;
  72. if (encoding !== 'buffer') {
  73. chunk = new Buffer(chunk, encoding);
  74. }
  75. if (!chunk || !chunk.length) {
  76. return done();
  77. }
  78. this.inputBytes += chunk.length;
  79. if (this._remainingBytes && this._remainingBytes.length) {
  80. chunk = Buffer.concat([this._remainingBytes, chunk]);
  81. this._remainingBytes = false;
  82. }
  83. if (chunk.length % 3) {
  84. this._remainingBytes = chunk.slice(chunk.length - chunk.length % 3);
  85. chunk = chunk.slice(0, chunk.length - chunk.length % 3);
  86. } else {
  87. this._remainingBytes = false;
  88. }
  89. b64 = this._curLine + encode(chunk);
  90. if (this.options.lineLength) {
  91. b64 = wrap(b64, this.options.lineLength);
  92. b64 = b64.replace(/(^|\n)([^\n]*)$/, function(match, lineBreak, lastLine) {
  93. _self._curLine = lastLine;
  94. return lineBreak;
  95. });
  96. }
  97. if (b64) {
  98. this.outputBytes += b64.length;
  99. this.push(b64);
  100. }
  101. done();
  102. };
  103. Encoder.prototype._flush = function(done) {
  104. if (this._remainingBytes && this._remainingBytes.length) {
  105. this._curLine += encode(this._remainingBytes);
  106. }
  107. if (this._curLine) {
  108. this._curLine = wrap(this._curLine, this.options.lineLength);
  109. this.outputBytes += this._curLine.length;
  110. this.push(this._curLine, 'ascii');
  111. this._curLine = '';
  112. }
  113. done();
  114. };
  115. /**
  116. * Creates a transform stream for decoding base64 encoded strings
  117. *
  118. * @constructor
  119. * @param {Object} options Stream options
  120. */
  121. function Decoder(options) {
  122. // init Transform
  123. this.options = options || {};
  124. this._curLine = '';
  125. this.inputBytes = 0;
  126. this.outputBytes = 0;
  127. Transform.call(this, this.options);
  128. }
  129. util.inherits(Decoder, Transform);
  130. Decoder.prototype._transform = function(chunk, encoding, done) {
  131. var b64, buf;
  132. chunk = chunk.toString('ascii');
  133. if (!chunk || !chunk.length) {
  134. return done();
  135. }
  136. this.inputBytes += chunk.length;
  137. b64 = (this._curLine + chunk);
  138. this._curLine = '';
  139. b64 = b64.replace(/[^a-zA-Z0-9+\/=]/g, '');
  140. if (b64.length % 4) {
  141. this._curLine = b64.substr(-b64.length % 4);
  142. if (this._curLine.length == b64.length) {
  143. b64 = '';
  144. } else {
  145. b64 = b64.substr(0, this._curLine.length);
  146. }
  147. }
  148. if (b64) {
  149. buf = decode(b64);
  150. this.outputBytes += buf.length;
  151. this.push(buf);
  152. }
  153. done();
  154. };
  155. Decoder.prototype._flush = function(done) {
  156. var b64, buf;
  157. if (this._curLine) {
  158. buf = decode(this._curLine);
  159. this.outputBytes += buf.length;
  160. this.push(buf);
  161. this._curLine = '';
  162. }
  163. done();
  164. };