2020-10-07 21:42:20 +02:00
|
|
|
#!/usr/bin/env python3
|
2020-06-29 21:35:54 +02:00
|
|
|
# -- coding: utf-8 --
|
|
|
|
|
|
|
|
import web
|
|
|
|
import cgi
|
|
|
|
web.config.debug = True
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
|
|
|
|
import shutil
|
|
|
|
import hashlib
|
|
|
|
import magic
|
|
|
|
import mimetypes
|
|
|
|
from base64 import urlsafe_b64encode
|
|
|
|
from random import random
|
2020-10-07 21:42:20 +02:00
|
|
|
from io import StringIO
|
2020-06-29 21:35:54 +02:00
|
|
|
|
|
|
|
sys.path.append(os.path.dirname(__file__))
|
|
|
|
from config import *
|
|
|
|
import utils
|
|
|
|
|
|
|
|
render = web.template.render(CONF_TEMPLATE_DIR)
|
|
|
|
cgi.maxlen = CONF_MAX_UPLOAD_SIZE
|
|
|
|
|
|
|
|
urls = (
|
|
|
|
'/', 'Greeter',
|
|
|
|
'/p', 'Pastebin',
|
|
|
|
'/info', 'FileInfo',
|
|
|
|
'/cleanup', 'Cleanup',
|
2020-10-07 22:11:32 +02:00
|
|
|
'/([a-zA-Z0-9_.-]*)', 'Pastebin',
|
|
|
|
'/([a-zA-Z0-9_-]+)/(.*)', 'Pastebin'
|
2020-06-29 21:35:54 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
class Greeter:
|
|
|
|
def GET(self):
|
|
|
|
web.header("Content-Type", 'text/html')
|
|
|
|
return render.index(web.ctx.host, CONF_MAX_UPLOAD_SIZE)
|
|
|
|
|
|
|
|
class Pastebin:
|
|
|
|
def __init__(self):
|
|
|
|
self.db = web.database(
|
|
|
|
dbn = CONF_SQL_DRIVER,
|
|
|
|
db = CONF_SQL_DATABASE,
|
|
|
|
user = CONF_SQL_USER,
|
|
|
|
pw = CONF_SQL_PASSWORD)
|
|
|
|
|
|
|
|
self.magic = magic.Magic(mime = True)
|
|
|
|
|
|
|
|
# this function returns (mime type, file extension) for the file with the
|
|
|
|
# given name or None, if the type is unknown
|
|
|
|
def detect_file_type(self, filename):
|
|
|
|
mime = self.magic.from_file(filename)
|
|
|
|
|
|
|
|
# prevent weird extensions like ".ksh" for plain-text
|
|
|
|
if mime == "text/plain":
|
|
|
|
extension = ".txt"
|
|
|
|
else:
|
|
|
|
extension = mimetypes.guess_extension(mime)
|
|
|
|
return mime, extension
|
|
|
|
|
|
|
|
def POST(self, doc = None):
|
|
|
|
web.header("Content-Type", 'text/plain')
|
|
|
|
|
|
|
|
try:
|
|
|
|
x = web.input(content = {})
|
|
|
|
except ValueError:
|
|
|
|
return "File too large!"
|
|
|
|
|
|
|
|
if 'content' in x:
|
|
|
|
# open a temporary file
|
|
|
|
tempFileName = "/tmp/pastebin_" + web.ctx.ip + "_" + str(random())
|
|
|
|
tempFile = open(tempFileName, "wb")
|
|
|
|
|
|
|
|
# calculate md5 hash of the uploaded data while saving it to the
|
|
|
|
# temporary file
|
|
|
|
m = hashlib.md5()
|
|
|
|
for line in x['content'].file:
|
|
|
|
m.update(line)
|
|
|
|
tempFile.write(line)
|
|
|
|
|
|
|
|
tempFile.close()
|
|
|
|
|
|
|
|
# generate the paste's id
|
2020-10-07 21:42:20 +02:00
|
|
|
pasteid = urlsafe_b64encode(m.digest()[0:9]).decode('ascii')
|
|
|
|
storeName = os.path.join(CONF_DATA_DIR, pasteid)
|
2020-06-29 21:35:54 +02:00
|
|
|
|
2020-10-07 22:11:32 +02:00
|
|
|
basename = os.path.basename(x['content'].filename)
|
|
|
|
pos = basename.find('.')
|
|
|
|
if pos >= 0:
|
|
|
|
basename = basename[:pos]
|
|
|
|
|
2020-06-29 21:35:54 +02:00
|
|
|
# get the mime type and extension from the data
|
|
|
|
mimeType, ext = self.detect_file_type(tempFileName)
|
|
|
|
if not ext:
|
|
|
|
ext = ""
|
|
|
|
|
|
|
|
if not os.path.exists(storeName):
|
|
|
|
# this file does not yet exist
|
|
|
|
|
|
|
|
# create the file's database entry
|
|
|
|
self.db.insert(CONF_SQL_FILESTABLE,
|
|
|
|
hash = pasteid,
|
|
|
|
detected_type = mimeType,
|
|
|
|
create_time = time.time(),
|
|
|
|
access_time = time.time())
|
|
|
|
|
|
|
|
# store the document permanently
|
|
|
|
shutil.move(tempFileName, storeName);
|
|
|
|
else:
|
|
|
|
# this file was uploaded before, so we just delete the temporary
|
|
|
|
# file
|
|
|
|
os.unlink(tempFileName);
|
|
|
|
|
|
|
|
# return the generated URL
|
2020-10-07 22:11:32 +02:00
|
|
|
return "http://" + web.ctx.host + "/" + pasteid + "/" + basename + ext + "\n"
|
2020-06-29 21:35:54 +02:00
|
|
|
else:
|
|
|
|
return "Nothing uploaded."
|
|
|
|
|
2020-10-07 22:11:32 +02:00
|
|
|
def GET(self, paste_id = None, reqName=None):
|
|
|
|
if paste_id:
|
|
|
|
if reqName:
|
|
|
|
fileName = reqName
|
2020-06-29 21:35:54 +02:00
|
|
|
else:
|
2020-10-07 22:11:32 +02:00
|
|
|
fileName = paste_id
|
|
|
|
|
|
|
|
dotpos = paste_id.rfind('.')
|
|
|
|
if dotpos >= 0:
|
|
|
|
paste_id = paste_id[:dotpos]
|
2020-06-29 21:35:54 +02:00
|
|
|
|
|
|
|
# set the content type from the user-specified file extension
|
2020-10-07 22:11:32 +02:00
|
|
|
mimeType, encoding = mimetypes.guess_type(fileName);
|
2020-06-29 21:35:54 +02:00
|
|
|
if mimeType:
|
|
|
|
if mimeType[0:4] == "text":
|
|
|
|
mimeType += "; charset=" + CONF_DEFAULT_CHARSET
|
|
|
|
|
|
|
|
web.header("Content-Type", mimeType)
|
|
|
|
if encoding:
|
|
|
|
web.header("Content-Encoding", encoding)
|
|
|
|
else:
|
|
|
|
web.header("Content-Type", CONF_DEFAULT_TYPE)
|
|
|
|
|
2020-10-07 22:11:32 +02:00
|
|
|
storeName = CONF_DATA_DIR + "/" + paste_id
|
2020-06-29 21:35:54 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
f = open(storeName, "rb")
|
|
|
|
for line in f:
|
|
|
|
yield line
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
# update the corresponding access time and count
|
|
|
|
items = self.db.select(CONF_SQL_FILESTABLE,
|
2020-10-07 22:11:32 +02:00
|
|
|
where = "hash=" + web.db.sqlquote(paste_id),
|
2020-06-29 21:35:54 +02:00
|
|
|
what = "access_count")
|
|
|
|
|
|
|
|
if items:
|
|
|
|
oldcount = items[0]['access_count']
|
|
|
|
|
|
|
|
self.db.update(CONF_SQL_FILESTABLE,
|
2020-10-07 22:11:32 +02:00
|
|
|
where = "hash=" + web.db.sqlquote(paste_id),
|
2020-06-29 21:35:54 +02:00
|
|
|
access_time = time.time(),
|
|
|
|
access_count = oldcount + 1)
|
|
|
|
else:
|
|
|
|
# The count could not be read.
|
|
|
|
# Probably there is no entry for this file -> create one
|
|
|
|
self.db.insert(CONF_SQL_FILESTABLE,
|
2020-10-07 22:11:32 +02:00
|
|
|
hash = paste_id,
|
2020-06-29 21:35:54 +02:00
|
|
|
detected_type = mimeType,
|
|
|
|
create_time = os.path.getmtime(storeName),
|
|
|
|
access_time = time.time(),
|
|
|
|
access_count = 1)
|
|
|
|
except IOError:
|
2020-10-07 22:11:32 +02:00
|
|
|
yield "Cannot read \"" + paste_id + "\"."
|
2020-06-29 21:35:54 +02:00
|
|
|
|
|
|
|
else:
|
|
|
|
raise web.seeother("/")
|
|
|
|
|
|
|
|
class FileInfo:
|
|
|
|
def __init__(self):
|
|
|
|
self.db = web.database(
|
|
|
|
dbn = CONF_SQL_DRIVER,
|
|
|
|
db = CONF_SQL_DATABASE,
|
|
|
|
user = CONF_SQL_USER,
|
|
|
|
pw = CONF_SQL_PASSWORD)
|
|
|
|
|
|
|
|
def GET(self, doc = None):
|
|
|
|
web.header("Content-Type", 'text/plain')
|
|
|
|
|
|
|
|
# generate a list of all files
|
|
|
|
items = self.db.select(CONF_SQL_FILESTABLE,
|
|
|
|
order = "access_time DESC")
|
|
|
|
|
|
|
|
if not items:
|
|
|
|
return "There are no files in the database."
|
|
|
|
else:
|
|
|
|
# table header
|
|
|
|
outbuf = StringIO()
|
|
|
|
outbuf.write("%-12s | %-20s | %-8s | %-25s | %-16s | %-10s |\n" % (
|
|
|
|
"Hash", "Detected Type", "Size", "Created", "Last Access", "# Accessed"))
|
|
|
|
outbuf.write("-"*12 + "-+-" + "-"*20 + "-+-" + "-"*8 + "-+-" + "-"*25
|
|
|
|
+ "-+-" + "-"*16 + "-+-" + "-"*10 + "-|\n")
|
|
|
|
|
|
|
|
now = time.time()
|
|
|
|
|
|
|
|
for i in items:
|
|
|
|
# determine file size (and existance)
|
|
|
|
storeName = CONF_DATA_DIR + "/" + i["hash"]
|
|
|
|
try:
|
|
|
|
fileSize = os.path.getsize(storeName)
|
|
|
|
sizeStr = utils.readable_size(fileSize)
|
|
|
|
except OSError:
|
|
|
|
sizeStr = "N/A"
|
|
|
|
|
|
|
|
lastacc_delay = (now - i['access_time'])
|
|
|
|
lastacc_days = lastacc_delay / 86400
|
|
|
|
lastacc_hours = (lastacc_delay / 3600) % 24
|
|
|
|
lastacc_minutes = (lastacc_delay / 60) % 60
|
|
|
|
|
|
|
|
outbuf.write("%12s | %-20s | %8s | %25s | %16s | %10u |\n" % (
|
|
|
|
i["hash"],
|
|
|
|
i["detected_type"],
|
|
|
|
sizeStr,
|
|
|
|
time.strftime(CONF_TIME_FORMAT, time.localtime(i['create_time'])),
|
|
|
|
"%ud, %2u:%02u ago" % (lastacc_days, lastacc_hours, lastacc_minutes),
|
|
|
|
i['access_count']))
|
|
|
|
return outbuf.getvalue()
|
|
|
|
|
|
|
|
class Cleanup:
|
|
|
|
def __init__(self):
|
|
|
|
self.db = web.database(
|
|
|
|
dbn = CONF_SQL_DRIVER,
|
|
|
|
db = CONF_SQL_DATABASE,
|
|
|
|
user = CONF_SQL_USER,
|
|
|
|
pw = CONF_SQL_PASSWORD)
|
|
|
|
|
|
|
|
def GET(self):
|
|
|
|
web.header("Content-Type", 'text/html')
|
|
|
|
|
|
|
|
getdata = web.input(doit = False)
|
|
|
|
|
|
|
|
if not getdata.doit:
|
|
|
|
return render.cleanup(stage = 1, result = None)
|
|
|
|
else:
|
|
|
|
# generate a list of all files
|
|
|
|
items = self.db.select(CONF_SQL_FILESTABLE,
|
|
|
|
order = "access_time DESC")
|
|
|
|
|
|
|
|
if not items:
|
|
|
|
return render.cleanup(stage = 2, result = "There are no files in the database.")
|
|
|
|
else:
|
|
|
|
deleted_entries = 0
|
|
|
|
for i in items:
|
|
|
|
# determine file existance
|
|
|
|
storeName = CONF_DATA_DIR + "/" + i["hash"]
|
|
|
|
if not os.path.exists(storeName):
|
|
|
|
# this entry has no corresponding file -> delete it
|
|
|
|
deleted_entries += self.db.delete(
|
|
|
|
CONF_SQL_FILESTABLE,
|
|
|
|
where = "hash=" + web.db.sqlquote(i["hash"]))
|
|
|
|
|
|
|
|
return render.cleanup(
|
|
|
|
stage = 2,
|
|
|
|
result = "Cleanup successful, " + str(deleted_entries) + " entries deleted.")
|
|
|
|
|
|
|
|
|
|
|
|
app = web.application(urls, globals())
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
app.run()
|
|
|
|
|
|
|
|
application = app.wsgifunc()
|