tinytar-fix
Version:
Pure javascript implementation of POSIX TAR file format.
268 lines (248 loc) • 9.02 kB
JavaScript
;
var utils = require('./utils');
var constants = require('./constants');
var recordSize = 512;
var defaultFileMode = constants.TPERMALL; // rwxrwxrwx
var defaultUid = 0; // root
var defaultGid = 0; // root
/*
struct posix_header { // byte offset
char name[100]; // 0
char mode[8]; // 100
char uid[8]; // 108
char gid[8]; // 116
char size[12]; // 124
char mtime[12]; // 136
char chksum[8]; // 148
char typeflag; // 156
char linkname[100]; // 157
char magic[6]; // 257
char version[2]; // 263
char uname[32]; // 265
char gname[32]; // 297
char devmajor[8]; // 329
char devminor[8]; // 337
char prefix[131]; // 345
char atime[12]; // 476
char ctime[12]; // 488
};
*/
var posixHeader = [
// <field name>, <size>, <offset>, <used>, <format>, <parse>, [ <check> ]
['name', 100, 0, function(file, field) {
return formatTarString(file[field[0]], field[1]);
}, function(buffer, offset, field) {
return parseTarString(buffer.slice(offset, offset + field[1]));
}],
['mode', 8, 100, function(file, field) {
var mode = file[field[0]] || defaultFileMode;
mode = mode & constants.TPERMMASK;
return formatTarNumber(mode, field[1], defaultFileMode);
}, function(buffer, offset, field) {
var result = parseTarNumber(buffer.slice(offset, offset + field[1]));
result &= constants.TPERMMASK;
return result;
}],
['uid', 8, 108, function(file, field) {
return formatTarNumber(file[field[0]], field[1], defaultUid);
}, function(buffer, offset, field) {
return parseTarNumber(buffer.slice(offset, offset + field[1]));
}],
['gid', 8, 116, function(file, field) {
return formatTarNumber(file[field[0]], field[1], defaultGid);
}, function(buffer, offset, field) {
return parseTarNumber(buffer.slice(offset, offset + field[1]));
}],
['size', 12, 124, function(file, field) {
return formatTarNumber(file.data.length, field[1]);
}, function(buffer, offset, field) {
return parseTarNumber(buffer.slice(offset, offset + field[1]));
}],
['modifyTime', 12, 136, function(file, field) {
return formatTarDateTime(file[field[0]], field[1]);
}, function(buffer, offset, field) {
return parseTarDateTime(buffer.slice(offset, offset + field[1]));
}],
['checksum', 8, 148, function(file, field) {
return ' '; // placeholder
}, function(buffer, offset, field) {
return parseTarNumber(buffer.slice(offset, offset + field[1]));
}],
['type', 1, 156, function(file, field) {
// get last octal digit; 0 - regular file
return '' + ((parseInt(file[field[0]], 10) || 0) % 8);
}, function(buffer, offset, field) {
return (parseInt(String.fromCharCode(buffer[offset]), 10) || 0) % 8;
}],
['linkName', 100, 157, function(file, field) {
return ''; // only regular files are supported
}, function(buffer, offset, field) {
return parseTarString(buffer.slice(offset, offset + field[1]));
}],
['ustar', 8, 257, function(file, field) {
return constants.TMAGIC; // magic + version
}, function(buffer, offset, field) {
return fixUstarMagic(
parseTarString(buffer.slice(offset, offset + field[1]), true)
);
}, function(file, field) {
return (file[field[0]] == constants.TMAGIC) ||
(file[field[0]] == constants.OLDGNU_MAGIC);
}],
['owner', 32, 265, function(file, field) {
return formatTarString(file[field[0]], field[1]);
}, function(buffer, offset, field) {
return parseTarString(buffer.slice(offset, offset + field[1]));
}],
['group', 32, 297, function(file, field) {
return formatTarString(file[field[0]], field[1]);
}, function(buffer, offset, field) {
return parseTarString(buffer.slice(offset, offset + field[1]));
}],
['majorNumber', 8, 329, function(file, field) {
return ''; // only regular files are supported
}, function(buffer, offset, field) {
return parseTarNumber(buffer.slice(offset, offset + field[1]));
}],
['minorNumber', 8, 337, function(file, field) {
return ''; // only regular files are supported
}, function(buffer, offset, field) {
return parseTarNumber(buffer.slice(offset, offset + field[1]));
}],
['prefix', 131, 345, function(file, field) {
return formatTarString(file[field[0]], field[1]);
}, function(buffer, offset, field) {
return parseTarString(buffer.slice(offset, offset + field[1]));
}],
['accessTime', 12, 476, function(file, field) {
return formatTarDateTime(file[field[0]], field[1]);
}, function(buffer, offset, field) {
return parseTarDateTime(buffer.slice(offset, offset + field[1]));
}],
['createTime', 12, 488, function(file, field) {
return formatTarDateTime(file[field[0]], field[1]);
}, function(buffer, offset, field) {
return parseTarDateTime(buffer.slice(offset, offset + field[1]));
}]
];
var effectiveHeaderSize = (function(header) {
var last = header[header.length - 1];
return last[2] + last[1]; // offset + size
})(posixHeader);
function fixUstarMagic(value) {
if (value.length == 8) {
var chars = value.split('');
if (chars[5] == constants.NULL_CHAR) {
// TMAGIC ?
if ((chars[6] == ' ') || (chars[6] == constants.NULL_CHAR)) {
chars[6] = '0';
}
if ((chars[7] == ' ') || (chars[7] == constants.NULL_CHAR)) {
chars[7] = '0';
}
chars = chars.join('');
return chars == constants.TMAGIC ? chars : value;
} else if (chars[7] == constants.NULL_CHAR) {
// OLDGNU_MAGIC ?
if (chars[5] == constants.NULL_CHAR) {
chars[5] = ' ';
}
if (chars[6] == constants.NULL_CHAR) {
chars[6] = ' ';
}
return chars == constants.OLDGNU_MAGIC ? chars : value;
}
}
return value;
}
function formatTarString(value, length) {
length -= 1; // preserve space for trailing null-char
if (utils.isUndefined(value)) {
value = '';
}
value = ('' + value).substr(0, length);
return value + constants.NULL_CHAR;
}
function formatTarNumber(value, length, defaultValue) {
defaultValue = parseInt(defaultValue) || 0;
length -= 1; // preserve space for trailing null-char
value = (parseInt(value) || defaultValue)
.toString(8).substr(-length, length);
while (value.length < length) {
value = '0' + value;
}
return value + constants.NULL_CHAR;
}
function formatTarDateTime(value, length) {
if (utils.isDateTime(value)) {
value = Math.floor(1 * value / 1000);
} else {
value = parseInt(value, 10);
if (isFinite(value)) {
if (value <= 0) {
return '';
}
} else {
value = Math.floor(1 * new Date() / 1000);
}
}
return formatTarNumber(value, length, 0);
}
function parseTarString(bytes, returnUnprocessed) {
var result = String.fromCharCode.apply(null, bytes);
if (returnUnprocessed) {
return result;
}
var index = result.indexOf(constants.NULL_CHAR);
return index >= 0 ? result.substr(0, index) : result;
}
function parseTarNumber(bytes) {
var result = String.fromCharCode.apply(null, bytes);
return parseInt(result.replace(/^0+$/g, ''), 8) || 0;
}
function parseTarDateTime(bytes) {
if ((bytes.length == 0) || (bytes[0] == 0)) {
return null;
}
return new Date(1000 * parseTarNumber(bytes));
}
function calculateChecksum(buffer, offset, skipChecksum) {
var from = parseInt(offset, 10) || 0;
var to = Math.min(from + effectiveHeaderSize, buffer.length);
var result = 0;
// When calculating checksum, `checksum` field should be
// threat as filled with space char (byte 32)
var skipFrom = 0;
var skipTo = 0;
if (skipChecksum) {
posixHeader.every(function(field) {
if (field[0] == 'checksum') {
skipFrom = from + field[2];
skipTo = skipFrom + field[1];
return false;
}
return true;
});
}
var whitespace = ' '.charCodeAt(0);
for (var i = from; i < to; i++) {
// 262144 = 8^6 - 6 octal digits - maximum possible value for checksum;
// wrap to avoid numeric overflow
var byte = (i >= skipFrom) && (i < skipTo) ? whitespace : buffer[i];
result = (result + byte) % 262144;
}
return result;
}
module.exports.recordSize = recordSize;
module.exports.defaultFileMode = defaultFileMode;
module.exports.defaultUid = defaultUid;
module.exports.defaultGid = defaultGid;
module.exports.posixHeader = posixHeader;
module.exports.effectiveHeaderSize = effectiveHeaderSize;
module.exports.calculateChecksum = calculateChecksum;
module.exports.formatTarString = formatTarString;
module.exports.formatTarNumber = formatTarNumber;
module.exports.formatTarDateTime = formatTarDateTime;
module.exports.parseTarString = parseTarString;
module.exports.parseTarNumber = parseTarNumber;
module.exports.parseTarDateTime = parseTarDateTime;