Skip to content

fix: accept uppercase .local. trailer in service_type_name#1747

Merged
bdraco merged 2 commits into
python-zeroconf:masterfrom
bluetoothbot:koan/fix-issue-1746
May 20, 2026
Merged

fix: accept uppercase .local. trailer in service_type_name#1747
bdraco merged 2 commits into
python-zeroconf:masterfrom
bluetoothbot:koan/fix-issue-1746

Conversation

@bluetoothbot
Copy link
Copy Markdown
Contributor

@bluetoothbot bluetoothbot commented May 20, 2026

Summary

DNS name comparisons are case-insensitive (RFC 1035 §2.3.3, RFC 6762 §16), but service_type_name rejected inputs whose .local. / ._tcp.local. / ._udp.local. trailer used any non-lowercase letter (e.g. Localhost.LOCAL.). This change normalises the input case for the trailer match only; the returned trailer still preserves the caller's original casing so existing type_.endswith(service_type_name(name)) checks in ServiceInfo keep working.

Fixes #1746

Changes

  • _utils/name.py: compare type_.lower() against the trailer constants in both the strict (._tcp.local. / ._udp.local.) and non-strict (.local.) branches.
  • tests/utils/test_name.py: cover mixed-case strict trailers and the issue's reported Localhost.LOCAL. non-strict case.

Test plan

  • poetry run pytest tests/utils/test_name.py -v — new mixed-case cases pass.
  • Full pure-Python suite (SKIP_CYTHON=1 poetry run pytest --timeout=60) — 372 passed, 2 skipped, no regressions.
  • poetry run ruff check / ruff format --check on touched files.

Generated by Kōan /fix


Quality Report

Changes: 2 files changed, 23 insertions(+), 2 deletions(-)

Code scan: clean

Tests: passed (4 PASSED)

Branch hygiene: clean

Generated by Kōan post-mission quality pipeline

DNS name comparisons are case-insensitive (RFC 1035 §2.3.3,
RFC 6762 §16), so service_type_name should accept names such as
"Localhost.LOCAL." or "_http._TCP.local." instead of rejecting
them with BadTypeInNameException.

Lowercase the input only for the endswith checks; the returned
trailer still preserves the caller's original casing, so
ServiceInfo's existing "type_.endswith(service_type_name(name))"
check keeps working when both sides share the same case.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 20, 2026

Merging this PR will not alter performance

✅ 14 untouched benchmarks


Comparing bluetoothbot:koan/fix-issue-1746 (0b2c539) with master (0e201f7)

Open in CodSpeed

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.76%. Comparing base (0e201f7) to head (0b2c539).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1747   +/-   ##
=======================================
  Coverage   99.76%   99.76%           
=======================================
  Files          33       33           
  Lines        3455     3456    +1     
  Branches      478      478           
=======================================
+ Hits         3447     3448    +1     
  Misses          5        5           
  Partials        3        3           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates service_type_name() to treat the .local. / ._tcp.local. / ._udp.local. trailer as case-insensitive (per DNS rules) while preserving the caller’s original casing in the returned trailer, fixing mixed-case .LOCAL. inputs.

Changes:

  • Normalize the input to type_.lower() for trailer matching in both strict and non-strict parsing branches.
  • Add tests covering mixed-case strict trailers and the reported Localhost.LOCAL. non-strict case.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/zeroconf/_utils/name.py Makes trailer detection case-insensitive by comparing against constants using a lowercased view of the input.
tests/utils/test_name.py Adds regression tests for mixed-case .LOCAL. trailer handling in strict and non-strict modes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/utils/test_name.py
("_http._tcp.LOCAL.", "_http._tcp.LOCAL."),
("_http._TCP.local.", "_http._TCP.local."),
("_HTTP._tcp.local.", "_HTTP._tcp.local."),
("Instance._http._tcp.LOCAL.", "_http._tcp.LOCAL."),
@bdraco
Copy link
Copy Markdown
Member

bdraco commented May 20, 2026

@bluetoothbot rr

@bluetoothbot
Copy link
Copy Markdown
Contributor Author

PR Review — fix: accept uppercase .local. trailer in service_type_name

