Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix tar padding #943

Merged
merged 1 commit into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
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
Loading