├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── lzmajs ├── binding.gyp ├── deps ├── .gitignore ├── .npmignore ├── xz-5.2.3-windows.7z ├── xz-5.2.3-windows.7z.sig ├── xz-5.2.3.tar.bz2 └── xz-5.2.3.tar.bz2.sig ├── index.js ├── liblzma-build.sh ├── liblzma-config.sh ├── package.json ├── src ├── filter-array.cpp ├── index-parser.cpp ├── index-parser.h ├── liblzma-functions.cpp ├── liblzma-node.hpp ├── lzma-stream.cpp ├── module.cpp ├── mt-options.cpp └── util.cpp └── test ├── compat.js ├── functions.js ├── hamlet.txt.2stream.xz ├── hamlet.txt.lzma ├── hamlet.txt.xz ├── helpers.js ├── internals.js ├── invalid.xz ├── parse-index.js ├── random ├── random-large ├── readme-examples.js ├── regression-1.js ├── regression-53.js ├── regression-7.js └── stream.js /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v[0-9]+.[0-9]+.[0-9]+* 9 | pull_request: 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [windows-latest, macOS-latest, ubuntu-latest] 17 | node-version: [12.x, 14.x, 16.x] 18 | 19 | steps: 20 | - name: Fix git checkout line endings 21 | run: git config --global core.autocrlf input 22 | - uses: actions/checkout@v2.3.4 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v2.3.0 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - name: Windows Setup 28 | if: matrix.os == 'windows-latest' 29 | run: npm run prepare-win32 30 | - name: Install 31 | run: npm install 32 | - name: Lint 33 | run: npm run jshint 34 | - name: Test 35 | run: npm run test 36 | 37 | prebuild: 38 | needs: test 39 | runs-on: ${{ matrix.os }}-latest 40 | strategy: 41 | matrix: 42 | os: [windows, macOS, ubuntu] 43 | arch: [x64, arm64] 44 | exclude: 45 | - os: windows 46 | arch: arm64 47 | include: 48 | - os: windows 49 | arch: ia32 50 | 51 | steps: 52 | - name: Fix git checkout line endings 53 | run: git config --global core.autocrlf input 54 | - uses: actions/checkout@v2.3.4 55 | - name: Setup Node.js 56 | uses: actions/setup-node@v2.3.0 57 | with: 58 | node-version: 12.x 59 | - name: Windows Setup 60 | if: matrix.os == 'windows' 61 | run: npm run prepare-win32 62 | - name: Install 63 | run: npm install 64 | - name: Prebuild binaries 65 | run: npm run prebuild --v8_enable_pointer_compression=false --v8_enable_31bit_smis_on_64bit_arch=false $([[ $OSTYPE != darwin* ]] && echo --llvm_version=0.0 || true) 66 | shell: bash 67 | env: 68 | PREBUILD_ARCH: ${{ matrix.arch }} 69 | - name: Upload binaries as an artifact 70 | uses: actions/upload-artifact@v2.3.1 71 | with: 72 | name: prebuild-${{ matrix.os }}-${{ matrix.arch }} 73 | path: prebuilds 74 | 75 | publish: 76 | needs: prebuild 77 | runs-on: ubuntu-latest 78 | if: github.ref_type == 'tag' 79 | 80 | steps: 81 | - name: Fix git checkout line endings 82 | run: git config --global core.autocrlf input 83 | - uses: actions/checkout@v2.3.4 84 | - name: Setup Node.js 85 | uses: actions/setup-node@v2.3.0 86 | with: 87 | node-version: 12.x 88 | registry-url: 'https://registry.npmjs.org' 89 | - name: Install 90 | run: npm install 91 | - name: Download prebuild artifacts 92 | uses: actions/download-artifact@v2.1.0 93 | with: 94 | path: artifacts 95 | - name: Merge artifacts to prebuilds directory 96 | run: | 97 | mkdir prebuilds 98 | mv artifacts/*/* prebuilds/ 99 | rm -r artifacts 100 | ls prebuilds 101 | - name: Publish to npm 102 | run: npm publish 103 | env: 104 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | binding*/ 3 | node_modules/ 4 | coverage/ 5 | npm-debug.log 6 | core 7 | README.md.xz 8 | .nyc_output 9 | /.idea/ 10 | prebuilds/ 11 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | output.log 3 | templates-compiled.js 4 | errors.log 5 | errors-*.log 6 | errors.*.log 7 | out 8 | doc 9 | buildstamp.js 10 | core 11 | coverage 12 | npm-debug.log 13 | output.*.log 14 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "expr": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "newcap": true, 6 | "noarg": true, 7 | "sub": true, 8 | "undef": true, 9 | "unused": "vars", 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true, 13 | "esnext": true, 14 | "globals": { 15 | "require": true, 16 | "exports": true, 17 | "console": true, 18 | "module": true, 19 | "gc": true, 20 | 21 | "it": true, 22 | "describe": true, 23 | "before": true, 24 | "beforeEach": true, 25 | "after": true, 26 | "afterEach": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/ 2 | test/ 3 | coverage/ 4 | binding*/ 5 | deps/xz-5.2.3-windows.7z 6 | npm-debug.log 7 | core 8 | README.md.xz 9 | .jshint* 10 | .travis* 11 | appveyor.yml 12 | .nyc-output 13 | .git 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for lzma-native 2 | 3 | ## 8.0.6, Jan 18 2022 4 | 5 | * [[`5bdb4b047c`](https://github.com/addaleax/lzma-native/commit/5bdb4b047c)] - **ci**: use npm instead of yarn (Anna Henningsen) 6 | * [[`204dfca905`](https://github.com/addaleax/lzma-native/commit/204dfca905)] - *Revert* "*Revert* "**ci**: set llvm_version to 0.0 on non-macOS hosts for prebuilds"" (Anna Henningsen) 7 | * [[`8c324b2672`](https://github.com/addaleax/lzma-native/commit/8c324b2672)] - *Revert* "**ci**: set llvm_version to 0.0 on non-macOS hosts for prebuilds" (Anna Henningsen) 8 | * [[`4639d45d73`](https://github.com/addaleax/lzma-native/commit/4639d45d73)] - **ci**: set llvm_version to 0.0 on non-macOS hosts for prebuilds (Anna Henningsen) 9 | * [[`d5164b3ded`](https://github.com/addaleax/lzma-native/commit/d5164b3ded)] - **test**: bump mocha timeout (Anna Henningsen) 10 | * [[`0ddd17aed8`](https://github.com/addaleax/lzma-native/commit/0ddd17aed8)] - **build**: fix dyld load error on Apple Silicon (#128) (tylinux) 11 | 12 | ## 8.0.5, Jan 11 2022 13 | 14 | * [[`2ee1daa361`](https://github.com/addaleax/lzma-native/commit/2ee1daa361)] - fix(build): remove bash-isms (Anna Henningsen) 15 | * [[`da5832b0c4`](https://github.com/addaleax/lzma-native/commit/da5832b0c4)] - **ci**: replace Travis CI/AppVeyor with GitHub Actions, prebuild for macOS/arm64 (#126) (Mark Lee) 16 | * [[`8af909b058`](https://github.com/addaleax/lzma-native/commit/8af909b058)] - **build**: fix build on Apple Silicon (#123) (tylinux) 17 | 18 | ## 8.0.1, May 13 2021 19 | 20 | * [[`5b724c30c1`](https://github.com/addaleax/lzma-native/commit/5b724c30c1)] - More prebuild updates (Anna Henningsen) 21 | * [[`43f11c229b`](https://github.com/addaleax/lzma-native/commit/43f11c229b)] - Try switching to prebuildify (Anna Henningsen) 22 | 23 | ## 7.0.1, Mar 11 2021 24 | 25 | * [[`d9b0b90b66`](https://github.com/addaleax/lzma-native/commit/d9b0b90b66)] - Upgrade to @mapbox/node-pre-gyp (Luis Solorzano) 26 | 27 | ## 6.0.1, Apr 23 2020 28 | 29 | * [[`d90d2fc354`](https://github.com/addaleax/lzma-native/commit/d90d2fc354)] - **build**: fix package filename for N-API (Anna Henningsen) 30 | 31 | ## 6.0.0, Jan 1 2020 32 | 33 | * [[`f33038b345`](https://github.com/addaleax/lzma-native/commit/f33038b345)] - **ci**: update platform list (Anna Henningsen) [#72](https://github.com/addaleax/lzma-native/pull/72) 34 | * [[`66499a02b6`](https://github.com/addaleax/lzma-native/commit/66499a02b6)] - **build**: remove cflags.sh build step (Anna Henningsen) [#72](https://github.com/addaleax/lzma-native/pull/72) 35 | * [[`8bea0ff0cf`](https://github.com/addaleax/lzma-native/commit/8bea0ff0cf)] - **src**: convert to N-API (Anna Henningsen) [#72](https://github.com/addaleax/lzma-native/pull/72) 36 | 37 | ## 5.0.1, Dec 19 2019 38 | 39 | * [[`56ff7693a8`](https://github.com/addaleax/lzma-native/commit/56ff7693a8)] - **ci**: revert back to using gcc 4.9 (Anna Henningsen) 40 | 41 | ## 5.0.0, Nov 23 2019 42 | 43 | * [[`607c4f450c`](https://github.com/addaleax/lzma-native/commit/607c4f450c)] - **ci**: remove Node.js 6, 11 (Anna Henningsen) [#87](https://github.com/addaleax/lzma-native/pull/87) 44 | 45 | ## 4.0.6, Nov 22 2019 46 | 47 | * [[`6ebced9b57`](https://github.com/addaleax/lzma-native/commit/6ebced9b57)] - Fix compatibility with Node 13 (Christian Moritz) [#86](https://github.com/addaleax/lzma-native/pull/86) 48 | 49 | ## 4.0.5, May 24 2019 50 | 51 | * [[`dfd9098c85`](https://github.com/addaleax/lzma-native/commit/dfd9098c85)] - Ignore .git when publishing to npm (Juan Cruz Viotti) [#83](https://github.com/addaleax/lzma-native/pull/83) 52 | 53 | ## 4.0.4, May 23 2019 54 | 55 | * [[`0dc31e34de`](https://github.com/addaleax/lzma-native/commit/0dc31e34de)] - Enable compilation for Node 12 (Gergely Imreh) [#81](https://github.com/addaleax/lzma-native/pull/81) 56 | 57 | ## 4.0.3, Nov 14 2018 58 | 59 | * [[`d07d5f5571`](https://github.com/addaleax/lzma-native/commit/d07d5f5571)] - **ci**: update platform list with Node 11 (Anna Henningsen) 60 | * [[`8bbfb0a4d1`](https://github.com/addaleax/lzma-native/commit/8bbfb0a4d1)] - **ci**: fix Node 8 version at 8.10 (Anna Henningsen) 61 | 62 | ## 4.0.2, Oct 26 2018 63 | 64 | * [[`bb6bfe0988`](https://github.com/addaleax/lzma-native/commit/bb6bfe0988)] - **package**: update node-pre-gyp to 0.11.0 (webcarrot) [#68](https://github.com/addaleax/lzma-native/pull/68) 65 | 66 | ## 4.0.1, Jul 26 2018 67 | 68 | * [[`93b50cc2f7`](https://github.com/addaleax/lzma-native/commit/93b50cc2f7)] - **package**: fix rimraf invocation in install script (webcarrot) [#63](https://github.com/addaleax/lzma-native/pull/63) 69 | 70 | ## 4.0.0, Jul 26 2018 71 | 72 | There are no breaking changes to the API provided by this module. 73 | 74 | This drops pre-built binaries and testing for officially unsupported Node.js 75 | versions. Those versions have known security issues and should not be used 76 | anymore. 77 | 78 | This also updates node-pre-gyp to a more recent version. In the past, 79 | this has caused trouble for some users, so this considered 80 | is a semver-major change as well. 81 | 82 | * [[`b625b3e525`](https://github.com/addaleax/lzma-native/commit/b625b3e525)] - **package**: stop bundling dependencies (Anna Henningsen) 83 | * [[`98155a8179`](https://github.com/addaleax/lzma-native/commit/98155a8179)] - **ci**: drop unsupported Node.js versions (4, 5, 7, 9) (Anna Henningsen) 84 | * [[`d59574481f`](https://github.com/addaleax/lzma-native/commit/d59574481f)] - **package**: update bl to 2.0.1 (Anna Henningsen) 85 | * [[`f2c6e84d2c`](https://github.com/addaleax/lzma-native/commit/f2c6e84d2c)] - **package**: update node-pre-gyp to 0.10.3 (simlu) [#61](https://github.com/addaleax/lzma-native/pull/61) 86 | 87 | ## 3.0.8, May 12 2018 88 | 89 | * [[`8c18848609`](https://github.com/addaleax/lzma-native/commit/8c18848609)] - **ci**: add Node.js 10 to matrix (Anna Henningsen) 90 | 91 | ## 3.0.7, Mar 26 2018 92 | 93 | This likely fixed a regression related to node-pre-gyp. 94 | 95 | * [[`430a440276`](https://github.com/addaleax/lzma-native/commit/430a440276)] - **package**: pin node-pre-gyp to 0.6.39 (Anna Henningsen) 96 | 97 | ## 3.0.6, Mar 26 2018 98 | 99 | * [[`484c53577f`](https://github.com/addaleax/lzma-native/commit/484c53577f)] - **package**: update dependencies (Anna Henningsen) 100 | * [[`6513708704`](https://github.com/addaleax/lzma-native/commit/6513708704)] - **lib**: use `Buffer.*` instead of deprecated Buffer constructor (Anna Henningsen) 101 | 102 | ## 3.0.5, Feb 21 2018 103 | 104 | * [[`c03299db13`](https://github.com/addaleax/lzma-native/commit/c03299db13)] - **ci**: remove OS X from coverage (Anna Henningsen) 105 | * [[`5f640416e0`](https://github.com/addaleax/lzma-native/commit/5f640416e0)] - **lib**: fix issue with invalid input (Anna Henningsen) 106 | 107 | ## 3.0.4, Nov 27 2017 108 | 109 | * [[`669ee5098b`](https://github.com/addaleax/lzma-native/commit/669ee5098b)] - **package**: replace unavailable host to node-pre-gyp.addaleax.net (JianyingLi) [#48](https://github.com/addaleax/lzma-native/pull/48) 110 | 111 | ## 3.0.3, Nov 26 2017 112 | 113 | * [[`fcba77ebe0`](https://github.com/addaleax/lzma-native/commit/fcba77ebe0)] - **ci**: include Node 9 support (Anna Henningsen) 114 | 115 | ## 3.0.2, Nov 07 2017 116 | 117 | * [[`82b97dd94f`](https://github.com/addaleax/lzma-native/commit/82b97dd94f)] - **package**: update dependencies (Anna Henningsen) 118 | 119 | ## 3.0.1, Jul 04 2017 120 | 121 | * [[`9e2ee5129f`](https://github.com/addaleax/lzma-native/commit/9e2ee5129f)] - **ci**: fix CI on Windows (Anna Henningsen) 122 | * [[`8d75757031`](https://github.com/addaleax/lzma-native/commit/8d75757031)] - **lib**: fix race condition (Alexander Sagen) [#40](https://github.com/addaleax/lzma-native/pull/40) 123 | 124 | ## 3.0.0, Jun 26 2017 125 | 126 | This is unlikely to break anybody’s code, but removing the build files after install might qualify as semver-major. 127 | 128 | * [[`d5a252e3de`](https://github.com/addaleax/lzma-native/commit/d5a252e3de)] - **build**: rimraf build/ after install (Anna Henningsen) 129 | * [[`fd2165e2ae`](https://github.com/addaleax/lzma-native/commit/fd2165e2ae)] - **ci**: add electron prebuilts again (Anna Henningsen) 130 | * [[`039ac523d0`](https://github.com/addaleax/lzma-native/commit/039ac523d0)] - **lib**: explicit util.promisify() compat (Anna Henningsen) 131 | 132 | ## 2.0.4, Jun 25 2017 133 | 134 | * [[`0cc00000b3`](https://github.com/addaleax/lzma-native/commit/0cc00000b3)] - **ci**: fix macOS prebuild binaries (Anna Henningsen) 135 | 136 | ## 2.0.3, Jun 21 2017 137 | 138 | * [[`621628abac`](https://github.com/addaleax/lzma-native/commit/621628abac)] - **ci**: add Node 8 to CI matrix (Anna Henningsen) 139 | 140 | ## 2.0.2, May 18 2017 141 | 142 | * [[`39bd6a2dc0`](https://github.com/addaleax/lzma-native/commit/39bd6a2dc0)] - **package**: pin nan to 2.5.1 (Anna Henningsen) 143 | 144 | ## 2.0.1, March 24 2017 145 | 146 | * [[`c0491a0a07`](https://github.com/addaleax/lzma-native/commit/c0491a0a07)] - refactored binding.gyp (Refael Ackermann) 147 | * [[`70883635b7`](https://github.com/addaleax/lzma-native/commit/70883635b7)] - **ci**: skip artifact encryption setup for non-tag builds (Anna Henningsen) 148 | 149 | ## 2.0.0, March 19 2017 150 | 151 | Changes since 1.5.2 152 | 153 | Notable changes: 154 | 155 | * Dropped support for Node 0.10 and 0.12, which includes dropping `any-promise` and `util-extend` as dependencies. 156 | * A changed path for the prebuilt binaries, which now includes versioning information. 157 | 158 | * [[`83e0007061`](https://github.com/addaleax/lzma-native/commit/83e0007061)] - Bump version to 1.5.3 159 | * [[`8021673b5d`](https://github.com/addaleax/lzma-native/commit/8021673b5d)] - Silence warnings about deprecated `NewInstance` usage 160 | * [[`061933c4c7`](https://github.com/addaleax/lzma-native/commit/061933c4c7)] - **bin**: drop `commander` dependency 161 | * [[`d752f96be4`](https://github.com/addaleax/lzma-native/commit/d752f96be4)] - **ci**: don’t use -flto for now 162 | * [[`92188bee5e`](https://github.com/addaleax/lzma-native/commit/92188bee5e)] - **ci**: fix AppVeyor allocation failures 163 | * [[`b79fa969d4`](https://github.com/addaleax/lzma-native/commit/b79fa969d4)] - **ci**: fix AppVeyor indexparser failures 164 | * [[`5fcc17e54f`](https://github.com/addaleax/lzma-native/commit/5fcc17e54f)] - **ci**: fix Travis gcc CI failures 165 | * [[`3f5d2609bd`](https://github.com/addaleax/lzma-native/commit/3f5d2609bd)] - **ci**: drop Node v0.10/v0.12 support 166 | * [[`48e48ea25a`](https://github.com/addaleax/lzma-native/commit/48e48ea25a)] - **ci**: ci file housekeeping 167 | * [[`c2d06b5e09`](https://github.com/addaleax/lzma-native/commit/c2d06b5e09)] - **ci**: work around node-gyp build failures 168 | * [[`f94287f711`](https://github.com/addaleax/lzma-native/commit/f94287f711)] - **ci,test**: drop explicit nw.js testing 169 | * [[`c61355984f`](https://github.com/addaleax/lzma-native/commit/c61355984f)] - **deps**: update xz to 5.2.3 170 | * [[`b07f501e26`](https://github.com/addaleax/lzma-native/commit/b07f501e26)] - **doc**: leave blank lines around headings in README 171 | * [[`dea30f3f20`](https://github.com/addaleax/lzma-native/commit/dea30f3f20)] - **lib**: drop util-extend dependency 172 | * [[`0988b8d360`](https://github.com/addaleax/lzma-native/commit/0988b8d360)] - **lib**: refactor js-facing Stream into class 173 | * [[`18bbdfc220`](https://github.com/addaleax/lzma-native/commit/18bbdfc220)] - **lib**: always use ES6 promises 174 | * [[`f5030e027e`](https://github.com/addaleax/lzma-native/commit/f5030e027e)] - **lib**: fix unhandled Promise rejections 175 | * [[`6e887ca52c`](https://github.com/addaleax/lzma-native/commit/6e887ca52c)] - **meta**: package.json housekeeping 176 | * [[`e884b2e7c1`](https://github.com/addaleax/lzma-native/commit/e884b2e7c1)] - **prebuild**: add versioning to the binding file path 177 | * [[`e8660b3728`](https://github.com/addaleax/lzma-native/commit/e8660b3728)] - **src**: use Nan::MakeCallback() for calling into JS 178 | * [[`bd7ee7ce3f`](https://github.com/addaleax/lzma-native/commit/bd7ee7ce3f)] - **test**: use `fs.unlinkSync` for synchronous unlinking 179 | 180 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Anna Henningsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lzma-native 2 | =========== 3 | 4 | [![NPM Version](https://img.shields.io/npm/v/lzma-native.svg?style=flat)](https://npmjs.org/package/lzma-native) 5 | [![NPM Downloads](https://img.shields.io/npm/dm/lzma-native.svg?style=flat)](https://npmjs.org/package/lzma-native) 6 | [![Build Status](https://travis-ci.org/addaleax/lzma-native.svg?style=flat&branch=master)](https://travis-ci.org/addaleax/lzma-native?branch=master) 7 | [![Windows](https://img.shields.io/appveyor/ci/addaleax/lzma-native/master.svg?label=windows)](https://ci.appveyor.com/project/addaleax/lzma-native) 8 | [![Coverage Status](https://coveralls.io/repos/addaleax/lzma-native/badge.svg?branch=master)](https://coveralls.io/r/addaleax/lzma-native?branch=master) 9 | [![Dependency Status](https://david-dm.org/addaleax/lzma-native.svg?style=flat)](https://david-dm.org/addaleax/lzma-native) 10 | [![devDependency Status](https://david-dm.org/addaleax/lzma-native/dev-status.svg?style=flat)](https://david-dm.org/addaleax/lzma-native#info=devDependencies) 11 | 12 | Node.js interface to the native liblzma compression library (.xz file format, among others) 13 | 14 | This package provides interfaces for compression and decompression 15 | of `.xz` (and legacy `.lzma`) files, both stream-based and string-based. 16 | 17 | 18 | 19 | ## Example usage 20 | 21 | 22 | 23 | ### Installation 24 | 25 | Simply install `lzma-native` via npm: 26 | ```bash 27 | $ npm install --save lzma-native 28 | ``` 29 | 30 | *Note*: As of version 1.0.0, this module provides pre-built binaries for multiple Node.js 31 | versions and all major OS using [node-pre-gyp](https://github.com/mapbox/node-pre-gyp), 32 | so for 99 % of users no compiler toolchain is necessary. 33 | Please [create an issue here](https://github.com/addaleax/lzma-native/issues/new) 34 | if you have any trouble installing this module. 35 | 36 | *Note*: `lzma-native@2.x` requires a Node version >= 4. If you want to support 37 | Node `0.10` or `0.12`, you can feel free to use `lzma-native@1.x`. 38 | 39 | 40 | 41 | ### For streams 42 | 43 | If you don’t have any fancy requirements, using this library is quite simple: 44 | 45 | 49 | 50 | 51 | 52 | ```js 53 | var lzma = require('lzma-native'); 54 | 55 | var compressor = lzma.createCompressor(); 56 | var input = fs.createReadStream('README.md'); 57 | var output = fs.createWriteStream('README.md.xz'); 58 | 59 | input.pipe(compressor).pipe(output); 60 | ``` 61 | 62 | For decompression, you can simply use `lzma.createDecompressor()`. 63 | 64 | Both functions return a stream where you can pipe your 65 | input in and read your (de)compressed output from. 66 | 67 | 68 | 69 | ### For simple strings/Buffers 70 | 71 | If you want your input/output to be Buffers (strings will be accepted as input), 72 | this even gets a little simpler: 73 | 74 | 75 | 76 | ```js 77 | lzma.compress('Banana', function(result) { 78 | console.log(result); // 79 | }); 80 | ``` 81 | 82 | Again, replace `lzma.compress` with `lzma.decompress` and you’ll get the inverse transformation. 83 | 84 | `lzma.compress()` and `lzma.decompress()` 85 | will return promises and you don’t need to provide any kind of callback 86 | ([Example code](#api-q-compress-examle)). 87 | 88 | 89 | 90 | ## API 91 | 92 | 93 | 94 | ### Compatibility implementations 95 | 96 | Apart from the API described here, `lzma-native` implements the APIs of the following 97 | other LZMA libraries so you can use it nearly as a drop-in replacement: 98 | 99 | * [node-xz][node-xz] via `lzma.Compressor` and `lzma.Decompressor` 100 | * [LZMA-JS][LZMA-JS] via `lzma.LZMA().compress` and `lzma.LZMA().decompress`, 101 | though without actual support for progress functions and returning `Buffer` objects 102 | instead of integer arrays. (This produces output in the `.lzma` file format, *not* the `.xz` format!) 103 | 104 | 105 | 106 | ### Multi-threaded encoding 107 | 108 | Since version `1.5.0`, lzma-native supports liblzma’s built-in multi-threading 109 | encoding capabilities. To make use of them, set the `threads` option to 110 | an integer value: `lzma.createCompressor({ threads: n });`. You can use 111 | value of `0` to use the number of processor cores. This option is only 112 | available for the `easyEncoder` (the default) and `streamEncoder` encoders. 113 | 114 | Note that, by default, encoding will take place in Node’s libuv thread pool 115 | regardless of this option, and setting it when multiple encoders are running 116 | is likely to affect performance negatively. 117 | 118 | 119 | 120 | ### Reference 121 | 122 | [Encoding strings and Buffer objects](#api-encoding-buffers) 123 | * [`compress()`](#api-compress) – Compress strings and Buffers 124 | * [`decompress()`](#api-decompress) – Decompress strings and Buffers 125 | * [`LZMA().compress()`](#api-LZMA_compress) ([LZMA-JS][LZMA-JS] compatibility) 126 | * [`LZMA().decompress()`](#api-LZMA_decompress) ([LZMA-JS][LZMA-JS] compatibility) 127 | 128 | [Creating streams for encoding](#api-creating-streams) 129 | * [`createCompressor()`](#api-create-compressor) – Compress streams 130 | * [`createDecompressor()`](#api-create-decompressor) – Decompress streams 131 | * [`createStream()`](#api-create-stream) – (De-)Compression with advanced options 132 | * [`Compressor()`](#api-robey_compressor) ([node-xz][node-xz] compatibility) 133 | * [`Decompressor()`](#api-robey_decompressor) ([node-xz][node-xz] compatibility) 134 | 135 | [.xz file metadata](#api-parse-indexes) 136 | * [`isXZ()`](#api-isxz) – Test Buffer for `.xz` file format 137 | * [`parseFileIndex()`](#api-parse-file-index) – Read `.xz` file metadata 138 | * [`parseFileIndexFD()`](#api-parse-file-index-fd) – Read `.xz` metadata from a file descriptor 139 | 140 | [Miscellaneous functions](#api-functions) 141 | * [`crc32()`](#api-crc32) – Calculate CRC32 checksum 142 | * [`checkSize()`](#api-check-size) – Return required size for specific checksum type 143 | * [`easyDecoderMemusage()`](#api-easy-decoder-memusage) – Expected memory usage 144 | * [`easyEncoderMemusage()`](#api-easy-encoder-memusage) – Expected memory usage 145 | * [`rawDecoderMemusage()`](#api-raw-decoder-memusage) – Expected memory usage 146 | * [`rawEncoderMemusage()`](#api-raw-encoder-memusage) – Expected memory usage 147 | * [`versionString()`](#api-version-string) – Native library version string 148 | * [`versionNumber()`](#api-version-number) – Native library numerical version identifier 149 | 150 | 151 | 152 | ### Encoding strings and Buffer objects 153 | 154 | 155 | 156 | 157 | #### `lzma.compress()`, `lzma.decompress()` 158 | 159 | * `lzma.compress(string, [opt, ]on_finish)` 160 | * `lzma.decompress(string, [opt, ]on_finish)` 161 | 162 | Param | Type | Description 163 | ------------ | ---------------- | -------------- 164 | `string` | Buffer / String | Any string or buffer to be (de)compressed (that can be passed to `stream.end(…)`) 165 | [`opt`] | Options / int | Optional. See [options](#api-options) 166 | `on_finish` | Callback | Will be invoked with the resulting Buffer as the first parameter when encoding is finished, and as `on_finish(null, err)` in case of an error. 167 | 168 | These methods will also return a promise that you can use directly. 169 | 170 | Example code: 171 | 172 | 173 | ```js 174 | lzma.compress('Bananas', 6, function(result) { 175 | lzma.decompress(result, function(decompressedResult) { 176 | assert.equal(decompressedResult.toString(), 'Bananas'); 177 | }); 178 | }); 179 | ``` 180 | 181 | 182 | Example code for promises: 183 | 184 | 185 | ```js 186 | lzma.compress('Bananas', 6).then(function(result) { 187 | return lzma.decompress(result); 188 | }).then(function(decompressedResult) { 189 | assert.equal(decompressedResult.toString(), 'Bananas'); 190 | }).catch(function(err) { 191 | // ... 192 | }); 193 | ``` 194 | 195 | 196 | 197 | 198 | #### `lzma.LZMA().compress()`, `lzma.LZMA().decompress()` 199 | 200 | * `lzma.LZMA().compress(string, mode, on_finish[, on_progress])` 201 | * `lzma.LZMA().decompress(string, on_finish[, on_progress])` 202 | 203 | (Compatibility; See [LZMA-JS][LZMA-JS] for the original specs.) 204 | 205 | **Note that the result of compression is in the older LZMA1 format (`.lzma` files).** 206 | This is different from the more universally used LZMA2 format (`.xz` files) and you will 207 | have to take care of possible compatibility issues with systems expecting `.xz` files. 208 | 209 | Param | Type | Description 210 | ------------- | ----------------------- | -------------- 211 | `string` | Buffer / String / Array | Any string, buffer, or array of integers or typed integers (e.g. `Uint8Array`) 212 | `mode` | int | [A number between 0 and 9](#api-options-preset), indicating compression level 213 | `on_finish` | Callback | Will be invoked with the resulting Buffer as the first parameter when encoding is finished, and as `on_finish(null, err)` in case of an error. 214 | `on_progress` | Callback | Indicates progress by passing a number in [0.0, 1.0]. Currently, this package only invokes the callback with 0.0 and 1.0. 215 | 216 | These methods will also return a promise that you can use directly. 217 | 218 | This does not work exactly as described in the original [LZMA-JS][LZMA-JS] specification: 219 | * The results are `Buffer` objects, not integer arrays. This just makes a lot 220 | more sense in a Node.js environment. 221 | * `on_progress` is currently only called with `0.0` and `1.0`. 222 | 223 | Example code: 224 | 225 | 226 | ```js 227 | lzma.LZMA().compress('Bananas', 4, function(result) { 228 | lzma.LZMA().decompress(result, function(decompressedResult) { 229 | assert.equal(decompressedResult.toString(), 'Bananas'); 230 | }); 231 | }); 232 | ``` 233 | 234 | For an example using promises, see [`compress()`](#api-q-compress-examle). 235 | 236 | 237 | 238 | ### Creating streams for encoding 239 | 240 | 241 | 242 | 243 | #### `lzma.createCompressor()`, `lzma.createDecompressor()` 244 | 245 | * `lzma.createCompressor([options])` 246 | * `lzma.createDecompressor([options])` 247 | 248 | Param | Type | Description 249 | ----------- | ---------------- | -------------- 250 | [`options`] | Options / int | Optional. See [options](#api-options) 251 | 252 | Return a [duplex][duplex] stream, i.e. a both readable and writable stream. 253 | Input will be read, (de)compressed and written out. You can use this to pipe 254 | input through this stream, i.e. to mimick the `xz` command line util, you can write: 255 | 256 | 257 | 258 | ```js 259 | var compressor = lzma.createCompressor(); 260 | 261 | process.stdin.pipe(compressor).pipe(process.stdout); 262 | ``` 263 | 264 | The output of compression will be in LZMA2 format (`.xz` files), while decompression 265 | will accept either format via automatic detection. 266 | 267 | 268 | 269 | 270 | #### `lzma.Compressor()`, `lzma.Decompressor()` 271 | 272 | * `lzma.Compressor([preset], [options])` 273 | * `lzma.Decompressor([options])` 274 | 275 | (Compatibility; See [node-xz][node-xz] for the original specs.) 276 | 277 | These methods handle the `.xz` file format. 278 | 279 | Param | Type | Description 280 | ----------- | ---------------- | -------------- 281 | [`preset`] | int | Optional. See [options.preset](#api-options-preset) 282 | [`options`] | Options | Optional. See [options](#api-options) 283 | 284 | Return a [duplex][duplex] stream, i.e. a both readable and writable stream. 285 | Input will be read, (de)compressed and written out. You can use this to pipe 286 | input through this stream, i.e. to mimick the `xz` command line util, you can write: 287 | 288 | 289 | 290 | ```js 291 | var compressor = lzma.Compressor(); 292 | 293 | process.stdin.pipe(compressor).pipe(process.stdout); 294 | ``` 295 | 296 | 297 | 298 | #### `lzma.createStream()` 299 | 300 | * `lzma.createStream(coder, options)` 301 | 302 | Param | Type | Description 303 | ----------- | ---------------- | -------------- 304 | [`coder`] | string | Any of the [supported coder names](#api-coders), e.g. `"easyEncoder"` (default) or `"autoDecoder"`. 305 | [`options`] | Options / int | Optional. See [options](#api-options) 306 | 307 | Return a [duplex][duplex] stream for (de-)compression. You can use this to pipe 308 | input through this stream. 309 | 310 | 311 | The available coders are (the most interesting ones first): 312 | 313 | * `easyEncoder` 314 | Standard LZMA2 ([`.xz` file format](https://en.wikipedia.org/wiki/.xz)) encoder. 315 | Supports [`options.preset`](#api-options-preset) and [`options.check`](#api-options-check) options. 316 | * `autoDecoder` 317 | Standard LZMA1/2 (both `.xz` and `.lzma`) decoder with auto detection of file format. 318 | Supports [`options.memlimit`](#api-options-memlimit) and [`options.flags`](#api-options-flags) options. 319 | * `aloneEncoder` 320 | Encoder which only uses the legacy `.lzma` format. 321 | Supports the whole range of [LZMA options](#api-options-lzma). 322 | 323 | Less likely to be of interest to you, but also available: 324 | 325 | * `aloneDecoder` 326 | Decoder which only uses the legacy `.lzma` format. 327 | Supports the [`options.memlimit`](#api-options-memlimit) option. 328 | * `rawEncoder` 329 | Custom encoder corresponding to `lzma_raw_encoder` (See the native library docs for details). 330 | Supports the [`options.filters`](#api-options-filters) option. 331 | * `rawDecoder` 332 | Custom decoder corresponding to `lzma_raw_decoder` (See the native library docs for details). 333 | Supports the [`options.filters`](#api-options-filters) option. 334 | * `streamEncoder` 335 | Custom encoder corresponding to `lzma_stream_encoder` (See the native library docs for details). 336 | Supports [`options.filters`](#api-options-filters) and [`options.check`](#api-options-check) options. 337 | * `streamDecoder` 338 | Custom decoder corresponding to `lzma_stream_decoder` (See the native library docs for details). 339 | Supports [`options.memlimit`](#api-options-memlimit) and [`options.flags`](#api-options-flags) options. 340 | 341 | 342 | 343 | #### Options 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | Option name | Type | Description 352 | ------------- | ---------- | ------------- 353 | `check` | check | Any of `lzma.CHECK_CRC32`, `lzma.CHECK_CRC64`, `lzma.CHECK_NONE`, `lzma.CHECK_SHA256` 354 | `memlimit` | float | A memory limit for (de-)compression in bytes 355 | `preset` | int | A number from 0 to 9, 0 being the fastest and weakest compression, 9 the slowest and highest compression level. (Please also see the [xz(1) manpage][xz-manpage] for notes – don’t just blindly use 9!) You can also OR this with `lzma.PRESET_EXTREME` (the `-e` option to the `xz` command line utility). 356 | `flags` | int | A bitwise or of `lzma.LZMA_TELL_NO_CHECK`, `lzma.LZMA_TELL_UNSUPPORTED_CHECK`, `lzma.LZMA_TELL_ANY_CHECK`, `lzma.LZMA_CONCATENATED` 357 | `synchronous` | bool | If true, forces synchronous coding (i.e. no usage of threading) 358 | `bufsize` | int | The default size for allocated buffers 359 | `threads` | int | Set to an integer to use liblzma’s multi-threading support. 0 will choose the number of CPU cores. 360 | `blockSize` | int | Maximum uncompressed size of a block in multi-threading mode 361 | `timeout` | int | Timeout for a single encoding operation in multi-threading mode 362 | 363 | 364 | 365 | `options.filters` can, if the coder supports it, be an array of filter objects, each with the following properties: 366 | 367 | * `.id` 368 | Any of `lzma.FILTERS_MAX`, `lzma.FILTER_ARM`, `lzma.FILTER_ARMTHUMB`, `lzma.FILTER_IA64`, 369 | `lzma.FILTER_POWERPC`, `lzma.FILTER_SPARC`, `lzma.FILTER_X86` or 370 | `lzma.FILTER_DELTA`, `lzma.FILTER_LZMA1`, `lzma.FILTER_LZMA2` 371 | 372 | The delta filter supports the additional option `.dist` for a distance between bytes (see the [xz(1) manpage][xz-manpage]). 373 | 374 | 375 | 376 | The LZMA filter supports the additional options `.dict_size`, `.lp`, `.lc`, `pb`, `.mode`, `nice_len`, `.mf`, `.depth` 377 | and `.preset`. See the [xz(1) manpage][xz-manpage] for meaning of these parameters and additional information. 378 | 379 | 380 | 381 | ### Miscellaneous functions 382 | 383 | 384 | 385 | #### `lzma.crc32()` 386 | 387 | * `lzma.crc32(input[, encoding[, previous]])` 388 | 389 | Compute the CRC32 checksum of a Buffer or string. 390 | 391 | Param | Type | Description 392 | ------------ | ---------------- | -------------- 393 | `input` | string / Buffer | Any string or Buffer. 394 | [`encoding`] | string | Optional. If `input` is a string, an encoding to use when converting into binary. 395 | [`previous`] | int | The result of a previous CRC32 calculation so that you can compute the checksum per each chunk 396 | 397 | Example usage: 398 | 399 | 400 | ```js 401 | lzma.crc32('Banana') // => 69690105 402 | ``` 403 | 404 | 405 | 406 | #### `lzma.checkSize()` 407 | 408 | * `lzma.checkSize(check)` 409 | 410 | Return the byte size of a check sum. 411 | 412 | Param | Type | Description 413 | ------------ | ---------------- | -------------- 414 | `check` | check | Any supported check constant. 415 | 416 | Example usage: 417 | 418 | 419 | ```js 420 | lzma.checkSize(lzma.CHECK_SHA256) // => 16 421 | lzma.checkSize(lzma.CHECK_CRC32) // => 4 422 | ``` 423 | 424 | 425 | 426 | #### `lzma.easyDecoderMemusage()` 427 | 428 | * `lzma.easyDecoderMemusage(preset)` 429 | 430 | Returns the approximate memory usage when decoding using easyDecoder for a given preset. 431 | 432 | Param | Type | Description 433 | ------------ | ----------- | -------------- 434 | `preset` | preset | A compression level from 0 to 9 435 | 436 | Example usage: 437 | 438 | 439 | ```js 440 | lzma.easyDecoderMemusage(6) // => 8454192 441 | ``` 442 | 443 | 444 | 445 | #### `lzma.easyEncoderMemusage()` 446 | 447 | * `lzma.easyEncoderMemusage(preset)` 448 | 449 | Returns the approximate memory usage when encoding using easyEncoder for a given preset. 450 | 451 | Param | Type | Description 452 | ------------ | ----------- | -------------- 453 | `preset` | preset | A compression level from 0 to 9 454 | 455 | Example usage: 456 | 457 | 458 | ```js 459 | lzma.easyEncoderMemusage(6) // => 97620499 460 | ``` 461 | 462 | 463 | 464 | #### `lzma.rawDecoderMemusage()` 465 | 466 | * `lzma.rawDecoderMemusage(filters)` 467 | 468 | Returns the approximate memory usage when decoding using rawDecoder for a given filter list. 469 | 470 | Param | Type | Description 471 | ------------ | ----------- | -------------- 472 | `filters` | array | An array of [filters](#api-options-filters) 473 | 474 | 475 | 476 | #### `lzma.rawEncoderMemusage()` 477 | 478 | * `lzma.rawEncoderMemusage(filters)` 479 | 480 | Returns the approximate memory usage when encoding using rawEncoder for a given filter list. 481 | 482 | Param | Type | Description 483 | ------------ | ----------- | -------------- 484 | `filters` | array | An array of [filters](#api-options-filters) 485 | 486 | 487 | 488 | #### `lzma.versionString()` 489 | 490 | * `lzma.versionString()` 491 | 492 | Returns the version of the underlying C library. 493 | 494 | Example usage: 495 | 496 | 497 | ```js 498 | lzma.versionString() // => '5.2.3' 499 | ``` 500 | 501 | 502 | 503 | #### `lzma.versionNumber()` 504 | 505 | * `lzma.versionNumber()` 506 | 507 | Returns the version of the underlying C library. 508 | 509 | Example usage: 510 | 511 | 512 | ```js 513 | lzma.versionNumber() // => 50020012 514 | ``` 515 | 516 | 517 | 518 | ### .xz file metadata 519 | 520 | 521 | 522 | #### `lzma.isXZ()` 523 | 524 | * `lzma.isXZ(input)` 525 | 526 | Tells whether an input buffer is an XZ file (`.xz`, LZMA2 format) using the 527 | file format’s magic number. This is not a complete test, i.e. the data 528 | following the file header may still be invalid in some way. 529 | 530 | Param | Type | Description 531 | ------------ | ---------------- | -------------- 532 | `input` | string / Buffer | Any string or Buffer (integer arrays accepted). 533 | 534 | Example usage: 535 | 536 | 537 | ```js 538 | lzma.isXZ(fs.readFileSync('test/hamlet.txt.xz')); // => true 539 | lzma.isXZ(fs.readFileSync('test/hamlet.txt.lzma')); // => false 540 | lzma.isXZ('Banana'); // => false 541 | ``` 542 | 543 | (The magic number of XZ files is hex `fd 37 7a 58 5a 00` at position 0.) 544 | 545 | 546 | 547 | #### `lzma.parseFileIndex()` 548 | 549 | * `lzma.parseFileIndex(options[, callback])` 550 | 551 | Read `.xz` file metadata. 552 | 553 | `options.fileSize` needs to be an integer indicating the size of the file 554 | being inspected, e.g. obtained by `fs.stat()`. 555 | 556 | `options.read(count, offset, cb)` must be a function that reads `count` bytes 557 | from the underlying file, starting at position `offset`. If that is not 558 | possible, e.g. because the file does not have enough bytes, the file should 559 | be considered corrupt. On success, `cb` should be called with a `Buffer` 560 | containing the read data. `cb` can be invoked as `cb(err, buffer)`, in which 561 | case `err` will be passed along to the original `callback` argument when set. 562 | 563 | `callback` will be called with `err` and `info` as its arguments. 564 | 565 | If no `callback` is provided, `options.read()` must work synchronously and 566 | the file info will be returned from `lzma.parseFileIndex()`. 567 | 568 | Example usage: 569 | 570 | 571 | ```js 572 | fs.readFile('test/hamlet.txt.xz', function(err, content) { 573 | // handle error 574 | 575 | lzma.parseFileIndex({ 576 | fileSize: content.length, 577 | read: function(count, offset, cb) { 578 | cb(content.slice(offset, offset + count)); 579 | } 580 | }, function(err, info) { 581 | // handle error 582 | 583 | // do something with e.g. info.uncompressedSize 584 | }); 585 | }); 586 | ``` 587 | 588 | 589 | 590 | #### `lzma.parseFileIndexFD()` 591 | 592 | * `lzma.parseFileIndexFD(fd, callback)` 593 | 594 | Read `.xz` metadata from a file descriptor. 595 | 596 | This is like [`parseFileIndex()`](#api-parse-file-index), but lets you 597 | pass an file descriptor in `fd`. The file will be inspected using 598 | `fs.stat()` and `fs.read()`. The file descriptor will not be opened or closed 599 | by this call. 600 | 601 | Example usage: 602 | 603 | 604 | ```js 605 | fs.open('test/hamlet.txt.xz', 'r', function(err, fd) { 606 | // handle error 607 | 608 | lzma.parseFileIndexFD(fd, function(err, info) { 609 | // handle error 610 | 611 | // do something with e.g. info.uncompressedSize 612 | 613 | fs.close(fd, function(err) { /* handle error */ }); 614 | }); 615 | }); 616 | ``` 617 | 618 | ## Installation 619 | 620 | This package includes the native C library, so there is no need to install it separately. 621 | 622 | ## Licensing 623 | 624 | The original C library package contains code under various licenses, 625 | with its core (liblzma) being public domain. See its contents for details. 626 | This wrapper is licensed under the MIT License. 627 | 628 | ## Related projects 629 | 630 | Other implementations of the LZMA algorithms for node.js and/or web clients include: 631 | 632 | * [lzma-purejs](https://github.com/cscott/lzma-purejs) 633 | * [LZMA-JS](https://github.com/nmrugg/LZMA-JS) 634 | * [node-xz](https://github.com/robey/node-xz) (native) 635 | * [node-liblzma](https://github.com/oorabona/node-liblzma) (native) 636 | 637 | Note that LZMA has been designed to have much faster decompression than 638 | compression, which is something you may want to take into account when 639 | choosing an compression algorithm for large files. Almost always, LZMA achieves 640 | higher compression ratios than other algorithms, though. 641 | 642 | ## Acknowledgements 643 | 644 | Initial development of this project was financially supported by [Tradity](https://tradity.de/). 645 | 646 | [node-xz]: https://github.com/robey/node-xz 647 | [LZMA-JS]: https://github.com/nmrugg/LZMA-JS 648 | [Q]: https://github.com/kriskowal/q 649 | [duplex]: https://nodejs.org/api/stream.html#stream_class_stream_duplex 650 | [xz-manpage]: https://www.freebsd.org/cgi/man.cgi?query=xz&sektion=1&manpath=FreeBSD+8.3-RELEASE 651 | -------------------------------------------------------------------------------- /bin/lzmajs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var program = require('commander'); 5 | var lzma = require('../'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | 9 | var argv = process.argv.slice(2); 10 | var positionalArgs = []; 11 | 12 | var level = undefined; 13 | var threads = undefined; 14 | var compress = true; 15 | 16 | for (var i = 0; i < argv.length; ++i) { 17 | if (argv[i][0] !== '-') { 18 | positionalArgs.push(argv[i]); 19 | continue; 20 | } 21 | 22 | if (!isNaN(+argv[i][1])) { 23 | level = +argv[i][1]; 24 | continue; 25 | } 26 | 27 | switch (argv[i]) { 28 | case '-d': 29 | case '--decompress': 30 | compress = false; 31 | break; 32 | case '-z': 33 | case '--compress': 34 | compress = true; 35 | break; 36 | case '-t': 37 | case '--threads': 38 | if (!isNaN(+argv[i+1])) 39 | threads = +argv[++i]; 40 | else 41 | threads = 0; 42 | break; 43 | default: 44 | case '-h': 45 | case '--help': 46 | usage(); 47 | return; 48 | } 49 | } 50 | 51 | function usage() { 52 | process.stdout.write('Usage: \n' + 53 | ' ' + path.basename(process.argv[1]) + 54 | ' [-d|-z] [-t num] [-1|...|-9] [infile] [outfile]\n' + 55 | '\n' + 56 | ' -d, --decompress Decompress infile to outfile\n' + 57 | ' -z, --compress Compress infile to outfile\n' + 58 | ' -t n, --threads n Use n threads for compressing\n' + 59 | ' -1, ..., -9 Specifiy compression level\n' + 60 | ' -h, --help Display this text\n' + 61 | '\n' + 62 | ' defaults to stdin and defaults to stdout.\n'); 63 | return; 64 | } 65 | 66 | var input = process.stdin, output = process.stdout; 67 | 68 | if (positionalArgs.length > 0) { 69 | input = fs.createReadStream(positionalArgs.shift()); 70 | } 71 | 72 | if (positionalArgs.length > 0) { 73 | output = fs.createWriteStream(positionalArgs.shift()); 74 | } 75 | 76 | var opts = { 77 | preset: level || lzma.PRESET_DEFAULT, 78 | threads: threads, 79 | }; 80 | 81 | var encoder = lzma.createStream(compress ? 'easyEncoder' : 'autoDecoder', opts); 82 | 83 | input.pipe(encoder).pipe(output); 84 | 85 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "dlldir%": "<(module_root_dir)/build/Release" 4 | }, 5 | "targets": [ 6 | { 7 | "target_name": "lzma_native", 8 | "sources": [ 9 | "src/util.cpp", 10 | "src/liblzma-functions.cpp", 11 | "src/filter-array.cpp", 12 | "src/lzma-stream.cpp", 13 | "src/module.cpp", 14 | "src/mt-options.cpp", 15 | "src/index-parser.cpp" 16 | ], 17 | 'include_dirs': [" nul 2>&1 & copy "<(module_root_dir)/deps/<(arch_lib_path)/liblzma.dll" <(dlldir)/liblzma.dll'] 93 | } 94 | ] 95 | } ], 96 | ] 97 | } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /deps/.gitignore: -------------------------------------------------------------------------------- 1 | bin_*/ 2 | doc/ 3 | include/ 4 | *.dll 5 | *.a 6 | -------------------------------------------------------------------------------- /deps/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/deps/.npmignore -------------------------------------------------------------------------------- /deps/xz-5.2.3-windows.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/deps/xz-5.2.3-windows.7z -------------------------------------------------------------------------------- /deps/xz-5.2.3-windows.7z.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/deps/xz-5.2.3-windows.7z.sig -------------------------------------------------------------------------------- /deps/xz-5.2.3.tar.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/deps/xz-5.2.3.tar.bz2 -------------------------------------------------------------------------------- /deps/xz-5.2.3.tar.bz2.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/deps/xz-5.2.3.tar.bz2.sig -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var stream = require('readable-stream'); 5 | var assert = require('assert'); 6 | var fs = require('fs'); 7 | var util = require('util'); 8 | 9 | var native = require('node-gyp-build')(__dirname); 10 | 11 | Object.assign(exports, native); 12 | 13 | // Please do not update this version except as part of a release commit. 14 | exports.version = '8.0.6'; 15 | 16 | var Stream = exports.Stream; 17 | 18 | Stream.curAsyncStreamsCount = 0; 19 | 20 | Stream.prototype.getStream = function(options) { 21 | options = options || {}; 22 | 23 | return new JSLzmaStream(this, options); 24 | }; 25 | 26 | class JSLzmaStream extends stream.Transform { 27 | constructor(nativeStream, options) { 28 | super(options); 29 | 30 | this.nativeStream = nativeStream; 31 | this.synchronous = (options.synchronous || !native.asyncCodeAvailable) ? true : false; 32 | this.chunkCallbacks = []; 33 | 34 | this.totalIn_ = 0; 35 | this.totalOut_ = 0; 36 | 37 | this._writingLastChunk = false; 38 | this._isFinished = false; 39 | 40 | if (!this.synchronous) { 41 | Stream.curAsyncStreamsCount++; 42 | 43 | var oldCleanup = this.cleanup; 44 | var countedCleanup = false; 45 | this.cleanup = () => { 46 | if (countedCleanup === false) { 47 | Stream.curAsyncStreamsCount--; 48 | countedCleanup = true; 49 | } 50 | oldCleanup.call(this); 51 | }; 52 | } 53 | 54 | // always clean up in case of error 55 | this.once('error-cleanup', this.cleanup); 56 | 57 | this.nativeStream.bufferHandler = (buf, processedChunks, err, totalIn, totalOut) => { 58 | if (totalIn !== null) { 59 | this.totalIn_ = totalIn; 60 | this.totalOut_ = totalOut; 61 | } 62 | 63 | setImmediate(() => { 64 | if (err) { 65 | this.push(null); 66 | this.emit('error-cleanup', err); 67 | this.emit('error', err); 68 | return; 69 | } 70 | 71 | if (totalIn !== null) { 72 | this.emit('progress', { 73 | totalIn: this.totalIn_, 74 | totalOut: this.totalOut_ 75 | }); 76 | } 77 | 78 | if (typeof processedChunks === 'number') { 79 | assert.ok(processedChunks <= this.chunkCallbacks.length); 80 | 81 | var chunkCallbacks = this.chunkCallbacks.splice(0, processedChunks); 82 | 83 | while (chunkCallbacks.length > 0) 84 | chunkCallbacks.shift().call(this); 85 | } else if (buf === null) { 86 | if (this._writingLastChunk) { 87 | this.push(null); 88 | } else { 89 | // There may be additional members in the file. 90 | // Reset and set _isFinished to tell `_flush()` that nothing 91 | // needs to be done. 92 | this._isFinished = true; 93 | 94 | if (this.nativeStream && this.nativeStream._restart) { 95 | this.nativeStream._restart(); 96 | } else { 97 | this.push(null); 98 | } 99 | } 100 | } else { 101 | this.push(buf); 102 | } 103 | }); 104 | }; 105 | 106 | if (typeof options.bufsize !== 'undefined') { 107 | this.bufsize = options.bufsize; 108 | } 109 | } 110 | 111 | get bufsize() { 112 | return this.setBufsize(null); 113 | } 114 | 115 | set bufsize(n) { 116 | if (typeof n !== 'number' || n <= 0) { 117 | throw new TypeError('bufsize must be a positive number'); 118 | } 119 | 120 | return this.setBufsize(n); 121 | } 122 | 123 | totalIn() { 124 | return this.totalIn_; 125 | } 126 | 127 | totalOut() { 128 | return this.totalOut_; 129 | } 130 | 131 | cleanup() { 132 | if (this.nativeStream) { 133 | this.nativeStream.resetUnderlying(); 134 | } 135 | 136 | this.nativeStream = null; 137 | } 138 | 139 | _transform(chunk, encoding, callback) { 140 | if (!this.nativeStream) return; 141 | // Split the chunk at 'YZ'. This is used to have a clean boundary at the 142 | // end of each `.xz` file stream. 143 | var possibleEndIndex = bufferIndexOfYZ(chunk); 144 | if (possibleEndIndex !== -1) { 145 | possibleEndIndex += 2; 146 | if (possibleEndIndex !== chunk.length) { 147 | this._transform(chunk.slice(0, possibleEndIndex), encoding, () => { 148 | this._transform(chunk.slice(possibleEndIndex), encoding, callback); 149 | }); 150 | 151 | return; 152 | } 153 | } 154 | 155 | if (this._isFinished && chunk) { 156 | chunk = skipLeadingZeroes(chunk); 157 | 158 | if (chunk.length > 0) { 159 | // Real data from a second stream member in the file! 160 | this._isFinished = false; 161 | } 162 | } 163 | 164 | if (chunk && chunk.length === 0) { 165 | return callback(); 166 | } 167 | 168 | this.chunkCallbacks.push(callback); 169 | 170 | try { 171 | this.nativeStream.code(chunk, !this.synchronous); 172 | } catch (e) { 173 | this.emit('error-cleanup', e); 174 | this.emit('error', e); 175 | } 176 | } 177 | 178 | _writev(chunks, callback) { 179 | chunks = chunks.map(chunk => chunk.chunk); 180 | this._write(Buffer.concat(chunks), null, callback); 181 | } 182 | 183 | _flush(callback) { 184 | this._writingLastChunk = true; 185 | 186 | if (this._isFinished) { 187 | this.cleanup(); 188 | callback(null); 189 | return; 190 | } 191 | 192 | this._transform(null, null, function() { 193 | this.cleanup(); 194 | callback.apply(this, arguments); 195 | }); 196 | } 197 | } 198 | 199 | // add all methods from the native Stream 200 | Object.getOwnPropertyNames(native.Stream.prototype).forEach(function(key) { 201 | if (typeof native.Stream.prototype[key] !== 'function' || key === 'constructor') 202 | return; 203 | JSLzmaStream.prototype[key] = function() { 204 | return this.nativeStream[key].apply(this.nativeStream, arguments); 205 | }; 206 | }); 207 | 208 | Stream.prototype.rawEncoder = function(options) { 209 | return this.rawEncoder_(options.filters || []); 210 | }; 211 | 212 | Stream.prototype.rawDecoder = function(options) { 213 | return this.rawDecoder_(options.filters || []); 214 | }; 215 | 216 | Stream.prototype.easyEncoder = function(options) { 217 | var preset = options.preset || exports.PRESET_DEFAULT; 218 | var check = options.check || exports.CHECK_CRC32; 219 | 220 | if (typeof options.threads !== 'undefined' && options.threads !== null) { 221 | return this.mtEncoder_(Object.assign({ 222 | preset: preset, 223 | filters: null, 224 | check: check 225 | }, options)); 226 | } else { 227 | return this.easyEncoder_(preset, check); 228 | } 229 | }; 230 | 231 | Stream.prototype.streamEncoder = function(options) { 232 | var filters = options.filters || []; 233 | var check = options.check || exports.CHECK_CRC32; 234 | 235 | if (typeof options.threads !== 'undefined' && options.threads !== null) { 236 | return this.mtEncoder_(Object.assign({ 237 | preset: null, 238 | filters: filters, 239 | check: check 240 | }, options)); 241 | } else { 242 | return this.streamEncoder_(filters, check); 243 | } 244 | }; 245 | 246 | Stream.prototype.streamDecoder = function(options) { 247 | this._initOptions = options; 248 | this._restart = function() { 249 | this.resetUnderlying(); 250 | this.streamDecoder(this._initOptions); 251 | }; 252 | 253 | return this.streamDecoder_(options.memlimit || null, options.flags || 0); 254 | }; 255 | 256 | Stream.prototype.autoDecoder = function(options) { 257 | this._initOptions = options; 258 | this._restart = function() { 259 | this.resetUnderlying(); 260 | this.autoDecoder(this._initOptions); 261 | }; 262 | 263 | return this.autoDecoder_(options.memlimit || null, options.flags || 0); 264 | }; 265 | 266 | Stream.prototype.aloneDecoder = function(options) { 267 | return this.aloneDecoder_(options.memlimit || null); 268 | }; 269 | 270 | /* helper functions for easy creation of streams */ 271 | var createStream = 272 | exports.createStream = function(coder, options) { 273 | if (['number', 'object'].indexOf(typeof coder) !== -1 && !options) { 274 | options = coder; 275 | coder = null; 276 | } 277 | 278 | if (parseInt(options) === parseInt(options)) 279 | options = {preset: parseInt(options)}; 280 | 281 | coder = coder || 'easyEncoder'; 282 | options = options || {}; 283 | 284 | var stream = new Stream(); 285 | stream[coder](options); 286 | 287 | if (options.memlimit) 288 | stream.memlimitSet(options.memlimit); 289 | 290 | return stream.getStream(options); 291 | }; 292 | 293 | exports.createCompressor = function(options) { 294 | return createStream('easyEncoder', options); 295 | }; 296 | 297 | exports.createDecompressor = function(options) { 298 | return createStream('autoDecoder', options); 299 | }; 300 | 301 | exports.crc32 = function(input, encoding, presetCRC32) { 302 | if (typeof encoding === 'number') { 303 | presetCRC32 = encoding; 304 | encoding = null; 305 | } 306 | 307 | if (typeof input === 'string') 308 | input = Buffer.from(input, encoding); 309 | 310 | return exports.crc32_(input, presetCRC32 || 0); 311 | }; 312 | 313 | /* compatibility: node-xz (https://github.com/robey/node-xz) */ 314 | exports.Compressor = function(preset, options) { 315 | options = Object.assign({}, options); 316 | 317 | if (preset) 318 | options.preset = preset; 319 | 320 | return createStream('easyEncoder', options); 321 | }; 322 | 323 | exports.Decompressor = function(options) { 324 | return createStream('autoDecoder', options); 325 | }; 326 | 327 | /* compatibility: LZMA-JS (https://github.com/nmrugg/LZMA-JS) */ 328 | function singleStringCoding(stream, string, on_finish, on_progress) { 329 | on_progress = on_progress || function() {}; 330 | on_finish = on_finish || function() {}; 331 | 332 | // possibly our input is an array of byte integers 333 | // or a typed array 334 | if (!Buffer.isBuffer(string)) 335 | string = Buffer.from(string); 336 | 337 | var deferred = {}, failed = false; 338 | 339 | stream.once('error', function(err) { 340 | failed = true; 341 | on_finish(null, err); 342 | }); 343 | 344 | // emulate Promise.defer() 345 | deferred.promise = new Promise(function(resolve, reject) { 346 | deferred.resolve = resolve; 347 | deferred.reject = reject; 348 | }); 349 | 350 | // Since using the Promise API is optional, generating unhandled 351 | // rejections is not okay. 352 | deferred.promise.catch(noop); 353 | 354 | stream.once('error', function(e) { 355 | deferred.reject(e); 356 | }); 357 | 358 | var buffers = []; 359 | 360 | stream.on('data', function(b) { 361 | buffers.push(b); 362 | }); 363 | 364 | stream.once('end', function() { 365 | var result = Buffer.concat(buffers); 366 | 367 | if (!failed) { 368 | on_progress(1.0); 369 | on_finish(result); 370 | } 371 | 372 | if (deferred) 373 | deferred.resolve(result); 374 | }); 375 | 376 | on_progress(0.0); 377 | 378 | stream.end(string); 379 | 380 | if (deferred) 381 | return deferred.promise; 382 | } 383 | 384 | exports.LZMA = function() { 385 | return { 386 | compress: function(string, mode, on_finish, on_progress) { 387 | var opt = {}; 388 | 389 | if (parseInt(mode) === parseInt(mode) && mode >= 1 && mode <= 9) 390 | opt.preset = parseInt(mode); 391 | 392 | var stream = createStream('aloneEncoder', opt); 393 | 394 | return singleStringCoding(stream, string, on_finish, on_progress); 395 | }, 396 | decompress: function(byte_array, on_finish, on_progress) { 397 | var stream = createStream('autoDecoder'); 398 | 399 | return singleStringCoding(stream, byte_array, on_finish, on_progress); 400 | }, 401 | // dummy, we don’t use web workers 402 | worker: function() { return null; } 403 | }; 404 | }; 405 | 406 | exports.compress = function(string, opt, on_finish) { 407 | if (typeof opt === 'function') { 408 | on_finish = opt; 409 | opt = {}; 410 | } 411 | 412 | var stream = createStream('easyEncoder', opt); 413 | return singleStringCoding(stream, string, on_finish); 414 | }; 415 | 416 | exports.decompress = function(string, opt, on_finish) { 417 | if (typeof opt === 'function') { 418 | on_finish = opt; 419 | opt = {}; 420 | } 421 | 422 | var stream = createStream('autoDecoder', opt); 423 | return singleStringCoding(stream, string, on_finish); 424 | }; 425 | 426 | if (util.promisify) { 427 | exports.compress[util.promisify.custom] = exports.compress; 428 | exports.decompress[util.promisify.custom] = exports.decompress; 429 | } 430 | 431 | exports.isXZ = function(buf) { 432 | return buf && buf.length >= 6 && 433 | buf[0] === 0xfd && 434 | buf[1] === 0x37 && 435 | buf[2] === 0x7a && 436 | buf[3] === 0x58 && 437 | buf[4] === 0x5a && 438 | buf[5] === 0x00; 439 | }; 440 | 441 | exports.parseFileIndex = function(options, callback) { 442 | if (typeof options !== 'object') { 443 | throw new TypeError('parseFileIndex needs an options object'); 444 | } 445 | 446 | var p = new native.IndexParser(); 447 | 448 | if (typeof options.fileSize !== 'number') { 449 | throw new TypeError('parseFileeIndex needs options.fileSize'); 450 | } 451 | 452 | if (typeof options.read !== 'function') { 453 | throw new TypeError('parseFileIndex needs a read callback'); 454 | } 455 | 456 | p.init(options.fileSize, options.memlimit || 0); 457 | p.read_cb = function(count, offset) { 458 | var inSameTick = true; 459 | var bytesRead = count; 460 | 461 | options.read(count, offset, function(err, buffer) { 462 | if (Buffer.isBuffer(err)) { 463 | buffer = err; 464 | err = null; 465 | } 466 | 467 | if (err) { 468 | if (typeof callback === 'undefined') { 469 | throw err; 470 | } 471 | 472 | return callback(err, null); 473 | } 474 | 475 | p.feed(buffer); 476 | bytesRead = buffer.length; 477 | 478 | if (inSameTick) { 479 | // The call to parse() is still on the call stack and will continue 480 | // seamlessly. 481 | return; 482 | } 483 | 484 | // Kick off parsing again. 485 | var info; 486 | 487 | try { 488 | info = p.parse(); 489 | } catch (e) { 490 | return callback(e, null); 491 | } 492 | 493 | if (info !== true) { 494 | return callback(null, cleanupIndexInfo(info)); 495 | } 496 | }); 497 | 498 | inSameTick = false; 499 | 500 | return bytesRead; 501 | }; 502 | 503 | var info; 504 | try { 505 | info = p.parse(); 506 | } catch (e) { 507 | if (typeof callback !== 'undefined') { 508 | callback(e, null); 509 | return; 510 | } 511 | 512 | throw e; 513 | } 514 | 515 | if (info !== true) { 516 | info = cleanupIndexInfo(info); 517 | if (typeof callback !== 'undefined' && info !== true) { 518 | callback(null, info); 519 | } 520 | 521 | return info; 522 | } 523 | }; 524 | 525 | exports.parseFileIndexFD = function(fd, callback) { 526 | return fs.fstat(fd, function(err, stats) { 527 | if (err) { 528 | return callback(err, null); 529 | } 530 | 531 | exports.parseFileIndex({ 532 | fileSize: stats.size, 533 | read: function(count, offset, cb) { 534 | var buffer = Buffer.allocUnsafe(count); 535 | 536 | fs.read(fd, buffer, 0, count, offset, function(err, bytesRead, buffer) { 537 | if (err) { 538 | return cb(err, null); 539 | } 540 | 541 | if (bytesRead !== count) { 542 | return cb(new Error('Truncated file!'), null); 543 | } 544 | 545 | cb(null, buffer); 546 | }); 547 | } 548 | }, callback); 549 | }); 550 | }; 551 | 552 | function cleanupIndexInfo(info) { 553 | var checkFlags = info.checks; 554 | 555 | info.checks = []; 556 | for (var i = 0; i < exports.CHECK_ID_MAX; i++) { 557 | if (checkFlags & (1 << i)) 558 | info.checks.push(i); 559 | } 560 | 561 | return info; 562 | } 563 | 564 | function skipLeadingZeroes(buffer) { 565 | var i; 566 | for (i = 0; i < buffer.length; i++) { 567 | if (buffer[i] !== 0x00) 568 | break; 569 | } 570 | 571 | return buffer.slice(i); 572 | } 573 | 574 | function bufferIndexOfYZ(chunk) { 575 | if (!chunk) { 576 | return -1; 577 | } 578 | 579 | if (chunk.indexOf) { 580 | return chunk.indexOf('YZ'); 581 | } 582 | 583 | var i; 584 | for (i = 0; i < chunk.length - 1; i++) { 585 | if (chunk[i] === 0x59 && chunk[i+1] === 0x5a) { 586 | return i; 587 | } 588 | } 589 | 590 | return -1; 591 | } 592 | 593 | function noop() {} 594 | 595 | })(); 596 | -------------------------------------------------------------------------------- /liblzma-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | case $(uname | tr '[:upper:]' '[:lower:]') in 5 | *bsd) alias make='gmake';; 6 | *) 7 | esac 8 | 9 | cd "$1/liblzma" 10 | make 11 | make install 12 | -------------------------------------------------------------------------------- /liblzma-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SRC_TARBALL="$2" 5 | TARGET_DIR="$1/liblzma" 6 | 7 | mkdir -p "$TARGET_DIR" 8 | cd "$TARGET_DIR" 9 | 10 | tar xvjf "$SRC_TARBALL" >node_liblzma_config.log 2>&1 11 | 12 | export CFLAGS="-fPIC $CFLAGS" 13 | 14 | # Fix build on Apple Silicon 15 | if [ $(uname) = "Darwin" -a $(uname -m) = "arm64" ]; then 16 | XZ_SRC_DIR=$(ls | grep xz-*) 17 | sed -i '' 's/\tnone)/\tarm64-*)\n\t\tbasic_machine=$(echo $basic_machine | sed "s\/arm64\/aarch64\/")\n\t\t;;\n\t\tnone)/g' $XZ_SRC_DIR/build-aux/config.sub 18 | fi 19 | 20 | sh xz-*/configure --enable-static --disable-shared --disable-scripts --disable-lzmainfo \ 21 | --disable-lzma-links --disable-lzmadec --disable-xzdec --disable-xz --disable-rpath \ 22 | --prefix="$TARGET_DIR/build" CFLAGS="$CFLAGS" >>node_liblzma_config.log 2>&1 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lzma-native", 3 | "version": "8.0.6", 4 | "engines": { 5 | "node": ">=10.0.0" 6 | }, 7 | "author": { 8 | "name": "Anna Henningsen", 9 | "email": "anna@addaleax.net" 10 | }, 11 | "description": "Provides bindings to the native liblzma library (.xz file format, among others)", 12 | "main": "index", 13 | "bin": { 14 | "lzmajs": "bin/lzmajs" 15 | }, 16 | "dependencies": { 17 | "node-addon-api": "^3.1.0", 18 | "node-gyp-build": "^4.2.1", 19 | "readable-stream": "^3.6.0" 20 | }, 21 | "keywords": [ 22 | "lzma", 23 | "compression", 24 | "crc32", 25 | "xz", 26 | "liblzma" 27 | ], 28 | "homepage": "https://github.com/addaleax/lzma-native", 29 | "license": "MIT", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/addaleax/lzma-native.git" 33 | }, 34 | "scripts": { 35 | "install": "node-gyp-build", 36 | "prebuild": "prebuildify --napi --electron-compat", 37 | "prepack": "[ $(ls prebuilds | wc -l) = '6' ] || (echo 'Some prebuilds are missing'; exit 1)", 38 | "test": "mocha --expose-gc -s 1000 -t 15000", 39 | "prepare": "npm run prepare-win32 || true", 40 | "prepare-win32": "cd deps && 7z x -y xz-5.2.3-windows.7z bin_i686/liblzma.dll bin_x86-64/liblzma.dll include doc/liblzma.def", 41 | "jshint": "jshint ." 42 | }, 43 | "gypfile": true, 44 | "bugs": { 45 | "url": "https://github.com/addaleax/lzma-native/issues" 46 | }, 47 | "devDependencies": { 48 | "bl": "^4.1.0", 49 | "jshint": "^2.12.0", 50 | "mocha": "^8.3.1", 51 | "prebuildify": "^5.0.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/filter-array.cpp: -------------------------------------------------------------------------------- 1 | #include "liblzma-node.hpp" 2 | 3 | namespace lzma { 4 | 5 | FilterArray::FilterArray(Value val) { 6 | Env env = val.Env(); 7 | HandleScope handle_scope(env); 8 | 9 | if (!val.IsArray()) 10 | throw TypeError::New(env, "Filter array expected"); 11 | Array arr = val.As(); 12 | 13 | size_t len = arr.Length(); 14 | 15 | String id_ = String::New(env, "id"); 16 | String options_ = String::New(env, "options"); 17 | 18 | for (size_t i = 0; i < len; ++i) { 19 | Value entry_v = arr[i]; 20 | if (!entry_v.IsObject() || !entry_v.As().Has(id_)) 21 | throw TypeError::New(env, "Filter array expected"); 22 | Object entry = entry_v.As(); 23 | 24 | String id = Value(entry[id_]).ToString(); 25 | Value opt_v = entry[options_]; 26 | 27 | lzma_filter f; 28 | f.id = FilterByName(id); 29 | f.options = nullptr; 30 | 31 | bool has_options = !opt_v.IsUndefined() && !opt_v.IsNull(); 32 | if (!has_options && (f.id != LZMA_FILTER_LZMA1 && f.id != LZMA_FILTER_LZMA2)) { 33 | filters.push_back(f); 34 | continue; 35 | } 36 | 37 | Object opt = has_options ? opt_v.ToObject() : Object::New(env); 38 | 39 | optbuf.push_back(options()); 40 | union options& bopt = optbuf.back(); 41 | 42 | switch (f.id) { 43 | case LZMA_FILTER_DELTA: 44 | bopt.delta.type = (lzma_delta_type) GetIntegerProperty(opt, "type", LZMA_DELTA_TYPE_BYTE); 45 | bopt.delta.dist = GetIntegerProperty(opt, "dist", 1); 46 | f.options = &bopt.delta; 47 | break; 48 | case LZMA_FILTER_LZMA1: 49 | case LZMA_FILTER_LZMA2: 50 | bopt.lzma = parseOptionsLZMA(opt); 51 | f.options = &bopt.lzma; 52 | break; 53 | default: 54 | throw TypeError::New(env, "LZMA wrapper library understands .options only for DELTA and LZMA1, LZMA2 filters"); 55 | } 56 | 57 | filters.push_back(f); 58 | } 59 | 60 | lzma_filter end; 61 | end.id = LZMA_VLI_UNKNOWN; 62 | filters.push_back(end); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/index-parser.cpp: -------------------------------------------------------------------------------- 1 | // The contents from this file are from a proposed API that is not yet 2 | // implemented in upstream liblzma. 3 | 4 | #include "index-parser.h" 5 | 6 | #include 7 | #include 8 | 9 | #undef my_min 10 | #define my_min(x, y) ((x) < (y) ? (x) : (y)) 11 | 12 | namespace lzma { 13 | 14 | void * 15 | lzma_alloc(size_t size, const lzma_allocator *allocator) 16 | { 17 | // Some malloc() variants return NULL if called with size == 0. 18 | if (size == 0) 19 | size = 1; 20 | 21 | void *ptr; 22 | 23 | if (allocator != NULL && allocator->alloc != NULL) 24 | ptr = allocator->alloc(allocator->opaque, 1, size); 25 | else 26 | ptr = malloc(size); 27 | 28 | return ptr; 29 | } 30 | 31 | void 32 | lzma_free(void *ptr, const lzma_allocator *allocator) 33 | { 34 | if (allocator != NULL && allocator->free != NULL) 35 | allocator->free(allocator->opaque, ptr); 36 | else 37 | free(ptr); 38 | 39 | return; 40 | } 41 | 42 | enum lip_state { 43 | PARSE_INDEX_INITED, 44 | PARSE_INDEX_READ_FOOTER, 45 | PARSE_INDEX_READ_INDEX, 46 | PARSE_INDEX_READ_STREAM_HEADER 47 | }; 48 | 49 | struct lzma_index_parser_internal_s { 50 | /// Current state. 51 | lip_state state; 52 | 53 | /// Current position in the file. We parse the file backwards so 54 | /// initialize it to point to the end of the file. 55 | int64_t pos; 56 | 57 | /// The footer flags of the current XZ stream. 58 | lzma_stream_flags footer_flags; 59 | 60 | /// All Indexes decoded so far. 61 | lzma_index *combined_index; 62 | 63 | /// The Index currently being decoded. 64 | lzma_index *this_index; 65 | 66 | /// Padding of the stream currently being decoded. 67 | lzma_vli stream_padding; 68 | 69 | /// Size of the Index currently being decoded. 70 | lzma_vli index_size; 71 | 72 | /// Keep track of how much memory is being used for Index decoding. 73 | uint64_t memused; 74 | 75 | /// lzma_stream for the Index decoder. 76 | lzma_stream strm; 77 | 78 | /// Keep the buffer coming as the last member to so all data that is 79 | /// ever actually used fits in a few cache lines. 80 | uint8_t buf[8192]; 81 | }; 82 | 83 | static lzma_ret 84 | parse_indexes_read(lzma_index_parser_data *info, 85 | uint8_t *buf, 86 | size_t size, 87 | int64_t pos) 88 | { 89 | int64_t read = info->read_callback(info->opaque, buf, size, pos); 90 | 91 | if (read < 0) { 92 | return LZMA_DATA_ERROR; 93 | } 94 | 95 | if ((size_t)read != size) { 96 | info->message = "Unexpected end of file"; 97 | return LZMA_DATA_ERROR; 98 | } 99 | 100 | return LZMA_OK; 101 | } 102 | 103 | extern lzma_ret 104 | my_lzma_parse_indexes_from_file(lzma_index_parser_data *info) 105 | { 106 | lzma_ret ret; 107 | lzma_index_parser_internal *internal = info->internal; 108 | info->message = NULL; 109 | 110 | // Apparently, we are already done. 111 | if (info->index != NULL) { 112 | ret = LZMA_PROG_ERROR; 113 | goto error; 114 | } 115 | 116 | // Passing file_size == SIZE_MAX can be used to safely clean up 117 | // everything when I/O failed asynchronously. 118 | if (info->file_size == SIZE_MAX) { 119 | ret = LZMA_OPTIONS_ERROR; 120 | goto error; 121 | } 122 | 123 | if (info->memlimit == 0) { 124 | info->memlimit = UINT64_MAX; 125 | } 126 | 127 | if (internal == NULL) { 128 | if (info->memlimit <= sizeof(lzma_index_parser_internal)) { 129 | // We don't really have a good figure for how much 130 | // memory may be necessary. Set memlimit to 0 to 131 | // indicate that something is obviously inacceptable. 132 | info->memlimit = 0; 133 | return LZMA_MEMLIMIT_ERROR; 134 | } 135 | 136 | internal = (lzma_index_parser_internal*)lzma_alloc(sizeof(lzma_index_parser_internal), 137 | info->allocator); 138 | 139 | if (internal == NULL) 140 | return LZMA_MEM_ERROR; 141 | 142 | internal->state = PARSE_INDEX_INITED; 143 | internal->pos = info->file_size; 144 | internal->combined_index = NULL; 145 | internal->this_index = NULL; 146 | info->internal = internal; 147 | 148 | lzma_stream strm_ = LZMA_STREAM_INIT; 149 | memcpy(&internal->strm, &strm_, sizeof(lzma_stream)); 150 | internal->strm.allocator = info->allocator; 151 | } 152 | 153 | // The header flags of the current stream are only ever used within a 154 | // call and don't need to go into the internals struct. 155 | lzma_stream_flags header_flags; 156 | 157 | int i; 158 | uint64_t memlimit; 159 | 160 | switch (internal->state) { 161 | case PARSE_INDEX_INITED: 162 | if (info->file_size <= 0) { 163 | // These strings are fixed so they can be translated by the xz 164 | // command line utility. 165 | info->message = "File is empty"; 166 | return LZMA_DATA_ERROR; 167 | } 168 | 169 | if (info->file_size < 2 * LZMA_STREAM_HEADER_SIZE) { 170 | info->message = "Too small to be a valid .xz file"; 171 | return LZMA_DATA_ERROR; 172 | } 173 | 174 | // Each loop iteration decodes one Index. 175 | do { 176 | // Check that there is enough data left to contain at least 177 | // the Stream Header and Stream Footer. This check cannot 178 | // fail in the first pass of this loop. 179 | if (internal->pos < 2 * LZMA_STREAM_HEADER_SIZE) { 180 | ret = LZMA_DATA_ERROR; 181 | goto error; 182 | } 183 | 184 | internal->pos -= LZMA_STREAM_HEADER_SIZE; 185 | internal->stream_padding = 0; 186 | 187 | // Locate the Stream Footer. There may be Stream Padding which 188 | // we must skip when reading backwards. 189 | while (true) { 190 | if (internal->pos < LZMA_STREAM_HEADER_SIZE) { 191 | ret = LZMA_DATA_ERROR; 192 | goto error; 193 | } 194 | 195 | ret = parse_indexes_read(info, 196 | internal->buf, 197 | LZMA_STREAM_HEADER_SIZE, 198 | internal->pos); 199 | 200 | if (ret != LZMA_OK) 201 | goto error; 202 | internal->state = PARSE_INDEX_READ_FOOTER; 203 | if (info->async) return LZMA_OK; 204 | case PARSE_INDEX_READ_FOOTER: 205 | 206 | // Stream Padding is always a multiple of four bytes. 207 | i = 2; 208 | if (((uint32_t *)internal->buf)[i] != 0) 209 | break; 210 | 211 | // To avoid calling the read callback for every four 212 | // bytes of Stream Padding, take advantage that we 213 | // read 12 bytes (LZMA_STREAM_HEADER_SIZE) already 214 | // and check them too before calling the read 215 | // callback again. 216 | do { 217 | internal->stream_padding += 4; 218 | internal->pos -= 4; 219 | --i; 220 | } while (i >= 0 && ((uint32_t *)internal->buf)[i] == 0); 221 | } 222 | 223 | // Decode the Stream Footer. 224 | ret = lzma_stream_footer_decode(&internal->footer_flags, 225 | internal->buf); 226 | if (ret != LZMA_OK) { 227 | goto error; 228 | } 229 | 230 | // Check that the Stream Footer doesn't specify something 231 | // that we don't support. This can only happen if the xz 232 | // version is older than liblzma and liblzma supports 233 | // something new. 234 | // 235 | // It is enough to check Stream Footer. Stream Header must 236 | // match when it is compared against Stream Footer with 237 | // lzma_stream_flags_compare(). 238 | if (internal->footer_flags.version != 0) { 239 | ret = LZMA_OPTIONS_ERROR; 240 | goto error; 241 | } 242 | 243 | // Check that the size of the Index field looks sane. 244 | internal->index_size = internal->footer_flags.backward_size; 245 | if ((lzma_vli)(internal->pos) < 246 | internal->index_size + 247 | LZMA_STREAM_HEADER_SIZE) { 248 | ret = LZMA_DATA_ERROR; 249 | goto error; 250 | } 251 | 252 | // Set pos to the beginning of the Index. 253 | internal->pos -= internal->index_size; 254 | 255 | // See how much memory we can use for decoding this Index. 256 | memlimit = info->memlimit; 257 | internal->memused = sizeof(lzma_index_parser_internal); 258 | if (internal->combined_index != NULL) { 259 | internal->memused = lzma_index_memused( 260 | internal->combined_index); 261 | assert(internal->memused <= memlimit); 262 | 263 | memlimit -= internal->memused; 264 | } 265 | 266 | // Decode the Index. 267 | ret = lzma_index_decoder(&internal->strm, 268 | &internal->this_index, 269 | memlimit); 270 | if (ret != LZMA_OK) { 271 | goto error; 272 | } 273 | 274 | do { 275 | // Don't give the decoder more input than the 276 | // Index size. 277 | internal->strm.avail_in = my_min(sizeof(internal->buf), 278 | internal->index_size); 279 | 280 | ret = parse_indexes_read(info, 281 | internal->buf, 282 | internal->strm.avail_in, 283 | internal->pos); 284 | 285 | if (ret != LZMA_OK) 286 | goto error; 287 | internal->state = PARSE_INDEX_READ_INDEX; 288 | if (info->async) return LZMA_OK; 289 | case PARSE_INDEX_READ_INDEX: 290 | 291 | internal->pos += internal->strm.avail_in; 292 | internal->index_size -= internal->strm.avail_in; 293 | 294 | internal->strm.next_in = internal->buf; 295 | ret = lzma_code(&internal->strm, LZMA_RUN); 296 | 297 | } while (ret == LZMA_OK); 298 | 299 | // If the decoding seems to be successful, check also that 300 | // the Index decoder consumed as much input as indicated 301 | // by the Backward Size field. 302 | if (ret == LZMA_STREAM_END && ( 303 | internal->index_size != 0 || 304 | internal->strm.avail_in != 0)) { 305 | ret = LZMA_DATA_ERROR; 306 | } 307 | 308 | if (ret != LZMA_STREAM_END) { 309 | // LZMA_BUFFER_ERROR means that the Index decoder 310 | // would have liked more input than what the Index 311 | // size should be according to Stream Footer. 312 | // The message for LZMA_DATA_ERROR makes more 313 | // sense in that case. 314 | if (ret == LZMA_BUF_ERROR) 315 | ret = LZMA_DATA_ERROR; 316 | 317 | // If the error was too low memory usage limit, 318 | // indicate also how much memory would have been needed. 319 | if (ret == LZMA_MEMLIMIT_ERROR) { 320 | uint64_t needed = lzma_memusage( 321 | &internal->strm); 322 | if (UINT64_MAX - needed < internal->memused) 323 | needed = UINT64_MAX; 324 | else 325 | needed += internal->memused; 326 | 327 | info->memlimit = needed; 328 | } 329 | 330 | goto error; 331 | } 332 | 333 | // Decode the Stream Header and check that its Stream Flags 334 | // match the Stream Footer. 335 | internal->pos -= internal->footer_flags.backward_size; 336 | internal->pos -= LZMA_STREAM_HEADER_SIZE; 337 | if ((lzma_vli)(internal->pos) < 338 | lzma_index_total_size(internal->this_index)) { 339 | ret = LZMA_DATA_ERROR; 340 | goto error; 341 | } 342 | 343 | internal->pos -= lzma_index_total_size(internal->this_index); 344 | 345 | ret = parse_indexes_read(info, 346 | internal->buf, 347 | LZMA_STREAM_HEADER_SIZE, 348 | internal->pos); 349 | 350 | if (ret != LZMA_OK) 351 | goto error; 352 | 353 | internal->state = PARSE_INDEX_READ_STREAM_HEADER; 354 | if (info->async) return LZMA_OK; 355 | case PARSE_INDEX_READ_STREAM_HEADER: 356 | 357 | ret = lzma_stream_header_decode(&header_flags, internal->buf); 358 | if (ret != LZMA_OK) { 359 | goto error; 360 | } 361 | 362 | ret = lzma_stream_flags_compare(&header_flags, 363 | &internal->footer_flags); 364 | if (ret != LZMA_OK) { 365 | goto error; 366 | } 367 | 368 | // Store the decoded Stream Flags into this_index. This is 369 | // needed so that we can print which Check is used in each 370 | // Stream. 371 | ret = lzma_index_stream_flags(internal->this_index, 372 | &internal->footer_flags); 373 | assert(ret == LZMA_OK); 374 | 375 | // Store also the size of the Stream Padding field. It is 376 | // needed to show the offsets of the Streams correctly. 377 | ret = lzma_index_stream_padding(internal->this_index, 378 | internal->stream_padding); 379 | assert(ret == LZMA_OK); 380 | 381 | if (internal->combined_index != NULL) { 382 | // Append the earlier decoded Indexes 383 | // after this_index. 384 | ret = lzma_index_cat( 385 | internal->this_index, 386 | internal->combined_index, 387 | info->allocator); 388 | if (ret != LZMA_OK) { 389 | goto error; 390 | } 391 | } 392 | 393 | internal->combined_index = internal->this_index; 394 | internal->this_index = NULL; 395 | 396 | info->stream_padding += internal->stream_padding; 397 | 398 | } while (internal->pos > 0); 399 | 400 | lzma_end(&internal->strm); 401 | 402 | // All OK. Make combined_index available to the caller. 403 | info->index = internal->combined_index; 404 | 405 | lzma_free(internal, info->allocator); 406 | info->internal = NULL; 407 | return LZMA_STREAM_END; 408 | } // end switch(internal->state) 409 | 410 | error: 411 | // Something went wrong, free the allocated memory. 412 | if (internal) { 413 | lzma_end(&internal->strm); 414 | lzma_index_end(internal->combined_index, info->allocator); 415 | lzma_index_end(internal->this_index, info->allocator); 416 | lzma_free(internal, info->allocator); 417 | } 418 | 419 | info->internal = NULL; 420 | 421 | // Doing this will prevent people from calling lzma_parse_indexes_from_file() 422 | // again without re-initializing. 423 | info->file_size = SIZE_MAX; 424 | return ret; 425 | } 426 | 427 | } 428 | 429 | #include "liblzma-node.hpp" 430 | 431 | namespace lzma { 432 | 433 | void IndexParser::InitializeExports(Object exports) { 434 | exports["IndexParser"] = DefineClass(exports.Env(), "IndexParser", { 435 | InstanceMethod("init", &IndexParser::Init), 436 | InstanceMethod("feed", &IndexParser::Feed), 437 | InstanceMethod("parse", &IndexParser::Parse), 438 | }); 439 | } 440 | 441 | namespace { 442 | extern "C" int64_t LZMA_API_CALL 443 | read_cb(void* opaque, uint8_t* buf, size_t count, int64_t offset) { 444 | IndexParser* p = static_cast(opaque); 445 | return p->readCallback(opaque, buf, count, offset); 446 | } 447 | 448 | extern "C" void* LZMA_API_CALL 449 | alloc_for_lzma_index(void *opaque, size_t nmemb, size_t size) { 450 | IndexParser* p = static_cast(opaque); 451 | size_t nBytes = nmemb * size + sizeof(size_t); 452 | 453 | size_t* result = static_cast(::malloc(nBytes)); 454 | if (!result) 455 | return result; 456 | 457 | *result = nBytes; 458 | MemoryManagement::AdjustExternalMemory(p->Env(), static_cast(nBytes)); 459 | return static_cast(result + 1); 460 | } 461 | 462 | extern "C" void LZMA_API_CALL 463 | free_for_lzma_index(void *opaque, void *ptr) { 464 | IndexParser* p = static_cast(opaque); 465 | if (!ptr) 466 | return; 467 | 468 | size_t* orig = static_cast(ptr) - 1; 469 | 470 | MemoryManagement::AdjustExternalMemory(p->Env(), -static_cast(*orig)); 471 | return ::free(static_cast(orig)); 472 | } 473 | } 474 | 475 | int64_t IndexParser::readCallback(void* opaque, uint8_t* buf, size_t count, int64_t offset) { 476 | currentReadBuffer = buf; 477 | currentReadSize = count; 478 | 479 | napi_value argv[2] = { 480 | Uint64ToNumberMaxNull(Env(), count), 481 | Uint64ToNumberMaxNull(Env(), offset) 482 | }; 483 | 484 | Function read_cb = Napi::Value(Value()["read_cb"]).As(); 485 | Napi::Value ret = read_cb.Call(Value(), 2, argv); 486 | 487 | if (currentReadBuffer) { 488 | info.async = true; 489 | return count; 490 | } else { 491 | // .feed() has been alreay been called synchronously 492 | info.async = false; 493 | return NumberToUint64ClampNullMax(ret); 494 | } 495 | } 496 | 497 | IndexParser::IndexParser(const CallbackInfo& args) 498 | : ObjectWrap(args), 499 | isCurrentlyInParseCall(false) { 500 | lzma_index_parser_data info_ = LZMA_INDEX_PARSER_DATA_INIT; 501 | info = info_; 502 | 503 | allocator.alloc = alloc_for_lzma_index; 504 | allocator.free = free_for_lzma_index; 505 | allocator.opaque = static_cast(this); 506 | 507 | info.read_callback = read_cb; 508 | info.opaque = static_cast(this); 509 | info.allocator = &allocator; 510 | } 511 | 512 | void IndexParser::Init(const CallbackInfo& args) { 513 | info.file_size = NumberToUint64ClampNullMax(args[0]); 514 | info.memlimit = NumberToUint64ClampNullMax(args[1]); 515 | } 516 | 517 | Object IndexParser::getObject() const { 518 | Napi::Env env = Env(); 519 | Object obj = Object::New(env); 520 | 521 | obj["streamPadding"] = Uint64ToNumberMaxNull(env, info.stream_padding); 522 | obj["memlimit"] = Uint64ToNumberMaxNull(env, info.memlimit); 523 | obj["streams"] = Uint64ToNumberMaxNull(env, lzma_index_stream_count(info.index)); 524 | obj["blocks"] = Uint64ToNumberMaxNull(env, lzma_index_block_count(info.index)); 525 | obj["fileSize"] = Uint64ToNumberMaxNull(env, lzma_index_file_size(info.index)); 526 | obj["uncompressedSize"] = Uint64ToNumberMaxNull(env, lzma_index_uncompressed_size(info.index)); 527 | obj["checks"] = Uint64ToNumberMaxNull(env, lzma_index_checks(info.index)); 528 | 529 | return obj; 530 | } 531 | 532 | Value IndexParser::Parse(const CallbackInfo& args) { 533 | if (isCurrentlyInParseCall) 534 | throw Error::New(Env(), "Cannot call IndexParser::Parse recursively"); 535 | 536 | struct RecursionGuard { 537 | explicit RecursionGuard(IndexParser* p) : p(p) { 538 | p->isCurrentlyInParseCall = true; 539 | } 540 | ~RecursionGuard() { 541 | p->isCurrentlyInParseCall = false; 542 | } 543 | IndexParser* p; 544 | }; 545 | 546 | lzma_ret ret; 547 | { 548 | RecursionGuard guard(this); 549 | ret = my_lzma_parse_indexes_from_file(&info); 550 | } 551 | 552 | if (ret == LZMA_OK) { 553 | return Boolean::New(Env(), true); 554 | } else if (ret == LZMA_STREAM_END) { 555 | return getObject(); 556 | } 557 | 558 | Error error = lzmaRetError(Env(), ret); 559 | if (info.message) { 560 | error.Value()["message"] = String::New(Env(), info.message); 561 | } 562 | throw error; 563 | } 564 | 565 | Value IndexParser::Feed(const CallbackInfo& info) { 566 | Napi::Value value_v = info[0]; 567 | if (!value_v.IsTypedArray()) 568 | throw TypeError::New(Env(), "Expected Buffer as input"); 569 | TypedArray value = value_v.As(); 570 | 571 | if (currentReadBuffer == nullptr) 572 | throw Error::New(Env(), "No input data was expected"); 573 | size_t length = value.ByteLength(); 574 | 575 | if (length > currentReadSize) 576 | length = currentReadSize; 577 | 578 | memcpy(currentReadBuffer, 579 | static_cast(value.ArrayBuffer().Data()) + value.ByteOffset(), 580 | length); 581 | currentReadBuffer = nullptr; 582 | 583 | return Uint64ToNumberMaxNull(Env(), length); 584 | } 585 | 586 | IndexParser::~IndexParser() { 587 | assert(!isCurrentlyInParseCall); 588 | info.file_size = SIZE_MAX; 589 | lzma_index_end(info.index, &allocator); 590 | info.index = nullptr; 591 | info.read_callback = nullptr; 592 | lzma_ret ret = my_lzma_parse_indexes_from_file(&info); 593 | assert(ret == LZMA_OPTIONS_ERROR); 594 | } 595 | 596 | } 597 | -------------------------------------------------------------------------------- /src/index-parser.h: -------------------------------------------------------------------------------- 1 | // The contents from this file are from a proposed API that is not yet 2 | // implemented in upstream liblzma. 3 | 4 | #ifndef INDEX_PARSER_H 5 | #define INDEX_PARSER_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace lzma { 12 | 13 | /** 14 | * \brief Internal data structure 15 | * 16 | * The contents of this structure is not visible outside the library. 17 | */ 18 | typedef struct lzma_index_parser_internal_s lzma_index_parser_internal; 19 | 20 | /** 21 | * \brief Reading the indexes of an .xz file 22 | * 23 | * The lzma_index_parser_data data structure is passed to 24 | * lzma_parse_indexes_from_file(), which can be used to retrieve the index 25 | * information for a given .xz file. 26 | * 27 | * It should be initialized with LZMA_INDEX_PARSER_DATA_INIT, 28 | * and, minimally, the file_size and read_callback() members need to be set. 29 | * 30 | * The allocation of internals happens transparently upon usage and does not 31 | * need to be taken care of. 32 | * In the case of an error, lzma_parse_indexes_from_file() performs all 33 | * necessary cleanup. 34 | * In the case of success, the index member will be set and needs to be 35 | * freed using lzma_index_end after the caller is done with it. If a custom 36 | * allocator was set on this struct, it needs to be used for freeing the 37 | * resulting index, too. 38 | * 39 | * Reading the data from the underlying file may happen synchronously or 40 | * asynchronously, see the description of the read_callback(). 41 | */ 42 | typedef struct { 43 | /** 44 | * \brief Combined Index of all Streams in the file 45 | * 46 | * This will be set to an lzma_index * when parsing the file was 47 | * successful, as indicated by a LZMA_STREAM_END return status. 48 | */ 49 | lzma_index *index; 50 | 51 | /** 52 | * \brief Total amount of Stream Padding 53 | * 54 | * This will be set when the file was successfully read. 55 | */ 56 | size_t stream_padding; 57 | 58 | /** 59 | * \brief Callback for reading data from the input file 60 | * 61 | * This member needs to be set to a function that provides a slice of 62 | * the input file. 63 | * 64 | * The opaque pointer will have the same value as the opaque pointer 65 | * set on this struct. 66 | * 67 | * When being invoked, it should read count bytes from the underlying 68 | * file, starting at the specified offset, into buf. 69 | * The return value may be -1, in which case 70 | * lzma_parse_indexes_from_file() will return with LZMA_DATA_ERROR. 71 | * Otherwise, the number of read bytes should be returned. If this is 72 | * not the number of requested bytes, it will be assumed that the file 73 | * was truncated, and lzma_parse_indexes_from_file() will fail with 74 | * LZMA_DATA_ERROR. 75 | * 76 | * It is possible to perform the underlying I/O operations in an 77 | * asynchronous manner. To do so, set the async flag on this struct 78 | * to true. After read_callback() is invoked, 79 | * lzma_parse_indexes_from_file() will return immediately with 80 | * LZMA_OK (unless the read_callback() return value indicates failure), 81 | * and you are expected to call lzma_parse_indexes_from_file() with 82 | * the same struct as soon as the buffer has been filled. 83 | * 84 | * If asynchronous reading is used and the underlying read operation 85 | * fails, you should set file_size to SIZE_MAX and call 86 | * lzma_parse_indexes_from_file() to trigger an error clean up all 87 | * remaining internal state. 88 | * 89 | * You should not perform any operations on this structure until 90 | * the data has been read in any case. 91 | * 92 | * This function is modelled after pread(2), which is a available on 93 | * some platforms and can be easily wrapped to be used here. 94 | */ 95 | int64_t (LZMA_API_CALL *read_callback)(void *opaque, 96 | uint8_t *buf, 97 | size_t count, 98 | int64_t offset); 99 | 100 | /// Opaque pointer that is passed to read_callback. 101 | void *opaque; 102 | 103 | /// Whether to return after calling read_callback and wait for 104 | /// another call. Defaults to synchronous operations. 105 | lzma_bool async; 106 | 107 | /** \brief Callback for reading data from the input file 108 | * 109 | * This needs to be set to the size of the input file before all 110 | * other operations. If this is set to SIZE_MAX, the parser will 111 | * fail with LZMA_OPTIONS_ERROR. This can be used to clean up 112 | * after a failed asynchronous read_callback(). 113 | * 114 | * On error, this will be set to SIZE_MAX. 115 | */ 116 | size_t file_size; 117 | 118 | /** \brief Memory limit for decoding the indexes. 119 | * 120 | * Set a memory limit for decoding. Default to UINT64_MAX for no limit. 121 | * If this is set too low to allocate the internal data structure 122 | * that is minimally required for parsing, this will be set to 0. 123 | * If this is set too low to parse the underlying .xz file, 124 | * this will be set to the amount of memory that would have 125 | * been necessary for parsing the file. 126 | */ 127 | uint64_t memlimit; 128 | 129 | /// Message that may be set when additional information is available 130 | /// on error. 131 | const char *message; 132 | 133 | /** 134 | * \brief Custom memory allocation functions 135 | * 136 | * In most cases this is NULL which makes liblzma use 137 | * the standard malloc() and free(). 138 | */ 139 | const lzma_allocator *allocator; 140 | 141 | /** 142 | * \brief Data which is internal to the index parser. 143 | * 144 | * Do not touch. You can check whether this is NULL to see if this 145 | * structure currently holds external resources, not counting the 146 | * possible index member that is set on success. 147 | */ 148 | lzma_index_parser_internal* internal; 149 | 150 | /* 151 | * Reserved space to allow possible future extensions without 152 | * breaking the ABI. Excluding the initialization of this structure, 153 | * you should not touch these, because the names of these variables 154 | * may change. 155 | */ 156 | void *reserved_ptr1; 157 | void *reserved_ptr2; 158 | void *reserved_ptr3; 159 | void *reserved_ptr4; 160 | uint64_t reserved_int1; 161 | uint64_t reserved_int2; 162 | size_t reserved_int3; 163 | size_t reserved_int4; 164 | lzma_reserved_enum reserved_enum1; 165 | lzma_reserved_enum reserved_enum2; 166 | } lzma_index_parser_data; 167 | 168 | /** 169 | * \brief Initialization for lzma_index_parser_data 170 | * 171 | * When you declare an instance of lzma_index_parser_data, you should 172 | * immediately initialize it to this value: 173 | * 174 | * lzma_index_parser_data strm = LZMA_INDEX_PARSER_DATA_INIT; 175 | * 176 | * Anything which applies for LZMA_STREAM_INIT applies here, too. 177 | */ 178 | #define LZMA_INDEX_PARSER_DATA_INIT \ 179 | { NULL, 0, NULL, NULL, 0, 0, 0, NULL, NULL, NULL, \ 180 | NULL, NULL, NULL, NULL, 0, 0, 0, 0, \ 181 | LZMA_RESERVED_ENUM, LZMA_RESERVED_ENUM } 182 | 183 | /** \brief Parse the Index(es) from the given .xz file 184 | * 185 | * Read metadata from the underlying file. 186 | * The info pointer should refer to a lzma_index_parser_data struct that 187 | * has been initialized using LZMA_INDEX_PARSER_DATA_INIT. 188 | * 189 | * This will call info->read_callback() multiple times to read parts of the 190 | * underlying .xz file and, upon success, fill info->index with an 191 | * lzma_index pointer that contains metadata for the whole file, accumulated 192 | * across multiple streams. 193 | * 194 | * \param info Pointer to a lzma_index_parser_data structure. 195 | * 196 | * \return On success, LZMA_STREAM_END is returned. 197 | * On error, another value is returned, and info->message may 198 | * be set to provide additional information. 199 | * If info->async is set, LZMA_OK may be returned to indicate 200 | * that another call to lzma_parse_indexes_from_file() should be 201 | * performed after the data has been read. 202 | */ 203 | extern lzma_ret 204 | my_lzma_parse_indexes_from_file(lzma_index_parser_data *info); 205 | 206 | } 207 | 208 | #endif 209 | -------------------------------------------------------------------------------- /src/liblzma-functions.cpp: -------------------------------------------------------------------------------- 1 | #include "liblzma-node.hpp" 2 | 3 | namespace lzma { 4 | 5 | Value lzmaVersionNumber(const CallbackInfo& info) { 6 | return Number::New(info.Env(), lzma_version_number()); 7 | } 8 | 9 | Value lzmaVersionString(const CallbackInfo& info) { 10 | return String::New(info.Env(), lzma_version_string()); 11 | } 12 | 13 | Value lzmaCheckIsSupported(const CallbackInfo& info) { 14 | lzma_check arg = (lzma_check) info[0].ToNumber().Int64Value(); 15 | 16 | return Boolean::New(info.Env(), lzma_check_is_supported(arg)); 17 | } 18 | 19 | Value lzmaCheckSize(const CallbackInfo& info) { 20 | lzma_check arg = (lzma_check) info[0].ToNumber().Int64Value(); 21 | 22 | return Number::New(info.Env(), lzma_check_size(arg)); 23 | } 24 | 25 | Value lzmaFilterEncoderIsSupported(const CallbackInfo& info) { 26 | uint64_t arg = FilterByName(info[0]); 27 | 28 | return Boolean::New(info.Env(), lzma_filter_encoder_is_supported(arg)); 29 | } 30 | 31 | Value lzmaFilterDecoderIsSupported(const CallbackInfo& info) { 32 | uint64_t arg = FilterByName(info[0]); 33 | 34 | return Boolean::New(info.Env(), lzma_filter_decoder_is_supported(arg)); 35 | } 36 | 37 | Value lzmaMfIsSupported(const CallbackInfo& info) { 38 | lzma_match_finder arg = (lzma_match_finder) info[0].ToNumber().Int64Value(); 39 | 40 | return Boolean::New(info.Env(), lzma_mf_is_supported(arg)); 41 | } 42 | 43 | Value lzmaModeIsSupported(const CallbackInfo& info) { 44 | lzma_mode arg = (lzma_mode) info[0].ToNumber().Int64Value(); 45 | 46 | return Boolean::New(info.Env(), lzma_mode_is_supported(arg)); 47 | } 48 | 49 | Value lzmaEasyEncoderMemusage(const CallbackInfo& info) { 50 | int64_t arg = info[0].ToNumber(); 51 | 52 | return Uint64ToNumberMaxNull(info.Env(), lzma_easy_encoder_memusage(arg)); 53 | } 54 | 55 | Value lzmaEasyDecoderMemusage(const CallbackInfo& info) { 56 | int64_t arg = info[0].ToNumber(); 57 | 58 | return Uint64ToNumberMaxNull(info.Env(), lzma_easy_decoder_memusage(arg)); 59 | } 60 | 61 | Value lzmaCRC32(const CallbackInfo& info) { 62 | int64_t arg = info[1].ToNumber(); 63 | 64 | std::vector data; 65 | 66 | if (!readBufferFromObj(info[0], &data)) 67 | throw TypeError::New(info.Env(), "CRC32 expects Buffer as input"); 68 | 69 | return Number::New(info.Env(), lzma_crc32(data.data(), data.size(), arg)); 70 | } 71 | 72 | Value lzmaRawEncoderMemusage(const CallbackInfo& info) { 73 | const FilterArray filters(info[0]); 74 | 75 | return Uint64ToNumberMaxNull(info.Env(), lzma_raw_encoder_memusage(filters.array())); 76 | } 77 | 78 | Value lzmaRawDecoderMemusage(const CallbackInfo& info) { 79 | const FilterArray filters(info[0]); 80 | 81 | return Uint64ToNumberMaxNull(info.Env(), lzma_raw_decoder_memusage(filters.array())); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/liblzma-node.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BUILDING_NODE_EXTENSION 2 | #define BUILDING_NODE_EXTENSION 3 | #endif 4 | 5 | #ifndef LIBLZMA_NODE_HPP 6 | #define LIBLZMA_NODE_HPP 7 | 8 | #include 9 | 10 | #include 11 | #include "index-parser.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace lzma { 24 | using namespace Napi; 25 | 26 | /* util */ 27 | /** 28 | * Return the filter constant associated with a v8 String handle 29 | */ 30 | lzma_vli FilterByName(Value name); 31 | 32 | /** 33 | * If rv represents an error, throw a javascript exception representing it. 34 | * Always returns rv as a v8 Integer. 35 | */ 36 | Number lzmaRet(Env env, lzma_ret rv); 37 | 38 | /** 39 | * Return a javascript exception representing rv. 40 | */ 41 | Error lzmaRetError(Env env, lzma_ret rv); 42 | 43 | /** 44 | * Takes a Node.js SlowBuffer or Buffer as input and populates data accordingly. 45 | * Returns true on success, false on failure. 46 | */ 47 | bool readBufferFromObj(Value value, std::vector* data); 48 | 49 | /** 50 | * Return a lzma_options_lzma struct as described by the v8 Object obj. 51 | */ 52 | lzma_options_lzma parseOptionsLZMA(Value obj); 53 | 54 | /** 55 | * Return a v8 Number representation of an uint64_t where UINT64_MAX will be mapped to null 56 | */ 57 | Value Uint64ToNumberMaxNull(Env env, uint64_t in); 58 | 59 | /** 60 | * Return a v8 Number representation of an uint64_t where 0 will be mapped to null 61 | */ 62 | Value Uint64ToNumber0Null(Env env, uint64_t in); 63 | 64 | /** 65 | * Return a uint64_t representation of a v8 Number, 66 | * where values above UINT64_MAX map to UINT64_MAX and null to UINT64_MAX. 67 | * Throws an TypeError if the input is not a number. 68 | */ 69 | uint64_t NumberToUint64ClampNullMax(Value in); 70 | 71 | /** 72 | * Return an integer property of an object (which can be passed to Nan::Get), 73 | * providing a default value if no such property is present 74 | */ 75 | inline int64_t GetIntegerProperty(Object obj, const char* name, int64_t def) { 76 | Value v = obj[name]; 77 | 78 | if (v.IsUndefined()) 79 | return def; 80 | 81 | return v.ToNumber().Int64Value(); 82 | } 83 | 84 | /* bindings in one-to-one correspondence to the lzma functions */ 85 | Value lzmaVersionNumber(const CallbackInfo& info); 86 | Value lzmaVersionString(const CallbackInfo& info); 87 | Value lzmaCheckIsSupported(const CallbackInfo& info); 88 | Value lzmaCheckSize(const CallbackInfo& info); 89 | Value lzmaFilterEncoderIsSupported(const CallbackInfo& info); 90 | Value lzmaFilterDecoderIsSupported(const CallbackInfo& info); 91 | Value lzmaMfIsSupported(const CallbackInfo& info); 92 | Value lzmaModeIsSupported(const CallbackInfo& info); 93 | Value lzmaEasyEncoderMemusage(const CallbackInfo& info); 94 | Value lzmaEasyDecoderMemusage(const CallbackInfo& info); 95 | Value lzmaCRC32(const CallbackInfo& info); 96 | Value lzmaRawEncoderMemusage(const CallbackInfo& info); 97 | Value lzmaRawDecoderMemusage(const CallbackInfo& info); 98 | 99 | /* wrappers */ 100 | /** 101 | * List of liblzma filters with corresponding options 102 | */ 103 | class FilterArray { 104 | public: 105 | FilterArray() = default; 106 | explicit FilterArray(Value arr); 107 | 108 | lzma_filter* array() { return filters.data(); } 109 | const lzma_filter* array() const { return filters.data(); } 110 | 111 | private: 112 | FilterArray(const FilterArray&); 113 | FilterArray& operator=(const FilterArray&); 114 | 115 | union options { 116 | lzma_options_delta delta; 117 | lzma_options_lzma lzma; 118 | }; 119 | 120 | std::vector filters; 121 | std::list optbuf; 122 | }; 123 | 124 | /** 125 | * Wrapper for lzma_mt (multi-threading options). 126 | */ 127 | class MTOptions { 128 | public: 129 | MTOptions() = default; 130 | explicit MTOptions(Value val); 131 | 132 | lzma_mt* opts() { return &opts_; } 133 | const lzma_mt* opts() const { return &opts_; } 134 | 135 | private: 136 | std::unique_ptr filters_; 137 | lzma_mt opts_; 138 | }; 139 | 140 | /** 141 | * Node.js object wrap for lzma_stream wrapper. Corresponds to exports.Stream 142 | */ 143 | class LZMAStream : public ObjectWrap { 144 | public: 145 | explicit LZMAStream(const CallbackInfo& info); 146 | ~LZMAStream(); 147 | static void InitializeExports(Object exports); 148 | 149 | /* regard as private: */ 150 | void doLZMACodeFromAsync(); 151 | void invokeBufferHandlers(bool hasLock); 152 | void* alloc(size_t nmemb, size_t size); 153 | void free(void* ptr); 154 | 155 | private: 156 | void resetUnderlying(); 157 | void doLZMACode(); 158 | 159 | static Napi::Value New(const CallbackInfo& info); 160 | 161 | void adjustExternalMemory(int64_t bytesChange); 162 | void reportAdjustedExternalMemoryToV8(); 163 | 164 | struct MemScope { 165 | explicit MemScope(LZMAStream* stream) : stream(stream) { } 166 | ~MemScope() { stream->reportAdjustedExternalMemoryToV8(); } 167 | LZMAStream* stream; 168 | }; 169 | 170 | AsyncContext async_context; 171 | std::atomic nonAdjustedExternalMemory; 172 | std::mutex mutex; 173 | 174 | void ResetUnderlying(const CallbackInfo& info); 175 | Napi::Value SetBufsize(const CallbackInfo& info); 176 | void Code(const CallbackInfo& info); 177 | Napi::Value Memusage(const CallbackInfo& info); 178 | Napi::Value MemlimitGet(const CallbackInfo& info); 179 | Napi::Value MemlimitSet(const CallbackInfo& info); 180 | Napi::Value RawEncoder(const CallbackInfo& info); 181 | Napi::Value RawDecoder(const CallbackInfo& info); 182 | Napi::Value FiltersUpdate(const CallbackInfo& info); 183 | Napi::Value EasyEncoder(const CallbackInfo& info); 184 | Napi::Value StreamEncoder(const CallbackInfo& info); 185 | Napi::Value AloneEncoder(const CallbackInfo& info); 186 | Napi::Value MTEncoder(const CallbackInfo& info); 187 | Napi::Value StreamDecoder(const CallbackInfo& info); 188 | Napi::Value AutoDecoder(const CallbackInfo& info); 189 | Napi::Value AloneDecoder(const CallbackInfo& info); 190 | 191 | lzma_allocator allocator; 192 | lzma_stream _; 193 | size_t bufsize; 194 | std::string error; 195 | 196 | bool shouldFinish; 197 | size_t processedChunks; 198 | lzma_ret lastCodeResult; 199 | std::queue> inbufs; 200 | std::queue> outbufs; 201 | }; 202 | 203 | /** 204 | * Async worker for a single coding step. 205 | */ 206 | class LZMAStreamCodingWorker : public AsyncWorker { 207 | public: 208 | LZMAStreamCodingWorker(LZMAStream* stream_) 209 | : AsyncWorker(Function(stream_->Env(), nullptr), "LZMAStreamCodingWorker"), 210 | stream(stream_) { 211 | Receiver().Set(static_cast(0), stream->Value()); 212 | } 213 | 214 | ~LZMAStreamCodingWorker() {} 215 | 216 | void Execute() override { 217 | stream->doLZMACodeFromAsync(); 218 | } 219 | 220 | private: 221 | void OnOK() { 222 | stream->invokeBufferHandlers(false); 223 | } 224 | 225 | void OnOK(const Error& e) { 226 | stream->invokeBufferHandlers(false); 227 | } 228 | 229 | LZMAStream* stream; 230 | }; 231 | 232 | class IndexParser : public ObjectWrap { 233 | public: 234 | explicit IndexParser(const CallbackInfo& info); 235 | ~IndexParser(); 236 | 237 | static void InitializeExports(Object exports); 238 | 239 | /* regard as private: */ 240 | int64_t readCallback(void* opaque, uint8_t* buf, size_t count, int64_t offset); 241 | 242 | private: 243 | lzma_index_parser_data info; 244 | lzma_allocator allocator; 245 | 246 | uint8_t* currentReadBuffer; 247 | size_t currentReadSize; 248 | bool isCurrentlyInParseCall; 249 | 250 | Object getObject() const; 251 | 252 | void Init(const CallbackInfo& info); 253 | Napi::Value Feed(const CallbackInfo& info); 254 | Napi::Value Parse(const CallbackInfo& info); 255 | }; 256 | } 257 | 258 | #endif 259 | -------------------------------------------------------------------------------- /src/lzma-stream.cpp: -------------------------------------------------------------------------------- 1 | #include "liblzma-node.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace lzma { 8 | 9 | namespace { 10 | extern "C" void* LZMA_API_CALL 11 | alloc_for_lzma(void *opaque, size_t nmemb, size_t size) { 12 | LZMAStream* strm = static_cast(opaque); 13 | 14 | return strm->alloc(nmemb, size); 15 | } 16 | 17 | extern "C" void LZMA_API_CALL 18 | free_for_lzma(void *opaque, void *ptr) { 19 | LZMAStream* strm = static_cast(opaque); 20 | 21 | return strm->free(ptr); 22 | } 23 | } 24 | 25 | LZMAStream::LZMAStream(const CallbackInfo& info) : 26 | ObjectWrap(info), 27 | async_context(info.Env(), "LZMAStream"), 28 | bufsize(65536), 29 | shouldFinish(false), 30 | processedChunks(0), 31 | lastCodeResult(LZMA_OK) 32 | { 33 | std::memset(&_, 0, sizeof(lzma_stream)); 34 | 35 | allocator.alloc = alloc_for_lzma; 36 | allocator.free = free_for_lzma; 37 | allocator.opaque = static_cast(this); 38 | _.allocator = &allocator; 39 | 40 | nonAdjustedExternalMemory = 0; 41 | MemoryManagement::AdjustExternalMemory(info.Env(), sizeof(LZMAStream)); 42 | } 43 | 44 | void LZMAStream::resetUnderlying() { 45 | if (_.internal != nullptr) 46 | lzma_end(&_); 47 | 48 | reportAdjustedExternalMemoryToV8(); 49 | std::memset(&_, 0, sizeof(lzma_stream)); 50 | _.allocator = &allocator; 51 | lastCodeResult = LZMA_OK; 52 | processedChunks = 0; 53 | } 54 | 55 | LZMAStream::~LZMAStream() { 56 | resetUnderlying(); 57 | 58 | MemoryManagement::AdjustExternalMemory(Env(), -int64_t(sizeof(LZMAStream))); 59 | } 60 | 61 | void* LZMAStream::alloc(size_t nmemb, size_t size) { 62 | size_t nBytes = nmemb * size + sizeof(size_t); 63 | 64 | size_t* result = static_cast(::malloc(nBytes)); 65 | if (!result) 66 | return result; 67 | 68 | *result = nBytes; 69 | adjustExternalMemory(static_cast(nBytes)); 70 | return static_cast(result + 1); 71 | } 72 | 73 | void LZMAStream::free(void* ptr) { 74 | if (!ptr) 75 | return; 76 | 77 | size_t* orig = static_cast(ptr) - 1; 78 | 79 | adjustExternalMemory(-static_cast(*orig)); 80 | return ::free(static_cast(orig)); 81 | } 82 | 83 | void LZMAStream::reportAdjustedExternalMemoryToV8() { 84 | int64_t to_be_reported = nonAdjustedExternalMemory.exchange(0); 85 | if (to_be_reported == 0) 86 | return; 87 | 88 | MemoryManagement::AdjustExternalMemory(Env(), nonAdjustedExternalMemory); 89 | } 90 | 91 | void LZMAStream::adjustExternalMemory(int64_t bytesChange) { 92 | nonAdjustedExternalMemory += bytesChange; 93 | } 94 | 95 | void LZMAStream::ResetUnderlying(const CallbackInfo& info) { 96 | MemScope mem_scope(this); 97 | std::lock_guard lock(mutex); 98 | 99 | resetUnderlying(); 100 | } 101 | 102 | Value LZMAStream::SetBufsize(const CallbackInfo& info) { 103 | size_t oldBufsize, newBufsize = NumberToUint64ClampNullMax(info[0]); 104 | 105 | { 106 | std::lock_guard lock(mutex); 107 | 108 | oldBufsize = bufsize; 109 | 110 | if (newBufsize && newBufsize != UINT_MAX) 111 | bufsize = newBufsize; 112 | } 113 | 114 | return Number::New(Env(), oldBufsize); 115 | } 116 | 117 | void LZMAStream::Code(const CallbackInfo& info) { 118 | MemScope mem_scope(this); 119 | std::lock_guard lock(mutex); 120 | 121 | std::vector inputData; 122 | 123 | if (info[0].IsUndefined() || info[0].IsNull()) { 124 | shouldFinish = true; 125 | } else { 126 | if (!readBufferFromObj(info[0], &inputData)) 127 | return; 128 | 129 | if (inputData.empty()) 130 | shouldFinish = true; 131 | } 132 | inbufs.push(std::move(inputData)); 133 | 134 | bool async = info[1].ToBoolean(); 135 | 136 | if (async) { 137 | (new LZMAStreamCodingWorker(this))->Queue(); 138 | } else { 139 | doLZMACode(); 140 | invokeBufferHandlers(true); 141 | } 142 | } 143 | 144 | template 145 | struct Maybe { 146 | 147 | }; 148 | 149 | void LZMAStream::invokeBufferHandlers(bool hasLock) { 150 | Napi::Env env = Env(); 151 | HandleScope scope(env); 152 | MemScope mem_scope(this); 153 | 154 | std::unique_lock lock; 155 | if (!hasLock) 156 | lock = std::unique_lock(mutex); 157 | 158 | Function bufferHandler = Napi::Value(Value()["bufferHandler"]).As(); 159 | std::vector outbuf; 160 | 161 | auto CallBufferHandlerWithArgv = [&](size_t argc, const napi_value* argv) { 162 | if (!hasLock) lock.unlock(); 163 | bufferHandler.MakeCallback(Value(), 5, argv, async_context); 164 | if (!hasLock) lock.lock(); 165 | }; 166 | 167 | uint64_t in = UINT64_MAX, out = UINT64_MAX; 168 | if (_.internal) 169 | lzma_get_progress(&_, &in, &out); 170 | Napi::Value in_ = Uint64ToNumberMaxNull(env, in); 171 | Napi::Value out_ = Uint64ToNumberMaxNull(env, out); 172 | 173 | while (outbufs.size() > 0) { 174 | outbuf = std::move(outbufs.front()); 175 | outbufs.pop(); 176 | 177 | napi_value argv[5] = { 178 | Buffer::Copy(env, reinterpret_cast(outbuf.data()), outbuf.size()), 179 | env.Undefined(), env.Undefined(), in_, out_ 180 | }; 181 | CallBufferHandlerWithArgv(5, argv); 182 | } 183 | 184 | bool reset = false; 185 | if (lastCodeResult != LZMA_OK) { 186 | Napi::Value errorArg = env.Null(); 187 | 188 | if (lastCodeResult != LZMA_STREAM_END) 189 | errorArg = lzmaRetError(env, lastCodeResult).Value(); 190 | 191 | reset = true; 192 | 193 | napi_value argv[5] = { env.Null(), env.Undefined(), errorArg, in_, out_ }; 194 | CallBufferHandlerWithArgv(5, argv); 195 | } 196 | 197 | if (processedChunks) { 198 | size_t pc = processedChunks; 199 | processedChunks = 0; 200 | 201 | napi_value argv[5] = { 202 | env.Undefined(), Number::New(env, static_cast(pc)), 203 | env.Undefined(), in_, out_ 204 | }; 205 | CallBufferHandlerWithArgv(5, argv); 206 | } 207 | 208 | if (reset) 209 | resetUnderlying(); // resets lastCodeResult! 210 | } 211 | 212 | void LZMAStream::doLZMACodeFromAsync() { 213 | std::lock_guard lock(mutex); 214 | 215 | doLZMACode(); 216 | } 217 | 218 | void LZMAStream::doLZMACode() { 219 | std::vector outbuf(bufsize), inbuf; 220 | _.next_out = outbuf.data(); 221 | _.avail_out = outbuf.size(); 222 | _.avail_in = 0; 223 | 224 | lzma_action action = LZMA_RUN; 225 | 226 | size_t readChunks = 0; 227 | 228 | // _.internal is set to nullptr when lzma_end() is called via resetUnderlying() 229 | while (_.internal) { 230 | if (_.avail_in == 0) { // more input neccessary? 231 | while (_.avail_in == 0 && !inbufs.empty()) { 232 | inbuf = std::move(inbufs.front()); 233 | inbufs.pop(); 234 | readChunks++; 235 | 236 | _.next_in = inbuf.data(); 237 | _.avail_in = inbuf.size(); 238 | } 239 | } 240 | 241 | if (shouldFinish && inbufs.empty()) 242 | action = LZMA_FINISH; 243 | 244 | _.next_out = outbuf.data(); 245 | _.avail_out = outbuf.size(); 246 | 247 | lastCodeResult = lzma_code(&_, action); 248 | 249 | if (lastCodeResult != LZMA_OK && lastCodeResult != LZMA_STREAM_END) { 250 | processedChunks += readChunks; 251 | readChunks = 0; 252 | 253 | break; 254 | } 255 | 256 | if (_.avail_out == 0 || _.avail_in == 0 || lastCodeResult == LZMA_STREAM_END) { 257 | size_t outsz = outbuf.size() - _.avail_out; 258 | 259 | if (outsz > 0) { 260 | #ifndef LZMA_NO_CXX11_RVALUE_REFERENCES // C++11 261 | outbufs.emplace(outbuf.data(), outbuf.data() + outsz); 262 | #else 263 | outbufs.push(std::vector(outbuf.data(), outbuf.data() + outsz)); 264 | #endif 265 | } 266 | 267 | if (lastCodeResult == LZMA_STREAM_END) { 268 | processedChunks += readChunks; 269 | readChunks = 0; 270 | 271 | break; 272 | } 273 | } 274 | 275 | if (_.avail_out == outbuf.size()) { // no progress was made 276 | if (!shouldFinish) { 277 | processedChunks += readChunks; 278 | readChunks = 0; 279 | } 280 | 281 | 282 | if (!shouldFinish) 283 | break; 284 | } 285 | } 286 | } 287 | 288 | void LZMAStream::InitializeExports(Object exports) { 289 | exports["Stream"] = DefineClass(exports.Env(), "LZMAStream", { 290 | InstanceMethod("setBufsize", &LZMAStream::SetBufsize), 291 | InstanceMethod("resetUnderlying", &LZMAStream::ResetUnderlying), 292 | InstanceMethod("code", &LZMAStream::Code), 293 | InstanceMethod("memusage", &LZMAStream::Memusage), 294 | InstanceMethod("memlimitGet", &LZMAStream::MemlimitGet), 295 | InstanceMethod("memlimitSet", &LZMAStream::MemlimitSet), 296 | InstanceMethod("rawEncoder_", &LZMAStream::RawEncoder), 297 | InstanceMethod("rawDecoder_", &LZMAStream::RawDecoder), 298 | InstanceMethod("filtersUpdate", &LZMAStream::FiltersUpdate), 299 | InstanceMethod("easyEncoder_", &LZMAStream::EasyEncoder), 300 | InstanceMethod("streamEncoder_", &LZMAStream::StreamEncoder), 301 | InstanceMethod("aloneEncoder", &LZMAStream::AloneEncoder), 302 | InstanceMethod("mtEncoder_", &LZMAStream::MTEncoder), 303 | InstanceMethod("streamDecoder_", &LZMAStream::StreamDecoder), 304 | InstanceMethod("autoDecoder_", &LZMAStream::AutoDecoder), 305 | InstanceMethod("aloneDecoder_", &LZMAStream::AloneDecoder), 306 | }); 307 | } 308 | 309 | Value LZMAStream::Memusage(const CallbackInfo& info) { 310 | std::lock_guard lock(mutex); 311 | 312 | return Uint64ToNumber0Null(Env(), lzma_memusage(&_)); 313 | } 314 | 315 | Value LZMAStream::MemlimitGet(const CallbackInfo& info) { 316 | std::lock_guard lock(mutex); 317 | 318 | return Uint64ToNumber0Null(Env(), lzma_memlimit_get(&_)); 319 | } 320 | 321 | Value LZMAStream::MemlimitSet(const CallbackInfo& info) { 322 | std::lock_guard lock(mutex); 323 | 324 | if (!info[0].IsNumber()) 325 | throw TypeError::New(Env(), "memlimitSet() needs a numerical argument"); 326 | 327 | Number arg = info[0].As(); 328 | 329 | return lzmaRet(Env(), lzma_memlimit_set(&_, NumberToUint64ClampNullMax(arg))); 330 | } 331 | 332 | Value LZMAStream::RawEncoder(const CallbackInfo& info) { 333 | std::lock_guard lock(mutex); 334 | 335 | const FilterArray filters(info[0]); 336 | 337 | return lzmaRet(Env(), lzma_raw_encoder(&_, filters.array())); 338 | } 339 | 340 | Value LZMAStream::RawDecoder(const CallbackInfo& info) { 341 | std::lock_guard lock(mutex); 342 | 343 | const FilterArray filters(info[0]); 344 | 345 | return lzmaRet(Env(), lzma_raw_decoder(&_, filters.array())); 346 | } 347 | 348 | Value LZMAStream::FiltersUpdate(const CallbackInfo& info) { 349 | std::lock_guard lock(mutex); 350 | 351 | const FilterArray filters(info[0]); 352 | 353 | return lzmaRet(Env(), lzma_filters_update(&_, filters.array())); 354 | } 355 | 356 | Value LZMAStream::EasyEncoder(const CallbackInfo& info) { 357 | std::lock_guard lock(mutex); 358 | 359 | int64_t preset = info[0].ToNumber().Int64Value(); 360 | int64_t check = info[1].ToNumber().Int64Value(); 361 | 362 | return lzmaRet(Env(), lzma_easy_encoder(&_, preset, (lzma_check) check)); 363 | } 364 | 365 | Value LZMAStream::StreamEncoder(const CallbackInfo& info) { 366 | std::lock_guard lock(mutex); 367 | 368 | const FilterArray filters(info[0]); 369 | int64_t check = info[1].ToNumber().Int64Value(); 370 | 371 | return lzmaRet(Env(), lzma_stream_encoder(&_, filters.array(), (lzma_check) check)); 372 | } 373 | 374 | Value LZMAStream::MTEncoder(const CallbackInfo& info) { 375 | std::lock_guard lock(mutex); 376 | 377 | const MTOptions mt(info[0]); 378 | 379 | return lzmaRet(Env(), lzma_stream_encoder_mt(&_, mt.opts())); 380 | } 381 | 382 | Value LZMAStream::AloneEncoder(const CallbackInfo& info) { 383 | std::lock_guard lock(mutex); 384 | 385 | lzma_options_lzma o = parseOptionsLZMA(info[0]); 386 | 387 | return lzmaRet(Env(), lzma_alone_encoder(&_, &o)); 388 | } 389 | 390 | Value LZMAStream::StreamDecoder(const CallbackInfo& info) { 391 | std::lock_guard lock(mutex); 392 | 393 | uint64_t memlimit = NumberToUint64ClampNullMax(info[0]); 394 | int64_t flags = info[1].ToNumber().Int64Value(); 395 | 396 | return lzmaRet(Env(), lzma_stream_decoder(&_, memlimit, flags)); 397 | } 398 | 399 | Value LZMAStream::AutoDecoder(const CallbackInfo& info) { 400 | std::lock_guard lock(mutex); 401 | 402 | uint64_t memlimit = NumberToUint64ClampNullMax(info[0]); 403 | int64_t flags = info[1].ToNumber().Int64Value(); 404 | 405 | return lzmaRet(Env(), lzma_auto_decoder(&_, memlimit, flags)); 406 | } 407 | 408 | Value LZMAStream::AloneDecoder(const CallbackInfo& info) { 409 | std::lock_guard lock(mutex); 410 | 411 | uint64_t memlimit = NumberToUint64ClampNullMax(info[0]); 412 | 413 | return lzmaRet(Env(), lzma_alone_decoder(&_, memlimit)); 414 | } 415 | 416 | } 417 | -------------------------------------------------------------------------------- /src/module.cpp: -------------------------------------------------------------------------------- 1 | #include "liblzma-node.hpp" 2 | 3 | using namespace lzma; 4 | 5 | static Napi::Object moduleInit(Env env, Object exports) { 6 | LZMAStream::InitializeExports(exports); 7 | IndexParser::InitializeExports(exports); 8 | 9 | exports["versionNumber"] = Function::New(env, lzmaVersionNumber); 10 | exports["versionString"] = Function::New(env, lzmaVersionString); 11 | exports["checkIsSupported"] = Function::New(env, lzmaCheckIsSupported); 12 | exports["checkSize"] = Function::New(env, lzmaCheckSize); 13 | exports["crc32_"] = Function::New(env, lzmaCRC32); 14 | exports["filterEncoderIsSupported"] = Function::New(env, lzmaFilterEncoderIsSupported); 15 | exports["filterDecoderIsSupported"] = Function::New(env, lzmaFilterDecoderIsSupported); 16 | exports["rawEncoderMemusage"] = Function::New(env, lzmaRawEncoderMemusage); 17 | exports["rawDecoderMemusage"] = Function::New(env, lzmaRawDecoderMemusage); 18 | exports["mfIsSupported"] = Function::New(env, lzmaMfIsSupported); 19 | exports["modeIsSupported"] = Function::New(env, lzmaModeIsSupported); 20 | exports["easyEncoderMemusage"] = Function::New(env, lzmaEasyEncoderMemusage); 21 | exports["easyDecoderMemusage"] = Function::New(env, lzmaEasyDecoderMemusage); 22 | 23 | // enum lzma_ret 24 | exports["OK"] = Number::New(env, LZMA_OK); 25 | exports["STREAM_END"] = Number::New(env, LZMA_STREAM_END); 26 | exports["NO_CHECK"] = Number::New(env, LZMA_NO_CHECK); 27 | exports["UNSUPPORTED_CHECK"] = Number::New(env, LZMA_UNSUPPORTED_CHECK); 28 | exports["GET_CHECK"] = Number::New(env, LZMA_GET_CHECK); 29 | exports["MEM_ERROR"] = Number::New(env, LZMA_MEM_ERROR); 30 | exports["MEMLIMIT_ERROR"] = Number::New(env, LZMA_MEMLIMIT_ERROR); 31 | exports["FORMAT_ERROR"] = Number::New(env, LZMA_FORMAT_ERROR); 32 | exports["OPTIONS_ERROR"] = Number::New(env, LZMA_OPTIONS_ERROR); 33 | exports["DATA_ERROR"] = Number::New(env, LZMA_DATA_ERROR); 34 | exports["BUF_ERROR"] = Number::New(env, LZMA_BUF_ERROR); 35 | exports["PROG_ERROR"] = Number::New(env, LZMA_PROG_ERROR); 36 | 37 | // enum lzma_action 38 | exports["RUN"] = Number::New(env, LZMA_RUN); 39 | exports["SYNC_FLUSH"] = Number::New(env, LZMA_SYNC_FLUSH); 40 | exports["FULL_FLUSH"] = Number::New(env, LZMA_FULL_FLUSH); 41 | exports["FINISH"] = Number::New(env, LZMA_FINISH); 42 | 43 | // enum lzma_check 44 | exports["CHECK_NONE"] = Number::New(env, LZMA_CHECK_NONE); 45 | exports["CHECK_CRC32"] = Number::New(env, LZMA_CHECK_CRC32); 46 | exports["CHECK_CRC64"] = Number::New(env, LZMA_CHECK_CRC64); 47 | exports["CHECK_SHA256"] = Number::New(env, LZMA_CHECK_SHA256); 48 | 49 | // lzma_match_finder 50 | exports["MF_HC3"] = Number::New(env, LZMA_MF_HC3); 51 | exports["MF_HC4"] = Number::New(env, LZMA_MF_HC4); 52 | exports["MF_BT2"] = Number::New(env, LZMA_MF_BT2); 53 | exports["MF_BT3"] = Number::New(env, LZMA_MF_BT3); 54 | exports["MF_BT4"] = Number::New(env, LZMA_MF_BT4); 55 | 56 | // lzma_mode 57 | exports["MODE_FAST"] = Number::New(env, LZMA_MODE_FAST); 58 | exports["MODE_NORMAL"] = Number::New(env, LZMA_MODE_NORMAL); 59 | 60 | // defines 61 | exports["FILTER_X86"] = String::New(env, "LZMA_FILTER_X86"); 62 | exports["FILTER_POWERPC"] = String::New(env, "LZMA_FILTER_POWERPC"); 63 | exports["FILTER_IA64"] = String::New(env, "LZMA_FILTER_IA64"); 64 | exports["FILTER_ARM"] = String::New(env, "LZMA_FILTER_ARM"); 65 | exports["FILTER_ARMTHUMB"] = String::New(env, "LZMA_FILTER_ARMTHUMB"); 66 | exports["FILTER_SPARC"] = String::New(env, "LZMA_FILTER_SPARC"); 67 | exports["FILTER_DELTA"] = String::New(env, "LZMA_FILTER_DELTA"); 68 | exports["FILTERS_MAX"] = String::New(env, "LZMA_FILTERS_MAX"); 69 | exports["FILTER_LZMA1"] = String::New(env, "LZMA_FILTER_LZMA1"); 70 | exports["FILTER_LZMA2"] = String::New(env, "LZMA_FILTER_LZMA2"); 71 | exports["VLI_UNKNOWN"] = String::New(env, "LZMA_VLI_UNKNOWN"); 72 | 73 | exports["VLI_BYTES_MAX"] = Number::New(env, LZMA_VLI_BYTES_MAX); 74 | exports["CHECK_ID_MAX"] = Number::New(env, LZMA_CHECK_ID_MAX); 75 | exports["CHECK_SIZE_MAX"] = Number::New(env, LZMA_CHECK_SIZE_MAX); 76 | exports["PRESET_DEFAULT"] = Number::New(env, LZMA_PRESET_DEFAULT); 77 | exports["PRESET_LEVEL_MASK"] = Number::New(env, LZMA_PRESET_LEVEL_MASK); 78 | exports["PRESET_EXTREME"] = Number::New(env, LZMA_PRESET_EXTREME); 79 | exports["TELL_NO_CHECK"] = Number::New(env, LZMA_TELL_NO_CHECK); 80 | exports["TELL_UNSUPPORTED_CHECK"] = Number::New(env, LZMA_TELL_UNSUPPORTED_CHECK); 81 | exports["TELL_ANY_CHECK"] = Number::New(env, LZMA_TELL_ANY_CHECK); 82 | exports["CONCATENATED"] = Number::New(env, LZMA_CONCATENATED); 83 | exports["STREAM_HEADER_SIZE"] = Number::New(env, LZMA_STREAM_HEADER_SIZE); 84 | exports["VERSION_MAJOR"] = Number::New(env, LZMA_VERSION_MAJOR); 85 | exports["VERSION_MINOR"] = Number::New(env, LZMA_VERSION_MINOR); 86 | exports["VERSION_PATCH"] = Number::New(env, LZMA_VERSION_PATCH); 87 | exports["VERSION_STABILITY"] = Number::New(env, LZMA_VERSION_STABILITY); 88 | exports["VERSION_STABILITY_ALPHA"] = Number::New(env, LZMA_VERSION_STABILITY_ALPHA); 89 | exports["VERSION_STABILITY_BETA"] = Number::New(env, LZMA_VERSION_STABILITY_BETA); 90 | exports["VERSION_STABILITY_STABLE"] = Number::New(env, LZMA_VERSION_STABILITY_STABLE); 91 | exports["VERSION"] = Number::New(env, LZMA_VERSION); 92 | exports["VERSION_STRING"] = String::New(env, LZMA_VERSION_STRING); 93 | 94 | exports["asyncCodeAvailable"] = Boolean::New(env, true); 95 | return exports; 96 | } 97 | 98 | NODE_API_MODULE(lzma_native, moduleInit) 99 | 100 | -------------------------------------------------------------------------------- /src/mt-options.cpp: -------------------------------------------------------------------------------- 1 | #include "liblzma-node.hpp" 2 | 3 | namespace lzma { 4 | 5 | MTOptions::MTOptions(Value val) { 6 | Object opt = val.IsUndefined() || val.IsNull() ? 7 | Object::New(val.Env()) : val.ToObject(); 8 | opts_.flags = 0; 9 | opts_.filters = nullptr; 10 | 11 | opts_.block_size = Value(opt["blockSize"]).ToNumber().Int64Value(); 12 | opts_.timeout = Value(opt["timeout"]).ToNumber().Uint32Value(); 13 | opts_.preset = Value(opt["preset"]).ToNumber().Uint32Value(); 14 | opts_.check = (lzma_check)Value(opt["check"]).ToNumber().Int32Value(); 15 | opts_.threads = Value(opt["threads"]).ToNumber().Uint32Value(); 16 | 17 | if (opts_.threads == 0) { 18 | opts_.threads = lzma_cputhreads(); 19 | } 20 | 21 | Value filters = opt["filters"]; 22 | if (filters.IsArray()) { 23 | filters_.reset(new FilterArray(filters)); 24 | opts_.filters = filters_->array(); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | #include "liblzma-node.hpp" 2 | #include 3 | 4 | namespace lzma { 5 | 6 | lzma_vli FilterByName(Value name) { 7 | std::string cpp_string(name.ToString()); 8 | 9 | struct SearchEntry { 10 | const char* str; 11 | lzma_vli value; 12 | }; 13 | 14 | static const struct SearchEntry search[] = { 15 | { "LZMA_FILTER_X86", LZMA_FILTER_X86 }, 16 | { "LZMA_FILTER_POWERPC", LZMA_FILTER_POWERPC }, 17 | { "LZMA_FILTER_IA64", LZMA_FILTER_IA64 }, 18 | { "LZMA_FILTER_ARM", LZMA_FILTER_ARM }, 19 | { "LZMA_FILTER_ARMTHUMB", LZMA_FILTER_ARMTHUMB }, 20 | { "LZMA_FILTER_SPARC", LZMA_FILTER_SPARC }, 21 | { "LZMA_FILTER_DELTA", LZMA_FILTER_DELTA }, 22 | { "LZMA_FILTER_LZMA1", LZMA_FILTER_LZMA1 }, 23 | { "LZMA_FILTER_LZMA2", LZMA_FILTER_LZMA2 }, 24 | { "LZMA_FILTERS_MAX", LZMA_FILTERS_MAX }, 25 | { "LZMA_VLI_UNKNOWN", LZMA_VLI_UNKNOWN } 26 | }; 27 | 28 | for (const struct SearchEntry* p = search; ; ++p) 29 | if (p->value == LZMA_VLI_UNKNOWN || cpp_string == p->str) 30 | return p->value; 31 | } 32 | 33 | Error lzmaRetError(Env env, lzma_ret rv) { 34 | struct ErrorInfo { 35 | lzma_ret code; 36 | const char* name; 37 | const char* desc; 38 | }; 39 | 40 | /* description strings taken from liblzma/…/api/base.h */ 41 | static const struct ErrorInfo searchErrorInfo[] = { 42 | { LZMA_OK, "LZMA_OK", "Operation completed successfully" }, 43 | { LZMA_STREAM_END, "LZMA_STREAM_END", "End of stream was reached" }, 44 | { LZMA_NO_CHECK, "LZMA_NO_CHECK", "Input stream has no integrity check" }, 45 | { LZMA_UNSUPPORTED_CHECK, "LZMA_UNSUPPORTED_CHECK", "Cannot calculate the integrity check" }, 46 | { LZMA_GET_CHECK, "LZMA_GET_CHECK", "Integrity check type is now available" }, 47 | { LZMA_MEM_ERROR, "LZMA_MEM_ERROR", "Cannot allocate memory" }, 48 | { LZMA_MEMLIMIT_ERROR, "LZMA_MEMLIMIT_ERROR", "Memory usage limit was reached" }, 49 | { LZMA_FORMAT_ERROR, "LZMA_FORMAT_ERROR", "File format not recognized" }, 50 | { LZMA_OPTIONS_ERROR, "LZMA_OPTIONS_ERROR", "Invalid or unsupported options" }, 51 | { LZMA_DATA_ERROR, "LZMA_DATA_ERROR", "Data is corrupt" }, 52 | { LZMA_PROG_ERROR, "LZMA_PROG_ERROR", "Programming error" }, 53 | { LZMA_BUF_ERROR, "LZMA_BUF_ERROR", "No progress is possible" }, 54 | { (lzma_ret)-1, "LZMA_UNKNOWN_ERROR", "Unknown error code" } 55 | }; 56 | 57 | const struct ErrorInfo* p = searchErrorInfo; 58 | while (p->code != rv && p->code != (lzma_ret)-1) 59 | ++p; 60 | 61 | Error err = Error::New(env, p->desc); 62 | err.Set("code", Number::New(env, rv)); 63 | err.Set("name", String::New(env, p->name)); 64 | err.Set("desc", String::New(env, p->desc)); 65 | 66 | return err; 67 | } 68 | 69 | Number lzmaRet(Env env, lzma_ret rv) { 70 | if (rv != LZMA_OK && rv != LZMA_STREAM_END) 71 | throw lzmaRetError(env, rv); 72 | 73 | return Number::New(env, rv); 74 | } 75 | 76 | bool readBufferFromObj(Value buf_, std::vector* data) { 77 | if (!buf_.IsTypedArray()) { 78 | throw TypeError::New(buf_.Env(), "Expected Buffer as input"); 79 | return false; 80 | } 81 | 82 | TypedArray buf = buf_.As(); 83 | size_t len = buf.ByteLength(); 84 | const uint8_t* ptr = len > 0 ? 85 | static_cast(buf.ArrayBuffer().Data()) + buf.ByteOffset() : 86 | reinterpret_cast(""); 87 | 88 | *data = std::vector(ptr, ptr + len); 89 | 90 | return true; 91 | } 92 | 93 | lzma_options_lzma parseOptionsLZMA (Value val) { 94 | HandleScope scope(val.Env()); 95 | Object obj = val.IsUndefined() || val.IsNull() ? 96 | Object::New(val.Env()) : val.ToObject(); 97 | 98 | lzma_options_lzma r; 99 | r.dict_size = GetIntegerProperty(obj, "dictSize", LZMA_DICT_SIZE_DEFAULT); 100 | r.lp = GetIntegerProperty(obj, "lp", LZMA_LP_DEFAULT); 101 | r.lc = GetIntegerProperty(obj, "lc", LZMA_LC_DEFAULT); 102 | r.pb = GetIntegerProperty(obj, "pb", LZMA_PB_DEFAULT); 103 | r.mode = (lzma_mode)GetIntegerProperty(obj, "mode", (int64_t)LZMA_MODE_FAST); 104 | r.nice_len = GetIntegerProperty(obj, "niceLen", 64); 105 | r.mf = (lzma_match_finder)GetIntegerProperty(obj, "mf", (int64_t)LZMA_MF_HC4); 106 | r.depth = GetIntegerProperty(obj, "depth", 0); 107 | uint64_t preset_ = GetIntegerProperty(obj, "preset", UINT64_MAX); 108 | 109 | r.preset_dict = nullptr; 110 | 111 | if (preset_ != UINT64_MAX) 112 | lzma_lzma_preset(&r, preset_); 113 | 114 | return r; 115 | } 116 | 117 | Value Uint64ToNumberMaxNull(Env env, uint64_t in) { 118 | if (in == UINT64_MAX) 119 | return env.Null(); 120 | else 121 | return Number::New(env, in); 122 | } 123 | 124 | Value Uint64ToNumber0Null(Env env, uint64_t in) { 125 | if (in == 0) 126 | return env.Null(); 127 | else 128 | return Number::New(env, in); 129 | } 130 | 131 | uint64_t NumberToUint64ClampNullMax(Value in) { 132 | if (in.IsNull() || in.IsUndefined()) 133 | return UINT64_MAX; 134 | 135 | Number n = in.ToNumber(); 136 | 137 | return n.Int64Value(); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /test/compat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | var util = require('util'); 6 | var helpers = require('./helpers.js'); 7 | 8 | var lzma = require('../'); 9 | 10 | describe('Compressor/Decompressor', function() { 11 | it('can compress', function(done) { 12 | var c = new lzma.Compressor(); 13 | 14 | c.on('finish', done); 15 | c.end('Hello!'); 16 | }); 17 | 18 | it('takes preset and options arguments', function(done) { 19 | var c = new lzma.Compressor(7, {synchronous: true}); 20 | 21 | c.on('finish', done); 22 | c.end('Bananas'); 23 | }); 24 | 25 | it('can round-trip', function(done) { 26 | var enc = new lzma.Compressor(); 27 | var dec = new lzma.Decompressor(); 28 | var outfile = 'test/random.lzma.unlzma'; 29 | var outstream = helpers.fsCreateWriteStream(outfile); 30 | 31 | outstream.on('finish', function() { 32 | assert.ok(helpers.bufferEqual(fs.readFileSync('test/random'), fs.readFileSync(outfile))); 33 | fs.unlinkSync(outfile); 34 | done(); 35 | }); 36 | 37 | fs.createReadStream('test/random').pipe(enc).pipe(dec).pipe(outstream); 38 | }); 39 | }); 40 | 41 | describe('LZMA.compress()/decompress()', function() { 42 | it('can compress strings to Buffers', function(done) { 43 | var LZMA = new lzma.LZMA(); 44 | 45 | LZMA.compress('Banana', 6, function(result) { 46 | assert.ok(Buffer.isBuffer(result)); 47 | assert.ok(result.length > 0); 48 | 49 | done(); 50 | }); 51 | }); 52 | 53 | it('can decompress integer arrays', function(done) { 54 | var LZMA = new lzma.LZMA(); 55 | 56 | LZMA.decompress( 57 | [0x5d, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x21, 0x18, 58 | 0x49, 0xc6, 0x24, 0x17, 0x18, 0x93, 0x42, 0x5f, 0xff, 0xfd, 0xa2, 0xd0, 0x00], function(result) { 59 | assert.ok(Buffer.isBuffer(result)); 60 | assert.equal(result.toString(), 'Banana'); 61 | 62 | done(); 63 | }); 64 | }); 65 | 66 | it('can decompress typed integer arrays', function(done) { 67 | var LZMA = new lzma.LZMA(); 68 | 69 | LZMA.decompress( 70 | new Uint8Array( 71 | [0x5d, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x21, 0x18, 72 | 0x49, 0xc6, 0x24, 0x17, 0x18, 0x93, 0x42, 0x5f, 0xff, 0xfd, 0xa2, 0xd0, 0x00]), function(result) { 73 | assert.ok(Buffer.isBuffer(result)); 74 | assert.equal(result.toString(), 'Banana'); 75 | 76 | done(); 77 | }); 78 | }); 79 | 80 | it('can round-trip', function(done) { 81 | var LZMA = new lzma.LZMA(); 82 | 83 | LZMA.compress('Bananas', 5, function(result) { 84 | assert.equal(result.toString('base64'), 'XQAAgAD//////////wAhGEnQgnOEP++//7v9AAA='); 85 | LZMA.decompress(result, function(result) { 86 | assert.ok(Buffer.isBuffer(result)); 87 | assert.equal(result.toString(), 'Bananas'); 88 | 89 | done(); 90 | }); 91 | }); 92 | }); 93 | 94 | it('can round-trip, even for compressed data which uses LZMA2', function(done) { 95 | var LZMA = new lzma.LZMA(); 96 | 97 | lzma.compress('Bananas', function(result) { 98 | LZMA.decompress(result, function(result) { 99 | assert.equal(result.toString(), 'Bananas'); 100 | 101 | done(); 102 | }); 103 | }); 104 | }); 105 | }); 106 | 107 | var BananasCompressed = '/Td6WFoAAAFpIt42AgAhARYAAAB0L+WjAQAGQmFuYW5hcwAA0aJr3wABGwcS69QXkEKZDQEAAAAAAVla'; 108 | describe('lzma.compress()/decompress()', function() { 109 | it('can round-trip', function(done) { 110 | lzma.compress('Bananas', 5, function(result) { 111 | assert.equal(result.toString('base64'), BananasCompressed); 112 | lzma.decompress(result, function(result) { 113 | assert.ok(Buffer.isBuffer(result)); 114 | assert.equal(result.toString(), 'Bananas'); 115 | 116 | done(); 117 | }); 118 | }); 119 | }); 120 | }); 121 | 122 | 123 | describe('lzma.compress()/decompress() with ES6 Promises', function() { 124 | assert(typeof Promise === 'function'); 125 | 126 | it('can round-trip', function() { 127 | return lzma.compress('Bananas', 5).then(function(result) { 128 | assert.equal(result.toString('base64'), BananasCompressed); 129 | return lzma.decompress(result); 130 | }).then(function(result) { 131 | assert.ok(Buffer.isBuffer(result)); 132 | assert.equal(result.toString(), 'Bananas'); 133 | }); 134 | }); 135 | 136 | it('fails for invalid input', function() { 137 | return lzma.decompress('ABC').then(function(result) { 138 | assert.ok(false); // never get here due to error 139 | }).catch(function(err) { 140 | assert.ok(err); 141 | }); 142 | }); 143 | }); 144 | 145 | describe('lzma.compress()/decompress() with util.promisify()', function() { 146 | var majorVersion = process.version.match(/^v(\d+)\./); 147 | if (majorVersion && +majorVersion[1] < 8) { 148 | return; 149 | } 150 | 151 | var compress = util.promisify(lzma.compress); 152 | var decompress = util.promisify(lzma.decompress); 153 | 154 | it('can round-trip', function() { 155 | return compress('Bananas', 5).then(function(result) { 156 | assert.equal(result.toString('base64'), BananasCompressed); 157 | return decompress(result); 158 | }).then(function(result) { 159 | assert.ok(Buffer.isBuffer(result)); 160 | assert.equal(result.toString(), 'Bananas'); 161 | }); 162 | }); 163 | 164 | it('fails for invalid input', function() { 165 | return decompress('ABC').then(function(result) { 166 | assert.ok(false); // never get here due to error 167 | }).catch(function(err) { 168 | assert.ok(err); 169 | }); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /test/functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | 6 | var lzma = require('../'); 7 | 8 | describe('lzma', function() { 9 | describe('#versionNumber', function() { 10 | it('should be present and of number type', function() { 11 | assert.ok(lzma.versionNumber()); 12 | assert.equal(typeof lzma.versionNumber(), 'number'); 13 | }); 14 | }); 15 | 16 | describe('#versionString', function() { 17 | it('should be present and of string type', function() { 18 | assert.ok(lzma.versionNumber()); 19 | assert.equal(typeof lzma.versionString(), 'string'); 20 | }); 21 | }); 22 | 23 | describe('#checkIsSupported', function() { 24 | it('should at least support no check and crc32', function() { 25 | assert.strictEqual(true, lzma.checkIsSupported(lzma.CHECK_NONE)); 26 | assert.strictEqual(true, lzma.checkIsSupported(lzma.CHECK_CRC32)); 27 | }); 28 | it('should return false for non-existing checks', function() { 29 | // -1 would be thee bitwise or of all possible checks 30 | assert.strictEqual(false, lzma.checkIsSupported(-1)); 31 | }); 32 | }); 33 | 34 | describe('#checkSize', function() { 35 | it('should be zero for CHECK_NONE', function() { 36 | assert.strictEqual(0, lzma.checkSize(lzma.CHECK_NONE)); 37 | }); 38 | 39 | it('should be non-zero for crc32', function() { 40 | assert.ok(lzma.checkSize(lzma.CHECK_CRC32) > 0); 41 | }); 42 | 43 | it('should be monotonous', function() { 44 | assert.ok(lzma.checkSize(lzma.CHECK_CRC32 | lzma.CHECK_SHA256) >= lzma.checkSize(lzma.CHECK_CRC32)); 45 | }); 46 | 47 | it('should be strictly monotonous if SHA256 is supported', function() { 48 | assert.ok(lzma.checkSize(lzma.CHECK_CRC32 | lzma.CHECK_SHA256) > lzma.checkSize(lzma.CHECK_CRC32) || 49 | !lzma.checkIsSupported(lzma.CHECK_SHA256)); 50 | }); 51 | }); 52 | 53 | var exampleSentenceWords = ['The ', 'quick ', 'brown ', 'fox ', 'jumps ', 'over ', 'the ', 'lazy ', 'dog']; 54 | var exampleSentence = exampleSentenceWords.join(''); 55 | 56 | describe('#crc32', function() { 57 | it('should be the standard CRC32 value for a few strings', function() { 58 | assert.strictEqual(0x00000000, lzma.crc32('')); 59 | assert.strictEqual(0x414fa339, lzma.crc32(exampleSentence)); 60 | assert.strictEqual(0x414fa339, lzma.crc32(Buffer.from(exampleSentence))); 61 | assert.strictEqual(0xafabd35e, lzma.crc32('crc32')); 62 | }); 63 | 64 | it('should allow cumulative calculation of the checksum', function() { 65 | var crc32Rev = function(prev, cur) { 66 | return lzma.crc32(cur, prev); 67 | }; 68 | 69 | assert.strictEqual(lzma.crc32(exampleSentence), 70 | exampleSentenceWords.reduce(crc32Rev, 0)); 71 | }); 72 | 73 | it('should fail if the input type is not obvious', function() { 74 | assert.throws(function() { 75 | lzma.crc32({some: 'object'}); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('#filterEncoderIsSupported', function() { 81 | it('should return true for LZMA1, LZMA2', function() { 82 | assert.strictEqual(true, lzma.filterEncoderIsSupported(lzma.FILTER_LZMA1)); 83 | assert.strictEqual(true, lzma.filterEncoderIsSupported(lzma.FILTER_LZMA2)); 84 | }); 85 | 86 | it('should return false for VLI_UNKNOWN', function() { 87 | assert.strictEqual(false, lzma.filterEncoderIsSupported(lzma.VLI_UNKNOWN)); 88 | }); 89 | 90 | it('should throw for objects which are not convertible to string', function() { 91 | var badObject = { toString: function() { throw Error('badObject.toString()'); } }; 92 | assert.throws(function() { lzma.filterEncoderIsSupported(badObject); }); 93 | }); 94 | }); 95 | 96 | describe('#filterDecoderIsSupported', function() { 97 | it('should return true for LZMA1, LZMA2', function() { 98 | assert.strictEqual(true, lzma.filterDecoderIsSupported(lzma.FILTER_LZMA1)); 99 | assert.strictEqual(true, lzma.filterDecoderIsSupported(lzma.FILTER_LZMA2)); 100 | }); 101 | 102 | it('should return false for VLI_UNKNOWN', function() { 103 | assert.strictEqual(false, lzma.filterDecoderIsSupported(lzma.VLI_UNKNOWN)); 104 | }); 105 | }); 106 | 107 | describe('#mfIsSupported', function() { 108 | it('should return true for MF_HC4', function() { 109 | assert.strictEqual(true, lzma.mfIsSupported(lzma.MF_HC4)); 110 | }); 111 | 112 | it('should return true for a wrong value', function() { 113 | assert.strictEqual(false, lzma.mfIsSupported(-1)); 114 | }); 115 | }); 116 | 117 | describe('#modeIsSupported', function() { 118 | it('should return true for LZMA_MODE_FAST', function() { 119 | assert.strictEqual(true, lzma.modeIsSupported(lzma.MODE_FAST)); 120 | }); 121 | 122 | it('should return true for a wrong value', function() { 123 | assert.strictEqual(false, lzma.modeIsSupported(-1)); 124 | }); 125 | }); 126 | 127 | describe('#lzmaFilterEncoderIsSupported', function() { 128 | it('should return true for and only for encoding-related filters', function() { 129 | assert.strictEqual(false, lzma.filterEncoderIsSupported()); 130 | assert.strictEqual(false, lzma.filterEncoderIsSupported(null)); 131 | assert.strictEqual(false, lzma.filterEncoderIsSupported('')); 132 | assert.strictEqual(false, lzma.filterEncoderIsSupported(lzma.LZMA_VLI_UNKNOWN)); 133 | assert.strictEqual(true, lzma.filterEncoderIsSupported(lzma.FILTER_LZMA1)); 134 | assert.strictEqual(true, lzma.filterEncoderIsSupported(lzma.FILTER_LZMA2)); 135 | assert.strictEqual(true, lzma.filterEncoderIsSupported(lzma.FILTERS_MAX)); 136 | assert.strictEqual(false, lzma.filterEncoderIsSupported(lzma.FILTERS_MAX+1)); 137 | 138 | assert.strictEqual('boolean', typeof lzma.filterEncoderIsSupported(lzma.FILTER_POWERPC)); 139 | assert.strictEqual('boolean', typeof lzma.filterEncoderIsSupported(lzma.FILTER_IA64)); 140 | assert.strictEqual('boolean', typeof lzma.filterEncoderIsSupported(lzma.FILTER_ARM)); 141 | assert.strictEqual('boolean', typeof lzma.filterEncoderIsSupported(lzma.FILTER_ARMTHUMB)); 142 | assert.strictEqual('boolean', typeof lzma.filterEncoderIsSupported(lzma.FILTER_SPARC)); 143 | assert.strictEqual('boolean', typeof lzma.filterEncoderIsSupported(lzma.FILTER_DELTA)); 144 | }); 145 | }); 146 | 147 | describe('#filterDecoderIsSupported', function() { 148 | it('should return true for and only for encoding-related filters', function() { 149 | assert.strictEqual(false, lzma.filterDecoderIsSupported()); 150 | assert.strictEqual(false, lzma.filterDecoderIsSupported(null)); 151 | assert.strictEqual(false, lzma.filterDecoderIsSupported('')); 152 | assert.strictEqual(false, lzma.filterDecoderIsSupported(lzma.LZMA_VLI_UNKNOWN)); 153 | assert.strictEqual(true, lzma.filterDecoderIsSupported(lzma.FILTER_LZMA1)); 154 | assert.strictEqual(true, lzma.filterDecoderIsSupported(lzma.FILTER_LZMA2)); 155 | assert.strictEqual(true, lzma.filterDecoderIsSupported(lzma.FILTERS_MAX)); 156 | assert.strictEqual(false, lzma.filterDecoderIsSupported(lzma.FILTERS_MAX+1)); 157 | 158 | assert.strictEqual('boolean', typeof lzma.filterDecoderIsSupported(lzma.FILTER_POWERPC)); 159 | assert.strictEqual('boolean', typeof lzma.filterDecoderIsSupported(lzma.FILTER_IA64)); 160 | assert.strictEqual('boolean', typeof lzma.filterDecoderIsSupported(lzma.FILTER_ARM)); 161 | assert.strictEqual('boolean', typeof lzma.filterDecoderIsSupported(lzma.FILTER_ARMTHUMB)); 162 | assert.strictEqual('boolean', typeof lzma.filterDecoderIsSupported(lzma.FILTER_SPARC)); 163 | assert.strictEqual('boolean', typeof lzma.filterDecoderIsSupported(lzma.FILTER_DELTA)); 164 | }); 165 | }); 166 | 167 | describe('#rawEncoderMemusage', function() { 168 | it('should be positive for LZMA1, LZMA2', function() { 169 | assert.ok(lzma.rawEncoderMemusage([{id: lzma.FILTER_LZMA1}]) > 0); 170 | assert.ok(lzma.rawEncoderMemusage([{id: lzma.FILTER_LZMA2}]) > 0); 171 | }); 172 | 173 | it('should return null for VLI_UNKNOWN', function() { 174 | assert.strictEqual(null, lzma.rawEncoderMemusage([{id: lzma.VLI_UNKNOWN}])); 175 | }); 176 | 177 | it('should be monotonous in the preset parameter', function() { 178 | for (var i = 1; i < 9; ++i) 179 | assert.ok(lzma.rawEncoderMemusage([{id: lzma.FILTER_LZMA2, preset: i+1}]) >= 180 | lzma.rawEncoderMemusage([{id: lzma.FILTER_LZMA2, preset: i}])); 181 | }); 182 | 183 | it('should fail if input is not an array of filter objects', function() { 184 | assert.throws(function() { lzma.rawEncoderMemusage(null); }); 185 | assert.throws(function() { lzma.rawEncoderMemusage([null]); }); 186 | }); 187 | }); 188 | 189 | describe('#rawDecoderMemusage', function() { 190 | it('should be positive for LZMA1, LZMA2', function() { 191 | assert.ok(lzma.rawDecoderMemusage([{id: lzma.FILTER_LZMA1}]) > 0); 192 | assert.ok(lzma.rawDecoderMemusage([{id: lzma.FILTER_LZMA2}]) > 0); 193 | }); 194 | 195 | it('should return null for VLI_UNKNOWN', function() { 196 | assert.strictEqual(null, lzma.rawDecoderMemusage([{id: lzma.VLI_UNKNOWN}])); 197 | }); 198 | 199 | it('should be monotonous in the preset parameter', function() { 200 | for (var i = 1; i < 9; ++i) 201 | assert.ok(lzma.rawDecoderMemusage([{id: lzma.FILTER_LZMA2, preset: i+1}]) >= 202 | lzma.rawDecoderMemusage([{id: lzma.FILTER_LZMA2, preset: i}])); 203 | }); 204 | 205 | it('should fail if input is not an array of filter objects', function() { 206 | assert.throws(function() { lzma.rawDecoderMemusage(null); }); 207 | assert.throws(function() { lzma.rawDecoderMemusage([null]); }); 208 | }); 209 | }); 210 | 211 | describe('#easyEncoderMemusage', function() { 212 | if('should be positive', function() { 213 | assert.ok(lzma.easyEncoderMemusage(1) > 0); 214 | }); 215 | 216 | it('should be monotonous in the preset parameter', function() { 217 | for (var i = 1; i < 9; ++i) 218 | assert.ok(lzma.easyEncoderMemusage(i+1) >= lzma.easyEncoderMemusage(i)); 219 | }); 220 | }); 221 | 222 | describe('#easyDecoderMemusage', function() { 223 | if('should be positive', function() { 224 | assert.ok(lzma.easyDecoderMemusage(1) > 0); 225 | }); 226 | 227 | it('should be monotonous in the preset parameter', function() { 228 | for (var i = 1; i < 9; ++i) 229 | assert.ok(lzma.easyDecoderMemusage(i+1) >= lzma.easyDecoderMemusage(i)); 230 | }); 231 | }); 232 | 233 | describe('#isXZ', function() { 234 | it('should correctly identify an XZ file', function() { 235 | assert.ok(lzma.isXZ(fs.readFileSync('test/hamlet.txt.xz'))); 236 | }); 237 | 238 | it('should fail for an LZMA file', function() { 239 | assert.ok(!lzma.isXZ(fs.readFileSync('test/hamlet.txt.lzma'))); 240 | }); 241 | 242 | it('should fail for invalid buffers', function() { 243 | assert.ok(!lzma.isXZ()); 244 | assert.ok(!lzma.isXZ([])); 245 | assert.ok(!lzma.isXZ(Buffer.alloc(1))); 246 | assert.ok(!lzma.isXZ('ASDFGHJKL')); 247 | }); 248 | }); 249 | 250 | /* meta stuff */ 251 | describe('.version', function() { 252 | it('should be the same as the package.json version', function() { 253 | var pj = JSON.parse(fs.readFileSync('package.json')); 254 | 255 | assert.strictEqual(pj.version, lzma.version); 256 | }); 257 | }); 258 | }); 259 | -------------------------------------------------------------------------------- /test/hamlet.txt.2stream.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/test/hamlet.txt.2stream.xz -------------------------------------------------------------------------------- /test/hamlet.txt.lzma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/test/hamlet.txt.lzma -------------------------------------------------------------------------------- /test/hamlet.txt.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/test/hamlet.txt.xz -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var util = require('util'); 5 | var stream = require('readable-stream'); 6 | 7 | function fsCreateWriteStream(filename) { 8 | var s = fs.createWriteStream(filename); 9 | if (process.version.match(/^v0.8/)) 10 | s.on('close', function() { s.emit('finish'); }); 11 | return s; 12 | } 13 | 14 | function bufferEqual(a, b) { 15 | /* The bl module does not expose array indexing for its instances, 16 | * however, Buffer.get is deprecated and will be removed. 17 | * (See https://github.com/nodejs/io.js/blob/60a974d200/lib/buffer.js#L425) 18 | * => All incoming objects will be coerced to Buffer */ 19 | if (!Buffer.isBuffer(a)) 20 | a = a.slice(); 21 | 22 | if (!Buffer.isBuffer(b)) 23 | b = b.slice(); 24 | 25 | if (a.length !== b.length) 26 | return false; 27 | 28 | for (var i = 0; i < a.length; ++i) { 29 | if (a[i] !== b[i]) 30 | return false; 31 | } 32 | 33 | return true; 34 | } 35 | 36 | function NullStream(options) { 37 | stream.Writable.call(this, options); 38 | } 39 | util.inherits(NullStream, stream.Writable); 40 | NullStream.prototype._write = function(chunk, encoding, callback) { 41 | callback(); 42 | }; 43 | 44 | exports.fsCreateWriteStream = fsCreateWriteStream; 45 | exports.bufferEqual = bufferEqual; 46 | exports.NullStream = NullStream; 47 | -------------------------------------------------------------------------------- /test/internals.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var lzma = require('../'); 6 | 7 | describe('lzma-native internals', function() { 8 | describe('#code', function() { 9 | it('should fail for non-buffer input', function() { 10 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 11 | 12 | stream.nativeStream.bufferHandler = function() {}; 13 | assert.throws(function() { stream.nativeStream.code('I am not a Buffer object'); }); 14 | }); 15 | }); 16 | 17 | describe('new/constructor', function() { 18 | it('can be called with `new`', function() { 19 | var stream = new lzma.Stream({synchronous: true}); 20 | assert.ok(stream.code); 21 | }); 22 | }); 23 | 24 | describe('crc32_', function() { 25 | it('Should fail when non-numeric previous values are supplied', function() { 26 | assert.throws(function() { 27 | return lzma.crc32_('Banana', 'Not numeric'); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/invalid.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/test/invalid.xz -------------------------------------------------------------------------------- /test/parse-index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | 6 | var lzma = require('../'); 7 | 8 | if (!assert.deepStrictEqual) { 9 | assert.deepStrictEqual = assert.deepEqual; 10 | } 11 | 12 | describe('lzma', function() { 13 | var checkInfo = function(info) { 14 | assert.strictEqual(info.memlimit, null); 15 | assert.strictEqual(info.streams, 2); 16 | assert.strictEqual(info.blocks, 2); 17 | assert.ok(info.fileSize < info.uncompressedSize); 18 | assert.deepStrictEqual(info.checks, [ lzma.CHECK_CRC64 ]); 19 | }; 20 | 21 | if (typeof gc !== 'undefined') { 22 | afterEach('garbage-collect', gc); 23 | } 24 | 25 | describe('#parseFileIndex', function() { 26 | var hamletXZ; 27 | before('Read from a buffer into memory', function() { 28 | hamletXZ = fs.readFileSync('test/hamlet.txt.2stream.xz'); 29 | }); 30 | 31 | it('should fail for zero-length files', function(done) { 32 | lzma.parseFileIndex({ 33 | fileSize: 0, 34 | read: function(count, offset, cb) { 35 | assert.ok(false); 36 | } 37 | }, function(err, info) { 38 | assert.ok(err); 39 | assert(/File is empty/.test(err.message)); 40 | 41 | done(); 42 | }); 43 | }); 44 | 45 | it('should fail for too-small files', function(done) { 46 | lzma.parseFileIndex({ 47 | fileSize: 10, 48 | read: function(count, offset, cb) { 49 | assert.ok(false); 50 | } 51 | }, function(err, info) { 52 | assert.ok(err); 53 | assert(/Too small/.test(err.message)); 54 | 55 | done(); 56 | }); 57 | }); 58 | 59 | it('should fail for truncated files', function(done) { 60 | lzma.parseFileIndex({ 61 | fileSize: hamletXZ.length, 62 | read: function(count, offset, cb) { 63 | cb(hamletXZ.slice(offset, offset + count - 1)); 64 | } 65 | }, function(err, info) { 66 | assert.ok(err); 67 | assert.strictEqual(err.name, 'LZMA_DATA_ERROR'); 68 | 69 | done(); 70 | }); 71 | }); 72 | 73 | it('should fail when I/O errors are passed along', function(done) { 74 | lzma.parseFileIndex({ 75 | fileSize: hamletXZ.length, 76 | read: function(count, offset, cb) { 77 | cb(new Error('I/O failed')); 78 | } 79 | }, function(err, info) { 80 | assert.ok(err); 81 | assert.strictEqual(err.message, 'I/O failed'); 82 | 83 | done(); 84 | }); 85 | }); 86 | 87 | it('should fail when I/O errors are passed along, sync version', function() { 88 | assert.throws(function() { 89 | lzma.parseFileIndex({ 90 | fileSize: hamletXZ.length, 91 | read: function(count, offset, cb) { 92 | cb(new Error('I/O failed')); 93 | } 94 | }); 95 | }, /I\/O failed/); 96 | }); 97 | 98 | it('should fail for invalid files', function(done) { 99 | lzma.parseFileIndex({ 100 | fileSize: hamletXZ.length, 101 | read: function(count, offset, cb) { 102 | var buf = Buffer.alloc(count); 103 | cb(buf); 104 | } 105 | }, function(err, info) { 106 | assert.ok(err); 107 | assert.strictEqual(err.name, 'LZMA_DATA_ERROR'); 108 | 109 | done(); 110 | }); 111 | }); 112 | 113 | it('should be able to parse a file synchronously', function(done) { 114 | lzma.parseFileIndex({ 115 | fileSize: hamletXZ.length, 116 | read: function(count, offset, cb) { 117 | cb(hamletXZ.slice(offset, offset + count)); 118 | } 119 | }, function(err, info) { 120 | if (err) return done(err); 121 | 122 | checkInfo(info); 123 | 124 | done(); 125 | }); 126 | }); 127 | 128 | it('should be able to parse a file with synchronous return', function() { 129 | var info = lzma.parseFileIndex({ 130 | fileSize: hamletXZ.length, 131 | read: function(count, offset, cb) { 132 | cb(hamletXZ.slice(offset, offset + count)); 133 | } 134 | }); 135 | 136 | checkInfo(info); 137 | }); 138 | 139 | it('should be able to parse a file asynchronously', function(done) { 140 | lzma.parseFileIndex({ 141 | fileSize: hamletXZ.length, 142 | read: function(count, offset, cb) { 143 | process.nextTick(function() { 144 | cb(null, hamletXZ.slice(offset, offset + count)); 145 | }); 146 | } 147 | }, function(err, info) { 148 | if (err) return done(err); 149 | 150 | checkInfo(info); 151 | 152 | done(); 153 | }); 154 | }); 155 | 156 | it('should be able to parse a file asynchronously, alternative callback style', function(done) { 157 | lzma.parseFileIndex({ 158 | fileSize: hamletXZ.length, 159 | read: function(count, offset, cb) { 160 | process.nextTick(function() { 161 | cb(hamletXZ.slice(offset, offset + count)); 162 | }); 163 | } 164 | }, function(err, info) { 165 | if (err) return done(err); 166 | 167 | checkInfo(info); 168 | 169 | done(); 170 | }); 171 | }); 172 | }); 173 | 174 | describe('#parseFileIndexFD', function() { 175 | var fd; 176 | before('Open the file', function(done) { 177 | fs.open('test/hamlet.txt.2stream.xz', 'r', function(err, fd_) { 178 | fd = fd_; 179 | done(err); 180 | }); 181 | }); 182 | 183 | after('Close the file', function(done) { 184 | fs.close(fd, done); 185 | }); 186 | 187 | it('should be able to parse a file from a file descriptor', function(done) { 188 | lzma.parseFileIndexFD(fd, function(err, info) { 189 | if (err) return done(err); 190 | 191 | checkInfo(info); 192 | done(); 193 | }); 194 | }); 195 | 196 | it('should fail for invalid file descriptors', function(done) { 197 | lzma.parseFileIndexFD(1000, function(err, info) { 198 | assert.ok(err); 199 | assert.ok(!info); 200 | done(); 201 | }); 202 | }); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /test/random: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/test/random -------------------------------------------------------------------------------- /test/random-large: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addaleax/lzma-native/287f5ae4daf8240f9d8f73a9908264bb1d325480/test/random-large -------------------------------------------------------------------------------- /test/readme-examples.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | 'use strict'; 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var assert = require('assert'); 8 | var helpers = require('./helpers.js'); 9 | 10 | var lzma = require('../'); 11 | 12 | /* 13 | * This test module looks for code snippets of a certain format in the README 14 | * file for this repository and runs them (possibly modified to be better 15 | * suited for testing). 16 | * 17 | * It greps the README file for code blocks enclosed in "```" which are 18 | * preceded by HTML comments of the approximate format 19 | * 20 | * and replaces certain references to standard library objects 21 | * (e.g. process.stdin or console.log), then runs the test code. 22 | */ 23 | 24 | describe('Example code in README', function() { 25 | var snippets = []; 26 | var testSnippedRE = /[^`]*```(?:js)?\n((?:[^`]|\n)+)```/mgi; 27 | 28 | var README = fs.readFileSync(path.join(__dirname, '../README.md'), 'utf-8'); 29 | 30 | var match; 31 | while (match = testSnippedRE.exec(README)) 32 | snippets.push({ name: match[1], code: match[2] }); 33 | 34 | var identity = function(v) { return v; }; 35 | 36 | describe('Should correctly run all test scripts', function() { 37 | for (var i = 0; i < snippets.length; i++) { (function() { // jshint ignore:line 38 | var code = snippets[i].code; 39 | 40 | code = code.replace(/var lzma = [^;]+;/mg, ''); 41 | code = code.replace(/console\.(log|error|warn|trace)/g, 'identity'); 42 | code = code.replace(/process\.stdin/g, 'fs.createReadStream("test/random")'); 43 | code = code.replace(/process\.stdout/g, '(new helpers.NullStream())'); 44 | 45 | it('Should run README test script: ' + snippets[i].name, function() { 46 | return eval(code); // jshint ignore:line 47 | }); 48 | })(); } // jshint ignore:line 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/regression-1.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lzma = require('../'); 4 | 5 | // this test relies on mocha’s 2000 ms test timeout (only in case it fails, of course) 6 | describe('regression-#1', function() { 7 | it('should perform correctly', function(done) { 8 | var complete = 0; 9 | var N = 4; 10 | 11 | for (var i = 0; i < N; ++i) { 12 | lzma.compress("", function() { // jshint ignore:line 13 | if (++complete === N) 14 | done(); 15 | }); // jshint ignore:line 16 | } 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/regression-53.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lzma = require('../'); 4 | var fs = require('fs'); 5 | var assert = require('assert'); 6 | 7 | describe('regression-#53', function() { 8 | it('should perform correctly', function(done) { 9 | var input = fs.readFileSync('test/invalid.xz'); 10 | 11 | lzma.decompress(input, function(result, error) { 12 | assert.strictEqual(result, null); 13 | assert.strictEqual(error.name, 'LZMA_DATA_ERROR'); 14 | done(); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/regression-7.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lzma = require('../'); 4 | var fs = require('fs'); 5 | var bl = require('bl'); 6 | 7 | describe('regression-#7', function() { 8 | it('should perform correctly', function(done) { 9 | var input = fs.createReadStream('test/random-large'); 10 | var compressor = lzma.createCompressor({synchronous:true}); 11 | 12 | input.pipe(compressor).pipe(bl(done)); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | var bl = require('bl'); 6 | var helpers = require('./helpers.js'); 7 | 8 | var lzma = require('../'); 9 | 10 | describe('LZMAStream', function() { 11 | var random_data, x86BinaryData, hamlet, largeRandom; 12 | 13 | function encodeAndDecode(enc, dec, done, data) { 14 | data = data || random_data; 15 | 16 | data.duplicate().pipe(enc).pipe(dec).pipe(bl(function(err, buf) { 17 | assert.ok(helpers.bufferEqual(data, buf)); 18 | done(err); 19 | })); 20 | } 21 | 22 | before('read random test data', function(done) { 23 | random_data = bl(done); 24 | fs.createReadStream('test/random').pipe(random_data); 25 | }); 26 | 27 | before('read large random test data', function(done) { 28 | largeRandom = bl(done); 29 | fs.createReadStream('test/random-large').pipe(largeRandom); 30 | }); 31 | 32 | before('read hamlet.txt test data', function(done) { 33 | hamlet = bl(done); 34 | fs.createReadStream('test/hamlet.txt.xz').pipe(lzma.createDecompressor()).pipe(hamlet); 35 | }); 36 | 37 | before('read an executable file', function(done) { 38 | /* process.execPath is e.g. /usr/bin/node 39 | * it does not matter for functionality testing whether 40 | * this is actually x86 code, only for compression ratio */ 41 | x86BinaryData = bl(function() { 42 | x86BinaryData = bl(x86BinaryData.slice(0, 20480)); 43 | return done(); 44 | }); 45 | 46 | fs.createReadStream(process.execPath).pipe(x86BinaryData); 47 | }); 48 | 49 | describe('#autoDecoder', function() { 50 | it('should be able to decode .lzma in async mode', function(done) { 51 | var stream = lzma.createStream('autoDecoder'); 52 | stream.on('end', done); 53 | stream.on('data', function() {}); 54 | 55 | if (lzma.asyncCodeAvailable) 56 | assert.ok(!stream.synchronous); 57 | 58 | fs.createReadStream('test/hamlet.txt.lzma').pipe(stream); 59 | }); 60 | 61 | it('should be able to decode .xz in async mode', function(done) { 62 | var stream = lzma.createStream('autoDecoder'); 63 | stream.on('end', done); 64 | stream.on('data', function() {}); 65 | 66 | if (lzma.asyncCodeAvailable) 67 | assert.ok(!stream.synchronous); 68 | 69 | fs.createReadStream('test/hamlet.txt.xz').pipe(stream); 70 | }); 71 | 72 | it('should be able to decode .lzma in sync mode', function(done) { 73 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 74 | stream.on('end', done); 75 | stream.on('data', function() {}); 76 | 77 | assert.ok(stream.synchronous); 78 | 79 | fs.createReadStream('test/hamlet.txt.lzma').pipe(stream); 80 | }); 81 | 82 | it('should be able to decode .xz in sync mode', function(done) { 83 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 84 | stream.on('end', done); 85 | stream.on('data', function() {}); 86 | 87 | assert.ok(stream.synchronous); 88 | 89 | fs.createReadStream('test/hamlet.txt.xz').pipe(stream); 90 | }); 91 | 92 | it('should bark loudly when given non-decodable data in async mode', function(done) { 93 | var stream = lzma.createStream('autoDecoder'); 94 | var sawError = false; 95 | 96 | stream.on('error', function() { sawError = true; }); 97 | stream.on('end', function() { 98 | assert.ok(sawError); 99 | done(); 100 | }); 101 | stream.on('data', function() {}); 102 | 103 | fs.createReadStream('test/random').pipe(stream); 104 | }); 105 | 106 | it('should bark loudly when given non-decodable data in sync mode', function(done) { 107 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 108 | var sawError = false; 109 | 110 | stream.on('error', function() { sawError = true; }); 111 | stream.on('end', function() { 112 | assert.ok(sawError); 113 | done(); 114 | }); 115 | stream.on('data', function() {}); 116 | 117 | fs.createReadStream('test/random').pipe(stream); 118 | }); 119 | }); 120 | 121 | describe('#aloneEncoder', function() { 122 | it('should be undone by autoDecoder in async mode', function(done) { 123 | var enc = lzma.createStream('aloneEncoder'); 124 | var dec = lzma.createStream('autoDecoder'); 125 | encodeAndDecode(enc, dec, done); 126 | }); 127 | 128 | it('should be undone by aloneDecoder in async mode', function(done) { 129 | var enc = lzma.createStream('aloneEncoder'); 130 | var dec = lzma.createStream('aloneDecoder'); 131 | encodeAndDecode(enc, dec, done); 132 | }); 133 | 134 | it('should be undone by autoDecoder in sync mode', function(done) { 135 | var enc = lzma.createStream('aloneEncoder', {synchronous: true}); 136 | var dec = lzma.createStream('autoDecoder', {synchronous: true}); 137 | encodeAndDecode(enc, dec, done); 138 | }); 139 | 140 | it('should be undone by aloneDecoder in sync mode', function(done) { 141 | var enc = lzma.createStream('aloneEncoder', {synchronous: true}); 142 | var dec = lzma.createStream('aloneDecoder', {synchronous: true}); 143 | encodeAndDecode(enc, dec, done); 144 | }); 145 | }); 146 | 147 | describe('#easyEncoder', function() { 148 | [ 149 | {value: lzma.PRESET_EXTREME, name: 'e' }, 150 | {value: 0, name: '' }, 151 | ].map(function(presetFlag) { 152 | [ 153 | 1, 3, 4, 6, 7, 9 154 | ].map(function(preset) { // test only some presets 155 | [ 156 | { file: hamlet, name: 'Hamlet' }, 157 | { file: random_data, name: 'random test data' }, 158 | { file: largeRandom, name: 'large random test data' }, 159 | { file: x86BinaryData, name: 'x86 binary data' } 160 | ].map(function(entry) { 161 | [ 162 | { synchronous: true, name: 'sync' }, 163 | { synchronous: false, name: 'async' }, 164 | ].map(function(syncInfo) { 165 | var info = 'with ' + entry.name + ', preset = ' + preset + presetFlag.name; 166 | it('should be undone by autoDecoder in ' + syncInfo.name + ' mode ' + info, function(done) { 167 | if (preset >= 7 && process.env.APPVEYOR) { 168 | // Sometimes there’s not enough memory on AppVeyor machines. :-( 169 | this.skip(); 170 | return; 171 | } 172 | 173 | var enc = lzma.createStream('easyEncoder', { 174 | preset: preset | presetFlag.value, 175 | synchronous: syncInfo.synchronous 176 | }); 177 | 178 | var dec = lzma.createStream('autoDecoder'); 179 | 180 | encodeAndDecode(enc, dec, done, entry.file); 181 | }); 182 | }); 183 | }); 184 | }); 185 | }); 186 | 187 | it('should correctly encode the empty string in async MT mode', function(done) { 188 | var enc = lzma.createStream('easyEncoder', { threads: 2 }); 189 | var dec = lzma.createStream('autoDecoder'); 190 | encodeAndDecode(enc, dec, done, bl('')); 191 | }); 192 | 193 | it('should correctly encode the empty string in async MT mode with default threading', function(done) { 194 | var enc = lzma.createStream('easyEncoder', { threads: 0 }); 195 | var dec = lzma.createStream('autoDecoder'); 196 | encodeAndDecode(enc, dec, done, bl('')); 197 | }); 198 | 199 | it('should correctly encode the empty string in sync MT mode', function(done) { 200 | var enc = lzma.createStream('easyEncoder', { threads: 2, synchronous: true }); 201 | var dec = lzma.createStream('autoDecoder'); 202 | encodeAndDecode(enc, dec, done, bl('')); 203 | }); 204 | 205 | it('should correctly encode the empty string in async mode', function(done) { 206 | var enc = lzma.createStream('easyEncoder'); 207 | var dec = lzma.createStream('autoDecoder'); 208 | encodeAndDecode(enc, dec, done, bl('')); 209 | }); 210 | 211 | it('should correctly encode the empty string in sync mode', function(done) { 212 | var enc = lzma.createStream('easyEncoder', {synchronous: true}); 213 | var dec = lzma.createStream('autoDecoder', {synchronous: true}); 214 | encodeAndDecode(enc, dec, done, bl('')); 215 | }); 216 | 217 | it('should be reasonably fast for one big chunk', function(done) { 218 | // “node createData.js | xz -9 > /dev/null” takes about 120ms for me. 219 | this.timeout(360); // three times as long as the above shell pipeline 220 | var outstream = new helpers.NullStream(); 221 | outstream.on('finish', done); 222 | var enc = lzma.createStream('easyEncoder'); 223 | enc.pipe(outstream); 224 | var x = 0, y = 0, str = ''; 225 | for (var i = 0; i < 1000; ++i) { 226 | var data = {type: "position", x: x, y: y, i: i}; 227 | str += JSON.stringify(data) + ",\n"; 228 | x += (i * 101) % 307; 229 | y += (i * 211) % 307; 230 | } 231 | enc.end(str); 232 | }); 233 | 234 | it('should be reasonably fast for many small chunks', function(done) { 235 | // “node createData.js | xz -9 > /dev/null” takes about 120ms for me. 236 | this.timeout(360); // three times as long as the above shell pipeline 237 | var outstream = new helpers.NullStream(); 238 | outstream.on('finish', done); 239 | var enc = lzma.createStream('easyEncoder'); 240 | enc.pipe(outstream); 241 | var x = 0, y = 0; 242 | for (var i = 0; i < 1000; ++i) { 243 | var data = {type: "position", x: x, y: y, i: i}; 244 | enc.write(JSON.stringify(data) + ",\n"); 245 | x += (i * 101) % 307; 246 | y += (i * 211) % 307; 247 | } 248 | enc.end(); 249 | }); 250 | }); 251 | 252 | describe('#streamEncoder', function() { 253 | it('should be undone by autoDecoder in async mode using the x86 filter', function(done) { 254 | var enc = lzma.createStream('streamEncoder', { 255 | filters: [ 256 | { id: lzma.FILTER_X86 }, 257 | { id: lzma.FILTER_LZMA2 } 258 | ], 259 | check: lzma.CHECK_SHA256 260 | }); 261 | var dec = lzma.createStream('autoDecoder'); 262 | 263 | encodeAndDecode(enc, dec, done, x86BinaryData); 264 | }); 265 | 266 | it('should be undone by autoDecoder in sync mode using the x86 filter', function(done) { 267 | var enc = lzma.createStream('streamEncoder', { 268 | filters: [ 269 | { id: lzma.FILTER_X86 }, 270 | { id: lzma.FILTER_LZMA2 } 271 | ], 272 | check: lzma.CHECK_SHA256, 273 | synchronous: true 274 | }); 275 | var dec = lzma.createStream('autoDecoder', {synchronous: true}); 276 | 277 | encodeAndDecode(enc, dec, done, x86BinaryData); 278 | }); 279 | 280 | it('should be undone by autoDecoder in async mode using the x86 filter in MT mode', function(done) { 281 | var enc = lzma.createStream('streamEncoder', { 282 | filters: [ 283 | { id: lzma.FILTER_X86 }, 284 | { id: lzma.FILTER_LZMA2 } 285 | ], 286 | check: lzma.CHECK_SHA256, 287 | threads: 2 288 | }); 289 | var dec = lzma.createStream('autoDecoder'); 290 | 291 | encodeAndDecode(enc, dec, done, x86BinaryData); 292 | }); 293 | 294 | it('should be undone by autoDecoder in sync mode using the x86 filter in MT mode', function(done) { 295 | var enc = lzma.createStream('streamEncoder', { 296 | filters: [ 297 | { id: lzma.FILTER_X86 }, 298 | { id: lzma.FILTER_LZMA2 } 299 | ], 300 | check: lzma.CHECK_SHA256, 301 | synchronous: true, 302 | threads: 2 303 | }); 304 | var dec = lzma.createStream('autoDecoder', {synchronous: true}); 305 | 306 | encodeAndDecode(enc, dec, done, x86BinaryData); 307 | }); 308 | 309 | it('should be undone by streamDecoder in async mode using the delta filter', function(done) { 310 | var enc = lzma.createStream('streamEncoder', { 311 | filters: [ 312 | { id: lzma.FILTER_DELTA, options: { dist: 2 } }, 313 | { id: lzma.FILTER_LZMA2 } 314 | ], 315 | check: lzma.CHECK_SHA256 316 | }); 317 | var dec = lzma.createStream('streamDecoder'); 318 | 319 | encodeAndDecode(enc, dec, done, x86BinaryData); 320 | }); 321 | 322 | it('should be undone by streamDecoder in sync mode using the delta filter', function(done) { 323 | var enc = lzma.createStream('streamEncoder', { 324 | filters: [ 325 | { id: lzma.FILTER_DELTA, options: { dist: 2 } }, 326 | { id: lzma.FILTER_LZMA2 } 327 | ], 328 | check: lzma.CHECK_SHA256, 329 | synchronous: true 330 | }); 331 | var dec = lzma.createStream('streamDecoder', {synchronous: true}); 332 | 333 | encodeAndDecode(enc, dec, done, x86BinaryData); 334 | }); 335 | 336 | it('should fail for an invalid combination of filter objects', function() { 337 | assert.throws(function() { 338 | lzma.createStream('streamEncoder', { 339 | filters: [ 340 | {id: lzma.FILTER_LZMA2}, 341 | {id: lzma.FILTER_X86} 342 | ] 343 | }); 344 | }); 345 | }); 346 | 347 | it('should fail for filters which do not expect options', function() { 348 | assert.throws(function() { 349 | lzma.createStream('streamEncoder', { 350 | filters: [ 351 | { id: lzma.FILTER_X86, options: { Banana: 'Banana' } }, 352 | { id: lzma.FILTER_LZMA2 } 353 | ] 354 | }); 355 | }); 356 | }); 357 | }); 358 | 359 | describe('#streamDecoder', function() { 360 | it('should accept an memlimit argument', function() { 361 | var memlimit = 20 << 20; /* 20 MB */ 362 | var s = lzma.createStream('streamDecoder', { memlimit: memlimit, synchronous: true }); 363 | 364 | assert.strictEqual(s.memlimitGet(), memlimit); 365 | }); 366 | 367 | it('should fail when the memlimit argument is invalid', function() { 368 | assert.throws(function() { 369 | lzma.createStream('streamDecoder', { memlimit: 'ABC' }); 370 | }); 371 | }); 372 | }); 373 | 374 | describe('#rawEncoder', function() { 375 | var rawFilters = [ 376 | { id: lzma.FILTER_X86 }, 377 | { id: lzma.FILTER_LZMA2, options: { dictSize: 1 << 24 /* 16 MB */ } } 378 | ]; 379 | 380 | it('should be undone by rawDecoder in async mode', function(done) { 381 | var enc = lzma.createStream('rawEncoder', { filters: rawFilters }); 382 | var dec = lzma.createStream('rawDecoder', { filters: rawFilters }); 383 | 384 | encodeAndDecode(enc, dec, done); 385 | }); 386 | 387 | it('should be undone by rawDecoder in sync mode', function(done) { 388 | var enc = lzma.createStream('rawEncoder', { filters: rawFilters, synchronous: true }); 389 | var dec = lzma.createStream('rawDecoder', { filters: rawFilters, synchronous: true }); 390 | 391 | encodeAndDecode(enc, dec, done); 392 | }); 393 | }); 394 | 395 | describe('#createStream', function() { 396 | it('should work fine when synchronous streams are abandoned', function(done) { 397 | lzma.createStream({synchronous: true}); 398 | 399 | done(); 400 | }); 401 | 402 | it('should return streams which emit `finish` and `end` events', function(done) { 403 | var s = lzma.createStream(); 404 | var finished = false; 405 | var ended = false; 406 | 407 | var maybeDone = function() { 408 | if (finished && ended) 409 | done(); 410 | }; 411 | 412 | s.on('finish', function() { finished = true; maybeDone(); }); 413 | s.on('end', function() { ended = true; maybeDone(); }); 414 | s.on('data', function() {}); 415 | 416 | s.end(); 417 | }); 418 | 419 | it('should return errors with .code, .name and .desc properties', function(done) { 420 | try { 421 | lzma.createStream({check: lzma.CHECK_ID_MAX + 1}); 422 | } catch (e) { 423 | assert.ok(e.name); 424 | assert.ok(e.desc); 425 | 426 | done(); 427 | } 428 | }); 429 | }); 430 | 431 | describe('#memusage', function() { 432 | it('should return a meaningful value when decoding', function(done) { 433 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 434 | stream.on('end', done); 435 | stream.on('data', function() {}); 436 | 437 | fs.createReadStream('test/hamlet.txt.lzma').pipe(stream); 438 | assert.ok(stream.memusage() > 0); 439 | }); 440 | 441 | it('should return null when encoding', function() { 442 | var stream = lzma.createCompressor({synchronous: true}); 443 | 444 | assert.strictEqual(stream.memusage(), null); 445 | }); 446 | 447 | it('should fail when called with null or {} as the this object', function() { 448 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 449 | assert.throws(stream.nativeStream.memusage.bind(null)); 450 | assert.throws(stream.nativeStream.memusage.bind({})); 451 | }); 452 | }); 453 | 454 | describe('#memlimitGet/#memlimitSet', function() { 455 | it('should set values of memory limits', function(done) { 456 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 457 | stream.on('end', done); 458 | stream.on('data', function() {}); 459 | 460 | assert.ok(stream.memlimitGet() > 0); 461 | stream.memlimitSet(1 << 30); 462 | assert.equal(stream.memlimitGet(), 1 << 30); 463 | fs.createReadStream('test/hamlet.txt.lzma').pipe(stream); 464 | }); 465 | 466 | it('should fail for invalid memory limit specifications', function() { 467 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 468 | 469 | // use undefined because that’s never converted to Number 470 | assert.throws(function() { stream.memlimitSet(undefined); }); 471 | }); 472 | }); 473 | 474 | describe('#totalIn/#totalOut', function() { 475 | it('should return meaningful values during the coding process', function(done) { 476 | var stream = lzma.createStream('autoDecoder', {synchronous: true}); 477 | var valuesWereSet = false; 478 | 479 | stream.on('end', function() { 480 | assert(valuesWereSet); 481 | done(); 482 | }); 483 | 484 | stream.on('data', function() { 485 | valuesWereSet = valuesWereSet || stream.totalIn() > 0 && stream.totalOut() > 0; 486 | }); 487 | 488 | fs.createReadStream('test/hamlet.txt.lzma').pipe(stream); 489 | }); 490 | }); 491 | 492 | describe('bufsize', function() { 493 | it('Should only accept positive integers', function() { 494 | var stream = new lzma.createStream({synchronous: true}); 495 | 496 | assert.throws(function() { 497 | stream.bufsize = 'Not numeric'; 498 | }, /bufsize must be a positive number/); 499 | 500 | assert.throws(function() { 501 | stream.bufsize = 0; 502 | }, /bufsize must be a positive number/); 503 | 504 | assert.throws(function() { 505 | stream.bufsize = -65536; 506 | }, /bufsize must be a positive number/); 507 | }); 508 | 509 | it('Should default to 64k', function() { 510 | var stream = new lzma.createStream({synchronous: true}); 511 | 512 | assert.strictEqual(stream.bufsize, 65536); 513 | }); 514 | 515 | it('Should accept values from options', function() { 516 | var stream = new lzma.createStream({synchronous: true, bufsize: 16384}); 517 | 518 | assert.strictEqual(stream.bufsize, 16384); 519 | }); 520 | 521 | it('Should be overridable', function() { 522 | var stream = new lzma.createStream({synchronous: true}); 523 | 524 | stream.bufsize = 8192; 525 | assert.strictEqual(stream.bufsize, 8192); 526 | }); 527 | }); 528 | 529 | describe('multi-stream files', function() { 530 | var zeroes = Buffer.alloc(16); 531 | 532 | it('can be decoded by #autoDecoder', function(done) { 533 | var enc1 = lzma.createStream('easyEncoder', {synchronous: true}); 534 | var enc2 = lzma.createStream('easyEncoder', {synchronous: true}); 535 | var dec = lzma.createStream('autoDecoder', {synchronous: true}); 536 | 537 | dec.pipe(bl(function(err, buf) { 538 | assert.ifError(err); 539 | assert.strictEqual(buf.toString(), 'abcdef'); 540 | done(); 541 | })); 542 | 543 | enc1.pipe(dec, { end: false }); 544 | enc1.end('abc', function() { 545 | enc2.pipe(dec, { end: true }); 546 | enc2.end('def'); 547 | }); 548 | }); 549 | 550 | it('can be decoded by #autoDecoder with padding', function(done) { 551 | lzma.compress('abc', { synchronous: true }, function(abc, err) { 552 | assert.ifError(err); 553 | lzma.compress('def', { synchronous: true }, function(def, err) { 554 | assert.ifError(err); 555 | lzma.decompress(Buffer.concat([abc, zeroes, def]), { 556 | synchronous: true 557 | }, function(result, err) { 558 | assert.ifError(err); 559 | assert.strictEqual(result.toString(), 'abcdef'); 560 | done(); 561 | }); 562 | }); 563 | }); 564 | }); 565 | 566 | it('supports padding without multi-stream files', function(done) { 567 | lzma.compress('abc', { synchronous: true }, function(abc, err) { 568 | assert.ifError(err); 569 | lzma.decompress(Buffer.concat([abc, zeroes]), { 570 | synchronous: true 571 | }, function(result, err) { 572 | assert.ifError(err); 573 | assert.strictEqual(result.toString(), 'abc'); 574 | done(); 575 | }); 576 | }); 577 | }); 578 | }); 579 | 580 | after('should not have any open asynchronous streams', function() { 581 | if (typeof gc === 'function') 582 | gc(); 583 | 584 | assert.equal(lzma.Stream.curAsyncStreamsCount, 0); 585 | }); 586 | }); 587 | --------------------------------------------------------------------------------