Safely handle errors when adding/editing profiles. Fixes #3347
Some checks failed
nmap multiplatform autobuilds / build (arm64, gcc, ubuntu-latest-gcc-arm64, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, freebsd-15-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, macos-15-clang, macos-15) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, macos-26-clang, macos-26) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, netbsd-10-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, openbsd-7-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, solaris-11-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, ubuntu-latest-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (egcc, openbsd-7-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, freebsd-15-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, netbsd-10-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, solaris-11-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, ubuntu-latest-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (msvc, windows-latest-msvc, windows-latest) (push) Has been cancelled

This commit is contained in:
dmiller 2026-07-24 22:24:03 +00:00
parent 3cb5343e43
commit e2265fe97c
3 changed files with 76 additions and 43 deletions

View file

@ -58,7 +58,8 @@
import re
from configparser import DuplicateSectionError, NoSectionError, NoOptionError
from configparser import DuplicateSectionError, NoSectionError, NoOptionError, \
InterpolationError
from configparser import Error as ConfigParser_Error
from zenmapCore.Paths import Path
@ -202,14 +203,32 @@ class Profile(UmitConfigParser, object):
self.attributes = {}
def _get_it(self, profile, attribute):
if self._verify_profile(profile):
return self.get(profile, attribute)
return ""
def _get_it(self, profile, attribute, fallback=None):
try:
return self.get(profile, attribute, fallback=fallback)
except InterpolationError:
pass
except Exception:
return ""
try:
value = self.get(profile, attribute, fallback=fallback, raw=True)
if attribute == "command" and value.endswith("%s"):
value = value[:-2]
return value
except Exception:
return ""
def _set_it(self, profile, attribute, value=''):
if self._verify_profile(profile):
try:
return self.set(profile, attribute, value)
except NoSectionError:
pass
except ValueError as e:
if attribute == "command" and value.endswith("%s"):
return self._set_it(profile, attribute, value[:-2])
raise ValueError(_("Invalid %(attribute)s entry") % {
"attribute": attribute
}) from e
def add_profile(self, profile_name, **attributes):
"""Add a profile with the given name and attributes to the collection
@ -219,24 +238,23 @@ class Profile(UmitConfigParser, object):
log.debug(">>> Add Profile '%s': %s" % (profile_name, attributes))
try:
if not attributes["command"]:
raise NoOptionError("command", profile_name)
with self.section_transaction(profile_name):
self.add_section(profile_name)
except DuplicateSectionError:
return None
# Set each of the attributes ("command", "description") in the
# ConfigParser.
for attr in attributes:
self._set_it(profile_name, attr, attributes[attr])
# Set each of the attributes ("command", "description") in the
# ConfigParser.
for attr in attributes:
self._set_it(profile_name, attr, attributes[attr])
self.save_changes()
self.save_changes()
def remove_profile(self, profile_name):
try:
with self.section_transaction(profile_name):
self.remove_section(profile_name)
except Exception:
pass
self.save_changes()
self.save_changes()
def _verify_profile(self, profile_name):
if profile_name not in self.sections():
@ -362,7 +380,10 @@ class CommandProfile (Profile, object):
Profile.__init__(self, user_profile)
def get_command(self, profile):
command_string = self._get_it(profile, 'command')
try:
command_string = self._get_it(profile, 'command', "nmap")
except ConfigParser_Error:
return "nmap"
# Corrupted config file can include multiple commands.
# Take the first one.
if isinstance(command_string, list):
@ -376,7 +397,12 @@ class CommandProfile (Profile, object):
return command_string
def get_description(self, profile):
desc = self._get_it(profile, 'description')
try:
desc = self._get_it(profile, 'description', "")
except NoSectionError:
raise
except ConfigParser_Error:
return ""
if isinstance(desc, list):
desc = " ".join(desc)
return desc
@ -645,20 +671,3 @@ class PathsConfig(object):
nmap_command_path = property(get_nmap_command_path, set_nmap_command_path)
ndiff_command_path = property(
get_ndiff_command_path, set_ndiff_command_path)
# Exceptions
class ProfileNotFound:
def __init__(self, profile):
self.profile = profile
def __str__(self):
return "No profile named '" + self.profile + "' found!"
class ProfileCouldNotBeSaved:
def __init__(self, profile):
self.profile = profile
def __str__(self):
return "Profile named '" + self.profile + "' could not be saved!"

View file

@ -58,6 +58,7 @@
from configparser import ConfigParser, DEFAULTSECT, NoOptionError, \
NoSectionError
from contextlib import contextmanager
from zenmapCore.UmitLogging import log
@ -68,6 +69,27 @@ class UmitConfigParser(ConfigParser):
self.failed = False
ConfigParser.__init__(self, *args)
@contextmanager
def section_transaction(self, section):
section_existed = self.has_section(section)
if section_existed:
section_copy = dict(self.items(section, raw=True))
try:
yield self
except Exception:
if self.has_section(section):
try:
self.remove_section(section)
except Exception:
pass
if section_existed:
try:
self.add_section(section)
self[section].update(section_copy)
except Exception:
pass
raise
def set(self, section, option, value):
if not self.has_section(section):
self.add_section(section)

View file

@ -323,13 +323,15 @@ class ProfileEditor(HIGWindow):
profile_name,
command=command,
description=description)
except ValueError:
except ValueError as e:
message = str(e)
if e.__cause__:
message += "\nReason: {}".format(e.__cause__)
alert = HIGAlertDialog(
message_format=_('Disallowed profile name'),
secondary_text=_('Sorry, the name "%s" is not allowed due '
'to technical limitations. (The underlying '
'ConfigParser used to store profiles does not allow '
'it.) Choose a different name.' % profile_name))
message_format=_('Unable to save profile'),
secondary_text=_(
'An error was encountered when saving the profile:\n'
'%(message)s') % {"message": message})
alert.run()
alert.destroy()
return