unpack static method

Future<int> unpack({
  1. required RandomAccessFile source,
  2. required String outputDirPath,
  3. int chunkSize = 1 << 20,
})

Reads a pack from source and materializes each entry under outputDirPath. Every path is re-validated against traversal. Returns the entry count.

Implementation

static Future<int> unpack({
  required RandomAccessFile source,
  required String outputDirPath,
  int chunkSize = 1 << 20,
}) async {
  final buffer = Uint8List(chunkSize);
  var count = 0;
  while (true) {
    final pathLenBytes = await _readExactlyOrNull(source, 4);
    if (pathLenBytes == null) break; // clean EOF at an entry boundary
    final pathLen = _readUint32(pathLenBytes);
    if (pathLen <= 0 || pathLen > maxPathBytes) {
      throw PqForgeException('Invalid pack entry path length: $pathLen');
    }
    final relativePath = _decodeUtf8(await _readExactly(source, pathLen));
    _requireSafeRelativePath(relativePath);
    final contentLen = _readUint64(await _readExactly(source, 8));

    final output = File(_join(outputDirPath, relativePath));
    await output.parent.create(recursive: true);
    final sink = await output.open(mode: FileMode.write);
    try {
      var remaining = contentLen;
      while (remaining > 0) {
        final want = remaining < buffer.length ? remaining : buffer.length;
        final n = await source.readInto(buffer, 0, want);
        if (n <= 0) {
          throw PqForgeException('Truncated pack content for $relativePath');
        }
        await sink.writeFrom(buffer, 0, n);
        remaining -= n;
      }
    } finally {
      await sink.close();
    }
    count++;
  }
  return count;
}