From 3fcebec354c5ee134c44fd6a5eb21c4e309559ae Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 23 Aug 2020 12:32:38 +0100 Subject: [PATCH] Monkeypatch zeroconf to remove the 15 character service name limit. --- .../devices/smart_device_app/driver.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index eb99b7b800..e66879ae24 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -384,6 +384,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.debug_start_time = time.time() self.debug_time = time.time() self.is_connected = False + # Hack to work around the newly-enforced 15 character service name limit. + # "monkeypatch" zeroconf with a function without the check + import zeroconf + zeroconf.service_type_name = service_type_name # Don't call this method from the GUI unless you are sure that there is no # network traffic in progress. Otherwise the gui might hang waiting for the @@ -2032,3 +2036,111 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): def is_running(self): return getattr(self, 'listen_socket', None) is not None + +# Function to monkeypatch zeroconf to remove the 15 character name length restriction. +# Copied from https://github.com/jstasiak/python-zeroconf version 0.28.1 + +from zeroconf import (BadTypeInNameException, _HAS_A_TO_Z, + _HAS_ONLY_A_TO_Z_NUM_HYPHEN_UNDERSCORE, + _HAS_ASCII_CONTROL_CHARS, + _HAS_ONLY_A_TO_Z_NUM_HYPHEN) + +def service_type_name(type_: str, *, allow_underscores: bool = False) -> str: + """ + Validate a fully qualified service name, instance or subtype. [rfc6763] + + Returns fully qualified service name. + + Domain names used by mDNS-SD take the following forms: + + . <_tcp|_udp> . local. + . . <_tcp|_udp> . local. + ._sub . . <_tcp|_udp> . local. + + 1) must end with 'local.' + + This is true because we are implementing mDNS and since the 'm' means + multi-cast, the 'local.' domain is mandatory. + + 2) local is preceded with either '_udp.' or '_tcp.' + + 3) service name precedes <_tcp|_udp> + + The rules for Service Names [RFC6335] state that they may be no more + than fifteen characters long (not counting the mandatory underscore), + consisting of only letters, digits, and hyphens, must begin and end + with a letter or digit, must not contain consecutive hyphens, and + must contain at least one letter. + + The instance name and sub type may be up to 63 bytes. + + The portion of the Service Instance Name is a user- + friendly name consisting of arbitrary Net-Unicode text [RFC5198]. It + MUST NOT contain ASCII control characters (byte values 0x00-0x1F and + 0x7F) [RFC20] but otherwise is allowed to contain any characters, + without restriction, including spaces, uppercase, lowercase, + punctuation -- including dots -- accented characters, non-Roman text, + and anything else that may be represented using Net-Unicode. + + :param type_: Type, SubType or service name to validate + :return: fully qualified service name (eg: _http._tcp.local.) + """ + if not (type_.endswith('._tcp.local.') or type_.endswith('._udp.local.')): + raise BadTypeInNameException("Type '%s' must end with '._tcp.local.' or '._udp.local.'" % type_) + + remaining = type_[: -len('._tcp.local.')].split('.') + name = remaining.pop() + if not name: + raise BadTypeInNameException("No Service name found") + + if len(remaining) == 1 and len(remaining[0]) == 0: + raise BadTypeInNameException("Type '%s' must not start with '.'" % type_) + + if name[0] != '_': + raise BadTypeInNameException("Service name (%s) must start with '_'" % name) + + # remove leading underscore + name = name[1:] + +# if len(name) > 15: +# raise BadTypeInNameException("Service name (%s) must be <= 15 bytes" % name) + + if '--' in name: + raise BadTypeInNameException("Service name (%s) must not contain '--'" % name) + + if '-' in (name[0], name[-1]): + raise BadTypeInNameException("Service name (%s) may not start or end with '-'" % name) + + if not _HAS_A_TO_Z.search(name): + raise BadTypeInNameException("Service name (%s) must contain at least one letter (eg: 'A-Z')" % name) + + allowed_characters_re = ( + _HAS_ONLY_A_TO_Z_NUM_HYPHEN_UNDERSCORE if allow_underscores else _HAS_ONLY_A_TO_Z_NUM_HYPHEN + ) + + if not allowed_characters_re.search(name): + raise BadTypeInNameException( + "Service name (%s) must contain only these characters: " + "A-Z, a-z, 0-9, hyphen ('-')%s" % (name, ", underscore ('_')" if allow_underscores else "") + ) + + if remaining and remaining[-1] == '_sub': + remaining.pop() + if len(remaining) == 0 or len(remaining[0]) == 0: + raise BadTypeInNameException("_sub requires a subtype name") + + if len(remaining) > 1: + remaining = ['.'.join(remaining)] + + if remaining: + length = len(remaining[0].encode('utf-8')) + if length > 63: + raise BadTypeInNameException("Too long: '%s'" % remaining[0]) + + if _HAS_ASCII_CONTROL_CHARS.search(remaining[0]): + raise BadTypeInNameException( + "Ascii control character 0x00-0x1F and 0x7F illegal in '%s'" % remaining[0] + ) + + return '_' + name + type_[-len('._tcp.local.') :] +