Skip to content

Commit 23be2d2

Browse files
committed
Add validation/prevent --set-owner, --set-owner-short, and --set-ham from accepting empty or whitespace-only names. This is in relation to meshtastic#6867 firmware feature request meshtastic/firmware#6867
1 parent 622a435 commit 23be2d2

File tree

4 files changed

+195
-2
lines changed

4 files changed

+195
-2
lines changed

meshtastic/__main__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,18 @@ def onConnected(interface):
342342
if args.set_owner or args.set_owner_short:
343343
closeNow = True
344344
waitForAckNak = True
345+
346+
# Validate owner names before connecting to device
347+
if args.set_owner is not None:
348+
stripped_long_name = args.set_owner.strip()
349+
if not stripped_long_name:
350+
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
351+
352+
if args.set_owner_short is not None:
353+
stripped_short_name = args.set_owner_short.strip()
354+
if not stripped_short_name:
355+
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
356+
345357
if args.set_owner and args.set_owner_short:
346358
print(f"Setting device owner to {args.set_owner} and short name to {args.set_owner_short}")
347359
elif args.set_owner:
@@ -644,11 +656,19 @@ def onConnected(interface):
644656
interface.getNode(args.dest, False, **getNode_kwargs).beginSettingsTransaction()
645657

646658
if "owner" in configuration:
659+
# Validate owner name before setting
660+
owner_name = str(configuration["owner"]).strip()
661+
if not owner_name:
662+
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
647663
print(f"Setting device owner to {configuration['owner']}")
648664
waitForAckNak = True
649665
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(configuration["owner"])
650666

651667
if "owner_short" in configuration:
668+
# Validate owner short name before setting
669+
owner_short_name = str(configuration["owner_short"]).strip()
670+
if not owner_short_name:
671+
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
652672
print(
653673
f"Setting device owner short to {configuration['owner_short']}"
654674
)
@@ -658,6 +678,10 @@ def onConnected(interface):
658678
)
659679

660680
if "ownerShort" in configuration:
681+
# Validate owner short name before setting
682+
owner_short_name = str(configuration["ownerShort"]).strip()
683+
if not owner_short_name:
684+
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
661685
print(
662686
f"Setting device owner short to {configuration['ownerShort']}"
663687
)
@@ -1088,6 +1112,7 @@ def export_config(interface) -> str:
10881112
configObj["location"]["alt"] = alt
10891113

10901114
config = MessageToDict(interface.localNode.localConfig) #checkme - Used as a dictionary here and a string below
1115+
#was used as a string here and a Dictionary above
10911116
if config:
10921117
# Convert inner keys to correct snake/camelCase
10931118
prefs = {}
@@ -1182,6 +1207,22 @@ def common():
11821207
meshtastic.util.support_info()
11831208
meshtastic.util.our_exit("", 0)
11841209

1210+
# Early validation for owner names before attempting device connection
1211+
if hasattr(args, 'set_owner') and args.set_owner is not None:
1212+
stripped_long_name = args.set_owner.strip()
1213+
if not stripped_long_name:
1214+
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
1215+
1216+
if hasattr(args, 'set_owner_short') and args.set_owner_short is not None:
1217+
stripped_short_name = args.set_owner_short.strip()
1218+
if not stripped_short_name:
1219+
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
1220+
1221+
if hasattr(args, 'set_ham') and args.set_ham is not None:
1222+
stripped_ham_name = args.set_ham.strip()
1223+
if not stripped_ham_name:
1224+
meshtastic.util.our_exit("ERROR: Ham ID cannot be empty or contain only whitespace characters")
1225+
11851226
if have_powermon:
11861227
create_power_meter()
11871228

