Skip to content

Commit 9035c6a

Browse files
authored
Merge pull request python-zeroconf#77 from stephenrauch/fix-instance-name-with-dot
Allow dots in service instance name
2 parents 136dce9 + e46af83 commit 9035c6a

File tree

3 files changed

+50
-18
lines changed

3 files changed

+50
-18
lines changed

README.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,14 @@ Changelog
129129
* Many exceptions will now log a warning the first time they are seen
130130
* Catch and log sendto() errors
131131
* Fix/Implement duplicate name change
132+
* Fix overly strict name validation introduced in 0.17.6
132133
* Greatly improve handling of oversized packets including:
133134

134135
- Implement name compression per RFC1035
135136
- Limit size of generated packets to 9000 bytes as per RFC6762
136137
- Better handle over sized incoming packets
137138

138-
* Increased test coverage to 94%
139+
* Increased test coverage to 95%
139140

140141
0.17.6
141142
------

test_zeroconf.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -422,24 +422,43 @@ def test_bad_service_names(self):
422422
'_22._udp.local.',
423423
'_2-2._tcp.local.',
424424
'_1234567890-abcde._udp.local.',
425-
'._x._udp.local.',
425+
'\x00._x._udp.local.',
426426
)
427427
for name in bad_names_to_try:
428428
self.assertRaises(
429429
r.BadTypeInNameException,
430430
self.browser.get_service_info, name, 'x.' + name)
431431

432-
def test_bad_sub_types(self):
433-
bad_names_to_try = (
434-
'_sub._http._tcp.local.',
432+
def test_good_instance_names(self):
433+
good_names_to_try = (
434+
'.._x._tcp.local.',
435435
'x.sub._http._tcp.local.',
436+
'6d86f882b90facee9170ad3439d72a4d6ee9f511._zget._http._tcp.local.'
437+
)
438+
for name in good_names_to_try:
439+
r.service_type_name(name)
440+
441+
def test_bad_types(self):
442+
bad_names_to_try = (
443+
'._x._tcp.local.',
436444
'a' * 64 + '._sub._http._tcp.local.',
437445
'a' * 62 + u'â._sub._http._tcp.local.',
438446
)
439447
for name in bad_names_to_try:
440448
self.assertRaises(
441449
r.BadTypeInNameException, r.service_type_name, name)
442450

451+
def test_bad_sub_types(self):
452+
bad_names_to_try = (
453+
'_sub._http._tcp.local.',
454+
'._sub._http._tcp.local.',
455+
'\x7f._sub._http._tcp.local.',
456+
'\x1f._sub._http._tcp.local.',
457+
)
458+
for name in bad_names_to_try:
459+
self.assertRaises(
460+
r.BadTypeInNameException, r.service_type_name, name)
461+
443462
def test_good_service_names(self):
444463
good_names_to_try = (
445464
'_x._tcp.local.',

zeroconf.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def emit(self, record):
158158

159159
_HAS_A_TO_Z = re.compile(r'[A-Za-z]')
160160
_HAS_ONLY_A_TO_Z_NUM_HYPHEN = re.compile(r'^[A-Za-z0-9\-]+$')
161+
_HAS_ASCII_CONTROL_CHARS = re.compile(r'[\x00-\x1f\x7f]')
161162

162163

163164
@enum.unique
@@ -212,6 +213,14 @@ def service_type_name(type_):
212213
213214
The instance name <Instance> and sub type <sub> may be up to 63 bytes.
214215
216+
The portion of the Service Instance Name is a user-
217+
friendly name consisting of arbitrary Net-Unicode text [RFC5198]. It
218+
MUST NOT contain ASCII control characters (byte values 0x00-0x1F and
219+
0x7F) [RFC20] but otherwise is allowed to contain any characters,
220+
without restriction, including spaces, uppercase, lowercase,
221+
punctuation -- including dots -- accented characters, non-Roman text,
222+
and anything else that may be represented using Net-Unicode.
223+
215224
:param type_: Type, SubType or service name to validate
216225
:return: fully qualified service name (eg: _http._tcp.local.)
217226
"""
@@ -220,15 +229,15 @@ def service_type_name(type_):
220229
"Type '%s' must end with '._tcp.local.' or '._udp.local.'" %
221230
type_)
222231

223-
if type_.startswith('.'):
224-
raise BadTypeInNameException(
225-
"Type '%s' must not start with '.'" % type_)
226-
227232
remaining = type_[:-len('._tcp.local.')].split('.')
228233
name = remaining.pop()
229234
if not name:
230235
raise BadTypeInNameException("No Service name found")
231236

237+
if len(remaining) == 1 and len(remaining[0]) == 0:
238+
raise BadTypeInNameException(
239+
"Type '%s' must not start with '.'" % type_)
240+
232241
if name[0] != '_':
233242
raise BadTypeInNameException(
234243
"Service name (%s) must start with '_'" % name)
@@ -260,20 +269,23 @@ def service_type_name(type_):
260269

261270
if remaining and remaining[-1] == '_sub':
262271
remaining.pop()
263-
if len(remaining) == 0:
272+
if len(remaining) == 0 or len(remaining[0]) == 0:
264273
raise BadTypeInNameException(
265274
"_sub requires a subtype name")
266275

267276
if len(remaining) > 1:
268-
raise BadTypeInNameException(
269-
"Unexpected characters '%s.' in '%s'" % (
270-
'.'.join(remaining[1:]), type_))
277+
remaining = ['.'.join(remaining)]
271278

272279
if remaining:
273280
length = len(remaining[0].encode('utf-8'))
274281
if length > 63:
275282
raise BadTypeInNameException("Too long: '%s'" % remaining[0])
276283

284+
if _HAS_ASCII_CONTROL_CHARS.search(remaining[0]):
285+
raise BadTypeInNameException(
286+
"Ascii control character 0x00-0x1F and 0x7F illegal in '%s'" %
287+
remaining[0])
288+
277289
return '_' + name + type_[-len('._tcp.local.'):]
278290

279291

@@ -1846,13 +1858,13 @@ def unregister_all_services(self):
18461858
def check_service(self, info, allow_name_change):
18471859
"""Checks the network for a unique service name, modifying the
18481860
ServiceInfo passed in if it is not unique."""
1849-
service_name = service_type_name(info.name)
18501861

1851-
# This asserts breaks on the current subtype based tests
1862+
# This is kind of funky because of the subtype based tests
18521863
# need to make subtypes a first class citizen
1853-
# assert service_name == info.type
1854-
# instead try:
1855-
assert service_name == '.'.join(info.type.split('.')[-4:])
1864+
service_name = service_type_name(info.name)
1865+
if not info.type.endswith(service_name):
1866+
raise BadTypeInNameException
1867+
18561868
instance_name = info.name[:-len(service_name) - 1]
18571869
next_instance_number = 2
18581870

0 commit comments

Comments
 (0)