Index: third_party/mozprofile/mozprofile/permissions.py |
=================================================================== |
--- third_party/mozprofile/mozprofile/permissions.py (revision 0) |
+++ third_party/mozprofile/mozprofile/permissions.py (revision 0) |
@@ -0,0 +1,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 |
+ |
+ 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 or 443. |
+ |
+ 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 |
+ default_ports = {'http': '80', |
+ 'https': '443', |
+ 'ws': '443', |
+ 'wss': '443'} |
+ port = default_ports.get(scheme, '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")) |
+ |
+ prefs.append(("capability.principal.codebase.p%s.id" % i, "%s://%s:%s" % |
+ (l.scheme, l.host, l.port))) |
+ 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] |
+ |
+ origins = ", ".join(origins) |
+ |
+ for l in self._locations: |
+ if "primary" in l.options: |
+ webServer = l.host |
+ port = l.port |
+ |
+ # TODO: this should live in a template! |
+ # TODO: So changing the 5th line of the regex below from (\\\\\\\\d+) |
+ # to (\\\\d+) makes this code work. Not sure why there would be this |
+ # difference between automation.py.in and this file. |
+ 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 || isHttps || isWebSocket || isWebSocketSSL) |
+ return 'PROXY %(remote)s:%(port)s'; |
+ return 'DIRECT'; |
+}""" % { "origins": origins, |
+ "remote": webServer, |
+ "port": port } |
+ 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() |
Property changes on: third_party/mozprofile/mozprofile/permissions.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |