Sometimes, some gemini capsules fail with one of the two:
AttributeError: 'IPv6Address' object has no attribute 'count'
AttributeError: 'IPv4Address' object has no attribute 'count'
This happened on gemini://thrig.me/blog/2023/03/02/legacy-ueb.gmi before being solved.
I believe that the problem is in the fix_ipv6_url function which is quite buggy.
After investigation, this is definitely not the fix_ipv6_url function. The bug happen even when bypassing this function.
Strangely, I’ve not been able to reproduce the bug with HAS_CRYPTOGRAPHY = False.
This only happens with gemini URL.
gemini://thrig.me/blog/2023/03/04/xkcd-colon-slash-slash.gmi might cause the bug sometimes
Traceback (most recent call last):
File "/home/ploum/dev/offpunk/offpunk.py", line 2121, in _go_to_gi
gi = self._fetch_over_network(gi)
File "/home/ploum/dev/offpunk/offpunk.py", line 2424, in _fetch_over_network
address, f = self._send_request(gi)
File "/home/ploum/dev/offpunk/offpunk.py", line 2603, in _send_request
self._validate_cert(address[4][0], host, cert)
File "/home/ploum/dev/offpunk/offpunk.py", line 2715, in _validate_cert
ssl._dnsname_match(name, host)
File "/usr/lib/python3.10/ssl.py", line 289, in _dnsname_match
wildcards = dn.count('*')
AttributeError: 'IPv4Address' object has no attribute 'count'
For the record, I’ve found the culprit.
The problem is that, when browsing Gemini, offpunk is using custom certificate code written by solderpunk for AV-98. This code calls directly a private function in the standard SSL library:
ssl._dnsname_match(dn,hostname)
This function expects two strings and will raise a CertificateError if the hostname doesn’t match the DN rules (including "*" rules).
In some very rare case, the dn exctracted from a certificate is an ip adress. What I still don’t understand is that, from the same certificate, the code sometime extracts an IP address, sometimes a DN.
But when it’s an IP address, this IP is not considered a string but an IPv4/6 object. Which cannot be manipulated as a string.
The quick and ugly fix to this is to cast the DN object as a string.
Of course, the real problem would be to understand what is happening in that naughty code and why there’s a need to call a private function.