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: Prepared buildtools for Safari Created Sept. 6, 2013, 2:45 p.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 from urlparse import urlparse 22 from urlparse import urlparse
22 from collections import OrderedDict
23 23
24 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files 24 from packager import readMetadata, getDefaultFileName, getBuildVersion, getTempl ate, Files
25 from buildtools.packagerChrome import convertJS, importGeckoLocales, getIgnoredF iles, getPackageFiles, ImageConverter 25 from packagerChrome import convertJS, importGeckoLocales, getIgnoredFiles, getPa ckageFiles, defaultLocale
26 26
27 def createPlist(params, files): 27 def processFile(path, data, params):
28 template = getTemplate('Info.plist.tmpl') 28 return data
29
30 def createManifest(params, files):
31 template = getTemplate('Info.plist.tmpl', autoEscape=True)
29 metadata = params['metadata'] 32 metadata = params['metadata']
30 catalog = json.loads(files['_locales/en_US/messages.json']) 33 catalog = json.loads(files['_locales/%s/messages.json' % defaultLocale])
31 34
32 def parse_section(section, depth=1): 35 def parse_section(section, depth=1):
33 rv = {} 36 result = {}
34 37
35 if not metadata.has_section(section): 38 if not metadata.has_section(section):
36 return rv 39 return result
37 40
38 for opt in metadata.options(section): 41 for opt in metadata.options(section):
39 bits = opt.split('_', depth) 42 bits = opt.split('_', depth)
40 key = bits.pop(-1).replace('_', ' ').title() 43 key = bits.pop().replace('_', ' ').title()
41 d = rv
42
43 for x in bits:
44 d = d.setdefault(x, {})
45
46 val = metadata.get(section, opt) 44 val = metadata.get(section, opt)
47 45
48 if val is True: 46 try:
49 d[key] = '<true/>' 47 val = int(val)
50 elif val is False: 48 except ValueError:
51 d[key] = '<false/>' 49 try:
52 elif isinstance(val, (int, long)): 50 val = float(val)
53 d[key] = '<real>%d</real>' % val 51 except ValueError:
54 else: 52 pass
55 d[key] = '<string>%s</string>' % val 53
56 54 reduce(lambda d, x: d.setdefault(x, {}), bits, result)[key] = val
57 return rv 55
56 return result
57
58 def get_optional(*args):
59 try:
60 return metadata.get(*args)
61 except ConfigParser.Error:
62 return None
58 63
59 allowedDomains = set() 64 allowedDomains = set()
60 allowAllDomains = False 65 allowAllDomains = False
61 allowSecurePages = False 66 allowSecurePages = False
62 67
63 for perm in re.split(r'\s+', metadata.get('general', 'permissions')): 68 for perm in metadata.get('general', 'permissions').split():
64 if perm == '<all_urls>': 69 if perm == '<all_urls>':
65 allowAllDomains = True 70 allowAllDomains = True
66 allowSecurePages = True 71 allowSecurePages = True
67 continue 72 continue
68 73
69 url = urlparse(perm) 74 url = urlparse(perm)
70 75
71 if url.scheme == 'https': 76 if url.scheme == 'https':
72 allowSecurePages = True 77 allowSecurePages = True
73 elif url.scheme != 'http': 78 elif url.scheme != 'http':
74 continue 79 continue
75 80
76 if '*' in url.hostname: 81 if '*' in url.hostname:
77 allowAllDomains = True 82 allowAllDomains = True
78 continue 83 continue
79 84
80 allowedDomains.add(url.hostname) 85 allowedDomains.add(url.hostname)
81 86
82 return template.render( 87 return template.render(
83 author=metadata.get('general', 'author'), 88 basename=metadata.get('general', 'basename'),
84 version=params['version'], 89 version=params['version'],
90 shortVersion=metadata.get('general', 'version'),
91 releaseBuild=params['releaseBuild'],
85 name=catalog['name']['message'], 92 name=catalog['name']['message'],
86 description=catalog['description']['message'], 93 description=catalog['description_safari']['message'],
87 website=metadata.get('general', 'website'), 94 author=get_optional('general', 'author'),
88 identifier=metadata.get('general', 'identifier'), 95 homepage=get_optional('general', 'homepage'),
96 updateURL=get_optional('general', 'updateURL'),
89 allowedDomains=allowedDomains, 97 allowedDomains=allowedDomains,
90 allowAllDomains=allowAllDomains, 98 allowAllDomains=allowAllDomains,
91 allowSecurePages=allowSecurePages, 99 allowSecurePages=allowSecurePages,
92 contentScripts={ 100 startScripts=(get_optional('contentScripts', 'document_start') or '').split( ),
93 'start': metadata.get('contentScripts', 'document_start').split(), 101 endScripts=(get_optional('contentScripts', 'document_end') or '').split(),
94 'end': metadata.get('contentScripts', 'document_end' ).split(),
95 },
96 menus=parse_section('menus', 2), 102 menus=parse_section('menus', 2),
97 toolbarItems=parse_section('toolbar_items'), 103 toolbarItems=parse_section('toolbar_items'),
98 popovers=parse_section('popovers') 104 popovers=parse_section('popovers')
99 ).encode('utf-8') 105 ).encode('utf-8')
100 106
101 def createBackgroundPage(params): 107 def createBackgroundPage(params):
102 template = getTemplate('background.html.tmpl') 108 template = getTemplate('background.html.tmpl', autoEscape=True)
103 return template.render( 109 return template.render(
104 backgroundScripts=re.split(r'\s+', params['metadata'].get( 110 backgroundScripts=params['metadata'].get(
105 'general', 'backgroundScripts' 111 'general', 'backgroundScripts'
106 )) 112 ).split()
107 ).encode('utf-8') 113 ).encode('utf-8')
108 114
109 def createInfoModule(params): 115 def createInfoModule(params):
110 template = getTemplate('safariInfo.js.tmpl') 116 template = getTemplate('safariInfo.js.tmpl')
111 return template.render(params).encode('utf-8'); 117 return template.render(params).encode('utf-8')
112 118
113 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):
114 import subprocess 129 import subprocess
115 import tempfile 130 import tempfile
116 import shutil 131 import shutil
117 import errno
118 import M2Crypto 132 import M2Crypto
119 133
120 # write files to temporary directory and create a xar archive 134 # write files to temporary directory and create a xar archive
121 dirname = tempfile.mkdtemp() 135 dirname = tempfile.mkdtemp()
122 try: 136 try:
123 for filename, contents in files.iteritems(): 137 for filename, contents in files.iteritems():
124 path = os.path.join(dirname, filename) 138 path = os.path.join(dirname, filename)
125 139
126 while True: 140 try:
127 try: 141 os.makedirs(os.path.dirname(path))
128 file = open(path, 'wb') 142 except OSError:
129 break 143 pass
130 except IOError, e: 144
131 if e.errno != errno.ENOENT: 145 with open(path, 'wb') as file:
132 raise
133 os.makedirs(os.path.dirname(path))
134
135 with file:
136 file.write(contents) 146 file.write(contents)
137 147
138 subprocess.check_output( 148 subprocess.check_output(
139 ['xar', '-czf', os.path.abspath(outFile), '--distribution'] + os.listdir(d irname), 149 ['xar', '-czf', os.path.abspath(outFile), '--distribution'] + os.listdir(d irname),
140 cwd=dirname 150 cwd=dirname
141 ) 151 )
142 finally: 152 finally:
143 shutil.rmtree(dirname) 153 shutil.rmtree(dirname)
144 154
145 # add placeholder signature and certificates to the xar archive, and get data to sign 155 certificate_filenames = []
146 key = M2Crypto.RSA.load_key(keyFile)
147 digestinfo_filename = tempfile.mktemp()
148 try: 156 try:
149 subprocess.check_call( 157 # load key and certificates from the all-in-one key file
150 [ 158 # and write each certificate in DER format to a seperate
151 'xar', '--sign', '-f', outFile, 159 # temporary file, that they can be passed to xar
152 '--digestinfo-to-sign', digestinfo_filename, 160 bio = M2Crypto.BIO.openfile(keyFile)
153 '--sig-size', str(len(key.sign(''))) 161 try:
154 ] + [ 162 key = M2Crypto.RSA.load_key_bio(bio)
155 arg for cert in certs for arg in ('--cert-loc', cert) 163
156 ] 164 bio.reset()
157 ) 165 while True:
158 166 try:
159 with open(digestinfo_filename, 'rb') as file: 167 cert = M2Crypto.X509.load_cert_bio(bio)
160 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)
161 finally: 199 finally:
162 try: 200 for filename in certificate_filenames:
163 os.unlink(digestinfo_filename) 201 os.unlink(filename)
164 except OSError, e:
165 if e.errno != errno.ENOENT:
166 raise
167 202
168 # sign data and inject signature into xar archive 203 # sign data and inject signature into xar archive
169 fd, signature_filename = tempfile.mkstemp() 204 fd, signature_filename = tempfile.mkstemp()
170 try: 205 try:
171 try: 206 try:
172 os.write(fd, key.private_encrypt( 207 os.write(fd, key.private_encrypt(
173 digestinfo, 208 digestinfo,
174 M2Crypto.RSA.pkcs1_padding 209 M2Crypto.RSA.pkcs1_padding
175 )) 210 ))
176 finally: 211 finally:
177 os.close(fd) 212 os.close(fd)
178 213
179 subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', outF ile]) 214 subprocess.check_call(['xar', '--inject-sig', signature_filename, '-f', outF ile])
180 finally: 215 finally:
181 os.unlink(signature_filename) 216 os.unlink(signature_filename)
182 217
183 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):
184 metadata = readMetadata(baseDir, type) 219 metadata = readMetadata(baseDir, type)
185 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) 220 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum)
186 221
187 if not outFile: 222 if not outFile:
188 outFile = getDefaultFileName(baseDir, metadata, version, 'safariextz' if key File else 'zip') 223 outFile = getDefaultFileName(baseDir, metadata, version, 'safariextz' if key File else 'zip')
189 224
190 params = { 225 params = {
191 'type': type, 226 'type': type,
192 'baseDir': baseDir, 227 'baseDir': baseDir,
193 'releaseBuild': releaseBuild, 228 'releaseBuild': releaseBuild,
194 'version': version, 229 'version': version,
195 'devenv': False, 230 'devenv': False,
196 'metadata': metadata, 231 'metadata': metadata,
197 } 232 }
198 233
199 files = Files(getPackageFiles(params), getIgnoredFiles(params), 234 files = Files(getPackageFiles(params), getIgnoredFiles(params),
200 process=lambda path, data: data) 235 process=lambda path, data: processFile(path, data, params))
201 if metadata.has_section('mapping'): 236 if metadata.has_section('mapping'):
202 files.readMappedFiles(metadata.items('mapping')) 237 files.readMappedFiles(metadata.items('mapping'))
203 files.read(baseDir) 238 files.read(baseDir)
204 239
205 if metadata.has_section('convert_js'): 240 if metadata.has_section('convert_js'):
206 convertJS(params, files) 241 convertJS(params, files)
207 242
208 if metadata.has_section('convert_img'): 243 if metadata.has_section('convert_img'):
209 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 )
210 252
211 if metadata.has_section('import_locales'): 253 if metadata.has_section('import_locales'):
212 importGeckoLocales(params, files) 254 importGeckoLocales(params, files)
213 255
214 files['lib/info.js'] = createInfoModule(params) 256 files['lib/info.js'] = createInfoModule(params)
215 files['background.html'] = createBackgroundPage(params) 257 files['background.html'] = createBackgroundPage(params)
216 files['Info.plist'] = createPlist(params, files) 258 files['Info.plist'] = createManifest(params, files)
217 259
218 dirname = getDefaultFileName('', metadata, version, 'safariextension') 260 fixAbsoluteUrls(files)
261
262 dirname = metadata.get('general', 'basename') + '.safariextension'
219 for filename in files.keys(): 263 for filename in files.keys():
220 files[os.path.join(dirname, filename)] = files.pop(filename) 264 files[os.path.join(dirname, filename)] = files.pop(filename)
221 265
222 if keyFile: 266 if keyFile:
223 createSignedXarArchive(outFile, files, keyFile, certs) 267 createSignedXarArchive(outFile, files, keyFile)
224 else: 268 else:
225 files.zip(outFile) 269 files.zip(outFile)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld