Example #1
0
 def test_NonMatch(self):
     # Failure to match path.
     manifest = XpiManifest("locale chromepath en-US directory/")
     chrome_path, locale = manifest.getChromePathAndLocale(
         'nonexistent/file')
     self.failIf(chrome_path is not None, "Unexpected path match.")
     self.failIf(locale is not None, "Got locale without a match.")
 def test_TrivialParse(self):
     # Parse and use minimal manifest.
     manifest = XpiManifest("locale chromepath en-US directory/")
     self.assertEqual(len(manifest._locales), 1)
     chrome_path, locale = manifest.getChromePathAndLocale("directory/file.dtd")
     self.failIf(chrome_path is None, "Failed to match simple path")
     self.assertEqual(chrome_path, "chromepath/file.dtd", "Bad chrome path")
Example #3
0
 def test_TrivialParse(self):
     # Parse and use minimal manifest.
     manifest = XpiManifest("locale chromepath en-US directory/")
     self.assertEqual(len(manifest._locales), 1)
     chrome_path, locale = manifest.getChromePathAndLocale(
         'directory/file.dtd')
     self.failIf(chrome_path is None, "Failed to match simple path")
     self.assertEqual(chrome_path, "chromepath/file.dtd", "Bad chrome path")
Example #4
0
 def test_ReverseMappingLongestMatch(self):
     # Reverse mapping always finds the longest match.
     manifest = XpiManifest("""
         locale browser en-US jar:locales/
         locale browser en-US jar:locales/en-US.jar!/chrome/
         locale browser en-US jar:locales/en-US.jar!/
         """.lstrip())
     path = manifest.findMatchingXpiPath('browser/gui/print.dtd', 'en-US')
     self.assertEqual(path, "jar:locales/en-US.jar!/chrome/gui/print.dtd")
 def test_ReverseMappingLongestMatch(self):
     # Reverse mapping always finds the longest match.
     manifest = XpiManifest(
         """
         locale browser en-US jar:locales/
         locale browser en-US jar:locales/en-US.jar!/chrome/
         locale browser en-US jar:locales/en-US.jar!/
         """.lstrip()
     )
     path = manifest.findMatchingXpiPath("browser/gui/print.dtd", "en-US")
     self.assertEqual(path, "jar:locales/en-US.jar!/chrome/gui/print.dtd")
 def test_MultipleLocales(self):
     # Different locales.
     dirs = {"foo": "en-US", "bar": "es", "ixx": "zh_CN", "zup": "zh_TW", "gna": "pt", "gnu": "pt_BR"}
     manifest_text = "\n".join(["locale %s %s %sdir/\n" % (dir, locale, dir) for dir, locale in dirs.iteritems()])
     manifest = XpiManifest(manifest_text)
     self._checkSortOrder(manifest)
     for dir, dirlocale in dirs.iteritems():
         path = "%sdir/file.html" % dir
         chrome_path, locale = manifest.getChromePathAndLocale(path)
         self.assertEqual(chrome_path, "%s/file.html" % dir, "Bad chrome path in multi-line parse.")
         self.assertEqual(locale, dirlocale, "Locales got mixed up.")
Example #7
0
 def test_NoUsefulLines(self):
     # Parse manifest without useful data.  Lines that don't match what
     # we're looking for are ignored.
     manifest = XpiManifest("""
         There are no usable
         locale lines
         in this file.
         """.lstrip())
     self.assertEqual(len(manifest._locales), 0)
     chrome_path, locale = manifest.getChromePathAndLocale('lines')
     self.failIf(chrome_path is not None, "Empty manifest matched a path.")
     chrome_path, locale = manifest.getChromePathAndLocale('')
     self.failIf(chrome_path is not None, "Matched empty path.")
 def test_NoUsefulLines(self):
     # Parse manifest without useful data.  Lines that don't match what
     # we're looking for are ignored.
     manifest = XpiManifest(
         """
         There are no usable
         locale lines
         in this file.
         """.lstrip()
     )
     self.assertEqual(len(manifest._locales), 0)
     chrome_path, locale = manifest.getChromePathAndLocale("lines")
     self.failIf(chrome_path is not None, "Empty manifest matched a path.")
     chrome_path, locale = manifest.getChromePathAndLocale("")
     self.failIf(chrome_path is not None, "Matched empty path.")
