tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

unpack.py (7679B)


      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
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import codecs
      6 from urllib.parse import urlparse
      7 
      8 import mozpack.path as mozpath
      9 from mozpack.chrome.manifest import (
     10    ManifestEntryWithRelPath,
     11    ManifestResource,
     12    is_manifest,
     13    parse_manifest,
     14 )
     15 from mozpack.copier import FileCopier, FileRegistry
     16 from mozpack.files import BaseFinder, DeflatedFile, FileFinder, ManifestFile
     17 from mozpack.mozjar import JarReader
     18 from mozpack.packager import SimplePackager
     19 from mozpack.packager.formats import FlatFormatter
     20 
     21 
     22 class UnpackFinder(BaseFinder):
     23    """
     24    Special Finder object that treats the source package directory as if it
     25    were in the flat chrome format, whatever chrome format it actually is in.
     26 
     27    This means that for example, paths like chrome/browser/content/... match
     28    files under jar:chrome/browser.jar!/content/... in case of jar chrome
     29    format.
     30 
     31    The only argument to the constructor is a Finder instance or a path.
     32    The UnpackFinder is populated with files from this Finder instance,
     33    or with files from a FileFinder using the given path as its root.
     34    """
     35 
     36    def __init__(self, source, omnijar_name=None, unpack_xpi=True, **kwargs):
     37        if isinstance(source, BaseFinder):
     38            assert not kwargs
     39            self._finder = source
     40        else:
     41            self._finder = FileFinder(source, **kwargs)
     42        self.base = self._finder.base
     43        self.files = FileRegistry()
     44        self.kind = "flat"
     45        if omnijar_name:
     46            self.omnijar = omnijar_name
     47        else:
     48            # Can't include globally because of bootstrapping issues.
     49            from buildconfig import substs
     50 
     51            self.omnijar = substs.get("OMNIJAR_NAME", "omni.ja")
     52        self.jarlogs = {}
     53        self.compressed = False
     54        self._unpack_xpi = unpack_xpi
     55 
     56        jars = set()
     57 
     58        for p, f in self._finder.find("*"):
     59            # Skip the precomplete file, which is generated at packaging time.
     60            if p == "precomplete":
     61                continue
     62            base = mozpath.dirname(p)
     63            # If the file matches the omnijar pattern, it is an omnijar.
     64            # All the files it contains go in the directory containing the full
     65            # pattern. Manifests are merged if there is a corresponding manifest
     66            # in the directory.
     67            if self._maybe_zip(f) and mozpath.match(p, "**/%s" % self.omnijar):
     68                jar = self._open_jar(p, f)
     69                if "chrome.manifest" in jar:
     70                    self.kind = "omni"
     71                    self._fill_with_jar(p[: -len(self.omnijar) - 1], jar)
     72                    continue
     73            # If the file is a manifest, scan its entries for some referencing
     74            # jar: urls. If there are some, the files contained in the jar they
     75            # point to, go under a directory named after the jar.
     76            if is_manifest(p):
     77                m = self.files[p] if self.files.contains(p) else ManifestFile(base)
     78                for e in parse_manifest(
     79                    self.base, p, codecs.getreader("utf-8")(f.open())
     80                ):
     81                    m.add(self._handle_manifest_entry(e, jars))
     82                if self.files.contains(p):
     83                    continue
     84                f = m
     85            # If we're unpacking packed addons and the file is a packed addon,
     86            # unpack it under a directory named after the xpi.
     87            if self._unpack_xpi and p.endswith(".xpi") and self._maybe_zip(f):
     88                self._fill_with_jar(p[:-4], self._open_jar(p, f))
     89                continue
     90            if p not in jars:
     91                self.files.add(p, f)
     92 
     93    def _fill_with_jar(self, base, jar):
     94        for j in jar:
     95            path = mozpath.join(base, j.filename)
     96            if is_manifest(j.filename):
     97                m = (
     98                    self.files[path]
     99                    if self.files.contains(path)
    100                    else ManifestFile(mozpath.dirname(path))
    101                )
    102                for e in parse_manifest(None, path, j):
    103                    m.add(e)
    104                if not self.files.contains(path):
    105                    self.files.add(path, m)
    106                continue
    107            else:
    108                self.files.add(path, DeflatedFile(j))
    109 
    110    def _handle_manifest_entry(self, entry, jars):
    111        jarpath = None
    112        if (
    113            isinstance(entry, ManifestEntryWithRelPath)
    114            and urlparse(entry.relpath).scheme == "jar"
    115        ):
    116            jarpath, entry = self._unjarize(entry, entry.relpath)
    117        elif (
    118            isinstance(entry, ManifestResource)
    119            and urlparse(entry.target).scheme == "jar"
    120        ):
    121            jarpath, entry = self._unjarize(entry, entry.target)
    122        if jarpath:
    123            # Don't defer unpacking the jar file. If we already saw
    124            # it, take (and remove) it from the registry. If we
    125            # haven't, try to find it now.
    126            if self.files.contains(jarpath):
    127                jar = self.files[jarpath]
    128                self.files.remove(jarpath)
    129            else:
    130                jar = [f for p, f in self._finder.find(jarpath)]
    131                assert len(jar) == 1
    132                jar = jar[0]
    133            if jarpath not in jars:
    134                base = mozpath.splitext(jarpath)[0]
    135                for j in self._open_jar(jarpath, jar):
    136                    self.files.add(mozpath.join(base, j.filename), DeflatedFile(j))
    137            jars.add(jarpath)
    138            self.kind = "jar"
    139        return entry
    140 
    141    def _open_jar(self, path, file):
    142        """
    143        Return a JarReader for the given BaseFile instance, keeping a log of
    144        the preloaded entries it has.
    145        """
    146        jar = JarReader(fileobj=file.open())
    147        self.compressed = max(self.compressed, jar.compression)
    148        if jar.last_preloaded:
    149            jarlog = list(jar.entries.keys())
    150            self.jarlogs[path] = jarlog[: jarlog.index(jar.last_preloaded) + 1]
    151        return jar
    152 
    153    def find(self, path):
    154        for p in self.files.match(path):
    155            yield p, self.files[p]
    156 
    157    def _maybe_zip(self, file):
    158        """
    159        Return whether the given BaseFile looks like a ZIP/Jar.
    160        """
    161        header = file.open().read(8)
    162        return len(header) == 8 and (header[0:2] == b"PK" or header[4:6] == b"PK")
    163 
    164    def _unjarize(self, entry, relpath):
    165        """
    166        Transform a manifest entry pointing to chrome data in a jar in one
    167        pointing to the corresponding unpacked path. Return the jar path and
    168        the new entry.
    169        """
    170        base = entry.base
    171        jar, relpath = urlparse(relpath).path.split("!", 1)
    172        entry = (
    173            entry.rebase(mozpath.join(base, "jar:%s!" % jar))
    174            .move(mozpath.join(base, mozpath.splitext(jar)[0]))
    175            .rebase(base)
    176        )
    177        return mozpath.join(base, jar), entry
    178 
    179 
    180 def unpack_to_registry(source, registry, omnijar_name=None):
    181    """
    182    Transform a jar chrome or omnijar packaged directory into a flat package.
    183 
    184    The given registry is filled with the flat package.
    185    """
    186    finder = UnpackFinder(source, omnijar_name)
    187    packager = SimplePackager(FlatFormatter(registry))
    188    for p, f in finder.find("*"):
    189        packager.add(p, f)
    190    packager.close()
    191 
    192 
    193 def unpack(source, omnijar_name=None):
    194    """
    195    Transform a jar chrome or omnijar packaged directory into a flat package.
    196    """
    197    copier = FileCopier()
    198    unpack_to_registry(source, copier, omnijar_name)
    199    copier.copy(source, skip_if_older=False)