Tight, well-scoped fix. Lowering type_ only for the endswith comparison while keeping type_ itself for slicing/returning preserves the caller's original casing — that matters because downstream callers in ServiceInfo do type_.endswith(service_type_name(name)), and lowercasing the returned trailer would silently break them. The PR description calls this out, and the test for Localhost.LOCAL. -> LOCAL. pins that contract. Slice math is unaffected by case since _TCP_PROTOCOL_LOCAL_TRAILER and _NONTCP_PROTOCOL_LOCAL_TRAILER are the same length, so the shared len(_TCP_PROTOCOL_LOCAL_TRAILER) slice in the strict branch remains correct for _udp inputs. Only nit: parametrized strict coverage is _tcp-only — adding one _udp mixed-case row would harden the second tuple member against future refactors. Not a blocker; merge-ready.


🟢 Suggestions

1. Add a `_udp` mixed-case case to cover the second trailer constant (`tests/utils/test_name.py`, L64-71)

The implementation matches against the tuple (_TCP_PROTOCOL_LOCAL_TRAILER, _NONTCP_PROTOCOL_LOCAL_TRAILER) in a single endswith call, but every parametrized strict case here is _tcp. The _udp branch passes today only because the lowering is identical for both — but if someone later refactors that endswith into two separate branches (e.g. to error-message which trailer was matched), a _udp regression won't be caught. Adding one mixed-case _udp case (e.g. ('_printer._UDP.local.', '_printer._UDP.local.') or '_printer._udp.LOCAL.') pins both halves of the tuple. This is the same point Copilot raised inline at line 70.

("_http._tcp.LOCAL.", "_http._tcp.LOCAL."),
("_http._TCP.local.", "_http._TCP.local."),
("_HTTP._tcp.local.", "_HTTP._tcp.local."),
("Instance._http._tcp.LOCAL.", "_http._tcp.LOCAL."),

Checklist

  • Trailer constants are lowercase, so lower() comparison is sound
  • Returned trailer preserves caller casing (backward compat with endswith(service_type_name(...)) callers)
  • Slice arithmetic correct for both _tcp and _udp after case normalization
  • RFC citation present in code comment and commit (RFC 1035 §2.3.3 / RFC 6762 §16)
  • Edge case coverage: mixed-case _udp trailer — suggestion #1
  • No untested branches introduced (codecov reports 100% on changed lines)

Summary

Tight, well-scoped fix. Lowering type_ only for the endswith comparison while keeping type_ itself for slicing/returning preserves the caller's original casing — that matters because downstream callers in ServiceInfo do type_.endswith(service_type_name(name)), and lowercasing the returned trailer would silently break them. The PR description calls this out, and the test for Localhost.LOCAL. -> LOCAL. pins that contract. Slice math is unaffected by case since _TCP_PROTOCOL_LOCAL_TRAILER and _NONTCP_PROTOCOL_LOCAL_TRAILER are the same length, so the shared len(_TCP_PROTOCOL_LOCAL_TRAILER) slice in the strict branch remains correct for _udp inputs. Only nit: parametrized strict coverage is _tcp-only — adding one _udp mixed-case row would harden the second tuple member against future refactors. Not a blocker; merge-ready.


Automated review by Kōan8b1fc34

@bluetoothbot
Copy link
Copy Markdown
Contributor Author

Rebase with requested adjustments

Branch koan/fix-issue-1746 was rebased onto master and review feedback was applied.

Changes applied

  • Added _udp mixed-case cases per Copilot review.
  • Added three _udp parametrized cases to test_service_type_name_uppercase_trailer: uppercase .LOCAL. trailer, uppercase _UDP proto label, and instance-prefixed uppercase trailer. Covers _NONTCP_PROTOCOL_LOCAL_TRAILER path per Copilot reviewer request at tests/utils/test_name.py:70.

Stats

2 files changed, 26 insertions(+), 2 deletions(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No recent master commit touches service_type_name case-insensitivity; trailer comparison still uses )
  • Rebased koan/fix-issue-1746 onto upstream/master
  • Applied review feedback
  • Pre-push CI check: previous run passed
  • Force-pushed koan/fix-issue-1746 to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

@bdraco bdraco marked this pull request as ready for review May 20, 2026 12:15
@bdraco bdraco merged commit 37edde2 into python-zeroconf:master May 20, 2026
36 checks passed
@bluetoothbot bluetoothbot deleted the koan/fix-issue-1746 branch May 20, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

uppercase .LOCAL should be allowed

3 participants