• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:collection';
6import 'dart:convert';
7import 'dart:io' as io;
8
9import 'package:path/path.dart' as path;
10import 'package:archive/archive.dart' as a;
11
12import 'cache.dart';
13import 'limits.dart';
14
15enum FileType {
16  binary, // won't have its own license block
17  text, // might have its own UTF-8 license block
18  latin1Text, // might have its own Windows-1252 license block
19  zip, // should be parsed as an archive and drilled into
20  tar, // should be parsed as an archive and drilled into
21  gz, // should be parsed as a single compressed file and exposed
22  bzip2, // should be parsed as a single compressed file and exposed
23  metadata, // can be skipped entirely (e.g. Mac OS X ._foo files)
24}
25
26typedef Reader = List<int> Function();
27
28class BytesOf extends Key { BytesOf(dynamic value) : super(value); }
29class UTF8Of extends Key { UTF8Of(dynamic value) : super(value); }
30class Latin1Of extends Key { Latin1Of(dynamic value) : super(value); }
31
32bool matchesSignature(List<int> bytes, List<int> signature) {
33  if (bytes.length < signature.length)
34    return false;
35  for (int index = 0; index < signature.length; index += 1) {
36    if (signature[index] != -1 && bytes[index] != signature[index])
37      return false;
38  }
39  return true;
40}
41
42bool hasSubsequence(List<int> bytes, List<int> signature, int limit) {
43  if (bytes.length < limit)
44    limit = bytes.length;
45  for (int index = 0; index < limit; index += 1) {
46    if (bytes.length - index < signature.length)
47      return false;
48    for (int offset = 0; offset < signature.length; offset += 1) {
49      if (signature[offset] != -1 && bytes[index + offset] != signature[offset])
50        break;
51      if (offset + 1 == signature.length)
52        return true;
53    }
54  }
55  return false;
56}
57
58const String kMultiLicenseFileHeader = 'Notices for files contained in';
59
60bool isMultiLicenseNotice(Reader reader) {
61  final List<int> bytes = reader();
62  return ascii.decode(bytes.take(kMultiLicenseFileHeader.length).toList(), allowInvalid: true) == kMultiLicenseFileHeader;
63}
64
65FileType identifyFile(String name, Reader reader) {
66  List<int> bytes;
67  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.
68      (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).
69      (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).
70      (path.split(name).reversed.take(3).toList().reversed.join('/') == 'third_party/cares/cares.rc')) // This file has a copyright symbol in Latin1 in it
71    return FileType.latin1Text;
72  if (path.split(name).reversed.take(6).toList().reversed.join('/') == 'dart/runtime/tests/vm/dart/bad_snapshot' || // Not any particular format
73      path.split(name).reversed.take(8).toList().reversed.join('/') == 'third_party/android_tools/ndk/sources/cxx-stl/stlport/src/stlport.rc') // uses the word "copyright" but doesn't have a copyright header
74    return FileType.binary;
75  final String base = path.basename(name);
76  if (base.startsWith('._')) {
77    bytes ??= reader();
78    if (matchesSignature(bytes, <int>[0x00, 0x05, 0x16, 0x07, 0x00, 0x02, 0x00, 0x00, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58]))
79      return FileType.metadata; // The ._* files in Mac OS X archives that gives icons and stuff
80  }
81  if (path.split(name).contains('cairo')) {
82    bytes ??= reader();
83    // "Copyright <latin1 copyright symbol> "
84    if (hasSubsequence(bytes, <int>[0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0xA9, 0x20], kMaxSize))
85      return FileType.latin1Text;
86  }
87  switch (base) {
88    // Build files
89    case 'DEPS': return FileType.text;
90    case 'MANIFEST': return FileType.text;
91    // Licenses
92    case 'COPYING': return FileType.text;
93    case 'LICENSE': return FileType.text;
94    case 'NOTICE.txt': return isMultiLicenseNotice(reader) ? FileType.binary : FileType.text;
95    case 'NOTICE': return FileType.text;
96    // Documentation
97    case 'Changes': return FileType.text;
98    case 'change.log': return FileType.text;
99    case 'ChangeLog': return FileType.text;
100    case 'CHANGES.0': return FileType.latin1Text;
101    case 'README': return FileType.text;
102    case 'TODO': return FileType.text;
103    case 'NEWS': return FileType.text;
104    case 'README.chromium': return FileType.text;
105    case 'README.flutter': return FileType.text;
106    case 'README.tests': return FileType.text;
107    case 'OWNERS': return FileType.text;
108    case 'AUTHORS': return FileType.text;
109    // Signatures (found in .jar files typically)
110    case 'CERT.RSA': return FileType.binary;
111    case 'ECLIPSE_.RSA': return FileType.binary;
112    // Binary data files
113    case 'tzdata': return FileType.binary;
114    case 'compressed_atrace_data.txt': return FileType.binary;
115    // Source files that don't use UTF-8
116    case 'Messages_de_DE.properties': // has a few non-ASCII characters they forgot to escape (from gnu-libstdc++)
117    case 'mmx_blendtmp.h': // author name in comment contains latin1 (mesa)
118    case 'calling_convention.txt': // contains a soft hyphen instead of a real hyphen for some reason (mesa)
119    // Character encoding data files
120    case 'danish-ISO-8859-1.txt':
121    case 'eucJP.txt':
122    case 'hangul-eucKR.txt':
123    case 'hania-eucKR.txt':
124    case 'ibm-37-test.txt':
125    case 'iso8859-1.txt':
126    case 'ISO-8859-2.txt':
127    case 'ISO-8859-3.txt':
128    case 'koi8r.txt':
129      return FileType.latin1Text;
130    // Giant data files
131    case 'icudtl_dat.S':
132    case 'icudtl.dat':
133      return FileType.binary;
134  }
135  switch (path.extension(name)) {
136    // C/C++ code
137    case '.h': return FileType.text;
138    case '.c': return FileType.text;
139    case '.cc': return FileType.text;
140    case '.cpp': return FileType.text;
141    case '.inc': return FileType.text;
142    // ObjectiveC code
143    case '.m': return FileType.text;
144    // Assembler
145    case '.asm': return FileType.text;
146    // Shell
147    case '.sh': return FileType.text;
148    case '.bat': return FileType.text;
149    // Build files
150    case '.in': return FileType.text;
151    case '.ac': return FileType.text;
152    case '.am': return FileType.text;
153    case '.gn': return FileType.text;
154    case '.gni': return FileType.text;
155    case '.gyp': return FileType.text;
156    case '.gypi': return FileType.text;
157    // Java code
158    case '.java': return FileType.text;
159    case '.jar': return FileType.zip; // Java package
160    case '.class': return FileType.binary; // compiled Java bytecode (usually found inside .jar archives)
161    case '.dex': return FileType.binary; // Dalvik Executable (usually found inside .jar archives)
162    // Dart code
163    case '.dart': return FileType.text;
164    case '.dill': return FileType.binary; // Compiled Dart code
165    // LLVM bitcode
166    case '.bc': return FileType.binary;
167    // Python code
168    case '.py':
169      bytes ??= reader();
170      // # -*- coding: Latin-1 -*-
171      if (matchesSignature(bytes, <int>[0x23, 0x20, 0x2d, 0x2a, 0x2d, 0x20, 0x63, 0x6f, 0x64,
172                                        0x69, 0x6e, 0x67, 0x3a, 0x20, 0x4c, 0x61, 0x74, 0x69,
173                                        0x6e, 0x2d, 0x31, 0x20, 0x2d, 0x2a, 0x2d]))
174        return FileType.latin1Text;
175      return FileType.text;
176    case '.pyc': return FileType.binary; // compiled Python bytecode
177    // Machine code
178    case '.so': return FileType.binary; // ELF shared object
179    case '.xpt': return FileType.binary; // XPCOM Type Library
180    // Documentation
181    case '.md': return FileType.text;
182    case '.txt': return FileType.text;
183    case '.html': return FileType.text;
184    // Fonts
185    case '.ttf': return FileType.binary; // TrueType Font
186    case '.ttcf': // (mac)
187    case '.ttc': return FileType.binary; // TrueType Collection (windows)
188    case '.woff': return FileType.binary; // Web Open Font Format
189    case '.otf': return FileType.binary; // OpenType Font
190    // Graphics formats
191    case '.gif': return FileType.binary; // GIF
192    case '.png': return FileType.binary; // PNG
193    case '.tga': return FileType.binary; // Truevision TGA (TARGA)
194    case '.dng': return FileType.binary; // Digial Negative (Adobe RAW format)
195    case '.jpg':
196    case '.jpeg': return FileType.binary; // JPEG
197    case '.ico': return FileType.binary; // Windows icon format
198    case '.icns': return FileType.binary; // macOS icon format
199    case '.bmp': return FileType.binary; // Windows bitmap format
200    case '.wbmp': return FileType.binary; // Wireless bitmap format
201    case '.webp': return FileType.binary; // WEBP
202    case '.pdf': return FileType.binary; // PDF
203    case '.emf': return FileType.binary; // Windows enhanced metafile format
204    case '.skp': return FileType.binary; // Skia picture format
205    // Videos
206    case '.ogg': return FileType.binary; // Ogg media
207    case '.mp4': return FileType.binary; // MPEG media
208    case '.ts': return FileType.binary; // MPEG2 transport stream
209    // Other binary files
210    case '.raw': return FileType.binary; // raw audio or graphical data
211    case '.bin': return FileType.binary; // some sort of binary data
212    case '.rsc': return FileType.binary; // some sort of resource data
213    case '.arsc': return FileType.binary; // Android compiled resources
214    case '.apk': return FileType.zip; // Android Package
215    case '.crx': return FileType.binary; // Chrome extension
216    case '.keystore': return FileType.binary;
217    case '.icc': return FileType.binary; // Color profile
218    case '.swp': return FileType.binary; // Vim swap file
219    // Archives
220    case '.zip': return FileType.zip; // ZIP
221    case '.tar': return FileType.tar; // Tar
222    case '.gz': return FileType.gz; // GZip
223    case '.bzip2': return FileType.bzip2; // BZip2
224    // Image file types from the Fuchsia SDK.
225    case '.blk':
226    case '.vboot':
227    case '.snapshot':
228    case '.zbi':
229      return FileType.binary;
230    // Special cases
231    case '.patch':
232    case '.diff':
233      // Don't try to read the copyright out of patch files, since there'll be fragments.
234      return FileType.binary;
235    case '.plist':
236      // These commonly include the word "copyright" but in a way that isn't necessarily a copyright statement that applies to the file.
237      // Since there's so few of them, and none have their own copyright statement, we just treat them as binary files.
238      return FileType.binary;
239  }
240  bytes ??= reader();
241  assert(bytes.isNotEmpty);
242  if (matchesSignature(bytes, <int>[0x1F, 0x8B]))
243    return FileType.gz; // GZip archive
244  if (matchesSignature(bytes, <int>[0x42, 0x5A]))
245    return FileType.bzip2; // BZip2 archive
246  if (matchesSignature(bytes, <int>[0x42, 0x43]))
247    return FileType.binary; // LLVM Bitcode
248  if (matchesSignature(bytes, <int>[0xAC, 0xED]))
249    return FileType.binary; // Java serialized object
250  if (matchesSignature(bytes, <int>[0x4D, 0x5A]))
251    return FileType.binary; // MZ executable (DOS, Windows PEs, etc)
252  if (matchesSignature(bytes, <int>[0xFF, 0xD8, 0xFF]))
253    return FileType.binary; // JPEG
254  if (matchesSignature(bytes, <int>[-1, -1, 0xda, 0x27])) // -1 is a wildcard
255    return FileType.binary; // ICU data files (.brk, .dict, etc)
256  if (matchesSignature(bytes, <int>[0x03, 0x00, 0x08, 0x00]))
257    return FileType.binary; // Android Binary XML
258  if (matchesSignature(bytes, <int>[0x25, 0x50, 0x44, 0x46]))
259    return FileType.binary; // PDF
260  if (matchesSignature(bytes, <int>[0x43, 0x72, 0x32, 0x34]))
261    return FileType.binary; // Chrome extension
262  if (matchesSignature(bytes, <int>[0x4F, 0x67, 0x67, 0x53]))
263    return FileType.binary; // Ogg media
264  if (matchesSignature(bytes, <int>[0x50, 0x4B, 0x03, 0x04]))
265    return FileType.zip; // ZIP archive
266  if (matchesSignature(bytes, <int>[0x7F, 0x45, 0x4C, 0x46]))
267    return FileType.binary; // ELF
268  if (matchesSignature(bytes, <int>[0xCA, 0xFE, 0xBA, 0xBE]))
269    return FileType.binary; // compiled Java bytecode (usually found inside .jar archives)
270  if (matchesSignature(bytes, <int>[0xCE, 0xFA, 0xED, 0xFE]))
271    return FileType.binary; // Mach-O binary, 32 bit, reverse byte ordering scheme
272  if (matchesSignature(bytes, <int>[0xCF, 0xFA, 0xED, 0xFE]))
273    return FileType.binary; // Mach-O binary, 64 bit, reverse byte ordering scheme
274  if (matchesSignature(bytes, <int>[0xFE, 0xED, 0xFA, 0xCE]))
275    return FileType.binary; // Mach-O binary, 32 bit
276  if (matchesSignature(bytes, <int>[0xFE, 0xED, 0xFA, 0xCF]))
277    return FileType.binary; // Mach-O binary, 64 bit
278  if (matchesSignature(bytes, <int>[0x75, 0x73, 0x74, 0x61, 0x72]))
279    return FileType.bzip2; // Tar
280  if (matchesSignature(bytes, <int>[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]))
281    return FileType.binary; // GIF87a
282  if (matchesSignature(bytes, <int>[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]))
283    return FileType.binary; // GIF89a
284  if (matchesSignature(bytes, <int>[0x64, 0x65, 0x78, 0x0A, 0x30, 0x33, 0x35, 0x00]))
285    return FileType.binary; // Dalvik Executable
286  if (matchesSignature(bytes, <int>[0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A])) {
287    // TODO(ianh): implement .ar parser, https://github.com/flutter/flutter/issues/25633
288    return FileType.binary; // Unix archiver (ar)
289  }
290  if (matchesSignature(bytes, <int>[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0a]))
291    return FileType.binary; // PNG
292  if (matchesSignature(bytes, <int>[0x58, 0x50, 0x43, 0x4f, 0x4d, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x4c, 0x69, 0x62, 0x0d, 0x0a, 0x1a]))
293    return FileType.binary; // XPCOM Type Library
294  return FileType.text;
295}
296
297
298// INTERFACE
299
300// base class
301abstract class IoNode {
302  // Subclasses of IoNode are not mutually exclusive.
303  // For example, a ZIP file is represented as a File that also implements Directory.
304  String get name;
305  String get fullName;
306
307  @override
308  String toString() => fullName;
309}
310
311// interface
312abstract class File extends IoNode {
313  List<int> readBytes();
314}
315
316// interface
317abstract class TextFile extends File {
318  String readString();
319}
320
321mixin UTF8TextFile implements TextFile {
322  @override
323  String readString() {
324    try {
325      return cache(UTF8Of(this), () => utf8.decode(readBytes()));
326    } on FormatException {
327      print(fullName);
328      rethrow;
329    }
330  }
331}
332
333mixin Latin1TextFile implements TextFile {
334  @override
335  String readString() {
336    return cache(Latin1Of(this), () {
337      final List<int> bytes = readBytes();
338      if (bytes.any((int byte) => byte == 0x00))
339        throw '$fullName contains a U+0000 NULL and is probably not actually encoded as Win1252';
340      bool isUTF8 = false;
341      try {
342        cache(UTF8Of(this), () => utf8.decode(readBytes()));
343        isUTF8 = true;
344      } on FormatException {
345        // Exceptions are fine/expected for non-UTF8 text, which we test for
346        // immediately below.
347      }
348      if (isUTF8)
349        throw '$fullName contains valid UTF-8 and is probably not actually encoded as Win1252';
350      return latin1.decode(bytes);
351    });
352  }
353}
354
355// interface
356abstract class Directory extends IoNode {
357  Iterable<IoNode> get walk;
358}
359
360// interface
361abstract class Link extends IoNode { }
362
363mixin ZipFile on File implements Directory {
364  ArchiveDirectory _root;
365
366  @override
367  Iterable<IoNode> get walk {
368    try {
369      _root ??= ArchiveDirectory.parseArchive(a.ZipDecoder().decodeBytes(readBytes()), fullName);
370      return _root.walk;
371    } catch (exception) {
372      print('failed to parse archive:\n$fullName');
373      rethrow;
374    }
375  }
376}
377
378mixin TarFile on File implements Directory {
379  ArchiveDirectory _root;
380
381  @override
382  Iterable<IoNode> get walk {
383    try {
384      _root ??= ArchiveDirectory.parseArchive(a.TarDecoder().decodeBytes(readBytes()), fullName);
385      return _root.walk;
386    } catch (exception) {
387      print('failed to parse archive:\n$fullName');
388      rethrow;
389    }
390  }
391}
392
393mixin GZipFile on File implements Directory {
394  InMemoryFile _data;
395
396  @override
397  Iterable<IoNode> get walk sync* {
398    try {
399      final String innerName = path.basenameWithoutExtension(fullName);
400      _data ??= InMemoryFile.parse(fullName + '!' + innerName, a.GZipDecoder().decodeBytes(readBytes()));
401      if (_data != null)
402        yield _data;
403    } catch (exception) {
404      print('failed to parse archive:\n$fullName');
405      rethrow;
406    }
407  }
408}
409
410mixin BZip2File on File implements Directory {
411  InMemoryFile _data;
412
413  @override
414  Iterable<IoNode> get walk sync* {
415    try {
416      final String innerName = path.basenameWithoutExtension(fullName);
417      _data ??= InMemoryFile.parse(fullName + '!' + innerName, a.BZip2Decoder().decodeBytes(readBytes()));
418      if (_data != null)
419        yield _data;
420    } catch (exception) {
421      print('failed to parse archive:\n$fullName');
422      rethrow;
423    }
424  }
425}
426
427
428// FILESYSTEM IMPLEMENTATIoN
429
430class FileSystemDirectory extends IoNode implements Directory {
431  FileSystemDirectory(this._directory);
432
433  factory FileSystemDirectory.fromPath(String name) {
434    return FileSystemDirectory(io.Directory(name));
435  }
436
437  final io.Directory _directory;
438
439  @override
440  String get name => path.basename(_directory.path);
441
442  @override
443  String get fullName => _directory.path;
444
445  List<int> _readBytes(io.File file) {
446    return cache/*List<int>*/(BytesOf(file), () => file.readAsBytesSync());
447  }
448
449  @override
450  Iterable<IoNode> get walk sync* {
451    final List<io.FileSystemEntity> list = _directory.listSync().toList();
452    list.sort((io.FileSystemEntity a, io.FileSystemEntity b) => a.path.compareTo(b.path));
453    for (io.FileSystemEntity entity in list) {
454      if (entity is io.Directory) {
455        yield FileSystemDirectory(entity);
456      } else if (entity is io.Link) {
457        yield FileSystemLink(entity);
458      } else {
459        assert(entity is io.File);
460        final io.File fileEntity = entity;
461        if (fileEntity.lengthSync() > 0) {
462          switch (identifyFile(fileEntity.path, () => _readBytes(fileEntity))) {
463            case FileType.binary: yield FileSystemFile(fileEntity); break;
464            case FileType.zip: yield FileSystemZipFile(fileEntity); break;
465            case FileType.tar: yield FileSystemTarFile(fileEntity); break;
466            case FileType.gz: yield FileSystemGZipFile(fileEntity); break;
467            case FileType.bzip2: yield FileSystemBZip2File(fileEntity); break;
468            case FileType.text: yield FileSystemUTF8TextFile(fileEntity); break;
469            case FileType.latin1Text: yield FileSystemLatin1TextFile(fileEntity); break;
470            case FileType.metadata: break; // ignore this file
471          }
472        }
473      }
474    }
475  }
476}
477
478class FileSystemLink extends IoNode implements Link {
479  FileSystemLink(this._link);
480
481  final io.Link _link;
482
483  @override
484  String get name => path.basename(_link.path);
485
486  @override
487  String get fullName => _link.path;
488}
489
490class FileSystemFile extends IoNode implements File {
491  FileSystemFile(this._file);
492
493  final io.File _file;
494
495  @override
496  String get name => path.basename(_file.path);
497
498  @override
499  String get fullName => _file.path;
500
501  @override
502  List<int> readBytes() {
503    return cache(BytesOf(_file), () => _file.readAsBytesSync());
504  }
505}
506
507class FileSystemUTF8TextFile extends FileSystemFile with UTF8TextFile {
508  FileSystemUTF8TextFile(io.File file) : super(file);
509}
510
511class FileSystemLatin1TextFile extends FileSystemFile with Latin1TextFile {
512  FileSystemLatin1TextFile(io.File file) : super(file);
513}
514
515class FileSystemZipFile extends FileSystemFile with ZipFile {
516  FileSystemZipFile(io.File file) : super(file);
517}
518
519class FileSystemTarFile extends FileSystemFile with TarFile {
520  FileSystemTarFile(io.File file) : super(file);
521}
522
523class FileSystemGZipFile extends FileSystemFile with GZipFile {
524  FileSystemGZipFile(io.File file) : super(file);
525}
526
527class FileSystemBZip2File extends FileSystemFile with BZip2File {
528  FileSystemBZip2File(io.File file) : super(file);
529}
530
531
532// ARCHIVES
533
534class ArchiveDirectory extends IoNode implements Directory {
535  ArchiveDirectory(this.fullName, this.name);
536
537  @override
538  final String fullName;
539
540  @override
541  final String name;
542
543  final Map<String, ArchiveDirectory> _subdirectories = SplayTreeMap<String, ArchiveDirectory>();
544  final List<ArchiveFile> _files = <ArchiveFile>[];
545
546  void _add(a.ArchiveFile entry, List<String> remainingPath) {
547    if (remainingPath.length > 1) {
548      final String subdirectoryName = remainingPath.removeAt(0);
549      _subdirectories.putIfAbsent(
550        subdirectoryName,
551        () => ArchiveDirectory('$fullName/$subdirectoryName', subdirectoryName)
552      )._add(entry, remainingPath);
553    } else {
554      if (entry.size > 0) {
555        final String entryFullName = fullName + '/' + path.basename(entry.name);
556        switch (identifyFile(entry.name, () => entry.content)) {
557          case FileType.binary: _files.add(ArchiveFile(entryFullName, entry)); break;
558          case FileType.zip: _files.add(ArchiveZipFile(entryFullName, entry)); break;
559          case FileType.tar: _files.add(ArchiveTarFile(entryFullName, entry)); break;
560          case FileType.gz: _files.add(ArchiveGZipFile(entryFullName, entry)); break;
561          case FileType.bzip2: _files.add(ArchiveBZip2File(entryFullName, entry)); break;
562          case FileType.text: _files.add(ArchiveUTF8TextFile(entryFullName, entry)); break;
563          case FileType.latin1Text: _files.add(ArchiveLatin1TextFile(entryFullName, entry)); break;
564          case FileType.metadata: break; // ignore this file
565        }
566      }
567    }
568  }
569
570  static ArchiveDirectory parseArchive(a.Archive archive, String ownerPath) {
571    final ArchiveDirectory root = ArchiveDirectory('$ownerPath!', '');
572    for (a.ArchiveFile file in archive.files) {
573      if (file.size > 0)
574        root._add(file, file.name.split('/'));
575    }
576    return root;
577  }
578
579  @override
580  Iterable<IoNode> get walk sync* {
581    yield* _subdirectories.values;
582    yield* _files;
583  }
584}
585
586class ArchiveFile extends IoNode implements File {
587  ArchiveFile(this.fullName, this._file);
588
589  final a.ArchiveFile _file;
590
591  @override
592  String get name => path.basename(_file.name);
593
594  @override
595  final String fullName;
596
597  @override
598  List<int> readBytes() {
599    return _file.content;
600  }
601}
602
603class ArchiveUTF8TextFile extends ArchiveFile with UTF8TextFile {
604  ArchiveUTF8TextFile(String fullName, a.ArchiveFile file) : super(fullName, file);
605}
606
607class ArchiveLatin1TextFile extends ArchiveFile with Latin1TextFile {
608  ArchiveLatin1TextFile(String fullName, a.ArchiveFile file) : super(fullName, file);
609}
610
611class ArchiveZipFile extends ArchiveFile with ZipFile {
612  ArchiveZipFile(String fullName, a.ArchiveFile file) : super(fullName, file);
613}
614
615class ArchiveTarFile extends ArchiveFile with TarFile {
616  ArchiveTarFile(String fullName, a.ArchiveFile file) : super(fullName, file);
617}
618
619class ArchiveGZipFile extends ArchiveFile with GZipFile {
620  ArchiveGZipFile(String fullName, a.ArchiveFile file) : super(fullName, file);
621}
622
623class ArchiveBZip2File extends ArchiveFile with BZip2File {
624  ArchiveBZip2File(String fullName, a.ArchiveFile file) : super(fullName, file);
625}
626
627
628// IN-MEMORY FILES (e.g. contents of GZipped files)
629
630class InMemoryFile extends IoNode implements File {
631  InMemoryFile(this.fullName, this._bytes);
632
633  static InMemoryFile parse(String fullName, List<int> bytes) {
634    if (bytes.isEmpty)
635      return null;
636    switch (identifyFile(fullName, () => bytes)) {
637      case FileType.binary: return InMemoryFile(fullName, bytes); break;
638      case FileType.zip: return InMemoryZipFile(fullName, bytes); break;
639      case FileType.tar: return InMemoryTarFile(fullName, bytes); break;
640      case FileType.gz: return InMemoryGZipFile(fullName, bytes); break;
641      case FileType.bzip2: return InMemoryBZip2File(fullName, bytes); break;
642      case FileType.text: return InMemoryUTF8TextFile(fullName, bytes); break;
643      case FileType.latin1Text: return InMemoryLatin1TextFile(fullName, bytes); break;
644      case FileType.metadata: break; // ignore this file
645    }
646    assert(false);
647    return null;
648  }
649
650  final List<int> _bytes;
651
652  @override
653  String get name => '<data>';
654
655  @override
656  final String fullName;
657
658  @override
659  List<int> readBytes() => _bytes;
660}
661
662class InMemoryUTF8TextFile extends InMemoryFile with UTF8TextFile {
663  InMemoryUTF8TextFile(String fullName, List<int> file) : super(fullName, file);
664}
665
666class InMemoryLatin1TextFile extends InMemoryFile with Latin1TextFile {
667  InMemoryLatin1TextFile(String fullName, List<int> file) : super(fullName, file);
668}
669
670class InMemoryZipFile extends InMemoryFile with ZipFile {
671  InMemoryZipFile(String fullName, List<int> file) : super(fullName, file);
672}
673
674class InMemoryTarFile extends InMemoryFile with TarFile {
675  InMemoryTarFile(String fullName, List<int> file) : super(fullName, file);
676}
677
678class InMemoryGZipFile extends InMemoryFile with GZipFile {
679  InMemoryGZipFile(String fullName, List<int> file) : super(fullName, file);
680}
681
682class InMemoryBZip2File extends InMemoryFile with BZip2File {
683  InMemoryBZip2File(String fullName, List<int> file) : super(fullName, file);
684}
685