-- Leo's gemini proxy

-- Connecting to going-flying.com:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

gemini.git

going-flying.com git repository

summary

tree

log

refs


gemini.git/cgi-bin/converter | 8 KB


view raw


#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
'''converter (c) 2021 Matthew J Ernisse <matt@going-flying.com>
All Rights Reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

    * Redistributions of source code must retain the
      above copyright notice, this list of conditions
      and the following disclaimer.
    * 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 OWNER 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 base64
import datetime
import secrets
import sys
import qrcode
from urllib.parse import parse_qs, quote, unquote, urlparse


import gmicgi

BACK_LINK = '/cgi-bin/converter/'
MAIN_SCREEN = '''
# Matt's Gemini CGI Toolbox.
I wanted to see what kind of things I could do with a CGI script.  Think a collection of little oneliners.  This is that.  As I think of little toys I'll probably add to this.  It happens to be written in Python.

## Number base converters

Display a number in binary, decimal and hexadecimal.  If your input cannot be recognized, the script will return a permanent failure.

=> bin	Binary Input
=> dec	Decimal Input
=> hex	Hexadecimal Input

## Encode / Decode

=> base64/encode	Base64 Encode
=> base64/decode	Base64 Decode

=> url/encode		URL Quote
=> url/decode		URL Unquote

## Random Stuff
While I have taken care to be fairly reasonable with these things, please note that Molly Brown does produce logs that get saved which means that while they may be cryptographically secure, they also may end up in a log file somewhere.  As such these should be used for demonstration purposes only.

=> random/base32	Random number, Base32 encoded
=> random/totp		Random number, encoded as a TOTP secret

## Other Stuff

=> unixdate		Unix timestamp to UTC date

## Suggestions?
Want to see something here?  Feel free to drop me a line.
=> mailto:matt@going-flying.com


