simplify even more
This commit is contained in:
parent
ca345d9b06
commit
1b121472b9
|
|
@ -29,43 +29,73 @@ enum TarType {
|
||||||
CONTTYPE
|
CONTTYPE
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseHeader(buffer: Buffer) {
|
class TarEntry {
|
||||||
if (buffer.length < 512)
|
name: string;
|
||||||
throw new Error('Invalid header: ' + buffer.toString('utf8'));
|
size: number;
|
||||||
|
type: TarType;
|
||||||
|
mode: number;
|
||||||
|
linkname: string;
|
||||||
|
uid: number;
|
||||||
|
gid: number;
|
||||||
|
|
||||||
let name = buffer.toString('utf8', 0, 100).replace(/\0/g, '');
|
fileStream: fs.WriteStream | null = null;
|
||||||
const prefixField = buffer.toString('utf8', 345, 500).replace(/\0/g, '');
|
remainingBytes = 0;
|
||||||
|
|
||||||
|
constructor(header: Buffer) {
|
||||||
|
if (header.length < 512)
|
||||||
|
throw new Error('Invalid header: ' + header.toString('utf8'));
|
||||||
|
|
||||||
|
this.name = header.toString('utf8', 0, 100).replace(/\0/g, '');
|
||||||
|
const prefixField = header.toString('utf8', 345, 500).replace(/\0/g, '');
|
||||||
if (prefixField)
|
if (prefixField)
|
||||||
name = path.join(prefixField, name);
|
this.name = path.join(prefixField, this.name);
|
||||||
name = name.replace(/^\/+/, '');
|
this.name = this.name.replace(/^\/+/, '');
|
||||||
|
|
||||||
const size = parseInt(buffer.toString('utf8', 124, 136).trim(), 8);
|
this.size = parseInt(header.toString('utf8', 124, 136).trim(), 8);
|
||||||
const type = parseInt(buffer.toString('ascii', 156, 157), 10) as TarType;
|
this.type = parseInt(header.toString('ascii', 156, 157), 10) as TarType;
|
||||||
const mode = parseInt(buffer.toString('utf8', 100, 108).trim(), 8) || 0o644;
|
this.mode = parseInt(header.toString('utf8', 100, 108).trim(), 8) || 0o644;
|
||||||
const linkname = buffer.toString('utf8', 157, 257).replace(/\0/g, '');
|
this.linkname = header.toString('utf8', 157, 257).replace(/\0/g, '');
|
||||||
|
|
||||||
const uid = parseInt(buffer.toString('utf8', 108, 116).trim(), 8);
|
this.uid = parseInt(header.toString('utf8', 108, 116).trim(), 8);
|
||||||
const gid = parseInt(buffer.toString('utf8', 116, 124).trim(), 8);
|
this.gid = parseInt(header.toString('utf8', 116, 124).trim(), 8);
|
||||||
|
}
|
||||||
return {
|
|
||||||
name,
|
async writeToDisk(outputPath: (path: string) => string) {
|
||||||
size,
|
const fullPath = outputPath(this.name);
|
||||||
type,
|
switch (this.type) {
|
||||||
mode,
|
case TarType.DIRTYPE:
|
||||||
linkname,
|
await fs.promises.mkdir(fullPath, { recursive: true, mode: 0o755 });
|
||||||
uid,
|
break;
|
||||||
gid
|
case TarType.SYMTYPE:
|
||||||
};
|
await fs.promises.symlink(this.linkname, fullPath);
|
||||||
|
break;
|
||||||
|
case TarType.REGTYPE:
|
||||||
|
this.fileStream = fs.createWriteStream(fullPath, { mode: this.mode });
|
||||||
|
await once(this.fileStream, 'ready');
|
||||||
|
this.remainingBytes = this.size;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported type ${this.type} for '${this.name}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk(chunk: Buffer) {
|
||||||
|
assert(this.fileStream);
|
||||||
|
this.fileStream.write(chunk);
|
||||||
|
this.remainingBytes -= chunk.length;
|
||||||
|
if (this.remainingBytes === 0) {
|
||||||
|
this.fileStream.end();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TarHeader = NonNullable<ReturnType<typeof parseHeader>>;
|
|
||||||
|
|
||||||
export class TarExtractor extends Writable {
|
export class TarExtractor extends Writable {
|
||||||
private queue = Promise.resolve();
|
private queue = Promise.resolve();
|
||||||
private buffer = Buffer.alloc(0);
|
private buffer = Buffer.alloc(0);
|
||||||
private currentHeader: TarHeader | null = null;
|
private currentEntry: TarEntry | null = null;
|
||||||
private remainingBytes = 0;
|
|
||||||
private currentFileStream: fs.WriteStream | null = null;
|
|
||||||
|
|
||||||
constructor(private outputPath: (path: string) => string) {
|
constructor(private outputPath: (path: string) => string) {
|
||||||
super();
|
super();
|
||||||
|
|
@ -78,55 +108,28 @@ export class TarExtractor extends Writable {
|
||||||
private async _writeImpl(chunk: Buffer): Promise<undefined> {
|
private async _writeImpl(chunk: Buffer): Promise<undefined> {
|
||||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||||
while (this.buffer.length >= 512) {
|
while (this.buffer.length >= 512) {
|
||||||
if (!this.currentHeader) {
|
if (!this.currentEntry) {
|
||||||
// Check for end of archive (two consecutive zero blocks)
|
// two consecutive zero blocks mark end of archive, skip them
|
||||||
if (this.buffer.subarray(0, 512).every(byte => byte === 0)) {
|
if (this.buffer.subarray(0, 512).every(byte => byte === 0)) {
|
||||||
this.buffer = this.buffer.subarray(512);
|
this.buffer = this.buffer.subarray(512);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = parseHeader(this.buffer);
|
const entry = new TarEntry(this.buffer);
|
||||||
this.buffer = this.buffer.subarray(512);
|
this.buffer = this.buffer.subarray(512);
|
||||||
|
await entry.writeToDisk(this.outputPath);
|
||||||
const fullPath = this.outputPath(header.name);
|
if (entry.type === TarType.REGTYPE)
|
||||||
switch (header.type) {
|
this.currentEntry = entry;
|
||||||
case TarType.DIRTYPE:
|
} else if (this.currentEntry.remainingBytes > 0) {
|
||||||
await fs.promises.mkdir(fullPath, { recursive: true, mode: 0o755 });
|
const chunk = this.buffer.subarray(0, this.currentEntry.remainingBytes);
|
||||||
break;
|
this.buffer = this.buffer.subarray(chunk.length);
|
||||||
case TarType.SYMTYPE:
|
const finished = this.currentEntry.chunk(chunk);
|
||||||
await fs.promises.symlink(header.linkname, fullPath);
|
if (finished) {
|
||||||
break;
|
const padding = 512 - (this.currentEntry.size % 512);
|
||||||
case TarType.REGTYPE:
|
|
||||||
this.currentFileStream = fs.createWriteStream(fullPath, { mode: header.mode });
|
|
||||||
await once(this.currentFileStream, 'ready');
|
|
||||||
this.currentHeader = header;
|
|
||||||
this.remainingBytes = header.size;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported type ${header.type} for '${header.name}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(this.currentFileStream);
|
|
||||||
assert(this.remainingBytes > 0);
|
|
||||||
|
|
||||||
const dataChunk = this.buffer.subarray(0, this.remainingBytes);
|
|
||||||
this.buffer = this.buffer.subarray(this.remainingBytes);
|
|
||||||
this.remainingBytes -= dataChunk.length;
|
|
||||||
|
|
||||||
this.currentFileStream.write(dataChunk);
|
|
||||||
|
|
||||||
if (this.remainingBytes === 0) {
|
|
||||||
this.currentFileStream.end();
|
|
||||||
this.currentFileStream = null;
|
|
||||||
|
|
||||||
const padding = 512 - (this.currentHeader.size % 512);
|
|
||||||
if (padding < 512)
|
if (padding < 512)
|
||||||
this.buffer = this.buffer.subarray(padding);
|
this.buffer = this.buffer.subarray(padding);
|
||||||
|
this.currentEntry = null;
|
||||||
this.currentHeader = null;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue