#!/usr/bin/env python3 import socket import ssl import sys import urllib.parse # read target url from arguments arg = sys.argv[1] if '//' not in arg: arg = '//' + arg url = urllib.parse.urlparse(arg, scheme='gemini') if url.scheme != 'gemini': sys.exit('Unable to handle scheme') port = url.port if url.port is not None else 1965 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.options |= OP_NO_TLSv1 | OP_NO_TLSV1_1 | OP_NO_TLSv1_2 # ignore certs for now, TODO actually handle cert TOFU context.check_hostname = False context.verify_mode = ssl.CERT_NONE # TODO AF_INET6 support with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: with context.wrap_socket(sock, server_hostname=url.hostname) as ssock: # connect to server [TLS handshake occurs after connect() by default] ip = socket.gethostbyname(url.hostname) ssock.connect((ip, port)) ssock.sendall(urlparse.urlunsplit(url) + '\r\n') with ssock.makefile() as io: header = io.readline() print(header) status, meta = header.split(' ', 1) switch = status[0] if switch == '1': query = urllib.parse.quote(input('server requesting input: ' + meta)) # TODO retry request else if switch == '2': # meta is mime type # TODO handle text/gemini i guess? if meta.startswith('text/'): body = io.read() for line in body.spiltlines(): print(line) else: # TODO download file instead sys.exit('didn\'t get a text file') else if switch == '3': print('temporary redirect to: ' + meta) # TODO prompt to follow else if switch == '4': print('temporary failure: ' + meta) else if switch == '5': print('permanent failure: ' + meta) else if switch == '6': print('client certificate required: ' + meta) # TODO prompt to retry else: print('unknown response: ' + meta)