Skip to content

Commit

Permalink
fix: fix tar padding (#943)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cafe137 authored Sep 10, 2024
1 parent e6487b2 commit a40d5ec
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 40 deletions.
2 changes: 1 addition & 1 deletion src/utils/tar-writer.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TarStream } from './tar'
export async function writeTar(collection: Collection, tarStream: TarStream) {
for (const item of collection) {
if (item.file) {
await tarStream.beginFile(item.path, item.file.size)
tarStream.beginFile(item.path, item.file.size)
await tarStream.appendFile(new Uint8Array(await fileArrayBuffer(item.file)))
await tarStream.endFile()
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/tar-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TarStream } from './tar'

export async function writeTar(collection: Collection, tarStream: TarStream) {
for (const item of collection) {
await tarStream.beginFile(item.path, item.size)
tarStream.beginFile(item.path, item.size)

if (item.fsPath) {
const stream = createReadStream(item.fsPath)
Expand Down
57 changes: 39 additions & 18 deletions src/utils/tar.browser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class TarStream {
pieces = [] as Uint8Array[]
currentFileSize = 0

get output() {
return this.pieces.reduce((acc, piece) => {
const newAcc = new Uint8Array(acc.length + piece.length)
Expand All @@ -10,54 +11,74 @@ export class TarStream {
return newAcc
})
}
async beginFile(path: string, size: number) {

beginFile(path: string, size: number) {
const header = createHeader(path, size)
this.pieces.push(header)
this.currentFileSize = 0
}

async appendFile(data: Uint8Array) {
this.pieces.push(data)
this.currentFileSize += data.length
}

async endFile() {
const padding = 512 - (this.currentFileSize % 512)
this.pieces.push(new Uint8Array(padding))
const padding = this.currentFileSize % 512 === 0 ? 0 : 512 - (this.currentFileSize % 512)
if (padding > 0) {

Check failure on line 28 in src/utils/tar.browser.ts

View workflow job for this annotation

GitHub Actions / check (16.x)

Expected blank line before this statement
this.pieces.push(new Uint8Array(padding))
}
}

async end() {
this.pieces.push(createEndOfArchive())
}
}

function createHeader(path: string, size: number): Uint8Array {
const header = new Uint8Array(512) // Initialize header with zeros
const encoder = new TextEncoder()

function writeToBuffer(str: string, offset: number, length: number) {
const bytes = encoder.encode(str)
header.set(bytes.slice(0, length), offset)
}

writeToBuffer(path, 0, 100) // File name
writeToBuffer('0000777', 100, 8) // File mode
writeToBuffer('0001750', 108, 8) // UID
writeToBuffer('0001750', 116, 8) // GID
writeToBuffer(size.toString(8).padStart(11, '0') + ' ', 124, 12) // File size
writeToBuffer(Math.floor(Date.now() / 1000).toString(8) + ' ', 136, 12) // Mod time
writeToBuffer(' ', 148, 8) // Checksum placeholder
writeToBuffer('0', 156, 1) // Typeflag
writeToBuffer('ustar ', 257, 8) // Magic and version

for (let i = 345; i < 512; i++) {
header[i] = 0 // Fill remaining with zeros
}
// Initialize header with zeros
const header = new Uint8Array()
header.fill(0, 0, 512)

// File name, truncated to 100 characters if necessary
writeToBuffer(path.slice(0, 100).padEnd(100, '\0'), 0, 100)

// File mode (octal) and null-terminated
writeToBuffer('0000777\0', 100, 8)

// UID and GID (octal) and null-terminated
writeToBuffer('0001750\0', 108, 8) // UID
writeToBuffer('0001750\0', 116, 8) // GID

// File size in octal (11 chars) and null-terminated
writeToBuffer(size.toString(8).padStart(11, '0') + '\0', 124, 12)

// Modification time in octal and null-terminated
const modTime = Math.floor(new Date().getTime() / 1000)
writeToBuffer(modTime.toString(8).padStart(11, '0') + '\0', 136, 12)

// Checksum placeholder (8 spaces)
writeToBuffer(' ', 148, 8)

// Typeflag (normal file)
writeToBuffer('0', 156, 1)

// USTAR magic and version
writeToBuffer('ustar\0\0', 257, 8)

// Calculate checksum
let checksum = 0
for (let i = 0; i < 512; i++) {
checksum += header[i]
}

// Write checksum
writeToBuffer(checksum.toString(8).padStart(6, '0') + '\0 ', 148, 8)

return header
Expand Down
77 changes: 57 additions & 20 deletions src/utils/tar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,80 @@ import { PassThrough } from 'stream'
export class TarStream {
output = new PassThrough()
currentFileSize = 0
async beginFile(path: string, size: number) {

beginFile(path: string, size: number) {
const header = createHeader(path, size)
this.output.write(header)
this.currentFileSize = 0
}

async appendFile(data: Uint8Array) {
return new Promise<void>(resolve => {
this.output.write(data, () => {
if (!this.output.write(data)) {
this.output.once('drain', () => {
resolve()
})
} else {
resolve()
})
}
this.currentFileSize += data.length
})
}

async endFile() {
const padding = 512 - (this.currentFileSize % 512)
this.output.write(Buffer.alloc(padding, 0))
const padding = this.currentFileSize % 512 === 0 ? 0 : 512 - (this.currentFileSize % 512)
if (padding > 0) {

Check failure on line 28 in src/utils/tar.ts

View workflow job for this annotation

GitHub Actions / check (16.x)

Expected blank line before this statement
this.output.write(Buffer.alloc(padding, 0))
}
}

async end() {
this.output.write(createEndOfArchive())
this.output.end()
return new Promise<void>(resolve => {
this.output.write(createEndOfArchive())
this.output.end(() => {
resolve()
})
})
}
}

function createHeader(path: string, size: number): Uint8Array {
const header = Buffer.alloc(512, 0) // Initialize header with zeros
header.write(path, 0, 100) // File name
header.write('0000777', 100, 8) // File mode
header.write('0001750', 108, 8) // UID
header.write('0001750', 116, 8) // GID
header.write(size.toString(8).padStart(11, '0') + ' ', 124, 12) // File size
header.write(Math.floor(new Date().getTime() / 1000).toString(8) + ' ', 136, 12) // Mod time
header.write(' ', 148, 8) // Checksum placeholder
header.write('0', 156, 1) // Typeflag
header.write('ustar ', 257, 8) // Magic and version
header.write('0'.repeat(8 * 12), 345, 8 * 12) // Fill remaining with zeros
const checksum = header.reduce((sum, elem) => sum + elem, 0)
header.write(checksum.toString(8).padStart(6, '0') + '\0 ', 148, 8) // Write checksum
// Initialize header with zeros
const header = Buffer.alloc(512, 0)

// File name, truncated to 100 characters if necessary
header.write(path.slice(0, 100).padEnd(100, '\0'), 0, 100)

// File mode (octal) and null-terminated
header.write('0000777\0', 100, 8)

// UID and GID (octal) and null-terminated
header.write('0001750\0', 108, 8) // UID
header.write('0001750\0', 116, 8) // GID

// File size in octal (11 chars) and null-terminated
header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12)

// Modification time in octal and null-terminated
const modTime = Math.floor(new Date().getTime() / 1000)
header.write(modTime.toString(8).padStart(11, '0') + '\0', 136, 12)

// Checksum placeholder (8 spaces)
header.write(' ', 148, 8)

// Typeflag (normal file)
header.write('0', 156, 1)

// USTAR magic and version
header.write('ustar\0\0', 257, 8)

// Calculate checksum
let checksum = 0
for (let i = 0; i < 512; i++) {
checksum += header[i]
}

header.write(checksum.toString(8).padStart(6, '0') + '\0 ', 148, 8)

return header
}
Expand Down

0 comments on commit a40d5ec

Please sign in to comment.