Skip to content

Commit

Permalink
Update tar (#3976)
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 authored Aug 7, 2023
1 parent 98565c3 commit d764740
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 118 deletions.
8 changes: 8 additions & 0 deletions lib/src/third_party/tar/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.0.1

- Fix an incompatibility with Dart 3.1.

## 1.0.0

- __Breaking__ Add class modifiers where applicable.

## 0.5.6

- Allow cancelling a `TarEntry.contents` subscription before reading more files.
Expand Down
6 changes: 2 additions & 4 deletions lib/src/third_party/tar/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
include: package:extra_pedantic/analysis_options.2.0.0.yaml
include: package:extra_pedantic/analysis_options.4.0.0.yaml

analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
language:
strict-inference: true
strict-raw-types: true
Expand All @@ -12,6 +9,7 @@ linter:
rules:
close_sinks: false # This rule has just too many false-positives...
comment_references: true
package_api_docs: true
literal_only_boolean_expressions: false # Nothing wrong with a little while(true)
parameter_assignments: false
unnecessary_await_in_return: false
Expand Down
7 changes: 2 additions & 5 deletions lib/src/third_party/tar/lib/src/entry.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import 'dart:async';

import 'package:meta/meta.dart';

import 'header.dart';

/// An entry in a tar file.
///
/// Usually, tar entries are read from a stream, and they're bound to the stream
/// from which they've been read. This means that they can only be read once,
/// and that only one [TarEntry] is active at a time.
@sealed
class TarEntry {
final class TarEntry {
/// The parsed [TarHeader] of this tar entry.
final TarHeader header;

Expand Down Expand Up @@ -58,7 +55,7 @@ class TarEntry {
}

/// A tar entry stored in memory.
class SynchronousTarEntry extends TarEntry {
final class SynchronousTarEntry extends TarEntry {
/// The contents of this tar entry as a byte array.
final List<int> data;

Expand Down
10 changes: 8 additions & 2 deletions lib/src/third_party/tar/lib/src/exception.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import 'package:meta/meta.dart';

/// An exception indicating that there was an issue parsing a `.tar` file.
/// Intended to be seen by the user.
class TarException extends FormatException {
///
/// The [message] contains reported from this exception contains details on the
/// location of the parsing error.
///
/// This is the only exception that should be thrown by the `tar` package. Other
/// exceptions are either a bug in this package or errors thrown as a response
/// to API misuse.
final class TarException extends FormatException {
@internal
TarException(String message) : super(message);

Expand Down
182 changes: 108 additions & 74 deletions lib/src/third_party/tar/lib/src/format.dart
Original file line number Diff line number Diff line change
@@ -1,87 +1,46 @@
import 'package:meta/meta.dart';

/// Handy map to help us translate [TarFormat] values to their names.
/// Be sure to keep this consistent with the constant initializers in
/// [TarFormat].
const _formatNames = {
1: 'V7',
2: 'USTAR',
4: 'PAX',
8: 'GNU',
16: 'STAR',
};

/// Holds the possible TAR formats that a file could take.
///
/// This library only supports the V7, USTAR, PAX, GNU, and STAR formats.
@sealed
class TarFormat {
/// This library supports the V7, USTAR, PAX, GNU, and STAR formats. The
/// [MaybeTarFormat] class generally describes any combination of those formats
/// and represents that we don't know the exact format yet. As soon as we do
/// know, the [TarFormat] enum represents the exact format of a header.
sealed class MaybeTarFormat {
/// The TAR formats are encoded in powers of two in [_value], such that we
/// can refine our guess via bit operations as we discover more information
/// about the TAR file.
/// A value of 0 means that the format is invalid.
final int _value;

const TarFormat._internal(this._value);

@override
int get hashCode => _value;

@override
bool operator ==(Object other) {
if (other is! TarFormat) return false;
int get _value;

return _value == other._value;
factory MaybeTarFormat._(int value) {
return switch (value) {
1 => TarFormat.v7,
2 => TarFormat.ustar,
4 => TarFormat.pax,
8 => TarFormat.gnu,
16 => TarFormat.star,
final other => _MaybeTarFormat(other),
};
}

@override
String toString() {
if (!isValid()) return 'Invalid';

final possibleNames = _formatNames.entries
.where((e) => _value & e.key != 0)
.map((e) => e.value);

return possibleNames.join(' or ');
}

/// Returns if [other] is a possible resolution of `this`.
///
/// For example, a [TarFormat] with a value of 6 means that we do not have
/// enough information to determine if it is [TarFormat.ustar] or
/// [TarFormat.pax], so either of them could be possible resolutions of
/// `this`.
bool has(TarFormat other) => _value & other._value != 0;

/// Returns a new [TarFormat] that signifies that it can be either
/// Returns a new [MaybeTarFormat] that signifies that it can be either
/// `this` or [other]'s format.
///
/// **Example:**
/// ```dart
/// TarFormat format = TarFormat.USTAR | TarFormat.PAX;
/// TarFormat format = TarFormat.ustar | TarFormat.pax;
/// ```
///
/// The above code would signify that we have limited `format` to either
/// the USTAR or PAX format, but need further information to refine the guess.
TarFormat operator |(TarFormat other) {
return mayBe(other);
}
MaybeTarFormat operator |(TarFormat other);

/// Returns a new [TarFormat] that signifies that it can be either
/// `this` or [other]'s format.
///
/// **Example:**
/// ```dart
/// TarFormat format = TarFormat.PAX;
/// format = format.mayBe(TarFormat.USTAR);
/// ```
/// Returns if [other] is a possible resolution of `this`.
///
/// The above code would signify that we learnt that in addition to being a
/// PAX format, it could also be of the USTAR format.
TarFormat mayBe(TarFormat? other) {
if (other == null) return this;
return TarFormat._internal(_value | other._value);
}
/// For example, a [TarFormat] with a value of 6 means that we do not have
/// enough information to determine if it is [TarFormat.ustar] or
/// [TarFormat.pax], so either of them could be possible resolutions of
/// `this`.
bool has(MaybeTarFormat other);

/// Returns a new [TarFormat] that signifies that it can only be [other]'s
/// format.
Expand All @@ -98,16 +57,20 @@ class TarFormat {
///
/// If `has(other) == false`, [mayOnlyBe] will result in an unknown
/// [TarFormat].
TarFormat mayOnlyBe(TarFormat other) {
return TarFormat._internal(_value & other._value);
}
MaybeTarFormat mayOnlyBe(MaybeTarFormat other);

/// Returns if this format might be valid.
///
/// This returns true as well even if we have yet to fully determine what the
/// format is.
bool isValid() => _value > 0;
bool get valid;
}

/// A fully resolved tar format.
///
/// When we know that a tar entry must use a specific format, this is represented
/// with a value from this [TarFormat] enum.
enum TarFormat implements MaybeTarFormat {
/// Original Unix Version 7 (V7) AT&T tar tool prior to standardization.
///
/// The structure of the V7 Header consists of the following:
Expand All @@ -132,7 +95,7 @@ class TarFormat {
/// https://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&format=html
/// https://www.gnu.org/software/tar/manual/html_chapter/tar_15.html#SEC188
/// http://cdrtools.sourceforge.net/private/man/star/star.4.html
static const v7 = TarFormat._internal(1);
v7(1, 'V7'),

/// USTAR (Unix Standard TAR) header format defined in POSIX.1-1988.
///
Expand Down Expand Up @@ -178,7 +141,7 @@ class TarFormat {
/// https://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&format=html
/// https://www.gnu.org/software/tar/manual/html_chapter/tar_15.html#SEC188
/// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06
static const ustar = TarFormat._internal(2);
ustar(2, 'USTAR'),

/// PAX header format defined in POSIX.1-2001.
///
Expand All @@ -196,7 +159,7 @@ class TarFormat {
/// https://www.gnu.org/software/tar/manual/html_chapter/tar_15.html#SEC188
/// http://cdrtools.sourceforge.net/private/man/star/star.4.html
/// http://pubs.opengroup.org/onlinepubs/009695399/utilities/pax.html
static const pax = TarFormat._internal(4);
pax(4, 'PAX'),

/// GNU header format.
///
Expand Down Expand Up @@ -246,7 +209,7 @@ class TarFormat {
///
/// Reference:
/// https://www.gnu.org/software/tar/manual/html_node/Standard.html
static const gnu = TarFormat._internal(8);
gnu(8, 'GNU'),

/// Schily's TAR format, which is incompatible with USTAR.
/// This does not cover STAR extensions to the PAX format; these fall under
Expand Down Expand Up @@ -284,5 +247,76 @@ class TarFormat {
///
/// Reference:
/// http://cdrtools.sourceforge.net/private/man/star/star.4.html
static const star = TarFormat._internal(16);
star(16, 'STAR'),
;

@override
final int _value;

final String _name;

const TarFormat(this._value, this._name);

@override
bool get valid => true;

@override
MaybeTarFormat operator |(TarFormat other) {
return other == this ? this : _MaybeTarFormat(_value | other._value);
}

@override
bool has(MaybeTarFormat other) {
return other == this;
}

@override
MaybeTarFormat mayOnlyBe(MaybeTarFormat other) {
return MaybeTarFormat._(_value & other._value);
}

@override
String toString() => _name;
}

final class _MaybeTarFormat implements MaybeTarFormat {
// Note: We never represent a single tar format in a _MaybeTarFormat, these
// are represented in the TarFormat enum.
@override
final int _value;

const _MaybeTarFormat(this._value);

@override
int get hashCode => _value;

@override
bool operator ==(Object other) {
if (other is! TarFormat) return false;

return _value == other._value;
}

@override
String toString() {
if (!valid) return 'Invalid';

return TarFormat.values.where(has).map((e) => e._name).join(' or ');
}

@override
bool has(MaybeTarFormat other) => _value & other._value != 0;

@override
bool get valid => _value != 0;

@override
MaybeTarFormat mayOnlyBe(MaybeTarFormat other) {
return MaybeTarFormat._(_value & other._value);
}

@override
MaybeTarFormat operator |(TarFormat other) {
return MaybeTarFormat._(_value | other._value);
}
}
20 changes: 13 additions & 7 deletions lib/src/third_party/tar/lib/src/header.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ enum TypeFlag {
///
/// A tar header stores meta-information about the matching tar entry, such as
/// its name.
@sealed
abstract class TarHeader {
sealed class TarHeader {
/// Type of header entry. In the V7 TAR format, this field was known as the
/// link flag.
TypeFlag get typeFlag;
Expand Down Expand Up @@ -125,7 +124,11 @@ abstract class TarHeader {
int get devMinor;

/// The TAR format of the header.
TarFormat get format;
///
/// When this library is sure it knows the format of the tar entry, this will
/// be a [TarFormat] enum value. In other cases, a [MaybeTarFormat] could
/// represent multiple possible formats.
MaybeTarFormat get format;

/// Checks if this header indicates that the file will have content.
bool get hasContent {
Expand Down Expand Up @@ -226,11 +229,14 @@ class HeaderImpl extends TarHeader {
int devMinor;

@override
TarFormat format;
MaybeTarFormat format;

@override
TypeFlag get typeFlag {
return internalTypeFlag == TypeFlag.regA ? TypeFlag.reg : internalTypeFlag;
return switch (internalTypeFlag) {
TypeFlag.regA => TypeFlag.reg, // normalize
final other => other,
};
}

/// This constructor is meant to help us deal with header-only headers (i.e.
Expand Down Expand Up @@ -281,7 +287,7 @@ class HeaderImpl extends TarHeader {
throw TarException.header('Indicates an invalid size of $size');
}

if (format.isValid() && format != TarFormat.v7) {
if (format.valid && format != TarFormat.v7) {
// If it's a valid header that is not of the v7 format, it will have the
// USTAR fields
header
Expand Down Expand Up @@ -370,7 +376,7 @@ class HeaderImpl extends TarHeader {
/// Checks that [rawHeader] represents a valid tar header based on the
/// checksum, and then attempts to guess the specific format based
/// on magic values. If the checksum fails, then an error is thrown.
TarFormat _getFormat(Uint8List rawHeader) {
MaybeTarFormat _getFormat(Uint8List rawHeader) {
final checksum = rawHeader.readOctal(checksumOffset, checksumLength);

// Modern TAR archives use the unsigned checksum, but we check the signed
Expand Down
Loading

0 comments on commit d764740

Please sign in to comment.