meshtastic/node.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,16 @@ def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None
307307
nChars = 4
308308
if long_name is not None:
309309
long_name = long_name.strip()
310+
# Validate that long_name is not empty or whitespace-only
311+
if not long_name:
312+
our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
310313
p.set_owner.long_name = long_name
311314
p.set_owner.is_licensed = is_licensed
312315
if short_name is not None:
313316
short_name = short_name.strip()
317+
# Validate that short_name is not empty or whitespace-only
318+
if not short_name:
319+
our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
314320
if len(short_name) > nChars:
315321
short_name = short_name[:nChars]
316322
print(f"Maximum is 4 characters, truncated to {short_name}")

meshtastic/tests/test_main.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2713,3 +2713,79 @@ def test_remove_ignored_node():
27132713
main()
27142714

27152715
mocked_node.removeIgnored.assert_called_once_with("!12345678")
2716+
2717+
@pytest.mark.unit
2718+
@pytest.mark.usefixtures("reset_mt_config")
2719+
def test_main_set_owner_short_to_bob(capsys):
2720+
"""Test --set-owner-short bob"""
2721+
sys.argv = ["", "--set-owner-short", "bob"]
2722+
mt_config.args = sys.argv
2723+
2724+
iface = MagicMock(autospec=SerialInterface)
2725+
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
2726+
main()
2727+
out, err = capsys.readouterr()
2728+
assert re.search(r"Connected to radio", out, re.MULTILINE)
2729+
assert re.search(r"Setting device owner short to bob", out, re.MULTILINE)
2730+
assert err == ""
2731+
mo.assert_called()
2732+
2733+
2734+
@pytest.mark.unit
2735+
@pytest.mark.usefixtures("reset_mt_config")
2736+
def test_main_set_owner_whitespace_only(capsys):
2737+
"""Test --set-owner with whitespace-only name"""
2738+
sys.argv = ["", "--set-owner", " "]
2739+
mt_config.args = sys.argv
2740+
2741+
with pytest.raises(SystemExit) as excinfo:
2742+
main()
2743+
2744+
out, err = capsys.readouterr()
2745+
assert "ERROR: Long Name cannot be empty or contain only whitespace characters" in out
2746+
assert excinfo.value.code == 1
2747+
2748+
2749+
@pytest.mark.unit
2750+
@pytest.mark.usefixtures("reset_mt_config")
2751+
def test_main_set_owner_empty_string(capsys):
2752+
"""Test --set-owner with empty string"""
2753+
sys.argv = ["", "--set-owner", ""]
2754+
mt_config.args = sys.argv
2755+
2756+
with pytest.raises(SystemExit) as excinfo:
2757+
main()
2758+
2759+
out, err = capsys.readouterr()
2760+
assert "ERROR: Long Name cannot be empty or contain only whitespace characters" in out
2761+
assert excinfo.value.code == 1
2762+
2763+
2764+
@pytest.mark.unit
2765+
@pytest.mark.usefixtures("reset_mt_config")
2766+
def test_main_set_owner_short_whitespace_only(capsys):
2767+
"""Test --set-owner-short with whitespace-only name"""
2768+
sys.argv = ["", "--set-owner-short", " "]
2769+
mt_config.args = sys.argv
2770+
2771+
with pytest.raises(SystemExit) as excinfo:
2772+
main()
2773+
2774+
out, err = capsys.readouterr()
2775+
assert "ERROR: Short Name cannot be empty or contain only whitespace characters" in out
2776+
assert excinfo.value.code == 1
2777+
2778+
2779+
@pytest.mark.unit
2780+
@pytest.mark.usefixtures("reset_mt_config")
2781+
def test_main_set_owner_short_empty_string(capsys):
2782+
"""Test --set-owner-short with empty string"""
2783+
sys.argv = ["", "--set-owner-short", ""]
2784+
mt_config.args = sys.argv
2785+
2786+
with pytest.raises(SystemExit) as excinfo:
2787+
main()
2788+
2789+
out, err = capsys.readouterr()
2790+
assert "ERROR: Short Name cannot be empty or contain only whitespace characters" in out
2791+
assert excinfo.value.code == 1

