2
0

tar.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. const tar = {};
  2. tar.Archive = class {
  3. static open(data) {
  4. const stream = data instanceof Uint8Array ? new tar.BinaryReader(data) : data;
  5. if (stream && stream.length >= 512) {
  6. const buffer = stream.peek(512);
  7. const sum = buffer.map((value, index) => (index >= 148 && index < 156) ? 32 : value).reduce((a, b) => a + b, 0);
  8. const checksum = parseInt(Array.from(buffer.slice(148, 156)).map((c) => String.fromCharCode(c)).join('').split('\0').shift(), 8);
  9. if (!isNaN(checksum) && sum === checksum) {
  10. return new tar.Archive(stream);
  11. }
  12. }
  13. return null;
  14. }
  15. constructor(stream) {
  16. this._entries = new Map();
  17. const position = stream.position;
  18. while (stream.position < stream.length) {
  19. const entry = new tar.Entry(stream);
  20. if (entry.type === '' || entry.type === '0' || entry.type === '1' || entry.type === '2') {
  21. this._entries.set(entry.name, entry.stream);
  22. }
  23. if (stream.position + 512 > stream.length ||
  24. stream.peek(512).every((value) => value === 0x00)) {
  25. break;
  26. }
  27. }
  28. stream.seek(position);
  29. }
  30. get entries() {
  31. return this._entries;
  32. }
  33. };
  34. tar.Entry = class {
  35. constructor(stream) {
  36. const buffer = stream.read(512);
  37. const reader = new tar.BinaryReader(buffer);
  38. const sum = buffer.map((value, index) => (index >= 148 && index < 156) ? 32 : value).reduce((a, b) => a + b, 0);
  39. let checksum = '';
  40. for (let i = 148; i < 156 && buffer[i] !== 0x00; i++) {
  41. checksum += String.fromCharCode(buffer[i]);
  42. }
  43. checksum = parseInt(checksum, 8);
  44. if (isNaN(checksum) || sum !== checksum) {
  45. throw new tar.Error('Invalid tar archive.');
  46. }
  47. this._name = reader.string(100);
  48. reader.string(8); // file mode
  49. reader.string(8); // owner
  50. reader.string(8); // group
  51. const size = reader.size();
  52. reader.string(12); // timestamp
  53. reader.string(8); // checksum
  54. this._type = reader.string(1);
  55. reader.string(100); // name of linked file
  56. if (reader.string(6) === 'ustar') {
  57. reader.string(2); // ustar version
  58. reader.string(32); // owner user name
  59. reader.string(32); // owner group name
  60. reader.string(8); // device major number
  61. reader.string(8); // device number number
  62. const prefix = reader.string(155);
  63. this._name = prefix ? `${prefix}/${this._name}` : this._name;
  64. }
  65. this._stream = stream.stream(size);
  66. stream.read(((size % 512) === 0) ? 0 : (512 - (size % 512)));
  67. }
  68. get type() {
  69. return this._type;
  70. }
  71. get name() {
  72. return this._name;
  73. }
  74. get stream() {
  75. return this._stream;
  76. }
  77. };
  78. tar.BinaryReader = class {
  79. constructor(buffer) {
  80. this._buffer = buffer;
  81. this._length = buffer.length;
  82. this._position = 0;
  83. this._view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
  84. }
  85. get position() {
  86. return this._position;
  87. }
  88. get length() {
  89. return this._length;
  90. }
  91. create(buffer) {
  92. return new tar.BinaryReader(buffer);
  93. }
  94. stream(length) {
  95. return this.create(this.read(length));
  96. }
  97. seek(position) {
  98. this._position = position >= 0 ? position : this._length + position;
  99. if (this._position > this._length || this._position < 0) {
  100. throw new tar.Error('Invalid tar archive. Unexpected end of file.');
  101. }
  102. }
  103. skip(offset) {
  104. this._position += offset;
  105. if (this._position > this._length || this._position < 0) {
  106. throw new tar.Error('Invalid tar archive. Unexpected end of file.');
  107. }
  108. }
  109. peek(length) {
  110. if (this._position === 0 && length === undefined) {
  111. return this._buffer;
  112. }
  113. const position = this._position;
  114. this.skip(length === undefined ? this._length - this._position : length);
  115. const end = this._position;
  116. this.seek(position);
  117. return this._buffer.subarray(position, end);
  118. }
  119. read(length) {
  120. if (this._position === 0 && length === undefined) {
  121. this._position = this._length;
  122. return this._buffer;
  123. }
  124. const position = this._position;
  125. this.skip(length === undefined ? this._length - this._position : length);
  126. return this._buffer.subarray(position, this._position);
  127. }
  128. string(length) {
  129. const buffer = this.read(length);
  130. let position = 0;
  131. let content = '';
  132. for (let i = 0; i < length; i++) {
  133. const c = buffer[position++];
  134. if (c === 0) {
  135. break;
  136. }
  137. content += String.fromCharCode(c);
  138. }
  139. return content;
  140. }
  141. size() {
  142. const buffer = this.read(12);
  143. if (buffer[0] & 0x80) {
  144. buffer[0] &= 0x7f;
  145. let value = 0n;
  146. for (const byte of buffer) {
  147. value = (value << 8n) | BigInt(byte);
  148. if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
  149. throw new tar.Error('Tar entry size exceeds safe integer.');
  150. }
  151. }
  152. return value.toNumber();
  153. }
  154. const octal = String.fromCharCode(...buffer);
  155. return parseInt(octal.trim() || '0', 8);
  156. }
  157. };
  158. tar.Error = class extends Error {
  159. constructor(message) {
  160. super(message);
  161. this.name = 'tar Error';
  162. }
  163. };
  164. export const Archive = tar.Archive;