Skip to content

Commit

Permalink
examples/compareCentralAndLocalHeaders.js
Browse files Browse the repository at this point in the history
  • Loading branch information
thejoshwolfe committed Feb 18, 2024
1 parent c18cfe4 commit 72b1821
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 7 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,11 +452,10 @@ These fields are of type `Number`:
These fields are of type `Buffer`, and represent variable-length bytes before being processed:
* `fileNameRaw`
* `extraFieldRaw`
* `commentRaw`
* `fileCommentRaw`

There are additional fields described below: `fileName`, `extraFields`, `comment`.
These are the `*Raw` fields above after going through some processing, such as UTF-8 decoding.
See their own sections below.
There are additional fields described below: `fileName`, `extraFields`, `fileComment`.
These are the processed versions of the `*Raw` fields listed above. See their own sections below.
(Note the inconsistency in pluralization of "field" vs "fields" in `extraField`, `extraFields`, and `extraFieldRaw`.
Sorry about that.)

Expand Down Expand Up @@ -761,7 +760,8 @@ This library makes no attempt to interpret the Language Encoding Flag.
* Added `readLocalFileHeader()` and `Class: LocalFileHeader`.
* Added `openReadStreamLowLevel()`.
* Added `getFileNameLowLevel()` and `parseExtraFields()`.
Added fields to `Class: Entry`: `fileNameRaw`, `extraFieldRaw`, `commentRaw`.
Added fields to `Class: Entry`: `fileNameRaw`, `extraFieldRaw`, `fileCommentRaw`.
* Added `examples/compareCentralAndLocalHeaders.js` that demonstrate many of these low level APIs.
* Noted dropped support of node versions before 12 in the `"engines"` field of `package.json`.
* 3.0.0
* BREAKING CHANGE: implementations of [RandomAccessReader](#class-randomaccessreader) that implement a `destroy` method must instead implement `_destroy` in accordance with the node standard https://nodejs.org/api/stream.html#writable_destroyerr-callback (note the error and callback parameters). If you continue to override `destory` instead, some error handling may be subtly broken. Additionally, this is required for async iterators to work correctly in some versions of node. [issue #110](https://github.com/thejoshwolfe/yauzl/issues/110)
Expand Down
142 changes: 142 additions & 0 deletions examples/compareCentralAndLocalHeaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@

var yauzl = require("../"); // replace with: var yauzl = require("yauzl");

function usage() {
console.log(
"usage: node compareCentralAndLocalHeaders.js path/to/file.zip\n" +
"\n" +
"Shows a table comparing the central directory metadata and local\n" +
"file headers for each item in a zipfile.\n" +
"");
process.exit(1);
}

var zipfilePath = null;
var detailedExtraFieldBreakdown = true;
process.argv.slice(2).forEach(function(arg) {
if (/^-/.test(arg)) usage();
if (zipfilePath != null) usage();
zipfilePath = arg;
});
if (zipfilePath == null) usage();

yauzl.open(zipfilePath, {lazyEntries: true, decodeStrings: false}, function(err, zipfile) {
if (err) throw err;
zipfile.on("error", function(err) {
throw err;
});
zipfile.readEntry();
zipfile.on("entry", function(entry) {
zipfile.readLocalFileHeader(entry, function(err, localFileHeader) {
if (err) throw err;
compare(entry, localFileHeader);
zipfile.readEntry();
});
});
})

function compare(entry, localFileHeader) {
console.log(yauzl.getFileNameLowLevel(entry.generalPurposeBitFlag, entry.fileNameRaw, entry.extraFields, false));

// Compare integers
var integerFields = [
"versionMadeBy",
"versionNeededToExtract",
"generalPurposeBitFlag",
"compressionMethod",
"lastModFileTime",
"lastModFileDate",
"crc32",
"compressedSize",
"uncompressedSize",
"fileNameLength",
"extraFieldLength",
"fileCommentLength",
"internalFileAttributes",
"externalFileAttributes",
"relativeOffsetOfLocalHeader",
];
function formatNumber(numberOrNull) {
if (numberOrNull == null) return "-";
return "0x" + numberOrNull.toString(16);
}

var rows = [["field", "central", "local", "diff"]];
rows.push(...integerFields.map(function(name) {
var a = entry[name];
var b = localFileHeader[name];
var diff = a == null || b == null ? "" :
a === b ? "" : "x";
return [name, formatNumber(a), formatNumber(b), diff]
}));

var columnWidths = [0, 1, 2, 3].map(function(i) {
return Math.max(...rows.map(row => row[i].length));
});
var formatFunctions = ["padEnd", "padStart", "padStart", "padEnd"];

console.log("┌" + columnWidths.map(w => "─".repeat(w)).join("┬") + "┐");
for (var i = 0; i < rows.length; i++) {
console.log("│" + rows[i].map((x, j) => x[formatFunctions[j]](columnWidths[j])).join("│") + "│");
if (i === 0) {
console.log("├" + columnWidths.map(w => "─".repeat(w)).join("┼") + "┤");
}
}
console.log("└" + columnWidths.map(w => "─".repeat(w)).join("┴") + "┘");

// Compare variable length data.
console.log("central.fileName:", entry.fileNameRaw.toString("hex"));
if (entry.fileNameRaw.equals(localFileHeader.fileName)) {
console.log("(local matches)");
} else {
console.log(" local.fileName:", localFileHeader.fileName.toString("hex"));
}
console.log("central.extraField:", entry.extraFieldRaw.toString("hex"));
if (entry.extraFieldRaw.equals(localFileHeader.extraField)) {
console.log("(local matches)");
} else {
console.log(" local.extraField:", localFileHeader.extraField.toString("hex"));
}

if (detailedExtraFieldBreakdown) {
var centralExtraFieldMap = extraFieldsToMap(entry.extraFields);
var localExtraFieldMap = extraFieldsToMap(yauzl.parseExtraFields(localFileHeader.extraField));
for (var key in centralExtraFieldMap) {
var centralData = centralExtraFieldMap[key];
var localData = localExtraFieldMap[key];
delete localExtraFieldMap[key]; // to isolate unhandled keys.
console.log(" [" + key + "]central:", centralData.toString("hex"));
if (localData != null && centralData.equals(localData)) {
console.log(" [" + key + "](local matches)");
} else if (localData != null) {
console.log(" [" + key + "] local:", localData.toString("hex"));
} else {
console.log(" [" + key + "] local: <missing>");
}
}
// Any keys left here don't match anything.
for (var key in localExtraFieldMap) {
var localData = localExtraFieldMap[key];
console.log(" [" + key + "]central: <missing>");
console.log(" [" + key + "] local:", localData.toString("hex"));
}
}
console.log("central.comment:", entry.fileCommentRaw.toString("hex"));

console.log("");
}

function extraFieldsToMap(extraFields) {
var map = {};
extraFields.forEach(({id, data}) => {
var key = "0x" + id.toString(16).padStart(4, "0");
var baseKey = key;
var i = 1;
while (key in map) {
key = baseKey + "." + i;
i++;
}
map[key] = data;
});
return map;
}
3 changes: 1 addition & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,7 @@ function parseExtraFields(extraFieldBuffer) {
var dataStart = i + 4;
var dataEnd = dataStart + dataSize;
if (dataEnd > extraFieldBuffer.length) throw new Error("extra field length exceeds extra field buffer size");
var dataBuffer = newBuffer(dataSize);
extraFieldBuffer.copy(dataBuffer, 0, dataStart, dataEnd);
var dataBuffer = extraFieldBuffer.subarray(dataStart, dataEnd);
extraFields.push({
id: headerId,
data: dataBuffer,
Expand Down

0 comments on commit 72b1821

Please sign in to comment.