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 ConfigParser | |
7 import json | |
8 import os | |
9 import re | |
10 from urlparse import urlparse | |
11 | |
12 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl
ate, Files | |
13 from packagerChrome import convertJS, import_locales, getIgnoredFiles, getPackag
eFiles, defaultLocale, createScriptPage | |
14 | |
15 | |
16 def processFile(path, data, params): | |
17 return data | |
18 | |
19 | |
20 def createManifest(params, files): | |
21 template = getTemplate('Info.plist.tmpl', autoEscape=True) | |
22 metadata = params['metadata'] | |
23 catalog = json.loads(files['_locales/%s/messages.json' % defaultLocale]) | |
24 | |
25 def parse_section(section, depth=1): | |
26 result = {} | |
27 | |
28 if not metadata.has_section(section): | |
29 return result | |
30 | |
31 for opt in metadata.options(section): | |
32 bits = opt.split('_', depth) | |
33 key = bits.pop().replace('_', ' ').title() | |
34 val = metadata.get(section, opt) | |
35 | |
36 try: | |
37 val = int(val) | |
38 except ValueError: | |
39 try: | |
40 val = float(val) | |
41 except ValueError: | |
42 pass | |
43 | |
44 reduce(lambda d, x: d.setdefault(x, {}), bits, result)[key] = val | |
45 | |
46 return result | |
47 | |
48 def get_optional(*args): | |
49 try: | |
50 return metadata.get(*args) | |
51 except ConfigParser.Error: | |
52 return None | |
53 | |
54 allowedDomains = set() | |
55 allowAllDomains = False | |
56 allowSecurePages = False | |
57 | |
58 for perm in metadata.get('general', 'permissions').split(): | |
59 if perm == '<all_urls>': | |
60 allowAllDomains = True | |
61 allowSecurePages = True | |
62 continue | |
63 | |
64 url = urlparse(perm) | |
65 | |
66 if url.scheme == 'https': | |
67 allowSecurePages = True | |
68 elif url.scheme != 'http': | |
69 continue | |
70 | |
71 if '*' in url.hostname: | |
72 allowAllDomains = True | |
73 continue | |
74 | |
75 allowedDomains.add(url.hostname) | |
76 | |
77 return template.render( | |
78 basename=metadata.get('general', 'basename'), | |
79 version=params['version'], | |
80 releaseBuild=params['releaseBuild'], | |
81 name=catalog['name']['message'], | |
82 description=catalog['description']['message'], | |
83 author=get_optional('general', 'author'), | |
84 homepage=get_optional('general', 'homepage'), | |
85 updateURL=get_optional('general', 'updateURL'), | |
86 allowedDomains=allowedDomains, | |
87 allowAllDomains=allowAllDomains, | |
88 allowSecurePages=allowSecurePages, | |
89 startScripts=(get_optional('contentScripts', 'document_start') or '').sp
lit(), | |
90 endScripts=(get_optional('contentScripts', 'document_end') or '').split(
), | |
91 menus=parse_section('menus', 2), | |
92 toolbarItems=parse_section('toolbar_items'), | |
93 popovers=parse_section('popovers'), | |
94 developerIdentifier=params.get('developerIdentifier') | |
95 ).encode('utf-8') | |
96 | |
97 | |
98 def createInfoModule(params): | |
99 template = getTemplate('safariInfo.js.tmpl') | |
100 return template.render(params).encode('utf-8') | |
101 | |
102 | |
103 def _get_sequence(data): | |
104 from Crypto.Util import asn1 | |
105 sequence = asn1.DerSequence() | |
106 sequence.decode(data) | |
107 return sequence | |
108 | |
109 | |
110 def get_developer_identifier(certs): | |
111 for cert in certs: | |
112 # See https://tools.ietf.org/html/rfc5280#section-4 | |
113 tbscertificate = _get_sequence(base64.b64decode(cert))[0] | |
114 subject = _get_sequence(tbscertificate)[5] | |
115 | |
116 # We could decode the subject but since we have to apply a regular | |
117 # expression on CN entry anyway we can just skip that. | |
118 m = re.search(r'Safari Developer: \((\S*?)\)', subject) | |
119 if m: | |
120 return m.group(1) | |
121 | |
122 raise Exception('No Safari developer certificate found in chain') | |
123 | |
124 | |
125 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False,
keyFile=None, devenv=False): | |
126 metadata = readMetadata(baseDir, type) | |
127 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) | |
128 | |
129 if not outFile: | |
130 outFile = getDefaultFileName(metadata, version, 'safariextz' if keyFile
else 'zip') | |
131 | |
132 params = { | |
133 'type': type, | |
134 'baseDir': baseDir, | |
135 'releaseBuild': releaseBuild, | |
136 'version': version, | |
137 'devenv': devenv, | |
138 'metadata': metadata, | |
139 } | |
140 | |
141 mapped = metadata.items('mapping') if metadata.has_section('mapping') else [
] | |
142 files = Files(getPackageFiles(params), getIgnoredFiles(params), | |
143 process=lambda path, data: processFile(path, data, params)) | |
144 files.readMappedFiles(mapped) | |
145 files.read(baseDir, skip=[opt for opt, _ in mapped]) | |
146 | |
147 if metadata.has_section('convert_js'): | |
148 convertJS(params, files) | |
149 | |
150 if metadata.has_section('preprocess'): | |
151 files.preprocess( | |
152 [f for f, _ in metadata.items('preprocess')], | |
153 {'needsExt': True} | |
154 ) | |
155 | |
156 if metadata.has_section('import_locales'): | |
157 import_locales(params, files) | |
158 | |
159 if metadata.has_option('general', 'testScripts'): | |
160 files['qunit/index.html'] = createScriptPage(params, 'testIndex.html.tmp
l', | |
161 ('general', 'testScripts')) | |
162 | |
163 if keyFile: | |
164 from buildtools import xarfile | |
165 certs, key = xarfile.read_certificates_and_key(keyFile) | |
166 params['developerIdentifier'] = get_developer_identifier(certs) | |
167 | |
168 files['lib/info.js'] = createInfoModule(params) | |
169 files['background.html'] = createScriptPage(params, 'background.html.tmpl', | |
170 ('general', 'backgroundScripts')
) | |
171 files['Info.plist'] = createManifest(params, files) | |
172 | |
173 dirname = metadata.get('general', 'basename') + '.safariextension' | |
174 for filename in files.keys(): | |
175 files[os.path.join(dirname, filename)] = files.pop(filename) | |
176 | |
177 if not devenv and keyFile: | |
178 from buildtools import xarfile | |
179 xarfile.create(outFile, files, keyFile) | |
180 else: | |
181 files.zip(outFile) | |
OLD | NEW |