Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: packagerSafari.py

Issue 11544056: Prepared buildtools for Safari (Closed)
Left Patch Set: Created Sept. 9, 2013, 9:25 a.m.
Right Patch Set: Created Oct. 31, 2013, 3:40 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « packagerGecko.py ('k') | safariInfo.js.tmpl » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # coding: utf-8 1 # coding: utf-8
2 2
3 # This file is part of the Adblock Plus build tools, 3 # This file is part of the Adblock Plus build tools,
4 # Copyright (C) 2006-2013 Eyeo GmbH 4 # Copyright (C) 2006-2013 Eyeo GmbH
5 # 5 #
6 # Adblock Plus is free software: you can redistribute it and/or modify 6 # Adblock Plus is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License version 3 as 7 # it under the terms of the GNU General Public License version 3 as
8 # published by the Free Software Foundation. 8 # published by the Free Software Foundation.
9 # 9 #
10 # Adblock Plus is distributed in the hope that it will be useful, 10 # Adblock Plus is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details. 13 # GNU General Public License for more details.
14 # 14 #
15 # You should have received a copy of the GNU General Public License 15 # You should have received a copy of the GNU General Public License
16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
17 17
18 import os 18 import os
19 import re 19 import re
20 import json 20 import json
21 import ConfigParser 21 import ConfigParser
22 from urlparse import urlparse 22 from urlparse import urlparse
23 from collections import OrderedDict
24 23
25 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files 24 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files
26 from buildtools.packagerChrome import convertJS, importGeckoLocales, getIgnoredF iles, getPackageFiles, ImageConverter 25 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa ckageFiles, defaultLocale
27 26
28 def createPlist(params, files): 27 def processFile(path, data, params):
29 template = getTemplate('Info.plist.tmpl') 28 return data
29
30 def createManifest(params, files):
31 template = getTemplate('Info.plist.tmpl', autoEscape=True)
30 metadata = params['metadata'] 32 metadata = params['metadata']
31 catalog = json.loads(files['_locales/en_US/messages.json']) 33 catalog = json.loads(files['_locales/%s/messages.json' % defaultLocale])
32 34
33 def parse_section(section, depth=1): 35 def parse_section(section, depth=1):
34 rv = {} 36 result = {}
35 37
36 if not metadata.has_section(section): 38 if not metadata.has_section(section):
37 return rv 39 return result
38 40
39 for opt in metadata.options(section): 41 for opt in metadata.options(section):
40 bits = opt.split('_', depth) 42 bits = opt.split('_', depth)
41 key = bits.pop(-1).replace('_', ' ').title() 43 key = bits.pop().replace('_', ' ').title()
42 d = rv
43
44 for x in bits:
45 d = d.setdefault(x, {})
46
47 val = metadata.get(section, opt) 44 val = metadata.get(section, opt)
48 45
49 try: 46 try:
50 float(val) 47 val = int(val)
51 except ValueError: 48 except ValueError:
52 type = 'string' 49 try:
53 else: 50 val = float(val)
54 type = 'real' 51 except ValueError:
55 52 pass
56 d[key] = '<%s>%s</%s>' % (type, val, type) 53
57 54 reduce(lambda d, x: d.setdefault(x, {}), bits, result)[key] = val
58 return rv 55
56 return result
59 57
60 def get_optional(*args): 58 def get_optional(*args):
61 try: 59 try:
62 return metadata.get(*args) 60 return metadata.get(*args)
63 except ConfigParser.Error: 61 except ConfigParser.Error:
64 return None 62 return None
65 63
66 allowedDomains = set() 64 allowedDomains = set()
67 allowAllDomains = False 65 allowAllDomains = False
68 allowSecurePages = False 66 allowSecurePages = False
69 67
70 for perm in re.split(r'\s+', metadata.get('general', 'permissions')): 68 for perm in metadata.get('general', 'permissions').split():
71 if perm == '<all_urls>': 69 if perm == '<all_urls>':
72 allowAllDomains = True 70 allowAllDomains = True
73 allowSecurePages = True 71 allowSecurePages = True
74 continue 72 continue
75 73
76 url = urlparse(perm) 74 url = urlparse(perm)
77 75
78 if url.scheme == 'https': 76 if url.scheme == 'https':
79 allowSecurePages = True 77 allowSecurePages = True
80 elif url.scheme != 'http': 78 elif url.scheme != 'http':
81 continue 79 continue
82 80
83 if '*' in url.hostname: 81 if '*' in url.hostname:
84 allowAllDomains = True 82 allowAllDomains = True
85 continue 83 continue
86 84
87 allowedDomains.add(url.hostname) 85 allowedDomains.add(url.hostname)
88 86
89 return template.render( 87 return template.render(
88 basename=metadata.get('general', 'basename'),
90 version=params['version'], 89 version=params['version'],
90 shortVersion=metadata.get('general', 'version'),
91 releaseBuild=params['releaseBuild'],
91 name=catalog['name']['message'], 92 name=catalog['name']['message'],
92 description=catalog['description']['message'], 93 description=catalog['description_safari']['message'],
93 author=get_optional('general', 'author'), 94 author=get_optional('general', 'author'),
94 website=get_optional('general', 'website'), 95 homepage=get_optional('general', 'homepage'),
95 updateURL=get_optional('general', 'updateURL'), 96 updateURL=get_optional('general', 'updateURL'),
96 identifier=metadata.get('general', 'identifier'),
97 allowedDomains=allowedDomains, 97 allowedDomains=allowedDomains,
98 allowAllDomains=allowAllDomains, 98 allowAllDomains=allowAllDomains,
99 allowSecurePages=allowSecurePages, 99 allowSecurePages=allowSecurePages,
100 contentScripts={ 100 startScripts=(get_optional('contentScripts', 'document_start') or '').split( ),
101 'start': metadata.get('contentScripts', 'document_start').split(), 101 endScripts=(get_optional('contentScripts', 'document_end') or '').split(),
102 'end': metadata.get('contentScripts', 'document_end' ).split(),
103 },
104 menus=parse_section('menus', 2), 102 menus=parse_section('menus', 2),
105 toolbarItems=parse_section('toolbar_items'), 103 toolbarItems=parse_section('toolbar_items'),
106 popovers=parse_section('popovers') 104 popovers=parse_section('popovers')
107 ).encode('utf-8') 105 ).encode('utf-8')
108 106
109 def createBackgroundPage(params): 107 def createBackgroundPage(params):
110 template = getTemplate('background.html.tmpl') 108 template = getTemplate('background.html.tmpl', autoEscape=True)
111 return template.render( 109 return template.render(
112 backgroundScripts=re.split(r'\s+', params['metadata'].get( 110 backgroundScripts=params['metadata'].get(
113 'general', 'backgroundScripts' 111 'general', 'backgroundScripts'
114 )) 112 ).split()
115 ).encode('utf-8') 113 ).encode('utf-8')
116 114
117 def createInfoModule(params): 115 def createInfoModule(params):
118 template = getTemplate('safariInfo.js.tmpl') 116 template = getTemplate('safariInfo.js.tmpl')
119 return template.render(params).encode('utf-8'); 117 return template.render(params).encode('utf-8')
120 118
121 def createSignedXarArchive(outFile, files, keyFile, certs): 119 def fixAbsoluteUrls(files):
120 for filename, content in files.iteritems():
121 if os.path.splitext(filename)[1].lower() == '.html':
122 files[filename] = re.sub(
123 r'(<[^<>]*?\b(?:href|src)\s*=\s*["\']?)\/+',
124 r'\1' + '/'.join(['..'] * filename.count('/') + ['']),
125 content, re.S | re.I
126 )
127
128 def createSignedXarArchive(outFile, files, keyFile):
122 import subprocess 129 import subprocess
123 import tempfile 130 import tempfile
124 import shutil 131 import shutil
125 import errno
126 import M2Crypto 132 import M2Crypto
127 133
128 # write files to temporary directory and create a xar archive 134 # write files to temporary directory and create a xar archive
129 dirname = tempfile.mkdtemp() 135 dirname = tempfile.mkdtemp()
130 try: 136 try:
131 for filename, contents in files.iteritems(): 137 for filename, contents in files.iteritems():
132 path = os.path.join(dirname, filename) 138 path = os.path.join(dirname, filename)
133 139
134 while True: 140 try:
135 try: 141 os.makedirs(os.path.dirname(path))
136 file = open(path, 'wb') 142 except OSError:
137 break 143 pass
138 except IOError, e: 144
139 if e.errno != errno.ENOENT: 145 with open(path, 'wb') as file:
140 raise
141 os.makedirs(os.path.dirname(path))
Sebastian Noack 2013/09/10 12:40:43 This uses exactly the same amount of lines of code
142
143 with file:
144 file.write(contents) 146 file.write(contents)
145 147
146 subprocess.check_output( 148 subprocess.check_output(
147 ['xar', '-czf', os.path.abspath(outFile), '--distribution'] + os.listdir(d irname), 149 ['xar', '-czf', os.path.abspath(outFile), '--distribution'] + os.listdir(d irname),
148 cwd=dirname 150 cwd=dirname
149 ) 151 )
150 finally: 152 finally:
151 shutil.rmtree(dirname) 153 shutil.rmtree(dirname)
152 154
153 # add placeholder signature and certificates to the xar archive, and get data to sign 155 certificate_filenames = []
154 key = M2Crypto.RSA.load_key(keyFile)
155 digestinfo_filename = tempfile.mktemp()
156 try: 156 try:
157 subprocess.check_call( 157 # load key and certificates from the all-in-one key file
158 [ 158 # and write each certificate in DER format to a seperate
159 'xar', '--sign', '-f', outFile, 159 # temporary file, that they can be passed to xar
160 '--digestinfo-to-sign', digestinfo_filename, 160 bio = M2Crypto.BIO.openfile(keyFile)
161 '--sig-size', str(len(key.sign(''))) 161 try:
162 ] + [ 162 key = M2Crypto.RSA.load_key_bio(bio)
163 arg for cert in certs for arg in ('--cert-loc', cert) 163
164 ] 164 bio.reset()
165 ) 165 while True:
166 166 try:
167 with open(digestinfo_filename, 'rb') as file: 167 cert = M2Crypto.X509.load_cert_bio(bio)
168 digestinfo = file.read() 168 except M2Crypto.X509.X509Error:
169 break
170
171 fd, filename = tempfile.mkstemp()
172 try:
173 certificate_filenames.append(filename)
174 os.write(fd, cert.as_der())
175 finally:
176 os.close(fd)
177 finally:
178 bio.close()
179
180 # add certificates and placeholder signature
181 # to the xar archive, and get data to sign
182 fd, digestinfo_filename = tempfile.mkstemp()
183 os.close(fd)
184 try:
185 subprocess.check_call(
186 [
187 'xar', '--sign', '-f', outFile,
188 '--digestinfo-to-sign', digestinfo_filename,
189 '--sig-size', str(len(key.private_encrypt('', M2Crypto.RSA.pkcs1_paddi ng)))
190 ] + [
191 arg for cert in certificate_filenames for arg in ('--cert-loc', cert)
192 ]
193 )
194
195 with open(digestinfo_filename, 'rb') as file:
196 digestinfo = file.read()
197 finally:
198 os.unlink(digestinfo_filename)
169 finally: 199 finally:
170 try: 200 for filename in certificate_filenames:
171 os.unlink(digestinfo_filename) 201 os.unlink(filename)
172 except OSError, e:
173 if e.errno != errno.ENOENT:
174 raise
175 202
176 # sign data and inject signature into xar archive 203 # sign data and inject signature into xar archive
177 fd, signature_filename = tempfile.mkstemp() 204 fd, signature_filename = tempfile.mkstemp()
Wladimir Palant 2013/09/10 14:02:12 You are right, my comment was wishful thinking, no
178 try: 205 try:
179 try: 206 try:
180 os.write(fd, key.private_encrypt( 207 os.write(fd, key.private_encrypt(
181 digestinfo, 208 digestinfo,
182 M2Crypto.RSA.pkcs1_padding 209 M2Crypto.RSA.pkcs1_padding
183 )) 210 ))
184 finally: 211 finally:
185 os.close(fd) 212 os.close(fd)
186 213
187 subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', outF ile]) 214 subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', outF ile])
188 finally: 215 finally:
189 os.unlink(signature_filename) 216 os.unlink(signature_filename)
190 217
191 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, keyFile=None, certs=()): 218 def createBuild(baseDir, type, outFile=None, buildNum=None, releaseBuild=False, keyFile=None):
192 metadata = readMetadata(baseDir, type) 219 metadata = readMetadata(baseDir, type)
193 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) 220 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum)
194 221
195 if not outFile: 222 if not outFile:
196 outFile = getDefaultFileName(baseDir, metadata, version, 'safariextz' if key File else 'zip') 223 outFile = getDefaultFileName(baseDir, metadata, version, 'safariextz' if key File else 'zip')
197 224
198 params = { 225 params = {
199 'type': type, 226 'type': type,
200 'baseDir': baseDir, 227 'baseDir': baseDir,
201 'releaseBuild': releaseBuild, 228 'releaseBuild': releaseBuild,
202 'version': version, 229 'version': version,
203 'devenv': False, 230 'devenv': False,
204 'metadata': metadata, 231 'metadata': metadata,
205 } 232 }
206 233
207 files = Files(getPackageFiles(params), getIgnoredFiles(params), 234 files = Files(getPackageFiles(params), getIgnoredFiles(params),
208 process=lambda path, data: data) 235 process=lambda path, data: processFile(path, data, params))
209 if metadata.has_section('mapping'): 236 if metadata.has_section('mapping'):
210 files.readMappedFiles(metadata.items('mapping')) 237 files.readMappedFiles(metadata.items('mapping'))
211 files.read(baseDir) 238 files.read(baseDir)
212 239
213 if metadata.has_section('convert_js'): 240 if metadata.has_section('convert_js'):
214 convertJS(params, files) 241 convertJS(params, files)
215 242
216 if metadata.has_section('convert_img'): 243 if metadata.has_section('convert_img'):
217 ImageConverter().convert(params, files) 244 from imageConversion import convertImages
245 convertImages(params, files)
246
247 if metadata.has_section('preprocess'):
248 files.preprocess(
249 [f for f, _ in metadata.items('preprocess')],
250 {'needsExt': True}
251 )
218 252
219 if metadata.has_section('import_locales'): 253 if metadata.has_section('import_locales'):
220 importGeckoLocales(params, files) 254 importGeckoLocales(params, files)
221 255
222 files['lib/info.js'] = createInfoModule(params) 256 files['lib/info.js'] = createInfoModule(params)
223 files['background.html'] = createBackgroundPage(params) 257 files['background.html'] = createBackgroundPage(params)
224 files['Info.plist'] = createPlist(params, files) 258 files['Info.plist'] = createManifest(params, files)
225 259
226 dirname = getDefaultFileName('', metadata, version, 'safariextension') 260 fixAbsoluteUrls(files)
261
262 dirname = metadata.get('general', 'basename') + '.safariextension'
227 for filename in files.keys(): 263 for filename in files.keys():
228 files[os.path.join(dirname, filename)] = files.pop(filename) 264 files[os.path.join(dirname, filename)] = files.pop(filename)
229 265
230 if keyFile: 266 if keyFile:
231 createSignedXarArchive(outFile, files, keyFile, certs) 267 createSignedXarArchive(outFile, files, keyFile)
232 else: 268 else:
233 files.zip(outFile) 269 files.zip(outFile)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld