blob: b318ff59d70e56d5e50a6553999a72752f2b2f1b [file] [log] [blame]
import 'dart:convert';
import 'archive.dart';
import 'archive_file.dart';
import 'tar/tar_file.dart';
import 'util/input_stream.dart';
final paxRecordRegexp = RegExp(r"(\d+) (\w+)=(.*)");
/// Decode a tar formatted buffer into an [Archive] object.
class TarDecoder {
List<TarFile> files = [];
Archive decodeBytes(List<int> data,
{bool verify = false, bool storeData = true}) {
return decodeBuffer(InputStream(data),
verify: verify, storeData: storeData);
}
Archive decodeBuffer(InputStreamBase input,
{bool verify = false, bool storeData = true}) {
final archive = Archive();
files.clear();
String? nextName;
String? nextLinkName;
// TarFile paxHeader = null;
while (!input.isEOS) {
// End of archive when two consecutive 0's are found.
final endCheck = input.peekBytes(2).toUint8List();
if (endCheck.length < 2 || (endCheck[0] == 0 && endCheck[1] == 0)) {
break;
}
final tf = TarFile.read(input, storeData: storeData);
// GNU tar puts filenames in files when they exceed tar's native length.
if (tf.filename == '././@LongLink') {
nextName = tf.rawContent!.readString();
continue;
}
// In POSIX formatted tar files, a separate 'PAX' file contains extended
// metadata for files. These are identified by having a type flag 'X'.
// TODO: parse these metadata values.
if (tf.typeFlag == TarFile.TYPE_G_EX_HEADER ||
tf.typeFlag == TarFile.TYPE_G_EX_HEADER2) {
// TODO handle PAX global header.
continue;
}
if (tf.typeFlag == TarFile.TYPE_EX_HEADER ||
tf.typeFlag == TarFile.TYPE_EX_HEADER2) {
utf8
.decode(tf.rawContent!.toUint8List())
.split('\n')
.where((s) => paxRecordRegexp.hasMatch(s))
.forEach((record) {
var match = paxRecordRegexp.firstMatch(record)!;
var keyword = match.group(2);
var value = match.group(3)!;
switch (keyword) {
case 'path':
nextName = value;
break;
case 'linkpath':
nextLinkName = value;
break;
default:
// TODO: support other pax headers.
}
});
continue;
}
// Fix file attributes.
if (nextName != null) {
tf.filename = nextName!;
nextName = null;
}
if (nextLinkName != null) {
tf.nameOfLinkedFile = nextLinkName!;
nextLinkName = null;
}
files.add(tf);
final file = ArchiveFile(tf.filename, tf.fileSize, tf.rawContent);
file.mode = tf.mode;
file.ownerId = tf.ownerId;
file.groupId = tf.groupId;
file.lastModTime = tf.lastModTime;
file.isFile = tf.isFile;
file.isSymbolicLink = tf.typeFlag == TarFile.TYPE_SYMBOLIC_LINK;
file.nameOfLinkedFile = tf.nameOfLinkedFile;
archive.addFile(file);
}
return archive;
}
}