diff -Nru apport-2.20.11/apport/hookutils.py apport-2.20.11/apport/hookutils.py --- apport-2.20.11/apport/hookutils.py 2021-10-26 19:58:44.000000000 +0200 +++ apport-2.20.11/apport/hookutils.py 2022-07-25 19:25:10.000000000 +0200 @@ -15,7 +15,6 @@ import subprocess import os import sys -import time import datetime import glob import re @@ -24,7 +23,6 @@ import tempfile import select import shutil -import locale import json from apport.packaging_impl import impl as packaging @@ -582,6 +580,8 @@ elif os.access('/var/log/syslog', os.R_OK): p = subprocess.Popen(['tail', '-n', '10000', '/var/log/syslog'], stdout=subprocess.PIPE) + else: + return '' return __filter_re_process(pattern, p) @@ -728,6 +728,29 @@ attach_gsettings_schema(report, schema) +def attach_journal_errors(report, time_window=10) -> None: + '''Attach journal warnings and errors. + + If the report contains a date, get the journal logs around that + date (plus/minus the time_window in seconds). Otherwise attach the + latest 1000 journal logs since the last boot. + ''' + + if not os.path.exists('/run/systemd/system'): + return + + crash_timestamp = report.get_timestamp() + if crash_timestamp: + before_crash = crash_timestamp - time_window + after_crash = crash_timestamp + time_window + args = [f'--since=@{before_crash}', f'--until=@{after_crash}'] + else: + args = ['-b', '--lines=1000'] + report['JournalErrors'] = command_output( + ['journalctl', '--priority=warning'] + args + ) + + def attach_network(report): '''Attach generic network-related information to report.''' @@ -970,17 +993,11 @@ else: return None - # report time is in local TZ - orig_ctime = locale.getlocale(locale.LC_TIME) try: - try: - locale.setlocale(locale.LC_TIME, 'C') - report_time = time.mktime(time.strptime(report['Date'])) - except KeyError: - return None - finally: - locale.setlocale(locale.LC_TIME, orig_ctime) - except locale.Error: + report_time = report.get_timestamp() + except AttributeError: + return None + if report_time is None: return None # determine session creation time @@ -1013,9 +1030,10 @@ mismatches = [] if os.path.exists(location): attach_root_command_outputs(report, {'CasperMD5json': "cat '%s'" % location}) - check = json.loads(report['CasperMD5json']) - result = check['result'] - mismatches = check['checksum_missmatch'] + if 'CasperMD5json' in report: + check = json.loads(report['CasperMD5json']) + result = check['result'] + mismatches = check['checksum_missmatch'] report['CasperMD5CheckResult'] = result if mismatches: report['CasperMD5CheckMismatches'] = ' '.join(mismatches) diff -Nru apport-2.20.11/apport/report.py apport-2.20.11/apport/report.py --- apport-2.20.11/apport/report.py 2022-02-23 00:39:23.000000000 +0100 +++ apport-2.20.11/apport/report.py 2022-07-25 19:25:10.000000000 +0200 @@ -9,8 +9,8 @@ # option) any later version. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. -import subprocess, tempfile, os.path, re, pwd, grp, os, time, io -import fnmatch, glob, traceback, errno, sys, atexit, locale, imp, stat +import subprocess, tempfile, os.path, re, pwd, grp, os, io +import fnmatch, glob, traceback, errno, sys, atexit, importlib.util, stat import xml.dom, xml.dom.minidom from xml.parsers.expat import ExpatError @@ -556,32 +556,16 @@ return None - @classmethod - def _python_module_path(klass, module): - '''Determine path of given Python module''' - - module = module.replace('/', '.').split('.') - pathlist = sys.path - - path = None - while module: - name = module.pop(0) - - try: - (fd, path, desc) = imp.find_module(name, pathlist) - except ImportError: - path = None - break - if fd: - fd.close() - pathlist = [path] - - if not module and desc[2] == imp.PKG_DIRECTORY: - module = ['__init__'] - - if path and path.endswith('.pyc'): - path = path[:-1] - return path + @staticmethod + def _python_module_path(module): + """Determine path of given Python module""" + try: + spec = importlib.util.find_spec(module) + except ImportError: + return None + if spec is None: + return None + return spec.origin def add_proc_info(self, pid=None, proc_pid_fd=None, extraenv=[]): '''Add /proc/pid information. @@ -1673,7 +1657,7 @@ if not gdb_path: return '', '' command = [gdb_path] - environ = None + environ = {"HOME": "/nonexistent"} if not same_arch: # check if we have gdb-multiarch @@ -1703,10 +1687,10 @@ gdb_sandbox, native_multiarch, gdb_sandbox) pyhome = '%s/usr' % gdb_sandbox # env settings need to be modified for gdb in a sandbox - environ = {'LD_LIBRARY_PATH': ld_lib_path, - 'PYTHONHOME': pyhome, - 'GCONV_PATH': '%s/usr/lib/%s/gconv' % - (gdb_sandbox, native_multiarch)} + environ |= {'LD_LIBRARY_PATH': ld_lib_path, + 'PYTHONHOME': pyhome, + 'GCONV_PATH': '%s/usr/lib/%s/gconv' % + (gdb_sandbox, native_multiarch)} command.insert(0, '%s/lib/%s/ld-linux-x86-64.so.2' % (gdb_sandbox, native_multiarch)) command += ['--ex', 'set data-directory %s/usr/share/gdb' % @@ -1817,21 +1801,3 @@ return None return (my_session, session_start_time) - - def get_timestamp(self): - '''Get timestamp (seconds since epoch) from Date field - - Return None if it is not present. - ''' - # report time is from asctime(), not in locale representation - orig_ctime = locale.getlocale(locale.LC_TIME) - try: - try: - locale.setlocale(locale.LC_TIME, 'C') - return time.mktime(time.strptime(self['Date'])) - except KeyError: - return None - finally: - locale.setlocale(locale.LC_TIME, orig_ctime) - except locale.Error: - return None diff -Nru apport-2.20.11/apport_python_hook.py apport-2.20.11/apport_python_hook.py --- apport-2.20.11/apport_python_hook.py 2021-10-26 19:58:44.000000000 +0200 +++ apport-2.20.11/apport_python_hook.py 2022-07-25 19:25:10.000000000 +0200 @@ -10,7 +10,6 @@ # option) any later version. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. -import os import sys CONFIG = '/etc/default/apport' @@ -21,8 +20,9 @@ # This doesn't use apport.packaging.enabled() because it is too heavyweight # See LP: #528355 - import re try: + import re + with open(CONFIG) as f: conf = f.read() return re.search(r'^\s*enabled\s*=\s*0\s*$', conf, re.M) is None @@ -31,7 +31,7 @@ return True -def apport_excepthook(exc_type, exc_obj, exc_tb): +def apport_excepthook(binary, exc_type, exc_obj, exc_tb): '''Catch an uncaught exception and make a traceback.''' # create and save a problem report. Note that exceptions in this code @@ -49,49 +49,35 @@ if exc_type in (KeyboardInterrupt, ): return - # if python apt modules are not built for the python version than it - # is not supported. LP: #1774843 - try: - import apt_pkg - # make pyflakes happy - apt_pkg.DATE - except ImportError: - return - # do not do anything if apport was disabled if not enabled(): return try: - from cStringIO import StringIO - StringIO # pyflakes - except ImportError: - from io import StringIO + try: + from cStringIO import StringIO + StringIO # pyflakes + except ImportError: + from io import StringIO - import re, traceback - from apport.fileutils import likely_packaged, get_recent_crashes + import os + import re, traceback - # apport will look up the package from the executable path. - try: - binary = os.path.realpath(os.path.join(os.getcwd(), sys.argv[0])) - except (TypeError, AttributeError, IndexError): - # the module has mutated sys.argv, plan B - try: - binary = os.readlink('/proc/%i/exe' % os.getpid()) - except OSError: - return + import apport.report + from apport.fileutils import likely_packaged, get_recent_crashes + except (ImportError, IOError): + return - # for interactive python sessions, sys.argv[0] == ''; catch that and - # other irregularities - if not os.access(binary, os.X_OK) or not os.path.isfile(binary): + # for interactive Python sessions, sys.argv[0] == "" + if not binary: return + binary = os.path.realpath(binary) + # filter out binaries in user accessible paths if not likely_packaged(binary): return - import apport.report - pr = apport.report.Report() # special handling of dbus-python exceptions @@ -210,4 +196,23 @@ def install(): '''Install the python apport hook.''' - sys.excepthook = apport_excepthook + # Record before the program can mutate sys.argv and can call os.chdir(). + binary = sys.argv[0] + if binary and not binary.startswith("/"): + # pylint: disable=import-outside-toplevel; for Python starup time + import os + + try: + binary = f"{os.getcwd()}/{binary}" + except FileNotFoundError: + try: + binary = os.readlink("/proc/self/cwd") + if binary.endswith(" (deleted)"): + binary = binary[:-10] + except IOError: + return + + def partial_apport_excepthook(exc_type, exc_obj, exc_tb): + return apport_excepthook(binary, exc_type, exc_obj, exc_tb) + + sys.excepthook = partial_apport_excepthook diff -Nru apport-2.20.11/backends/packaging-apt-dpkg.py apport-2.20.11/backends/packaging-apt-dpkg.py --- apport-2.20.11/backends/packaging-apt-dpkg.py 2021-10-26 19:58:44.000000000 +0200 +++ apport-2.20.11/backends/packaging-apt-dpkg.py 2022-07-25 19:25:10.000000000 +0200 @@ -907,6 +907,7 @@ lp_cache[pkg] = ver else: obsolete += '%s version %s required, but %s is available\n' % (pkg, ver, cache_pkg.candidate.version) + ver = cache_pkg.candidate.version candidate = cache_pkg.candidate real_pkgs.add(pkg) diff -Nru apport-2.20.11/data/apport apport-2.20.11/data/apport --- apport-2.20.11/data/apport 2022-05-10 15:23:23.000000000 +0200 +++ apport-2.20.11/data/apport 2022-07-25 19:25:10.000000000 +0200 @@ -13,6 +13,7 @@ # option) any later version. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. +import contextlib import sys, os, os.path, subprocess, time, traceback, pwd, io import signal, inspect, grp, fcntl, socket, atexit, array, struct import errno, argparse, stat @@ -131,14 +132,15 @@ log = os.environ.get('APPORT_LOG_FILE', '/var/log/apport.log') try: f = os.open(log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600) - try: - admgid = grp.getgrnam('adm')[2] - os.chown(log, -1, admgid) - os.chmod(log, 0o640) - except KeyError: - pass # if group adm doesn't exist, just leave it as root except OSError: # on a permission error, don't touch stderr return + + # if group adm doesn't exist, just leave it as root + with contextlib.suppress(KeyError, OSError): + admgid = grp.getgrnam('adm')[2] + os.chown(log, -1, admgid) + os.chmod(log, 0o640) + os.dup2(f, 1) os.dup2(f, 2) sys.stderr = os.fdopen(2, 'wb') @@ -179,7 +181,9 @@ signal.signal(signal.SIGBUS, _log_signal_handler) -def write_user_coredump(pid, timestamp, limit, from_report=None): +def write_user_coredump( + pid, timestamp, limit, coredump_fd=None, from_report=None +): '''Write the core into a directory if ulimit requests it.''' # three cases: @@ -230,8 +234,7 @@ error_log('writing core dump %s of size %i' % (core_name, core_size)) os.write(core_file, r['CoreDump']) else: - # read from stdin - block = os.read(0, 1048576) + block = os.read(coredump_fd, 1048576) while True: size = len(block) @@ -248,7 +251,7 @@ os.close(core_file) os.unlink(core_path, dir_fd=cwd) return - block = os.read(0, 1048576) + block = os.read(coredump_fd, 1048576) # Make sure the user can read it os.fchown(core_file, crash_uid, -1) @@ -460,7 +463,9 @@ ) if options.pid is None: - parser.print_usage() + parser.error( + "No process ID (PID) provided. Please specify PID with -p/--pid." + ) sys.exit(1) for arg in rest: @@ -485,6 +490,8 @@ # ################################################################# +init_error_log() + # systemd socket activation if 'LISTEN_FDS' in os.environ: try: @@ -530,8 +537,6 @@ options = parse_arguments() -init_error_log() - # Check if we received a valid global PID (kernel >= 3.12). If we do, # then compare it with the local PID. If they don't match, it's an # indication that the crash originated from another PID namespace. @@ -640,6 +645,7 @@ signum = options.signal_number core_ulimit = options.core_ulimit dump_mode = options.dump_mode + coredump_fd = sys.stdin.fileno() get_pid_info(pid) @@ -687,7 +693,7 @@ # ignore SIGQUIT (it's usually deliberately generated by users) if signum == str(int(signal.SIGQUIT)): - write_user_coredump(pid, process_start, core_ulimit) + write_user_coredump(pid, process_start, core_ulimit, coredump_fd) sys.exit(0) info = apport.Report('Crash') @@ -703,6 +709,8 @@ # etc). if options.executable_path is not None and os.path.exists(options.executable_path): info['ExecutablePath'] = options.executable_path + else: + info['ExecutablePath'] = os.readlink('exe', dir_fd=proc_pid_fd) # Drop privileges temporarily to make sure that we don't # include information in the crash report that the user should @@ -738,7 +746,7 @@ error_log('executable does not belong to a package, ignoring') # check if the user wants a core dump recover_privileges() - write_user_coredump(pid, process_start, core_ulimit) + write_user_coredump(pid, process_start, core_ulimit, coredump_fd) sys.exit(0) # ignore SIGXCPU and SIGXFSZ since this indicates some external @@ -746,7 +754,7 @@ if signum in [str(signal.SIGXCPU), str(signal.SIGXFSZ)]: error_log('Ignoring signal %s (caused by exceeding soft RLIMIT)' % signum) recover_privileges() - write_user_coredump(pid, process_start, core_ulimit) + write_user_coredump(pid, process_start, core_ulimit, coredump_fd) sys.exit(0) # ignore blacklisted binaries @@ -785,7 +793,9 @@ crash_counter = apport.fileutils.get_recent_crashes(f) crash_counter += 1 if crash_counter > 1: - write_user_coredump(pid, process_start, core_ulimit) + write_user_coredump( + pid, process_start, core_ulimit, coredump_fd + ) error_log('this executable already crashed %i times, ignoring' % crash_counter) sys.exit(0) # remove the old file, so that we can create the new one with @@ -793,7 +803,9 @@ os.unlink(report) else: error_log('apport: report %s already exists and unseen, skipping to avoid disk usage DoS' % report) - write_user_coredump(pid, process_start, core_ulimit) + write_user_coredump( + pid, process_start, core_ulimit, coredump_fd + ) sys.exit(0) # we prefer having a file mode of 0 while writing; fd = os.open(report, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0) diff -Nru apport-2.20.11/data/general-hooks/generic.py apport-2.20.11/data/general-hooks/generic.py --- apport-2.20.11/data/general-hooks/generic.py 2021-10-26 19:58:44.000000000 +0200 +++ apport-2.20.11/data/general-hooks/generic.py 2022-07-25 19:25:10.000000000 +0200 @@ -31,7 +31,10 @@ treshold = 50 for mount in mounts: - st = os.statvfs(mount) + try: + st = os.statvfs(mount) + except FileNotFoundError: + continue free_mb = st.f_bavail * st.f_frsize / 1000000 if free_mb < treshold: @@ -88,9 +91,7 @@ # log errors if report['ProblemType'] == 'Crash': - if os.path.exists('/run/systemd/system'): - report['JournalErrors'] = apport.hookutils.command_output( - ['journalctl', '-b', '--priority=warning', '--lines=1000']) + apport.hookutils.attach_journal_errors(report) if __name__ == '__main__': diff -Nru apport-2.20.11/data/whoopsie-upload-all apport-2.20.11/data/whoopsie-upload-all --- apport-2.20.11/data/whoopsie-upload-all 2022-04-13 21:45:49.000000000 +0200 +++ apport-2.20.11/data/whoopsie-upload-all 2022-07-25 19:25:10.000000000 +0200 @@ -102,15 +102,15 @@ r.add_gdb_info() except (IOError, EOFError, OSError, zlib.error) as e: # gzip.GzipFile.read can raise zlib.error. See LP bug #1947800 - if hasattr(e, 'errno'): - # calling add_gdb_info raises ENOENT if the crash's executable - # is missing or gdb is not available but apport-retrace could - # still process it - if e.errno != errno.ENOENT: - sys.stderr.write('ERROR: processing %s: %s\n' % (report, str(e))) - if os.path.exists(report): - os.unlink(report) - return None + + # Calling add_gdb_info raises ENOENT if the crash's executable + # is missing or gdb is not available, but apport-retrace could + # still process it. + if getattr(e, 'errno', None) != errno.ENOENT: + sys.stderr.write('ERROR: processing %s: %s\n' % (report, str(e))) + if os.path.exists(report): + os.unlink(report) + return None # write updated report, we use os.open and os.fdopen as # /proc/sys/fs/protected_regular is set to 1 (LP: #1848064) diff -Nru apport-2.20.11/debian/changelog apport-2.20.11/debian/changelog --- apport-2.20.11/debian/changelog 2022-05-10 15:23:35.000000000 +0200 +++ apport-2.20.11/debian/changelog 2022-07-25 19:25:10.000000000 +0200 @@ -1,3 +1,78 @@ +apport (2.20.11-0ubuntu82.2) jammy; urgency=medium + + * Point Vcs-* URIs to git + * whoopsie-upload-all: Do not upload after EOFError/zlib.error + * Fix UnboundLocalError if syslog is not accessible + * Grab a slice of JournalErrors around the crash time (LP: #1962454) + * general-hooks/generic.py: Fix FileNotFoundError if home does not exist + * data/apport: + - Fix log file writing if chown/chmod fails + - Initialize error log as first step + - Fix PermissionError for setuid programs inside container (LP: #1982487) + - Fix reading from stdin inside containers (LP: #1982555) + * apport_python_hook: + - Import io and os only on demand + - Check being enabled first + - Properly handle missing modules + - Fix FileNotFoundError if cwd was deleted (LP: #1979637) + - Fix crash if os.getcwd() fails (LP: #1977954) + * apport-gtk: Exclude trailing dot from URL links (LP: #1978301) + * Fix AttributeError: 'NoneType' object has no attribute 'write' + (LP: #1979211) + * Replace deprecated 'imp' module (LP: #1947425) + * Fix KeyError: 'CasperMD5json' (LP: #1964828) + * Fix trying to find debug packages for non-existing version + * Run more test cases during package build + * Update test dependencies + * Mark autopkgtest with isolation-container restriction + * tests: + - Move to unittest's builtin "mock" module + - Make test_find_package_desktopfile deterministic + - Fix failure if kernel module isofs is not installed + - Set HOME to /nonexistent when calling gdb + - Ensure that as/gcc calls exit successfully + - Do not check recommended dependencies + - Rename test directory into tests (for tests.helper import) + - Introduce pidof helper function + - Skip UI test if kernel thread is not found + - Fix GTK UI tests if whoopsie.path is disabled + - Fix GTK UI race condition and reduce timeout again (LP: #1780767) + - Fix race in tests for run_report_bug() + - Fix race in test_crash_system_slice + - Fix check for not running test executable + - Mock add_gdb_info calls in KDE UI tests + - Generate test crash file for test_add_gdb_info_script directly + - Use _generate_sigsegv_report in test_add_gdb_info_abort + - Use shadow in *_different_binary_source + - Fix KDE UI tests if whoopsie.path is disabled + - Fix race with progress dialog in KDE UI tests + - Mock kernel package version in UI test + - Fix test_kerneloops_nodetails if kernel is not installed + - Drop obsolete test_nonwritable_cwd + - Fix report generation for non-readable exe + - Drop obsolete test_crash_setuid_nonwritable_cwd + - Drop useless tests for suid_dumpable=0 + - Drop useless tests for no core signals + - Drop broken test_crash_setuid_drop_and_kill + - Expect linux-signed on arm64/s390x as well + - Skip SegvAnalysis for non x86 architectures + - Use unlimited core ulimit for SIGQUIT test + - Drop using apt-helper + - Prevent pkexec call and spawning pkttyagent + - Fix race with progress window in GTK UI tests + - Use sleep instead of yes for tests + - Fix killing itself in test_unpackaged_script + - Fix test_add_gdb_info_script on armhf + - Fix wrong Ubuntu archive URI on ports + - Fix KeyError in test_install_packages_unversioned + - Upgrade APT tests to use Ubuntu 22.04 (jammy) + - Fix path of installed Java crash.jar + - Run UI KDE tests again + - Print stdout/stderr if GDB fails + - Print Apport log if reports was not created + + -- Benjamin Drung Mon, 25 Jul 2022 19:25:10 +0200 + apport (2.20.11-0ubuntu82.1) jammy-security; urgency=medium * SECURITY UPDATE: Fix multiple security issues diff -Nru apport-2.20.11/debian/control apport-2.20.11/debian/control --- apport-2.20.11/debian/control 2022-02-23 00:32:52.000000000 +0100 +++ apport-2.20.11/debian/control 2022-07-25 19:25:10.000000000 +0200 @@ -7,30 +7,43 @@ python3-gi, gir1.2-glib-2.0 (>= 1.29.17), lsb-release, - net-tools, + pkg-config, python3-all Build-Depends-Indep: python3-distutils-extra (>= 2.24~), python3-apt (>= 0.7.9), + binutils, + default-jdk-headless | java-sdk-headless, + dirmngr, dh-python, + gnome-icon-theme, + gpg, intltool, + iputils-ping, + kmod, + python3-dbus, + python3-launchpadlib, + python3-pyqt5, + python3-systemd, + ubuntu-dbgsym-keyring, + ubuntu-keyring, + valgrind, xvfb, - python3-mock, procps, psmisc, gir1.2-gtk-3.0 (>= 3.1.90), gir1.2-wnck-3.0, pycodestyle | pep8, pyflakes3, - xterm, + ubuntu-dbgsym-keyring, + ubuntu-keyring, dbus-x11, gvfs-daemons, - libglib2.0-dev, libc6-dbg | libc-dbg, - default-jdk | java-sdk Maintainer: Ubuntu Developers Standards-Version: 3.9.8 X-Python3-Version: >= 3.0 -Vcs-Bzr: https://code.launchpad.net/~ubuntu-core-dev/ubuntu/jammy/apport/ubuntu +Vcs-Browser: https://code.launchpad.net/~ubuntu-core-dev/ubuntu/+source/apport/+git/apport +Vcs-Git: https://git.launchpad.net/~ubuntu-core-dev/ubuntu/+source/apport -b ubuntu/jammy Homepage: https://wiki.ubuntu.com/Apport Package: apport diff -Nru apport-2.20.11/debian/rules apport-2.20.11/debian/rules --- apport-2.20.11/debian/rules 2021-10-26 19:58:44.000000000 +0200 +++ apport-2.20.11/debian/rules 2022-07-22 14:55:31.000000000 +0200 @@ -22,12 +22,8 @@ ifeq (, $(findstring nocheck, $(DEB_BUILD_OPTIONS))) # drop LD_PRELOAD to avoid running under fakeroot; drop TMPDIR to work # around LP#972324 (set by autopkgtest) - # run subset of tests that work on buildds, full tests are in - # autopkgtest - set -e; for t in apport_unpack apport_valgrind crashdb hooks packaging \ - parse_segv problem_report rethread; do \ - env -u LD_PRELOAD -u TMPDIR APPORT_TEST_NOXVFB=1 sh test/run $$t; \ - done + env -u LD_PRELOAD -u TMPDIR HOME=$$(mktemp -d -t home.XXXXXXXXXX) SKIP_ONLINE_TESTS=1 SKIP_SYSTEM_TESTS=1 sh tests/run + rm -rf /tmp/home.* endif override_dh_installinit: diff -Nru apport-2.20.11/gtk/apport-gtk apport-2.20.11/gtk/apport-gtk --- apport-2.20.11/gtk/apport-gtk 2021-10-26 19:58:44.000000000 +0200 +++ apport-2.20.11/gtk/apport-gtk 2022-07-25 19:25:10.000000000 +0200 @@ -383,13 +383,7 @@ def _ui_message_dialog(self, title, text, _type, buttons=Gtk.ButtonsType.CLOSE): self.md = Gtk.MessageDialog(message_type=_type, buttons=buttons) if 'http://' in text or 'https://' in text: - if not isinstance(text, bytes): - text = text.encode('UTF-8') - text = GLib.markup_escape_text(text) - text = re.sub(r'(https?://[a-zA-Z0-9._-]+(?:[a-zA-Z0-9_#?%+=./-])*)', - r'\1', text) - # turn URLs into links - self.md.set_markup(text) + self.md.set_markup(text_to_markup(text)) else: # work around gnome #620579 self.md.set_property('text', text) @@ -590,6 +584,16 @@ return True +def text_to_markup(text: str) -> str: + """Turn URLs into links""" + escaped_text = GLib.markup_escape_text(text) + return re.sub( + "(https?://[a-zA-Z0-9._-]+[a-zA-Z0-9_#?%+=./-]*[a-zA-Z0-9_#?%+=/-])", + r'\1', + escaped_text, + ) + + if __name__ == '__main__': if not have_display: apport.fatal('This program needs a running X session. Please see "man apport-cli" for a command line version of Apport.') diff -Nru apport-2.20.11/problem_report.py apport-2.20.11/problem_report.py --- apport-2.20.11/problem_report.py 2021-10-26 19:58:44.000000000 +0200 +++ apport-2.20.11/problem_report.py 2022-07-25 19:25:10.000000000 +0200 @@ -11,6 +11,7 @@ # option) any later version. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. +import locale import zlib, base64, time, sys, gzip, struct, os from email.encoders import encode_base64 from email.mime.multipart import MIMEMultipart @@ -269,6 +270,24 @@ raise ValueError('%s has no binary content' % [item for item, element in b64_block.items() if element is False]) + def get_timestamp(self) -> int: + '''Get timestamp (seconds since epoch) from Date field + + Return None if it is not present. + ''' + # report time is from asctime(), not in locale representation + orig_ctime = locale.getlocale(locale.LC_TIME) + try: + try: + locale.setlocale(locale.LC_TIME, 'C') + return int(time.mktime(time.strptime(self['Date']))) + except KeyError: + return None + finally: + locale.setlocale(locale.LC_TIME, orig_ctime) + except locale.Error: + return None + def has_removed_fields(self): '''Check if the report has any keys which were not loaded.