Example #9
0
    def __init__(self, filename, archive, xpi_path=None, manifest=None):
        """Open zip (or XPI, or jar) file and scan its contents.

        :param filename: Name of this zip (XPI/jar) archive.
        :param archive: File-like object containing this zip archive.
        :param xpi_path: Full path of this file inside the XPI archive.
            Leave out for the XPI archive itself.
        :param manifest: `XpiManifest` representing the XPI archive's
            manifest file, if any.
        """
        self.filename = filename
        self.header = None
        self.last_translator = None
        self.manifest = manifest
        try:
            self.archive = ZipFile(archive, 'r')
        except BadZipfile as exception:
            raise TranslationFormatInvalidInputError(
                filename=filename, message=str(exception))

        if xpi_path is None:
            # This is the main XPI file.
            xpi_path = ''
            contained_files = set(self.archive.namelist())
            if manifest is None:
                # Look for a manifest.
                for filename in ['chrome.manifest', 'en-US.manifest']:
                    if filename in contained_files:
                        manifest_content = self.archive.read(filename)
                        self.manifest = XpiManifest(manifest_content)
                        break
            if 'install.rdf' in contained_files:
                rdf_content = self.archive.read('install.rdf')
                self.header = XpiHeader(rdf_content)

        # Strip trailing newline to avoid doubling it.
        xpi_path = xpi_path.rstrip('/')

        self._begin()

        # Process zipped files.  Sort by path to keep ordering deterministic.
        # Ordering matters in sequence numbering (which in turn shows up in
        # the UI), but also for consistency in duplicates resolution and for
        # automated testing.
        for entry in sorted(self.archive.namelist()):
            self._processEntry(entry, xpi_path)

        self._finish()
Example #10
0
 def test_DuplicateLines(self):
     # The manifest ignores redundant lines with the same path.
     manifest = XpiManifest("""
         locale dup fy boppe
         locale dup fy boppe
         """.lstrip())
     self.assertEqual(len(manifest._locales), 1)
 def test_MultipleLines(self):
     # Parse manifest file with multiple entries.
     manifest = XpiManifest(
         """
         locale foo en-US foodir/
         locale bar en-US bardir/
         locale ixx en-US ixxdir/
         locale gna en-US gnadir/
         """.lstrip()
     )
     self.assertEqual(len(manifest._locales), 4)
     self._checkSortOrder(manifest)
     for dir in ["gna", "bar", "ixx", "foo"]:
         path = "%sdir/file.html" % dir
         chrome_path, locale = manifest.getChromePathAndLocale(path)
         self.assertEqual(chrome_path, "%s/file.html" % dir, "Bad chrome path in multi-line parse.")
         self.assertEqual(locale, "en-US", "Bad locale in multi-line parse.")
Example #12
0
 def test_MultipleLines(self):
     # Parse manifest file with multiple entries.
     manifest = XpiManifest("""
         locale foo en-US foodir/
         locale bar en-US bardir/
         locale ixx en-US ixxdir/
         locale gna en-US gnadir/
         """.lstrip())
     self.assertEqual(len(manifest._locales), 4)
     self._checkSortOrder(manifest)
     for dir in ['gna', 'bar', 'ixx', 'foo']:
         path = "%sdir/file.html" % dir
         chrome_path, locale = manifest.getChromePathAndLocale(path)
         self.assertEqual(chrome_path, "%s/file.html" % dir,
                          "Bad chrome path in multi-line parse.")
         self.assertEqual(locale, 'en-US',
                          "Bad locale in multi-line parse.")
Example #13
0
    def test_IgnoredLines(self):
        # Ignored lines: anything that doesn't start with "locale" or doesn't
        # have the right number of arguments.  The one correct line is picked
        # out though.
        manifest = XpiManifest("""
            nonlocale obsolete fr foodir/
            anotherline

            #locale obsolete fr foodir/
            locale okay fr foodir/
            locale overlong fr foordir/ etc. etc. etc.
            locale incomplete fr
            """.lstrip())
        self.assertEqual(len(manifest._locales), 1)
        chrome_path, locale = manifest.getChromePathAndLocale('foodir/x')
        self.failIf(chrome_path is None, "Garbage lines messed up match.")
        self.assertEqual(chrome_path, "okay/x", "Matched wrong line.")
        self.assertEqual(locale, "fr", "Inexplicably mismatched locale.")
Example #14
0
 def test_PathBoundaries(self):
     # Paths can only match on path boundaries, where the slashes are
     # supposed to be.
     manifest = XpiManifest("""
         locale short el /ploink/squit
         locale long he /ploink/squittle
         """.lstrip())
     self._checkSortOrder(manifest)
     self._checkLookup(manifest, 'ploink/squit/x', 'short/x', 'el')
     self._checkLookup(manifest, '/ploink/squittle/x', 'long/x', 'he')
Example #15
0
 def test_JarLookup(self):
     # Simple, successful lookup of a correct path inside a jar file.
     manifest = XpiManifest("""
         locale foo en_GB jar:foo.jar!/dir/
         locale bar id jar:bar.jar!/
         """.lstrip())
     self._checkSortOrder(manifest)
     self._checkLookup(manifest, 'jar:foo.jar!/dir/file', 'foo/file',
                       'en_GB')
     self._checkLookup(manifest, 'jar:bar.jar!/dir/file', 'bar/dir/file',
                       'id')
    def test_IgnoredLines(self):
        # Ignored lines: anything that doesn't start with "locale" or doesn't
        # have the right number of arguments.  The one correct line is picked
        # out though.
        manifest = XpiManifest(
            """
            nonlocale obsolete fr foodir/
            anotherline

            #locale obsolete fr foodir/
            locale okay fr foodir/
            locale overlong fr foordir/ etc. etc. etc.
            locale incomplete fr
            """.lstrip()
        )
        self.assertEqual(len(manifest._locales), 1)
        chrome_path, locale = manifest.getChromePathAndLocale("foodir/x")
        self.failIf(chrome_path is None, "Garbage lines messed up match.")
        self.assertEqual(chrome_path, "okay/x", "Matched wrong line.")
        self.assertEqual(locale, "fr", "Inexplicably mismatched locale.")
Example #17
0
 def test_MultipleLocales(self):
     # Different locales.
     dirs = {
         'foo': 'en-US',
         'bar': 'es',
         'ixx': 'zh_CN',
         'zup': 'zh_TW',
         'gna': 'pt',
         'gnu': 'pt_BR'
     }
     manifest_text = '\n'.join([
         "locale %s %s %sdir/\n" % (dir, locale, dir)
         for dir, locale in dirs.iteritems()
     ])
     manifest = XpiManifest(manifest_text)
     self._checkSortOrder(manifest)
     for dir, dirlocale in dirs.iteritems():
         path = "%sdir/file.html" % dir
         chrome_path, locale = manifest.getChromePathAndLocale(path)
         self.assertEqual(chrome_path, "%s/file.html" % dir,
                          "Bad chrome path in multi-line parse.")
         self.assertEqual(locale, dirlocale, "Locales got mixed up.")
Example #18
0
 def test_NestedJars(self):
     # Jar files can be contained in jar files.
     manifest = XpiManifest("""
         locale x it jar:dir/x.jar!/subdir/y.jar!/
         locale y it jar:dir/x.jar!/subdir/y.jar!/deep/
         locale z it jar:dir/x.jar!/subdir/z.jar!/
         """.lstrip())
     self._checkSortOrder(manifest)
     self._checkLookup(manifest, 'jar:dir/x.jar!/subdir/y.jar!/foo',
                       'x/foo', 'it')
     self._checkLookup(manifest, 'jar:dir/x.jar!/subdir/y.jar!/deep/foo',
                       'y/foo', 'it')
     self._checkLookup(manifest, 'dir/x.jar!/subdir/z.jar!/foo', 'z/foo',
                       'it')
Example #19
0
    def __init__(self, filename, archive, xpi_path=None, manifest=None):
        """Open zip (or XPI, or jar) file and scan its contents.

        :param filename: Name of this zip (XPI/jar) archive.
        :param archive: File-like object containing this zip archive.
        :param xpi_path: Full path of this file inside the XPI archive.
            Leave out for the XPI archive itself.
        :param manifest: `XpiManifest` representing the XPI archive's
            manifest file, if any.
        """
        self.filename = filename
        self.header = None
        self.last_translator = None
        self.manifest = manifest
        try:
            self.archive = ZipFile(archive, "r")
        except BadZipfile as exception:
            raise TranslationFormatInvalidInputError(filename=filename, message=str(exception))

        if xpi_path is None:
            # This is the main XPI file.
            xpi_path = ""
            contained_files = set(self.archive.namelist())
            if manifest is None:
                # Look for a manifest.
                for filename in ["chrome.manifest", "en-US.manifest"]:
                    if filename in contained_files:
                        manifest_content = self.archive.read(filename)
                        self.manifest = XpiManifest(manifest_content)
                        break
            if "install.rdf" in contained_files:
                rdf_content = self.archive.read("install.rdf")
                self.header = XpiHeader(rdf_content)

        # Strip trailing newline to avoid doubling it.
        xpi_path = xpi_path.rstrip("/")

        self._begin()

        # Process zipped files.  Sort by path to keep ordering deterministic.
        # Ordering matters in sequence numbering (which in turn shows up in
        # the UI), but also for consistency in duplicates resolution and for
        # automated testing.
        for entry in sorted(self.archive.namelist()):
            self._processEntry(entry, xpi_path)

        self._finish()
