exarch-rs 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/native/exarch-rs.darwin-arm64.node +0 -0
- package/native/exarch-rs.darwin-x64.node +0 -0
- package/native/exarch-rs.linux-arm64-gnu.node +0 -0
- package/native/exarch-rs.linux-x64-gnu.node +0 -0
- package/native/exarch-rs.win32-x64-msvc.node +0 -0
- package/package.json +3 -3
- package/src/error.rs +48 -1
- package/src/report.rs +2 -2
package/README.md
CHANGED
|
@@ -167,11 +167,13 @@ Builder-style security configuration.
|
|
|
167
167
|
|
|
168
168
|
```typescript
|
|
169
169
|
const config = new SecurityConfig()
|
|
170
|
-
.maxFileSize(bytes)
|
|
171
|
-
.maxTotalSize(bytes)
|
|
172
|
-
.maxFileCount(count)
|
|
173
|
-
.maxCompressionRatio(n)
|
|
174
|
-
.
|
|
170
|
+
.maxFileSize(bytes) // Max size per file
|
|
171
|
+
.maxTotalSize(bytes) // Max total extraction size
|
|
172
|
+
.maxFileCount(count) // Max number of files
|
|
173
|
+
.maxCompressionRatio(n) // Max compression ratio (zip bomb detection)
|
|
174
|
+
.allowedExtensions([".txt", ".md"]) // Restrict to a set of extensions
|
|
175
|
+
.bannedPathComponents(["__MACOSX"]) // Skip these path components
|
|
176
|
+
.setAllowSolidArchives(true); // Allow solid 7z archives (default: false)
|
|
175
177
|
```
|
|
176
178
|
|
|
177
179
|
## Security Features
|
|
@@ -203,6 +205,8 @@ The library provides built-in protection against:
|
|
|
203
205
|
|
|
204
206
|
**Note:** 7z creation is not yet supported. Solid and encrypted 7z archives are rejected for security reasons. Unix symlinks inside 7z archives are reported as regular files (sevenz-rust2 API limitation).
|
|
205
207
|
|
|
208
|
+
**Note:** Since v0.4.0, partial extraction failures return the `ExtractionReport` accumulated up to the failure point without the inner error text being duplicated, and the report is now correctly delivered across the FFI boundary instead of being dropped early.
|
|
209
|
+
|
|
206
210
|
## Comparison with tar-fs
|
|
207
211
|
|
|
208
212
|
```javascript
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exarch-rs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Memory-safe archive extraction library with built-in security validation",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@biomejs/biome": "^2.
|
|
40
|
-
"@napi-rs/cli": "^3.
|
|
39
|
+
"@biomejs/biome": "^2.4.15",
|
|
40
|
+
"@napi-rs/cli": "^3.6.2"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">= 18"
|
package/src/error.rs
CHANGED
|
@@ -182,7 +182,18 @@ pub fn convert_error(err: CoreError) -> Error {
|
|
|
182
182
|
msg.push_str(&reason);
|
|
183
183
|
Error::new(Status::GenericFailure, msg)
|
|
184
184
|
}
|
|
185
|
-
CoreError::PartialExtraction { source,
|
|
185
|
+
CoreError::PartialExtraction { source, report } => {
|
|
186
|
+
let inner = convert_error(*source);
|
|
187
|
+
let mut msg = String::with_capacity(inner.reason.len() + 64);
|
|
188
|
+
msg.push_str("PARTIAL_EXTRACTION: ");
|
|
189
|
+
msg.push_str(&inner.reason);
|
|
190
|
+
let _ = write!(
|
|
191
|
+
&mut msg,
|
|
192
|
+
" | filesExtracted={}, bytesWritten={}",
|
|
193
|
+
report.files_extracted, report.bytes_written
|
|
194
|
+
);
|
|
195
|
+
Error::new(Status::GenericFailure, msg)
|
|
196
|
+
}
|
|
186
197
|
}
|
|
187
198
|
}
|
|
188
199
|
|
|
@@ -349,4 +360,40 @@ mod tests {
|
|
|
349
360
|
assert!(err_str.contains("IO_ERROR"));
|
|
350
361
|
assert!(err_str.contains("file not found"));
|
|
351
362
|
}
|
|
363
|
+
|
|
364
|
+
/// Regression test for #210: `convert_error` must embed `filesExtracted`
|
|
365
|
+
/// and `bytesWritten` in the error message for `PartialExtraction`.
|
|
366
|
+
#[test]
|
|
367
|
+
fn test_partial_extraction_node_message_format() {
|
|
368
|
+
use exarch_core::ExtractionReport;
|
|
369
|
+
use exarch_core::QuotaResource;
|
|
370
|
+
|
|
371
|
+
let report = ExtractionReport {
|
|
372
|
+
files_extracted: 3,
|
|
373
|
+
bytes_written: 1024,
|
|
374
|
+
..ExtractionReport::default()
|
|
375
|
+
};
|
|
376
|
+
let source = CoreError::QuotaExceeded {
|
|
377
|
+
resource: QuotaResource::FileCount { current: 4, max: 3 },
|
|
378
|
+
};
|
|
379
|
+
let err = CoreError::PartialExtraction {
|
|
380
|
+
source: Box::new(source),
|
|
381
|
+
report,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
let napi_err = convert_error(err);
|
|
385
|
+
let msg = napi_err.reason.clone();
|
|
386
|
+
assert!(
|
|
387
|
+
msg.contains("PARTIAL_EXTRACTION"),
|
|
388
|
+
"message must contain PARTIAL_EXTRACTION, got: {msg}"
|
|
389
|
+
);
|
|
390
|
+
assert!(
|
|
391
|
+
msg.contains("filesExtracted=3"),
|
|
392
|
+
"message must contain filesExtracted=3, got: {msg}"
|
|
393
|
+
);
|
|
394
|
+
assert!(
|
|
395
|
+
msg.contains("bytesWritten=1024"),
|
|
396
|
+
"message must contain bytesWritten=1024, got: {msg}"
|
|
397
|
+
);
|
|
398
|
+
}
|
|
352
399
|
}
|
package/src/report.rs
CHANGED
|
@@ -285,7 +285,7 @@ mod tests {
|
|
|
285
285
|
core_report.files_extracted = 100_000;
|
|
286
286
|
core_report.directories_created = 50_000;
|
|
287
287
|
core_report.bytes_written = 10_000_000_000; // 10 GB
|
|
288
|
-
core_report.duration = Duration::
|
|
288
|
+
core_report.duration = Duration::from_hours(1);
|
|
289
289
|
|
|
290
290
|
let report = ExtractionReport::from(core_report);
|
|
291
291
|
|
|
@@ -312,7 +312,7 @@ mod tests {
|
|
|
312
312
|
#[test]
|
|
313
313
|
fn test_extraction_report_duration_hours() {
|
|
314
314
|
let mut core_report = CoreReport::new();
|
|
315
|
-
core_report.duration = Duration::
|
|
315
|
+
core_report.duration = Duration::from_hours(2);
|
|
316
316
|
|
|
317
317
|
let report = ExtractionReport::from(core_report);
|
|
318
318
|
|