Skip to content

Commit 85fec93

Browse files
committed
test(host-kit): restore zero tar modes by entry order
Matching headers by name would patch every duplicate when one declares a zero mode and depended on the 100-byte name field. The fixture now patches by emitted entry order, skipping the PAX extended header that precedes an entry, and the zero-mode tar test carries a PAX record to prove the skip.
1 parent 4b1b594 commit 85fec93

2 files changed

Lines changed: 24 additions & 22 deletions

File tree

‎packages/host-kit/src/internal/archive-extraction.fixtures.ts‎

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,50 +99,48 @@ export async function writeTarFixture(
9999
entries: readonly TarFixtureEntry[],
100100
): Promise<void> {
101101
const pack = tar.pack();
102-
const zeroModeNames = new Set<string>();
103-
for (const entry of entries) {
104-
if (entry.header.mode === 0) zeroModeNames.add(entry.header.name);
102+
const zeroModeEntries = new Set<number>();
103+
entries.forEach((entry, index) => {
104+
if (entry.header.mode === 0) zeroModeEntries.add(index);
105105
const header: tar.Headers & { pax?: Record<string, string> } = entry.pax
106106
? { ...entry.header, pax: entry.pax }
107107
: { ...entry.header };
108108
pack.entry(header, Buffer.from(entry.data ?? ''));
109-
}
109+
});
110110
pack.finalize();
111111
const chunks: Buffer[] = [];
112112
for await (const chunk of pack) chunks.push(Buffer.from(chunk));
113113
const archive = Buffer.concat(chunks);
114-
restoreZeroTarModes(archive, zeroModeNames);
114+
restoreZeroTarModes(archive, zeroModeEntries);
115115
await fs.writeFile(archivePath, archive);
116116
}
117117

118118
const TAR_BLOCK = 512;
119+
const PAX_TYPEFLAGS = new Set(['x', 'g']);
119120

120121
/**
121122
* tar-stream's pack replaces a zero mode with its default, so a declared zero is written back into
122-
* the emitted ustar header, checksum included.
123+
* the emitted ustar header, checksum included. Entries are matched by emitted order; the PAX
124+
* extended header tar-stream emits before an entry is not counted.
123125
*/
124-
function restoreZeroTarModes(archive: Buffer, names: ReadonlySet<string>): void {
125-
if (names.size === 0) return;
126+
function restoreZeroTarModes(archive: Buffer, zeroModeEntries: ReadonlySet<number>): void {
127+
if (zeroModeEntries.size === 0) return;
128+
let entryIndex = 0;
126129
for (let offset = 0; offset + TAR_BLOCK <= archive.length;) {
127130
const block = archive.subarray(offset, offset + TAR_BLOCK);
128-
const name = readTarField(block, 0, 100);
129-
if (!name) return;
130-
if (names.has(name.replace(/\/$/, ''))) {
131-
block.write('000000 ', 100, 'ascii');
132-
block.write(`${tarChecksum(block).toString(8).padStart(6, '0')} `, 148, 'ascii');
131+
if (block.every((byte) => byte === 0)) return;
132+
if (!PAX_TYPEFLAGS.has(String.fromCharCode(block[156]!))) {
133+
if (zeroModeEntries.has(entryIndex)) {
134+
block.write('000000 ', 100, 'ascii');
135+
block.write(`${tarChecksum(block).toString(8).padStart(6, '0')} `, 148, 'ascii');
136+
}
137+
entryIndex += 1;
133138
}
134-
const size = Number.parseInt(readTarField(block, 124, 12), 8) || 0;
139+
const size = Number.parseInt(block.toString('ascii', 124, 136).split('\0')[0]!, 8) || 0;
135140
offset += TAR_BLOCK + Math.ceil(size / TAR_BLOCK) * TAR_BLOCK;
136141
}
137142
}
138143

139-
function readTarField(block: Buffer, start: number, length: number): string {
140-
return block
141-
.toString('ascii', start, start + length)
142-
.split('\0')[0]!
143-
.trim();
144-
}
145-
146144
function tarChecksum(block: Buffer): number {
147145
let sum = 8 * 0x20;
148146
for (let index = 0; index < TAR_BLOCK; index += 1) {

‎packages/host-kit/src/internal/archive-extraction.test.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,11 @@ test.each<ArchiveFixture>([
236236
type: 'tar',
237237
entries: [
238238
{ header: { name: 'App.app/private', type: 'directory', mode: 0 } },
239-
{ header: { name: 'App.app/private/secret', mode: 0 }, data: 'x' },
239+
{
240+
header: { name: 'App.app/private/secret', mode: 0 },
241+
pax: { path: 'App.app/private/secret' },
242+
data: 'x',
243+
},
240244
],
241245
},
242246
])('an explicit $type mode of zero extracts with owner access only', async (fixture) => {

0 commit comments

Comments
 (0)