blob: a720829cd3e4e5d5dba89dc44a734f0e7108a87a [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'dart:convert';
import 'dart:io' as io;
import 'package:archive/archive.dart' as a;
import 'package:path/path.dart' as path;
import 'cache.dart';
import 'limits.dart';
import 'patterns.dart';
enum FileType {
binary, // won't have its own license block
text, // might have its own UTF-8 license block
latin1Text, // might have its own Windows-1252 license block
zip, // should be parsed as an archive and drilled into
tar, // should be parsed as an archive and drilled into
gz, // should be parsed as a single compressed file and exposed
bzip2, // should be parsed as a single compressed file and exposed
notPartOfBuild, // can be skipped entirely (e.g. Mac OS X ._foo files, bash scripts)
}
typedef Reader = List<int> Function();
class BytesOf extends Key {
BytesOf(super.value);
}
class UTF8Of extends Key {
UTF8Of(super.value);
}
class Latin1Of extends Key {
Latin1Of(super.value);
}
bool matchesSignature(List<int> bytes, List<int> signature) {
if (bytes.length < signature.length) {
return false;
}
for (int index = 0; index < signature.length; index += 1) {
if (signature[index] != -1 && bytes[index] != signature[index]) {
return false;
}
}
return true;
}
bool hasSubsequence(List<int> bytes, List<int> signature, int limit) {
if (bytes.length < limit) {
limit = bytes.length;
}
for (int index = 0; index < limit; index += 1) {
if (bytes.length - index < signature.length) {
return false;
}
for (int offset = 0; offset < signature.length; offset += 1) {
if (signature[offset] != -1 && bytes[index + offset] != signature[offset]) {
break;
}
if (offset + 1 == signature.length) {
return true;
}
}
}
return false;
}
const String kMultiLicenseFileHeader = 'Notices for files contained in';
bool isMultiLicenseNotice(Reader reader) {
final List<int> bytes = reader();
return ascii.decode(bytes.take(kMultiLicenseFileHeader.length).toList(), allowInvalid: true) ==
kMultiLicenseFileHeader;
}
FileType identifyFile(String name, Reader reader) {
List<int>? bytes;
if ((path.split(name).reversed.take(6).toList().reversed.join('/') ==
'third_party/icu/source/extra/uconv/README') || // This specific ICU README isn't in UTF-8.
(path.split(name).reversed.take(6).toList().reversed.join('/') ==
'third_party/icu/source/samples/uresb/sr.txt') || // This specific sample contains non-UTF-8 data (unlike other sr.txt files).
(path.split(name).reversed.take(2).toList().reversed.join('/') ==
'builds/detect.mk') || // This specific freetype sample contains non-UTF-8 data (unlike other .mk files).
(path.split(name).reversed.take(3).toList().reversed.join('/') == 'third_party/cares/cares.rc')) {
return FileType.latin1Text;
}
if (path.split(name).reversed.take(6).toList().reversed.join('/') ==
'dart/runtime/tests/vm/dart/bad_snapshot') {
// Not any particular format
return FileType.binary;
}
if (path.split(name).reversed.take(9).toList().reversed.join('/') ==
'fuchsia/sdk/linux/dart/zircon/lib/src/fakes/handle_disposition.dart' || // has bogus but benign "authors" reference, reported to jamesr@
path.split(name).reversed.take(6).toList().reversed.join('/') ==
'third_party/angle/src/common/fuchsia_egl/fuchsia_egl.c' || // has bogus but benign "authors" reference, reported to author and legal team
path.split(name).reversed.take(6).toList().reversed.join('/') ==
'third_party/angle/src/common/fuchsia_egl/fuchsia_egl.h' || // has bogus but benign "authors" reference, reported to author and legal team
path.split(name).reversed.take(6).toList().reversed.join('/') ==
'third_party/angle/src/common/fuchsia_egl/fuchsia_egl_backend.h') {
// has bogus but benign "authors" reference, reported to author and legal team
return FileType.binary;
}
if (path.split(name).reversed.take(6).toList().reversed.join('/') ==
'flutter/third_party/brotli/c/common/dictionary.bin.br') {
// Brotli-compressed Brotli dictionary
return FileType.binary;
}
final String base = path.basename(name);
if (base.startsWith('._')) {
bytes ??= reader();
if (matchesSignature(bytes, <int>[
0x00,
0x05,
0x16,
0x07,
0x00,
0x02,
0x00,
0x00,
0x4d,
0x61,
0x63,
0x20,
0x4f,
0x53,
0x20,
0x58,
])) {
return FileType.notPartOfBuild;
} // The ._* files in Mac OS X archives that gives icons and stuff
}
if (path.split(name).contains('cairo')) {
bytes ??= reader();
// "Copyright <latin1 copyright symbol> "
if (hasSubsequence(bytes, <int>[
0x43,
0x6f,
0x70,
0x79,
0x72,
0x69,
0x67,
0x68,
0x74,
0x20,
0xA9,
0x20,
], kMaxSize)) {
return FileType.latin1Text;
}
}
switch (base) {
// Build files
case 'DEPS':
return FileType.text;
case 'MANIFEST':
return FileType.text;
// Licenses
case 'COPYING':
return FileType.text;
case 'LICENSE':
return FileType.text;
case 'NOTICE.txt':
return isMultiLicenseNotice(reader) ? FileType.binary : FileType.text;
case 'NOTICE':
return FileType.text;
// Documentation
case 'Changes':
return FileType.text;
case 'change.log':
return FileType.text;
case 'ChangeLog':
return FileType.text;
case 'CHANGES.0':
return FileType.latin1Text;
case 'README':
return FileType.text;
case 'TODO':
return FileType.text;
case 'NEWS':
return FileType.text;
case 'README.chromium':
return FileType.text;
case 'README.flutter':
return FileType.text;
case 'README.tests':
return FileType.text;
case 'OWNERS':
return FileType.text;
case 'AUTHORS':
return FileType.text;
// Signatures (found in .jar files typically)
case 'CERT.RSA':
return FileType.binary;
case 'ECLIPSE_.RSA':
return FileType.binary;
// Binary data files
case 'tzdata':
return FileType.binary;
case 'compressed_atrace_data.txt':
return FileType.binary;
// Source files that don't use UTF-8
case 'Messages_de_DE.properties': // has a few non-ASCII characters they forgot to escape (from gnu-libstdc++)
case 'mmx_blendtmp.h': // author name in comment contains latin1 (mesa)
case 'calling_convention.txt': // contains a soft hyphen instead of a real hyphen for some reason (mesa)
// Character encoding data files
case 'danish-ISO-8859-1.txt':
case 'eucJP.txt':
case 'hangul-eucKR.txt':
case 'hania-eucKR.txt':
case 'ibm-37-test.txt':
case 'iso8859-1.txt':
case 'ISO-8859-2.txt':
case 'ISO-8859-3.txt':
case 'koi8r.txt':
return FileType.latin1Text;
// Giant data files
case 'icudtl_dat.S':
case 'icudtl.dat':
case 'icudtl.dat.hash':
return FileType.binary;
}
switch (path.extension(name)) {
// C/C++ code
case '.h':
return FileType.text;
case '.c':
return FileType.text;
case '.cc':
return FileType.text;
case '.cpp':
return FileType.text;
case '.inc':
return FileType.text;
// Go code
case '.go':
return FileType.text;
// ObjectiveC code
case '.m':
return FileType.text;
// Assembler
case '.asm':
return FileType.text;
// Shell
case '.sh':
return FileType.notPartOfBuild;
case '.bat':
return FileType.notPartOfBuild;
// Build files
case '.ac':
return FileType.notPartOfBuild;
case '.am':
return FileType.notPartOfBuild;
case '.gn':
return FileType.notPartOfBuild;
case '.gni':
return FileType.notPartOfBuild;
case '.gyp':
return FileType.notPartOfBuild;
case '.gypi':
return FileType.notPartOfBuild;
// Java code
case '.java':
return FileType.text;
case '.jar':
return FileType.zip; // Java package
case '.class':
return FileType.binary; // compiled Java bytecode (usually found inside .jar archives)
case '.dex':
return FileType.binary; // Dalvik Executable (usually found inside .jar archives)
// Dart code
case '.dart':
return FileType.text;
case '.dill':
return FileType.binary; // Compiled Dart code
// LLVM bitcode
case '.bc':
return FileType.binary;
// Python code
case '.py':
bytes ??= reader();
// # -*- coding: Latin-1 -*-
if (matchesSignature(bytes, <int>[
0x23,
0x20,
0x2d,
0x2a,
0x2d,
0x20,
0x63,
0x6f,
0x64,
0x69,
0x6e,
0x67,
0x3a,
0x20,
0x4c,
0x61,
0x74,
0x69,
0x6e,
0x2d,
0x31,
0x20,
0x2d,
0x2a,
0x2d,
])) {
return FileType.latin1Text;
}
return FileType.text;
case '.pyc':
return FileType.binary; // compiled Python bytecode
// Machine code
case '.so':
return FileType.binary; // ELF shared object
case '.xpt':
return FileType.binary; // XPCOM Type Library
// Graphics code
case '.glsl':
return FileType.text;
case '.spvasm':
return FileType.text;
// Documentation
case '.md':
return FileType.text;
case '.txt':
return FileType.text;
case '.html':
return FileType.text;
// Fonts
case '.ttf':
return FileType.binary; // TrueType Font
case '.ttcf': // (mac)
case '.ttc':
return FileType.binary; // TrueType Collection (windows)
case '.woff':
return FileType.binary; // Web Open Font Format
case '.otf':
return FileType.binary; // OpenType Font
// Graphics formats
case '.gif':
return FileType.binary; // GIF
case '.png':
return FileType.binary; // PNG
case '.tga':
return FileType.binary; // Truevision TGA (TARGA)
case '.dng':
return FileType.binary; // Digial Negative (Adobe RAW format)
case '.jpg':
case '.jpeg':
return FileType.binary; // JPEG
case '.ico':
return FileType.binary; // Windows icon format
case '.icns':
return FileType.binary; // macOS icon format
case '.bmp':
return FileType.binary; // Windows bitmap format
case '.wbmp':
return FileType.binary; // Wireless bitmap format
case '.webp':
return FileType.binary; // WEBP
case '.pdf':
return FileType.binary; // PDF
case '.emf':
return FileType.binary; // Windows enhanced metafile format
case '.skp':
return FileType.binary; // Skia picture format
case '.mskp':
return FileType.binary; // Skia picture format
case '.spv':
return FileType.binary; // SPIR-V
// Videos
case '.ogg':
return FileType.binary; // Ogg media
case '.mp4':
return FileType.binary; // MPEG media
case '.ts':
return FileType.binary; // MPEG2 transport stream
// Other binary files
case '.raw':
return FileType.binary; // raw audio or graphical data
case '.bin':
return FileType.binary; // some sort of binary data
case '.rsc':
return FileType.binary; // some sort of resource data
case '.arsc':
return FileType.binary; // Android compiled resources
case '.apk':
return FileType.zip; // Android Package
case '.crx':
return FileType.binary; // Chrome extension
case '.keystore':
return FileType.binary;
case '.icc':
return FileType.binary; // Color profile
case '.swp':
return FileType.binary; // Vim swap file
case '.bfbs':
return FileType.binary; // Flatbuffers Binary Schema
// Archives
case '.zip':
return FileType.zip; // ZIP
case '.tar':
return FileType.tar; // Tar
case '.gz':
return FileType.gz; // GZip
case '.bzip2':
return FileType.bzip2; // BZip2
// Image file types from the Fuchsia SDK.
case '.blk':
case '.vboot':
case '.snapshot':
case '.zbi':
return FileType.binary;
// Special cases
case '.patch':
case '.diff':
// Don't try to read the copyright out of patch files, since there'll be fragments.
return FileType.binary;
case '.plist':
// These commonly include the word "copyright" but in a way that isn't necessarily a copyright statement that applies to the file.
// Since there's so few of them, and none have their own copyright statement, we just treat them as binary files.
return FileType.binary;
}
bytes ??= reader();
assert(bytes.isNotEmpty);
if (matchesSignature(bytes, <int>[0x1F, 0x8B])) {
return FileType.gz;
} // GZip archive
if (matchesSignature(bytes, <int>[0x42, 0x5A])) {
return FileType.bzip2;
} // BZip2 archive
if (matchesSignature(bytes, <int>[0x42, 0x43])) {
return FileType.binary;
} // LLVM Bitcode
if (matchesSignature(bytes, <int>[0xAC, 0xED])) {
return FileType.binary;
} // Java serialized object
if (matchesSignature(bytes, <int>[0x4D, 0x5A])) {
return FileType.binary;
} // MZ executable (DOS, Windows PEs, etc)
if (matchesSignature(bytes, <int>[0xFF, 0xD8, 0xFF])) {
return FileType.binary;
} // JPEG
if (matchesSignature(bytes, <int>[-1, -1, 0xda, 0x27])) {
return FileType.binary;
} // ICU data files (.brk, .dict, etc)
if (matchesSignature(bytes, <int>[0x03, 0x00, 0x08, 0x00])) {
return FileType.binary;
} // Android Binary XML
if (matchesSignature(bytes, <int>[0x25, 0x50, 0x44, 0x46])) {
return FileType.binary;
} // PDF
if (matchesSignature(bytes, <int>[0x43, 0x72, 0x32, 0x34])) {
return FileType.binary;
} // Chrome extension
if (matchesSignature(bytes, <int>[0x4F, 0x67, 0x67, 0x53])) {
return FileType.binary;
} // Ogg media
if (matchesSignature(bytes, <int>[0x50, 0x4B, 0x03, 0x04])) {
return FileType.zip;
} // ZIP archive
if (matchesSignature(bytes, <int>[0x7F, 0x45, 0x4C, 0x46])) {
return FileType.binary;
} // ELF
if (matchesSignature(bytes, <int>[0xCA, 0xFE, 0xBA, 0xBE])) {
return FileType.binary;
} // compiled Java bytecode (usually found inside .jar archives)
if (matchesSignature(bytes, <int>[0xCE, 0xFA, 0xED, 0xFE])) {
return FileType.binary;
} // Mach-O binary, 32 bit, reverse byte ordering scheme
if (matchesSignature(bytes, <int>[0xCF, 0xFA, 0xED, 0xFE])) {
return FileType.binary;
} // Mach-O binary, 64 bit, reverse byte ordering scheme
if (matchesSignature(bytes, <int>[0xFE, 0xED, 0xFA, 0xCE])) {
return FileType.binary;
} // Mach-O binary, 32 bit
if (matchesSignature(bytes, <int>[0xFE, 0xED, 0xFA, 0xCF])) {
return FileType.binary;
} // Mach-O binary, 64 bit
if (matchesSignature(bytes, <int>[0x75, 0x73, 0x74, 0x61, 0x72])) {
return FileType.bzip2;
} // Tar
if (matchesSignature(bytes, <int>[0x47, 0x49, 0x46, 0x38, 0x37, 0x61])) {
return FileType.binary;
} // GIF87a
if (matchesSignature(bytes, <int>[0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) {
return FileType.binary;
} // GIF89a
if (matchesSignature(bytes, <int>[0x64, 0x65, 0x78, 0x0A, 0x30, 0x33, 0x35, 0x00])) {
return FileType.binary;
} // Dalvik Executable
if (matchesSignature(bytes, <int>[0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A])) {
// TODO(ianh): implement .ar parser, https://github.com/flutter/flutter/issues/25633
return FileType.binary; // Unix archiver (ar)
}
if (matchesSignature(bytes, <int>[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0a])) {
return FileType.binary;
} // PNG
if (matchesSignature(bytes, <int>[
0x58,
0x50,
0x43,
0x4f,
0x4d,
0x0a,
0x54,
0x79,
0x70,
0x65,
0x4c,
0x69,
0x62,
0x0d,
0x0a,
0x1a,
])) {
return FileType.binary;
} // XPCOM Type Library
if (matchesSignature(bytes, <int>[0x23, 0x21])) {
// #! indicates a shell script, those are not part of the build
return FileType.notPartOfBuild;
}
return FileType.text;
}
String _normalize(String fileContents) {
fileContents = fileContents.replaceAll(newlinePattern, '\n');
fileContents = fileContents.replaceAll('\t', ' ' * 4);
return fileContents;
}
// INTERFACE
// base class
abstract class IoNode {
// Subclasses of IoNode are not mutually exclusive.
// For example, a ZIP file is represented as a File that also implements Directory.
String get name;
String get fullName;
@override
String toString() => fullName;
}
// interface
abstract class File extends IoNode {
List<int>? readBytes();
}
// interface
abstract class TextFile extends File {
String readString();
}
mixin UTF8TextFile implements TextFile {
@override
String readString() {
try {
return cache(UTF8Of(this), () => _normalize(utf8.decode(readBytes()!)));
} on FormatException {
print(fullName);
rethrow;
}
}
}
mixin Latin1TextFile implements TextFile {
@override
String readString() {
return cache(Latin1Of(this), () {
final List<int> bytes = readBytes()!;
if (bytes.any((int byte) => byte == 0x00)) {
throw '$fullName contains a U+0000 NULL and is probably not actually encoded as Win1252';
}
bool isUTF8 = false;
try {
cache(UTF8Of(this), () => utf8.decode(readBytes()!));
isUTF8 = true;
} on FormatException {
// Exceptions are fine/expected for non-UTF8 text, which we test for
// immediately below.
}
if (isUTF8) {
throw '$fullName contains valid UTF-8 and is probably not actually encoded as Win1252';
}
return _normalize(latin1.decode(bytes));
});
}
}
// interface
abstract class Directory extends IoNode {
// lists children (shallow walk, not deep walk)
Iterable<IoNode> get walk;
}
// interface
abstract class Link extends IoNode {}
mixin ZipFile on File implements Directory {
ArchiveDirectory? _root;
@override
Iterable<IoNode> get walk {
try {
_root ??= ArchiveDirectory.parseArchive(a.ZipDecoder().decodeBytes(readBytes()!), fullName);
return _root!.walk;
} catch (exception) {
print('failed to parse archive:\n$fullName');
rethrow;
}
}
}
mixin TarFile on File implements Directory {
ArchiveDirectory? _root;
@override
Iterable<IoNode> get walk {
try {
_root ??= ArchiveDirectory.parseArchive(a.TarDecoder().decodeBytes(readBytes()!), fullName);
return _root!.walk;
} catch (exception) {
print('failed to parse archive:\n$fullName');
rethrow;
}
}
}
mixin GZipFile on File implements Directory {
InMemoryFile? _data;
@override
Iterable<IoNode> get walk sync* {
try {
final String innerName = path.basenameWithoutExtension(fullName);
_data ??=
InMemoryFile.parse('$fullName!$innerName', a.GZipDecoder().decodeBytes(readBytes()!))!;
if (_data != null) {
yield _data!;
}
} catch (exception) {
print('failed to parse archive:\n$fullName');
rethrow;
}
}
}
mixin BZip2File on File implements Directory {
InMemoryFile? _data;
@override
Iterable<IoNode> get walk sync* {
try {
final String innerName = path.basenameWithoutExtension(fullName);
_data ??=
InMemoryFile.parse('$fullName!$innerName', a.BZip2Decoder().decodeBytes(readBytes()!))!;
if (_data != null) {
yield _data!;
}
} catch (exception) {
print('failed to parse archive:\n$fullName');
rethrow;
}
}
}
// FILESYSTEM IMPLEMENTATIoN
class FileSystemDirectory extends IoNode implements Directory {
FileSystemDirectory(this._directory);
factory FileSystemDirectory.fromPath(String name) {
return FileSystemDirectory(io.Directory(name));
}
final io.Directory _directory;
@override
String get name => path.basename(_directory.path);
@override
String get fullName => _directory.path;
List<int> _readBytes(io.File file) {
return cache(BytesOf(file), () => file.readAsBytesSync());
}
@override
Iterable<IoNode> get walk sync* {
final List<io.FileSystemEntity> list = _directory.listSync().toList();
list.sort((io.FileSystemEntity a, io.FileSystemEntity b) => a.path.compareTo(b.path));
for (final io.FileSystemEntity entity in list) {
if (entity is io.Directory) {
yield FileSystemDirectory(entity);
} else if (entity is io.Link) {
yield FileSystemLink(entity);
} else {
assert(entity is io.File);
final io.File fileEntity = entity as io.File;
if (fileEntity.lengthSync() > 0) {
switch (identifyFile(fileEntity.path, () => _readBytes(fileEntity))) {
case FileType.binary:
yield FileSystemFile(fileEntity);
case FileType.zip:
yield FileSystemZipFile(fileEntity);
case FileType.tar:
yield FileSystemTarFile(fileEntity);
case FileType.gz:
yield FileSystemGZipFile(fileEntity);
case FileType.bzip2:
yield FileSystemBZip2File(fileEntity);
case FileType.text:
yield FileSystemUTF8TextFile(fileEntity);
case FileType.latin1Text:
yield FileSystemLatin1TextFile(fileEntity);
case FileType.notPartOfBuild:
break; // ignore this file
}
}
}
}
}
}
class FileSystemLink extends IoNode implements Link {
FileSystemLink(this._link);
final io.Link _link;
@override
String get name => path.basename(_link.path);
@override
String get fullName => _link.path;
}
class FileSystemFile extends IoNode implements File {
FileSystemFile(this._file);
final io.File _file;
@override
String get name => path.basename(_file.path);
@override
String get fullName => _file.path;
@override
List<int> readBytes() {
return cache(BytesOf(_file), () => _file.readAsBytesSync());
}
}
class FileSystemUTF8TextFile extends FileSystemFile with UTF8TextFile {
FileSystemUTF8TextFile(super.file);
}
class FileSystemLatin1TextFile extends FileSystemFile with Latin1TextFile {
FileSystemLatin1TextFile(super.file);
}
class FileSystemZipFile extends FileSystemFile with ZipFile {
FileSystemZipFile(super.file);
}
class FileSystemTarFile extends FileSystemFile with TarFile {
FileSystemTarFile(super.file);
}
class FileSystemGZipFile extends FileSystemFile with GZipFile {
FileSystemGZipFile(super.file);
}
class FileSystemBZip2File extends FileSystemFile with BZip2File {
FileSystemBZip2File(super.file);
}
// ARCHIVES
class ArchiveDirectory extends IoNode implements Directory {
ArchiveDirectory(this.fullName, this.name);
@override
final String fullName;
@override
final String name;
final Map<String, ArchiveDirectory> _subdirectories = SplayTreeMap<String, ArchiveDirectory>();
final List<ArchiveFile> _files = <ArchiveFile>[];
void _add(a.ArchiveFile entry, List<String> remainingPath) {
if (remainingPath.length > 1) {
final String subdirectoryName = remainingPath.removeAt(0);
_subdirectories
.putIfAbsent(
subdirectoryName,
() => ArchiveDirectory('$fullName/$subdirectoryName', subdirectoryName),
)
._add(entry, remainingPath);
} else {
if (entry.size > 0) {
final String entryFullName = '$fullName/${path.basename(entry.name)}';
switch (identifyFile(entry.name, () => entry.content as List<int>)) {
case FileType.binary:
_files.add(ArchiveFile(entryFullName, entry));
case FileType.zip:
_files.add(ArchiveZipFile(entryFullName, entry));
case FileType.tar:
_files.add(ArchiveTarFile(entryFullName, entry));
case FileType.gz:
_files.add(ArchiveGZipFile(entryFullName, entry));
case FileType.bzip2:
_files.add(ArchiveBZip2File(entryFullName, entry));
case FileType.text:
_files.add(ArchiveUTF8TextFile(entryFullName, entry));
case FileType.latin1Text:
_files.add(ArchiveLatin1TextFile(entryFullName, entry));
case FileType.notPartOfBuild:
break; // ignore this file
}
}
}
}
static ArchiveDirectory parseArchive(a.Archive archive, String ownerPath) {
final ArchiveDirectory root = ArchiveDirectory('$ownerPath!', '');
for (final a.ArchiveFile file in archive.files) {
if (file.size > 0) {
root._add(file, file.name.split('/'));
}
}
return root;
}
@override
Iterable<IoNode> get walk sync* {
yield* _subdirectories.values;
yield* _files;
}
}
class ArchiveFile extends IoNode implements File {
ArchiveFile(this.fullName, this._file);
final a.ArchiveFile _file;
@override
String get name => path.basename(_file.name);
@override
final String fullName;
@override
List<int>? readBytes() {
return _file.content as List<int>?;
}
}
class ArchiveUTF8TextFile extends ArchiveFile with UTF8TextFile {
ArchiveUTF8TextFile(super.fullName, super.file);
}
class ArchiveLatin1TextFile extends ArchiveFile with Latin1TextFile {
ArchiveLatin1TextFile(super.fullName, super.file);
}
class ArchiveZipFile extends ArchiveFile with ZipFile {
ArchiveZipFile(super.fullName, super.file);
}
class ArchiveTarFile extends ArchiveFile with TarFile {
ArchiveTarFile(super.fullName, super.file);
}
class ArchiveGZipFile extends ArchiveFile with GZipFile {
ArchiveGZipFile(super.fullName, super.file);
}
class ArchiveBZip2File extends ArchiveFile with BZip2File {
ArchiveBZip2File(super.fullName, super.file);
}
// IN-MEMORY FILES (e.g. contents of GZipped files)
class InMemoryFile extends IoNode implements File {
InMemoryFile(this.fullName, this._bytes);
static InMemoryFile? parse(String fullName, List<int> bytes) {
if (bytes.isEmpty) {
return null;
}
switch (identifyFile(fullName, () => bytes)) {
case FileType.binary:
return InMemoryFile(fullName, bytes);
case FileType.zip:
return InMemoryZipFile(fullName, bytes);
case FileType.tar:
return InMemoryTarFile(fullName, bytes);
case FileType.gz:
return InMemoryGZipFile(fullName, bytes);
case FileType.bzip2:
return InMemoryBZip2File(fullName, bytes);
case FileType.text:
return InMemoryUTF8TextFile(fullName, bytes);
case FileType.latin1Text:
return InMemoryLatin1TextFile(fullName, bytes);
case FileType.notPartOfBuild:
break; // ignore this file
}
assert(false);
return null;
}
final List<int> _bytes;
@override
String get name => '<data>';
@override
final String fullName;
@override
List<int> readBytes() => _bytes;
}
class InMemoryUTF8TextFile extends InMemoryFile with UTF8TextFile {
InMemoryUTF8TextFile(super.fullName, super.file);
}
class InMemoryLatin1TextFile extends InMemoryFile with Latin1TextFile {
InMemoryLatin1TextFile(super.fullName, super.file);
}
class InMemoryZipFile extends InMemoryFile with ZipFile {
InMemoryZipFile(super.fullName, super.file);
}
class InMemoryTarFile extends InMemoryFile with TarFile {
InMemoryTarFile(super.fullName, super.file);
}
class InMemoryGZipFile extends InMemoryFile with GZipFile {
InMemoryGZipFile(super.fullName, super.file);
}
class InMemoryBZip2File extends InMemoryFile with BZip2File {
InMemoryBZip2File(super.fullName, super.file);
}