9b23259426fea792ba2aa42c6ebbba4a3c0827fac7a4ccb702ce00c68903e7d41b368608b099eed612ee50eaec8597d317e07e459d5596d314e4ea258d7191 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. // parse a 512-byte header block to a data object, or vice-versa
  2. // encode returns `true` if a pax extended header is needed, because
  3. // the data could not be faithfully encoded in a simple header.
  4. // (Also, check header.needPax to see if it needs a pax header.)
  5. import { posix as pathModule } from 'node:path';
  6. import * as large from './large-numbers.js';
  7. import * as types from './types.js';
  8. export class Header {
  9. cksumValid = false;
  10. needPax = false;
  11. nullBlock = false;
  12. block;
  13. path;
  14. mode;
  15. uid;
  16. gid;
  17. size;
  18. cksum;
  19. #type = 'Unsupported';
  20. linkpath;
  21. uname;
  22. gname;
  23. devmaj = 0;
  24. devmin = 0;
  25. atime;
  26. ctime;
  27. mtime;
  28. charset;
  29. comment;
  30. constructor(data, off = 0, ex, gex) {
  31. if (Buffer.isBuffer(data)) {
  32. this.decode(data, off || 0, ex, gex);
  33. }
  34. else if (data) {
  35. this.#slurp(data);
  36. }
  37. }
  38. decode(buf, off, ex, gex) {
  39. if (!off) {
  40. off = 0;
  41. }
  42. if (!buf || !(buf.length >= off + 512)) {
  43. throw new Error('need 512 bytes for header');
  44. }
  45. this.path = decString(buf, off, 100);
  46. this.mode = decNumber(buf, off + 100, 8);
  47. this.uid = decNumber(buf, off + 108, 8);
  48. this.gid = decNumber(buf, off + 116, 8);
  49. this.size = decNumber(buf, off + 124, 12);
  50. this.mtime = decDate(buf, off + 136, 12);
  51. this.cksum = decNumber(buf, off + 148, 12);
  52. // if we have extended or global extended headers, apply them now
  53. // See https://github.com/npm/node-tar/pull/187
  54. // Apply global before local, so it overrides
  55. if (gex)
  56. this.#slurp(gex, true);
  57. if (ex)
  58. this.#slurp(ex);
  59. // old tar versions marked dirs as a file with a trailing /
  60. const t = decString(buf, off + 156, 1);
  61. if (types.isCode(t)) {
  62. this.#type = t || '0';
  63. }
  64. if (this.#type === '0' && this.path.slice(-1) === '/') {
  65. this.#type = '5';
  66. }
  67. // tar implementations sometimes incorrectly put the stat(dir).size
  68. // as the size in the tarball, even though Directory entries are
  69. // not able to have any body at all. In the very rare chance that
  70. // it actually DOES have a body, we weren't going to do anything with
  71. // it anyway, and it'll just be a warning about an invalid header.
  72. if (this.#type === '5') {
  73. this.size = 0;
  74. }
  75. this.linkpath = decString(buf, off + 157, 100);
  76. if (buf.subarray(off + 257, off + 265).toString() ===
  77. 'ustar\u000000') {
  78. this.uname = decString(buf, off + 265, 32);
  79. this.gname = decString(buf, off + 297, 32);
  80. /* c8 ignore start */
  81. this.devmaj = decNumber(buf, off + 329, 8) ?? 0;
  82. this.devmin = decNumber(buf, off + 337, 8) ?? 0;
  83. /* c8 ignore stop */
  84. if (buf[off + 475] !== 0) {
  85. // definitely a prefix, definitely >130 chars.
  86. const prefix = decString(buf, off + 345, 155);
  87. this.path = prefix + '/' + this.path;
  88. }
  89. else {
  90. const prefix = decString(buf, off + 345, 130);
  91. if (prefix) {
  92. this.path = prefix + '/' + this.path;
  93. }
  94. this.atime = decDate(buf, off + 476, 12);
  95. this.ctime = decDate(buf, off + 488, 12);
  96. }
  97. }
  98. let sum = 8 * 0x20;
  99. for (let i = off; i < off + 148; i++) {
  100. sum += buf[i];
  101. }
  102. for (let i = off + 156; i < off + 512; i++) {
  103. sum += buf[i];
  104. }
  105. this.cksumValid = sum === this.cksum;
  106. if (this.cksum === undefined && sum === 8 * 0x20) {
  107. this.nullBlock = true;
  108. }
  109. }
  110. #slurp(ex, gex = false) {
  111. Object.assign(this, Object.fromEntries(Object.entries(ex).filter(([k, v]) => {
  112. // we slurp in everything except for the path attribute in
  113. // a global extended header, because that's weird. Also, any
  114. // null/undefined values are ignored.
  115. return !(v === null ||
  116. v === undefined ||
  117. (k === 'path' && gex) ||
  118. (k === 'linkpath' && gex) ||
  119. k === 'global');
  120. })));
  121. }
  122. encode(buf, off = 0) {
  123. if (!buf) {
  124. buf = this.block = Buffer.alloc(512);
  125. }
  126. if (this.#type === 'Unsupported') {
  127. this.#type = '0';
  128. }
  129. if (!(buf.length >= off + 512)) {
  130. throw new Error('need 512 bytes for header');
  131. }
  132. const prefixSize = this.ctime || this.atime ? 130 : 155;
  133. const split = splitPrefix(this.path || '', prefixSize);
  134. const path = split[0];
  135. const prefix = split[1];
  136. this.needPax = !!split[2];
  137. this.needPax = encString(buf, off, 100, path) || this.needPax;
  138. this.needPax =
  139. encNumber(buf, off + 100, 8, this.mode) || this.needPax;
  140. this.needPax =
  141. encNumber(buf, off + 108, 8, this.uid) || this.needPax;
  142. this.needPax =
  143. encNumber(buf, off + 116, 8, this.gid) || this.needPax;
  144. this.needPax =
  145. encNumber(buf, off + 124, 12, this.size) || this.needPax;
  146. this.needPax =
  147. encDate(buf, off + 136, 12, this.mtime) || this.needPax;
  148. buf[off + 156] = this.#type.charCodeAt(0);
  149. this.needPax =
  150. encString(buf, off + 157, 100, this.linkpath) || this.needPax;
  151. buf.write('ustar\u000000', off + 257, 8);
  152. this.needPax =
  153. encString(buf, off + 265, 32, this.uname) || this.needPax;
  154. this.needPax =
  155. encString(buf, off + 297, 32, this.gname) || this.needPax;
  156. this.needPax =
  157. encNumber(buf, off + 329, 8, this.devmaj) || this.needPax;
  158. this.needPax =
  159. encNumber(buf, off + 337, 8, this.devmin) || this.needPax;
  160. this.needPax =
  161. encString(buf, off + 345, prefixSize, prefix) || this.needPax;
  162. if (buf[off + 475] !== 0) {
  163. this.needPax =
  164. encString(buf, off + 345, 155, prefix) || this.needPax;
  165. }
  166. else {
  167. this.needPax =
  168. encString(buf, off + 345, 130, prefix) || this.needPax;
  169. this.needPax =
  170. encDate(buf, off + 476, 12, this.atime) || this.needPax;
  171. this.needPax =
  172. encDate(buf, off + 488, 12, this.ctime) || this.needPax;
  173. }
  174. let sum = 8 * 0x20;
  175. for (let i = off; i < off + 148; i++) {
  176. sum += buf[i];
  177. }
  178. for (let i = off + 156; i < off + 512; i++) {
  179. sum += buf[i];
  180. }
  181. this.cksum = sum;
  182. encNumber(buf, off + 148, 8, this.cksum);
  183. this.cksumValid = true;
  184. return this.needPax;
  185. }
  186. get type() {
  187. return (this.#type === 'Unsupported' ?
  188. this.#type
  189. : types.name.get(this.#type));
  190. }
  191. get typeKey() {
  192. return this.#type;
  193. }
  194. set type(type) {
  195. const c = String(types.code.get(type));
  196. if (types.isCode(c) || c === 'Unsupported') {
  197. this.#type = c;
  198. }
  199. else if (types.isCode(type)) {
  200. this.#type = type;
  201. }
  202. else {
  203. throw new TypeError('invalid entry type: ' + type);
  204. }
  205. }
  206. }
  207. const splitPrefix = (p, prefixSize) => {
  208. const pathSize = 100;
  209. let pp = p;
  210. let prefix = '';
  211. let ret = undefined;
  212. const root = pathModule.parse(p).root || '.';
  213. if (Buffer.byteLength(pp) < pathSize) {
  214. ret = [pp, prefix, false];
  215. }
  216. else {
  217. // first set prefix to the dir, and path to the base
  218. prefix = pathModule.dirname(pp);
  219. pp = pathModule.basename(pp);
  220. do {
  221. if (Buffer.byteLength(pp) <= pathSize &&
  222. Buffer.byteLength(prefix) <= prefixSize) {
  223. // both fit!
  224. ret = [pp, prefix, false];
  225. }
  226. else if (Buffer.byteLength(pp) > pathSize &&
  227. Buffer.byteLength(prefix) <= prefixSize) {
  228. // prefix fits in prefix, but path doesn't fit in path
  229. ret = [pp.slice(0, pathSize - 1), prefix, true];
  230. }
  231. else {
  232. // make path take a bit from prefix
  233. pp = pathModule.join(pathModule.basename(prefix), pp);
  234. prefix = pathModule.dirname(prefix);
  235. }
  236. } while (prefix !== root && ret === undefined);
  237. // at this point, found no resolution, just truncate
  238. if (!ret) {
  239. ret = [p.slice(0, pathSize - 1), '', true];
  240. }
  241. }
  242. return ret;
  243. };
  244. const decString = (buf, off, size) => buf
  245. .subarray(off, off + size)
  246. .toString('utf8')
  247. .replace(/\0.*/, '');
  248. const decDate = (buf, off, size) => numToDate(decNumber(buf, off, size));
  249. const numToDate = (num) => num === undefined ? undefined : new Date(num * 1000);
  250. const decNumber = (buf, off, size) => Number(buf[off]) & 0x80 ?
  251. large.parse(buf.subarray(off, off + size))
  252. : decSmallNumber(buf, off, size);
  253. const nanUndef = (value) => (isNaN(value) ? undefined : value);
  254. const decSmallNumber = (buf, off, size) => nanUndef(parseInt(buf
  255. .subarray(off, off + size)
  256. .toString('utf8')
  257. .replace(/\0.*$/, '')
  258. .trim(), 8));
  259. // the maximum encodable as a null-terminated octal, by field size
  260. const MAXNUM = {
  261. 12: 0o77777777777,
  262. 8: 0o7777777,
  263. };
  264. const encNumber = (buf, off, size, num) => num === undefined ? false
  265. : num > MAXNUM[size] || num < 0 ?
  266. (large.encode(num, buf.subarray(off, off + size)), true)
  267. : (encSmallNumber(buf, off, size, num), false);
  268. const encSmallNumber = (buf, off, size, num) => buf.write(octalString(num, size), off, size, 'ascii');
  269. const octalString = (num, size) => padOctal(Math.floor(num).toString(8), size);
  270. const padOctal = (str, size) => (str.length === size - 1 ?
  271. str
  272. : new Array(size - str.length - 1).join('0') + str + ' ') + '\0';
  273. const encDate = (buf, off, size, date) => date === undefined ? false : (encNumber(buf, off, size, date.getTime() / 1000));
  274. // enough to fill the longest string we've got
  275. const NULLS = new Array(156).join('\0');
  276. // pad with nulls, return true if it's longer or non-ascii
  277. const encString = (buf, off, size, str) => str === undefined ? false : ((buf.write(str + NULLS, off, size, 'utf8'),
  278. str.length !== Buffer.byteLength(str) || str.length > size));
  279. //# sourceMappingURL=header.js.map