tor-browser

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

flags.py (8446B)


      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 re
      6 from collections import OrderedDict
      7 
      8 from packaging.version import Version
      9 
     10 from mozpack.errors import errors
     11 
     12 
     13 class Flag:
     14    """
     15    Class for flags in manifest entries in the form:
     16        "flag"   (same as "flag=true")
     17        "flag=yes|true|1"
     18        "flag=no|false|0"
     19    """
     20 
     21    def __init__(self, name):
     22        """
     23        Initialize a Flag with the given name.
     24        """
     25        self.name = name
     26        self.value = None
     27 
     28    def add_definition(self, definition):
     29        """
     30        Add a flag value definition. Replaces any previously set value.
     31        """
     32        if definition == self.name:
     33            self.value = True
     34            return
     35        assert definition.startswith(self.name)
     36        if definition[len(self.name)] != "=":
     37            return errors.fatal("Malformed flag: %s" % definition)
     38        value = definition[len(self.name) + 1 :]
     39        if value in ("yes", "true", "1", "no", "false", "0"):
     40            self.value = value
     41        else:
     42            return errors.fatal("Unknown value in: %s" % definition)
     43 
     44    def matches(self, value):
     45        """
     46        Return whether the flag value matches the given value. The values
     47        are canonicalized for comparison.
     48        """
     49        if value in ("yes", "true", "1", True):
     50            return self.value in ("yes", "true", "1", True)
     51        if value in ("no", "false", "0", False):
     52            return self.value in ("no", "false", "0", False, None)
     53        raise RuntimeError("Invalid value: %s" % value)
     54 
     55    def __str__(self):
     56        """
     57        Serialize the flag value in the same form given to the last
     58        add_definition() call.
     59        """
     60        if self.value is None:
     61            return ""
     62        if self.value is True:
     63            return self.name
     64        return "%s=%s" % (self.name, self.value)
     65 
     66    def __eq__(self, other):
     67        return str(self) == other
     68 
     69 
     70 class StringFlag:
     71    """
     72    Class for string flags in manifest entries in the form:
     73        "flag=string"
     74        "flag!=string"
     75    """
     76 
     77    def __init__(self, name):
     78        """
     79        Initialize a StringFlag with the given name.
     80        """
     81        self.name = name
     82        self.values = []
     83 
     84    def add_definition(self, definition):
     85        """
     86        Add a string flag definition.
     87        """
     88        assert definition.startswith(self.name)
     89        value = definition[len(self.name) :]
     90        if value.startswith("="):
     91            self.values.append(("==", value[1:]))
     92        elif value.startswith("!="):
     93            self.values.append(("!=", value[2:]))
     94        else:
     95            return errors.fatal("Malformed flag: %s" % definition)
     96 
     97    def matches(self, value):
     98        """
     99        Return whether one of the string flag definitions matches the given
    100        value.
    101        For example,
    102 
    103            flag = StringFlag('foo')
    104            flag.add_definition('foo!=bar')
    105            flag.matches('bar') returns False
    106            flag.matches('qux') returns True
    107            flag = StringFlag('foo')
    108            flag.add_definition('foo=bar')
    109            flag.add_definition('foo=baz')
    110            flag.matches('bar') returns True
    111            flag.matches('baz') returns True
    112            flag.matches('qux') returns False
    113        """
    114        if not self.values:
    115            return True
    116        for comparison, val in self.values:
    117            if eval("value %s val" % comparison):
    118                return True
    119        return False
    120 
    121    def __str__(self):
    122        """
    123        Serialize the flag definitions in the same form given to each
    124        add_definition() call.
    125        """
    126        res = []
    127        for comparison, val in self.values:
    128            if comparison == "==":
    129                res.append("%s=%s" % (self.name, val))
    130            else:
    131                res.append("%s!=%s" % (self.name, val))
    132        return " ".join(res)
    133 
    134    def __eq__(self, other):
    135        return str(self) == other
    136 
    137 
    138 class VersionFlag:
    139    """
    140    Class for version flags in manifest entries in the form:
    141        "flag=version"
    142        "flag<=version"
    143        "flag<version"
    144        "flag>=version"
    145        "flag>version"
    146    """
    147 
    148    def __init__(self, name):
    149        """
    150        Initialize a VersionFlag with the given name.
    151        """
    152        self.name = name
    153        self.values = []
    154 
    155    def add_definition(self, definition):
    156        """
    157        Add a version flag definition.
    158        """
    159        assert definition.startswith(self.name)
    160        value = definition[len(self.name) :]
    161        if value.startswith("="):
    162            self.values.append(("==", Version(value[1:])))
    163        elif len(value) > 1 and value[0] in ["<", ">"]:
    164            if value[1] == "=":
    165                if len(value) < 3:
    166                    return errors.fatal("Malformed flag: %s" % definition)
    167                self.values.append((value[0:2], Version(value[2:])))
    168            else:
    169                self.values.append((value[0], Version(value[1:])))
    170        else:
    171            return errors.fatal("Malformed flag: %s" % definition)
    172 
    173    def matches(self, value):
    174        """
    175        Return whether one of the version flag definitions matches the given
    176        value.
    177        For example,
    178 
    179            flag = VersionFlag('foo')
    180            flag.add_definition('foo>=1.0')
    181            flag.matches('1.0') returns True
    182            flag.matches('1.1') returns True
    183            flag.matches('0.9') returns False
    184            flag = VersionFlag('foo')
    185            flag.add_definition('foo>=1.0')
    186            flag.add_definition('foo<0.5')
    187            flag.matches('0.4') returns True
    188            flag.matches('1.0') returns True
    189            flag.matches('0.6') returns False
    190        """
    191        value = Version(value)
    192        if not self.values:
    193            return True
    194        for comparison, val in self.values:
    195            if eval("value %s val" % comparison):
    196                return True
    197        return False
    198 
    199    def __str__(self):
    200        """
    201        Serialize the flag definitions in the same form given to each
    202        add_definition() call.
    203        """
    204        res = []
    205        for comparison, val in self.values:
    206            if comparison == "==":
    207                res.append("%s=%s" % (self.name, val))
    208            else:
    209                res.append("%s%s%s" % (self.name, comparison, val))
    210        return " ".join(res)
    211 
    212    def __eq__(self, other):
    213        return str(self) == other
    214 
    215 
    216 class Flags(OrderedDict):
    217    """
    218    Class to handle a set of flags definitions given on a single manifest
    219    entry.
    220 
    221    """
    222 
    223    FLAGS = {
    224        "application": StringFlag,
    225        "appversion": VersionFlag,
    226        "platformversion": VersionFlag,
    227        "contentaccessible": Flag,
    228        "os": StringFlag,
    229        "osversion": VersionFlag,
    230        "abi": StringFlag,
    231        "platform": Flag,
    232        "xpcnativewrappers": Flag,
    233        "tablet": Flag,
    234        "process": StringFlag,
    235        "backgroundtask": StringFlag,
    236    }
    237    RE = re.compile(r"([!<>=]+)")
    238 
    239    def __init__(self, *flags):
    240        """
    241        Initialize a set of flags given in string form.
    242           flags = Flags('contentaccessible=yes', 'appversion>=3.5')
    243        """
    244        OrderedDict.__init__(self)
    245        for f in flags:
    246            name = self.RE.split(f)
    247            name = name[0]
    248            if name not in self.FLAGS:
    249                errors.fatal("Unknown flag: %s" % name)
    250                continue
    251            if name not in self:
    252                self[name] = self.FLAGS[name](name)
    253            self[name].add_definition(f)
    254 
    255    def __str__(self):
    256        """
    257        Serialize the set of flags.
    258        """
    259        return " ".join(str(self[k]) for k in self)
    260 
    261    def match(self, **filter):
    262        """
    263        Return whether the set of flags match the set of given filters.
    264            flags = Flags('contentaccessible=yes', 'appversion>=3.5',
    265                          'application=foo')
    266 
    267            flags.match(application='foo') returns True
    268            flags.match(application='foo', appversion='3.5') returns True
    269            flags.match(application='foo', appversion='3.0') returns False
    270 
    271        """
    272        for name, value in filter.items():
    273            if name not in self:
    274                continue
    275            if not self[name].matches(value):
    276                return False
    277        return True