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

Make parsing of file names more robust #8

Merged
merged 5 commits into from
Oct 24, 2023
Merged
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
43 changes: 30 additions & 13 deletions tar-extractor.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -56,21 +58,26 @@ export class TarExtractor {
parseHeader(buffer: Buffer, paxHeaderData: Record<string, any>): TarFileHeader | null {
const h = new TarFileHeader();

h.name = buffer.subarray(0, 100).toString().replace(/\0/g, '');
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;
}

// 8 bytes for mode
// 8 bytes for uid
// 8 bytes for gid
h.size = parseInt(buffer.subarray(124, 136).toString(), 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();
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();
h.prefix = buffer.subarray(345, 500).toString().replace(/\0/g, '');
h.ustarIndicator = this.__parseStr(buffer, 257, 263);
this.__log('trace', `- ustar ind. '${h.ustarIndicator}' from `, buffer.subarray(257, 263));
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}`;
}
Expand All @@ -83,6 +90,7 @@ export class TarExtractor {
h.size = parseInt(paxHeaderData['size'], 10);
}

this.__log('trace', `header parsed:`, h);
return h;
}

Expand Down Expand Up @@ -122,8 +130,17 @@ export class TarExtractor {
return paxHeaderData;
}

__log(...args: any[]): void {
if (this.debug) {
setLogLevel(level: LogLevel): void {
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]}`;
console.log.apply(console, args);
}
Expand Down
Loading