Example #20
0
 def test_JarMixup(self):
     # Two jar files can have files for the same locale.  Two locales can
     # have files in the same jar file.  Two translations in different
     # places can have the same chrome path.
     manifest = XpiManifest("""
         locale serbian sr jar:translations.jar!/sr/
         locale croatian hr jar:translations.jar!/hr/
         locale docs sr jar:docs.jar!/sr/
         locale docs hr jar:docs.jar!/hr/
         """.lstrip())
     self._checkSortOrder(manifest)
     self._checkLookup(manifest, 'jar:translations.jar!/sr/x', 'serbian/x',
                       'sr')
     self._checkLookup(manifest, 'jar:translations.jar!/hr/x', 'croatian/x',
                       'hr')
     self._checkLookup(manifest, 'jar:docs.jar!/sr/x', 'docs/x', 'sr')
     self._checkLookup(manifest, 'jar:docs.jar!/hr/x', 'docs/x', 'hr')
Example #21
0
 def test_Overlap(self):
     # Path matching looks for longest prefix.  Make sure this works right,
     # even when nested directories are in "overlapping" manifest entries.
     manifest = XpiManifest("""
         locale foo1 ca a/
         locale foo2 ca a/b/
         locale foo3 ca a/b/c/x1
         locale foo4 ca a/b/c/x2
         """.lstrip())
     self._checkSortOrder(manifest)
     self._checkLookup(manifest, 'a/bb', 'foo1/bb', 'ca')
     self._checkLookup(manifest, 'a/bb/c', 'foo1/bb/c', 'ca')
     self._checkLookup(manifest, 'a/b/y', 'foo2/y', 'ca')
     self._checkLookup(manifest, 'a/b/c/', 'foo2/c/', 'ca')
     self._checkLookup(manifest, 'a/b/c/x12', 'foo2/c/x12', 'ca')
     self._checkLookup(manifest, 'a/b/c/x1/y', 'foo3/y', 'ca')
     self._checkLookup(manifest, 'a/b/c/x2/y', 'foo4/y', 'ca')
 def _checkNormalize(self, bad_path, good_path):
     """Test that `bad_path` normalizes to `good_path`."""
     self.assertEqual(XpiManifest._normalizePath(bad_path), good_path)
 def test_ReverseMappingWrongLocale(self):
     # Reverse mapping fails if given the wrong locale.
     manifest = XpiManifest("locale browser en-US jar:locales/en-US.jar!/chrome/")
     path = manifest.findMatchingXpiPath("browser/gui/print.dtd", "pt")
     self.assertEqual(path, None)
 def test_NoReverseMapping(self):
     # Failed reverse lookup.
     manifest = XpiManifest("locale browser en-US jar:locales/en-US.jar!/chrome/")
     path = manifest.findMatchingXpiPath("manual/gui/print.dtd", "en-US")
     self.assertEqual(path, None)
 def test_ReverseMapping(self):
     # Test "reverse mapping" from chrome path to XPI path.
     manifest = XpiManifest("locale browser en-US jar:locales/en-US.jar!/chrome/")
     path = manifest.findMatchingXpiPath("browser/gui/print.dtd", "en-US")
     self.assertEqual(path, "jar:locales/en-US.jar!/chrome/gui/print.dtd")
 def test_NormalizeContainsLocales(self):
     # "containsLocales" lookup is normalized, just like chrome path
     # lookup, so it's not fazed by syntactical misspellings.
     manifest = XpiManifest("locale main kh jar:/x/foo.jar!bar.jar!")
     self.failIf(not manifest.containsLocales("x/foo.jar!//bar.jar!/"))
 def test_ContainsLocales(self):
     # Jar files need to be descended into if any locale line mentions a
     # path inside them.
     manifest = XpiManifest("locale in my jar:x/foo.jar!/y")
     self.failIf(not manifest.containsLocales("jar:x/foo.jar!/"))
     self.failIf(manifest.containsLocales("jar:zzz/foo.jar!/"))
