2020-01-12 20:08:36 +01:00
|
|
|
#!/usr/bin/env python3
|
2019-08-12 17:24:42 +02:00
|
|
|
"""
|
2020-05-19 07:14:44 +02:00
|
|
|
A very basic gemini client to use for testing server configurations.
|
2019-08-12 17:24:42 +02:00
|
|
|
"""
|
|
|
|
import argparse
|
|
|
|
import socket
|
|
|
|
import ssl
|
2020-05-18 05:08:16 +02:00
|
|
|
import sys
|
2020-12-29 06:03:07 +01:00
|
|
|
import typing
|
2019-08-12 17:24:42 +02:00
|
|
|
import urllib.parse
|
|
|
|
|
|
|
|
context = ssl.create_default_context()
|
|
|
|
context.check_hostname = False
|
|
|
|
context.verify_mode = ssl.CERT_NONE
|
|
|
|
|
|
|
|
|
2020-12-29 06:03:07 +01:00
|
|
|
def fetch(
|
|
|
|
url: str,
|
|
|
|
host: typing.Optional[str] = None,
|
|
|
|
port: typing.Optional[int] = None,
|
|
|
|
use_sni: bool = False,
|
|
|
|
) -> None:
|
2019-08-12 17:24:42 +02:00
|
|
|
parsed_url = urllib.parse.urlparse(url)
|
|
|
|
if not parsed_url.scheme:
|
|
|
|
parsed_url = urllib.parse.urlparse(f"gemini://{url}")
|
|
|
|
|
|
|
|
host = host or parsed_url.hostname
|
|
|
|
port = port or parsed_url.port or 1965
|
2020-05-19 07:14:44 +02:00
|
|
|
sni = host if use_sni else None
|
2020-05-19 06:25:22 +02:00
|
|
|
|
2019-08-12 17:24:42 +02:00
|
|
|
with socket.create_connection((host, port)) as sock:
|
2020-05-19 07:14:44 +02:00
|
|
|
with context.wrap_socket(sock, server_hostname=sni) as ssock:
|
2019-08-12 17:24:42 +02:00
|
|
|
ssock.sendall((url + "\r\n").encode())
|
2020-07-12 05:58:25 +02:00
|
|
|
|
2020-05-18 05:08:16 +02:00
|
|
|
fp = ssock.makefile("rb", buffering=0)
|
|
|
|
data = fp.read(1024)
|
|
|
|
while data:
|
|
|
|
sys.stdout.buffer.write(data)
|
2020-05-26 05:40:15 +02:00
|
|
|
sys.stdout.buffer.flush()
|
2020-05-18 05:08:16 +02:00
|
|
|
data = fp.read(1024)
|
2019-08-12 17:24:42 +02:00
|
|
|
|
|
|
|
|
2020-12-29 06:03:07 +01:00
|
|
|
def run_client() -> None:
|
2020-07-12 05:58:25 +02:00
|
|
|
# fmt: off
|
2019-08-12 17:24:42 +02:00
|
|
|
parser = argparse.ArgumentParser(description="A simple gemini client")
|
|
|
|
parser.add_argument("url")
|
2020-05-19 07:14:44 +02:00
|
|
|
parser.add_argument("--host", help="Server host")
|
|
|
|
parser.add_argument("--port", help="Server port")
|
|
|
|
parser.add_argument("--tls-certfile", help="Client certificate")
|
|
|
|
parser.add_argument("--tls-keyfile", help="Client private key")
|
|
|
|
parser.add_argument("--tls-alpn-protocol", help="Protocol for ALPN negotiation")
|
2020-07-12 05:58:25 +02:00
|
|
|
parser.add_argument("--tls-enable-sni", action="store_true", help="Specify the hostname using SNI")
|
|
|
|
parser.add_argument("--tls-keylog", help="Keylog file for TLS debugging (requires python 3.8+)")
|
|
|
|
# fmt: on
|
2019-08-21 03:17:58 +02:00
|
|
|
|
2020-05-19 06:25:22 +02:00
|
|
|
args = parser.parse_args()
|
2020-05-19 07:14:44 +02:00
|
|
|
if args.tls_certfile:
|
|
|
|
context.load_cert_chain(args.tls_certfile, args.tls_keyfile)
|
|
|
|
|
|
|
|
if args.tls_alpn_protocol:
|
|
|
|
context.set_alpn_protocols([args.tls_alpn_protocol])
|
2019-08-21 03:17:58 +02:00
|
|
|
|
2020-07-12 05:58:25 +02:00
|
|
|
if args.tls_keylog:
|
2020-12-29 06:03:07 +01:00
|
|
|
# This is a "private" variable that the stdlib exposes for debugging
|
2021-01-08 05:41:56 +01:00
|
|
|
context.keylog_filename = args.tls_keylog # type: ignore
|
2020-07-12 05:58:25 +02:00
|
|
|
|
2020-05-19 07:14:44 +02:00
|
|
|
fetch(args.url, args.host, args.port, args.tls_enable_sni)
|
2019-08-12 17:24:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
run_client()
|