~ubuntu-branches/ubuntu/precise/enigmail/precise-security

« back to all changes in this revision

Viewing changes to testing/mozbase/mozprofile/mozprofile/addons.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2012-11-12 16:36:01 UTC
  • mfrom: (0.12.15)
  • Revision ID: package-import@ubuntu.com-20121112163601-t8e8skdfi3ni9iqp
Tags: 2:1.4.6-0ubuntu0.12.04.1
* New upstream release v1.4.6
  - see LP: #1080212 for USN information
* Drop unneeded patches
  - remove debian/patches/correct-version-number.diff
  - remove debian/patches/dont_register_cids_multiple_times.diff
  - update debian/patches/series
* Support building in an objdir
  - update debian/rules

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 file,
 
3
# You can obtain one at http://mozilla.org/MPL/2.0/.
 
4
 
 
5
import os
 
6
import shutil
 
7
import tempfile
 
8
import urllib2
 
9
import zipfile
 
10
from distutils import dir_util
 
11
from manifestparser import ManifestParser
 
12
from xml.dom import minidom
 
13
 
 
14
# Needed for the AMO's rest API - https://developer.mozilla.org/en/addons.mozilla.org_%28AMO%29_API_Developers%27_Guide/The_generic_AMO_API
 
15
AMO_API_VERSION = "1.5"
 
16
 
 
17
class AddonManager(object):
 
18
    """
 
19
    Handles all operations regarding addons including: installing and cleaning addons
 
20
    """
 
21
 
 
22
    def __init__(self, profile):
 
23
        """
 
24
        profile - the path to the profile for which we install addons
 
25
        """
 
26
        self.profile = profile
 
27
 
 
28
        # information needed for profile reset:
 
29
        # https://github.com/mozilla/mozbase/blob/270a857328b130860d1b1b512e23899557a3c8f7/mozprofile/mozprofile/profile.py#L93
 
30
        self.installed_addons = []
 
31
        self.installed_manifests = []
 
32
 
 
33
        # addons that we've installed; needed for cleanup
 
34
        self._addon_dirs = []
 
35
 
 
36
    def install_addons(self, addons=None, manifests=None):
 
37
        """
 
38
        Installs all types of addons
 
39
        addons - a list of addon paths to install
 
40
        manifest - a list of addon manifests to install
 
41
        """
 
42
        # install addon paths
 
43
        if addons:
 
44
            if isinstance(addons, basestring):
 
45
                addons = [addons]
 
46
            self.installed_addons.extend(addons)
 
47
            for addon in addons:
 
48
                self.install_from_path(addon)
 
49
        # install addon manifests
 
50
        if manifests:
 
51
            if isinstance(manifests, basestring):
 
52
                manifests = [manifests]
 
53
            for manifest in manifests:
 
54
                self.install_from_manifest(manifest)
 
55
            self.installed_manifests.extended(manifests)
 
56
 
 
57
    def install_from_manifest(self, filepath):
 
58
        """
 
59
        Installs addons from a manifest
 
60
        filepath - path to the manifest of addons to install
 
61
        """
 
62
        manifest = ManifestParser()
 
63
        manifest.read(filepath)
 
64
        addons = manifest.get()
 
65
 
 
66
        for addon in addons:
 
67
            if '://' in addon['path'] or os.path.exists(addon['path']):
 
68
                self.install_from_path(addon['path'])
 
69
                continue
 
70
 
 
71
            # No path specified, try to grab it off AMO
 
72
            locale = addon.get('amo_locale', 'en_US')
 
73
 
 
74
            query = 'https://services.addons.mozilla.org/' + locale + '/firefox/api/' + AMO_API_VERSION + '/'
 
75
            if 'amo_id' in addon:
 
76
                query += 'addon/' + addon['amo_id']                 # this query grabs information on the addon base on its id
 
77
            else:
 
78
                query += 'search/' + addon['name'] + '/default/1'   # this query grabs information on the first addon returned from a search
 
79
            install_path = AddonManager.get_amo_install_path(query)
 
80
            self.install_from_path(install_path)
 
81
 
 
82
    @classmethod
 
83
    def get_amo_install_path(self, query):
 
84
        """
 
85
        Return the addon xpi install path for the specified AMO query.
 
86
        See: https://developer.mozilla.org/en/addons.mozilla.org_%28AMO%29_API_Developers%27_Guide/The_generic_AMO_API
 
87
        for query documentation.
 
88
        """
 
89
        response = urllib2.urlopen(query)
 
90
        dom = minidom.parseString(response.read())
 
91
        for node in dom.getElementsByTagName('install')[0].childNodes:
 
92
            if node.nodeType == node.TEXT_NODE:
 
93
                return node.data
 
94
 
 
95
    @classmethod
 
96
    def addon_details(cls, addon_path):
 
97
        """
 
98
        returns a dictionary of details about the addon
 
99
        - addon_path : path to the addon directory
 
100
        Returns:
 
101
        {'id':      u'rainbow@colors.org', # id of the addon
 
102
         'version': u'1.4',                # version of the addon
 
103
         'name':    u'Rainbow',            # name of the addon
 
104
         'unpack':  False } # whether to unpack the addon
 
105
        """
 
106
 
 
107
        # TODO: We don't use the unpack variable yet, but we should: bug 662683
 
