Initial commit: the Python 2.7 version based on web.py
This commit is contained in:
commit
37f600d9c2
19
config.py.template
Normal file
19
config.py.template
Normal file
|
@ -0,0 +1,19 @@
|
|||
import os
|
||||
|
||||
CONF_INSTALL_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
CONF_DATA_DIR = os.path.join(CONF_INSTALL_DIR, "pastes")
|
||||
CONF_TEMPLATE_DIR = os.path.join(CONF_INSTALL_DIR, "templates")
|
||||
|
||||
CONF_MAX_UPLOAD_SIZE = 256 * 1024 * 1024
|
||||
|
||||
CONF_DEFAULT_TYPE = "text/plain; charset=utf-8"
|
||||
CONF_DEFAULT_CHARSET = "utf-8"
|
||||
|
||||
CONF_TIME_FORMAT = "%c"
|
||||
|
||||
CONF_SQL_DRIVER = "mysql"
|
||||
CONF_SQL_USER = "pastebin"
|
||||
CONF_SQL_PASSWORD = "T0pS3cret!!1"
|
||||
CONF_SQL_DATABASE = "pastebin"
|
||||
CONF_SQL_FILESTABLE = "pastebin_files"
|
265
main.py
Executable file
265
main.py
Executable file
|
@ -0,0 +1,265 @@
|
|||
#!/usr/bin/env python2
|
||||
# -- 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 StringIO 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'
|
||||
)
|
||||
|
||||
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])
|
||||
storeName = CONF_DATA_DIR + "/" + pasteid
|
||||
|
||||
# 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 + ext + "\n"
|
||||
else:
|
||||
return "Nothing uploaded."
|
||||
|
||||
def GET(self, doc = None):
|
||||
if doc:
|
||||
dotindex = doc.find(".")
|
||||
if dotindex != -1:
|
||||
fileName = doc[:dotindex]
|
||||
fileExt = doc[dotindex+1:]
|
||||
else:
|
||||
fileName = doc
|
||||
fileExt = None
|
||||
|
||||
# set the content type from the user-specified file extension
|
||||
mimeType, encoding = mimetypes.guess_type(doc);
|
||||
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 + "/" + fileName
|
||||
|
||||
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(fileName),
|
||||
what = "access_count")
|
||||
|
||||
if items:
|
||||
oldcount = items[0]['access_count']
|
||||
|
||||
self.db.update(CONF_SQL_FILESTABLE,
|
||||
where = "hash=" + web.db.sqlquote(fileName),
|
||||
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 = fileName,
|
||||
detected_type = mimeType,
|
||||
create_time = os.path.getmtime(storeName),
|
||||
access_time = time.time(),
|
||||
access_count = 1)
|
||||
except IOError:
|
||||
yield "Cannot read \"" + fileName + "\"."
|
||||
|
||||
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()
|
4
paste.wsgi
Normal file
4
paste.wsgi
Normal file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import main
|
||||
application = main.app.wsgifunc()
|
7
setup.sql
Normal file
7
setup.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE files (
|
||||
hash CHAR(12) NOT NULL,
|
||||
detected_type VARCHAR(64) NOT NULL,
|
||||
create_time BIGINT UNSIGNED NOT NULL,
|
||||
access_time BIGINT UNSIGNED NOT NULL,
|
||||
access_count INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(hash));
|
29
templates/cleanup.html
Normal file
29
templates/cleanup.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
$def with (stage, result)
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Pastebin - Cleanup</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
|
||||
</head>
|
||||
<body>
|
||||
<h1>Pastebin - Cleanup</h1>
|
||||
$if stage == 1:
|
||||
<p>
|
||||
This will delete all database entries of files which do not exist any
|
||||
more.
|
||||
</p>
|
||||
<p>
|
||||
<form action="/cleanup" method="GET">
|
||||
Do you want to clean the database?
|
||||
<input type="hidden" name="doit" value="True">
|
||||
<input type="submit" value="Yes, I want!">
|
||||
</form>
|
||||
</p>
|
||||
$else:
|
||||
<p>
|
||||
Result of cleanup: $result
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
49
templates/index.html
Normal file
49
templates/index.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
$def with (host, maxsize)
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Pastebin</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
|
||||
</head>
|
||||
<body>
|
||||
<h1>Pastebin</h1>
|
||||
<p>
|
||||
Welcome to my Pastebin. You can upload any file here and a short link
|
||||
will be generated that you can send to others.
|
||||
</p>
|
||||
<p>
|
||||
<form action="/p" method="POST" enctype="multipart/form-data">
|
||||
Select a file to upload:
|
||||
<input type="file" name="content">
|
||||
<input type="submit" value="Upload">
|
||||
</form>
|
||||
</p>
|
||||
<hr>
|
||||
<h2>Additional Hints</h2>
|
||||
<p>Here are some hints for features which may not be obvious:</p>
|
||||
<ul>
|
||||
<li>
|
||||
You can use this tool directly from your command line using
|
||||
<a href="http://curl.haxx.se">curl</a>. It’s easiest when you define an
|
||||
alias like this:
|
||||
<pre>alias paste="curl -F 'content=@-' http://$host/p"</pre>
|
||||
|
||||
Then you can do something like
|
||||
<pre>paste < some_file</pre>
|
||||
and the link to your file will be printed directly to your shell.
|
||||
</li>
|
||||
<li>
|
||||
The server will try to detect the type of the uploaded data
|
||||
automatically and generate an appropriate extension for the file. If
|
||||
the server’s guess about the extension is wrong, you can change it.
|
||||
The server will then set a MIME-Type associated with the file
|
||||
extension on retrieval.
|
||||
</li>
|
||||
<li>
|
||||
The maximum upload size is currently set to $maxsize bytes.
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue