node-unrar-js
Version:
Pure JavaScript RAR archive extractor by compile the official unrar lib by Emscripten.
186 lines • 6.67 kB
JavaScript
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