Skip to content

Instantly share code, notes, and snippets.

@twilight-sparkle-irl
Created February 14, 2020 21:01
Show Gist options
  • Save twilight-sparkle-irl/8a2068d237406629e34cf57afafb647f to your computer and use it in GitHub Desktop.
Save twilight-sparkle-irl/8a2068d237406629e34cf57afafb647f to your computer and use it in GitHub Desktop.
Simple upload/download Flask thing, for when you don't want to set up an FTP server and just want to transfer files back-and-forth real quick.
#!/usr/bin/python3
# THIS IS NOT MEANT TO BE HOSTED FOR PUBLIC USE.
# THIS IS A TESTING TOOL.
# BY DEFAULT, DO NOT USE THIS FOR ANY PURPOSES.
###### REVIEW BOTTOM OF FILE FOR LICENSE! ######
# This file is standalone and only requires Flask be installed.
# Simply run it and it should work out of the box.
# It will create an uploads folder in the current directory by default, but that can be changed in UPLOAD_FOLDER.
import os, secrets
from functools import wraps
from flask import Flask, flash, request, redirect, url_for, send_from_directory, render_template_string
from werkzeug.utils import secure_filename
UPLOAD_FOLDER = './uploads/'
AUTH = [] # it is recommended to let the program automatically generate a safe username + password
ONLY_RESTRICT_UP = False # it is not recommended to change this.
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
if not AUTH or len(AUTH) != 2:
app.config['AUTH_USERNAME'] = secrets.token_urlsafe(secrets.randbelow(4)+8)
app.config['AUTH_PASSWORD'] = secrets.token_urlsafe(secrets.randbelow(16)+16)
else:
app.config['AUTH_USERNAME'] = AUTH[0]
app.config['AUTH_PASSWORD'] = AUTH[1]
print(f"Username is {app.config['AUTH_USERNAME']}{chr(0x0a)}Password is {app.config['AUTH_PASSWORD']}")
app.config['ONLY_RESTRICT_UP'] = ONLY_RESTRICT_UP
style="""
<style>body{font-family:sans-serif;margin:40px auto;max-width:650px;line-height:1.6;font-size:18px;color:#444;padding:0 10px}a{text-decoration:none}h1,h4{line-height:1}img{image-rendering:crisp-edges;image-rendering:pixelated;width:11px;}center>img{width:88px}ul{list-style-image:url(https://www.w3.org/icons/generic.gif)}:target{background-color:rgba(255,255,0,0.3);font-weight:bold;};}</style>
"""
dirtree_template = """<!doctype html><title>simple updown - down</title>"""+style+"""
<center><img src="https://www.w3.org/icons/folder.gif"><h4><a href="/up">[ Upload File ]</a></h4></center><hr />
<ul>
{%- for item in tree.children %}
<li id="{{ item.name }}"><a href="/uploads/{{ item.name }}">{{ item.name }}</a> <a href="/uploads/{{ item.name }}" download><img src="https://www.w3.org/icons/down.gif" alt="download" title="download"></a></li>
{%- endfor %}
</ul>"""
upload_template = """<!doctype html><title>simple updown - up</title>"""+style+"""
<center><img src="https://www.w3.org/icons/folder.open.gif"><hr/>
<form method=post enctype=multipart/form-data><input type=file name=file><input type=submit value=Upload></form>
</center>"""
def make_tree(path):
tree = dict(name=os.path.basename(path), children=[])
try: lst = os.listdir(path)
except OSError:
pass #ignore errors
else:
for name in lst:
fn = os.path.join(path, name)
if os.path.isdir(fn):
tree['children'].append(make_tree(fn))
else:
tree['children'].append(dict(name=name))
return tree
def login_required(f):
@wraps(f)
def wrapped_view(**kwargs):
auth = request.authorization
if not (auth and auth.username == app.config['AUTH_USERNAME'] and auth.password == app.config['AUTH_PASSWORD']):
return ('Unauthorized', 401, {
'WWW-Authenticate': 'Basic realm="Login required."'
})
return f(**kwargs)
return wrapped_view
def login_required_maybe(f):
@wraps(f)
def wrapped_view(**kwargs):
auth = request.authorization
if not app.config['ONLY_RESTRICT_UP'] and not (auth and auth.username == app.config['AUTH_USERNAME'] and auth.password == app.config['AUTH_PASSWORD']):
return ('Unauthorized', 401, {
'WWW-Authenticate': 'Basic realm="Login required."'
})
return f(**kwargs)
return wrapped_view
@app.route('/')
@login_required_maybe
def index():
return render_template_string(dirtree_template, tree=make_tree(app.config['UPLOAD_FOLDER']))
@app.route('/up', methods=['GET', 'POST'])
@login_required
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file:
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect('/#'+filename)
return upload_template
@app.route('/uploads/<filename>')
@login_required_maybe
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
if __name__=="__main__":
if not os.path.isdir(app.config['UPLOAD_FOLDER']):
os.mkdir(app.config['UPLOAD_FOLDER'])
app.run(host='0.0.0.0', port=39270)
# This software is licensed under the following license.
#
# PAULA BEAN OPEN SOURCE LICENSE
# Version 2, Feburary 2020
#
# Copyright (C) 2020 Starlight Glimmer
#
# Everyone is permitted to copy and distribute verbatim or modified
# copies of this license document and the corresponding source code
# or whatever under the following conditions.
#
# PAULA BEAN OPEN SOURCE LICENSE
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
# 0. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# 1. Brillant
#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment