Skip to content

Commit 5937ab8

Browse files
committed
Fix empty reference error. Closes #177
1 parent cc35779 commit 5937ab8

File tree

4 files changed

+40
-16
lines changed

4 files changed

+40
-16
lines changed

keepmenu/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from keepmenu.menu import dmenu_err
1414

15+
1516
# Setup logging for debugging. Usage: logger.info(...)
1617
# import logging
1718
# logger = logging.getLogger(__name__)
@@ -113,4 +114,25 @@ def reload_config(conf_file = None): # pylint: disable=too-many-statements,too-
113114
dmenu_err(f"{' or '.join([shlex.split(i)[0] for i in clips])} needed for clipboard support")
114115

115116

117+
def safe_deref(entry, field):
118+
"""Safely dereference an entry field, handling cases where referenced fields are None.
119+
120+
When an entry has a field reference (e.g., {REF:U@I:target}) and the target field
121+
is None/empty, pykeepass's deref raises a TypeError. This wrapper catches that
122+
error and returns an empty string.
123+
124+
Args:
125+
entry: KeePass entry object
126+
field: Field name to dereference (e.g., 'username', 'password', 'title')
127+
128+
Returns:
129+
str: Dereferenced field value or empty string if None or error
130+
131+
"""
132+
try:
133+
return entry.deref(field) or ""
134+
except TypeError:
135+
# Handle case where referenced field is None
136+
return ""
137+
116138
# vim: set et ts=4 sw=4 :

keepmenu/totp.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import time
88
from urllib import parse
99

10+
from keepmenu import safe_deref
11+
1012

1113
TOTP_PUBLIC_FIELDS = ('TOTP Settings', 'TimeOtp-Length', 'TimeOtp-Period', 'TimeOtp-Algorithm')
1214
TOTP_SECRET_FIELDS = ('otp',
@@ -133,7 +135,7 @@ def get_otp_url(kp_entry):
133135
"""
134136
otp_url = ""
135137
if hasattr(kp_entry, "otp"):
136-
otp_url = kp_entry.deref("otp") or ""
138+
otp_url = safe_deref(kp_entry, "otp")
137139
else:
138140
otp_url = kp_entry.get_custom_property("otp")
139141
if otp_url:

keepmenu/type.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ def type_entry(entry, db_autotype=None):
127127
libraries.get(library, type_entry_pynput)(entry, tokens)
128128

129129
PLACEHOLDER_AUTOTYPE_TOKENS = {
130-
"{TITLE}" : lambda e: e.deref('title') or "",
131-
"{USERNAME}": lambda e: e.deref('username') or "",
132-
"{URL}" : lambda e: e.deref('url') or "",
133-
"{PASSWORD}": lambda e: e.deref('password') or "",
134-
"{NOTES}" : lambda e: e.deref('notes') or "",
130+
"{TITLE}" : lambda e: keepmenu.safe_deref(e, 'title'),
131+
"{USERNAME}": lambda e: keepmenu.safe_deref(e, 'username'),
132+
"{URL}" : lambda e: keepmenu.safe_deref(e, 'url'),
133+
"{PASSWORD}": lambda e: keepmenu.safe_deref(e, 'password'),
134+
"{NOTES}" : lambda e: keepmenu.safe_deref(e, 'notes'),
135135
"{TOTP}" : lambda e: gen_otp(get_otp_url(e)),
136136
"{TIMEOTP}" : lambda e: gen_otp(get_otp_url(e)),
137137
}

keepmenu/view.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ def view_all_entries(options, kp_entries, dbname):
2020
# Have to number each entry to capture duplicates correctly
2121
kps = str("\n").join([kp_entry_pattern.format(j,
2222
os.path.join("/".join(i.path[:-1]),
23-
i.deref('title') or ""),
24-
i.deref('username') or "",
25-
i.deref('url') or "",
23+
keepmenu.safe_deref(i, 'title')),
24+
keepmenu.safe_deref(i, 'username'),
25+
keepmenu.safe_deref(i, 'url'),
2626
na=num_align)
2727
for j, i in enumerate(kp_entries)])
2828
if options:
@@ -50,13 +50,13 @@ def view_entry(kp_entry):
5050
Returns: dmenu selection
5151
5252
"""
53-
fields = [os.path.join("/".join(kp_entry.path[:-1]), kp_entry.deref('title') or "")
53+
fields = [os.path.join("/".join(kp_entry.path[:-1]), keepmenu.safe_deref(kp_entry, 'title'))
5454
or "Title: None",
55-
kp_entry.deref('username') or "Username: None",
56-
'**********' if kp_entry.deref('password') else "Password: None",
55+
keepmenu.safe_deref(kp_entry, 'username') or "Username: None",
56+
'**********' if keepmenu.safe_deref(kp_entry, 'password') else "Password: None",
5757
"TOTP: ******" if get_otp_url(kp_entry) else "TOTP: None",
58-
kp_entry.deref('url') or "URL: None",
59-
"Notes: <Enter to view>" if kp_entry.deref('notes') else "Notes: None",
58+
keepmenu.safe_deref(kp_entry, 'url') or "URL: None",
59+
"Notes: <Enter to view>" if keepmenu.safe_deref(kp_entry, 'notes') else "Notes: None",
6060
str(f"Expire time: {kp_entry.expiry_time}")
6161
if kp_entry.expires is True else "Expiry date: None"]
6262

@@ -74,11 +74,11 @@ def view_entry(kp_entry):
7474

7575
sel = dmenu_select(len(fields), inp="\n".join(fields))
7676
if sel == "Notes: <Enter to view>":
77-
sel = view_notes(kp_entry.deref('notes') or "")
77+
sel = view_notes(keepmenu.safe_deref(kp_entry, 'notes'))
7878
elif sel == "Notes: None":
7979
sel = ""
8080
elif sel == '**********':
81-
sel = kp_entry.deref('password') or ""
81+
sel = keepmenu.safe_deref(kp_entry, 'password')
8282
elif sel == "TOTP: ******":
8383
sel = gen_otp(get_otp_url(kp_entry))
8484
elif sel == fields[4] and not keepmenu.CONF.getboolean("database", "type_url", fallback=False):

0 commit comments

Comments
 (0)