entry.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // A passthrough read/write stream that sets its properties
  2. // based on a header, extendedHeader, and globalHeader
  3. //
  4. // Can be either a file system object of some sort, or
  5. // a pax/ustar metadata entry.
  6. module.exports = Entry
  7. var TarHeader = require("./header.js")
  8. , tar = require("../tar")
  9. , assert = require("assert").ok
  10. , Stream = require("stream").Stream
  11. , inherits = require("inherits")
  12. , fstream = require("fstream").Abstract
  13. function Entry (header, extended, global) {
  14. Stream.call(this)
  15. this.readable = true
  16. this.writable = true
  17. this._needDrain = false
  18. this._paused = false
  19. this._reading = false
  20. this._ending = false
  21. this._ended = false
  22. this._remaining = 0
  23. this._abort = false
  24. this._queue = []
  25. this._index = 0
  26. this._queueLen = 0
  27. this._read = this._read.bind(this)
  28. this.props = {}
  29. this._header = header
  30. this._extended = extended || {}
  31. // globals can change throughout the course of
  32. // a file parse operation. Freeze it at its current state.
  33. this._global = {}
  34. var me = this
  35. Object.keys(global || {}).forEach(function (g) {
  36. me._global[g] = global[g]
  37. })
  38. this._setProps()
  39. }
  40. inherits(Entry, Stream)
  41. Entry.prototype.write = function (c) {
  42. if (this._ending) this.error("write() after end()", null, true)
  43. if (this._remaining === 0) {
  44. this.error("invalid bytes past eof")
  45. }
  46. // often we'll get a bunch of \0 at the end of the last write,
  47. // since chunks will always be 512 bytes when reading a tarball.
  48. if (c.length > this._remaining) {
  49. c = c.slice(0, this._remaining)
  50. }
  51. this._remaining -= c.length
  52. // put it on the stack.
  53. var ql = this._queueLen
  54. this._queue.push(c)
  55. this._queueLen ++
  56. this._read()
  57. // either paused, or buffered
  58. if (this._paused || ql > 0) {
  59. this._needDrain = true
  60. return false
  61. }
  62. return true
  63. }
  64. Entry.prototype.end = function (c) {
  65. if (c) this.write(c)
  66. this._ending = true
  67. this._read()
  68. }
  69. Entry.prototype.pause = function () {
  70. this._paused = true
  71. this.emit("pause")
  72. }
  73. Entry.prototype.resume = function () {
  74. // console.error(" Tar Entry resume", this.path)
  75. this.emit("resume")
  76. this._paused = false
  77. this._read()
  78. return this._queueLen - this._index > 1
  79. }
  80. // This is bound to the instance
  81. Entry.prototype._read = function () {
  82. // console.error(" Tar Entry _read", this.path)
  83. if (this._paused || this._reading || this._ended) return
  84. // set this flag so that event handlers don't inadvertently
  85. // get multiple _read() calls running.
  86. this._reading = true
  87. // have any data to emit?
  88. while (this._index < this._queueLen && !this._paused) {
  89. var chunk = this._queue[this._index ++]
  90. this.emit("data", chunk)
  91. }
  92. // check if we're drained
  93. if (this._index >= this._queueLen) {
  94. this._queue.length = this._queueLen = this._index = 0
  95. if (this._needDrain) {
  96. this._needDrain = false
  97. this.emit("drain")
  98. }
  99. if (this._ending) {
  100. this._ended = true
  101. this.emit("end")
  102. }
  103. }
  104. // if the queue gets too big, then pluck off whatever we can.
  105. // this should be fairly rare.
  106. var mql = this._maxQueueLen
  107. if (this._queueLen > mql && this._index > 0) {
  108. mql = Math.min(this._index, mql)
  109. this._index -= mql
  110. this._queueLen -= mql
  111. this._queue = this._queue.slice(mql)
  112. }
  113. this._reading = false
  114. }
  115. Entry.prototype._setProps = function () {
  116. // props = extended->global->header->{}
  117. var header = this._header
  118. , extended = this._extended
  119. , global = this._global
  120. , props = this.props
  121. // first get the values from the normal header.
  122. var fields = tar.fields
  123. for (var f = 0; fields[f] !== null; f ++) {
  124. var field = fields[f]
  125. , val = header[field]
  126. if (typeof val !== "undefined") props[field] = val
  127. }
  128. // next, the global header for this file.
  129. // numeric values, etc, will have already been parsed.
  130. ;[global, extended].forEach(function (p) {
  131. Object.keys(p).forEach(function (f) {
  132. if (typeof p[f] !== "undefined") props[f] = p[f]
  133. })
  134. })
  135. // no nulls allowed in path or linkpath
  136. ;["path", "linkpath"].forEach(function (p) {
  137. if (props.hasOwnProperty(p)) {
  138. props[p] = props[p].split("\0")[0]
  139. }
  140. })
  141. // set date fields to be a proper date
  142. ;["mtime", "ctime", "atime"].forEach(function (p) {
  143. if (props.hasOwnProperty(p)) {
  144. props[p] = new Date(props[p] * 1000)
  145. }
  146. })
  147. // set the type so that we know what kind of file to create
  148. var type
  149. switch (tar.types[props.type]) {
  150. case "OldFile":
  151. case "ContiguousFile":
  152. type = "File"
  153. break
  154. case "GNUDumpDir":
  155. type = "Directory"
  156. break
  157. case undefined:
  158. type = "Unknown"
  159. break
  160. case "Link":
  161. case "SymbolicLink":
  162. case "CharacterDevice":
  163. case "BlockDevice":
  164. case "Directory":
  165. case "FIFO":
  166. default:
  167. type = tar.types[props.type]
  168. }
  169. this.type = type
  170. this.path = props.path
  171. this.size = props.size
  172. // size is special, since it signals when the file needs to end.
  173. this._remaining = props.size
  174. }
  175. // the parser may not call write if _abort is true.
  176. // useful for skipping data from some files quickly.
  177. Entry.prototype.abort = function(){
  178. this._abort = true
  179. }
  180. Entry.prototype.warn = fstream.warn
  181. Entry.prototype.error = fstream.error