tor-browser

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

checkGmpBalrog.py (8034B)


      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 argparse
      6 import hashlib
      7 import re
      8 from datetime import datetime
      9 from urllib.parse import urlparse
     10 from xml.etree import ElementTree
     11 
     12 import requests
     13 
     14 
     15 def check_hash(plugin, url, valid_urls):
     16    if url in valid_urls:
     17        return
     18    valid_urls[url] = True
     19 
     20    response = requests.get(url)
     21    response.raise_for_status()
     22 
     23    if "hashValue" in plugin.attrib:
     24        hashValue = hashlib.sha512(response.content).hexdigest()
     25        if hashValue != plugin.attrib["hashValue"]:
     26            raise Exception(
     27                "Given hash {} and calculated hash {} differ",
     28                plugin.attrib["hashValue"],
     29                hashValue,
     30            )
     31    if "size" in plugin.attrib:
     32        size = len(response.content)
     33        if size != int(plugin.attrib["size"]):
     34            raise Exception(
     35                "Given size {} and calculated size {} differ",
     36                int(plugin.attrib["size"]),
     37                size,
     38            )
     39 
     40 
     41 def fetch_balrog_xml(
     42    url_base: str, plugin_id, version: str, buildid: str, channels, targets, checkHash
     43 ) -> str:
     44    url = "{url_base}/{version}/{buildid}/{target}/en-US/{channel}/default/default/default/update.xml"
     45    valid_urls = {}
     46    results = {}
     47    for channel in channels:
     48        results[channel] = {}
     49        for target in targets:
     50            balrog_url = url.format_map({
     51                "url_base": url_base,
     52                "buildid": buildid,
     53                "channel": channel,
     54                "version": version,
     55                "target": target,
     56            })
     57 
     58            response = requests.get(balrog_url)
     59            response.raise_for_status()
     60 
     61            plugin_urls = []
     62            tree = ElementTree.fromstring(response.content)
     63            for plugin in tree.findall("./addons/addon"):
     64                if not "id" in plugin.attrib:
     65                    continue
     66                if plugin.attrib["id"] != plugin_id:
     67                    continue
     68                if "URL" in plugin.attrib:
     69                    if checkHash:
     70                        check_hash(plugin, plugin.attrib["URL"], valid_urls)
     71                    plugin_urls.append(plugin.attrib["URL"])
     72                for mirror in plugin.findall("./mirror"):
     73                    if "URL" in mirror.attrib:
     74                        if checkHash:
     75                            check_hash(plugin, plugin.attrib["URL"], valid_urls)
     76                        plugin_urls.append(mirror.attrib["URL"])
     77 
     78            results[channel][target] = plugin_urls
     79 
     80    matching_channels = {}
     81    for channel in channels:
     82        matching_channels[channel] = [channel]
     83        for other_channel in channels:
     84            if (
     85                channel == other_channel
     86                or channel not in results
     87                or other_channel not in results
     88            ):
     89                continue
     90            if results[channel] == results[other_channel]:
     91                matching_channels[channel].append(other_channel)
     92                del results[other_channel]
     93 
     94    for channel in results:
     95        print(", ".join(matching_channels[channel]))
     96        for target in targets:
     97            print(f"\t{target}")
     98            for url in results[channel][target]:
     99                print(f"\t\t{url}")
    100 
    101 
    102 def main():
    103    examples = """examples:
    104  python dom/media/tools/checkGmpBalrog.py widevine 133.0
    105  python dom/media/tools/checkGmpBalrog.py widevine 133.0 --target Darwin_aarch64-gcc3 Darwin_x86_64-gcc3
    106  python dom/media/tools/checkGmpBalrog.py --url http://localhost:8080 openh264 125.0
    107  python dom/media/tools/checkGmpBalrog.py widevine_l1 115.14.0 --staging --channel nightly beta"""
    108 
    109    parser = argparse.ArgumentParser(
    110        description="Check Balrog XML for GMP plugin updates",
    111        epilog=examples,
    112        formatter_class=argparse.RawDescriptionHelpFormatter,
    113    )
    114    parser.add_argument(
    115        "plugin",
    116        help="which plugin: openh264, widevine, widevine_l1",
    117    )
    118    parser.add_argument("version", help="version of Firefox")
    119    parser.add_argument(
    120        "--channel", action="extend", nargs="+", help="check specific channel(s)"
    121    )
    122    parser.add_argument(
    123        "--list-channels", action="store_true", help="list the supported channels"
    124    )
    125    parser.add_argument(
    126        "--target", action="extend", nargs="+", help="check specific target(s)"
    127    )
    128    parser.add_argument(
    129        "--list-targets", action="store_true", help="list the supported targets"
    130    )
    131    parser.add_argument("--buildid", help="override generated build ID to be specific")
    132    parser.add_argument(
    133        "--url", help="override base URL from which to fetch the balrog configuration"
    134    )
    135    parser.add_argument(
    136        "--staging",
    137        action="store_true",
    138        help="using the balrog staging URL instead of production",
    139    )
    140    parser.add_argument(
    141        "--checkHash",
    142        action="store_true",
    143        help="download plugins and validate the size/hash",
    144    )
    145    args = parser.parse_args()
    146 
    147    valid_channels = ["esr", "release", "beta", "nightly", "nightlytest"]
    148    if args.list_channels:
    149        for channel in valid_channels:
    150            print(channel)
    151        return
    152    if args.channel is not None:
    153        for channel in args.channel:
    154            if channel not in valid_channels:
    155                parser.error("`%s` is invalid, see --list-channels" % channel)
    156                return
    157        channels = args.channel
    158    else:
    159        channels = valid_channels
    160 
    161    valid_targets = [
    162        "Darwin_aarch64-gcc3",
    163        "Darwin_x86_64-gcc3",
    164        "Linux_aarch64-gcc3",
    165        "Linux_x86-gcc3",
    166        "Linux_x86_64-gcc3",
    167        "WINNT_aarch64-msvc-aarch64",
    168        "WINNT_x86-msvc",
    169        "WINNT_x86_64-msvc",
    170    ]
    171    valid_aliases = [
    172        "Linux_x86_64-gcc3-asan",
    173        "WINNT_x86-msvc-x64",
    174        "WINNT_x86-msvc-x86",
    175        "WINNT_x86_64-msvc-x64",
    176        "WINNT_x86_64-msvc-x64-asan",
    177    ]
    178    if args.list_targets:
    179        for target in valid_targets:
    180            print(target)
    181        for target in valid_aliases:
    182            print("%s (alias)" % target)
    183        return
    184    if args.target is not None:
    185        for target in args.target:
    186            if target not in valid_targets and target not in valid_aliases:
    187                parser.error("`%s` is invalid, see --list-targets" % target)
    188                return
    189        targets = args.target
    190    else:
    191        targets = valid_targets
    192 
    193    if args.buildid is not None:
    194        if not re.match(r"^\d{14}$", args.buildid):
    195            parser.error("`%s` is invalid, build id must be 14 digits")
    196            return
    197        buildid = args.buildid
    198 
    199    else:
    200        buildid = datetime.today().strftime("%y%m%d%H%M%S")
    201 
    202    url_base = "https://aus5.mozilla.org"
    203    if args.staging:
    204        url_base = "https://stage.balrog.nonprod.cloudops.mozgcp.net"
    205    if args.url is not None:
    206        url_base = args.url
    207    if url_base[-1] == "/":
    208        url_base = url_base[:-1]
    209    url_base += "/update/3/GMP"
    210 
    211    parsed_url = urlparse(url_base)
    212    if parsed_url.scheme not in ("http", "https"):
    213        parser.error("expected http(s) scheme, got `%s`" % parsed_url.scheme)
    214        return
    215    if parsed_url.path != "/update/3/GMP":
    216        parser.error("expected url path of `/update/3/GMP`, got `%s`" % parsed_url.path)
    217        return
    218 
    219    if args.plugin == "openh264":
    220        plugin = "gmp-gmpopenh264"
    221    elif args.plugin == "widevine":
    222        plugin = "gmp-widevinecdm"
    223    elif args.plugin == "widevine_l1":
    224        plugin = "gmp-widevinecdm-l1"
    225    else:
    226        parser.error("plugin not recognized")
    227        return
    228 
    229    if not re.match(r"^\d+\.\d+(\.\d+)?$", args.version):
    230        parser.error("version must be of the form ###.###(.###)")
    231        return
    232 
    233    fetch_balrog_xml(
    234        url_base, plugin, args.version, buildid, channels, targets, args.checkHash
    235    )
    236 
    237 
    238 main()