Merge ~bjornt/maas:bug-1773698-2.4 into maas:2.4

Proposed by Björn Tillenius
Status: Merged
Approved by: Björn Tillenius
Approved revision: 0b9333baded7e9749bdc1ca563a469659cbff3e6
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~bjornt/maas:bug-1773698-2.4
Merge into: maas:2.4
Diff against target: 214 lines (+62/-42)
2 files modified
src/provisioningserver/utils/avahi.py (+11/-3)
src/provisioningserver/utils/tests/test_avahi.py (+51/-39)
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
MAAS Lander unittests Pending
Review via email: mp+348266@code.launchpad.net

Commit message

Backport d3a94c7328dbac5d5b3f617b03b8efc760e904a1.

LP: #1773698 - observer-mdns fails on unicode issue

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/provisioningserver/utils/avahi.py b/src/provisioningserver/utils/avahi.py
2index 69af5c1..743f60a 100644
3--- a/src/provisioningserver/utils/avahi.py
4+++ b/src/provisioningserver/utils/avahi.py
5@@ -88,9 +88,17 @@ def parse_avahi_event(line: str) -> dict:
6 # consistency with the event names used in the avahi code).
7 data = {}
8 # Limit to 9 fields here in case a ';' appears in the TXT record unescaped.
9- fields = line.rstrip().split(';', 9)
10+ fields = line.rstrip().split(b';', 9)
11 if len(fields) < 6:
12 return None
13+ for index, field in enumerate(fields):
14+ # the 9th field is TXT, which can contain anything, including
15+ # binary characters. There's a bug about this, that maybe
16+ # avahi-browser should escape those binary characters, which
17+ # would allow us to treat everything as utf-8:
18+ # https://github.com/lathiat/avahi/issues/169
19+ if index != 9:
20+ fields[index] = field.decode('utf-8')
21 event_type = fields[0]
22 # The type of the event is indicated in the first character from
23 # avahi-browse. The following fields (no matter the event type) will
24@@ -239,7 +247,7 @@ def _reader_from_avahi():
25 stdout=subprocess.PIPE)
26 try:
27 # Avahi says "All strings used in DNS-SD are UTF-8 strings".
28- yield io.TextIOWrapper(avahi_browse.stdout, encoding='utf-8')
29+ yield avahi_browse.stdout
30 finally:
31 # SIGINT or SIGTERM (see ActionScript.setup) has been received,
32 # avahi-browse may have crashed or been terminated, or there may have
33@@ -266,7 +274,7 @@ def _reader_from_stdin(stdin):
34 def _reader_from_file(filename):
35 """Reader from `filename`."""
36 # Avahi says "All strings used in DNS-SD are UTF-8 strings".
37- with open(filename, "r", encoding="utf-8") as infile:
38+ with open(filename, "rb") as infile:
39 yield infile
40
41
42diff --git a/src/provisioningserver/utils/tests/test_avahi.py b/src/provisioningserver/utils/tests/test_avahi.py
43index 96aca7f..d876ea7 100644
44--- a/src/provisioningserver/utils/tests/test_avahi.py
45+++ b/src/provisioningserver/utils/tests/test_avahi.py
46@@ -61,9 +61,9 @@ class TestParseAvahiEvent(MAASTestCase):
47
48 def test__parses_browser_new_event(self):
49 input = (
50- "+;eth0;IPv4"
51- ";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
52- "_http._tcp;local")
53+ b"+;eth0;IPv4"
54+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
55+ b"_http._tcp;local")
56 event = parse_avahi_event(input)
57 self.assertEquals(
58 event,
59@@ -79,9 +79,9 @@ class TestParseAvahiEvent(MAASTestCase):
60
61 def test__parses_browser_removed_event(self):
62 input = (
63- "-;eth0;IPv4"
64- ";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
65- "_http._tcp;local")
66+ b"-;eth0;IPv4"
67+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
68+ b"_http._tcp;local")
69 event = parse_avahi_event(input)
70 self.assertEquals(
71 event,
72@@ -97,13 +97,13 @@ class TestParseAvahiEvent(MAASTestCase):
73
74 def test__parses_resolver_found_event(self):
75 input = (
76- "=;eth0;IPv4"
77- ";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
78- "_http._tcp;local;"
79- "printer.local;"
80- "192.168.0.222;"
81- "80;"
82- '"priority=50" "rp=RAW"')
83+ b"=;eth0;IPv4"
84+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
85+ b"_http._tcp;local;"
86+ b"printer.local;"
87+ b"192.168.0.222;"
88+ b"80;"
89+ b'"priority=50" "rp=RAW"')
90 event = parse_avahi_event(input)
91 self.assertEquals(
92 event,
93@@ -118,12 +118,25 @@ class TestParseAvahiEvent(MAASTestCase):
94 'fqdn': 'printer.local',
95 'hostname': 'printer',
96 'port': '80',
97- 'txt': '"priority=50" "rp=RAW"'
98+ 'txt': b'"priority=50" "rp=RAW"'
99 }
100 )
101
102+ def test__parses_txt_binary(self):
103+ input = (
104+ b"=;eth0;IPv4"
105+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
106+ b"_http._tcp;local;"
107+ b"printer.local;"
108+ b"192.168.0.222;"
109+ b"80;"
110+ b'"BluetoothAddress=\xc8i\xcdB\xe2\x09"')
111+ event = parse_avahi_event(input)
112+ self.assertEquals(
113+ b'"BluetoothAddress=\xc8i\xcdB\xe2\x09"', event['txt'])
114+
115 def test__returns_none_for_malformed_input(self):
116- self.assertThat(parse_avahi_event(";;;"), Equals(None))
117+ self.assertThat(parse_avahi_event(b";;;"), Equals(None))
118
119
120 def observe_mdns(*, input, output, verbose=False):
121@@ -143,9 +156,9 @@ class TestObserveMDNS(MAASTestCase):
122 def test__prints_event_json_in_verbose_mode(self):
123 out = io.StringIO()
124 input = (
125- "+;eth0;IPv4"
126- ";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
127- "_http._tcp;local\n")
128+ b"+;eth0;IPv4"
129+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
130+ b"_http._tcp;local\n")
131 expected_result = {
132 'event': 'BROWSER_NEW',
133 'interface': 'eth0',
134@@ -163,9 +176,9 @@ class TestObserveMDNS(MAASTestCase):
135 def test__skips_unimportant_events_without_verbose_enabled(self):
136 out = io.StringIO()
137 input = (
138- "+;eth0;IPv4"
139- ";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
140- "_http._tcp;local\n")
141+ b"+;eth0;IPv4"
142+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
143+ b"_http._tcp;local\n")
144 observe_mdns(verbose=False, input=[input], output=out)
145 output = io.StringIO(out.getvalue())
146 lines = output.readlines()
147@@ -174,13 +187,13 @@ class TestObserveMDNS(MAASTestCase):
148 def test__non_verbose_removes_redundant_events_and_outputs_summary(self):
149 out = io.StringIO()
150 input = (
151- "=;eth0;IPv4"
152- ";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
153- "_http._tcp;local;"
154- "printer.local;"
155- "192.168.0.222;"
156- "80;"
157- '"priority=50" "rp=RAW"\n')
158+ b"=;eth0;IPv4"
159+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
160+ b"_http._tcp;local;"
161+ b"printer.local;"
162+ b"192.168.0.222;"
163+ b"80;"
164+ b'"priority=50" "rp=RAW"\n')
165 observe_mdns(verbose=False, input=[input, input], output=out)
166 output = io.StringIO(out.getvalue())
167 lines = output.readlines()
168@@ -196,13 +209,13 @@ class TestObserveMDNS(MAASTestCase):
169 def test__non_verbose_removes_waits_before_emitting_duplicate_entry(self):
170 out = io.StringIO()
171 input = (
172- "=;eth0;IPv4"
173- ";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
174- "_http._tcp;local;"
175- "printer.local;"
176- "192.168.0.222;"
177- "80;"
178- '"priority=50" "rp=RAW"\n')
179+ b"=;eth0;IPv4"
180+ b";HP\\032Color\\032LaserJet\\032CP2025dn\\032\\040test\\041;"
181+ b"_http._tcp;local;"
182+ b"printer.local;"
183+ b"192.168.0.222;"
184+ b"80;"
185+ b'"priority=50" "rp=RAW"\n')
186 # If we see the same entry 3 times over the course of 15 minutes, we
187 # should only see output two out of the three times.
188 self.patch(time, "monotonic").side_effect = (100.0, 200.0, 900.0)
189@@ -239,7 +252,6 @@ class TestObserveMDNSCommand(MAASTestCase):
190 b"192.168.0.222;"
191 b"80;"
192 b'"priority=50" "rp=RAW"\n')
193- self.test_input_str = self.test_input_bytes.decode('utf-8')
194
195 def test__calls_subprocess_by_default(self):
196 parser = ArgumentParser()
197@@ -263,14 +275,14 @@ class TestObserveMDNSCommand(MAASTestCase):
198 add_arguments(parser)
199 args = parser.parse_args(['--input-file', '-'])
200 output = io.StringIO()
201- run(args, output=output, stdin=[self.test_input_str])
202+ run(args, output=output, stdin=[self.test_input_bytes])
203 self.assertThat(output.getvalue(), Not(HasLength(0)))
204
205 def test__allows_file_input(self):
206- with NamedTemporaryFile('w') as f:
207+ with NamedTemporaryFile('wb') as f:
208 parser = ArgumentParser()
209 add_arguments(parser)
210- f.write(self.test_input_str)
211+ f.write(self.test_input_bytes)
212 f.flush()
213 args = parser.parse_args(['--input-file', f.name])
214 output = io.StringIO()

Subscribers

People subscribed via source and target branches