A twister-based game of intrigue and bad crypto
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
6.0 KiB

#!/usr/bin/env python3
## Based on https://pastee.org/api with 2 modifications by thedod:
## 1. Standard 2to3 run
## 2. Added sloppy_get() [use at your own risk] despite advice against
## such practices at http://stackoverflow.com/a/1732454
import http.client
import optparse
import os
import sys
import threading
import urllib.request, urllib.parse, urllib.error
import urllib.parse
import re
import html
__version__ = (3, 1, 0)
PASTEE_URL = "https://pastee.org"
DEFAULT_LEXER = "text"
DEFAULT_TTL = 30 # days
class Paste:
"""Class representing a paste that has been submitted."""
def __init__(self, content, lexer, url):
"""Constructor.
Args:
content: paste content
lexer: lexer used for this paste
url: URL to access the paste
"""
self.content = content
self.lexer = lexer
self.url = url
def __unicode__(self):
return self.url
def __str__(self):
return str(self.__unicode__)
class PasteClient:
"""Pasting client for a Pastee application.
Instances of this class can be used to programmatically create new pastes on
an installation of Pastee (https://pastee.org).
This class is thread-safe.
"""
def __init__(self, url=PASTEE_URL):
"""Constructor.
Args:
url: URL to Pastee installation (defaults to https://pastee.org)
"""
parse = urllib.parse.urlsplit(url)
self._scheme = parse[0]
self._netloc = parse[1]
self._lock = threading.Semaphore()
def sloppy_get(self,paste_id):
"""Assumes a paste with no lexer that doesn't contain "</pre></div>".
Sometimes you're *sure* it won't ;)"""
if self._scheme == "https":
self._conn = http.client.HTTPSConnection(self._netloc)
else:
self._conn = http.client.HTTPConnection(self._netloc)
self._conn.request('GET','/{}'.format(paste_id))
try:
response = self._conn.getresponse()
return html.unescape(
re.findall(
b'<div class="syntax"><pre>(.*)</pre></div>',
response.read(), re.MULTILINE|re.DOTALL
)[0].decode('unicode_escape'))
except:
pass # Todo: error handling :p
return None
def paste(self, content, lexer=None, ttl=None, key=None):
"""Create a new paste.
Args:
content: string of text to paste
lexer: lexer to use (defaults to text)
ttl: time-to-live in days (defaults to 30)
key: encrypt paste with this key; if not specified, paste is not
encrypted
Returns:
Paste object
"""
if lexer is None:
lexer = DEFAULT_LEXER
if ttl is None:
ttl = DEFAULT_TTL
if self._scheme == "https":
self._conn = http.client.HTTPSConnection(self._netloc)
else:
self._conn = http.client.HTTPConnection(self._netloc)
headers = {"Content-type": "application/x-www-form-urlencoded",
"Accept": "text/plain"}
params = {"lexer": lexer,
"content": content,
"ttl": int(ttl * 86400)}
if key is not None:
params["encrypt"] = "checked"
params["key"] = key
self._lock.acquire()
self._conn.request("POST", "/submit", urllib.parse.urlencode(params), headers)
response = self._conn.getresponse()
self._lock.release()
return self._make_paste(response, content, lexer)
def paste_file(self, filename, lexer=None, ttl=None, key=None):
"""Create a new paste from a file.
Args:
filename: path to file
lexer: lexer to use (defaults to extension of the file or text)
ttl: time-to-live in days (defaults to 30)
key: encrypt paste with this key; if not specified, paste is not
encrypted
Returns:
Paste object
"""
_, ext = os.path.splitext(filename)
if lexer is None and ext:
lexer = ext[1:] # remove leading period first
# TODO(ms): need exception handling here
fd = open(filename, "r")
content = fd.read()
fd.close()
return self.paste(content, lexer=lexer, ttl=ttl, key=key)
def _make_paste(self, response, content, lexer):
for (key, value) in response.getheaders():
if key.lower() == "location":
return self._clean_url(value)
return Paste(content, lexer, self._clean_url(value))
def _clean_url(self, url):
p = urllib.parse.urlsplit(url)
scheme = p[0]
netloc_split = p[1].split(":")
hostname = netloc_split[0]
if len(netloc_split) > 1:
port = int(netloc_split[1])
else:
port = scheme == "https" and 443 or 80
path = p[2]
port_str = ""
if port != 80 and scheme == "http":
port_str = ":%d" % port
elif port != 443 and scheme == "https":
port_str = ":%d" % port
return "%s://%s%s%s" % (scheme, hostname, port_str, path)
def die_with_error(message):
"""Print a message and exit with exit code 1.
Args:
message: message to print before exiting
"""
print("error: %s" % message)
sys.exit(1)
def main():
parser = optparse.OptionParser()
parser.add_option("-l", "--lexer", dest="lexer", metavar="LEXERNAME",
help=("Force use of a particular lexer (i.e. c, py). "
"This defaults to the extension of the supplied "
"filenames, or 'text' if pasting from stdin."))
parser.add_option("-t", "--ttl", dest="ttl", metavar="DAYS",
help=("Number of days before the paste will expire."))
## I'd rather not promote server side crypto. Sorry.
#parser.add_option("-k", "--key", dest="key", metavar="PASSPHRASE",
# help=("Encrypt pastes with this key."))
(options, filenames) = parser.parse_args()
lexer = options.lexer
key = options.key
try:
ttl = float(options.ttl)
except ValueError:
die_with_error("floating point number must be passed for TTL")
except TypeError:
ttl = None
client = PasteClient()
if filenames:
# paste from multiple files
for filename in filenames:
print(client.paste_file(filename, lexer=lexer, ttl=ttl, key=key))
else:
# paste from stdin
print(client.paste(sys.stdin.read(), lexer=lexer, ttl=ttl, key=key))
if __name__ == "__main__":
main()