108
        details = {
 
109
            'id': None,
 
110
            'unpack': False,
 
111
            'name': None,
 
112
            'version': None
 
113
        }
 
114
 
 
115
        def get_namespace_id(doc, url):
 
116
            attributes = doc.documentElement.attributes
 
117
            namespace = ""
 
118
            for i in range(attributes.length):
 
119
                if attributes.item(i).value == url:
 
120
                    if ":" in attributes.item(i).name:
 
121
                        # If the namespace is not the default one remove 'xlmns:'
 
122
                        namespace = attributes.item(i).name.split(':')[1] + ":"
 
123
                        break
 
124
            return namespace
 
125
 
 
126
        def get_text(element):
 
127
            """Retrieve the text value of a given node"""
 
128
            rc = []
 
129
            for node in element.childNodes:
 
130
                if node.nodeType == node.TEXT_NODE:
 
131
                    rc.append(node.data)
 
132
            return ''.join(rc).strip()
 
133
 
 
134
        doc = minidom.parse(os.path.join(addon_path, 'install.rdf'))
 
135
 
 
136
        # Get the namespaces abbreviations
 
137
        em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#")
 
138
        rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
 
139
 
 
140
        description = doc.getElementsByTagName(rdf + "Description").item(0)
 
141
        for node in description.childNodes:
 
142
            # Remove the namespace prefix from the tag for comparison
 
143
            entry = node.nodeName.replace(em, "")
 
144
            if entry in details.keys():
 
145
                details.update({ entry: get_text(node) })
 
146
 
 
147
        # turn unpack into a true/false value
 
148
        if isinstance(details['unpack'], basestring):
 
149
            details['unpack'] = details['unpack'].lower() == 'true'
 
150
 
 
151
        return details
 
152
 
 
153
    def install_from_path(self, path, unpack=False):
 
154
        """
 
155
        Installs addon from a filepath, url
 
156
        or directory of addons in the profile.
 
157
        - path: url, path to .xpi, or directory of addons
 
158
        - unpack: whether to unpack unless specified otherwise in the install.rdf
 
159
        """
 
160
 
 
161
        # if the addon is a url, download it
 
162
        # note that this won't work with protocols urllib2 doesn't support
 
163
        if '://' in path:
 
164
            response = urllib2.urlopen(path)
 
165
            fd, path = tempfile.mkstemp(suffix='.xpi')
 
166
            os.write(fd, response.read())
 
167
            os.close(fd)
 
168
            tmpfile = path
 
169
        else:
 
170
            tmpfile = None
 
171
 
 
172
        # if the addon is a directory, install all addons in it
 
173
        addons = [path]
 
174
        if not path.endswith('.xpi') and not os.path.exists(os.path.join(path, 'install.rdf')):
 
175
            # If the path doesn't exist, then we don't really care, just return
 
176
            if not os.path.isdir(path):
 
177
                return
 
178
            addons = [os.path.join(path, x) for x in os.listdir(path) if
 
179
                    os.path.isdir(os.path.join(path, x))]
 
180
 
 
181
        # install each addon
 
182
        for addon in addons:
 
183
            tmpdir = None
 
184
            xpifile = None
 
185
            if addon.endswith('.xpi'):
 
186
                tmpdir = tempfile.mkdtemp(suffix = '.' + os.path.split(addon)[-1])
 
187
                compressed_file = zipfile.ZipFile(addon, 'r')
 
188
                for name in compressed_file.namelist():
 
189
                    if name.endswith('/'):
 
190
                        os.makedirs(os.path.join(tmpdir, name))
 
191
                    else:
 
192
                        if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))):
 
193
                            os.makedirs(os.path.dirname(os.path.join(tmpdir, name)))
 
194
                        data = compressed_file.read(name)
 
195
                        f = open(os.path.join(tmpdir, name), 'wb')
 
196
                        f.write(data)
 
197
                        f.close()
 
198
                xpifile = addon
 
199
                addon = tmpdir
 
200
 
 
201
            # determine the addon id
 
202
            addon_details = AddonManager.addon_details(addon)
 
203
            addon_id = addon_details.get('id')
 
204
            assert addon_id, 'The addon id could not be found: %s' % addon
 
205
 
 
206
            # copy the addon to the profile
 
207
            extensions_path = os.path.join(self.profile, 'extensions', 'staged')
 
208
            addon_path = os.path.join(extensions_path, addon_id)
 
209
            if not unpack and not addon_details['unpack'] and xpifile:
 
210
                if not os.path.exists(extensions_path):
 
211
                    os.makedirs(extensions_path)
 
212
                shutil.copy(xpifile, addon_path + '.xpi')
 
213
            else:
 
214
                dir_util.copy_tree(addon, addon_path, preserve_symlinks=1)
 
215
                self._addon_dirs.append(addon_path)
 
216
 
 
217
            # remove the temporary directory, if any
 
218
            if tmpdir:
 
219
                dir_util.remove_tree(tmpdir)
 
220
 
 
221
        # remove temporary file, if any
 
222
        if tmpfile:
 
223
            os.remove(tmpfile)
 
224
 
 
225
    def clean_addons(self):
 
226
        """Cleans up addons in the profile."""
 
227
        for addon in self._addon_dirs:
 
228
            if os.path.isdir(addon):
 
229
                dir_util.remove_tree(addon)