Compare commits
No commits in common. "0f35971493c53704b8f5bf4728c2027720c8c3d3" and "7bbf949c094d9d1071ff4dc324b7a169fa09e205" have entirely different histories.
0f35971493
...
7bbf949c09
|
@ -69,7 +69,6 @@ class Browser:
|
||||||
|
|
||||||
SEARCH_NEXT = 0
|
SEARCH_NEXT = 0
|
||||||
SEARCH_PREVIOUS = 1
|
SEARCH_PREVIOUS = 1
|
||||||
MAX_REDIRECTIONS = 5
|
|
||||||
|
|
||||||
def __init__(self, config, cert_stash):
|
def __init__(self, config, cert_stash):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -421,7 +420,7 @@ class Browser:
|
||||||
- history: whether the URL should be pushed to history on success.
|
- history: whether the URL should be pushed to history on success.
|
||||||
- use_cache: whether we should look for an already cached document.
|
- use_cache: whether we should look for an already cached document.
|
||||||
"""
|
"""
|
||||||
if redirects > self.MAX_REDIRECTIONS:
|
if redirects > 5:
|
||||||
self.set_status_error(f"Too many redirections ({url}).")
|
self.set_status_error(f"Too many redirections ({url}).")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ MAX_URL_LEN = 1024
|
||||||
def open_gemini_url(
|
def open_gemini_url(
|
||||||
browser: Browser,
|
browser: Browser,
|
||||||
url: str,
|
url: str,
|
||||||
redirects: int = 0,
|
redirects: int =0,
|
||||||
use_cache: bool = False,
|
use_cache: bool =False,
|
||||||
cert_and_key=None
|
cert_and_key=None
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""Open a Gemini URL and set the formatted response as content.
|
"""Open a Gemini URL and set the formatted response as content.
|
||||||
|
@ -41,9 +41,9 @@ def open_gemini_url(
|
||||||
present the user the problems found and let her decide whether to trust
|
present the user the problems found and let her decide whether to trust
|
||||||
temporarily the certificate or not BUT we currently do not parse the
|
temporarily the certificate or not BUT we currently do not parse the
|
||||||
certificate's fields, not even the pubkey, so this state is never used.
|
certificate's fields, not even the pubkey, so this state is never used.
|
||||||
- STATE_UNKNOWN_CERT: the certificate is valid but has not been seen
|
- STATE_UNKNOWN_CERT: the certificate is valid but has not been seen before;
|
||||||
before; as we're doing TOFU here, we could automatically trust it or let
|
as we're doing TOFU here, we could automatically trust it or let the user
|
||||||
the user choose. For simplicity, we always trust it permanently.
|
choose. For simplicity, we always trust it permanently.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- browser: Browser object making the request.
|
- browser: Browser object making the request.
|
||||||
|
@ -111,21 +111,16 @@ def open_gemini_url(
|
||||||
trust_always=True
|
trust_always=True
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
data = req.proceed()
|
||||||
data = req.proceed()
|
|
||||||
except OSError:
|
|
||||||
browser.set_status_error(f"Connection error ({url}).")
|
|
||||||
return None
|
|
||||||
if not data:
|
if not data:
|
||||||
browser.set_status_error(f"Response empty or timed out ({url}).")
|
browser.set_status_error(f"Server did not respond in time ({url}).")
|
||||||
return None
|
return None
|
||||||
response = Response.parse(data)
|
response = Response.parse(data)
|
||||||
if not response:
|
if not response:
|
||||||
browser.set_status_error(f"Response parsing failed ({url}).")
|
browser.set_status_error(f"Server response parsing failed ({url}).")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return _handle_response(browser, response, url, redirects,
|
return _handle_response(browser, response, url, redirects)
|
||||||
used_cert=cert_and_key is not None)
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_untrusted_cert(browser: Browser, request: Request):
|
def _handle_untrusted_cert(browser: Browser, request: Request):
|
||||||
|
@ -152,8 +147,7 @@ def _handle_response(
|
||||||
browser: Browser,
|
browser: Browser,
|
||||||
response: Response,
|
response: Response,
|
||||||
url: str,
|
url: str,
|
||||||
redirects: int,
|
redirects: int
|
||||||
used_cert: bool = False,
|
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""Handle a response from a Gemini server.
|
"""Handle a response from a Gemini server.
|
||||||
|
|
||||||
|
@ -177,11 +171,7 @@ def _handle_response(
|
||||||
elif response.generic_code == 10:
|
elif response.generic_code == 10:
|
||||||
return _handle_input_request(browser, url, response.meta)
|
return _handle_input_request(browser, url, response.meta)
|
||||||
elif response.code == 60:
|
elif response.code == 60:
|
||||||
if used_cert:
|
return _handle_cert_required(browser, response, url, redirects)
|
||||||
error = "Server ignored our certificate."
|
|
||||||
browser.set_status_error(error)
|
|
||||||
else:
|
|
||||||
return _handle_cert_required(browser, response, url, redirects)
|
|
||||||
elif response.code in (61, 62):
|
elif response.code in (61, 62):
|
||||||
details = response.meta or Response.code.name
|
details = response.meta or Response.code.name
|
||||||
error = f"Client certificate error: {details}"
|
error = f"Client certificate error: {details}"
|
||||||
|
@ -192,11 +182,7 @@ def _handle_response(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _handle_successful_response(
|
def _handle_successful_response(browser: Browser, response: Response, url: str):
|
||||||
browser: Browser,
|
|
||||||
response: Response,
|
|
||||||
url: str
|
|
||||||
):
|
|
||||||
"""Handle a successful response content from a Gemini server.
|
"""Handle a successful response content from a Gemini server.
|
||||||
|
|
||||||
According to the MIME type received or inferred, the response is either
|
According to the MIME type received or inferred, the response is either
|
||||||
|
@ -229,8 +215,7 @@ def _handle_successful_response(
|
||||||
error = f"Unknown encoding {encoding}."
|
error = f"Unknown encoding {encoding}."
|
||||||
else:
|
else:
|
||||||
render_opts = get_render_options(browser.config)
|
render_opts = get_render_options(browser.config)
|
||||||
pref_mode = get_url_render_mode_pref(
|
pref_mode = get_url_render_mode_pref(browser.capsule_prefs, url)
|
||||||
browser.capsule_prefs, url)
|
|
||||||
if pref_mode:
|
if pref_mode:
|
||||||
render_opts.mode = pref_mode
|
render_opts.mode = pref_mode
|
||||||
page = Page.from_gemtext(text, render_opts)
|
page = Page.from_gemtext(text, render_opts)
|
||||||
|
@ -270,7 +255,7 @@ def _handle_successful_response(
|
||||||
def _handle_input_request(
|
def _handle_input_request(
|
||||||
browser: Browser,
|
browser: Browser,
|
||||||
from_url: str,
|
from_url: str,
|
||||||
message: str = None
|
message: str =None
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""Focus command-line to pass input to the server.
|
"""Focus command-line to pass input to the server.
|
||||||
|
|
||||||
|
@ -326,12 +311,11 @@ def _handle_cert_required(
|
||||||
|
|
||||||
def select_identity(identities: list):
|
def select_identity(identities: list):
|
||||||
"""Let user select the appropriate identity among candidates."""
|
"""Let user select the appropriate identity among candidates."""
|
||||||
# TODO support multiple identities; for now we just use the first
|
# TODO support multiple identities; for now we just use the first available.
|
||||||
# available.
|
|
||||||
return identities[0] if identities else None
|
return identities[0] if identities else None
|
||||||
|
|
||||||
|
|
||||||
def create_identity(browser: Browser, url: str, reason: Optional[str] = None):
|
def create_identity(browser: Browser, url: str, reason: Optional[str] =None):
|
||||||
"""Walk the user through identity creation.
|
"""Walk the user through identity creation.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -368,7 +352,7 @@ def create_identity(browser: Browser, url: str, reason: Optional[str] = None):
|
||||||
|
|
||||||
|
|
||||||
def forget_certificate(browser: Browser, hostname: str):
|
def forget_certificate(browser: Browser, hostname: str):
|
||||||
"""Remove the fingerprint for this hostname from the cert stash."""
|
"""Remove the fingerprint associated to this hostname for the cert stash."""
|
||||||
key = browser.prompt(f"Remove fingerprint for {hostname}?")
|
key = browser.prompt(f"Remove fingerprint for {hostname}?")
|
||||||
if key != "y":
|
if key != "y":
|
||||||
browser.reset_status()
|
browser.reset_status()
|
||||||
|
@ -376,5 +360,4 @@ def forget_certificate(browser: Browser, hostname: str):
|
||||||
if untrust_fingerprint(browser.stash, hostname):
|
if untrust_fingerprint(browser.stash, hostname):
|
||||||
browser.set_status(f"Known certificate for {hostname} removed.")
|
browser.set_status(f"Known certificate for {hostname} removed.")
|
||||||
else:
|
else:
|
||||||
browser.set_status_error(
|
browser.set_status_error(f"Known certificate for {hostname} not found.")
|
||||||
f"Known certificate for {hostname} not found.")
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ class Request:
|
||||||
sending the request header and receiving the response:
|
sending the request header and receiving the response:
|
||||||
|
|
||||||
1. Instantiate a Request.
|
1. Instantiate a Request.
|
||||||
2. `connect` opens the connection and aborts it or leaves the caller free
|
2. `connect` opens the connection and aborts it or leaves the caller free to
|
||||||
to check stuff.
|
check stuff.
|
||||||
3. `proceed` or `abort` can be called.
|
3. `proceed` or `abort` can be called.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -35,8 +35,8 @@ class Request:
|
||||||
- cert_stash: certificate stash to use an possibly update.
|
- cert_stash: certificate stash to use an possibly update.
|
||||||
- state: request state.
|
- state: request state.
|
||||||
- hostname: hostname derived from url, stored when `connect` is called.
|
- hostname: hostname derived from url, stored when `connect` is called.
|
||||||
- payload: bytes object of the payload request; build during `connect`,
|
- payload: bytes object of the payload request; build during `connect`, used
|
||||||
used during `proceed`.
|
during `proceed`.
|
||||||
- ssock: TLS-wrapped socket.
|
- ssock: TLS-wrapped socket.
|
||||||
- cert_validation: validation results dict, set after certificate has been
|
- cert_validation: validation results dict, set after certificate has been
|
||||||
reviewed.
|
reviewed.
|
||||||
|
@ -79,12 +79,12 @@ class Request:
|
||||||
certificate status is not CertStatus.VALID (Request.STATE_OK).
|
certificate status is not CertStatus.VALID (Request.STATE_OK).
|
||||||
|
|
||||||
If connect returns False, the secure socket is aborted before return so
|
If connect returns False, the secure socket is aborted before return so
|
||||||
there is no need to call `abort`. If connect returns True, it is up to
|
there is no need to call `abort`. If connect returns True, it is up to the
|
||||||
the caller to decide whether to continue (call `proceed`) the
|
caller to decide whether to continue (call `proceed`) the connection or
|
||||||
connection or abort it (call `abort`).
|
abort it (call `abort`).
|
||||||
|
|
||||||
The request `state` is updated to reflect the connection state after
|
The request `state` is updated to reflect the connection state after the
|
||||||
the function returns. The following list describes states related to
|
function returns. The following list describes states related to
|
||||||
connection failure (False returned):
|
connection failure (False returned):
|
||||||
|
|
||||||
- STATE_INVALID_URL: URL is not valid.
|
- STATE_INVALID_URL: URL is not valid.
|
||||||
|
@ -95,8 +95,8 @@ class Request:
|
||||||
For all request states from now on, the `cert_validation` attribute is
|
For all request states from now on, the `cert_validation` attribute is
|
||||||
updated with the result of the certificate validation.
|
updated with the result of the certificate validation.
|
||||||
|
|
||||||
The following list describes states related to validation failure
|
The following list describes states related to validation failure (False
|
||||||
(False returned):
|
returned):
|
||||||
|
|
||||||
- STATE_ERROR_CERT: server certificate could not be validated at all.
|
- STATE_ERROR_CERT: server certificate could not be validated at all.
|
||||||
- STATE_UNTRUSTED_CERT: server certificate mismatched the known
|
- STATE_UNTRUSTED_CERT: server certificate mismatched the known
|
||||||
|
@ -117,8 +117,7 @@ class Request:
|
||||||
|
|
||||||
- The DER hash is compared against the fingerprint for this hostname
|
- The DER hash is compared against the fingerprint for this hostname
|
||||||
*and port*; the specification does not tell much about that, but we
|
*and port*; the specification does not tell much about that, but we
|
||||||
are slightly more restrictive here by adding the port in the
|
are slightly more restrictive here by adding the port in the equation.
|
||||||
equation.
|
|
||||||
- The state STATE_INVALID_CERT is actually never used in Bebop because
|
- The state STATE_INVALID_CERT is actually never used in Bebop because
|
||||||
of the current tendency to ignore any certificate fields and only
|
of the current tendency to ignore any certificate fields and only
|
||||||
check the whole cert fingerprint. Here it is considered the same as a
|
check the whole cert fingerprint. Here it is considered the same as a
|
||||||
|
|
Reference in a new issue