Skip to content

Commit 5df8a57

Browse files
authored
feat: expose flag to disable strict name checking in service registration (#1215)
1 parent aff625d commit 5df8a57

5 files changed

Lines changed: 79 additions & 8 deletions

File tree

src/zeroconf/_core.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ def register_service(
620620
ttl: Optional[int] = None,
621621
allow_name_change: bool = False,
622622
cooperating_responders: bool = False,
623+
strict: bool = True,
623624
) -> None:
624625
"""Registers service information to the network with a default TTL.
625626
Zeroconf will then respond to requests for information for that
@@ -635,7 +636,7 @@ def register_service(
635636
assert self.loop is not None
636637
run_coro_with_timeout(
637638
await_awaitable(
638-
self.async_register_service(info, ttl, allow_name_change, cooperating_responders)
639+
self.async_register_service(info, ttl, allow_name_change, cooperating_responders, strict)
639640
),
640641
self.loop,
641642
_REGISTER_TIME * _REGISTER_BROADCASTS,
@@ -647,6 +648,7 @@ async def async_register_service(
647648
ttl: Optional[int] = None,
648649
allow_name_change: bool = False,
649650
cooperating_responders: bool = False,
651+
strict: bool = True,
650652
) -> Awaitable:
651653
"""Registers service information to the network with a default TTL.
652654
Zeroconf will then respond to requests for information for that
@@ -662,7 +664,7 @@ async def async_register_service(
662664

663665
info.set_server_if_missing()
664666
await self.async_wait_for_start()
665-
await self.async_check_service(info, allow_name_change, cooperating_responders)
667+
await self.async_check_service(info, allow_name_change, cooperating_responders, strict)
666668
self.registry.async_add(info)
667669
return asyncio.ensure_future(self._async_broadcast_service(info, _REGISTER_TIME, None))
668670

@@ -810,11 +812,15 @@ def unregister_all_services(self) -> None:
810812
)
811813

812814
async def async_check_service(
813-
self, info: ServiceInfo, allow_name_change: bool, cooperating_responders: bool = False
815+
self,
816+
info: ServiceInfo,
817+
allow_name_change: bool,
818+
cooperating_responders: bool = False,
819+
strict: bool = True,
814820
) -> None:
815821
"""Checks the network for a unique service name, modifying the
816822
ServiceInfo passed in if it is not unique."""
817-
instance_name = instance_name_from_service_info(info)
823+
instance_name = instance_name_from_service_info(info, strict=strict)
818824
if cooperating_responders:
819825
return
820826
next_instance_number = 2
@@ -829,7 +835,7 @@ async def async_check_service(
829835
# change the name and look for a conflict
830836
info.name = f'{instance_name}-{next_instance_number}.{info.type}'
831837
next_instance_number += 1
832-
service_type_name(info.name)
838+
service_type_name(info.name, strict=strict)
833839
next_time = now
834840
i = 0
835841

src/zeroconf/_services/info.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@
7676
from .._core import Zeroconf
7777

7878

79-
def instance_name_from_service_info(info: "ServiceInfo") -> str:
79+
def instance_name_from_service_info(info: "ServiceInfo", strict: bool = True) -> str:
8080
"""Calculate the instance name from the ServiceInfo."""
8181
# This is kind of funky because of the subtype based tests
8282
# need to make subtypes a first class citizen
83-
service_name = service_type_name(info.name)
83+
service_name = service_type_name(info.name, strict=strict)
8484
if not info.type.endswith(service_name):
8585
raise BadTypeInNameException
8686
return info.name[: -len(service_name) - 1]

src/zeroconf/asyncio.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ async def async_register_service(
180180
ttl: Optional[int] = None,
181181
allow_name_change: bool = False,
182182
cooperating_responders: bool = False,
183+
strict: bool = True,
183184
) -> Awaitable:
184185
"""Registers service information to the network with a default TTL.
185186
Zeroconf will then respond to requests for information for that
@@ -192,7 +193,7 @@ async def async_register_service(
192193
and therefore can be awaited if necessary.
193194
"""
194195
return await self.zeroconf.async_register_service(
195-
info, ttl, allow_name_change, cooperating_responders
196+
info, ttl, allow_name_change, cooperating_responders, strict
196197
)
197198

198199
async def async_unregister_all_services(self) -> None:

tests/test_asyncio.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,41 @@ async def test_async_service_registration_name_does_not_match_type() -> None:
456456
await aiozc.async_close()
457457

458458

459+
@pytest.mark.asyncio
460+
async def test_async_service_registration_name_strict_check() -> None:
461+
"""Test registering services throws when the name does not comply."""
462+
zc = Zeroconf(interfaces=['127.0.0.1'])
463+
aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
464+
type_ = "_ibisip_http._tcp.local."
465+
name = "CustomerInformationService-F4D4895E9EEB"
466+
registration_name = f"{name}.{type_}"
467+
468+
desc = {'path': '/~paulsm/'}
469+
info = ServiceInfo(
470+
type_,
471+
registration_name,
472+
80,
473+
0,
474+
0,
475+
desc,
476+
"ash-2.local.",
477+
addresses=[socket.inet_aton("10.0.1.2")],
478+
)
479+
with pytest.raises(BadTypeInNameException):
480+
await zc.async_check_service(info, allow_name_change=False)
481+
482+
with pytest.raises(BadTypeInNameException):
483+
task = await aiozc.async_register_service(info)
484+
await task
485+
486+
await zc.async_check_service(info, allow_name_change=False, strict=False)
487+
task = await aiozc.async_register_service(info, strict=False)
488+
await task
489+
490+
await aiozc.async_unregister_service(info)
491+
await aiozc.async_close()
492+
493+
459494
@pytest.mark.asyncio
460495
async def test_async_tasks() -> None:
461496
"""Test awaiting broadcast tasks"""

tests/utils/test_name.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33

44
"""Unit tests for zeroconf._utils.name."""
5+
import socket
56

67
import pytest
78

89
from zeroconf import BadTypeInNameException
10+
from zeroconf._services.info import ServiceInfo, instance_name_from_service_info
911
from zeroconf._utils import name as nameutils
1012

1113

@@ -25,6 +27,33 @@ def test_service_type_name_overlong_full_name():
2527
nameutils.service_type_name(f"{long_name}._tivo-videostream._tcp.local.", strict=False)
2628

2729

30+
@pytest.mark.parametrize(
31+
"instance_name, service_type",
32+
(
33+
("CustomerInformationService-F4D4885E9EEB", "_ibisip_http._tcp.local."),
34+
("DeviceManagementService_F4D4885E9EEB", "_ibisip_http._tcp.local."),
35+
),
36+
)
37+
def test_service_type_name_non_strict_compliant_names(instance_name, service_type):
38+
"""Test service_type_name for valid names, but not strict-compliant."""
39+
desc = {'path': '/~paulsm/'}
40+
service_name = f'{instance_name}.{service_type}'
41+
service_server = 'ash-1.local.'
42+
service_address = socket.inet_aton("10.0.1.2")
43+
info = ServiceInfo(
44+
service_type, service_name, 22, 0, 0, desc, service_server, addresses=[service_address]
45+
)
46+
assert info.get_name() == instance_name
47+
48+
with pytest.raises(BadTypeInNameException):
49+
nameutils.service_type_name(service_name)
50+
with pytest.raises(BadTypeInNameException):
51+
instance_name_from_service_info(info)
52+
53+
nameutils.service_type_name(service_name, strict=False)
54+
assert instance_name_from_service_info(info, strict=False) == instance_name
55+
56+
2857
def test_possible_types():
2958
"""Test possible types from name."""
3059
assert nameutils.possible_types('.') == set()

0 commit comments

Comments
 (0)