calcurse-caldav: improve config file error handling

The previous implementation allowed sections and keys other than those
used by the script which led to a variety of bug reports due to typos in
the configuration. Disallow entries other than those explicitly used and
make both section and key names case-sensitive (previously, only section
names where case-sensitive).

Check that Hostname and Path are set before using them.

Addresses GitHub issues #327 and #350.

Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
This commit is contained in:
Lukas Fleischer 2021-04-03 12:05:11 -04:00
parent 4777c60056
commit 594bd62378

View File

@ -22,6 +22,70 @@ except ModuleNotFoundError:
pass pass
class Config:
_map = {}
def __init__(self, fn):
self._map = {
'Auth': {
'Password': None,
'Username': None,
},
'CustomHeaders': {},
'General': {
'AuthMethod': 'basic',
'Binary': 'calcurse',
'Debug': False,
'DryRun': True,
'HTTPS': True,
'Hostname': None,
'InsecureSSL': False,
'Path': None,
'SyncFilter': 'cal,todo',
'Verbose': False,
},
'OAuth2': {
'ClientID': None,
'ClientSecret': None,
'RedirectURI': 'http://127.0.0.1',
'Scope': None,
},
}
config = configparser.RawConfigParser()
config.optionxform = str
if verbose:
print('Loading configuration from ' + configfn + '...')
try:
config.read_file(open(fn))
except FileNotFoundError as e:
die('Configuration file not found: {}'.format(fn))
for sec in config.sections():
if sec not in self._map:
die('Unexpected config section: {}'.format(sec))
if not self._map[sec]:
# Import section with custom key-value pairs.
self._map[sec] = dict(config.items(sec))
continue
# Import section with predefined keys.
for key, val in config.items(sec):
if key not in self._map[sec]:
die('Unexpected config key in section {}: {}'.format(sec, key))
if type(self._map[sec][key]) == bool:
self._map[sec][key] = config.getboolean(sec, key)
else:
self._map[sec][key] = val
def section(self, section):
return self._map[section]
def get(self, section, key):
return self._map[section][key]
def msgfmt(msg, prefix=''): def msgfmt(msg, prefix=''):
lines = [] lines = []
for line in msg.splitlines(): for line in msg.splitlines():
@ -592,101 +656,46 @@ debug_raw = args.debug_raw
password = os.getenv('CALCURSE_CALDAV_PASSWORD') password = os.getenv('CALCURSE_CALDAV_PASSWORD')
# Read configuration. # Read configuration.
config = configparser.RawConfigParser() config = Config(configfn)
if verbose:
print('Loading configuration from ' + configfn + '...')
try:
config.read_file(open(configfn))
except FileNotFoundError as e:
die('Configuration file not found: {}'.format(configfn))
if config.has_option('General', 'InsecureSSL'): authmethod = config.get('General', 'AuthMethod').lower()
insecure_ssl = config.getboolean('General', 'InsecureSSL') calcurse = [config.get('General', 'Binary')]
else: debug = debug or config.get('General', 'Debug')
insecure_ssl = False dry_run = config.get('General', 'DryRun')
hostname = config.get('General', 'Hostname')
https = config.get('General', 'HTTPS')
insecure_ssl = config.get('General', 'InsecureSSL')
path = config.get('General', 'Path')
sync_filter = config.get('General', 'SyncFilter')
verbose = verbose or config.get('General', 'Verbose')
# Read config for "HTTPS" option (default=True) password = password or config.get('Auth', 'Password')
if config.has_option('General', 'HTTPS'): username = config.get('Auth', 'Username')
https = config.getboolean('General', 'HTTPS')
else:
https = True
if config.has_option('General', 'Binary'): client_id = config.get('OAuth2', 'ClientID')
calcurse = [config.get('General', 'Binary')] client_secret = config.get('OAuth2', 'ClientSecret')
else: redirect_uri = config.get('OAuth2', 'RedirectURI')
calcurse = ['calcurse'] scope = config.get('OAuth2', 'Scope')
custom_headers = config.section('CustomHeaders')
# Append data directory to calcurse command.
if datadir: if datadir:
check_dir(datadir) check_dir(datadir)
calcurse += ['-D', datadir] calcurse += ['-D', datadir]
if config.has_option('General', 'DryRun'): # Validate sync filter.
dry_run = config.getboolean('General', 'DryRun') invalid_filter_values = validate_sync_filter()
else: if len(invalid_filter_values):
dry_run = True die('Invalid value(s) in SyncFilter option: ' + ', '.join(invalid_filter_values))
if not verbose and config.has_option('General', 'Verbose'): # Ensure host name and path are defined and initialize *_uri.
verbose = config.getboolean('General', 'Verbose') if not hostname:
die('Hostname missing in configuration.')
if not debug and config.has_option('General', 'Debug'): if not path:
debug = config.getboolean('General', 'Debug') die('Path missing in configuration.')
urlprefix = "https://" if https else "http://"
if config.has_option('General', 'AuthMethod'): path = '/{}/'.format(path.strip('/'))
authmethod = config.get('General', 'AuthMethod').lower()
else:
authmethod = 'basic'
if config.has_option('General', 'SyncFilter'):
sync_filter = config.get('General', 'SyncFilter')
invalid_filter_values = validate_sync_filter()
if len(invalid_filter_values):
die('Invalid value(s) in SyncFilter option: ' + ', '.join(invalid_filter_values))
else:
sync_filter = 'cal,todo'
if config.has_option('Auth', 'UserName'):
username = config.get('Auth', 'UserName')
else:
username = None
if config.has_option('Auth', 'Password') and not password:
password = config.get('Auth', 'Password')
if config.has_section('CustomHeaders'):
custom_headers = dict(config.items('CustomHeaders'))
else:
custom_headers = {}
if config.has_option('OAuth2', 'ClientID'):
client_id = config.get('OAuth2', 'ClientID')
else:
client_id = None
if config.has_option('OAuth2', 'ClientSecret'):
client_secret = config.get('OAuth2', 'ClientSecret')
else:
client_secret = None
if config.has_option('OAuth2', 'Scope'):
scope = config.get('OAuth2', 'Scope')
else:
scope = None
if config.has_option('OAuth2', 'RedirectURI'):
redirect_uri = config.get('OAuth2', 'RedirectURI')
else:
redirect_uri = 'http://127.0.0.1'
# Change URl prefix according to HTTP/HTTPS
if https:
urlprefix = "https://"
else:
urlprefix = "http://"
hostname = config.get('General', 'HostName')
path = '/' + config.get('General', 'Path').strip('/') + '/'
hostname_uri = urlprefix + hostname hostname_uri = urlprefix + hostname
absolute_uri = hostname_uri + path absolute_uri = hostname_uri + path