Example #28
0
 def test_NoReverseMapping(self):
     # Failed reverse lookup.
     manifest = XpiManifest(
         "locale browser en-US jar:locales/en-US.jar!/chrome/")
     path = manifest.findMatchingXpiPath('manual/gui/print.dtd', 'en-US')
     self.assertEqual(path, None)
Example #29
0
 def test_NormalizedLookup(self):
     # Both sides of a path lookup are normalized, so that a matching
     # prefix is recognized in a path even if the two have some meaningless
     # differences in their spelling.
     manifest = XpiManifest("locale x nn //a/dir")
     self._checkLookup(manifest, "a//dir///etc", 'x/etc', 'nn')
Example #30
0
 def test_ContainsLocales(self):
     # Jar files need to be descended into if any locale line mentions a
     # path inside them.
     manifest = XpiManifest("locale in my jar:x/foo.jar!/y")
     self.failIf(not manifest.containsLocales("jar:x/foo.jar!/"))
     self.failIf(manifest.containsLocales("jar:zzz/foo.jar!/"))
Example #31
0
class MozillaZipTraversal:
    """Traversal of an XPI file, or a jar file inside an XPI file.

    To traverse and process an XPI file, derive a class from this one
    and replace any hooks that you may need.

    If an XPI manifest is provided, traversal will be restricted to
    directories it lists as containing localizable resources.
    """

    def __init__(self, filename, archive, xpi_path=None, manifest=None):
        """Open zip (or XPI, or jar) file and scan its contents.

        :param filename: Name of this zip (XPI/jar) archive.
        :param archive: File-like object containing this zip archive.
        :param xpi_path: Full path of this file inside the XPI archive.
            Leave out for the XPI archive itself.
        :param manifest: `XpiManifest` representing the XPI archive's
            manifest file, if any.
        """
        self.filename = filename
        self.header = None
        self.last_translator = None
        self.manifest = manifest
        try:
            self.archive = ZipFile(archive, 'r')
        except BadZipfile as exception:
            raise TranslationFormatInvalidInputError(
                filename=filename, message=str(exception))

        if xpi_path is None:
            # This is the main XPI file.
            xpi_path = ''
            contained_files = set(self.archive.namelist())
            if manifest is None:
                # Look for a manifest.
                for filename in ['chrome.manifest', 'en-US.manifest']:
                    if filename in contained_files:
                        manifest_content = self.archive.read(filename)
                        self.manifest = XpiManifest(manifest_content)
                        break
            if 'install.rdf' in contained_files:
                rdf_content = self.archive.read('install.rdf')
                self.header = XpiHeader(rdf_content)

        # Strip trailing newline to avoid doubling it.
        xpi_path = xpi_path.rstrip('/')

        self._begin()

        # Process zipped files.  Sort by path to keep ordering deterministic.
        # Ordering matters in sequence numbering (which in turn shows up in
        # the UI), but also for consistency in duplicates resolution and for
        # automated testing.
        for entry in sorted(self.archive.namelist()):
            self._processEntry(entry, xpi_path)

        self._finish()

    def _processEntry(self, entry, xpi_path):
        """Read one zip archive entry, figure out what to do with it."""
        rootname, suffix = splitext(entry)
        if basename(rootname) == '':
            # If filename starts with a dot, that's not really a suffix.
            suffix = ''

        if suffix == '.jar':
            jarpath = make_jarpath(xpi_path, entry)
            if not self.manifest or self.manifest.containsLocales(jarpath):
                # If this is a jar file that may contain localizable
                # resources, don't process it in the normal way; recurse
                # by creating another parser instance.
                content = self.archive.read(entry)
                nested_instance = self.__class__(
                    filename=entry, archive=StringIO(content),
                    xpi_path=jarpath, manifest=self.manifest)

                self._processNestedJar(nested_instance)
                return

        # Construct XPI path; identical to "entry" if previous xpi_path
        # was empty.  XPI paths use slashes as separators, regardless of
        # what the native filesystem uses.
        xpi_path = '/'.join([xpi_path, entry]).lstrip('/')

        if self.manifest is None:
            # No manifest, so we don't have chrome paths.  Process
            # everything just to be sure.
            chrome_path = None
            locale_code = None
        else:
            chrome_path, locale_code = self.manifest.getChromePathAndLocale(
                xpi_path)
            if chrome_path is None:
                # Not in a directory containing localizable resources.
                return

        self._processTranslatableFile(
            entry, locale_code, xpi_path, chrome_path, suffix)

    def _begin(self):
        """Overridable hook: optional pre-traversal actions."""

    def _processTranslatableFile(self, entry, locale_code, xpi_path,
                                 chrome_path, filename_suffix):
        """Overridable hook: process a file entry.

        Called only for files that may be localizable.  If there is a
        manifest, that means the file must be in a location (or subtree)
        named by a "locale" entry.

        :param entry: Full name of file inside this zip archive,
            including path relative to the archive's root.
        :param locale_code: Code for locale this file belongs to, e.g.
            "en-US".
        :param xpi_path: Full path of this file inside the XPI archive,
            e.g. "jar:locale/en-US.jar!/data/messages.dtd".
        :param chrome_path: File's chrome path.  This is a kind of
            "normalized" path used in XPI to describe a virtual
            directory hierarchy.  The zip archive's actual layout (which
            the XPI paths describe) may be different.
        :param filename_suffix: File name suffix or "extension" of the
            translatable file.  This may be e.g. ".dtd" or ".xhtml," or
            the empty string if the filename does not contain a dot.
        """
        raise NotImplementedError(
            "XPI traversal class provides no _processTranslatableFile().")

    def _processNestedJar(self, zip_instance):
        """Overridable hook: handle a nested jar file.

        :param zip_instance: An instance of the same class as self, which
            has just parsed the nested jar file.
        """
        raise NotImplementedError(
            "XPI traversal class provides no _processNestedJar().")

    def _finish(self):
        """Overridable hook: post-traversal actions."""
        raise NotImplementedError(
            "XPI traversal class provides no _finish().")
