import 'dart:convert'; import 'dart:typed_data'; import 'package:archive/archive.dart'; import 'package:crypto/crypto.dart'; import 'package:intl/locale.dart'; import 'package:pkpass/pkpass.dart'; import 'package:pkpass/src/utils/file_matcher.dart'; import 'package:pkpass/src/utils/lproj_parser.dart'; final _utf8codec = Utf8Codec(); final _jsonCodec = JsonCodec(); class PassFile { const PassFile(this.metadata, this._folder); static Future parse(Uint8List pass) async { final codec = ZipDecoder(); Archive archive; try { archive = codec.decodeBytes(pass); } catch (e) { throw InvalidEncodingError(); } Map manifest; try { final file = archive.files .singleWhere((element) => element.name == 'manifest.json'); manifest = (_jsonCodec.decode( _utf8codec.decode( file.rawContent?.toUint8List() ?? (file.content as Uint8List), ), ) as Map) .cast(); } catch (e) { throw ManifestNotFoundError(); } final folder = []; await Future.wait( manifest.entries.map( (manifestEntry) async { final file = archive.files .singleWhere((element) => element.name == manifestEntry.key); final content = file.rawContent?.toUint8List() ?? file.content as Uint8List; String hash = sha1.convert(content).toString(); final checksum = manifestEntry.value; if (hash != checksum) { throw ManifestChecksumError(expected: checksum, actual: hash); } folder.add(file); }, ), ); final passFile = archive.singleWhere((element) => element.name == 'pass.json'); final PassMetadata metadata = PassMetadata.fromJson( _jsonCodec.decode(passFile.stringContent) as Map, ); return PassFile(metadata, folder); } final PassMetadata metadata; final List _folder; Uint8List? _matchUtf8List({ required String name, required Locale? locale, required int scale, }) { final files = _folder.map((e) => e.name).toList(); final path = FileMatcher.matchFile( files: files, name: name, scale: scale, locale: locale, extension: 'png', ); if (path == null) return null; final file = _folder.singleWhere((element) => element.name == path); final content = file.rawContent?.toUint8List() ?? file.content as Uint8List; return content; } Uint8List? getBackground({Locale? locale, int scale = 1}) => _matchUtf8List(name: 'background', locale: locale, scale: scale); Uint8List? getFooter({Locale? locale, int scale = 1}) => _matchUtf8List(name: 'footer', locale: locale, scale: scale); Uint8List? getIcon({Locale? locale, int scale = 1}) => _matchUtf8List(name: 'icon', locale: locale, scale: scale); Uint8List? getLogo({Locale? locale, int scale = 1}) => _matchUtf8List(name: 'logo', locale: locale, scale: scale); Uint8List? getStrip({Locale? locale, int scale = 1}) => _matchUtf8List(name: 'strip', locale: locale, scale: scale); Uint8List? getThumbnail({Locale? locale, int scale = 1}) => _matchUtf8List(name: 'thumbnail', locale: locale, scale: scale); Map? getLocalizations(Locale? locale) { final files = _folder.map((e) => e.name).toList(); final paths = FileMatcher.matchLocale( files: files, name: 'pass', extension: 'strings', locale: locale, ); print(paths); if (paths.isEmpty) return null; final file = _folder.singleWhere((element) => element.name == paths.first); return LProjParser.parse(file.stringContent); } } extension on ArchiveFile { String get stringContent => _utf8codec.decode(rawContent?.toUint8List() ?? (content as Uint8List)); }