diff --git a/bin/chrome_update.py b/bin/chrome_update.py new file mode 100755 index 00000000..6c4af312 --- /dev/null +++ b/bin/chrome_update.py @@ -0,0 +1,106 @@ +#! /usr/bin/env -S guix shell python python-wrapper python-requests python-feedparser -- python + +# Usage: ./chrome_update.py +# Most of this script is based on the update.py script from Nixpkgs: +# https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/networking/browsers/chromium/update.py + +import json +import re +import subprocess +import textwrap + +from collections import OrderedDict +from urllib.request import urlopen + +import feedparser +import requests + +RELEASES_URL = 'https://versionhistory.googleapis.com/v1/chrome/platforms/linux/channels/all/versions/all/releases' +DEB_URL = 'https://dl.google.com/linux/chrome/deb/pool/main/g' +HTML_TAGS = re.compile(r'<[^>]+>') + +def guix_download(url): + """Prefetches the content of the given URL and returns its hash.""" + out = subprocess.check_output(['guix', 'download', url], stderr=subprocess.DEVNULL).decode().splitlines()[-1] + return out + + +def get_package_version(package): + out = subprocess.check_output(['bash', '-c', f"guix show {package} | guix shell recutils -- recsel -p version"], stderr=subprocess.DEVNULL).decode().splitlines()[-1] + return out.replace("version: ", "") + + +def print_cves(target_version): + feed = feedparser.parse('https://chromereleases.googleblog.com/feeds/posts/default') + + for entry in feed.entries: + url = requests.get(entry.link).url.split('?')[0] + if re.search(r'Stable Channel Update for Desktop', entry.title): + if target_version and entry.title == '': + # Workaround for a special case (Chrome Releases bug?): + if 'the-stable-channel-has-been-updated-to' not in url: + continue + else: + continue + content = entry.content[0].value + content = HTML_TAGS.sub('', content) # Remove any HTML tags + if re.search(r'Linux', content) is None: + continue + # print(url) # For debugging purposes + version = re.search(r'\d+(\.\d+){3}', content).group(0) + if target_version: + if version != target_version: + continue + + if fixes := re.search(r'This update includes .+ security fix(es)?\.', content): + fixes = fixes.group(0) + if zero_days := re.search(r'Google is aware( of reports)? th(e|at) .+ in the wild\.', content): + fixes += " " + zero_days.group(0) + print('\n' + '\n'.join(textwrap.wrap(fixes, width=72))) + if cve_list := re.findall(r'CVE-[^: ]+', content): + cve_list = list(OrderedDict.fromkeys(cve_list)) # Remove duplicates but preserve the order + cve_string = ', '.join(cve_list) + print("\nFixes " + '\n'.join(textwrap.wrap(cve_string, width=72)) + ".") + break + + +with urlopen(RELEASES_URL) as resp: + releases = json.load(resp)['releases'] + + for release in releases: + if "endTime" in release["serving"]: + continue + + channel_name = re.findall("chrome\/platforms\/linux\/channels\/(.*)\/versions\/", release['name'])[0] + + channel = {'version': release['version']} + cves = "" + if channel_name == 'dev': + google_chrome_suffix = 'unstable' + elif channel_name == 'ungoogled-chromium': + google_chrome_suffix = 'stable' + else: + google_chrome_suffix = channel_name + + channel['name'] = f"google-chrome-{google_chrome_suffix}" + + try: + channel['hash'] = guix_download( + f'{DEB_URL}/{channel["name"]}/' + + f'{channel["name"]}_{release["version"]}-1_amd64.deb') + except subprocess.CalledProcessError: + # This release isn't actually available yet. Continue to + # the next one. + continue + + print(f"====================================== {channel['name']} ===================================") + print(f"Current version: {get_package_version(channel['name'])}") + print(f"Pulled version: {channel['version']}") + print(f"Hash: {channel['hash']}") + print("Commit message:\n\n") + print(f"nongnu: {channel['name']}: Update to {channel['version']}. ") + if channel_name == "stable": + print_cves(channel['version']) + print("") + print(f"* nongnu/packages/chrome.scm ({channel['name']}): Update to {channel['version']}.") + print("")