protocol: catch connection/SSL errors
This commit is contained in:
parent
15037ec0a6
commit
5ef9a0430a
|
@ -34,6 +34,8 @@ class Request:
|
|||
STATE_UNTRUSTED_CERT = 5
|
||||
# Valid and trusted cert: proceed.
|
||||
STATE_OK = 6
|
||||
# Connection failed.
|
||||
STATE_CONNECTION_FAILED = 7
|
||||
|
||||
def __init__(self, url, cert_stash):
|
||||
self.url = url
|
||||
|
@ -43,6 +45,7 @@ class Request:
|
|||
self.ssock = None
|
||||
self.cert = None
|
||||
self.cert_status = None
|
||||
self.error = ""
|
||||
|
||||
def connect(self):
|
||||
"""Connect to a Gemini server and return a RequestEventType.
|
||||
|
@ -73,9 +76,21 @@ class Request:
|
|||
return False
|
||||
self.payload += LINE_TERM
|
||||
|
||||
context = Request.get_ssl_context()
|
||||
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()
|
||||
try:
|
||||
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)
|
||||
self.cert_status, self.cert = \
|
||||
validate_cert(der, hostname, self.cert_stash)
|
||||
|
|
|
@ -135,7 +135,7 @@ class Screen:
|
|||
|
||||
def set_status_error(self, text):
|
||||
"""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()
|
||||
|
||||
def open_url(self, url, redirections=0):
|
||||
|
@ -145,7 +145,7 @@ class Screen:
|
|||
current URL, unless there is no current URL yet.
|
||||
"""
|
||||
if redirections > 5:
|
||||
self.set_status_error(f"too many redirections ({url})")
|
||||
self.set_status_error(f"Too many redirections ({url}).")
|
||||
return
|
||||
if self.current_url:
|
||||
parts = parse_url(url)
|
||||
|
@ -157,7 +157,7 @@ class Screen:
|
|||
url = join_url(self.current_url, url)
|
||||
self.open_gemini_url(sanitize_url(url), redirections)
|
||||
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):
|
||||
"""Open a Gemini URL and set the formatted response as content."""
|
||||
|
@ -166,13 +166,16 @@ class Screen:
|
|||
connected = req.connect()
|
||||
if not connected:
|
||||
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)
|
||||
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.
|
||||
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:
|
||||
self.set_status_error(f"connection failed ({url})")
|
||||
self.set_status_error(f"Connection failed ({url}).")
|
||||
return
|
||||
|
||||
if req.state == Request.STATE_INVALID_CERT:
|
||||
|
@ -186,7 +189,7 @@ class Screen:
|
|||
|
||||
response = Response.parse(req.proceed())
|
||||
if not response:
|
||||
self.set_status_error(f"server response parsing failed ({url})")
|
||||
self.set_status_error(f"Server response parsing failed ({url}).")
|
||||
return
|
||||
|
||||
if response.code == 20:
|
||||
|
@ -198,7 +201,8 @@ class Screen:
|
|||
elif response.generic_code == 30 and response.meta:
|
||||
self.open_url(response.meta, redirections=redirections + 1)
|
||||
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):
|
||||
"""Load Gemtext data as the current page."""
|
||||
|
@ -280,7 +284,7 @@ class Screen:
|
|||
try:
|
||||
self.open_link(links, int(link_input))
|
||||
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):
|
||||
"""Handle input chars to be used as link ID."""
|
||||
|
@ -313,7 +317,7 @@ class Screen:
|
|||
def open_link(self, links, link_id: int):
|
||||
"""Open the link with this link ID."""
|
||||
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
|
||||
self.open_url(links[link_id])
|
||||
|
||||
|
|
Reference in a new issue