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.
210 lines
6.0 KiB
210 lines
6.0 KiB
10 years ago
|
#!/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()
|