protocol: catch connection/SSL errors

This commit is contained in:
dece 2021-02-15 19:57:49 +01:00
parent 15037ec0a6
commit 5ef9a0430a
2 changed files with 31 additions and 12 deletions

View file

@ -34,6 +34,8 @@ class Request:
STATE_UNTRUSTED_CERT = 5 STATE_UNTRUSTED_CERT = 5
# Valid and trusted cert: proceed. # Valid and trusted cert: proceed.
STATE_OK = 6 STATE_OK = 6
# Connection failed.
STATE_CONNECTION_FAILED = 7
def __init__(self, url, cert_stash): def __init__(self, url, cert_stash):
self.url = url self.url = url
@ -43,6 +45,7 @@ class Request:
self.ssock = None self.ssock = None
self.cert = None self.cert = None
self.cert_status = None self.cert_status = None
self.error = ""
def connect(self): def connect(self):
"""Connect to a Gemini server and return a RequestEventType. """Connect to a Gemini server and return a RequestEventType.
@ -73,9 +76,21 @@ class Request:
return False return False
self.payload += LINE_TERM self.payload += LINE_TERM
try:
sock = socket.create_connection((hostname, port))
except socket.gaierror as exc:
self.state = Request.STATE_CONNECTION_FAILED
self.error = exc.strerror
return False
context = Request.get_ssl_context() context = Request.get_ssl_context()
sock = socket.create_connection((hostname, port)) try:
self.ssock = context.wrap_socket(sock) self.ssock = context.wrap_socket(sock)
except OSError as exc:
self.state = Request.STATE_CONNECTION_FAILED
self.error = exc.strerror
return False
der = self.ssock.getpeercert(binary_form=True) der = self.ssock.getpeercert(binary_form=True)
self.cert_status, self.cert = \ self.cert_status, self.cert = \
validate_cert(der, hostname, self.cert_stash) validate_cert(der, hostname, self.cert_stash)

View file

@ -135,7 +135,7 @@ class Screen:
def set_status_error(self, text): def set_status_error(self, text):
"""Set an error message in the status bar.""" """Set an error message in the status bar."""
self.status_data = f"Error: {text}", ColorPair.ERROR self.status_data = text, ColorPair.ERROR
self.refresh_status_line() self.refresh_status_line()
def open_url(self, url, redirections=0): def open_url(self, url, redirections=0):
@ -145,7 +145,7 @@ class Screen:
current URL, unless there is no current URL yet. current URL, unless there is no current URL yet.
""" """
if redirections > 5: if redirections > 5:
self.set_status_error(f"too many redirections ({url})") self.set_status_error(f"Too many redirections ({url}).")
return return
if self.current_url: if self.current_url:
parts = parse_url(url) parts = parse_url(url)
@ -157,7 +157,7 @@ class Screen:
url = join_url(self.current_url, url) url = join_url(self.current_url, url)
self.open_gemini_url(sanitize_url(url), redirections) self.open_gemini_url(sanitize_url(url), redirections)
else: else:
self.set_status_error(f"protocol {parts.scheme} not supported") self.set_status_error(f"Protocol {parts.scheme} not supported.")
def open_gemini_url(self, url, redirections=0, history=True): def open_gemini_url(self, url, redirections=0, history=True):
"""Open a Gemini URL and set the formatted response as content.""" """Open a Gemini URL and set the formatted response as content."""
@ -166,13 +166,16 @@ class Screen:
connected = req.connect() connected = req.connect()
if not connected: if not connected:
if req.state == Request.STATE_ERROR_CERT: if req.state == Request.STATE_ERROR_CERT:
error = f"certificate was missing or corrupt ({url})" error = f"Certificate was missing or corrupt ({url})."
self.set_status_error(error) self.set_status_error(error)
elif req.state == Request.STATE_UNTRUSTED_CERT: elif req.state == Request.STATE_UNTRUSTED_CERT:
self.set_status_error(f"certificate has been changed ({url})") self.set_status_error(f"Certificate has been changed ({url}).")
# TODO propose the user ways to handle this. # TODO propose the user ways to handle this.
elif req.state == Request.STATE_CONNECTION_FAILED:
error = f": {req.error}" if req.error else ""
self.set_status_error(f"Connection failed ({url}){error}.")
else: else:
self.set_status_error(f"connection failed ({url})") self.set_status_error(f"Connection failed ({url}).")
return return
if req.state == Request.STATE_INVALID_CERT: if req.state == Request.STATE_INVALID_CERT:
@ -186,7 +189,7 @@ class Screen:
response = Response.parse(req.proceed()) response = Response.parse(req.proceed())
if not response: if not response:
self.set_status_error(f"server response parsing failed ({url})") self.set_status_error(f"Server response parsing failed ({url}).")
return return
if response.code == 20: if response.code == 20:
@ -198,7 +201,8 @@ class Screen:
elif response.generic_code == 30 and response.meta: elif response.generic_code == 30 and response.meta:
self.open_url(response.meta, redirections=redirections + 1) self.open_url(response.meta, redirections=redirections + 1)
elif response.generic_code in (40, 50): elif response.generic_code in (40, 50):
self.set_status_error(response.meta or Response.code.name) error = f"Server error: {response.meta or Response.code.name}."
self.set_status_error(error)
def load_page(self, gemtext: bytes): def load_page(self, gemtext: bytes):
"""Load Gemtext data as the current page.""" """Load Gemtext data as the current page."""
@ -280,7 +284,7 @@ class Screen:
try: try:
self.open_link(links, int(link_input)) self.open_link(links, int(link_input))
except ValueError: except ValueError:
self.set_status_error("invalid link ID") self.set_status_error("Invalid link ID.")
def _validate_link_digit(self, ch: int, links, max_digits: int): def _validate_link_digit(self, ch: int, links, max_digits: int):
"""Handle input chars to be used as link ID.""" """Handle input chars to be used as link ID."""
@ -313,7 +317,7 @@ class Screen:
def open_link(self, links, link_id: int): def open_link(self, links, link_id: int):
"""Open the link with this link ID.""" """Open the link with this link ID."""
if not link_id in links: if not link_id in links:
self.set_status_error(f"unknown link ID {link_id}.") self.set_status_error(f"Unknown link ID {link_id}.")
return return
self.open_url(links[link_id]) self.open_url(links[link_id])