#!/usr/bin/env python3 # -- 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 from io import StringIO 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', '/([a-zA-Z0-9_.-]*)', 'Pastebin', '/([a-zA-Z0-9_-]+)/(.*)', 'Pastebin' ) 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 pasteid = urlsafe_b64encode(m.digest()[0:9]).decode('ascii') storeName = os.path.join(CONF_DATA_DIR, pasteid) basename = os.path.basename(x['content'].filename) pos = basename.find('.') if pos >= 0: basename = basename[:pos] # 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 return "http://" + web.ctx.host + "/" + pasteid + "/" + basename + ext + "\n" else: return "Nothing uploaded." def GET(self, paste_id = None, reqName=None): if paste_id: if reqName: fileName = reqName else: fileName = paste_id dotpos = paste_id.rfind('.') if dotpos >= 0: paste_id = paste_id[:dotpos] # set the content type from the user-specified file extension mimeType, encoding = mimetypes.guess_type(fileName); 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) storeName = CONF_DATA_DIR + "/" + paste_id 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, where = "hash=" + web.db.sqlquote(paste_id), what = "access_count") if items: oldcount = items[0]['access_count'] self.db.update(CONF_SQL_FILESTABLE, where = "hash=" + web.db.sqlquote(paste_id), 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, hash = paste_id, detected_type = mimeType, create_time = os.path.getmtime(storeName), access_time = time.time(), access_count = 1) except IOError: yield "Cannot read \"" + paste_id + "\"." 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()