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