def validate_xpi_manifest(filename, content):
    """Validate XPI manifest."""
    XpiManifest(content)
Example #33
0
 def test_NormalizeContainsLocales(self):
     # "containsLocales" lookup is normalized, just like chrome path
     # lookup, so it's not fazed by syntactical misspellings.
     manifest = XpiManifest("locale main kh jar:/x/foo.jar!bar.jar!")
     self.failIf(not manifest.containsLocales("x/foo.jar!//bar.jar!/"))
Example #34
0
 def test_ReverseMapping(self):
     # Test "reverse mapping" from chrome path to XPI path.
     manifest = XpiManifest(
         "locale browser en-US jar:locales/en-US.jar!/chrome/")
     path = manifest.findMatchingXpiPath('browser/gui/print.dtd', 'en-US')
     self.assertEqual(path, "jar:locales/en-US.jar!/chrome/gui/print.dtd")
Example #35
0
 def _checkNormalize(self, bad_path, good_path):
     """Test that `bad_path` normalizes to `good_path`."""
     self.assertEqual(XpiManifest._normalizePath(bad_path), good_path)
 def test_NonMatch(self):
     # Failure to match path.
     manifest = XpiManifest("locale chromepath en-US directory/")
     chrome_path, locale = manifest.getChromePathAndLocale("nonexistent/file")
     self.failIf(chrome_path is not None, "Unexpected path match.")
     self.failIf(locale is not None, "Got locale without a match.")
