Skip to content

Instantly share code, notes, and snippets.

@mckern
Last active June 2, 2016 00:40
Show Gist options
  • Save mckern/57ebaa8b2d649156e8b5e6468ecdd344 to your computer and use it in GitHub Desktop.
Save mckern/57ebaa8b2d649156e8b5e6468ecdd344 to your computer and use it in GitHub Desktop.
Basic upload form, checksums files if they're uploaded, doesn't overwrite
#!/usr/bin/env python
'''
upload.py, a basic CGI scipt to enable uploading a file
onto a web server without direct SSH or filesystem access.
Insecure by design, and in no way intended for public usage.
Integrate it with your favorite internal chat robot, drop
it into a continuous integration pipeline, or just use the
best-of-1994 form interface directly!
Author: Ryan McKern
Last edited: Tue May 31 2016
'''
import cgi
import cgitb
import hashlib
import os
cgitb.enable()
if os.getenv("GROUP_WRITABLE"):
os.umask(0002)
BLOCKSIZE = os.getenv("BLOCKSIZE", 2**20)
UPLOAD_DIR = os.getenv("TARGET_DIRECTORY", "/tmp")
DIGEST_ALGORITHM = os.getenv("HASH_DIGEST", "sha256")
HTML_TEMPLATE = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>File Upload: {ERROR}</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body>
<h1>File Upload</h1>
<form action="{SCRIPT_NAME}" method="POST" enctype="multipart/form-data">
File name: <input name="file" type="file"><br>
<input name="submit" type="submit">
</form>
</body>
</html>"""
HTML_ERROR = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>File Upload: {ERROR}</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body><h1>Error: {ERROR}</h1>
<h1>File Upload</h1>
<form action="{SCRIPT_NAME}" method="POST" enctype="multipart/form-data">
File name: <input name="file" type="file"><br>
<input name="submit" type="submit">
</form>
</body>
</html>"""
def header(value):
'''
print a formatted header with value; ends in a single newline
'''
print "{}".format(value)
def statuscode(value):
'''
print a HTTP Status header with value; ends in two newlines
'''
header("Status: {}\n".format(value))
def content_type(value="text/html"):
'''
print a HTTP Content-type header with value; defaults to "text/html"
'''
header("Content-Type: {}".format(value))
def html(html_string, status_msg="200 OK"):
'''
print html_string as HTML; optionally takes a status_msg,
which defaults to "200 OK"
'''
content_type("text/html")
statuscode(status_msg)
print html_string
def html_values(error_msg=None):
'''
default values to interpolate into rendered HTML. Will take
optional error_msg if something went awry.
'''
return {"SCRIPT_NAME": os.getenv("SCRIPT_NAME", __file__),
"ERROR": error_msg}
def print_html_form(status_msg="200 OK"):
'''
print HTML_TEMPLATE and define a basic form for usage.
Takes an optional status_msg, which defaults to "200 OK".
'''
html(HTML_TEMPLATE.format(**html_values()), status_msg)
raise SystemExit(0)
def print_error_page(msg, status_msg="418 I'm a teapot (RFC 2324)"):
'''
print HTML_ERROR with msg; status_msg is optional, and defaults to
"418 I'm a teapot (RFC 2324)"
'''
html(HTML_ERROR.format(**html_values(msg)), status_msg)
raise SystemExit(0)
def write_file_with_checksum(filename, contents, digest=DIGEST_ALGORITHM):
checksum = hashlib.new(digest)
with open(filename + ".%s" % digest, "w") as cout:
with open(filename, "wb") as fout:
chunk = contents.read(BLOCKSIZE)
if not chunk:
return
checksum.update(chunk)
cout.write(checksum.hexdigest() + '\n')
fout.write(chunk)
def save_uploaded_file(form_field, upload_dir):
form = cgi.FieldStorage()
if form_field not in form:
return
fileitem = form[form_field]
if not fileitem.file:
print_error_page("No file uploaded", "400 Bad Request")
return
fname = os.path.join(upload_dir, fileitem.filename)
if os.path.isfile(fname):
print_error_page("File already exists", "403 Forbidden")
return
write_file_with_checksum(fname, fileitem.file)
save_uploaded_file("file", UPLOAD_DIR)
print_html_form()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment