first commit

This commit is contained in:
2025-10-10 18:00:07 -04:00
commit 06b59a3a99
3786 changed files with 571590 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
## [2.0.2](https://github.com/YuJianrong/node-unrar.js/compare/v2.0.1...v2.0.2) (2023-11-28)
### Bug Fixes
* remove the postinstall script which should not be executed when installed on user side ([#329](https://github.com/YuJianrong/node-unrar.js/issues/329)) ([16b4e3c](https://github.com/YuJianrong/node-unrar.js/commit/16b4e3c0fca8602392d7f492830867b05e762f4a)), closes [#324](https://github.com/YuJianrong/node-unrar.js/issues/324)
## [2.0.1](https://github.com/YuJianrong/node-unrar.js/compare/v2.0.0...v2.0.1) (2023-11-22)
### Bug Fixes
* fix husky blocker on semantic-release ([88cd80c](https://github.com/YuJianrong/node-unrar.js/commit/88cd80cd60cb5dd054235e4a0376d2ad2b745213))
* fix the case when file size is greater than 2G ([#323](https://github.com/YuJianrong/node-unrar.js/issues/323)) ([f81cacd](https://github.com/YuJianrong/node-unrar.js/commit/f81cacde37e4725a453563e9faa58e7932c23dcb))
#### 2.0.0 (2022-06-15)
- Add support for NodeJs v18
- **(Breaking change)**: `ArcFile.extraction` is optional for `createExtractorFromData` now. If `ArcFile.fileHeader.flags.directory` is `true`, this field will be `undefined`.
#### 1.0.6 (2022-03-16)
- Add `createExtractorFromFile` support in Webpack
#### 1.0.5 (2022-02-28)
- Fix an issue on file extracting to current working directory ('./')
#### 1.0.4 (2022-02-15)
- Fix folder creation issue on Windows.
#### 1.0.3 (2021-05-10)
- Fix for Security Vulnerability on dependencies
#### 1.0.2 (2021-04-01)
- Fix for Security Vulnerability on dependencies
#### 1.0.1 (2021-02-18)
- Add new option `filenameTransform` for `ExtractorFromFileOptions`.
#### 1.0.0 (2020-12-29)
- Refactory by the latest EMScripten and UnRar DLL source. Since both of them changed a lot, there are many breaking changes in this update
- **(Breaking change)**: Use Javascript Exception instead of tuple `[state, result]` for error handling.
- **(Breaking change)**: The extractor object return by `createExtractorFromData` and `createExtractorFromFile` are now promise object. Since loading the wasm object in the latest EMScriten is asynchronized.
- **(Breaking change)**: The `fileHeaders` and `files` returned by the extractions api are changed from array to iterator, make the extraction lazy.
#### 0.8.0
- First release
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2017 Jianrong Yu.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+230
View File
@@ -0,0 +1,230 @@
# node-unrar-js
[![Build Status](https://github.com/YuJianrong/node-unrar.js/workflows/Build/badge.svg)](https://github.com/YuJianrong/node-unrar.js/actions?query=workflow%3ABuild)
[![npm version](https://badge.fury.io/js/node-unrar-js.svg)](https://badge.fury.io/js/node-unrar-js)
[![MIT License](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php)
[![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.svg?v=101)](https://www.typescriptlang.org/)
`node-unrar.js` is a npm module to extract rar archive in pure JavaScript. It's combined by a Javascript adoption layer and JavaScript unRar library compiled by [Emscripten](http://emscripten.org/) from the [C++ unrar library](http://www.rarlab.com/rar/unrarsrc-6.1.7.tar.gz) which hosted on <http://www.rarlab.com/rar_add.htm> .
## Installation
You can install the module via `npm`:
```bash
npm install node-unrar-js
```
## Features
- Fully support for RAR archive, because it comes from the official source code.
- Unicode support, for both comment and file path/name.
- API for Extraction to both memory and file system.
- Both Commonjs module (for nodejs) and ES2015 module (for webpack) are supported.
## Unsupported Features
- Volume archives are not supported.
- Synchronize File System IO functions are used in File Extraction.
## API to create the extractor
- `async function createExtractorFromData(options: ExtractorFromDataOptions): Promise<Extractor<Uint8Array>>` - Create the in Memory Extractor
Options `ExtractorFromDataOptions`:
- `data: ArrayBuffer` : ArrayBuffer object of the RAR archive file
- `password?: string` : _Optional_ password
- `wasmBinary? ArrayBuffer;` : _Optional_ Use in browser/webpack, the wasm binary must be loaded in the code and send to this function to load the wasm code
- `async function createExtractorFromFile(options: ExtractorFromFileOptions): Promise<Extractor>` - Get the File Extractor
Options `ExtractorFromFileOptions`:
- `filepath: string` : File path of the RAR archive file
- `targetPath?: string` : _Optional_ target folder
- `password?: string` : _Optional_ password
- `filenameTransform?: (filename: string) => string`: _Optional_ transform the file name before it's created on file system
- `wasmBinary? ArrayBuffer;` : _Optional_ Use in nodejs/webpack, the wasm binary must be loaded in the code and send to this function to load the wasm code in webpack based nodejs project (please read [Used in Webpack-bundled NodeJS Project](#use-in-webpack-bundled-nodejs-project) for more details).
_Node_: This function is not available in EM2015 Module since the EM2015 Module is used for webpack in Browser.
## API of the extractor
- `getFileList(): ArcList` : Get the file header and file list of the archive.
Members in `ArcList`:
- `arcHeader: ArcHeader` : The header of the archive
- `fileHeaders: Generator<FileHeader>` : The iterator of the `FileHeader` objects
```js
{
arcHeader: {
comment: "",
flags: {
authInfo: false,
headerEncrypted: false,
lock: false,
recoveryRecord: false,
solid: false,
volume: false,
},
}, fileHeaders: (Iterator)
{
crc: 0,
flags: {
directory: false,
encrypted: false,
solid: false,
},
method: "Storing",
name: "FileName",
packSize: 0,
time: "2017-04-03T10:41:42.000",
unpSize: 0,
unpVer: "2.9",
comment: "",
},
}
```
- `extract(options: ExtractOptions)`: Extract the files.
- Options `ExtractOptions`:
- `files?: string[] | ((fileHeader: FileHeader) => boolean)` _Optional_ Extract all the files if `files` is empty
- `string[]`: Extract the specific files only
- `(fileHeader: FileHeader) => boolean`: Extract only the filtered file (_eg. extract only the files without password_).
- `password?: string`: _Optional_ password for the extracted files only (Different password can be applied to any single file in RAR archive).
- The return values are different for `createExtractorFromData` and `createExtractorFromFile`:
- `ArcFiles<Uint8Array>` for `createExtractorFromData`
- `ArcFiles` for `createExtractorFromFile`
- Members in `ArcFiles`:
- `arcHeader: ArcHeader` : The header of the archive
- `files: Generator<ArcFile>` : The iterator of the `ArcFile` objects
- Members in `ArcFile`:
- `fileHeader: FileHeader` : The header of the extracted file
- `extraction?: Uint8Array` : The extracted content of the file (`createExtractorFromData` only). If the `ArcFile` is a folder (`ArcFile.fileHeader.flags.directory` is `true`), the `extraction` will be undefined, otherwise it will be the content of the file (in `Uint8Array).
```js
{
arcHeader: {...} // Same as the arcHeader above
files: (Iterator)
{
fileHeader: {...} // Same as the fileHeader above
extraction?: Uint8Array // createExtractorFromData only
]
}
```
**Note: The returned iterators from the two apis are lazy, it means the file header/content will not be parsed/extracted if any file is not iterated yet.**
## Exception
The customized Error Object `UnrarError` will be thrown for any exception in extracting. The definition of the Object is:
```Typescript
class UnrarError extends Error {
reason: FailReason;
file?: string | undefined; // Will be filled for any exception in extraction of a file
}
```
The following code is used in the `FailReason`:
| FailReason | Message |
| --------------------- | ---------------------------------------------------------- |
| ERAR_NO_MEMORY | Not enough memory |
| ERAR_BAD_DATA | Archive header or data are damaged |
| ERAR_BAD_ARCHIVE | File is not RAR archive |
| ERAR_UNKNOWN_FORMAT | Unknown archive format |
| ERAR_EOPEN | File open error |
| ERAR_ECREATE | File create error |
| ERAR_ECLOSE | File close error |
| ERAR_EREAD | File read error |
| ERAR_EWRITE | File write error |
| ERAR_SMALL_BUF | Buffer for archive comment is too small, comment truncated |
| ERAR_UNKNOWN | Unknown error |
| ERAR_MISSING_PASSWORD | Password for encrypted file or header is not specified |
| ERAR_EREFERENCE | Cannot open file source for reference record |
| ERAR_BAD_PASSWORD | Wrong password is specified |
## Memory Leak
Note: although the return value `fileHeaders` and `files` are iterators, they must be traversed to the end! Otherwise the C++ object for archive extraction will not be destructed and cause memory leak.
## Example
```js
const fs = require("fs");
const unrar = require("node-unrar-js");
async function main() {
// Read the archive file into a typedArray
const buf = Uint8Array.from(fs.readFileSync("a.rar")).buffer;
const extractor = await unrar.createExtractorFromData({ data: buf });
const list = extractor.getFileList();
const listArcHeader = list.arcHeader; // archive header
const fileHeaders = [...list.fileHeaders]; // load the file headers
const extracted = extractor.extract({ files: ["1.txt"] });
// extracted.arcHeader : archive header
const files = [...extracted.files]; //load the files
files[0].fileHeader; // file header
files[0].extraction; // Uint8Array content, createExtractorFromData only
}
main();
```
## Demo in Webpack
This package can also be used in browser by Webpack, please visit the [demo project](https://github.com/YuJianrong/node-unrar.js/tree/master/demo/web) to see how to use it in webpack.
## Use in Webpack-bundled NodeJS Project
In most cases the exported ESModule is used in browser by Webpack, but in case if the NodeJs project (or an Electron project) is bundled by Webpack, the `wasmBinary` data must be loaded manually just like the browser by Webpack, it can be loaded like this:
```Typescript
import fs from 'fs';
import { createExtractorFromFile } from 'node-unrar-js/esm';
const wasmBinary = fs.readFileSync(require.resolve('node-unrar-js/esm/js/unrar.wasm'));
const extractor = await createExtractorFromFile({ wasmBinary, filepath: './WithComment.rar' });
// const list = extractor.getFileList();
```
Note: the package must be loaded from `'node-unrar-js/esm'` instead of `'node-unrar-js'` to enable the function `createExtractorFromFile` in ES Module.
## TypeScript
This module is written in [TypeScript](https://www.typescriptlang.org/), you can import it directly in TypeScript and get the benefit of static type checking and auto-complete of IDE.
## Development and Contribution
If you want to compile the module by yourself, please follow the steps below:
- Install the [docker](https://www.docker.com/)
- Download the c++ source of unRar library by:
`npm run downloadUnrarSrc`
- Build for debug:
`npm run build:debug`
- Build for release
`npm run build:release`
- Run Test
`npm run test`
## License
This module is licensed under MIT.
### [Changelog](https://github.com/YuJianrong/node-unrar.js/blob/master/CHANGELOG.md)
+10
View File
@@ -0,0 +1,10 @@
import { Extractor } from './js/Extractor';
export * from './index.esm';
export interface ExtractorFromFileOptions {
wasmBinary?: ArrayBuffer;
filepath: string;
targetPath?: string;
password?: string;
filenameTransform?: (filename: string) => string;
}
export declare function createExtractorFromFile({ wasmBinary, filepath, targetPath, password, filenameTransform, }: ExtractorFromFileOptions): Promise<Extractor>;
+8
View File
@@ -0,0 +1,8 @@
import { Extractor } from './js/Extractor';
export * from './js/Extractor';
export interface ExtractorFromDataOptions {
wasmBinary?: ArrayBuffer;
data: ArrayBuffer;
password?: string;
}
export declare function createExtractorFromData({ wasmBinary, data, password, }: ExtractorFromDataOptions): Promise<Extractor<Uint8Array>>;
+28
View File
@@ -0,0 +1,28 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createExtractorFromData = void 0;
const ExtractorData_1 = require("./js/ExtractorData");
const unrar_singleton_1 = require("./js/unrar.singleton");
__exportStar(require("./js/Extractor"), exports);
async function createExtractorFromData({ wasmBinary, data, password = '', }) {
const unrar = await (0, unrar_singleton_1.getUnrar)(wasmBinary && { wasmBinary });
const extractor = new ExtractorData_1.ExtractorData(unrar, data, password);
unrar.extractor = extractor;
return extractor;
}
exports.createExtractorFromData = createExtractorFromData;
//# sourceMappingURL=index.esm.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.esm.js","sourceRoot":"","sources":["../src/index.esm.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AACA,sDAAmD;AACnD,0DAAgD;AAChD,iDAA+B;AAQxB,KAAK,UAAU,uBAAuB,CAAC,EAC5C,UAAU,EACV,IAAI,EACJ,QAAQ,GAAG,EAAE,GACY;IACzB,MAAM,KAAK,GAAG,MAAM,IAAA,0BAAQ,EAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,6BAAa,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3D,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,OAAO,SAAS,CAAC;AACnB,CAAC;AATD,0DASC"}
+28
View File
@@ -0,0 +1,28 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createExtractorFromFile = void 0;
const ExtractorFile_1 = require("./js/ExtractorFile");
const unrar_singleton_1 = require("./js/unrar.singleton");
__exportStar(require("./index.esm"), exports);
async function createExtractorFromFile({ wasmBinary, filepath, targetPath = '', password = '', filenameTransform = (filename) => filename, }) {
const unrar = await (0, unrar_singleton_1.getUnrar)(wasmBinary && { wasmBinary });
const extractor = new ExtractorFile_1.ExtractorFile(unrar, filepath, targetPath, password, filenameTransform);
unrar.extractor = extractor;
return extractor;
}
exports.createExtractorFromFile = createExtractorFromFile;
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AACA,sDAAmD;AACnD,0DAAgD;AAChD,8CAA4B;AAUrB,KAAK,UAAU,uBAAuB,CAAC,EAC5C,UAAU,EACV,QAAQ,EACR,UAAU,GAAG,EAAE,EACf,QAAQ,GAAG,EAAE,EACb,iBAAiB,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,QAAQ,GACzB;IACzB,MAAM,KAAK,GAAG,MAAM,IAAA,0BAAQ,EAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,6BAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAC9F,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,OAAO,SAAS,CAAC;AACnB,CAAC;AAXD,0DAWC"}
+91
View File
@@ -0,0 +1,91 @@
export type SeekMethod = 'CUR' | 'SET' | 'END';
declare const ERROR_CODE: {
readonly 0: "ERAR_SUCCESS";
readonly 10: "ERAR_END_ARCHIVE";
readonly 11: "ERAR_NO_MEMORY";
readonly 12: "ERAR_BAD_DATA";
readonly 13: "ERAR_BAD_ARCHIVE";
readonly 14: "ERAR_UNKNOWN_FORMAT";
readonly 15: "ERAR_EOPEN";
readonly 16: "ERAR_ECREATE";
readonly 17: "ERAR_ECLOSE";
readonly 18: "ERAR_EREAD";
readonly 19: "ERAR_EWRITE";
readonly 20: "ERAR_SMALL_BUF";
readonly 21: "ERAR_UNKNOWN";
readonly 22: "ERAR_MISSING_PASSWORD";
readonly 23: "ERAR_EREFERENCE";
readonly 24: "ERAR_BAD_PASSWORD";
};
export type FailReason = Exclude<(typeof ERROR_CODE)[keyof typeof ERROR_CODE], 'ERAR_SUCCESS' | 'ERAR_END_ARCHIVE'>;
export declare class UnrarError extends Error {
reason: FailReason;
file?: string | undefined;
constructor(reason: FailReason, message: string, file?: string | undefined);
}
export type CompressMethod = 'Storing' | 'Fastest' | 'Fast' | 'Normal' | 'Good' | 'Best' | 'Unknown';
export interface FileHeader {
name: string;
flags: {
encrypted: boolean;
solid: boolean;
directory: boolean;
};
packSize: number;
unpSize: number;
crc: number;
time: string;
unpVer: string;
method: CompressMethod;
comment: string;
}
export interface ArcHeader {
comment: string;
flags: {
volume: boolean;
lock: boolean;
solid: boolean;
authInfo: boolean;
recoveryRecord: boolean;
headerEncrypted: boolean;
};
}
export interface ArcList {
arcHeader: ArcHeader;
fileHeaders: Generator<FileHeader>;
}
export type ArcFile<withContent = never> = {
fileHeader: FileHeader;
extraction?: withContent;
};
export interface ArcFiles<withContent = never> {
arcHeader: ArcHeader;
files: Generator<ArcFile<withContent>>;
}
export interface ExtractOptions {
files?: string[] | ((fileHeader: FileHeader) => boolean);
password?: string;
}
export declare abstract class Extractor<withContent = never> {
protected unrar: any;
protected abstract _filePath: string;
private _password;
private _archive;
constructor(unrar: any, password?: string);
getFileList(): ArcList;
extract({ files, password }?: ExtractOptions): ArcFiles<withContent>;
protected fileCreated(filename: string): void;
protected abstract open(filename: string): number;
protected abstract create(filename: string): number;
protected abstract read(fd: number, buf: number, size: number): number;
protected abstract write(fd: number, buf: number, size: number): boolean;
protected abstract tell(fd: number): number;
protected abstract seek(fd: number, pos: number, method: SeekMethod): boolean;
protected abstract closeFile(fd: number): void;
protected close(fd: number): void;
private openArc;
private processNextFile;
private closeArc;
private getFailException;
}
export {};
+191
View File
@@ -0,0 +1,191 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Extractor = exports.UnrarError = void 0;
const ERROR_CODE = {
0: 'ERAR_SUCCESS',
10: 'ERAR_END_ARCHIVE',
11: 'ERAR_NO_MEMORY',
12: 'ERAR_BAD_DATA',
13: 'ERAR_BAD_ARCHIVE',
14: 'ERAR_UNKNOWN_FORMAT',
15: 'ERAR_EOPEN',
16: 'ERAR_ECREATE',
17: 'ERAR_ECLOSE',
18: 'ERAR_EREAD',
19: 'ERAR_EWRITE',
20: 'ERAR_SMALL_BUF',
21: 'ERAR_UNKNOWN',
22: 'ERAR_MISSING_PASSWORD',
23: 'ERAR_EREFERENCE',
24: 'ERAR_BAD_PASSWORD',
};
const ERROR_MSG = {
ERAR_NO_MEMORY: 'Not enough memory',
ERAR_BAD_DATA: 'Archive header or data are damaged',
ERAR_BAD_ARCHIVE: 'File is not RAR archive',
ERAR_UNKNOWN_FORMAT: 'Unknown archive format',
ERAR_EOPEN: 'File open error',
ERAR_ECREATE: 'File create error',
ERAR_ECLOSE: 'File close error',
ERAR_EREAD: 'File read error',
ERAR_EWRITE: 'File write error',
ERAR_SMALL_BUF: 'Buffer for archive comment is too small, comment truncated',
ERAR_UNKNOWN: 'Unknown error',
ERAR_MISSING_PASSWORD: 'Password for encrypted file or header is not specified',
ERAR_EREFERENCE: 'Cannot open file source for reference record',
ERAR_BAD_PASSWORD: 'Wrong password is specified',
};
class UnrarError extends Error {
constructor(reason, message, file) {
super(message);
this.reason = reason;
this.file = file;
}
}
exports.UnrarError = UnrarError;
class Extractor {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
constructor(unrar, password = '') {
this.unrar = unrar;
this._password = password;
this._archive = null;
}
getFileList() {
const arcHeader = this.openArc(true);
function* getFileHeaders() {
while (true) {
const arcFile = this.processNextFile(() => true);
if (arcFile === 'ERAR_END_ARCHIVE') {
break;
}
yield arcFile.fileHeader;
}
this.closeArc();
}
return { arcHeader, fileHeaders: getFileHeaders.call(this) };
}
extract({ files, password } = {}) {
const arcHeader = this.openArc(false, password);
function* getFiles() {
let count = 0;
while (true) {
let shouldSkip = () => false;
if (Array.isArray(files)) {
if (count === files.length) {
break;
}
shouldSkip = ({ name }) => !files.includes(name);
}
else if (files) {
shouldSkip = (fileHeader) => !files(fileHeader);
}
const arcFile = this.processNextFile(shouldSkip);
if (arcFile === 'ERAR_END_ARCHIVE') {
break;
}
if (arcFile.extraction === 'skipped') {
continue;
}
count++;
yield {
fileHeader: arcFile.fileHeader,
};
}
this.closeArc();
}
return { arcHeader, files: getFiles.call(this) };
}
fileCreated(filename) {
return;
}
close(fd) {
this.closeFile(fd);
}
openArc(listOnly, password) {
this._archive = new this.unrar.RarArchive();
const header = this._archive.open(this._filePath, password ? password : this._password, listOnly);
if (header.state.errCode !== 0) {
throw this.getFailException(header.state.errCode, header.state.errType);
}
return {
comment: header.comment,
flags: {
volume: (header.flags & 0x0001) !== 0,
lock: (header.flags & 0x0004) !== 0,
solid: (header.flags & 0x0008) !== 0,
authInfo: (header.flags & 0x0020) !== 0,
recoveryRecord: (header.flags & 0x0040) !== 0,
headerEncrypted: (header.flags & 0x0080) !== 0,
},
};
}
processNextFile(shouldSkip) {
function getDateString(dosTime) {
const bitLen = [5, 6, 5, 5, 4, 7];
let parts = [];
for (const len of bitLen) {
parts.push(dosTime & ((1 << len) - 1));
dosTime >>= len;
}
parts = parts.reverse();
const pad = (num) => (num < 10 ? '0' + num : '' + num);
return (`${1980 + parts[0]}-${pad(parts[1])}-${pad(parts[2])}` +
`T${pad(parts[3])}:${pad(parts[4])}:${pad(parts[5] * 2)}.000`);
}
function getMethod(method) {
const methodMap = {
0x30: 'Storing',
0x31: 'Fastest',
0x32: 'Fast',
0x33: 'Normal',
0x34: 'Good',
0x35: 'Best',
};
return methodMap[method] || 'Unknown';
}
const arcFileHeader = this._archive.getFileHeader();
if (arcFileHeader.state.errCode === 10) {
return 'ERAR_END_ARCHIVE';
}
if (arcFileHeader.state.errCode !== 0) {
throw this.getFailException(arcFileHeader.state.errCode, arcFileHeader.state.errType);
}
const fileHeader = {
name: arcFileHeader.name,
flags: {
encrypted: (arcFileHeader.flags & 0x04) !== 0,
solid: (arcFileHeader.flags & 0x10) !== 0,
directory: (arcFileHeader.flags & 0x20) !== 0,
},
packSize: arcFileHeader.packSize,
unpSize: arcFileHeader.unpSize,
// hostOS: arcFileHeader.hostOS
crc: arcFileHeader.crc,
time: getDateString(arcFileHeader.time),
unpVer: `${Math.floor(arcFileHeader.unpVer / 10)}.${arcFileHeader.unpVer % 10}`,
method: getMethod(arcFileHeader.method),
comment: arcFileHeader.comment,
// // fileAttr: arcFileHeader.fileAttr,
};
const skip = shouldSkip(fileHeader);
const fileState = this._archive.readFile(skip);
if (fileState.errCode !== 0) {
throw this.getFailException(fileState.errCode, fileState.errType, fileHeader.name);
}
return {
fileHeader,
extraction: skip ? 'skipped' : 'extracted',
};
}
closeArc() {
this._archive.delete();
this._archive = null;
}
getFailException(errCode, _errType, file) {
const reason = ERROR_CODE[errCode];
this.closeArc();
return new UnrarError(reason, ERROR_MSG[reason], file);
}
}
exports.Extractor = Extractor;
//# sourceMappingURL=Extractor.js.map
@@ -0,0 +1,17 @@
import { ArcFiles, ExtractOptions, Extractor, SeekMethod } from './Extractor';
export declare class ExtractorData extends Extractor<Uint8Array> {
protected _filePath: string;
private dataFiles;
private dataFileMap;
private currentFd;
constructor(unrar: any, data: ArrayBuffer, password: string);
extract(options?: ExtractOptions): ArcFiles<Uint8Array>;
private getExtractedFileName;
protected open(filename: string): number;
protected create(filename: string): number;
protected closeFile(fd: number): void;
protected read(fd: number, buf: number, size: number): number;
protected write(fd: number, buf: number, size: number): boolean;
protected tell(fd: number): number;
protected seek(fd: number, pos: number, method: SeekMethod): boolean;
}
@@ -0,0 +1,13 @@
import { SeekMethod } from './Extractor';
export declare class DataFile {
private buffers;
private size;
private pos;
constructor(data?: Uint8Array);
read(size: number): Uint8Array | null;
readAll(): Uint8Array;
write(data: Uint8Array): boolean;
tell(): number;
seek(pos: number, method: SeekMethod): boolean;
private flatten;
}
@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataFile = void 0;
class DataFile {
constructor(data) {
this.buffers = [];
this.pos = 0;
this.size = 0;
if (data) {
this.buffers.push(data);
this.size = data.byteLength;
this.pos = 0;
}
}
read(size) {
this.flatten();
if (size + this.pos > this.size) {
// size = this.size - this.pos;
return null;
}
const oldPos = this.pos;
this.pos += size;
// return this.buffers[0].subarray(oldPos, this.pos);
return this.buffers[0].slice(oldPos, this.pos);
}
readAll() {
this.flatten();
return this.buffers[0] || new Uint8Array();
}
write(data) {
this.buffers.push(data);
this.size += data.byteLength;
this.pos += data.byteLength;
return true;
}
tell() {
return this.pos;
}
seek(pos, method) {
let newPos = this.pos;
if (method === 'SET') {
newPos = pos;
}
else if (method === 'CUR') {
newPos += pos;
}
else {
newPos = this.size - pos;
}
if (newPos < 0 || newPos > this.size) {
return false;
}
this.pos = newPos;
return true;
}
flatten() {
if (this.buffers.length <= 1) {
return;
}
const newBuffer = new Uint8Array(this.size);
let offset = 0;
for (const buffer of this.buffers) {
newBuffer.set(buffer, offset);
offset += buffer.byteLength;
}
this.buffers = [newBuffer];
}
}
exports.DataFile = DataFile;
//# sourceMappingURL=ExtractorData.helper.js.map
+96
View File
@@ -0,0 +1,96 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtractorData = void 0;
const ExtractorData_helper_1 = require("./ExtractorData.helper");
const Extractor_1 = require("./Extractor");
class ExtractorData extends Extractor_1.Extractor {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
constructor(unrar, data, password) {
super(unrar, password);
this.dataFiles = {};
this.dataFileMap = {};
this.currentFd = 1;
const rarFile = {
file: new ExtractorData_helper_1.DataFile(new Uint8Array(data)),
fd: this.currentFd++,
};
this._filePath = '_defaultUnrarJS_.rar';
this.dataFiles[this._filePath] = rarFile;
this.dataFileMap[rarFile.fd] = this._filePath;
}
extract(options = {}) {
const { arcHeader, files } = super.extract(options);
function* getFiles() {
for (const file of files) {
if (!file.fileHeader.flags.directory) {
file.extraction =
this.dataFiles[this.getExtractedFileName(file.fileHeader.name)].file.readAll();
}
yield file;
}
}
return { arcHeader, files: getFiles.call(this) };
}
getExtractedFileName(filename) {
return `*Extracted*/${filename}`;
}
open(filename) {
const dataFile = this.dataFiles[filename];
if (!dataFile) {
return 0;
}
return dataFile.fd;
}
create(filename) {
const fd = this.currentFd++;
this.dataFiles[this.getExtractedFileName(filename)] = {
file: new ExtractorData_helper_1.DataFile(),
fd: this.currentFd++,
};
this.dataFileMap[fd] = this.getExtractedFileName(filename);
return fd;
}
closeFile(fd) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return;
}
fileData.file.seek(0, 'SET');
}
read(fd, buf, size) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return -1;
}
const data = fileData.file.read(size);
if (data === null) {
return -1;
}
this.unrar.HEAPU8.set(data, buf);
return data.byteLength;
}
write(fd, buf, size) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return false;
}
fileData.file.write(this.unrar.HEAPU8.slice(buf, buf + size));
return true;
}
tell(fd) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return -1;
}
return fileData.file.tell();
}
seek(fd, pos, method) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return false;
}
return fileData.file.seek(pos, method);
}
}
exports.ExtractorData = ExtractorData;
//# sourceMappingURL=ExtractorData.js.map
@@ -0,0 +1,15 @@
import { Extractor, SeekMethod } from './Extractor';
export declare class ExtractorFile extends Extractor {
private filenameTransform;
protected _filePath: string;
private _target;
private fileMap;
constructor(unrar: any, filepath: string, targetPath: string, password: string, filenameTransform: (filename: string) => string);
protected open(filename: string): number;
protected create(filename: string): number;
protected closeFile(fd: number): void;
protected read(fd: number, buf: number, size: number): number;
protected write(fd: number, buf: number, size: number): boolean;
protected tell(fd: number): number;
protected seek(fd: number, pos: number, method: SeekMethod): boolean;
}
+104
View File
@@ -0,0 +1,104 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtractorFile = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const Extractor_1 = require("./Extractor");
class ExtractorFile extends Extractor_1.Extractor {
constructor(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
unrar, filepath, targetPath, password, filenameTransform) {
super(unrar, password);
this.filenameTransform = filenameTransform;
this._filePath = filepath;
this.fileMap = {};
this._target = targetPath;
}
open(filename) {
const fd = fs.openSync(filename, 'r');
this.fileMap[fd] = {
size: fs.fstatSync(fd).size,
pos: 0,
name: filename,
};
return fd;
}
create(filename) {
const fullpath = path.join(this._target, this.filenameTransform(filename));
const dir = path.parse(fullpath).dir;
// Skip if directory is the current directory
if (dir !== '') {
fs.mkdirSync(dir, { recursive: true });
}
const fd = fs.openSync(fullpath, 'w');
this.fileMap[fd] = {
size: 0,
pos: 0,
name: filename,
};
return fd;
}
closeFile(fd) {
delete this.fileMap[fd];
fs.closeSync(fd);
}
read(fd, buf, size) {
const file = this.fileMap[fd];
const buffer = Buffer.allocUnsafe(size);
const readed = fs.readSync(fd, buffer, 0, size, file.pos);
this.unrar.HEAPU8.set(buffer, buf);
file.pos += readed;
return readed;
}
write(fd, buf, size) {
const file = this.fileMap[fd];
const writeNum = fs.writeSync(fd, Buffer.from(this.unrar.HEAPU8.subarray(buf, buf + size)), 0, size);
file.pos += writeNum;
file.size += writeNum;
return writeNum === size;
}
tell(fd) {
return this.fileMap[fd].pos;
}
seek(fd, pos, method) {
const file = this.fileMap[fd];
let newPos = file.pos;
if (method === 'SET') {
newPos = 0;
}
else if (method === 'END') {
newPos = file.size;
}
newPos += pos;
if (newPos < 0 || newPos > file.size) {
return false;
}
file.pos = newPos;
return true;
}
}
exports.ExtractorFile = ExtractorFile;
//# sourceMappingURL=ExtractorFile.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
export declare function getUnrar(options?: {
wasmBinary?: ArrayBuffer;
}): Promise<any>;
@@ -0,0 +1,17 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUnrar = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any */
const unrar_1 = __importDefault(require("./unrar"));
let unrar;
async function getUnrar(options) {
if (!unrar) {
unrar = await (0, unrar_1.default)(options);
}
return unrar;
}
exports.getUnrar = getUnrar;
//# sourceMappingURL=unrar.singleton.js.map
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
import { Extractor } from './js/Extractor';
export * from './index.esm';
export interface ExtractorFromFileOptions {
wasmBinary?: ArrayBuffer;
filepath: string;
targetPath?: string;
password?: string;
filenameTransform?: (filename: string) => string;
}
export declare function createExtractorFromFile({ wasmBinary, filepath, targetPath, password, filenameTransform, }: ExtractorFromFileOptions): Promise<Extractor>;
+8
View File
@@ -0,0 +1,8 @@
import { Extractor } from './js/Extractor';
export * from './js/Extractor';
export interface ExtractorFromDataOptions {
wasmBinary?: ArrayBuffer;
data: ArrayBuffer;
password?: string;
}
export declare function createExtractorFromData({ wasmBinary, data, password, }: ExtractorFromDataOptions): Promise<Extractor<Uint8Array>>;
+10
View File
@@ -0,0 +1,10 @@
import { ExtractorData } from './js/ExtractorData';
import { getUnrar } from './js/unrar.singleton';
export * from './js/Extractor';
export async function createExtractorFromData({ wasmBinary, data, password = '', }) {
const unrar = await getUnrar(wasmBinary && { wasmBinary });
const extractor = new ExtractorData(unrar, data, password);
unrar.extractor = extractor;
return extractor;
}
//# sourceMappingURL=index.esm.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.esm.js","sourceRoot":"","sources":["../src/index.esm.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,cAAc,gBAAgB,CAAC;AAQ/B,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,EAC5C,UAAU,EACV,IAAI,EACJ,QAAQ,GAAG,EAAE,GACY;IACzB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3D,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,OAAO,SAAS,CAAC;AACnB,CAAC"}
+10
View File
@@ -0,0 +1,10 @@
import { ExtractorFile } from './js/ExtractorFile';
import { getUnrar } from './js/unrar.singleton';
export * from './index.esm';
export async function createExtractorFromFile({ wasmBinary, filepath, targetPath = '', password = '', filenameTransform = (filename) => filename, }) {
const unrar = await getUnrar(wasmBinary && { wasmBinary });
const extractor = new ExtractorFile(unrar, filepath, targetPath, password, filenameTransform);
unrar.extractor = extractor;
return extractor;
}
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,cAAc,aAAa,CAAC;AAU5B,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,EAC5C,UAAU,EACV,QAAQ,EACR,UAAU,GAAG,EAAE,EACf,QAAQ,GAAG,EAAE,EACb,iBAAiB,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,QAAQ,GACzB;IACzB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAC9F,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,OAAO,SAAS,CAAC;AACnB,CAAC"}
+91
View File
@@ -0,0 +1,91 @@
export type SeekMethod = 'CUR' | 'SET' | 'END';
declare const ERROR_CODE: {
readonly 0: "ERAR_SUCCESS";
readonly 10: "ERAR_END_ARCHIVE";
readonly 11: "ERAR_NO_MEMORY";
readonly 12: "ERAR_BAD_DATA";
readonly 13: "ERAR_BAD_ARCHIVE";
readonly 14: "ERAR_UNKNOWN_FORMAT";
readonly 15: "ERAR_EOPEN";
readonly 16: "ERAR_ECREATE";
readonly 17: "ERAR_ECLOSE";
readonly 18: "ERAR_EREAD";
readonly 19: "ERAR_EWRITE";
readonly 20: "ERAR_SMALL_BUF";
readonly 21: "ERAR_UNKNOWN";
readonly 22: "ERAR_MISSING_PASSWORD";
readonly 23: "ERAR_EREFERENCE";
readonly 24: "ERAR_BAD_PASSWORD";
};
export type FailReason = Exclude<(typeof ERROR_CODE)[keyof typeof ERROR_CODE], 'ERAR_SUCCESS' | 'ERAR_END_ARCHIVE'>;
export declare class UnrarError extends Error {
reason: FailReason;
file?: string | undefined;
constructor(reason: FailReason, message: string, file?: string | undefined);
}
export type CompressMethod = 'Storing' | 'Fastest' | 'Fast' | 'Normal' | 'Good' | 'Best' | 'Unknown';
export interface FileHeader {
name: string;
flags: {
encrypted: boolean;
solid: boolean;
directory: boolean;
};
packSize: number;
unpSize: number;
crc: number;
time: string;
unpVer: string;
method: CompressMethod;
comment: string;
}
export interface ArcHeader {
comment: string;
flags: {
volume: boolean;
lock: boolean;
solid: boolean;
authInfo: boolean;
recoveryRecord: boolean;
headerEncrypted: boolean;
};
}
export interface ArcList {
arcHeader: ArcHeader;
fileHeaders: Generator<FileHeader>;
}
export type ArcFile<withContent = never> = {
fileHeader: FileHeader;
extraction?: withContent;
};
export interface ArcFiles<withContent = never> {
arcHeader: ArcHeader;
files: Generator<ArcFile<withContent>>;
}
export interface ExtractOptions {
files?: string[] | ((fileHeader: FileHeader) => boolean);
password?: string;
}
export declare abstract class Extractor<withContent = never> {
protected unrar: any;
protected abstract _filePath: string;
private _password;
private _archive;
constructor(unrar: any, password?: string);
getFileList(): ArcList;
extract({ files, password }?: ExtractOptions): ArcFiles<withContent>;
protected fileCreated(filename: string): void;
protected abstract open(filename: string): number;
protected abstract create(filename: string): number;
protected abstract read(fd: number, buf: number, size: number): number;
protected abstract write(fd: number, buf: number, size: number): boolean;
protected abstract tell(fd: number): number;
protected abstract seek(fd: number, pos: number, method: SeekMethod): boolean;
protected abstract closeFile(fd: number): void;
protected close(fd: number): void;
private openArc;
private processNextFile;
private closeArc;
private getFailException;
}
export {};
+186
View File
@@ -0,0 +1,186 @@
const ERROR_CODE = {
0: 'ERAR_SUCCESS',
10: 'ERAR_END_ARCHIVE',
11: 'ERAR_NO_MEMORY',
12: 'ERAR_BAD_DATA',
13: 'ERAR_BAD_ARCHIVE',
14: 'ERAR_UNKNOWN_FORMAT',
15: 'ERAR_EOPEN',
16: 'ERAR_ECREATE',
17: 'ERAR_ECLOSE',
18: 'ERAR_EREAD',
19: 'ERAR_EWRITE',
20: 'ERAR_SMALL_BUF',
21: 'ERAR_UNKNOWN',
22: 'ERAR_MISSING_PASSWORD',
23: 'ERAR_EREFERENCE',
24: 'ERAR_BAD_PASSWORD',
};
const ERROR_MSG = {
ERAR_NO_MEMORY: 'Not enough memory',
ERAR_BAD_DATA: 'Archive header or data are damaged',
ERAR_BAD_ARCHIVE: 'File is not RAR archive',
ERAR_UNKNOWN_FORMAT: 'Unknown archive format',
ERAR_EOPEN: 'File open error',
ERAR_ECREATE: 'File create error',
ERAR_ECLOSE: 'File close error',
ERAR_EREAD: 'File read error',
ERAR_EWRITE: 'File write error',
ERAR_SMALL_BUF: 'Buffer for archive comment is too small, comment truncated',
ERAR_UNKNOWN: 'Unknown error',
ERAR_MISSING_PASSWORD: 'Password for encrypted file or header is not specified',
ERAR_EREFERENCE: 'Cannot open file source for reference record',
ERAR_BAD_PASSWORD: 'Wrong password is specified',
};
export class UnrarError extends Error {
constructor(reason, message, file) {
super(message);
this.reason = reason;
this.file = file;
}
}
export class Extractor {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
constructor(unrar, password = '') {
this.unrar = unrar;
this._password = password;
this._archive = null;
}
getFileList() {
const arcHeader = this.openArc(true);
function* getFileHeaders() {
while (true) {
const arcFile = this.processNextFile(() => true);
if (arcFile === 'ERAR_END_ARCHIVE') {
break;
}
yield arcFile.fileHeader;
}
this.closeArc();
}
return { arcHeader, fileHeaders: getFileHeaders.call(this) };
}
extract({ files, password } = {}) {
const arcHeader = this.openArc(false, password);
function* getFiles() {
let count = 0;
while (true) {
let shouldSkip = () => false;
if (Array.isArray(files)) {
if (count === files.length) {
break;
}
shouldSkip = ({ name }) => !files.includes(name);
}
else if (files) {
shouldSkip = (fileHeader) => !files(fileHeader);
}
const arcFile = this.processNextFile(shouldSkip);
if (arcFile === 'ERAR_END_ARCHIVE') {
break;
}
if (arcFile.extraction === 'skipped') {
continue;
}
count++;
yield {
fileHeader: arcFile.fileHeader,
};
}
this.closeArc();
}
return { arcHeader, files: getFiles.call(this) };
}
fileCreated(filename) {
return;
}
close(fd) {
this.closeFile(fd);
}
openArc(listOnly, password) {
this._archive = new this.unrar.RarArchive();
const header = this._archive.open(this._filePath, password ? password : this._password, listOnly);
if (header.state.errCode !== 0) {
throw this.getFailException(header.state.errCode, header.state.errType);
}
return {
comment: header.comment,
flags: {
volume: (header.flags & 0x0001) !== 0,
lock: (header.flags & 0x0004) !== 0,
solid: (header.flags & 0x0008) !== 0,
authInfo: (header.flags & 0x0020) !== 0,
recoveryRecord: (header.flags & 0x0040) !== 0,
headerEncrypted: (header.flags & 0x0080) !== 0,
},
};
}
processNextFile(shouldSkip) {
function getDateString(dosTime) {
const bitLen = [5, 6, 5, 5, 4, 7];
let parts = [];
for (const len of bitLen) {
parts.push(dosTime & ((1 << len) - 1));
dosTime >>= len;
}
parts = parts.reverse();
const pad = (num) => (num < 10 ? '0' + num : '' + num);
return (`${1980 + parts[0]}-${pad(parts[1])}-${pad(parts[2])}` +
`T${pad(parts[3])}:${pad(parts[4])}:${pad(parts[5] * 2)}.000`);
}
function getMethod(method) {
const methodMap = {
0x30: 'Storing',
0x31: 'Fastest',
0x32: 'Fast',
0x33: 'Normal',
0x34: 'Good',
0x35: 'Best',
};
return methodMap[method] || 'Unknown';
}
const arcFileHeader = this._archive.getFileHeader();
if (arcFileHeader.state.errCode === 10) {
return 'ERAR_END_ARCHIVE';
}
if (arcFileHeader.state.errCode !== 0) {
throw this.getFailException(arcFileHeader.state.errCode, arcFileHeader.state.errType);
}
const fileHeader = {
name: arcFileHeader.name,
flags: {
encrypted: (arcFileHeader.flags & 0x04) !== 0,
solid: (arcFileHeader.flags & 0x10) !== 0,
directory: (arcFileHeader.flags & 0x20) !== 0,
},
packSize: arcFileHeader.packSize,
unpSize: arcFileHeader.unpSize,
// hostOS: arcFileHeader.hostOS
crc: arcFileHeader.crc,
time: getDateString(arcFileHeader.time),
unpVer: `${Math.floor(arcFileHeader.unpVer / 10)}.${arcFileHeader.unpVer % 10}`,
method: getMethod(arcFileHeader.method),
comment: arcFileHeader.comment,
// // fileAttr: arcFileHeader.fileAttr,
};
const skip = shouldSkip(fileHeader);
const fileState = this._archive.readFile(skip);
if (fileState.errCode !== 0) {
throw this.getFailException(fileState.errCode, fileState.errType, fileHeader.name);
}
return {
fileHeader,
extraction: skip ? 'skipped' : 'extracted',
};
}
closeArc() {
this._archive.delete();
this._archive = null;
}
getFailException(errCode, _errType, file) {
const reason = ERROR_CODE[errCode];
this.closeArc();
return new UnrarError(reason, ERROR_MSG[reason], file);
}
}
//# sourceMappingURL=Extractor.js.map
+17
View File
@@ -0,0 +1,17 @@
import { ArcFiles, ExtractOptions, Extractor, SeekMethod } from './Extractor';
export declare class ExtractorData extends Extractor<Uint8Array> {
protected _filePath: string;
private dataFiles;
private dataFileMap;
private currentFd;
constructor(unrar: any, data: ArrayBuffer, password: string);
extract(options?: ExtractOptions): ArcFiles<Uint8Array>;
private getExtractedFileName;
protected open(filename: string): number;
protected create(filename: string): number;
protected closeFile(fd: number): void;
protected read(fd: number, buf: number, size: number): number;
protected write(fd: number, buf: number, size: number): boolean;
protected tell(fd: number): number;
protected seek(fd: number, pos: number, method: SeekMethod): boolean;
}
@@ -0,0 +1,13 @@
import { SeekMethod } from './Extractor';
export declare class DataFile {
private buffers;
private size;
private pos;
constructor(data?: Uint8Array);
read(size: number): Uint8Array | null;
readAll(): Uint8Array;
write(data: Uint8Array): boolean;
tell(): number;
seek(pos: number, method: SeekMethod): boolean;
private flatten;
}
@@ -0,0 +1,66 @@
export class DataFile {
constructor(data) {
this.buffers = [];
this.pos = 0;
this.size = 0;
if (data) {
this.buffers.push(data);
this.size = data.byteLength;
this.pos = 0;
}
}
read(size) {
this.flatten();
if (size + this.pos > this.size) {
// size = this.size - this.pos;
return null;
}
const oldPos = this.pos;
this.pos += size;
// return this.buffers[0].subarray(oldPos, this.pos);
return this.buffers[0].slice(oldPos, this.pos);
}
readAll() {
this.flatten();
return this.buffers[0] || new Uint8Array();
}
write(data) {
this.buffers.push(data);
this.size += data.byteLength;
this.pos += data.byteLength;
return true;
}
tell() {
return this.pos;
}
seek(pos, method) {
let newPos = this.pos;
if (method === 'SET') {
newPos = pos;
}
else if (method === 'CUR') {
newPos += pos;
}
else {
newPos = this.size - pos;
}
if (newPos < 0 || newPos > this.size) {
return false;
}
this.pos = newPos;
return true;
}
flatten() {
if (this.buffers.length <= 1) {
return;
}
const newBuffer = new Uint8Array(this.size);
let offset = 0;
for (const buffer of this.buffers) {
newBuffer.set(buffer, offset);
offset += buffer.byteLength;
}
this.buffers = [newBuffer];
}
}
//# sourceMappingURL=ExtractorData.helper.js.map
+92
View File
@@ -0,0 +1,92 @@
import { DataFile } from './ExtractorData.helper';
import { Extractor } from './Extractor';
export class ExtractorData extends Extractor {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
constructor(unrar, data, password) {
super(unrar, password);
this.dataFiles = {};
this.dataFileMap = {};
this.currentFd = 1;
const rarFile = {
file: new DataFile(new Uint8Array(data)),
fd: this.currentFd++,
};
this._filePath = '_defaultUnrarJS_.rar';
this.dataFiles[this._filePath] = rarFile;
this.dataFileMap[rarFile.fd] = this._filePath;
}
extract(options = {}) {
const { arcHeader, files } = super.extract(options);
function* getFiles() {
for (const file of files) {
if (!file.fileHeader.flags.directory) {
file.extraction =
this.dataFiles[this.getExtractedFileName(file.fileHeader.name)].file.readAll();
}
yield file;
}
}
return { arcHeader, files: getFiles.call(this) };
}
getExtractedFileName(filename) {
return `*Extracted*/${filename}`;
}
open(filename) {
const dataFile = this.dataFiles[filename];
if (!dataFile) {
return 0;
}
return dataFile.fd;
}
create(filename) {
const fd = this.currentFd++;
this.dataFiles[this.getExtractedFileName(filename)] = {
file: new DataFile(),
fd: this.currentFd++,
};
this.dataFileMap[fd] = this.getExtractedFileName(filename);
return fd;
}
closeFile(fd) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return;
}
fileData.file.seek(0, 'SET');
}
read(fd, buf, size) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return -1;
}
const data = fileData.file.read(size);
if (data === null) {
return -1;
}
this.unrar.HEAPU8.set(data, buf);
return data.byteLength;
}
write(fd, buf, size) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return false;
}
fileData.file.write(this.unrar.HEAPU8.slice(buf, buf + size));
return true;
}
tell(fd) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return -1;
}
return fileData.file.tell();
}
seek(fd, pos, method) {
const fileData = this.dataFiles[this.dataFileMap[fd]];
if (!fileData) {
return false;
}
return fileData.file.seek(pos, method);
}
}
//# sourceMappingURL=ExtractorData.js.map
+15
View File
@@ -0,0 +1,15 @@
import { Extractor, SeekMethod } from './Extractor';
export declare class ExtractorFile extends Extractor {
private filenameTransform;
protected _filePath: string;
private _target;
private fileMap;
constructor(unrar: any, filepath: string, targetPath: string, password: string, filenameTransform: (filename: string) => string);
protected open(filename: string): number;
protected create(filename: string): number;
protected closeFile(fd: number): void;
protected read(fd: number, buf: number, size: number): number;
protected write(fd: number, buf: number, size: number): boolean;
protected tell(fd: number): number;
protected seek(fd: number, pos: number, method: SeekMethod): boolean;
}
+77
View File
@@ -0,0 +1,77 @@
import * as fs from 'fs';
import * as path from 'path';
import { Extractor } from './Extractor';
export class ExtractorFile extends Extractor {
constructor(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
unrar, filepath, targetPath, password, filenameTransform) {
super(unrar, password);
this.filenameTransform = filenameTransform;
this._filePath = filepath;
this.fileMap = {};
this._target = targetPath;
}
open(filename) {
const fd = fs.openSync(filename, 'r');
this.fileMap[fd] = {
size: fs.fstatSync(fd).size,
pos: 0,
name: filename,
};
return fd;
}
create(filename) {
const fullpath = path.join(this._target, this.filenameTransform(filename));
const dir = path.parse(fullpath).dir;
// Skip if directory is the current directory
if (dir !== '') {
fs.mkdirSync(dir, { recursive: true });
}
const fd = fs.openSync(fullpath, 'w');
this.fileMap[fd] = {
size: 0,
pos: 0,
name: filename,
};
return fd;
}
closeFile(fd) {
delete this.fileMap[fd];
fs.closeSync(fd);
}
read(fd, buf, size) {
const file = this.fileMap[fd];
const buffer = Buffer.allocUnsafe(size);
const readed = fs.readSync(fd, buffer, 0, size, file.pos);
this.unrar.HEAPU8.set(buffer, buf);
file.pos += readed;
return readed;
}
write(fd, buf, size) {
const file = this.fileMap[fd];
const writeNum = fs.writeSync(fd, Buffer.from(this.unrar.HEAPU8.subarray(buf, buf + size)), 0, size);
file.pos += writeNum;
file.size += writeNum;
return writeNum === size;
}
tell(fd) {
return this.fileMap[fd].pos;
}
seek(fd, pos, method) {
const file = this.fileMap[fd];
let newPos = file.pos;
if (method === 'SET') {
newPos = 0;
}
else if (method === 'END') {
newPos = file.size;
}
newPos += pos;
if (newPos < 0 || newPos > file.size) {
return false;
}
file.pos = newPos;
return true;
}
}
//# sourceMappingURL=ExtractorFile.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
export declare function getUnrar(options?: {
wasmBinary?: ArrayBuffer;
}): Promise<any>;
+10
View File
@@ -0,0 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import unrarFactory from './unrar';
let unrar;
export async function getUnrar(options) {
if (!unrar) {
unrar = await unrarFactory(options);
}
return unrar;
}
//# sourceMappingURL=unrar.singleton.js.map
Binary file not shown.
+74
View File
@@ -0,0 +1,74 @@
{
"name": "node-unrar-js",
"version": "2.0.2",
"description": "Pure JavaScript RAR archive extractor by compile the official unrar lib by Emscripten.",
"homepage": "https://github.com/YuJianrong/node-unrar.js",
"bugs": {
"url": "https://github.com/YuJianrong/node-unrar.js/issues"
},
"repository": "YuJianrong/node-unrar.js",
"main": "./dist/index.js",
"module": "./esm/index.esm.js",
"scripts": {
"prepare": "husky install",
"downloadUnrarSrc": "wget https://www.rarlab.com/rar/unrarsrc-6.1.7.tar.gz && tar -xf unrarsrc-6.1.7.tar.gz -C src/cpp && rm -rf unrarsrc-6.1.7.tar.gz && husky install",
"clean": "rm -rf dist coverage dist_map esm",
"clean:test": "rm -rf dist/test dist/build esm/test esm/build dist/**/*.map esm/**/*.map",
"commit": "cz",
"lint": "eslint './src/**/*.ts' --max-warnings 0",
"tsc": "tsc",
"build:debug": "tsc && node ./dist/build/make.js",
"prebuild:release": "npm run lint",
"build:release": "npm run clean && tsc && node ./dist/build/make.js release && tsc -p tsconfig-esm.json && node ./dist/build/make.js release esm",
"test": "mocha dist/test/*.spec.js",
"test:debug": "npm run test -- --inspect-brk"
},
"keywords": [
"unrar"
],
"author": "Jianrong Yu <yujianrong@gmail.com>",
"license": "MIT",
"devDependencies": {
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/eslint": "^8.44.7",
"@types/mocha": "^10.0.5",
"@types/shelljs": "^0.8.15",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"husky": "^8.0.3",
"lint-staged": "^15.1.0",
"mocha": "^10.2.0",
"prettier": "^3.1.0",
"shelljs": "^0.8.5",
"typescript": "^5.3.2"
},
"engines": {
"node": ">=10.0.0"
},
"lint-staged": {
"*.{json,md,less,yaml,yml}": [
"prettier --write"
],
"*.{ts,tsx}": [
"eslint --fix --max-warnings 0"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"files": [
"dist",
"esm",
"*.md"
]
}