From 3e510e6c141e01d9be677985b233c92ff5b1c11b Mon Sep 17 00:00:00 2001 From: The Dod Date: Sun, 14 Jun 2015 00:19:13 +0700 Subject: [PATCH] Added pastee3 lib It's also a command line tool: try ./pastee3.py -h --- pastee3.py | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100755 pastee3.py diff --git a/pastee3.py b/pastee3.py new file mode 100755 index 0000000..a86502e --- /dev/null +++ b/pastee3.py @@ -0,0 +1,209 @@ +#!/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 "". +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'
(.*)
', + 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()