UNPKG

tar-iterator

Version:

Extract contents from tar archive type using an iterator API using streams or paths. Use stream interface and pipe transforms to add decompression algorithms

125 lines (124 loc) 4.63 kB
/** * Extensions - GNU/PAX extension handling for TAR * * Manages state and decoding for: * - GNU LongPath (type 'L') - paths > 100 chars * - GNU LongLink (type 'K') - symlink targets > 100 chars * - PAX Headers (type 'x') - extended attributes per-entry * - PAX Global Headers (type 'g') - extended attributes for all entries */ import { allocBufferUnsafe } from 'extract-base-iterator'; import { decodeLongPath, decodePax } from './headers.js'; /** * Create a fresh extension state */ export function createExtensionState() { return { gnuLongPath: null, gnuLongLink: null, paxHeader: null, paxGlobal: {}, extensionData: [], extensionRemaining: 0 }; } /** * Finalize extension data and update state * * @param state Extension state to update * @param currentState Parser state (STATE_GNU_LONG_PATH, etc.) * @param header Current header (for PAX global detection) * @param encoding Filename encoding */ export function finalizeExtension(state, currentState, header, encoding) { // Import state constants const STATE_GNU_LONG_PATH = 4; const STATE_GNU_LONG_LINK = 5; const STATE_PAX_HEADER = 6; // Concatenate all collected data const combined = concatBuffers(state.extensionData); state.extensionData = []; switch(currentState){ case STATE_GNU_LONG_PATH: state.gnuLongPath = decodeLongPath(combined, encoding); break; case STATE_GNU_LONG_LINK: state.gnuLongLink = decodeLongPath(combined, encoding); break; case STATE_PAX_HEADER: // Check if this was a global header if (header && header.type === 'pax-global-header') { const global = decodePax(combined); // Merge into global (don't replace, merge) for(const key in global){ // biome-ignore lint/suspicious/noPrototypeBuiltins: ES2021 compatibility if (global.hasOwnProperty(key)) { state.paxGlobal[key] = global[key]; } } } else { state.paxHeader = decodePax(combined); } break; } } /** * Apply pending GNU/PAX extensions to a header * * @param header Header to modify * @param state Extension state (will be partially cleared) */ export function applyExtensions(header, state) { // Apply PAX global header first if (state.paxGlobal) { applyPaxToHeader(header, state.paxGlobal); } // Apply PAX header (per-entry, overrides global) if (state.paxHeader) { applyPaxToHeader(header, state.paxHeader); header.pax = state.paxHeader; state.paxHeader = null; } // Apply GNU long path (overrides PAX path) if (state.gnuLongPath !== null) { header.name = state.gnuLongPath; state.gnuLongPath = null; } // Apply GNU long link (overrides PAX linkpath) if (state.gnuLongLink !== null) { header.linkname = state.gnuLongLink; state.gnuLongLink = null; } // Handle old tar versions that use trailing / to indicate directories // This check is done AFTER extensions are applied so we use the final // resolved name (GNU long path or PAX path), not the truncated name field. if (header.type === 'file' && header.name && header.name[header.name.length - 1] === '/') { header.type = 'directory'; } } /** * Apply PAX attributes to header */ export function applyPaxToHeader(header, pax) { if (pax.path) header.name = pax.path; if (pax.linkpath) header.linkname = pax.linkpath; if (pax.size) header.size = parseInt(pax.size, 10); if (pax.uid) header.uid = parseInt(pax.uid, 10); if (pax.gid) header.gid = parseInt(pax.gid, 10); if (pax.uname) header.uname = pax.uname; if (pax.gname) header.gname = pax.gname; if (pax.mtime) header.mtime = new Date(parseFloat(pax.mtime) * 1000); } /** * Concatenate buffers (Node 0.8 compatible) */ export function concatBuffers(buffers) { if (buffers.length === 0) return Buffer.alloc ? Buffer.alloc(0) : new Buffer(0); if (buffers.length === 1) return buffers[0]; const totalLength = buffers.reduce((sum, buf)=>sum + buf.length, 0); if (Buffer.concat) { return Buffer.concat(buffers, totalLength); } // Node 0.8 fallback const result = allocBufferUnsafe(totalLength); let offset = 0; for(let i = 0; i < buffers.length; i++){ buffers[i].copy(result, offset); offset += buffers[i].length; } return result; }