1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
|
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
"""
add permissions to the profile
"""
__all__ = ['MissingPrimaryLocationError', 'MultiplePrimaryLocationsError',
'DuplicateLocationError', 'BadPortLocationError',
'LocationsSyntaxError', 'Location', 'ServerLocations',
'Permissions']
import codecs
import itertools
import os
try:
import sqlite3
except ImportError:
from pysqlite2 import dbapi2 as sqlite3
import urlparse
class LocationError(Exception):
"Signifies an improperly formed location."
def __str__(self):
s = "Bad location"
if self.message:
s += ": %s" % self.message
return s
class MissingPrimaryLocationError(LocationError):
"No primary location defined in locations file."
def __init__(self):
LocationError.__init__(self, "missing primary location")
class MultiplePrimaryLocationsError(LocationError):
"More than one primary location defined."
def __init__(self):
LocationError.__init__(self, "multiple primary locations")
class DuplicateLocationError(LocationError):
"Same location defined twice."
def __init__(self, url):
LocationError.__init__(self, "duplicate location: %s" % url)
class BadPortLocationError(LocationError):
"Location has invalid port value."
def __init__(self, given_port):
LocationError.__init__(self, "bad value for port: %s" % given_port)
class LocationsSyntaxError(Exception):
"Signifies a syntax error on a particular line in server-locations.txt."
def __init__(self, lineno, err=None):
self.err = err
self.lineno = lineno
def __str__(self):
s = "Syntax error on line %s" % self.lineno
if self.err:
s += ": %s." % self.err
else:
s += "."
return s
class Location(object):
"Represents a location line in server-locations.txt."
attrs = ('scheme', 'host', 'port')
def __init__(self, scheme, host, port, options):
for attr in self.attrs:
setattr(self, attr, locals()[attr])
self.options = options
try:
int(self.port)
except ValueError:
raise BadPortLocationError(self.port)
def isEqual(self, location):
"compare scheme://host:port, but ignore options"
return len([i for i in self.attrs if getattr(self, i) == getattr(location, i)]) == len(self.attrs)
__eq__ = isEqual
def url(self):
return '%s://%s:%s' % (self.scheme, self.host, self.port)
def __str__(self):
return '%s %s' % (self.url(), ','.join(self.options))
class ServerLocations(object):
"""Iterable collection of locations.
Use provided functions to add new locations, rather that manipulating
_locations directly, in order to check for errors and to ensure the
callback is called, if given.
"""
def __init__(self, filename=None, add_callback=None):
self.add_callback = add_callback
self._locations = []
self.hasPrimary = False
if filename:
self.read(filename)
def __iter__(self):
return self._locations.__iter__()
def __len__(self):
return len(self._locations)
def add(self, location, suppress_callback=False):
if "primary" in location.options:
if self.hasPrimary:
raise MultiplePrimaryLocationsError()
self.hasPrimary = True
for loc in self._locations:
if loc.isEqual(location):
raise DuplicateLocationError(location.url())
self._locations.append(location)
if self.add_callback and not suppress_callback:
self.add_callback([location])
def add_host(self, host, port='80', scheme='http', options='privileged'):
if isinstance(options, basestring):
options = options.split(',')
self.add(Location(scheme, host, port, options))
def read(self, filename, check_for_primary=True):
"""
Reads the file (in the format of server-locations.txt) and add all
valid locations to the self._locations array.
If check_for_primary is True, a MissingPrimaryLocationError
exception is raised if no primary is found.
This format:
http://mxr.mozilla.org/mozilla-central/source/build/pgo/server-locations.txt
The only exception is that the port, if not defined, defaults to 80.
FIXME: Shouldn't this default to the protocol-appropriate port? Is
there any reason to have defaults at all?
"""
locationFile = codecs.open(filename, "r", "UTF-8")
lineno = 0
new_locations = []
for line in locationFile:
line = line.strip()
lineno += 1
# check for comments and blank lines
if line.startswith("#") or not line:
continue
# split the server from the options
try:
server, options = line.rsplit(None, 1)
options = options.split(',')
except ValueError:
server = line
options = []
# parse the server url
if '://' not in server:
server = 'http://' + server
scheme, netloc, path, query, fragment = urlparse.urlsplit(server)
# get the host and port
try:
host, port = netloc.rsplit(':', 1)
except ValueError:
host = netloc
port = '80'
try:
location = Location(scheme, host, port, options)
self.add(location, suppress_callback=True)
except LocationError, e:
raise LocationsSyntaxError(lineno, e)
new_locations.append(location)
# ensure that a primary is found
if check_for_primary and not self.hasPrimary:
raise LocationsSyntaxError(lineno + 1,
MissingPrimaryLocationError())
if self.add_callback:
self.add_callback(new_locations)
class Permissions(object):
_num_permissions = 0
def __init__(self, profileDir, locations=None):
self._profileDir = profileDir
self._locations = ServerLocations(add_callback=self.write_db)
if locations:
if isinstance(locations, ServerLocations):
self._locations = locations
self._locations.add_callback = self.write_db
self.write_db(self._locations._locations)
elif isinstance(locations, list):
for l in locations:
self._locations.add_host(**l)
elif isinstance(locations, dict):
self._locations.add_host(**locations)
elif os.path.exists(locations):
self._locations.read(locations)
def write_db(self, locations):
"""write permissions to the sqlite database"""
# Open database and create table
permDB = sqlite3.connect(os.path.join(self._profileDir, "permissions.sqlite"))
cursor = permDB.cursor();
# SQL copied from
# http://mxr.mozilla.org/mozilla-central/source/extensions/cookie/nsPermissionManager.cpp
cursor.execute("""CREATE TABLE IF NOT EXISTS moz_hosts (
id INTEGER PRIMARY KEY,
host TEXT,
type TEXT,
permission INTEGER,
expireType INTEGER,
expireTime INTEGER)""")
for location in locations:
# set the permissions
permissions = { 'allowXULXBL': 'noxul' not in location.options }
for perm, allow in permissions.iteritems():
self._num_permissions += 1
if allow:
permission_type = 1
else:
permission_type = 2
cursor.execute("INSERT INTO moz_hosts values(?, ?, ?, ?, 0, 0)",
(self._num_permissions, location.host, perm,
permission_type))
# Commit and close
permDB.commit()
cursor.close()
def network_prefs(self, proxy=False):
"""
take known locations and generate preferences to handle permissions and proxy
returns a tuple of prefs, user_prefs
"""
# Grant God-power to all the privileged servers on which tests run.
prefs = []
privileged = [i for i in self._locations if "privileged" in i.options]
for (i, l) in itertools.izip(itertools.count(1), privileged):
prefs.append(("capability.principal.codebase.p%s.granted" % i, "UniversalXPConnect"))
# TODO: do we need the port?
prefs.append(("capability.principal.codebase.p%s.id" % i, l.scheme + "://" + l.host))
prefs.append(("capability.principal.codebase.p%s.subjectName" % i, ""))
if proxy:
user_prefs = self.pac_prefs()
else:
user_prefs = []
return prefs, user_prefs
def pac_prefs(self):
"""
return preferences for Proxy Auto Config. originally taken from
http://mxr.mozilla.org/mozilla-central/source/build/automation.py.in
"""
prefs = []
# We need to proxy every server but the primary one.
origins = ["'%s'" % l.url()
for l in self._locations
if "primary" not in l.options]
origins = ", ".join(origins)
# TODO: this is not a reliable way to determine the Proxy host
for l in self._locations:
if "primary" in l.options:
webServer = l.host
httpPort = l.port
sslPort = 443
# TODO: this should live in a template!
pacURL = """data:text/plain,
function FindProxyForURL(url, host)
{
var origins = [%(origins)s];
var regex = new RegExp('^([a-z][-a-z0-9+.]*)' +
'://' +
'(?:[^/@]*@)?' +
'(.*?)' +
'(?::(\\\\\\\\d+))?/');
var matches = regex.exec(url);
if (!matches)
return 'DIRECT';
var isHttp = matches[1] == 'http';
var isHttps = matches[1] == 'https';
var isWebSocket = matches[1] == 'ws';
var isWebSocketSSL = matches[1] == 'wss';
if (!matches[3])
{
if (isHttp | isWebSocket) matches[3] = '80';
if (isHttps | isWebSocketSSL) matches[3] = '443';
}
if (isWebSocket)
matches[1] = 'http';
if (isWebSocketSSL)
matches[1] = 'https';
var origin = matches[1] + '://' + matches[2] + ':' + matches[3];
if (origins.indexOf(origin) < 0)
return 'DIRECT';
if (isHttp)
return 'PROXY %(remote)s:%(httpport)s';
if (isHttps || isWebSocket || isWebSocketSSL)
return 'PROXY %(remote)s:%(sslport)s';
return 'DIRECT';
}""" % { "origins": origins,
"remote": webServer,
"httpport":httpPort,
"sslport": sslPort }
pacURL = "".join(pacURL.splitlines())
prefs.append(("network.proxy.type", 2))
prefs.append(("network.proxy.autoconfig_url", pacURL))
return prefs
def clean_db(self):
"""Removed permissions added by mozprofile."""
sqlite_file = os.path.join(self._profileDir, "permissions.sqlite")
if not os.path.exists(sqlite_file):
return
# Open database and create table
permDB = sqlite3.connect(sqlite_file)
cursor = permDB.cursor();
# TODO: only delete values that we add, this would require sending in the full permissions object
cursor.execute("DROP TABLE IF EXISTS moz_hosts");
# Commit and close
permDB.commit()
cursor.close()
|