Example #37
0
class MozillaZipTraversal:
    """Traversal of an XPI file, or a jar file inside an XPI file.

    To traverse and process an XPI file, derive a class from this one
    and replace any hooks that you may need.

    If an XPI manifest is provided, traversal will be restricted to
    directories it lists as containing localizable resources.
    """

    def __init__(self, filename, archive, xpi_path=None, manifest=None):
        """Open zip (or XPI, or jar) file and scan its contents.

        :param filename: Name of this zip (XPI/jar) archive.
        :param archive: File-like object containing this zip archive.
        :param xpi_path: Full path of this file inside the XPI archive.
            Leave out for the XPI archive itself.
        :param manifest: `XpiManifest` representing the XPI archive's
            manifest file, if any.
        """
        self.filename = filename
        self.header = None
        self.last_translator = None
        self.manifest = manifest
        try:
            self.archive = ZipFile(archive, "r")
        except BadZipfile as exception:
            raise TranslationFormatInvalidInputError(filename=filename, message=str(exception))

        if xpi_path is None:
            # This is the main XPI file.
            xpi_path = ""
            contained_files = set(self.archive.namelist())
            if manifest is None:
                # Look for a manifest.
                for filename in ["chrome.manifest", "en-US.manifest"]:
                    if filename in contained_files:
                        manifest_content = self.archive.read(filename)
                        self.manifest = XpiManifest(manifest_content)
                        break
            if "install.rdf" in contained_files:
                rdf_content = self.archive.read("install.rdf")
                self.header = XpiHeader(rdf_content)

        # Strip trailing newline to avoid doubling it.
        xpi_path = xpi_path.rstrip("/")

        self._begin()

        # Process zipped files.  Sort by path to keep ordering deterministic.
        # Ordering matters in sequence numbering (which in turn shows up in
        # the UI), but also for consistency in duplicates resolution and for
        # automated testing.
        for entry in sorted(self.archive.namelist()):
            self._processEntry(entry, xpi_path)

        self._finish()

    def _processEntry(self, entry, xpi_path):
        """Read one zip archive entry, figure out what to do with it."""
        rootname, suffix = splitext(entry)
        if basename(rootname) == "":
            # If filename starts with a dot, that's not really a suffix.
            suffix = ""

        if suffix == ".jar":
            jarpath = make_jarpath(xpi_path, entry)
            if not self.manifest or self.manifest.containsLocales(jarpath):
                # If this is a jar file that may contain localizable
                # resources, don't process it in the normal way; recurse
                # by creating another parser instance.
                content = self.archive.read(entry)
                nested_instance = self.__class__(
                    filename=entry, archive=StringIO(content), xpi_path=jarpath, manifest=self.manifest
                )

                self._processNestedJar(nested_instance)
                return

        # Construct XPI path; identical to "entry" if previous xpi_path
        # was empty.  XPI paths use slashes as separators, regardless of
        # what the native filesystem uses.
        xpi_path = "/".join([xpi_path, entry]).lstrip("/")

        if self.manifest is None:
            # No manifest, so we don't have chrome paths.  Process
            # everything just to be sure.
            chrome_path = None
            locale_code = None
        else:
            chrome_path, locale_code = self.manifest.getChromePathAndLocale(xpi_path)
            if chrome_path is None:
                # Not in a directory containing localizable resources.
                return

        self._processTranslatableFile(entry, locale_code, xpi_path, chrome_path, suffix)

    def _begin(self):
        """Overridable hook: optional pre-traversal actions."""

    def _processTranslatableFile(self, entry, locale_code, xpi_path, chrome_path, filename_suffix):
        """Overridable hook: process a file entry.

        Called only for files that may be localizable.  If there is a
        manifest, that means the file must be in a location (or subtree)
        named by a "locale" entry.

        :param entry: Full name of file inside this zip archive,
            including path relative to the archive's root.
        :param locale_code: Code for locale this file belongs to, e.g.
            "en-US".
        :param xpi_path: Full path of this file inside the XPI archive,
            e.g. "jar:locale/en-US.jar!/data/messages.dtd".
        :param chrome_path: File's chrome path.  This is a kind of
            "normalized" path used in XPI to describe a virtual
            directory hierarchy.  The zip archive's actual layout (which
            the XPI paths describe) may be different.
        :param filename_suffix: File name suffix or "extension" of the
            translatable file.  This may be e.g. ".dtd" or ".xhtml," or
            the empty string if the filename does not contain a dot.
        """
        raise NotImplementedError("XPI traversal class provides no _processTranslatableFile().")

    def _processNestedJar(self, zip_instance):
        """Overridable hook: handle a nested jar file.

        :param zip_instance: An instance of the same class as self, which
            has just parsed the nested jar file.
        """
        raise NotImplementedError("XPI traversal class provides no _processNestedJar().")

    def _finish(self):
        """Overridable hook: post-traversal actions."""
        raise NotImplementedError("XPI traversal class provides no _finish().")
Example #38
0
 def test_ReverseMappingWrongLocale(self):
     # Reverse mapping fails if given the wrong locale.
     manifest = XpiManifest(
         "locale browser en-US jar:locales/en-US.jar!/chrome/")
     path = manifest.findMatchingXpiPath('browser/gui/print.dtd', 'pt')
     self.assertEqual(path, None)