meshtastic/tests/test_node.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,8 +1254,7 @@ def test_requestChannels_non_localNode_starting_index(caplog):
12541254
# },
12551255
# 'id': 1692918436,
12561256
# 'hopLimit': 3,
1257-
# 'priority':
1258-
# 'RELIABLE',
1257+
# 'priority': 'RELIABLE',
12591258
# 'raw': 'fake',
12601259
# 'fromId': '!9388f81c',
12611260
# 'toId': '!9388f81c'
@@ -1480,6 +1479,77 @@ def test_remove_ignored(ignored):
14801479
iface.sendData.assert_called_once()
14811480

14821481

1482+
@pytest.mark.unit
1483+
def test_setOwner_whitespace_only_long_name(capsys):
1484+
"""Test setOwner with whitespace-only long name"""
1485+
iface = MagicMock(autospec=MeshInterface)
1486+
anode = Node(iface, 123, noProto=True)
1487+
1488+
with pytest.raises(SystemExit) as excinfo:
1489+
anode.setOwner(long_name=" ")
1490+
1491+
out, err = capsys.readouterr()
1492+
assert "ERROR: Long Name cannot be empty or contain only whitespace characters" in out
1493+
assert excinfo.value.code == 1
1494+
1495+
1496+
@pytest.mark.unit
1497+
def test_setOwner_empty_long_name(capsys):
1498+
"""Test setOwner with empty long name"""
1499+
iface = MagicMock(autospec=MeshInterface)
1500+
anode = Node(iface, 123, noProto=True)
1501+
1502+
with pytest.raises(SystemExit) as excinfo:
1503+
anode.setOwner(long_name="")
1504+
1505+
out, err = capsys.readouterr()
1506+
assert "ERROR: Long Name cannot be empty or contain only whitespace characters" in out
1507+
assert excinfo.value.code == 1
1508+
1509+
1510+
@pytest.mark.unit
1511+
def test_setOwner_whitespace_only_short_name(capsys):
1512+
"""Test setOwner with whitespace-only short name"""
1513+
iface = MagicMock(autospec=MeshInterface)
1514+
anode = Node(iface, 123, noProto=True)
1515+
1516+
with pytest.raises(SystemExit) as excinfo:
1517+
anode.setOwner(short_name=" ")
1518+
1519+
out, err = capsys.readouterr()
1520+
assert "ERROR: Short Name cannot be empty or contain only whitespace characters" in out
1521+
assert excinfo.value.code == 1
1522+
1523+
1524+
@pytest.mark.unit
1525+
def test_setOwner_empty_short_name(capsys):
1526+
"""Test setOwner with empty short name"""
1527+
iface = MagicMock(autospec=MeshInterface)
1528+
anode = Node(iface, 123, noProto=True)
1529+
1530+
with pytest.raises(SystemExit) as excinfo:
1531+
anode.setOwner(short_name="")
1532+
1533+
out, err = capsys.readouterr()
1534+
assert "ERROR: Short Name cannot be empty or contain only whitespace characters" in out
1535+
assert excinfo.value.code == 1
1536+
1537+
1538+
@pytest.mark.unit
1539+
def test_setOwner_valid_names(caplog):
1540+
"""Test setOwner with valid names"""
1541+
iface = MagicMock(autospec=MeshInterface)
1542+
anode = Node(iface, 123, noProto=True)
1543+
1544+
with caplog.at_level(logging.DEBUG):
1545+
anode.setOwner(long_name="ValidName", short_name="VN")
1546+
1547+
# Should not raise any exceptions and should call _sendAdmin
1548+
iface._sendAdmin.assert_called_once()
1549+
assert re.search(r'p.set_owner.long_name:ValidName:', caplog.text, re.MULTILINE)
1550+
assert re.search(r'p.set_owner.short_name:VN:', caplog.text, re.MULTILINE)
1551+
1552+
14831553
# TODO
14841554
# @pytest.mark.unitslow
14851555
# def test_waitForConfig():

0 commit comments

Comments
 (0)