tor-browser

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

generateGmpJson.py (14984B)


      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 json
      8 import logging
      9 import re
     10 from urllib.parse import urlparse, urlunparse
     11 
     12 import requests
     13 
     14 
     15 def fetch_url_for_cdms(cdms, urlParams):
     16    any_version = None
     17    for cdm in cdms:
     18        if "fileName" in cdm:
     19            cdm["fileUrl"] = cdm["fileName"].format_map(urlParams)
     20            response = requests.get(cdm["fileUrl"], allow_redirects=False)
     21            if response.status_code != 302:
     22                raise Exception(
     23                    "{} unexpected status code {}".format(
     24                        cdm["target"], response.status_code
     25                    )
     26                )
     27 
     28            redirectUrl = response.headers["Location"]
     29            parsedUrl = urlparse(redirectUrl)
     30            if parsedUrl.scheme != "https":
     31                raise Exception(
     32                    "{} expected https scheme '{}'".format(cdm["target"], redirectUrl)
     33                )
     34 
     35            sanitizedUrl = urlunparse((
     36                parsedUrl.scheme,
     37                parsedUrl.netloc,
     38                parsedUrl.path,
     39                None,
     40                None,
     41                None,
     42            ))
     43 
     44            # Note that here we modify the returned URL from the
     45            # component update service because it returns a preferred
     46            # server for the caller of the script. This may not match
     47            # up with what the end users require. Google has requested
     48            # that we instead replace these results with the
     49            # edgedl.me.gvt1.com domain/path, which should be location
     50            # agnostic.
     51            normalizedUrl = re.sub(
     52                r"https.+?release2",
     53                "https://edgedl.me.gvt1.com/edgedl/release2",
     54                sanitizedUrl,
     55            )
     56            if not normalizedUrl:
     57                raise Exception(
     58                    "{} cannot normalize '{}'".format(cdm["target"], sanitizedUrl)
     59                )
     60 
     61            # Because some users are unable to resolve *.gvt1.com
     62            # URLs, we supply an alternative based on www.google.com.
     63            # This should resolve with success more frequently.
     64            mirrorUrl = re.sub(
     65                r"https.+?release2",
     66                "https://www.google.com/dl/release2",
     67                sanitizedUrl,
     68            )
     69 
     70            version = re.search(r".*?_([\d]+\.[\d]+\.[\d]+\.[\d]+)/", sanitizedUrl)
     71            if version is None:
     72                raise Exception(
     73                    "{} cannot extract version '{}'".format(cdm["target"], sanitizedUrl)
     74                )
     75            if any_version is None:
     76                any_version = version.group(1)
     77            elif version.group(1) != any_version:
     78                raise Exception(
     79                    "{} version {} mismatch {}".format(
     80                        cdm["target"], version.group(1), any_version
     81                    )
     82                )
     83            cdm["fileName"] = normalizedUrl
     84            if mirrorUrl and mirrorUrl != normalizedUrl:
     85                cdm["fileNameMirror"] = mirrorUrl
     86    return any_version
     87 
     88 
     89 def fetch_data_for_cdms(cdms, urlParams):
     90    for cdm in cdms:
     91        if "fileName" in cdm:
     92            cdm["fileUrl"] = cdm["fileName"].format_map(urlParams)
     93            response = requests.get(cdm["fileUrl"])
     94            response.raise_for_status()
     95            cdm["hashValue"] = hashlib.sha512(response.content).hexdigest()
     96            if "fileNameMirror" in cdm:
     97                cdm["mirrorUrl"] = cdm["fileNameMirror"].format_map(urlParams)
     98                mirrorresponse = requests.get(cdm["mirrorUrl"])
     99                mirrorresponse.raise_for_status()
    100                mirrorhash = hashlib.sha512(mirrorresponse.content).hexdigest()
    101                if cdm["hashValue"] != mirrorhash:
    102                    raise Exception(
    103                        "Primary hash {} and mirror hash {} differ",
    104                        cdm["hashValue"],
    105                        mirrorhash,
    106                    )
    107            cdm["filesize"] = len(response.content)
    108            if cdm["filesize"] == 0:
    109                raise Exception("Empty response for {target}".format_map(cdm))
    110 
    111 
    112 def generate_json_for_cdms(cdms):
    113    cdm_json = ""
    114    for cdm in cdms:
    115        if "alias" in cdm:
    116            cdm_json += (
    117                '        "{target}": {{\n'
    118                + '          "alias": "{alias}"\n'
    119                + "        }},\n"
    120            ).format_map(cdm)
    121        elif "mirrorUrl" in cdm:
    122            cdm_json += (
    123                '        "{target}": {{\n'
    124                + '          "fileUrl": "{fileUrl}",\n'
    125                + '          "mirrorUrls": [\n'
    126                + '            "{mirrorUrl}"\n'
    127                + "          ],\n"
    128                + '          "filesize": {filesize},\n'
    129                + '          "hashValue": "{hashValue}"\n'
    130                + "        }},\n"
    131            ).format_map(cdm)
    132        else:
    133            cdm_json += (
    134                '        "{target}": {{\n'
    135                + '          "fileUrl": "{fileUrl}",\n'
    136                + '          "mirrorUrls": [],\n'
    137                + '          "filesize": {filesize},\n'
    138                + '          "hashValue": "{hashValue}"\n'
    139                + "        }},\n"
    140            ).format_map(cdm)
    141    return cdm_json[:-2] + "\n"
    142 
    143 
    144 def calculate_gmpopenh264_json(version: str, version_hash: str, url_base: str) -> str:
    145    # fmt: off
    146    cdms = [
    147        {"target": "Darwin_aarch64-gcc3", "fileName": "{url_base}/openh264-macosx64-aarch64-{version}.zip"},
    148        {"target": "Darwin_x86_64-gcc3", "fileName": "{url_base}/openh264-macosx64-{version}.zip"},
    149        {"target": "Linux_aarch64-gcc3", "fileName": "{url_base}/openh264-linux64-aarch64-{version}.zip"},
    150        {"target": "Linux_x86-gcc3", "fileName": "{url_base}/openh264-linux32-{version}.zip"},
    151        {"target": "Linux_x86_64-gcc3", "fileName": "{url_base}/openh264-linux64-{version}.zip"},
    152        {"target": "Linux_x86_64-gcc3-asan", "alias": "Linux_x86_64-gcc3"},
    153        {"target": "WINNT_aarch64-msvc-aarch64", "fileName": "{url_base}/openh264-win64-aarch64-{version}.zip"},
    154        {"target": "WINNT_x86-msvc", "fileName": "{url_base}/openh264-win32-{version}.zip"},
    155        {"target": "WINNT_x86-msvc-x64", "alias": "WINNT_x86-msvc"},
    156        {"target": "WINNT_x86-msvc-x86", "alias": "WINNT_x86-msvc"},
    157        {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}/openh264-win64-{version}.zip"},
    158        {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"},
    159        {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"},
    160    ]
    161    # fmt: on
    162    try:
    163        fetch_data_for_cdms(cdms, {"url_base": url_base, "version": version_hash})
    164    except Exception as e:
    165        logging.error("calculate_gmpopenh264_json: could not create JSON due to: %s", e)
    166        return ""
    167    else:
    168        return (
    169            "{\n"
    170            + '  "hashFunction": "sha512",\n'
    171            + f'  "name": "OpenH264-{version}",\n'
    172            + '  "schema_version": 1000,\n'
    173            + '  "vendors": {\n'
    174            + '    "gmp-gmpopenh264": {\n'
    175            + '      "platforms": {\n'
    176            + generate_json_for_cdms(cdms)
    177            + "      },\n"
    178            + f'      "version": "{version}"\n'
    179            + "    }\n"
    180            + "  }\n"
    181            + "}"
    182        )
    183 
    184 
    185 def calculate_widevinecdm_json(version: str, url_base: str) -> str:
    186    # fmt: off
    187    cdms = [
    188        {"target": "Darwin_aarch64-gcc3", "fileName": "{url_base}/{version}-mac-arm64.zip"},
    189        {"target": "Darwin_x86_64-gcc3", "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"},
    190        {"target": "Darwin_x86_64-gcc3-u-i386-x86_64", "fileName": "{url_base}/{version}-mac-x64.zip"},
    191        {"target": "Linux_x86_64-gcc3", "fileName": "{url_base}/{version}-linux-x64.zip"},
    192        {"target": "Linux_x86_64-gcc3-asan", "alias": "Linux_x86_64-gcc3"},
    193        {"target": "WINNT_aarch64-msvc-aarch64", "fileName": "{url_base}/{version}-win-arm64.zip"},
    194        {"target": "WINNT_x86-msvc", "fileName": "{url_base}/{version}-win-x86.zip"},
    195        {"target": "WINNT_x86-msvc-x64", "alias": "WINNT_x86-msvc"},
    196        {"target": "WINNT_x86-msvc-x86", "alias": "WINNT_x86-msvc"},
    197        {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}/{version}-win-x64.zip"},
    198        {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"},
    199        {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"},
    200    ]
    201    # fmt: on
    202    try:
    203        fetch_data_for_cdms(cdms, {"url_base": url_base, "version": version})
    204    except Exception as e:
    205        logging.error("calculate_widevinecdm_json: could not create JSON due to: %s", e)
    206        return ""
    207    else:
    208        return (
    209            "{\n"
    210            + '  "hashFunction": "sha512",\n'
    211            + f'  "name": "Widevine-{version}",\n'
    212            + '  "schema_version": 1000,\n'
    213            + '  "vendors": {\n'
    214            + '    "gmp-widevinecdm": {\n'
    215            + '      "platforms": {\n'
    216            + generate_json_for_cdms(cdms)
    217            + "      },\n"
    218            + f'      "version": "{version}"\n'
    219            + "    }\n"
    220            + "  }\n"
    221            + "}"
    222        )
    223 
    224 
    225 def calculate_chrome_component_json(
    226    name: str, altname: str, url_base: str, cdms
    227 ) -> str:
    228    try:
    229        version = fetch_url_for_cdms(cdms, {"url_base": url_base})
    230        fetch_data_for_cdms(cdms, {})
    231    except Exception as e:
    232        logging.error(
    233            "calculate_chrome_component_json: could not create JSON due to: %s", e
    234        )
    235        return ""
    236    else:
    237        return (
    238            "{\n"
    239            + '  "hashFunction": "sha512",\n'
    240            + f'  "name": "{name}-{version}",\n'
    241            + '  "schema_version": 1000,\n'
    242            + '  "vendors": {\n'
    243            + f'    "gmp-{altname}": {{\n'
    244            + '      "platforms": {\n'
    245            + generate_json_for_cdms(cdms)
    246            + "      },\n"
    247            + f'      "version": "{version}"\n'
    248            + "    }\n"
    249            + "  }\n"
    250            + "}"
    251        )
    252 
    253 
    254 def calculate_widevinecdm_component_json(url_base: str) -> str:
    255    # fmt: off
    256    cdms = [
    257        {"target": "Darwin_aarch64-gcc3", "fileName": "{url_base}&os=mac&arch=arm64&os_arch=arm64"},
    258        {"target": "Darwin_x86_64-gcc3", "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"},
    259        {"target": "Darwin_x86_64-gcc3-u-i386-x86_64", "fileName": "{url_base}&os=mac&arch=x64&os_arch=x64"},
    260        {"target": "Linux_x86_64-gcc3", "fileName": "{url_base}&os=Linux&arch=x64&os_arch=x64"},
    261        {"target": "Linux_x86_64-gcc3-asan", "alias": "Linux_x86_64-gcc3"},
    262        {"target": "WINNT_aarch64-msvc-aarch64", "fileName": "{url_base}&os=win&arch=arm64&os_arch=arm64"},
    263        {"target": "WINNT_x86-msvc", "fileName": "{url_base}&os=win&arch=x86&os_arch=x86"},
    264        {"target": "WINNT_x86-msvc-x64", "alias": "WINNT_x86-msvc"},
    265        {"target": "WINNT_x86-msvc-x86", "alias": "WINNT_x86-msvc"},
    266        {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}&os=win&arch=x64&os_arch=x64"},
    267        {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"},
    268        {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"},
    269    ]
    270    # fmt: on
    271    return calculate_chrome_component_json(
    272        "Widevine",
    273        "widevinecdm",
    274        url_base.format_map({"guid": "oimompecagnajdejgnnjijobebaeigek"}),
    275        cdms,
    276    )
    277 
    278 
    279 def calculate_widevinecdm_l1_component_json(url_base: str) -> str:
    280    # fmt: off
    281    cdms = [
    282        {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}&os=win&arch=x64&os_arch=x64"},
    283        {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"},
    284        {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"},
    285    ]
    286    # fmt: on
    287    return calculate_chrome_component_json(
    288        "Widevine-L1",
    289        "widevinecdm-l1",
    290        url_base.format_map({"guid": "neifaoindggfcjicffkgpmnlppeffabd"}),
    291        cdms,
    292    )
    293 
    294 
    295 def main():
    296    examples = """examples:
    297  python dom/media/tools/generateGmpJson.py widevine 4.10.2557.0 >toolkit/content/gmp-sources/widevinecdm.json
    298  python dom/media/tools/generateGmpJson.py --url http://localhost:8080 openh264 2.3.1 0a48f4d2e9be2abb4fb01b4c3be83cf44ce91a6e
    299  python dom/media/tools/generateGmpJson.py widevine_component"""
    300 
    301    parser = argparse.ArgumentParser(
    302        description="Generate JSON for GMP plugin updates",
    303        epilog=examples,
    304        formatter_class=argparse.RawDescriptionHelpFormatter,
    305    )
    306    parser.add_argument(
    307        "plugin",
    308        help="which plugin: openh264, widevine, widevine_component, widevine_l1_component",
    309    )
    310    parser.add_argument("version", help="version of plugin", nargs="?")
    311    parser.add_argument("revision", help="revision hash of plugin", nargs="?")
    312    parser.add_argument("--url", help="override base URL from which to fetch plugins")
    313    parser.add_argument(
    314        "--testrequest",
    315        action="store_true",
    316        help="request upcoming version for component update service",
    317    )
    318    args = parser.parse_args()
    319 
    320    if args.plugin == "openh264":
    321        url_base = "https://ciscobinary.openh264.org"
    322        if args.version is None or args.revision is None:
    323            parser.error("openh264 requires version and revision")
    324    elif args.plugin == "widevine":
    325        url_base = "https://redirector.gvt1.com/edgedl/widevine-cdm"
    326        if args.version is None:
    327            parser.error("widevine requires version")
    328        if args.revision is not None:
    329            parser.error("widevine cannot use revision")
    330    elif args.plugin in ("widevine_component", "widevine_l1_component"):
    331        url_base = "https://update.googleapis.com/service/update2/crx?response=redirect&x=id%3D{guid}%26uc&acceptformat=crx3&updaterversion=999"
    332        if args.testrequest:
    333            url_base += "&testrequest=1"
    334        if args.version is not None or args.revision is not None:
    335            parser.error("chrome component cannot use version or revision")
    336    else:
    337        parser.error("plugin not recognized")
    338 
    339    if args.url is not None:
    340        url_base = args.url
    341 
    342    if url_base[-1] == "/":
    343        url_base = url_base[:-1]
    344 
    345    if args.plugin == "openh264":
    346        json_result = calculate_gmpopenh264_json(args.version, args.revision, url_base)
    347    elif args.plugin == "widevine":
    348        json_result = calculate_widevinecdm_json(args.version, url_base)
    349    elif args.plugin == "widevine_component":
    350        json_result = calculate_widevinecdm_component_json(url_base)
    351    elif args.plugin == "widevine_l1_component":
    352        json_result = calculate_widevinecdm_l1_component_json(url_base)
    353 
    354    try:
    355        json.loads(json_result)
    356    except json.JSONDecodeError as e:
    357        logging.error("invalid JSON produced: %s", e)
    358    else:
    359        print(json_result)
    360 
    361 
    362 main()