123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. var util = require('util')
  2. var Stream = require('stream')
  3. var StringDecoder = require('string_decoder').StringDecoder
  4. module.exports = StringStream
  5. module.exports.AlignedStringDecoder = AlignedStringDecoder
  6. function StringStream(from, to) {
  7. if (!(this instanceof StringStream)) return new StringStream(from, to)
  8. Stream.call(this)
  9. if (from == null) from = 'utf8'
  10. this.readable = this.writable = true
  11. this.paused = false
  12. this.toEncoding = (to == null ? from : to)
  13. this.fromEncoding = (to == null ? '' : from)
  14. this.decoder = new AlignedStringDecoder(this.toEncoding)
  15. }
  16. util.inherits(StringStream, Stream)
  17. StringStream.prototype.write = function(data) {
  18. if (!this.writable) {
  19. var err = new Error('stream not writable')
  20. err.code = 'EPIPE'
  21. this.emit('error', err)
  22. return false
  23. }
  24. if (this.fromEncoding) {
  25. if (Buffer.isBuffer(data) || typeof data === 'number') data = data.toString()
  26. data = new Buffer(data, this.fromEncoding)
  27. }
  28. var string = this.decoder.write(data)
  29. if (string.length) this.emit('data', string)
  30. return !this.paused
  31. }
  32. StringStream.prototype.flush = function() {
  33. if (this.decoder.flush) {
  34. var string = this.decoder.flush()
  35. if (string.length) this.emit('data', string)
  36. }
  37. }
  38. StringStream.prototype.end = function() {
  39. if (!this.writable && !this.readable) return
  40. this.flush()
  41. this.emit('end')
  42. this.writable = this.readable = false
  43. this.destroy()
  44. }
  45. StringStream.prototype.destroy = function() {
  46. this.decoder = null
  47. this.writable = this.readable = false
  48. this.emit('close')
  49. }
  50. StringStream.prototype.pause = function() {
  51. this.paused = true
  52. }
  53. StringStream.prototype.resume = function () {
  54. if (this.paused) this.emit('drain')
  55. this.paused = false
  56. }
  57. function AlignedStringDecoder(encoding) {
  58. StringDecoder.call(this, encoding)
  59. switch (this.encoding) {
  60. case 'base64':
  61. this.write = alignedWrite
  62. this.alignedBuffer = new Buffer(3)
  63. this.alignedBytes = 0
  64. break
  65. }
  66. }
  67. util.inherits(AlignedStringDecoder, StringDecoder)
  68. AlignedStringDecoder.prototype.flush = function() {
  69. if (!this.alignedBuffer || !this.alignedBytes) return ''
  70. var leftover = this.alignedBuffer.toString(this.encoding, 0, this.alignedBytes)
  71. this.alignedBytes = 0
  72. return leftover
  73. }
  74. function alignedWrite(buffer) {
  75. var rem = (this.alignedBytes + buffer.length) % this.alignedBuffer.length
  76. if (!rem && !this.alignedBytes) return buffer.toString(this.encoding)
  77. var returnBuffer = new Buffer(this.alignedBytes + buffer.length - rem)
  78. this.alignedBuffer.copy(returnBuffer, 0, 0, this.alignedBytes)
  79. buffer.copy(returnBuffer, this.alignedBytes, 0, buffer.length - rem)
  80. buffer.copy(this.alignedBuffer, 0, buffer.length - rem, buffer.length)
  81. this.alignedBytes = rem
  82. return returnBuffer.toString(this.encoding)
  83. }