rar-stream 5.1.0

High-performance RAR streaming library with optional async, crypto, NAPI, and WASM support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# rar-stream

> Fast RAR archive streaming for Rust, Node.js, and browsers. Zero dependencies core.

[![CI](https://github.com/doom-fish/rar-stream/actions/workflows/ci.yml/badge.svg)](https://github.com/doom-fish/rar-stream/actions/workflows/ci.yml)
[![npm version](https://badge.fury.io/js/rar-stream.svg)](https://www.npmjs.com/package/rar-stream)
[![npm downloads](https://img.shields.io/npm/dm/rar-stream.svg)](https://www.npmjs.com/package/rar-stream)
[![crates.io](https://img.shields.io/crates/v/rar-stream.svg)](https://crates.io/crates/rar-stream)
[![crates.io downloads](https://img.shields.io/crates/d/rar-stream.svg)](https://crates.io/crates/rar-stream)
[![docs.rs](https://docs.rs/rar-stream/badge.svg)](https://docs.rs/rar-stream)
[![MSRV](https://img.shields.io/badge/MSRV-1.85-blue.svg)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## What's New in v5.1.0

- โšก **Parallel RAR5 pipeline**: Beats the official C `unrar` in all 24 benchmark scenarios
- ๐ŸŒ **WASM RAR5 support**: Full RAR5 decompression in the browser via `WasmRar5Decoder`
- ๐Ÿ”Œ **NAPI pipeline**: Node.js users get 40% faster decompression automatically
- ๐Ÿงช **E2E browser tests**: Full Playwright tests for upload โ†’ decompress โ†’ verify workflow

## Features

- ๐Ÿš€ **Fast**: Native Rust implementation with NAPI bindings
- ๐Ÿ“ฆ **Zero dependencies**: Core library has no external dependencies
- ๐ŸŒ **Cross-platform**: Works on Linux, macOS, Windows
- ๐Ÿ”„ **Streaming**: Stream files directly from RAR archives
- ๐Ÿ“š **Multi-volume**: Supports split archives (.rar, .r00, .r01, ...)
- ๐Ÿ—œ๏ธ **Full decompression**: LZSS, PPMd, and filters
- ๐Ÿ” **Encrypted archives**: AES-256/AES-128 decryption for RAR4 & RAR5
- ๐Ÿ†• **RAR4 + RAR5**: Full support for both RAR formats
- ๐ŸŒ **Browser support**: WASM build available

## Installation

### Rust

```toml
[dependencies]
rar-stream = { version = "5", features = ["async", "crypto"] }
```

### Node.js

```bash
npm install rar-stream
# or
yarn add rar-stream
# or
pnpm add rar-stream
```

## Quick Start

```javascript
import { LocalFileMedia, RarFilesPackage } from 'rar-stream';

// Open a RAR archive
const media = new LocalFileMedia('./archive.rar');
const pkg = new RarFilesPackage([media]);

// Parse and list inner files
const files = await pkg.parse();

for (const file of files) {
  console.log(`${file.name}: ${file.length} bytes`);
  
  // Read entire file into memory
  const buffer = await file.readToEnd();
  
  // Or stream (returns Node.js Readable)
  const stream = file.createReadStream();
  stream.pipe(process.stdout);
}
```

### Rust Quick Start

```rust
use rar_stream::{RarFilesPackage, ParseOptions, LocalFileMedia, FileMedia};
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Open a RAR archive
    let file: Arc<dyn FileMedia> = Arc::new(LocalFileMedia::new("archive.rar")?);
    let package = RarFilesPackage::new(vec![file]);

    // Parse and list files
    let files = package.parse(ParseOptions::default()).await?;
    for f in &files {
        println!("{}: {} bytes", f.name, f.length);
    }

    // Read file content
    let content = files[0].read_to_end().await?;
    println!("Read {} bytes", content.len());
    Ok(())
}
```

## Examples

### Extract a File to Disk

```javascript
import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
import fs from 'fs';

const media = new LocalFileMedia('./archive.rar');
const pkg = new RarFilesPackage([media]);
const files = await pkg.parse();

// Find a specific file
const targetFile = files.find(f => f.name.endsWith('.txt'));
if (targetFile) {
  const content = await targetFile.readToEnd();
  fs.writeFileSync('./extracted.txt', content);
  console.log(`Extracted ${targetFile.name} (${content.length} bytes)`);
}
```

### Stream Video from RAR (Node.js Readable Stream)

```javascript
import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
import fs from 'fs';

const media = new LocalFileMedia('./movie.rar');
const pkg = new RarFilesPackage([media]);
const files = await pkg.parse();

const video = files.find(f => f.name.endsWith('.mkv'));
if (video) {
  // Get a Node.js Readable stream for the entire file
  const stream = video.createReadStream();
  stream.pipe(fs.createWriteStream('./extracted-video.mkv'));
  
  // Or stream a specific byte range (for HTTP range requests)
  const rangeStream = video.createReadStream({ start: 0, end: 1024 * 1024 - 1 });
}
```

### WebTorrent Integration

Use `rar-stream` with WebTorrent to stream video from RAR archives inside torrents:

```javascript
import WebTorrent from 'webtorrent';
import { RarFilesPackage } from 'rar-stream';

const client = new WebTorrent();

client.add(magnetUri, (torrent) => {
  // Find RAR files (includes .rar, .r00, .r01, etc. for multi-volume)
  // WebTorrent files already implement the FileMedia interface!
  const rarFiles = torrent.files
    .filter(f => /\.(rar|r\d{2})$/i.test(f.name))
    .sort((a, b) => a.name.localeCompare(b.name));

  // No wrapper needed - pass torrent files directly
  const pkg = new RarFilesPackage(rarFiles);
  pkg.parse().then(innerFiles => {
    const video = innerFiles.find(f => f.name.endsWith('.mkv'));
    if (video) {
      // Stream video content - this returns a Node.js Readable
      const stream = video.createReadStream();
      
      // Pipe to HTTP response, media player, etc.
      stream.pipe(process.stdout);
    }
  });
});
```

### HTTP Range Request Handler (Express)

```javascript
import express from 'express';
import { LocalFileMedia, RarFilesPackage } from 'rar-stream';

const app = express();

// Pre-parse the RAR archive
const media = new LocalFileMedia('./videos.rar');
const pkg = new RarFilesPackage([media]);
const files = await pkg.parse();
const video = files.find(f => f.name.endsWith('.mp4'));

app.get('/video', (req, res) => {
  const range = req.headers.range;
  const fileSize = video.length;
  
  if (range) {
    const [startStr, endStr] = range.replace(/bytes=/, '').split('-');
    const start = parseInt(startStr, 10);
    const end = endStr ? parseInt(endStr, 10) : fileSize - 1;
    
    res.writeHead(206, {
      'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': end - start + 1,
      'Content-Type': 'video/mp4',
    });
    
    // Stream the range directly from the RAR archive
    const stream = video.createReadStream({ start, end });
    stream.pipe(res);
  } else {
    res.writeHead(200, {
      'Content-Length': fileSize,
      'Content-Type': 'video/mp4',
    });
    video.createReadStream().pipe(res);
  }
});

app.listen(3000);
```

### Multi-Volume Archives

```javascript
import { LocalFileMedia, RarFilesPackage } from 'rar-stream';
import fs from 'fs';
import path from 'path';

// Find all volumes in a directory
const dir = './my-archive';
const volumeFiles = fs.readdirSync(dir)
  .filter(f => /\.(rar|r\d{2})$/i.test(f))
  .sort()
  .map(f => new LocalFileMedia(path.join(dir, f)));

console.log(`Found ${volumeFiles.length} volumes`);

const pkg = new RarFilesPackage(volumeFiles);
const files = await pkg.parse();

// Files spanning multiple volumes are handled automatically
for (const file of files) {
  console.log(`${file.name}: ${file.length} bytes`);
}
```

### Check if a File is a RAR Archive

```javascript
import { isRarArchive, parseRarHeader } from 'rar-stream';
import fs from 'fs';

// Read first 300 bytes (enough for header detection)
const buffer = Buffer.alloc(300);
const fd = fs.openSync('./unknown-file', 'r');
fs.readSync(fd, buffer, 0, 300, 0);
fs.closeSync(fd);

if (isRarArchive(buffer)) {
  const info = parseRarHeader(buffer);
  if (info) {
    console.log(`First file: ${info.name}`);
    console.log(`Packed size: ${info.packedSize} bytes`);
    console.log(`Unpacked size: ${info.unpackedSize} bytes`);
    console.log(`Compression method: 0x${info.method.toString(16)}`);
  }
} else {
  console.log('Not a RAR archive');
}
```

### Limit Number of Files Parsed

```javascript
import { LocalFileMedia, RarFilesPackage } from 'rar-stream';

const media = new LocalFileMedia('./large-archive.rar');
const pkg = new RarFilesPackage([media]);

// Only parse first 10 files (useful for previewing large archives)
const files = await pkg.parse({ maxFiles: 10 });
console.log(`Showing first ${files.length} files`);
```

## API Reference

### LocalFileMedia

Represents a local RAR file.

```typescript
class LocalFileMedia {
  constructor(path: string);
  
  readonly name: string;    // Filename (basename)
  readonly length: number;  // File size in bytes
  
  // Read a byte range into a Buffer
  // Create a Readable stream for a byte range
  createReadStream(opts: { start: number; end: number }): Readable;
}
```

### FileMedia Interface

Custom data sources (WebTorrent, S3, HTTP, etc.) must implement this interface:

```typescript
interface FileMedia {
  readonly name: string;
  readonly length: number;
  createReadStream(opts: { start: number; end: number }): Readable;
}
```

### RarFilesPackage

Parses single or multi-volume RAR archives.

```typescript
class RarFilesPackage {
  constructor(files: FileMedia[]);  // LocalFileMedia or custom FileMedia
  
  parse(opts?: {
    maxFiles?: number;  // Limit number of files to parse
  }): Promise<InnerFile[]>;
}
```

### InnerFile

Represents a file inside the RAR archive.

```typescript
import { Readable } from 'stream';

class InnerFile {
  readonly name: string;    // Full path inside archive
  readonly length: number;  // Uncompressed size in bytes
  
  // Read entire file into memory
  readToEnd(): Promise<Buffer>;
  
  // Create a Readable stream for the entire file or a byte range
  createReadStream(opts?: { 
    start?: number;   // Default: 0
    end?: number;     // Default: length - 1
  }): Readable;
}
```

### Utility Functions

```typescript
// Check if buffer starts with RAR signature
function isRarArchive(buffer: Buffer): boolean;

// Parse RAR header from buffer (needs ~300 bytes)
function parseRarHeader(buffer: Buffer): RarFileInfo | null;

// Convert a Readable stream to a Buffer
function streamToBuffer(stream: Readable): Promise<Buffer>;

// Create a FileMedia from any source with createReadStream
function createFileMedia(source: FileMedia): FileMedia;

interface RarFileInfo {
  name: string;
  packedSize: number;
  unpackedSize: number;
  method: number;
  continuesInNext: boolean;
}
```

## Compression Support

### RAR Format Compatibility

| Format | Signature | Support |
|--------|-----------|---------|
| RAR 1.5-4.x (RAR4) | `Rar!\x1a\x07\x00` | โœ… Full |
| RAR 5.0+ (RAR5) | `Rar!\x1a\x07\x01\x00` | โœ… Full |

### Compression Methods

| Method | RAR4 | RAR5 | Description |
|--------|------|------|-------------|
| Store | โœ… | โœ… | No compression |
| LZSS | โœ… | โœ… | Huffman + LZ77 sliding window |
| PPMd | โœ… | โ€” | Context-based (RAR4 only) |

### Filter Support

| Filter | RAR4 | RAR5 | Description |
|--------|------|------|-------------|
| E8 | โœ… | โœ… | x86 CALL preprocessing |
| E8E9 | โœ… | โœ… | x86 CALL/JMP preprocessing |
| Delta | โœ… | โœ… | Byte delta per channel |
| ARM | โ€” | โœ… | ARM branch preprocessing |
| Itanium | โœ… | โ€” | IA-64 preprocessing |
| RGB | โœ… | โ€” | Predictive color filter |
| Audio | โœ… | โ€” | Audio sample predictor |

### Encryption Support

| Feature | RAR4 | RAR5 | Notes |
|---------|------|------|-------|
| Encrypted files | โœ… | โœ… | `crypto` feature |
| Encrypted headers | โ€” | โœ… | RAR5 `-hp` archives |
| Algorithm | AES-128-CBC | AES-256-CBC | โ€” |
| Key derivation | SHA-1 (262k rounds) | PBKDF2-HMAC-SHA256 | โ€” |

To enable encryption support:

**Node.js/npm:** Encryption is always available.

**Rust:**
```toml
[dependencies]
rar-stream = { version = "5", features = ["async", "crypto"] }
```

## Performance

### RAR5 Decompression: Faster Than unrar

rar-stream's parallel pipeline **beats the official C `unrar`** (v7.0, multi-threaded) across all tested workloads.

Benchmark on AMD Ryzen 5 7640HS (6 cores):

| Archive | Size | rar-stream (pipeline) | unrar | Ratio |
|---------|------|----------------------|-------|-------|
| ISO (binary) | 200 MB | **422ms** | 453ms | **0.93ร—** |
| Text | 200 MB | **144ms** | 202ms | **0.71ร—** |
| Mixed | 200 MB | **342ms** | 527ms | **0.65ร—** |
| Binary | 500 MB | **824ms** | 1149ms | **0.72ร—** |
| Text | 500 MB | **424ms** | 604ms | **0.70ร—** |
| Mixed | 1 GB | **1953ms** | 2550ms | **0.77ร—** |

**Wins 24 out of 24 benchmark scenarios** across 6 data types ร— 4 compression settings.

Best case: **1.9ร— faster** than unrar (ISO 200MB, `-m5 -md128m`).

<details>
<summary>Full benchmark matrix (24 scenarios)</summary>

```
Archive                  Single   Pipeline    Unrar   Pipe/Unrar
----------------------------------------------------------------
bin-500_m3_32m            1278ms       884ms    1187ms     0.74x
bin-500_m5_128m           1200ms       824ms    1149ms     0.72x
bin-500_m5_32m            1247ms       852ms    1162ms     0.73x
bin-500_m5_4m             1378ms       942ms    1770ms     0.53x
iso-200_m3_32m             715ms       426ms     760ms     0.56x
iso-200_m5_128m            720ms       423ms     811ms     0.52x
iso-200_m5_32m             721ms       422ms     453ms     0.93x
iso-200_m5_4m              717ms       422ms     442ms     0.95x
mixed-1g_m3_32m           2974ms      2109ms    2775ms     0.76x
mixed-1g_m5_128m          3177ms      2213ms    2984ms     0.74x
mixed-1g_m5_32m           2979ms      2086ms    2731ms     0.76x
mixed-1g_m5_4m            2761ms      1953ms    2550ms     0.77x
mixed-200_m3_32m           499ms       385ms     547ms     0.70x
mixed-200_m5_128m          438ms       342ms     527ms     0.65x
mixed-200_m5_32m           495ms       384ms     539ms     0.71x
mixed-200_m5_4m            511ms       395ms     538ms     0.73x
text-200_m3_32m            209ms       145ms     202ms     0.72x
text-200_m5_128m           205ms       144ms     239ms     0.60x
text-200_m5_32m            209ms       144ms     202ms     0.71x
text-200_m5_4m             227ms       153ms     207ms     0.74x
text-500_m3_32m            606ms       432ms     613ms     0.70x
text-500_m5_128m           601ms       431ms     644ms     0.67x
text-500_m5_32m            604ms       424ms     604ms     0.70x
text-500_m5_4m             659ms       455ms     643ms     0.71x
```

</details>

### Node.js (NAPI) Performance

| Configuration | Time (200MB) | vs unrar |
|---------------|-------------|----------|
| v5.0.0 (single-threaded) | 1127ms | 2.73ร— slower |
| **v5.1.0 (pipeline)** | **673ms** | **1.53ร— slower** |

The remaining NAPI gap vs native is I/O + buffer copy overhead, not decompression.

### Optimization Features

- **Parallel pipeline**: Decode + apply in parallel using rayon worker threads
- **Split-buffer decode**: Separates literals from commands for cache-friendly apply
- **LTO (Link-Time Optimization)**: Enabled by default in release builds
- **SIMD**: Automatic vectorization for E8/E9 filter scanning and memcpy
- **Zero-copy streaming**: Direct buffer access without intermediate copies

## Migrating from v3.x

rar-stream v4 is a complete Rust rewrite with the same API. It's a drop-in replacement:

```javascript
// Works the same in v3.x and v4.x
import { LocalFileMedia, RarFilesPackage } from 'rar-stream';

const media = new LocalFileMedia('./archive.rar');
const pkg = new RarFilesPackage([media]);
const files = await pkg.parse();
```

### Breaking Changes

- Node.js 18+ required (was 14+)
- Native Rust implementation (faster, lower memory)

## License

MIT

## Credits

- Based on [unrar]https://www.rarlab.com/ reference implementation
- PPMd algorithm by Dmitry Shkarin