From 972c980a12527d48267c607a0a74deedf95f7e69 Mon Sep 17 00:00:00 2001 From: korya Date: Tue, 24 Oct 2023 14:14:44 -0400 Subject: [PATCH 1/5] Explicitly convert a buffer to a UTF-8 string when parsing names --- tar-extractor.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tar-extractor.ts b/tar-extractor.ts index e8a86a6..3108a14 100644 --- a/tar-extractor.ts +++ b/tar-extractor.ts @@ -56,7 +56,7 @@ export class TarExtractor { parseHeader(buffer: Buffer, paxHeaderData: Record): TarFileHeader | null { const h = new TarFileHeader(); - h.name = buffer.subarray(0, 100).toString().replace(/\0/g, ''); + h.name = buffer.subarray(0, 100).toString('utf8').replace(/\0/g, ''); if (!h.name) { return null; } @@ -64,13 +64,13 @@ export class TarExtractor { // 8 bytes for mode // 8 bytes for uid // 8 bytes for gid - h.size = parseInt(buffer.subarray(124, 136).toString(), 8); + h.size = parseInt(buffer.subarray(124, 136).toString('utf8'), 8); // 12 bytes for mtime // 8 bytes for checksum - h.typeFlag = buffer.subarray(156, 157).toString(); + h.typeFlag = buffer.subarray(156, 157).toString('utf8'); // 100 bytes for linkname - h.ustarIndicator = buffer.subarray(257, 263).toString(); - h.prefix = buffer.subarray(345, 500).toString().replace(/\0/g, ''); + h.ustarIndicator = buffer.subarray(257, 263).toString('utf8'); + h.prefix = buffer.subarray(345, 500).toString('utf8').replace(/\0/g, ''); if (h.prefix) { h.name = `${h.prefix}/${h.name}`; } From a0f9b309efdb2d8fbc86cc34e759fea444dd31c5 Mon Sep 17 00:00:00 2001 From: korya Date: Tue, 24 Oct 2023 14:17:29 -0400 Subject: [PATCH 2/5] Trim 0 bytes at the end of the string when parsing names --- tar-extractor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tar-extractor.ts b/tar-extractor.ts index 3108a14..e61bbd9 100644 --- a/tar-extractor.ts +++ b/tar-extractor.ts @@ -56,7 +56,7 @@ export class TarExtractor { parseHeader(buffer: Buffer, paxHeaderData: Record): TarFileHeader | null { const h = new TarFileHeader(); - h.name = buffer.subarray(0, 100).toString('utf8').replace(/\0/g, ''); + h.name = buffer.subarray(0, 100).toString('utf8').replace(/\0+$/, ''); if (!h.name) { return null; } @@ -70,7 +70,7 @@ export class TarExtractor { h.typeFlag = buffer.subarray(156, 157).toString('utf8'); // 100 bytes for linkname h.ustarIndicator = buffer.subarray(257, 263).toString('utf8'); - h.prefix = buffer.subarray(345, 500).toString('utf8').replace(/\0/g, ''); + h.prefix = buffer.subarray(345, 500).toString('utf8').replace(/\0+$/, ''); if (h.prefix) { h.name = `${h.prefix}/${h.name}`; } From cd4f4e9ba6a36d0f9a87b6fc4701f0abf3f853d1 Mon Sep 17 00:00:00 2001 From: korya Date: Tue, 24 Oct 2023 14:18:36 -0400 Subject: [PATCH 3/5] Log the parsed file name when debug flag is set --- tar-extractor.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tar-extractor.ts b/tar-extractor.ts index e61bbd9..c0e18bc 100644 --- a/tar-extractor.ts +++ b/tar-extractor.ts @@ -57,6 +57,8 @@ export class TarExtractor { const h = new TarFileHeader(); h.name = buffer.subarray(0, 100).toString('utf8').replace(/\0+$/, ''); + // XXX: Remove this debug print later + this.__log(`parsed file name '${h.name}' from header block of size ${buffer.length}`); if (!h.name) { return null; } From b51eaf41d33c126ebac1c29a93c1bf338aab8af0 Mon Sep 17 00:00:00 2001 From: korya Date: Tue, 24 Oct 2023 15:13:02 -0400 Subject: [PATCH 4/5] Support 2 log levels: debug and trace --- tar-extractor.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tar-extractor.ts b/tar-extractor.ts index c0e18bc..2896055 100644 --- a/tar-extractor.ts +++ b/tar-extractor.ts @@ -1,9 +1,11 @@ import RNFS from 'react-native-fs'; import { Buffer } from 'buffer'; +type LogLevel = '' | 'debug' | 'trace'; + export class TarExtractor { blockSize = 512; - debug = false; + logLevel: LogLevel = ''; async read( tarFilePath: string, @@ -13,20 +15,20 @@ export class TarExtractor { let offset = 0; let paxHeaderData = {}; - this.__log(`Reading ${tarFilePath} (${fileSize} bytes)`); + this.__log('debug', `Reading ${tarFilePath} (${fileSize} bytes)`); while (offset < fileSize) { - this.__log(`Reading header at offset ${offset}`); + this.__log('debug', `Reading header at offset ${offset}`); const headerBuffer = await this.readChunk(tarFilePath, offset, this.blockSize); const header = this.parseHeader(headerBuffer, paxHeaderData); if (!header) { - this.__log(`Invalid header at offset ${offset}`); + this.__log('debug', `Invalid header at offset ${offset}`); break; } offset += this.blockSize; if (header.isPax()) { - this.__log(`Found PAX header at offset ${offset}, size ${header.size}`); + this.__log('debug', `Found PAX header at offset ${offset}, size ${header.size}`); // PAX headers can span multiple blocks const paxBuffer = await this.readChunk(tarFilePath, offset, header.size); paxHeaderData = this.parsePaxHeader(paxBuffer); @@ -35,7 +37,7 @@ export class TarExtractor { continue; // Skip to the next iteration to handle the next header } - this.__log(`Iterating over file ${header.name} at offset ${offset}, size ${header.size}`); + this.__log('debug', `Iterating over file ${header.name} at offset ${offset}, size ${header.size}`); const fileDataSize = header.size; const file = new TarFile(header, () => this.readChunk(tarFilePath, offset, fileDataSize)); const shouldContinue = await callback(file); @@ -57,8 +59,7 @@ export class TarExtractor { const h = new TarFileHeader(); h.name = buffer.subarray(0, 100).toString('utf8').replace(/\0+$/, ''); - // XXX: Remove this debug print later - this.__log(`parsed file name '${h.name}' from header block of size ${buffer.length}`); + this.__log('trace', `- file name '${h.name}' from `, buffer.subarray(0, 100)); if (!h.name) { return null; } @@ -67,12 +68,16 @@ export class TarExtractor { // 8 bytes for uid // 8 bytes for gid h.size = parseInt(buffer.subarray(124, 136).toString('utf8'), 8); + this.__log('trace', `- file size '${h.size}' from `, buffer.subarray(124, 136)); // 12 bytes for mtime // 8 bytes for checksum h.typeFlag = buffer.subarray(156, 157).toString('utf8'); + this.__log('trace', `- file type '${h.typeFlag}' from `, buffer.subarray(156, 157)); // 100 bytes for linkname h.ustarIndicator = buffer.subarray(257, 263).toString('utf8'); + this.__log('trace', `- ustar ind. '${h.ustarIndicator}' from `, buffer.subarray(257, 263)); h.prefix = buffer.subarray(345, 500).toString('utf8').replace(/\0+$/, ''); + this.__log('trace', `- prefix '${h.prefix}' from `, buffer.subarray(345, 500)); if (h.prefix) { h.name = `${h.prefix}/${h.name}`; } @@ -85,6 +90,7 @@ export class TarExtractor { h.size = parseInt(paxHeaderData['size'], 10); } + this.__log('trace', `header parsed:`, h); return h; } @@ -124,8 +130,12 @@ export class TarExtractor { return paxHeaderData; } - __log(...args: any[]): void { - if (this.debug) { + setLogLevel(level: LogLevel): void { + this.logLevel = level; + } + + __log(level: LogLevel, ...args: any[]): void { + if (this.logLevel === level || this.logLevel === 'trace') { args[0] = `rnfs-tar: ${args[0]}`; console.log.apply(console, args); } From 769b89aca110bc0b1f59c1cfed6096a21a6c8741 Mon Sep 17 00:00:00 2001 From: korya Date: Tue, 24 Oct 2023 15:14:14 -0400 Subject: [PATCH 5/5] Parse and slice string values using Buffer's toString() --- tar-extractor.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tar-extractor.ts b/tar-extractor.ts index 2896055..8cfe9d0 100644 --- a/tar-extractor.ts +++ b/tar-extractor.ts @@ -58,7 +58,7 @@ export class TarExtractor { parseHeader(buffer: Buffer, paxHeaderData: Record): TarFileHeader | null { const h = new TarFileHeader(); - h.name = buffer.subarray(0, 100).toString('utf8').replace(/\0+$/, ''); + h.name = this.__parseStr(buffer, 0, 100, true); this.__log('trace', `- file name '${h.name}' from `, buffer.subarray(0, 100)); if (!h.name) { return null; @@ -67,16 +67,16 @@ export class TarExtractor { // 8 bytes for mode // 8 bytes for uid // 8 bytes for gid - h.size = parseInt(buffer.subarray(124, 136).toString('utf8'), 8); + h.size = parseInt(this.__parseStr(buffer, 124, 136), 8); this.__log('trace', `- file size '${h.size}' from `, buffer.subarray(124, 136)); // 12 bytes for mtime // 8 bytes for checksum - h.typeFlag = buffer.subarray(156, 157).toString('utf8'); + h.typeFlag = this.__parseStr(buffer, 156, 157, true); this.__log('trace', `- file type '${h.typeFlag}' from `, buffer.subarray(156, 157)); // 100 bytes for linkname - h.ustarIndicator = buffer.subarray(257, 263).toString('utf8'); + h.ustarIndicator = this.__parseStr(buffer, 257, 263); this.__log('trace', `- ustar ind. '${h.ustarIndicator}' from `, buffer.subarray(257, 263)); - h.prefix = buffer.subarray(345, 500).toString('utf8').replace(/\0+$/, ''); + h.prefix = this.__parseStr(buffer, 345, 500, true); this.__log('trace', `- prefix '${h.prefix}' from `, buffer.subarray(345, 500)); if (h.prefix) { h.name = `${h.prefix}/${h.name}`; @@ -134,6 +134,11 @@ export class TarExtractor { this.logLevel = level; } + __parseStr(buffer: Buffer, start: number, end: number, trim?: boolean): string { + const s = buffer.toString('utf8', start, end); + return !trim ? s : s.replace(/\0+$/, ''); + } + __log(level: LogLevel, ...args: any[]): void { if (this.logLevel === level || this.logLevel === 'trace') { args[0] = `rnfs-tar: ${args[0]}`;