Skip to content

Commit

Permalink
replace backslashes by default. closes #66, closes #88
Browse files Browse the repository at this point in the history
  • Loading branch information
thejoshwolfe committed Jul 3, 2018
1 parent 3e0fa59 commit 6a9e652
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 13 deletions.
23 changes: 18 additions & 5 deletions README.md
Expand Up @@ -65,7 +65,7 @@ function defaultCallback(err) {

Calls `fs.open(path, "r")` and reads the `fd` effectively the same as `fromFd()` would.

`options` may be omitted or `null`. The defaults are `{autoClose: true, lazyEntries: false, decodeStrings: true, validateEntrySizes: true}`.
`options` may be omitted or `null`. The defaults are `{autoClose: true, lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}`.

`autoClose` is effectively equivalent to:

Expand Down Expand Up @@ -95,6 +95,17 @@ This check happens as early as possible, which is either before emitting each `"
or during the `readStream` piping after calling `openReadStream()`.
See `openReadStream()` for more information on defending against zip bomb attacks.

When `strictFileNames` is `false` (the default) and `decodeStrings` is `true`,
all backslash (`\`) characters in each `entry.fileName` are replaced with forward slashes (`/`).
The spec forbids file names with backslashes,
but Microsoft's `System.IO.Compression.ZipFile` class in .NET versions 4.5.0 until 4.6.1
creates non-conformant zipfiles with backslashes in file names.
`strictFileNames` is `false` by default so that clients can read these
non-conformant zipfiles without knowing about this Microsoft-specific bug.
When `strictFileNames` is `true` and `decodeStrings` is `true`,
entries with backslashes in their file names will result in an error. See `validateFileName()`.
When `decodeStrings` is `false`, `strictFileNames` has no effect.

The `callback` is given the arguments `(err, zipfile)`.
An `err` is provided if the End of Central Directory Record cannot be found, or if its metadata appears malformed.
This kind of error usually indicates that this is not a zip file.
Expand All @@ -106,7 +117,7 @@ Reads from the fd, which is presumed to be an open .zip file.
Note that random access is required by the zip file specification,
so the fd cannot be an open socket or any other fd that does not support random access.

`options` may be omitted or `null`. The defaults are `{autoClose: false, lazyEntries: false, decodeStrings: true, validateEntrySizes: true}`.
`options` may be omitted or `null`. The defaults are `{autoClose: false, lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}`.

See `open()` for the meaning of the options and callback.

Expand All @@ -119,7 +130,7 @@ If a `ZipFile` is acquired from this method,
it will never emit the `close` event,
and calling `close()` is not necessary.

`options` may be omitted or `null`. The defaults are `{lazyEntries: false, decodeStrings: true, validateEntrySizes: true}`.
`options` may be omitted or `null`. The defaults are `{lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}`.

See `open()` for the meaning of the options and callback.
The `autoClose` option is ignored for this method.
Expand All @@ -133,7 +144,7 @@ The `reader` parameter must be of a type that is a subclass of
[RandomAccessReader](#class-randomaccessreader) that implements the required methods.
The `totalSize` is a Number and indicates the total file size of the zip file.

`options` may be omitted or `null`. The defaults are `{autoClose: true, lazyEntries: false, decodeStrings: true, validateEntrySizes: true}`.
`options` may be omitted or `null`. The defaults are `{autoClose: true, lazyEntries: false, decodeStrings: true, validateEntrySizes: true, strictFileNames: false}`.

See `open()` for the meaning of the options and callback.

Expand All @@ -156,7 +167,7 @@ if (errorMessage != null) throw new Error(errorMessage);
```

This function is automatically run for each entry, as long as `decodeStrings` is `true`.
See `open()` and `Event: "entry"` for more information.
See `open()`, `strictFileNames`, and `Event: "entry"` for more information.

### Class: ZipFile

Expand Down Expand Up @@ -594,6 +605,8 @@ This library makes no attempt to interpret the Language Encoding Flag.

## Change History

* 2.10.0
* Added support for non-conformant zipfiles created by Microsoft, and added option `strictFileNames` to disable the workaround. [issue #66](https://github.com/thejoshwolfe/yauzl/issues/66), [issue #88](https://github.com/thejoshwolfe/yauzl/issues/88)
* 2.9.2
* Removed `tools/hexdump-zip.js` and `tools/hex2bin.js`. Those tools are now located here: [thejoshwolfe/hexdump-zip](https://github.com/thejoshwolfe/hexdump-zip) and [thejoshwolfe/hex2bin](https://github.com/thejoshwolfe/hex2bin)
* Worked around performance problem with zlib when using `fromBuffer()` and `readStream.destroy()` for large compressed files. [issue #87](https://github.com/thejoshwolfe/yauzl/issues/87)
Expand Down
17 changes: 13 additions & 4 deletions index.js
Expand Up @@ -28,6 +28,7 @@ function open(path, options, callback) {
if (options.lazyEntries == null) options.lazyEntries = false;
if (options.decodeStrings == null) options.decodeStrings = true;
if (options.validateEntrySizes == null) options.validateEntrySizes = true;
if (options.strictFileNames == null) options.strictFileNames = false;
if (callback == null) callback = defaultCallback;
fs.open(path, "r", function(err, fd) {
if (err) return callback(err);
Expand All @@ -48,6 +49,7 @@ function fromFd(fd, options, callback) {
if (options.lazyEntries == null) options.lazyEntries = false;
if (options.decodeStrings == null) options.decodeStrings = true;
if (options.validateEntrySizes == null) options.validateEntrySizes = true;
if (options.strictFileNames == null) options.strictFileNames = false;
if (callback == null) callback = defaultCallback;
fs.fstat(fd, function(err, stats) {
if (err) return callback(err);
Expand All @@ -66,6 +68,7 @@ function fromBuffer(buffer, options, callback) {
if (options.lazyEntries == null) options.lazyEntries = false;
if (options.decodeStrings == null) options.decodeStrings = true;
if (options.validateEntrySizes == null) options.validateEntrySizes = true;
if (options.strictFileNames == null) options.strictFileNames = false;
// limit the max chunk size. see https://github.com/thejoshwolfe/yauzl/issues/87
var reader = fd_slicer.createFromBuffer(buffer, {maxChunkSize: 0x10000});
fromRandomAccessReader(reader, buffer.length, options, callback);
Expand All @@ -82,6 +85,7 @@ function fromRandomAccessReader(reader, totalSize, options, callback) {
if (options.decodeStrings == null) options.decodeStrings = true;
var decodeStrings = !!options.decodeStrings;
if (options.validateEntrySizes == null) options.validateEntrySizes = true;
if (options.strictFileNames == null) options.strictFileNames = false;
if (callback == null) callback = defaultCallback;
if (typeof totalSize !== "number") throw new Error("expected totalSize parameter to be a number");
if (totalSize > Number.MAX_SAFE_INTEGER) {
Expand Down Expand Up @@ -134,7 +138,7 @@ function fromRandomAccessReader(reader, totalSize, options, callback) {
: eocdrBuffer.slice(22);

if (!(entryCount === 0xffff || centralDirectoryOffset === 0xffffffff)) {
return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes));
return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes, options.strictFileNames));
}

// ZIP64 format
Expand Down Expand Up @@ -175,7 +179,7 @@ function fromRandomAccessReader(reader, totalSize, options, callback) {
// 48 - offset of start of central directory with respect to the starting disk number 8 bytes
centralDirectoryOffset = readUInt64LE(zip64EocdrBuffer, 48);
// 56 - zip64 extensible data sector (variable size)
return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes));
return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes, options.strictFileNames));
});
});
return;
Expand All @@ -185,7 +189,7 @@ function fromRandomAccessReader(reader, totalSize, options, callback) {
}

util.inherits(ZipFile, EventEmitter);
function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes) {
function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes, strictFileNames) {
var self = this;
EventEmitter.call(self);
self.reader = reader;
Expand All @@ -206,6 +210,7 @@ function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment,
self.lazyEntries = !!lazyEntries;
self.decodeStrings = !!decodeStrings;
self.validateEntrySizes = !!validateEntrySizes;
self.strictFileNames = !!strictFileNames;
self.isOpen = true;
self.emittedError = false;

Expand Down Expand Up @@ -413,7 +418,11 @@ ZipFile.prototype._readEntry = function() {
}

if (self.decodeStrings) {
var errorMessage = validateFileName(entry.fileName);
if (!self.strictFileNames) {
// allow backslash
entry.fileName = entry.fileName.replace(/\\/g, "/");
}
var errorMessage = validateFileName(entry.fileName, self.validateFileNameOptions);
if (errorMessage != null) return emitErrorAndAutoClose(self, new Error(errorMessage));
}
self.emit("entry", entry);
Expand Down
Binary file added test/success/sloppy-filenames.zip
Binary file not shown.
Empty file.
1 change: 1 addition & 0 deletions test/success/sloppy-filenames/a/txt
@@ -0,0 +1 @@
asdf
1 change: 1 addition & 0 deletions test/success/sloppy-filenames/b.txt
@@ -0,0 +1 @@
bsdf
26 changes: 22 additions & 4 deletions test/test.js
Expand Up @@ -56,8 +56,17 @@ listZipFiles([path.join(__dirname, "success"), path.join(__dirname, "wrong-entry
var key = addUnicodeSupport(name);
var realPath = path.join(expectedPathPrefix, name);
if (fs.statSync(realPath).isFile()) {
if (path.basename(name) !== ".git_please_make_this_directory") {
expectedArchiveContents[key] = fs.readFileSync(realPath);
switch (path.basename(name)) {
case ".git_please_make_this_directory":
// ignore
break;
case ".dont_expect_an_empty_dir_entry_for_this_dir":
delete expectedArchiveContents[path.dirname(name)];
break;
default:
// normal file
expectedArchiveContents[key] = fs.readFileSync(realPath);
break;
}
} else {
if (name !== ".") expectedArchiveContents[key] = DIRECTORY;
Expand Down Expand Up @@ -156,7 +165,15 @@ listZipFiles([path.join(__dirname, "failure")]).forEach(function(zipfilePath) {
var emittedError = false;
pend.go(function(cb) {
var operationsInProgress = 0;
yauzl.open(zipfilePath, function(err, zipfile) {
if (/invalid characters in fileName/.test(zipfilePath)) {
// this error can only happen when you specify an option
yauzl.open(zipfilePath, {strictFileNames: true}, onZipFile);
} else {
yauzl.open(zipfilePath, onZipFile);
}
return;

function onZipFile(err, zipfile) {
if (err) return checkErrorMessage(err);
zipfile.on("error", function(err) {
noEventsAllowedAfterError();
Expand Down Expand Up @@ -192,7 +209,7 @@ listZipFiles([path.join(__dirname, "failure")]).forEach(function(zipfilePath) {
throw new Error(zipfilePath + ": expected failure");
}
}
});
}
function checkErrorMessage(err) {
var actualMessage = err.message.replace(/[^0-9A-Za-z-]+/g, " ");
if (actualMessage !== expectedErrorMessage) {
Expand Down Expand Up @@ -362,6 +379,7 @@ function addUnicodeSupport(name) {
function manuallyDecodeFileName(fileName) {
// file names in this test suite are always utf8 compatible.
fileName = fileName.toString("utf8");
fileName = fileName.replace("\\", "/");
if (fileName === "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f") {
// we're not doing the unicode path extra field decoding outside of yauzl.
// just hardcode this answer.
Expand Down

0 comments on commit 6a9e652

Please sign in to comment.