The Dod
10 years ago
1 changed files with 209 additions and 0 deletions
@ -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 "</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() |
Loading…
Reference in new issue