#!/usr/bin/env python3 # Copyright (c) 2020 Avatar Aaron Janse # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import cgi import os import socket import ssl import sys import urllib.parse def absolutise_url(base, relative): # Absolutise relative links if "://" not in relative: # Python's URL tools somehow only work with known schemes? base = base.replace("gemini://","http://") relative = urllib.parse.urljoin(base, relative) relative = relative.replace("http://", "gemini://") return relative if len(sys.argv) != 2: print("Usage:") print("gcat gemini://gemini.circumlunar.space") sys.exit(1) url = sys.argv[1] parsed_url = urllib.parse.urlparse(url) if parsed_url.scheme == "": url = "gemini://"+url parsed_url = urllib.parse.urlparse(url) if parsed_url.scheme != "gemini": print("Sorry, Gemini links only.") sys.exit(1) if parsed_url.port is not None: useport = parsed_url.port else: useport = 1965 # Do the Gemini transaction while True: s = socket.create_connection((parsed_url.hostname, useport)) context = ssl.SSLContext() context.check_hostname = False context.verify_mode = ssl.CERT_NONE s = context.wrap_socket(s, server_hostname = parsed_url.netloc) s.sendall((url + '\r\n').encode("UTF-8")) # Get header and check for redirects fp = s.makefile("rb") header = fp.readline() print(header.decode("UTF-8"), end="") header = header.decode("UTF-8").strip() status, mime = header.split()[:2] # Handle input requests if status.startswith("1"): # Prompt query = input("INPUT" + mime + "> ") url += "?" + urllib.parse.quote(query) # Bit lazy... # Follow redirects elif status.startswith("3"): url = absolutise_url(url, mime) parsed_url = urllib.parse.urlparse(url) # Otherwise, we're done. else: break # Fail if transaction was not successful if status.startswith("2"): if mime.startswith("text/"): # Decode according to declared charset mime, mime_opts = cgi.parse_header(mime) body = fp.read() body = body.decode(mime_opts.get("charset","UTF-8")) print(body, end="") else: print(fp.read(), end="")