From d7647400b0c98d49b23ff14061bf576d50f685d4 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 7 Aug 2023 15:25:43 +0200 Subject: [PATCH] Update tar (#3976) --- lib/src/third_party/tar/CHANGELOG.md | 8 + lib/src/third_party/tar/analysis_options.yaml | 6 +- lib/src/third_party/tar/lib/src/entry.dart | 7 +- .../third_party/tar/lib/src/exception.dart | 10 +- lib/src/third_party/tar/lib/src/format.dart | 182 +++++++++++------- lib/src/third_party/tar/lib/src/header.dart | 20 +- lib/src/third_party/tar/lib/src/reader.dart | 12 +- lib/src/third_party/tar/lib/src/sparse.dart | 2 +- lib/src/third_party/tar/lib/src/utils.dart | 19 +- lib/src/third_party/tar/lib/src/writer.dart | 14 +- lib/src/third_party/tar/vendored-pubspec.yaml | 10 +- lib/src/third_party/vendor-state.yaml | 2 +- tool/extract_all_pub_dev.dart | 2 +- vendor.yaml | 2 +- 14 files changed, 178 insertions(+), 118 deletions(-) diff --git a/lib/src/third_party/tar/CHANGELOG.md b/lib/src/third_party/tar/CHANGELOG.md index a80455216..590a70db0 100644 --- a/lib/src/third_party/tar/CHANGELOG.md +++ b/lib/src/third_party/tar/CHANGELOG.md @@ -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. diff --git a/lib/src/third_party/tar/analysis_options.yaml b/lib/src/third_party/tar/analysis_options.yaml index 8b18047de..1dd563f70 100644 --- a/lib/src/third_party/tar/analysis_options.yaml +++ b/lib/src/third_party/tar/analysis_options.yaml @@ -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 @@ -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 diff --git a/lib/src/third_party/tar/lib/src/entry.dart b/lib/src/third_party/tar/lib/src/entry.dart index 71d17309e..7645bbb22 100644 --- a/lib/src/third_party/tar/lib/src/entry.dart +++ b/lib/src/third_party/tar/lib/src/entry.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'package:meta/meta.dart'; - import 'header.dart'; /// An entry in a tar file. @@ -9,8 +7,7 @@ import 'header.dart'; /// 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; @@ -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 data; diff --git a/lib/src/third_party/tar/lib/src/exception.dart b/lib/src/third_party/tar/lib/src/exception.dart index 3d9e614a3..fa1fc924e 100644 --- a/lib/src/third_party/tar/lib/src/exception.dart +++ b/lib/src/third_party/tar/lib/src/exception.dart @@ -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); diff --git a/lib/src/third_party/tar/lib/src/format.dart b/lib/src/third_party/tar/lib/src/format.dart index ef65ec9ee..b584f5c05 100644 --- a/lib/src/third_party/tar/lib/src/format.dart +++ b/lib/src/third_party/tar/lib/src/format.dart @@ -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. @@ -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: @@ -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. /// @@ -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. /// @@ -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. /// @@ -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 @@ -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); + } } diff --git a/lib/src/third_party/tar/lib/src/header.dart b/lib/src/third_party/tar/lib/src/header.dart index 2f3566252..e18cddf57 100644 --- a/lib/src/third_party/tar/lib/src/header.dart +++ b/lib/src/third_party/tar/lib/src/header.dart @@ -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; @@ -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 { @@ -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. @@ -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 @@ -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 diff --git a/lib/src/third_party/tar/lib/src/reader.dart b/lib/src/third_party/tar/lib/src/reader.dart index 8502a26d2..c16715159 100644 --- a/lib/src/third_party/tar/lib/src/reader.dart +++ b/lib/src/third_party/tar/lib/src/reader.dart @@ -19,8 +19,7 @@ import 'utils.dart'; /// It is designed to read from a stream and to spit out substreams for /// individual file contents in order to minimize the amount of memory needed /// to read each archive where possible. -@sealed -class TarReader implements StreamIterator { +final class TarReader implements StreamIterator { final BlockReader _reader; final PaxHeaders _paxHeaders = PaxHeaders(); final int _maxSpecialFileSize; @@ -246,7 +245,8 @@ class TarReader implements StreamIterator { /// This methods prevents: /// * concurrent calls to [moveNext] /// * a call to [moveNext] while a stream is active: - /// * if [contents] has never been listened to, we drain the stream + /// * if [TarEntry.contents] has never been listened to, or if it has a + /// cancelled subscription, we drain the stream. /// * otherwise, throws a [StateError] Future _prepareToReadHeaders() async { if (_isDone) { @@ -676,7 +676,7 @@ class TarReader implements StreamIterator { } @internal -class PaxHeaders extends UnmodifiableMapBase { +final class PaxHeaders extends UnmodifiableMapBase { final Map _globalHeaders = {}; Map _localHeaders = {}; @@ -872,7 +872,7 @@ enum _EntryStreamState { /// /// Draining this stream will set the [TarReader._currentStream] field back to /// null. There can only be one content stream at the time. -class _CurrentEntryStream extends Stream> { +final class _CurrentEntryStream extends Stream> { _EntryStreamState state = _EntryStreamState.preListen; final TarReader _reader; @@ -1007,6 +1007,6 @@ class _CurrentEntryStream extends Stream> { _listener.addError( TarException('Unexpected end of tar file'), StackTrace.current); } - _listener.close(); + unawaited(_listener.close()); } } diff --git a/lib/src/third_party/tar/lib/src/sparse.dart b/lib/src/third_party/tar/lib/src/sparse.dart index 35c0311a9..bb938d0ac 100644 --- a/lib/src/third_party/tar/lib/src/sparse.dart +++ b/lib/src/third_party/tar/lib/src/sparse.dart @@ -10,7 +10,7 @@ import 'utils.dart'; /// [SparseEntry]s can represent either data or holes, and we can easily /// convert between the two if we know the size of the file, all the sparse /// data and all the sparse entries combined must give the full size. -class SparseEntry { +final class SparseEntry { final int offset; final int length; diff --git a/lib/src/third_party/tar/lib/src/utils.dart b/lib/src/third_party/tar/lib/src/utils.dart index e2bb5b671..1ea5506bd 100644 --- a/lib/src/third_party/tar/lib/src/utils.dart +++ b/lib/src/third_party/tar/lib/src/utils.dart @@ -241,7 +241,7 @@ Stream> zeroes(int length) async* { } /// An optimized reader reading 512-byte blocks from an input stream. -class BlockReader { +final class BlockReader { final Stream> _input; StreamSubscription>? _subscription; bool _isClosed = false; @@ -300,8 +300,13 @@ class BlockReader { _outgoing = null; _pause(); + // Scheduling this in a microtask becuase the stream controller is + // synchronous. scheduleMicrotask(() { - outgoing.close(); + // We don't need to await this since the stream controller is not used + // afterwards, if there's a paused listener we don't really care about + // that. + unawaited(outgoing.close()); }); return true; } else if (outgoing.isPaused || outgoing.isClosed) { @@ -395,8 +400,14 @@ class BlockReader { } _isClosed = true; - _subscription?.cancel(); - outgoing.close(); + + // Can be unawated because this is an onDone callback of the subscription, + // the subscription is already complete and we're just cleaning up. + unawaited(_subscription?.cancel()); + + // Can be unawated because we're fully done here, we won't do anything else + // with the outgoing controller. + unawaited(outgoing.close()); } void _subscribeOrResume() { diff --git a/lib/src/third_party/tar/lib/src/writer.dart b/lib/src/third_party/tar/lib/src/writer.dart index 13dbdf3c3..24f919083 100644 --- a/lib/src/third_party/tar/lib/src/writer.dart +++ b/lib/src/third_party/tar/lib/src/writer.dart @@ -9,7 +9,8 @@ import 'format.dart'; import 'header.dart'; import 'utils.dart'; -class _WritingTransformer extends StreamTransformerBase> { +final class _WritingTransformer + extends StreamTransformerBase> { final OutputFormat format; const _WritingTransformer(this.format); @@ -19,7 +20,10 @@ class _WritingTransformer extends StreamTransformerBase> { // sync because the controller proxies another stream final controller = StreamController>(sync: true); controller.onListen = () { - stream.pipe(tarWritingSink(controller, format: format)); + // Can be unawaited since it's the only thing done in onListen and since + // pipe is a terminal operation managing the remaining lifecycle of this + // stream controller. + unawaited(stream.pipe(tarWritingSink(controller, format: format))); }; return controller.stream; @@ -152,7 +156,7 @@ enum OutputFormat { gnuLongName, } -class _WritingSink implements StreamSink { +final class _WritingSink implements StreamSink { final StreamSink> _output; final _SynchronousTarSink _synchronousWriter; bool _closed = false; @@ -247,7 +251,7 @@ Uint8List _paddingBytes(int size) { return Uint8List(padding); } -class _SynchronousTarConverter +final class _SynchronousTarConverter extends Converter> { final OutputFormat format; @@ -269,7 +273,7 @@ class _SynchronousTarConverter } } -class _SynchronousTarSink implements Sink { +final class _SynchronousTarSink implements Sink { final OutputFormat _format; final Sink> _output; diff --git a/lib/src/third_party/tar/vendored-pubspec.yaml b/lib/src/third_party/tar/vendored-pubspec.yaml index 0556ca8e7..5d641aa14 100644 --- a/lib/src/third_party/tar/vendored-pubspec.yaml +++ b/lib/src/third_party/tar/vendored-pubspec.yaml @@ -1,10 +1,10 @@ name: tar description: Memory-efficient, streaming implementation of the tar file format -version: 0.5.6 +version: 1.0.1 repository: https://github.com/simolus3/tar/ environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: async: ^2.6.0 @@ -13,12 +13,8 @@ dependencies: dev_dependencies: charcode: ^1.2.0 - extra_pedantic: ^3.0.0 + extra_pedantic: ^4.0.0 file: ^6.1.2 node_io: ^2.1.0 path: ^1.8.0 test: ^1.20.0 - -dependency_overrides: - # Waiting for https://github.com/pulyaevskiy/node-interop/issues/110 - file: '>=6.1.0 <6.1.3' diff --git a/lib/src/third_party/vendor-state.yaml b/lib/src/third_party/vendor-state.yaml index 09c5eebee..62b5a3242 100644 --- a/lib/src/third_party/vendor-state.yaml +++ b/lib/src/third_party/vendor-state.yaml @@ -18,7 +18,7 @@ config: - analysis_options.yaml tar: package: tar - version: 0.5.6 + version: 1.0.1 import_rewrites: {} include: - pubspec.yaml diff --git a/tool/extract_all_pub_dev.dart b/tool/extract_all_pub_dev.dart index 930172348..58f421308 100644 --- a/tool/extract_all_pub_dev.dart +++ b/tool/extract_all_pub_dev.dart @@ -19,7 +19,7 @@ import 'package:pub/src/log.dart' as log; const statusFilename = 'extract_all_pub_status.json'; Future> allPackageNames() async { - var nextUrl = Uri.https('pub.dev', 'api/packages?compact=1'); + var nextUrl = Uri.https('pub.dev', 'api/packages', {'compact': '1'}); final request = http.Request('GET', nextUrl); request.attachMetadataHeaders(); final response = await globalHttpClient.fetch(request); diff --git a/vendor.yaml b/vendor.yaml index 27db379ab..068dae661 100644 --- a/vendor.yaml +++ b/vendor.yaml @@ -7,4 +7,4 @@ vendored_dependencies: version: 2.0.1 tar: package: tar - version: 0.5.6 + version: 1.0.1