=> /	↩ Home
'''


def encode_response(cgi, input, result):
	global BACK_LINK
	cgi.Response.Ok('text/gemini')

	if type(result) == bytes:
		result = result.decode('utf-8')

	output = f'# Input\n{input}\n\n'
	output += f'# Output\n{result}\n\n'
	output += f'=> {BACK_LINK}	Back\n'
	output += f'=> /	Home'
	print(output)


def ender(i):
	s = {
		42: 'Life, The Universe, and Everything',
		69: 'Nice',
		242: 'I have 17 remixes of Headhunter here somewhere',
		420: 'Nice',
		1701: 'The Final Frontier',
		2257: 'The dawn of the third age of mankind',
		3303: 'Remember...',
		31337: 'Greetz to Zero Cool and Joey'
	}
	return f' ({s[i]})\n\n' if i in s.keys() else '\n\n'


def pretty_binary(i):
	''' Pad a binary number out into space-separated octets.'''
	j = bin(i)[2:]
	out = []

	while len(j) > 8:
		out.insert(0, j[len(j) - 8:])
		j = j[:len(j) - 8]

	return '0' * (8 - len(j)) + j + ' ' + ' '.join(out)


def result_table(cgi, input, b, d, h):
	''' Print out the result page. '''
	global BACK_LINK
	cgi.Response.Ok('text/gemini')

	output = f'# Input\n{input}{ender(d)}'
	output += f'## Binary Representation\n{b}\n\n'
	output += f'## Decimal Representation\n{d}\n\n'
	output += f'## Hexadecimal Representation\n0x{h}\n\n'
	output += f'=> {BACK_LINK}	Back\n'
	output += f'=> /	Home'
	print(output)


def secret_response(cgi, val, qr=False):
	global BACK_LINK
	cgi.Response.Ok('text/gemini')

	if type(val) == bytes:
		val = val.decode('utf-8')

	output = f'# Your secret\n'
	output += f'{val}\n'

	if qr:
		e_val = base64.b64encode(val.encode('utf-8')).decode('utf-8')
		output += f'=> {BACK_LINK}random/qr/{e_val}	QR Code\n\n'

	output += f'=> {BACK_LINK}	Back\n'
	output += f'=> /	Home'
	print(output)


if __name__ == '__main__':
	cgi = gmicgi.GeminiCGI()

	if not cgi.query_string:
		if not cgi.path_info:
			cgi.Response.Ok('text/gemini')
			print(MAIN_SCREEN)

		# Number base conversion
		elif cgi.path_info == 'bin':
			cgi.Response.Input('Binary Number?')

		elif cgi.path_info == 'dec':
			cgi.Response.Input('Number?')

		elif cgi.path_info == 'hex':
			cgi.Response.Input('Hex Number?')

		# base64 encode/decode
		elif cgi.path_info == 'base64/encode':
			cgi.Response.Input('String to encode?')

		elif cgi.path_info == 'base64/decode':
			cgi.Response.Input('String to decode?')

		# urlencode/decode
		elif cgi.path_info == 'url/encode':
			cgi.Response.Input('String to encode?')

		elif cgi.path_info == 'url/decode':
			cgi.Response.Input('String to decode?')

		# Random Stuff
		elif cgi.path_info == 'random/base32':
			val = base64.b32encode(secrets.token_bytes(30))
			secret_response(cgi, val)

		elif cgi.path_info == 'random/totp':
			cgi.Response.Input('Account Label?')

		elif cgi.path_info.startswith('random/qr'):
			val = cgi.path_info.split('/')[-1]
			val = base64.b64decode(val).decode('utf-8')
			png = qrcode.make(val)

			cgi.Response.Ok('image/png')
			sys.stdout.flush()

			png.save(sys.stdout.buffer)
			sys.stdout.flush()

		# Unixdate
		elif cgi.path_info == 'unixdate':
			cgi.Response.Input('UNIX Timestamp?')

		else:
			cgi.Response.Redirect(BACK_LINK)
	else:
		if not cgi.path_info:
			cgi.Response.Redirect(BACK_LINK)

		# Number base conversion
		elif cgi.path_info == 'bin':
			s = cgi.query_string

			try:
				d = int(s, 2)
				b = pretty_binary(d)
				h = f'{d:02X}'
			except Exception:
				cgi.Response.Fail()
				sys.exit()

			result_table(cgi, s, b, d, h)

		elif cgi.path_info == 'dec':
			s = cgi.query_string

			try:
				d = int(s)
				b = pretty_binary(d)
				h = f'{d:02X}'
			except Exception:
				cgi.Response.Fail()
				sys.exit()

			result_table(cgi, s, b, d, h)

		elif cgi.path_info == 'hex':
			s = cgi.query_string

			try:
				d = int(s, 16)
				b = pretty_binary(d)
				h = f'{d:02X}'
			except Exception:
				cgi.Response.Fail()
				sys.exit()

			result_table(cgi, s, b, d, h)

		# base64 encode/decode
		elif cgi.path_info == 'base64/encode':
			s = cgi.query_dequoted
			try:
				res = base64.b64encode(s.encode('utf-8'))
			except Exception as e:
				sys.stderr.write(f'{e!s}\n')
				sys.stderr.flush()
				cgi.Response.Fail()
				sys.exit()

			encode_response(cgi, s, res)

		elif cgi.path_info == 'base64/decode':
			s = cgi.query_dequoted
			try:
				res = base64.b64decode(s, validate=True)
			except Exception:
				cgi.Response.Fail()
				sys.exit()

			encode_response(cgi, s, res)

		# urlencode/decode
		elif cgi.path_info == 'url/encode':
			s = cgi.query_dequoted
			try:
				res = quote(s, errors='strict')
			except Exception:
				cgi.Response.Fail()
				sys.exit()

			encode_response(cgi, s, res)

		elif cgi.path_info == 'url/decode':
			s = cgi.query_dequoted
			try:
				res = unquote(s, errors='strict')
			except Exception:
				cgi.Response.Fail()
				sys.exit()

			encode_response(cgi, s, res)

		# Random Stuff
		elif cgi.path_info == 'random/totp':
			# Leave pre-urlencoded!
			label = cgi.query_string
			val = base64.b32encode(secrets.token_bytes(30))
			val = val.decode('utf-8')
			secret_response(
				cgi,
				f'otpauth://totp/{label}?secret={val}',
				True
			)

		# Unixdate
		elif cgi.path_info == 'unixdate':
			ts = cgi.query_dequoted
			try:
				dt = datetime.datetime.fromtimestamp(float(ts))
				t = dt.strftime('%A %d %B %Y %H:%M:%S UTC')

			except Exception:
				cgi.Response.Fail()
				sys.exit()

			cgi.Response.Ok('text/gemini')
			output = f'# UNIX Timestamp {ts}\n'
			output += f'{t}\n\n'
			output += f'=> {BACK_LINK}	Back\n'
			output += f'=> /	Home'
			print(output)

		else:
			cgi.Response.NotFound()



-- Response ended

-- Page fetched on Tue Oct 19 15:24:16 2021