Skip to content

Commit 433f1f1

Browse files
feat: adding support for filename encodings
1 parent d641571 commit 433f1f1

9 files changed

Lines changed: 33 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ jobs:
3838
- name: Lint
3939
run: yarn lint
4040
- name: Test
41-
run: yarn coverage
41+
run: yarn ci:coverage

cli.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ var extract = require('./')
77
var args = process.argv.slice(2)
88
var source = args[0]
99
var dest = args[1] || process.cwd()
10+
var encodingReg = /^--encoding=*/
11+
var encodingIndex = args.findIndex(entry => encodingReg.test(entry))
12+
var encoding
13+
if (encodingIndex !== -1) {
14+
encoding = args.splice(encodingIndex, 1)[0].replace(encodingReg, '')
15+
}
1016
if (!source) {
11-
console.error('Usage: extract-zip foo.zip <targetDirectory>')
17+
console.error('Usage: extract-zip foo.zip <targetDirectory> [--encoding=<encoding>]')
1218
process.exit(1)
1319
}
1420

15-
extract(source, { dir: dest })
21+
extract(source, { dir: dest, encoding })
1622
.catch(function (err) {
1723
console.error('error!', err)
1824
process.exit(1)

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Entry, ZipFile } from 'yauzl';
66

77
declare namespace extract {
88
interface Options {
9+
encoding?: string;
910
dir: string;
1011
defaultDirMode?: number;
1112
defaultFileMode?: number;

index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const debug = require('debug')('extract-zip')
33
const { createWriteStream, promises: fs } = require('fs')
44
const getStream = require('get-stream')
55
const path = require('path')
6-
const { promisify } = require('util')
6+
const { promisify, TextDecoder } = require('util')
77
const stream = require('stream')
88
const yauzl = require('yauzl')
99

@@ -14,12 +14,13 @@ class Extractor {
1414
constructor (zipPath, opts) {
1515
this.zipPath = zipPath
1616
this.opts = opts
17+
this.decoder = new TextDecoder(opts.encoding)
1718
}
1819

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

22-
this.zipfile = await openZip(this.zipPath, { lazyEntries: true })
23+
this.zipfile = await openZip(this.zipPath, { lazyEntries: true, decodeStrings: false })
2324
this.canceled = false
2425

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

3940
this.zipfile.on('entry', async entry => {
41+
entry.fileName = this.decoder.decode(entry.fileName)
42+
entry.fileNameLength = entry.fileName.length
43+
4044
/* istanbul ignore if */
4145
if (this.canceled) {
4246
debug('skipping entry', entry.fileName, { cancelled: this.canceled })

index.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ let options: extract.Options = {
1010
};
1111
options = {
1212
dir,
13+
encoding: 'shift-jis',
1314
defaultDirMode: 0o700,
1415
defaultFileMode,
1516
onEntry: (entry: Entry, zipfile: ZipFile): void => {

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"lint:js": "eslint .",
1515
"lint:ts": "eslint --config .eslintrc.typescript.js --ext .ts .",
1616
"test": "yarn lint && ava",
17+
"ci:coverage": "full-icu node_modules/nyc/bin/nyc.js ava",
1718
"tsd": "tsd"
1819
},
1920
"files": [
@@ -29,7 +30,7 @@
2930
"extract"
3031
],
3132
"engines": {
32-
"node": ">= 10.17.0"
33+
"node": ">= 10.18.0"
3334
},
3435
"dependencies": {
3536
"debug": "^4.1.1",
@@ -40,6 +41,7 @@
4041
"@types/yauzl": "^2.9.1"
4142
},
4243
"devDependencies": {
44+
"@leichtgewicht/full-icu": "^1.3.3",
4345
"@typescript-eslint/eslint-plugin": "^3.2.0",
4446
"@typescript-eslint/parser": "^3.2.0",
4547
"ava": "^3.5.1",

readme.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,19 @@ async function main () {
4242
### Options
4343

4444
- `dir` (required) - the path to the directory where the extracted files are written
45+
- `encoding` - string - [encoding][] to be used for file names, defaults to `utf-8`
4546
- `defaultDirMode` - integer - Directory Mode (permissions), defaults to `0o755`
4647
- `defaultFileMode` - integer - File Mode (permissions), defaults to `0o644`
4748
- `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
4849

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

52+
[encoding]: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/encoding
53+
5154
## CLI Usage
5255

5356
```
54-
extract-zip foo.zip <targetDirectory>
57+
extract-zip foo.zip <targetDirectory> [--encoding=<encoding>]
5558
```
5659

5760
If not specified, `targetDirectory` will default to `process.cwd()`.

test/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const subdirZip = path.join(__dirname, 'file-in-subdir-without-subdir-entry.zip'
1111
const symlinkDestZip = path.join(__dirname, 'symlink-dest.zip')
1212
const symlinkZip = path.join(__dirname, 'symlink.zip')
1313
const brokenZip = path.join(__dirname, 'broken.zip')
14+
const mojibakeZip = path.join(__dirname, 'mojibake.zip')
1415

1516
const relativeTarget = './cats'
1617

@@ -161,3 +162,11 @@ test('extract broken zip', async t => {
161162
message: 'invalid central directory file header signature: 0x2014b00'
162163
})
163164
})
165+
166+
test('extract mojibake', async t => {
167+
const dirPath = await mkdtemp(t, 'mojibake-zip')
168+
await extract(mojibakeZip, { dir: dirPath, encoding: 'windows-949' })
169+
await pathExists(t, path.join(dirPath, '새 텍스트 문서.txt'), 'file created')
170+
await pathExists(t, path.join(dirPath, '새 폴더'), 'folder created')
171+
await pathExists(t, path.join(dirPath, '새 폴더', '한글문서.txt'), 'subfile created')
172+
})

test/mojibake.zip

412 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)