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
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()
|
|
|