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

feat: adding support for filename encodings #116

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ jobs:
- name: Lint
run: yarn lint
- name: Test
run: yarn coverage
run: yarn ci:coverage
10 changes: 8 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ var extract = require('./')
var args = process.argv.slice(2)
var source = args[0]
var dest = args[1] || process.cwd()
var encodingReg = /^--encoding=*/
var encodingIndex = args.findIndex(entry => encodingReg.test(entry))
var encoding
if (encodingIndex !== -1) {
encoding = args.splice(encodingIndex, 1)[0].replace(encodingReg, '')
}
if (!source) {
console.error('Usage: extract-zip foo.zip <targetDirectory>')
console.error('Usage: extract-zip foo.zip <targetDirectory> [--encoding=<encoding>]')
process.exit(1)
}

extract(source, { dir: dest })
extract(source, { dir: dest, encoding })
.catch(function (err) {
console.error('error!', err)
process.exit(1)
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Entry, ZipFile } from 'yauzl';

declare namespace extract {
interface Options {
encoding?: string;
dir: string;
defaultDirMode?: number;
defaultFileMode?: number;
Expand Down
8 changes: 6 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const debug = require('debug')('extract-zip')
const { createWriteStream, promises: fs } = require('fs')
const getStream = require('get-stream')
const path = require('path')
const { promisify } = require('util')
const { promisify, TextDecoder } = require('util')
const stream = require('stream')
const yauzl = require('yauzl')

Expand All @@ -14,12 +14,13 @@ class Extractor {
constructor (zipPath, opts) {
this.zipPath = zipPath
this.opts = opts
this.decoder = new TextDecoder(opts.encoding)
}

async extract () {
debug('opening', this.zipPath, 'with opts', this.opts)

this.zipfile = await openZip(this.zipPath, { lazyEntries: true })
this.zipfile = await openZip(this.zipPath, { lazyEntries: true, decodeStrings: false })
this.canceled = false

return new Promise((resolve, reject) => {
Expand All @@ -37,6 +38,9 @@ class Extractor {
})

this.zipfile.on('entry', async entry => {
entry.fileName = this.decoder.decode(entry.fileName)
entry.fileNameLength = entry.fileName.length

/* istanbul ignore if */
if (this.canceled) {
debug('skipping entry', entry.fileName, { cancelled: this.canceled })
Expand Down
1 change: 1 addition & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ let options: extract.Options = {
};
options = {
dir,
encoding: 'shift-jis',
defaultDirMode: 0o700,
defaultFileMode,
onEntry: (entry: Entry, zipfile: ZipFile): void => {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"lint:js": "eslint .",
"lint:ts": "eslint --config .eslintrc.typescript.js --ext .ts .",
"test": "yarn lint && ava",
"ci:coverage": "full-icu node_modules/nyc/bin/nyc.js ava",
"tsd": "tsd"
},
"files": [
Expand All @@ -29,7 +30,7 @@
"extract"
],
"engines": {
"node": ">= 10.17.0"
"node": ">= 10.18.0"
},
"dependencies": {
"debug": "^4.1.1",
Expand All @@ -40,6 +41,7 @@
"@types/yauzl": "^2.9.1"
},
"devDependencies": {
"@leichtgewicht/full-icu": "^1.3.3",
"@typescript-eslint/eslint-plugin": "^3.2.0",
"@typescript-eslint/parser": "^3.2.0",
"ava": "^3.5.1",
Expand Down
5 changes: 4 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,19 @@ async function main () {
### Options

- `dir` (required) - the path to the directory where the extracted files are written
- `encoding` - string - [encoding][] to be used for file names, defaults to `utf-8`
- `defaultDirMode` - integer - Directory Mode (permissions), defaults to `0o755`
- `defaultFileMode` - integer - File Mode (permissions), defaults to `0o644`
- `onEntry` - function - if present, will be called with `(entry, zipfile)`, entry is every entry from the zip file forwarded from the `entry` event from yauzl. `zipfile` is the `yauzl` instance

Default modes are only used if no permissions are set in the zip file.

[encoding]: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/encoding

## CLI Usage

```
extract-zip foo.zip <targetDirectory>
extract-zip foo.zip <targetDirectory> [--encoding=<encoding>]
```

If not specified, `targetDirectory` will default to `process.cwd()`.
9 changes: 9 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const subdirZip = path.join(__dirname, 'file-in-subdir-without-subdir-entry.zip'
const symlinkDestZip = path.join(__dirname, 'symlink-dest.zip')
const symlinkZip = path.join(__dirname, 'symlink.zip')
const brokenZip = path.join(__dirname, 'broken.zip')
const mojibakeZip = path.join(__dirname, 'mojibake.zip')

const relativeTarget = './cats'

Expand Down Expand Up @@ -161,3 +162,11 @@ test('extract broken zip', async t => {
message: 'invalid central directory file header signature: 0x2014b00'
})
})

test('extract mojibake', async t => {
const dirPath = await mkdtemp(t, 'mojibake-zip')
await extract(mojibakeZip, { dir: dirPath, encoding: 'windows-949' })
await pathExists(t, path.join(dirPath, '새 텍스트 문서.txt'), 'file created')
await pathExists(t, path.join(dirPath, '새 폴더'), 'folder created')
await pathExists(t, path.join(dirPath, '새 폴더', '한글문서.txt'), 'subfile created')
})
Binary file added test/mojibake.zip
Binary file not shown.