| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 # This Source Code Form is subject to the terms of the Mozilla Public | 
|  | 2 # License, v. 2.0. If a copy of the MPL was not distributed with this | 
|  | 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
|  | 4 | 
|  | 5 import base64 | 
|  | 6 import hashlib | 
|  | 7 import mimetypes | 
|  | 8 import os | 
|  | 9 import zipfile | 
|  | 10 | 
|  | 11 import packager | 
|  | 12 import packagerChrome | 
|  | 13 | 
|  | 14 MANIFEST = 'AppxManifest.xml' | 
|  | 15 CONTENT_TYPES = '[Content_Types].xml' | 
|  | 16 BLOCKMAP = 'AppxBlockMap.xml' | 
|  | 17 BLOCKSIZE = 64 * 1024 | 
|  | 18 | 
|  | 19 | 
|  | 20 def _get_template_for(filename): | 
|  | 21     return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 
|  | 22 | 
|  | 23 | 
|  | 24 def _lfh_size(filename): | 
|  | 25     """Compute the size of zip local file header for `filename`.""" | 
|  | 26     try: | 
|  | 27         filename = filename.encode('utf-8') | 
|  | 28     except UnicodeDecodeError: | 
|  | 29         pass  # filename is already a byte string. | 
|  | 30     return zipfile.sizeFileHeader + len(filename) | 
|  | 31 | 
|  | 32 | 
|  | 33 def _make_blockmap_entry(filename, data): | 
|  | 34     blocks = [data[i:i + BLOCKSIZE] for i in range(0, len(data), BLOCKSIZE)] | 
|  | 35     return { | 
|  | 36         'name': filename.replace('/', '\\'), | 
|  | 37         'size': len(data), | 
|  | 38         'lfh_size': _lfh_size(filename), | 
|  | 39         'blocks': [ | 
|  | 40             {'hash': base64.b64encode(hashlib.sha256(block).digest())} | 
|  | 41             for block in blocks | 
|  | 42         ] | 
|  | 43     } | 
|  | 44 | 
|  | 45 | 
|  | 46 def create_appx_blockmap(files): | 
|  | 47     """Create APPX blockmap for the list of files.""" | 
|  | 48     template = _get_template_for(BLOCKMAP) | 
|  | 49     files = [_make_blockmap_entry(n, d) for n, d in files.items()] | 
|  | 50     return template.render(files=files).encode('utf-8') | 
|  | 51 | 
|  | 52 | 
|  | 53 def create_appx_manifest(params, files): | 
|  | 54     """Create AppxManifest.xml.""" | 
|  | 55     template = _get_template_for(MANIFEST) | 
|  | 56     params = dict(params) | 
|  | 57     metadata = params['metadata'] | 
|  | 58     params['package_identity'] = dict(metadata.items('package_identity')) | 
|  | 59     w = params['windows_version'] = {} | 
|  | 60     w['min'], w['max'] = metadata.get('compat', 'windows').split('/') | 
|  | 61     params.update(metadata.items('general')) | 
|  | 62     for size in ['44', '50', '150']: | 
|  | 63         path = 'Assets/logo_{}.png'.format(size) | 
|  | 64         if path not in files: | 
|  | 65             raise KeyError(path + 'is not found in files') | 
|  | 66         params['logo_' + size] = path.replace('/', '\\') | 
|  | 67     return template.render(params).encode('utf-8') | 
|  | 68 | 
|  | 69 | 
|  | 70 def move_files_to_extension(files): | 
|  | 71     """Move all files into `Extension` folder for APPX packaging.""" | 
|  | 72     # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'. | 
|  | 73     # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its | 
|  | 74     # original content would be lost. | 
|  | 75     names = sorted(files.keys(), key=len, reverse=True) | 
|  | 76     for filename in names: | 
|  | 77         files['Extension/' + filename] = files.pop(filename) | 
|  | 78 | 
|  | 79 | 
|  | 80 def create_content_types_map(filenames): | 
|  | 81     """Create [Content_Types].xml -- a mime type map.""" | 
|  | 82     params = {'defaults': {}, 'overrides': {}} | 
|  | 83     overrides = { | 
|  | 84         BLOCKMAP: 'application/vnd.ms-appx.blockmap+xml', | 
|  | 85         MANIFEST: 'application/vnd.ms-appx.manifest+xml' | 
|  | 86     } | 
|  | 87     for filename in filenames: | 
|  | 88         ext = os.path.splitext(filename)[1] | 
|  | 89         if ext: | 
|  | 90             content_type = mimetypes.guess_type(filename, strict=False)[0] | 
|  | 91             if content_type is not None: | 
|  | 92                 params['defaults'][ext[1:]] = content_type | 
|  | 93         if filename in overrides: | 
|  | 94             params['overrides']['/' + filename] = overrides[filename] | 
|  | 95     content_types_template = _get_template_for(CONTENT_TYPES) | 
|  | 96     return content_types_template.render(params).encode('utf-8') | 
|  | 97 | 
|  | 98 | 
|  | 99 def createBuild(baseDir, type='edge', outFile=None,  # noqa: preserve API. | 
|  | 100                 buildNum=None, releaseBuild=False, keyFile=None, | 
|  | 101                 devenv=False): | 
|  | 102 | 
|  | 103     metadata = packager.readMetadata(baseDir, type) | 
|  | 104     version = packager.getBuildVersion(baseDir, metadata, releaseBuild, | 
|  | 105                                        buildNum) | 
|  | 106 | 
|  | 107     outfile = outFile or packager.getDefaultFileName(metadata, version, 'appx') | 
|  | 108 | 
|  | 109     params = { | 
|  | 110         'type': type, | 
|  | 111         'baseDir': baseDir, | 
|  | 112         'releaseBuild': releaseBuild, | 
|  | 113         'version': version, | 
|  | 114         'devenv': devenv, | 
|  | 115         'metadata': metadata, | 
|  | 116     } | 
|  | 117 | 
|  | 118     files = packager.Files(packagerChrome.getPackageFiles(params), | 
|  | 119                            packagerChrome.getIgnoredFiles(params)) | 
|  | 120 | 
|  | 121     if metadata.has_section('mapping'): | 
|  | 122         mapped = metadata.items('mapping') | 
|  | 123         files.readMappedFiles(mapped) | 
|  | 124         files.read(baseDir, skip=[filename for filename, _ in mapped]) | 
|  | 125     else: | 
|  | 126         files.read(baseDir) | 
|  | 127 | 
|  | 128     if metadata.has_section('convert_js'): | 
|  | 129         packagerChrome.convertJS(params, files) | 
|  | 130 | 
|  | 131     if metadata.has_section('preprocess'): | 
|  | 132         files.preprocess(metadata.options('preprocess'), {'needsExt': True}) | 
|  | 133 | 
|  | 134     if metadata.has_section('import_locales'): | 
|  | 135         packagerChrome.importGeckoLocales(params, files) | 
|  | 136 | 
|  | 137     files['manifest.json'] = packagerChrome.createManifest(params, files) | 
|  | 138 | 
|  | 139     move_files_to_extension(files) | 
|  | 140 | 
|  | 141     if metadata.has_section('appx_assets'): | 
|  | 142         for name, path in metadata.items('appx_assets'): | 
|  | 143             path = os.path.join(baseDir, path) | 
|  | 144             files.read(path, 'Assets/' + name) | 
|  | 145 | 
|  | 146     files[MANIFEST] = create_appx_manifest(params, files) | 
|  | 147     files[CONTENT_TYPES] = create_content_types_map(files.keys() + [BLOCKMAP]) | 
|  | 148 | 
|  | 149     # We don't support AppxBlockmap.xml generation for compressed zip files at | 
|  | 150     # the moment. The only way to reliably calculate the compressed size of | 
|  | 151     # each 64k chunk in the zip file is to override the relevant parts of | 
|  | 152     # `zipfile` library. We have chosen to not do it for now, so we produce | 
|  | 153     # an uncompressed zip file. | 
|  | 154     files[BLOCKMAP] = create_appx_blockmap(files) | 
|  | 155 | 
|  | 156     # TODO: Implement AppxBlockmap.xml generation for compressed zip files. | 
|  | 157     # https://issues.adblockplus.org/ticket/4149 | 
|  | 158     files.zip(outfile, compress=False) | 
| OLD | NEW | 
|---|