blob: 5acafed6e6cc8b1ac041022b5b580416de329ff2 [file] [log] [blame]
import 'dart:collection';
import 'dart:convert';
import 'dart:typed_data';
import 'package:charcode/ascii.dart';
// Source of these constants: https://www.gnu.org/software/tar/manual/html_node/Standard.html
const magic = [$u, $s, $t, $a, $r, 0];
const regtype = $0; // '0' in C
const aregtype = 0; // '\0' in C
const linktype = $1;
const dirtype = $5;
const gnuTypeLongName = $L;
const gnuTypeLongLinkName = $K;
const globalExtended = $g;
const extendedHeader = $x;
const blockSize = 512;
const blockSizeLog2 = 9;
const maxIntFor12CharOct = 0x1ffffffff; // 777 7777 7777 in oct
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_03
const paxHeaderLinkName = 'linkpath';
const paxHeaderPath = 'path';
const paxHeaderUname = 'uname';
const paxHeaderGname = 'gname';
const paxHeaderSize = 'size';
/// These are the pax headers considered when reading tar files.
///
/// Other pax headers are dropped in the reader to avoid memory-based DOS
/// attacks. We already limit the size of a headers file by default, but an
/// attacker could provide many small global header files with bogus keys, which
/// we'd all have to store.
/// With this approach, we can ensure that the reader's buffer will have an
/// upper bound of `(supportedPaxHeaders.length + 1) * maxHeaderSize`.
const supportedPaxHeaders = {
paxHeaderLinkName,
paxHeaderPath,
paxHeaderUname,
paxHeaderGname,
paxHeaderSize,
};
const defaultSpecialLength = blockSize * 2;
extension ToTyped on List<int> {
Uint8List asUint8List() {
// Flow typing doesn't work on this
final $this = this;
return $this is Uint8List ? $this : Uint8List.fromList($this);
}
}
String readZeroTerminated(Uint8List data, int offset, int maxLength) {
final view = data.sublist(offset, offset + maxLength);
var contentLength = view.indexOf(0);
if (contentLength.isNegative) contentLength = maxLength;
return utf8.decode(view.sublist(0, contentLength));
}
/// Extended PAX headers in the POSIX tar format.
class PaxHeaders extends UnmodifiableMapBase<String, String> {
final Map<String, String> _globalHeaders = {};
Map<String, String> _localHeaders = {};
/// The size of the next tar entry as stored in these headers.
int? get size {
final sizeStr = this[paxHeaderSize];
return sizeStr != null ? int.parse(sizeStr) : null;
}
/// The file name of the next tar entry.
String? get fileName => this[paxHeaderPath];
set fileName(String? name) => _setOrRemove(paxHeaderPath, name);
/// The link name of the next tar entry
String? get linkName => this[paxHeaderLinkName];
set linkName(String? name) => _setOrRemove(paxHeaderLinkName, name);
void _setOrRemove(String key, String? value) {
if (value == null) {
_localHeaders.remove(key);
} else {
_localHeaders[key] = value;
}
}
/// Applies new global PAX-headers from the map.
///
/// The [headers] will replace global headers with the same key, but leave
/// others intact.
void newGlobals(Map<String, String> headers) {
_globalHeaders.addAll(headers);
}
/// Applies new local PAX-headers from the map.
///
/// This replaces all currently active local headers.
void newLocals(Map<String, String> headers) {
_localHeaders = headers;
}
/// Clears local headers.
///
/// This is used by the reader after a file has ended, as local headers only
/// apply to the next entry.
void clearLocals() {
_localHeaders = {};
}
@override
String? operator [](Object? key) {
return _globalHeaders[key] ?? _localHeaders[key];
}
@override
Iterable<String> get keys => {..._globalHeaders.keys, ..._localHeaders.keys};
}